Merge branch '21678-installer-diagnostics-internal'. Closes #21678 main
authorLucas Di Pentima <lucas.dipentima@curii.com>
Mon, 13 May 2024 19:48:05 +0000 (16:48 -0300)
committerLucas Di Pentima <lucas.dipentima@curii.com>
Mon, 13 May 2024 19:48:05 +0000 (16:48 -0300)
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima@curii.com>

570 files changed:
.github/workflows/tests.yml [new file with mode: 0644]
.gitignore
.licenseignore
build/README
build/package-build-dockerfiles/Makefile
build/package-build-dockerfiles/debian11/Dockerfile
build/package-build-dockerfiles/debian12/Dockerfile
build/package-build-dockerfiles/rocky8/Dockerfile
build/package-build-dockerfiles/ubuntu2004/Dockerfile
build/package-build-dockerfiles/ubuntu2204/Dockerfile
build/package-test-dockerfiles/.gitignore
build/package-test-dockerfiles/Makefile [deleted file]
build/package-test-dockerfiles/debian11/Dockerfile
build/package-test-dockerfiles/debian12/Dockerfile
build/package-test-dockerfiles/rocky8/Dockerfile
build/package-test-dockerfiles/ubuntu2004/Dockerfile
build/package-test-dockerfiles/ubuntu2204/Dockerfile
build/package-testing/common-test-rails-server-package.sh
build/package-testing/rpm-common-test-packages.sh
build/pypkg_info.py [new file with mode: 0644]
build/rails-package-scripts/README.md
build/rails-package-scripts/arvados-api-server.sh
build/rails-package-scripts/postinst.sh
build/rails-package-scripts/step2.sh
build/run-build-packages-one-target.sh
build/run-build-packages-python-and-ruby.sh
build/run-build-packages.sh
build/run-library.sh
build/run-tests.sh
cmd/arvados-client/cmd.go
cmd/arvados-server/arvados-git-httpd.service [deleted file]
cmd/arvados-server/cmd.go
doc/_config.yml
doc/_includes/_container_glob_patterns.liquid [new file with mode: 0644]
doc/_includes/_install_ruby_and_bundler.liquid
doc/_includes/_mount_types.liquid
doc/_includes/_ssh_intro.liquid
doc/_includes/_tutorial_git_repo_expectations.liquid [deleted file]
doc/admin/config-urls.html.textile.liquid
doc/admin/inspect.html.textile.liquid
doc/admin/management-token.html.textile.liquid
doc/admin/metrics.html.textile.liquid
doc/admin/upgrading.html.textile.liquid
doc/admin/user-management-cli.html.textile.liquid
doc/admin/user-management.html.textile.liquid
doc/api/crunch-scripts.html.textile.liquid [deleted file]
doc/api/methods/container_requests.html.textile.liquid
doc/api/methods/containers.html.textile.liquid
doc/api/methods/humans.html.textile.liquid [deleted file]
doc/api/methods/job_tasks.html.textile.liquid [deleted file]
doc/api/methods/jobs.html.textile.liquid [deleted file]
doc/api/methods/keep_disks.html.textile.liquid [deleted file]
doc/api/methods/nodes.html.textile.liquid [deleted file]
doc/api/methods/pipeline_instances.html.textile.liquid [deleted file]
doc/api/methods/pipeline_templates.html.textile.liquid [deleted file]
doc/api/methods/repositories.html.textile.liquid [deleted file]
doc/api/methods/specimens.html.textile.liquid [deleted file]
doc/api/methods/traits.html.textile.liquid [deleted file]
doc/architecture/index.html.textile.liquid
doc/images/add-new-repository.png [deleted file]
doc/install/arvbox.html.textile.liquid
doc/install/install-api-server.html.textile.liquid
doc/install/install-arv-git-httpd.html.textile.liquid [deleted file]
doc/install/install-composer.html.textile.liquid [deleted file]
doc/install/install-jobs-image.html.textile.liquid [deleted file]
doc/install/install-manual-prerequisites.html.textile.liquid
doc/install/install-shell-server.html.textile.liquid
doc/install/salt-multi-host.html.textile.liquid
doc/sdk/cli/index.html.textile.liquid
doc/user/tutorials/add-new-repository.html.textile.liquid [deleted file]
doc/user/tutorials/git-arvados-guide.html.textile.liquid [deleted file]
go.mod
go.sum
lib/boot/nginx.go
lib/boot/passenger.go
lib/boot/supervisor.go
lib/config/config.default.yml
lib/config/deprecated.go
lib/config/deprecated_test.go
lib/config/export.go
lib/config/load.go
lib/controller/federation/conn.go
lib/controller/federation/generate.go
lib/controller/federation/generated.go
lib/controller/handler_test.go
lib/controller/integration_test.go
lib/controller/router/response.go
lib/controller/router/router.go
lib/controller/rpc/conn.go
lib/controller/rpc/conn_test.go
lib/crunchrun/copier.go
lib/crunchrun/copier_test.go
lib/crunchrun/crunchrun.go
lib/crunchrun/crunchrun_test.go
lib/crunchrun/git_mount.go [deleted file]
lib/crunchrun/git_mount_test.go [deleted file]
lib/crunchrun/integration_test.go
lib/crunchrun/logging_test.go
lib/diagnostics/cmd.go
lib/diagnostics/docker_image_test.go [new file with mode: 0644]
lib/install/deps.go
lib/install/init.go
lib/service/cmd.go
sdk/cwl/arvados_cwl/__init__.py
sdk/cwl/arvados_cwl/arvcontainer.py
sdk/cwl/arvados_cwl/arvworkflow.py
sdk/cwl/arvados_cwl/done.py
sdk/cwl/arvados_cwl/executor.py
sdk/cwl/arvados_cwl/fsaccess.py
sdk/cwl/arvados_cwl/pathmapper.py
sdk/cwl/arvados_cwl/perf.py
sdk/cwl/arvados_cwl/runner.py
sdk/cwl/arvados_version.py
sdk/cwl/pytest.ini [new symlink]
sdk/cwl/setup.py
sdk/cwl/tests/federation/framework/check_exist.py
sdk/cwl/tests/federation/framework/prepare.py
sdk/cwl/tests/hw.py
sdk/cwl/tests/matcher.py
sdk/cwl/tests/test_container.py
sdk/cwl/tests/test_copy_deps.py
sdk/cwl/tests/test_fsaccess.py
sdk/cwl/tests/test_make_output.py
sdk/cwl/tests/test_pathmapper.py
sdk/cwl/tests/test_set_output_prop.py
sdk/cwl/tests/test_submit.py
sdk/cwl/tests/test_tq.py
sdk/cwl/tests/test_urljoin.py
sdk/cwl/tests/test_util.py
sdk/cwl/tests/wf/check_mem.py
sdk/go/arvados/api.go
sdk/go/arvados/collection.go
sdk/go/arvados/config.go
sdk/go/arvados/container.go
sdk/go/arvados/fs_collection.go
sdk/go/arvados/fs_collection_test.go
sdk/go/arvados/specimen.go [deleted file]
sdk/go/arvadosclient/pool.go
sdk/go/arvadostest/api.go
sdk/go/arvadostest/fixtures.go
sdk/go/health/aggregator_test.go
sdk/go/keepclient/keepclient.go
sdk/python/arvados-v1-discovery.json
sdk/python/arvados/_normalize_stream.py
sdk/python/arvados/_ranges.py
sdk/python/arvados/arvfile.py
sdk/python/arvados/cache.py
sdk/python/arvados/collection.py
sdk/python/arvados/commands/arv_copy.py
sdk/python/arvados/commands/ls.py
sdk/python/arvados/commands/migrate19.py
sdk/python/arvados/commands/put.py
sdk/python/arvados/commands/run.py
sdk/python/arvados/commands/ws.py
sdk/python/arvados/crunch.py
sdk/python/arvados/diskcache.py
sdk/python/arvados/keep.py
sdk/python/arvados/stream.py
sdk/python/arvados/timer.py
sdk/python/arvados_version.py
sdk/python/pytest.ini [new file with mode: 0644]
sdk/python/setup.py
sdk/python/tests/arvados_testutil.py
sdk/python/tests/keepstub.py
sdk/python/tests/manifest_examples.py
sdk/python/tests/nginx.conf
sdk/python/tests/performance/test_a_sample.py
sdk/python/tests/run_test_server.py
sdk/python/tests/slow_test.py [deleted file]
sdk/python/tests/test_api.py
sdk/python/tests/test_arv_copy.py
sdk/python/tests/test_arv_get.py
sdk/python/tests/test_arv_keepdocker.py
sdk/python/tests/test_arv_ls.py
sdk/python/tests/test_arv_put.py
sdk/python/tests/test_arv_ws.py
sdk/python/tests/test_arvfile.py
sdk/python/tests/test_benchmark_collections.py
sdk/python/tests/test_cache.py
sdk/python/tests/test_collections.py
sdk/python/tests/test_errors.py
sdk/python/tests/test_events.py
sdk/python/tests/test_http.py
sdk/python/tests/test_keep_client.py
sdk/python/tests/test_keep_locator.py
sdk/python/tests/test_retry.py
sdk/python/tests/test_retry_job_helpers.py
sdk/python/tests/test_sdk.py
sdk/python/tests/test_storage_classes.py [new file with mode: 0644]
sdk/python/tests/test_stream.py
sdk/python/tests/test_vocabulary.py
services/api/Gemfile.lock
services/api/app/assets/stylesheets/api_client_authorizations.css.scss [deleted file]
services/api/app/assets/stylesheets/api_clients.css.scss [deleted file]
services/api/app/assets/stylesheets/authorized_keys.css.scss [deleted file]
services/api/app/assets/stylesheets/collections.css.scss [deleted file]
services/api/app/assets/stylesheets/commit_ancestors.css.scss [deleted file]
services/api/app/assets/stylesheets/commits.css.scss [deleted file]
services/api/app/assets/stylesheets/groups.css.scss [deleted file]
services/api/app/assets/stylesheets/humans.css.scss [deleted file]
services/api/app/assets/stylesheets/job_tasks.css.scss [deleted file]
services/api/app/assets/stylesheets/jobs.css.scss [deleted file]
services/api/app/assets/stylesheets/keep_disks.css.scss [deleted file]
services/api/app/assets/stylesheets/links.css.scss [deleted file]
services/api/app/assets/stylesheets/logs.css.scss [deleted file]
services/api/app/assets/stylesheets/nodes.css [deleted file]
services/api/app/assets/stylesheets/nodes.css.scss [deleted file]
services/api/app/assets/stylesheets/pipeline_instances.css.scss [deleted file]
services/api/app/assets/stylesheets/pipeline_templates.css.scss [deleted file]
services/api/app/assets/stylesheets/repositories.css.scss [deleted file]
services/api/app/assets/stylesheets/specimens.css.scss [deleted file]
services/api/app/assets/stylesheets/traits.css.scss [deleted file]
services/api/app/assets/stylesheets/virtual_machines.css.scss [deleted file]
services/api/app/controllers/arvados/v1/collections_controller.rb
services/api/app/controllers/arvados/v1/groups_controller.rb
services/api/app/controllers/arvados/v1/humans_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/job_tasks_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/jobs_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/keep_disks_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/nodes_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/pipeline_instances_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/pipeline_templates_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/repositories_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/schema_controller.rb
services/api/app/controllers/arvados/v1/specimens_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/traits_controller.rb [deleted file]
services/api/app/controllers/arvados/v1/users_controller.rb
services/api/app/helpers/api_client_authorizations_helper.rb [deleted file]
services/api/app/helpers/api_clients_helper.rb [deleted file]
services/api/app/helpers/authorized_keys_helper.rb [deleted file]
services/api/app/helpers/collections_helper.rb [deleted file]
services/api/app/helpers/commits_helper.rb [deleted file]
services/api/app/helpers/groups_helper.rb [deleted file]
services/api/app/helpers/humans_helper.rb [deleted file]
services/api/app/helpers/job_tasks_helper.rb [deleted file]
services/api/app/helpers/jobs_helper.rb [deleted file]
services/api/app/helpers/keep_disks_helper.rb [deleted file]
services/api/app/helpers/links_helper.rb [deleted file]
services/api/app/helpers/logs_helper.rb [deleted file]
services/api/app/helpers/nodes_helper.rb [deleted file]
services/api/app/helpers/pipeline_instances_helper.rb [deleted file]
services/api/app/helpers/pipeline_templates_helper.rb [deleted file]
services/api/app/helpers/repositories_helper.rb [deleted file]
services/api/app/helpers/specimens_helper.rb [deleted file]
services/api/app/helpers/traits_helper.rb [deleted file]
services/api/app/helpers/virtual_machines_helper.rb [deleted file]
services/api/app/models/api_client_authorization.rb
services/api/app/models/container.rb
services/api/app/models/container_request.rb
services/api/app/models/human.rb [deleted file]
services/api/app/models/job.rb [deleted file]
services/api/app/models/job_task.rb [deleted file]
services/api/app/models/keep_disk.rb [deleted file]
services/api/app/models/node.rb [deleted file]
services/api/app/models/pipeline_instance.rb [deleted file]
services/api/app/models/pipeline_template.rb [deleted file]
services/api/app/models/repository.rb [deleted file]
services/api/app/models/specimen.rb [deleted file]
services/api/app/models/trait.rb [deleted file]
services/api/app/models/user.rb
services/api/config/arvados_config.rb
services/api/config/routes.rb
services/api/db/migrate/20240329173437_add_output_glob_to_containers.rb [new file with mode: 0644]
services/api/db/migrate/20240402162733_add_output_glob_index_to_containers.rb [new file with mode: 0644]
services/api/db/structure.sql
services/api/lib/can_be_an_owner.rb
services/api/script/arvados-git-sync.rb [deleted file]
services/api/script/migrate-gitolite-to-uuid-storage.rb [deleted file]
services/api/test/fixtures/api_client_authorizations.yml
services/api/test/fixtures/collections.yml
services/api/test/fixtures/container_requests.yml
services/api/test/fixtures/containers.yml
services/api/test/fixtures/humans.yml [deleted file]
services/api/test/fixtures/job_tasks.yml [deleted file]
services/api/test/fixtures/jobs.yml [deleted file]
services/api/test/fixtures/keep_disks.yml [deleted file]
services/api/test/fixtures/links.yml
services/api/test/fixtures/logs.yml
services/api/test/fixtures/nodes.yml [deleted file]
services/api/test/fixtures/pipeline_instances.yml [deleted file]
services/api/test/fixtures/pipeline_templates.yml [deleted file]
services/api/test/fixtures/repositories.yml [deleted file]
services/api/test/fixtures/specimens.yml [deleted file]
services/api/test/fixtures/traits.yml [deleted file]
services/api/test/functional/application_controller_test.rb
services/api/test/functional/arvados/v1/collections_controller_test.rb
services/api/test/functional/arvados/v1/commits_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/groups_controller_test.rb
services/api/test/functional/arvados/v1/humans_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/job_reuse_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/job_tasks_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/jobs_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/keep_disks_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/nodes_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/pipeline_instances_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/pipeline_templates_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/repositories_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/schema_controller_test.rb
services/api/test/functional/arvados/v1/specimens_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/traits_controller_test.rb [deleted file]
services/api/test/functional/arvados/v1/users_controller_test.rb
services/api/test/functional/database_controller_test.rb
services/api/test/functional/sys_controller_test.rb
services/api/test/helpers/git_test_helper.rb [deleted file]
services/api/test/helpers/users_test_helper.rb
services/api/test/integration/api_client_authorizations_scopes_test.rb
services/api/test/integration/bundler_version_test.rb [new file with mode: 0644]
services/api/test/integration/database_reset_test.rb
services/api/test/integration/groups_test.rb
services/api/test/integration/jobs_api_test.rb [deleted file]
services/api/test/integration/login_workflow_test.rb
services/api/test/integration/permissions_test.rb
services/api/test/integration/reader_tokens_test.rb
services/api/test/integration/remote_user_test.rb
services/api/test/integration/serialized_encoding_test.rb
services/api/test/integration/user_sessions_test.rb
services/api/test/integration/users_test.rb
services/api/test/test.git.tar [deleted file]
services/api/test/unit/arvados_model_test.rb
services/api/test/unit/commit_ancestor_test.rb [deleted file]
services/api/test/unit/commit_test.rb [deleted file]
services/api/test/unit/container_request_test.rb
services/api/test/unit/container_test.rb
services/api/test/unit/group_test.rb
services/api/test/unit/helpers/api_client_authorizations_helper_test.rb [deleted file]
services/api/test/unit/helpers/api_clients_helper_test.rb [deleted file]
services/api/test/unit/helpers/authorized_keys_helper_test.rb [deleted file]
services/api/test/unit/helpers/collections_helper_test.rb [deleted file]
services/api/test/unit/helpers/commit_ancestors_helper_test.rb [deleted file]
services/api/test/unit/helpers/commits_helper_test.rb [deleted file]
services/api/test/unit/helpers/groups_helper_test.rb [deleted file]
services/api/test/unit/helpers/humans_helper_test.rb [deleted file]
services/api/test/unit/helpers/job_tasks_helper_test.rb [deleted file]
services/api/test/unit/helpers/jobs_helper_test.rb [deleted file]
services/api/test/unit/helpers/keep_disks_helper_test.rb [deleted file]
services/api/test/unit/helpers/links_helper_test.rb [deleted file]
services/api/test/unit/helpers/logs_helper_test.rb [deleted file]
services/api/test/unit/helpers/nodes_helper_test.rb [deleted file]
services/api/test/unit/helpers/pipeline_instances_helper_test.rb [deleted file]
services/api/test/unit/helpers/pipeline_templates_helper_test.rb [deleted file]
services/api/test/unit/helpers/repositories_helper_test.rb [deleted file]
services/api/test/unit/helpers/specimens_helper_test.rb [deleted file]
services/api/test/unit/helpers/traits_helper_test.rb [deleted file]
services/api/test/unit/helpers/virtual_machines_helper_test.rb [deleted file]
services/api/test/unit/human_test.rb [deleted file]
services/api/test/unit/job_task_test.rb [deleted file]
services/api/test/unit/job_test.rb [deleted file]
services/api/test/unit/link_test.rb
services/api/test/unit/log_test.rb
services/api/test/unit/owner_test.rb
services/api/test/unit/permission_test.rb
services/api/test/unit/pipeline_instance_test.rb [deleted file]
services/api/test/unit/pipeline_template_test.rb [deleted file]
services/api/test/unit/repository_test.rb [deleted file]
services/api/test/unit/specimen_test.rb [deleted file]
services/api/test/unit/trait_test.rb [deleted file]
services/api/test/unit/user_test.rb
services/dockercleaner/arvados_version.py
services/dockercleaner/bin/arvados-docker-cleaner
services/dockercleaner/pytest.ini [new symlink]
services/dockercleaner/setup.py
services/fuse/arvados_fuse/__init__.py
services/fuse/arvados_fuse/command.py
services/fuse/arvados_fuse/crunchstat.py
services/fuse/arvados_fuse/fresh.py
services/fuse/arvados_fuse/fusedir.py
services/fuse/arvados_fuse/fusefile.py
services/fuse/arvados_version.py
services/fuse/pytest.ini [new symlink]
services/fuse/setup.py
services/fuse/tests/fstest.py
services/fuse/tests/integration_test.py
services/fuse/tests/mount_test_base.py
services/fuse/tests/performance/__init__.py [deleted file]
services/fuse/tests/performance/performance_profiler.py [deleted symlink]
services/fuse/tests/performance/test_collection_performance.py [deleted file]
services/fuse/tests/prof.py
services/fuse/tests/slow_test.py [deleted symlink]
services/fuse/tests/test_cache.py
services/fuse/tests/test_command_args.py
services/fuse/tests/test_crunchstat.py
services/fuse/tests/test_exec.py
services/fuse/tests/test_inodes.py
services/fuse/tests/test_mount.py
services/fuse/tests/test_retry.py
services/fuse/tests/test_tmp_collection.py
services/fuse/tests/test_token_expiry.py
services/fuse/tests/test_unmount.py
services/githttpd/auth_handler.go [deleted file]
services/githttpd/auth_handler_test.go [deleted file]
services/githttpd/cmd.go [deleted file]
services/githttpd/git_handler.go [deleted file]
services/githttpd/git_handler_test.go [deleted file]
services/githttpd/gitolite_test.go [deleted file]
services/githttpd/integration_test.go [deleted file]
services/githttpd/server_test.go [deleted file]
services/keep-web/handler.go
services/keep-web/server_test.go
services/keep-web/writebuffer.go [new file with mode: 0644]
services/keep-web/writebuffer_test.go [new file with mode: 0644]
services/keepproxy/keepproxy.go
services/keepproxy/keepproxy_test.go
services/keepstore/router.go
services/keepstore/router_test.go
services/keepstore/s3_volume.go
services/workbench2/.gitignore
services/workbench2/Makefile
services/workbench2/cypress/e2e/banner-tooltip.cy.js
services/workbench2/cypress/e2e/collection.cy.js
services/workbench2/cypress/e2e/process.cy.js
services/workbench2/cypress/e2e/project.cy.js
services/workbench2/cypress/e2e/search.cy.js
services/workbench2/cypress/e2e/sharing.cy.js
services/workbench2/cypress/e2e/user-profile.cy.js
services/workbench2/cypress/e2e/workflow.cy.js
services/workbench2/package.json
services/workbench2/src/components/code-snippet/code-snippet.tsx
services/workbench2/src/components/code-snippet/virtual-code-snippet.tsx [new file with mode: 0644]
services/workbench2/src/components/collection-panel-files/collection-panel-files.tsx
services/workbench2/src/components/conditional-tabs/conditional-tabs.test.tsx [new file with mode: 0644]
services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx [new file with mode: 0644]
services/workbench2/src/components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar.tsx
services/workbench2/src/components/copy-to-clipboard/copy-result-to-clipboard.ts [new file with mode: 0644]
services/workbench2/src/components/data-table-filters/data-table-filters-popover.tsx
services/workbench2/src/components/default-code-snippet/default-virtual-code-snippet.tsx [new file with mode: 0644]
services/workbench2/src/components/details-attribute/details-attribute.tsx
services/workbench2/src/components/icon/icon.tsx
services/workbench2/src/components/multiselect-toolbar/MultiselectToolbar.tsx
services/workbench2/src/components/multiselect-toolbar/ms-toolbar-action-filters.ts
services/workbench2/src/components/multiselect-toolbar/ms-toolbar-overflow-menu.tsx
services/workbench2/src/components/multiselect-toolbar/ms-toolbar-overflow-wrapper.tsx
services/workbench2/src/components/search-input/search-input.test.tsx
services/workbench2/src/components/search-input/search-input.tsx
services/workbench2/src/components/workflow-inputs-form/validators.ts
services/workbench2/src/index.tsx
services/workbench2/src/services/collection-service/collection-service-files-response.test.ts
services/workbench2/src/services/collection-service/collection-service.ts
services/workbench2/src/services/services.ts
services/workbench2/src/store/auth/auth-middleware.test.ts
services/workbench2/src/store/banner/banner-action.ts
services/workbench2/src/store/context-menu/context-menu-actions.test.ts
services/workbench2/src/store/context-menu/context-menu-actions.ts
services/workbench2/src/store/favorites/favorites-actions.ts
services/workbench2/src/store/open-in-new-tab/open-in-new-tab.actions.ts
services/workbench2/src/store/process-panel/process-panel-actions.ts
services/workbench2/src/store/project-panel/project-panel-middleware-service.ts
services/workbench2/src/store/projects/project-lock-actions.ts
services/workbench2/src/store/public-favorites/public-favorites-actions.ts
services/workbench2/src/store/trash-panel/trash-panel-middleware-service.ts
services/workbench2/src/store/trash/trash-actions.ts
services/workbench2/src/validators/require.tsx
services/workbench2/src/validators/validators.tsx
services/workbench2/src/views-components/auto-logout/auto-logout.test.tsx
services/workbench2/src/views-components/collection-panel-files/collection-panel-files.ts
services/workbench2/src/views-components/context-menu/action-sets/api-client-authorization-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/collection-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/collection-files-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/collection-files-not-selected-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/favorite-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/group-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/group-member-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/keep-service-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/link-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/permission-edit-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/process-resource-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/project-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/project-admin-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/repository-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/resource-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/root-project-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/search-results-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/ssh-key-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/trash-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/trashed-collection-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/user-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/virtual-machine-action-set.ts
services/workbench2/src/views-components/context-menu/action-sets/workflow-action-set.ts
services/workbench2/src/views-components/context-menu/actions/collection-copy-to-clipboard-action.tsx
services/workbench2/src/views-components/context-menu/actions/collection-file-viewer-action.test.tsx
services/workbench2/src/views-components/context-menu/actions/collection-file-viewer-action.tsx
services/workbench2/src/views-components/context-menu/actions/context-menu-divider.tsx [new file with mode: 0644]
services/workbench2/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx
services/workbench2/src/views-components/context-menu/actions/download-action.test.tsx
services/workbench2/src/views-components/context-menu/actions/download-collection-file-action.tsx
services/workbench2/src/views-components/context-menu/actions/file-viewer-action.tsx
services/workbench2/src/views-components/context-menu/context-menu-action-set.ts
services/workbench2/src/views-components/context-menu/context-menu.tsx
services/workbench2/src/views-components/context-menu/menu-item-sort.ts [new file with mode: 0644]
services/workbench2/src/views-components/dialog-upload/dialog-collection-files-upload.tsx
services/workbench2/src/views-components/login-form/login-form.tsx
services/workbench2/src/views-components/main-app-bar/help-menu.tsx
services/workbench2/src/views-components/multiselect-toolbar/ms-collection-action-set.ts
services/workbench2/src/views-components/multiselect-toolbar/ms-menu-actions.ts
services/workbench2/src/views-components/multiselect-toolbar/ms-process-action-set.ts
services/workbench2/src/views-components/multiselect-toolbar/ms-project-action-set.ts
services/workbench2/src/views-components/multiselect-toolbar/ms-workflow-action-set.ts
services/workbench2/src/views-components/repositories-sample-git-dialog/repositories-sample-git-dialog.tsx
services/workbench2/src/views-components/sharing-dialog/permission-select.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-dialog-component.test.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-dialog-component.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-dialog.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-invitation-form-component.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-invitation-form.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-management-form-component.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-public-access-form-component.tsx
services/workbench2/src/views-components/sharing-dialog/sharing-urls-component.tsx
services/workbench2/src/views-components/sharing-dialog/visibility-level-select.tsx
services/workbench2/src/views-components/token-dialog/token-dialog.test.tsx
services/workbench2/src/views-components/token-dialog/token-dialog.tsx
services/workbench2/src/views-components/webdav-s3-dialog/webdav-s3-dialog.tsx
services/workbench2/src/views/groups-panel/groups-panel.tsx
services/workbench2/src/views/login-panel/login-panel.tsx
services/workbench2/src/views/process-panel/process-cmd-card.tsx
services/workbench2/src/views/process-panel/process-io-card.test.tsx
services/workbench2/src/views/process-panel/process-io-card.tsx
services/workbench2/src/views/process-panel/process-log-card.tsx
services/workbench2/src/views/process-panel/process-output-collection-files.ts
services/workbench2/src/views/process-panel/process-panel-root.tsx
services/workbench2/src/views/process-panel/process-resource-card.tsx
services/workbench2/src/views/run-process-panel/inputs/enum-input.tsx
services/workbench2/src/views/run-process-panel/inputs/project-input.tsx
services/workbench2/src/views/run-process-panel/inputs/string-input.tsx
services/workbench2/src/views/search-results-panel/search-results-panel-view.tsx
services/workbench2/src/views/trash-panel/trash-panel.tsx
services/workbench2/src/views/virtual-machine-panel/virtual-machine-user-panel.tsx
services/workbench2/src/views/workbench/workbench.tsx
services/workbench2/tsconfig.json
services/workbench2/yarn.lock
services/ws/event_source_test.go
services/ws/event_test.go
tools/arvbox/lib/arvbox/docker/Dockerfile.base
tools/arvbox/lib/arvbox/docker/Dockerfile.demo
tools/arvbox/lib/arvbox/docker/api-setup.sh
tools/arvbox/lib/arvbox/docker/cluster-config.sh
tools/arvbox/lib/arvbox/docker/common.sh
tools/arvbox/lib/arvbox/docker/gitolite.rc [deleted file]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub [deleted file]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run [deleted symlink]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run [deleted symlink]
tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service [deleted file]
tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub [deleted file]
tools/arvbox/lib/arvbox/docker/service/gitolite/log/run [deleted symlink]
tools/arvbox/lib/arvbox/docker/service/gitolite/run [deleted symlink]
tools/arvbox/lib/arvbox/docker/service/gitolite/run-service [deleted file]
tools/arvbox/lib/arvbox/docker/service/nginx/run
tools/arvbox/lib/arvbox/docker/service/vm/run
tools/compute-images/scripts/base.sh
tools/crunchstat-summary/arvados_version.py
tools/crunchstat-summary/bin/crunchstat-summary
tools/crunchstat-summary/pytest.ini [new symlink]
tools/crunchstat-summary/setup.py
tools/crunchstat-summary/tests/test_examples.py
tools/keep-xref/keep-xref.py
tools/salt-install/config_examples/multi_host/aws/pillars/arvados.sls
tools/salt-install/config_examples/multi_host/aws/pillars/logrotate.sls [new file with mode: 0644]
tools/salt-install/config_examples/multi_host/aws/pillars/nginx_api_configuration.sls
tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/arvados.sls
tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/logrotate.sls [new file with mode: 0644]
tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/nginx_api_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/arvados.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/logrotate.sls [new file with mode: 0644]
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_api_configuration.sls
tools/salt-install/config_examples/single_host/single_hostname/pillars/nginx_workbench_configuration.sls
tools/salt-install/installer.sh
tools/salt-install/provision.sh
tools/salt-install/terraform/aws/services/main.tf
tools/user-activity/arvados_version.py
tools/user-activity/pytest.ini [new symlink]
tools/user-activity/setup.py

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644 (file)
index 0000000..f8224e4
--- /dev/null
@@ -0,0 +1,40 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+name: Arvados Tests
+
+on:
+  workflow_dispatch:
+  pull_request:
+    branches:
+      - main
+
+jobs:
+  workbench2:
+    name: Workbench2 Tests
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+      - name: Setup buildx
+        uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
+      - name: Build wb2 test container
+        uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
+        with:
+          context: .
+          file: "services/workbench2/docker/Dockerfile"
+          tags: workbench2-test:latest
+          load: true
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
+          push: false
+      - name: Run wb2 integration tests
+        uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3
+        with:
+          image: workbench2-test:latest
+          options: -v ${{github.workspace}}:/usr/src/arvados -w /usr/src/arvados/services/workbench2
+          run: |
+            yarn install
+            yarn test --no-watchAll --bail --ci || exit $?
+            tools/run-integration-tests.sh -a /usr/src/arvados
index 557386b99cb296db1d960254f6e18b85754e5952..2b98a71967502554ae4f3d8fff328894972ea38f 100644 (file)
@@ -35,3 +35,4 @@ _version.py
 arvados-snakeoil-ca.pem
 .vagrant
 packages
 arvados-snakeoil-ca.pem
 .vagrant
 packages
+.eslintcache
index 1e1c12a53a79a2a46a0865bf863f8933691e3ba6..926aff75a4f543811b3faba7256f0a89dd6a3a4c 100644 (file)
@@ -43,6 +43,7 @@ docker/jobs/1078ECD7.key
 *.min.js
 *.png
 */proc_stat
 *.min.js
 *.png
 */proc_stat
+*/pytest.ini
 */README
 */robots.txt
 */runit-docker/*
 */README
 */robots.txt
 */runit-docker/*
index e6d14cf66404009c8a6ec6f44606fb6e40f5d5d2..219b7791bfdac8fc698c8ccc554fea98d84aab71 100644 (file)
@@ -80,9 +80,6 @@ In order to build packages on a new distribution, you MUST:
 * Add the new `TARGET/generated` rule to the `all` target in
   `package-build-dockerfiles/Makefile`.
 * Write `package-build-dockerfiles/TARGET/Dockerfile`.
 * Add the new `TARGET/generated` rule to the `all` target in
   `package-build-dockerfiles/Makefile`.
 * Write `package-build-dockerfiles/TARGET/Dockerfile`.
-* Add a rule for `TARGET/generated` to `package-test-dockerfiles/Makefile`.
-* Add the new `TARGET/generated` rule to the `all` target in
-  `package-test-dockerfiles/Makefile`.
 * Write `package-test-dockerfiles/TARGET/Dockerfile`.
 * Create `package-testing/test-packages-TARGET.sh`, ideally by making it a
   symlink to `FORMAT-common-test-packages.sh`.
 * Write `package-test-dockerfiles/TARGET/Dockerfile`.
 * Create `package-testing/test-packages-TARGET.sh`, ideally by making it a
   symlink to `FORMAT-common-test-packages.sh`.
index be27fffab75037f4095bd5b17464b603095871db..920d626483d5f68798206025911ed84ece97fa14 100644 (file)
@@ -43,10 +43,7 @@ HOSTTYPE=$(shell echo $${HOSTTYPE})
 GOTARBALL=${GOTARBALL_$(HOSTTYPE)}
 NODETARBALL=${NODETARBALL_$(HOSTTYPE)}
 
 GOTARBALL=${GOTARBALL_$(HOSTTYPE)}
 NODETARBALL=${NODETARBALL_$(HOSTTYPE)}
 
-RVMKEY1=mpapis.asc
-RVMKEY2=pkuczynski.asc
-
-common-generated-all: common-generated/$(GOTARBALL) common-generated/$(NODETARBALL) common-generated/$(RVMKEY1) common-generated/$(RVMKEY2)
+common-generated-all: common-generated/$(GOTARBALL) common-generated/$(NODETARBALL)
 
 common-generated/$(GOTARBALL): common-generated
        wget -cqO common-generated/$(GOTARBALL) https://dl.google.com/go/$(GOTARBALL)
 
 common-generated/$(GOTARBALL): common-generated
        wget -cqO common-generated/$(GOTARBALL) https://dl.google.com/go/$(GOTARBALL)
@@ -54,11 +51,5 @@ common-generated/$(GOTARBALL): common-generated
 common-generated/$(NODETARBALL): common-generated
        wget -cqO common-generated/$(NODETARBALL) https://nodejs.org/dist/v12.22.12/$(NODETARBALL)
 
 common-generated/$(NODETARBALL): common-generated
        wget -cqO common-generated/$(NODETARBALL) https://nodejs.org/dist/v12.22.12/$(NODETARBALL)
 
-common-generated/$(RVMKEY1): common-generated
-       wget -cqO common-generated/$(RVMKEY1) https://rvm.io/mpapis.asc
-
-common-generated/$(RVMKEY2): common-generated
-       wget -cqO common-generated/$(RVMKEY2) https://rvm.io/pkuczynski.asc
-
 common-generated:
        mkdir common-generated
 common-generated:
        mkdir common-generated
index 5ca7e1f2434ad4db50e6aa5b587aea5a93de1b15..c1c9e2d92179f603d13f1728a18ae8a14c2b2623 100644 (file)
@@ -6,7 +6,6 @@ ARG HOSTTYPE
 ARG BRANCH
 ARG GOVERSION
 
 ARG BRANCH
 ARG GOVERSION
 
-## dont use debian:11 here since the word 'bullseye' is used for rvm precompiled binaries
 FROM debian:bullseye as build_x86_64
 # Install go
 ONBUILD ARG BRANCH
 FROM debian:bullseye as build_x86_64
 # Install go
 ONBUILD ARG BRANCH
@@ -46,36 +45,42 @@ ENV DEBIAN_FRONTEND noninteractive
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
-RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python3 libcurl4-gnutls-dev curl git procps libattr1-dev libfuse-dev libgnutls28-dev libpq-dev unzip python3-venv python3-dev libpam-dev equivs
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y \
+    build-essential \
+    curl \
+    equivs \
+    git \
+    libattr1-dev \
+    libcurl4-gnutls-dev \
+    libfuse-dev \
+    libgnutls28-dev \
+    libpam-dev \
+    libpq-dev \
+    pkgconf \
+    procps \
+    python3 \
+    python3-dev \
+    python3-venv \
+    ruby \
+    ruby-dev \
+    unzip
 
 
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
 # fpm depends on dotenv, but version 3.0 of that gem dropped support for
 # Ruby 2.7, so we need to specifically install an older version.
 # fpm depends on dotenv, but version 3.0 of that gem dropped support for
 # Ruby 2.7, so we need to specifically install an older version.
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 2.7 -j $(grep -c processor /proc/cpuinfo) --disable-binary && \
-    /usr/local/rvm/bin/rvm alias create default ruby-2.7 && \
-    echo "gem: --no-document" >> ~/.gemrc && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19 && \
-    /usr/local/rvm/bin/rvm-exec default gem install dotenv --version '~> 2.8' && \
-    /usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.15.1
+RUN echo "gem: --no-document" >> ~/.gemrc && \
+    gem install --conservative --version '~> 2.4.0' bundler && \
+    gem install dotenv --version '~> 2.8' && \
+    gem install fpm --version 1.15.1 && \
+    bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 
 
-RUN /usr/local/rvm/bin/rvm-exec default bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs $(grep -c processor /proc/cpuinfo)"
 
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs $(grep -c processor /proc/cpuinfo)"
 
-# Preseed the go module cache and the ruby gems, using the currently checked
-# out branch of the source tree. This avoids potential compatibility issues
-# between the version of Ruby and certain gems.
+# Preseed the go module cache.
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
-    cd /tmp/arvados/services/api && \
-    /usr/local/rvm/bin/rvm-exec default bundle install && \
-    cd /tmp/arvados && \
     go mod download
 
 ENV WORKSPACE /arvados
     go mod download
 
 ENV WORKSPACE /arvados
-CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "debian11"]
+CMD ["bash", "/jenkins/run-build-packages.sh", "--target", "debian11"]
index fa1d095e79fa74f23c134e9d657056b281543a8f..e25745565e9ba91fc8ccf3fae81b40bc9205e4d3 100644 (file)
@@ -6,7 +6,6 @@ ARG HOSTTYPE
 ARG BRANCH
 ARG GOVERSION
 
 ARG BRANCH
 ARG GOVERSION
 
-## dont use debian:12 here since the word 'bookworm' is used for rvm precompiled binaries
 FROM debian:bookworm as build_x86_64
 ONBUILD ARG BRANCH
 # Install go
 FROM debian:bookworm as build_x86_64
 ONBUILD ARG BRANCH
 # Install go
@@ -44,34 +43,39 @@ ENV DEBIAN_FRONTEND noninteractive
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
-RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python3 libcurl4-gnutls-dev curl git procps libattr1-dev libfuse-dev libgnutls28-dev libpq-dev unzip python3-venv python3-dev libpam-dev equivs
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y \
+    build-essential \
+    curl \
+    equivs \
+    git \
+    libattr1-dev \
+    libcurl4-gnutls-dev \
+    libfuse-dev \
+    libgnutls28-dev \
+    libpam-dev \
+    libpq-dev \
+    pkgconf \
+    procps \
+    python3 \
+    python3-dev \
+    python3-venv \
+    ruby \
+    ruby-dev \
+    unzip
 
 
-# Install RVM
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 3.2.2 -j $(grep -c processor /proc/cpuinfo) --disable-binary && \
-    /usr/local/rvm/bin/rvm alias create default ruby-3.2.2 && \
-    echo "gem: --no-document" >> ~/.gemrc && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19 && \
-    /usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.15.1
+RUN echo "gem: --no-document" >> ~/.gemrc && \
+    gem install --conservative --version '>= 2.4.0' bundler && \
+    gem install fpm --version 1.15.1 && \
+    bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 
 
-RUN /usr/local/rvm/bin/rvm-exec default bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs 8"
 
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs 8"
 
-# Preseed the go module cache and the ruby gems, using the currently checked
-# out branch of the source tree. This avoids potential compatibility issues
-# between the version of Ruby and certain gems.
+# Preseed the go module cache.
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
-    cd /tmp/arvados/services/api && \
-    /usr/local/rvm/bin/rvm-exec default bundle install && \
-    cd /tmp/arvados && \
     go mod download
 
 ENV WORKSPACE /arvados
     go mod download
 
 ENV WORKSPACE /arvados
-CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "debian12"]
+CMD ["bash", "/jenkins/run-build-packages.sh", "--target", "debian12"]
index a1038a9b8860d185db3db08305b32fcd2e8a6a88..1292a618bce7183584f2c638ff8b0dbcd21398bd 100644 (file)
@@ -35,7 +35,8 @@ FROM build_${HOSTTYPE}
 MAINTAINER Arvados Package Maintainers <packaging@arvados.org>
 
 # Install dependencies.
 MAINTAINER Arvados Package Maintainers <packaging@arvados.org>
 
 # Install dependencies.
-RUN microdnf --assumeyes --enablerepo=devel install \
+RUN microdnf --assumeyes module enable ruby:3.1 \
+ && microdnf --assumeyes --enablerepo=devel install \
     automake \
     bison \
     bzip2 \
     automake \
     bison \
     bzip2 \
@@ -53,6 +54,7 @@ RUN microdnf --assumeyes --enablerepo=devel install \
     openssl-devel \
     pam-devel \
     patch \
     openssl-devel \
     pam-devel \
     patch \
+    pkgconf \
     postgresql-devel \
     procps-ng \
     python39 \
     postgresql-devel \
     procps-ng \
     python39 \
@@ -60,6 +62,7 @@ RUN microdnf --assumeyes --enablerepo=devel install \
     readline-devel \
     rpm-build \
     ruby \
     readline-devel \
     rpm-build \
     ruby \
+    ruby-devel \
     sqlite-devel \
     tar \
     unzip \
     sqlite-devel \
     tar \
     unzip \
@@ -68,34 +71,22 @@ RUN microdnf --assumeyes --enablerepo=devel install \
     xz-libs \
     zlib-devel
 
     xz-libs \
     zlib-devel
 
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
 # fpm depends on dotenv, but version 3.0 of that gem dropped support for
 # Ruby 2.7, so we need to specifically install an older version.
 # fpm depends on dotenv, but version 3.0 of that gem dropped support for
 # Ruby 2.7, so we need to specifically install an older version.
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install --disable-binary 2.7 -j $(grep -c processor /proc/cpuinfo) && \
-    /usr/local/rvm/bin/rvm alias create default ruby-2.7 && \
-    echo "gem: --no-document" >> ~/.gemrc && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19 && \
-    /usr/local/rvm/bin/rvm-exec default gem install dotenv --version '~> 2.8' && \
-    /usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.15.1
+RUN echo "gem: --no-document" >> ~/.gemrc && \
+    gem install --conservative --version '>= 2.4.0' bundler && \
+    gem install dotenv --version '~> 2.8' && \
+    gem install fpm --version 1.15.1 && \
+    bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 
 
-RUN /usr/local/rvm/bin/rvm-exec default bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs $(grep -c processor /proc/cpuinfo)"
 
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs $(grep -c processor /proc/cpuinfo)"
 
-# Preseed the go module cache and the ruby gems, using the currently checked
-# out branch of the source tree. This avoids potential compatibility issues
-# between the version of Ruby and certain gems.
+# Preseed the go module cache.
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
-    cd /tmp/arvados/services/api && \
-    /usr/local/rvm/bin/rvm-exec default bundle install && \
-    cd /tmp/arvados && \
     go mod download
 
 ENV WORKSPACE /arvados
     go mod download
 
 ENV WORKSPACE /arvados
-CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "rocky8"]
+CMD ["bash", "/jenkins/run-build-packages.sh", "--target", "rocky8"]
index 576b6021c0595a0dbd9a72ed147f6dc289e5810a..dc57c8c03fdc315d85d7d3579f6c5ba40226760b 100644 (file)
@@ -50,36 +50,43 @@ ENV DEBIAN_FRONTEND noninteractive
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
-RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python3 libcurl4-gnutls-dev libgnutls28-dev curl git libattr1-dev libfuse-dev libpq-dev unzip tzdata python3-venv python3-dev libpam-dev shared-mime-info equivs
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y \
+    build-essential \
+    curl \
+    equivs \
+    git \
+    libattr1-dev \
+    libcurl4-gnutls-dev \
+    libfuse-dev \
+    libgnutls28-dev \
+    libpam-dev \
+    libpq-dev \
+    pkgconf \
+    python3 \
+    python3-dev \
+    python3-venv \
+    ruby \
+    ruby-dev \
+    shared-mime-info \
+    tzdata \
+    unzip
 
 
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
 # fpm depends on dotenv, but version 3.0 of that gem dropped support for
 # Ruby 2.7, so we need to specifically install an older version.
 # fpm depends on dotenv, but version 3.0 of that gem dropped support for
 # Ruby 2.7, so we need to specifically install an older version.
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 2.7 -j $(grep -c processor /proc/cpuinfo) && \
-    /usr/local/rvm/bin/rvm alias create default ruby-2.7 && \
-    echo "gem: --no-document" >> ~/.gemrc && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19 && \
-    /usr/local/rvm/bin/rvm-exec default gem install dotenv --version '~> 2.8' && \
-    /usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.15.1
+RUN echo "gem: --no-document" >> ~/.gemrc && \
+    gem install --conservative --version '~> 2.4.0' bundler && \
+    gem install dotenv --version '~> 2.8' && \
+    gem install fpm --version 1.15.1 && \
+    bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 
 
-RUN /usr/local/rvm/bin/rvm-exec default bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs $(grep -c processor /proc/cpuinfo)"
 
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs $(grep -c processor /proc/cpuinfo)"
 
-# Preseed the go module cache and the ruby gems, using the currently checked
-# out branch of the source tree. This avoids potential compatibility issues
-# between the version of Ruby and certain gems.
+# Preseed the go module cache.
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
-    cd /tmp/arvados/services/api && \
-    /usr/local/rvm/bin/rvm-exec default bundle install && \
-    cd /tmp/arvados && \
     go mod download
 
 ENV WORKSPACE /arvados
     go mod download
 
 ENV WORKSPACE /arvados
-CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "ubuntu2004"]
+CMD ["bash", "/jenkins/run-build-packages.sh", "--target", "ubuntu2004"]
index 79664fea6b3572dcfd68adfb15c504b3ce223807..35ef93ff2dadeae587f574b2274ff4c597971e61 100644 (file)
@@ -44,35 +44,40 @@ ENV DEBIAN_FRONTEND noninteractive
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
 
 SHELL ["/bin/bash", "-c"]
 # Install dependencies.
-RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python3 libcurl4-gnutls-dev libgnutls28-dev curl git libattr1-dev libfuse-dev libpq-dev unzip tzdata python3-venv python3-dev libpam-dev shared-mime-info equivs
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y \
+    build-essential \
+    curl \
+    equivs \
+    git \
+    libattr1-dev \
+    libcurl4-gnutls-dev \
+    libfuse-dev \
+    libgnutls28-dev \
+    libpam-dev \
+    libpq-dev \
+    pkgconf \
+    python3 \
+    python3-dev \
+    python3-venv \
+    ruby \
+    ruby-dev \
+    shared-mime-info \
+    tzdata \
+    unzip
 
 
-# Install RVM
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 3.2.2 -j $(grep -c processor /proc/cpuinfo) && \
-    /usr/local/rvm/bin/rvm alias create default ruby-3.2.2 && \
-    echo "gem: --no-document" >> ~/.gemrc && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19 && \
-    /usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.15.1
+RUN echo "gem: --no-document" >> ~/.gemrc && \
+    gem install --conservative --version '>= 2.4.0' bundler && \
+    gem install fpm --version 1.15.1 && \
+    bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 
 
-RUN /usr/local/rvm/bin/rvm-exec default bundle config --global jobs $(let a=$(grep -c processor /proc/cpuinfo )-1; echo $a)
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs 8"
 
 # Cf. https://build.betterup.com/one-weird-trick-that-will-speed-up-your-bundle-install/
 ENV MAKE "make --jobs 8"
 
-# Preseed the go module cache and the ruby gems, using the currently checked
-# out branch of the source tree. This avoids potential compatibility issues
-# between the version of Ruby and certain gems.
+# Preseed the go module cache.
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
 RUN git clone git://git.arvados.org/arvados.git /tmp/arvados && \
     cd /tmp/arvados && \
     if [[ -n "${BRANCH}" ]]; then git checkout ${BRANCH}; fi && \
-    cd /tmp/arvados/services/api && \
-    /usr/local/rvm/bin/rvm-exec default bundle install && \
-    cd /tmp/arvados && \
     go mod download
 
     go mod download
 
-
 ENV WORKSPACE /arvados
 ENV WORKSPACE /arvados
-CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "ubuntu2204"]
+CMD ["bash", "/jenkins/run-build-packages.sh", "--target", "ubuntu2204"]
index ceee9faa15d521481c53ade596c181776402b994..01d69cb3de72ce6503cdce893916d862b195c7f1 100644 (file)
@@ -1,2 +1 @@
 */generated
 */generated
-common-generated/
diff --git a/build/package-test-dockerfiles/Makefile b/build/package-test-dockerfiles/Makefile
deleted file mode 100644 (file)
index 02e2846..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-all: debian11/generated
-debian11/generated: common-generated-all
-       test -d debian11/generated || mkdir debian11/generated
-       cp -f -rlt debian11/generated common-generated/*
-
-all: debian12/generated
-debian12/generated: common-generated-all
-       test -d debian12/generated || mkdir debian12/generated
-       cp -f -rlt debian12/generated common-generated/*
-
-all: rocky8/generated
-rocky8/generated: common-generated-all
-       test -d rocky8/generated || mkdir rocky8/generated
-       cp -f -rlt rocky8/generated common-generated/*
-
-all: ubuntu2004/generated
-ubuntu2004/generated: common-generated-all
-       test -d ubuntu2004/generated || mkdir ubuntu2004/generated
-       cp -f -rlt ubuntu2004/generated common-generated/*
-
-all: ubuntu2204/generated
-ubuntu2204/generated: common-generated-all
-       test -d ubuntu2204/generated || mkdir ubuntu2204/generated
-       cp -f -rlt ubuntu2204/generated common-generated/*
-
-RVMKEY1=mpapis.asc
-RVMKEY2=pkuczynski.asc
-
-common-generated-all: common-generated/$(RVMKEY1) common-generated/$(RVMKEY2)
-
-common-generated/$(RVMKEY1): common-generated
-       wget -cqO common-generated/$(RVMKEY1) https://rvm.io/mpapis.asc
-
-common-generated/$(RVMKEY2): common-generated
-       wget -cqO common-generated/$(RVMKEY2) https://rvm.io/pkuczynski.asc
-
-common-generated:
-       mkdir common-generated
index a659e105d156650aaa78e00051589d9769bb7c12..cd57ffde621757a7a929a938264500b8d4a31acd 100644 (file)
@@ -9,20 +9,8 @@ ENV DEBIAN_FRONTEND noninteractive
 
 # Install dependencies
 RUN apt-get update && \
 
 # Install dependencies
 RUN apt-get update && \
-    apt-get -y install --no-install-recommends curl ca-certificates gpg procps gpg-agent
-
-# Install RVM
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 2.7 -j $(grep -c processor /proc/cpuinfo) --disable-binary && \
-    /usr/local/rvm/bin/rvm alias create default ruby-2.7 && \
+    apt-get -y install --no-install-recommends curl ca-certificates gpg procps gpg-agent ruby ruby-dev && \
     echo "gem: --no-document" >> /etc/gemrc && \
     echo "gem: --no-document" >> /etc/gemrc && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19
-
-# udev daemon can't start in a container, so don't try.
-RUN mkdir -p /etc/udev/disabled
-
-RUN echo "deb file:///arvados/packages/debian11/ /" >>/etc/apt/sources.list
+    gem install --conservative --version '~> 2.4.0' bundler && \
+    mkdir -p /etc/udev/disabled && \
+    echo "deb file:///arvados/packages/debian11/ /" >>/etc/apt/sources.list
index 4cdc41d73bba0e83b7e9cca577091bffc6181553..6cc700d742f03b92e428c2e64e8489a2d396a8e8 100644 (file)
@@ -9,20 +9,8 @@ ENV DEBIAN_FRONTEND noninteractive
 
 # Install dependencies
 RUN apt-get update && \
 
 # Install dependencies
 RUN apt-get update && \
-    apt-get -y install --no-install-recommends curl ca-certificates gpg procps gpg-agent
-
-# Install RVM
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 3.2.2 -j $(grep -c processor /proc/cpuinfo) --disable-binary && \
-    /usr/local/rvm/bin/rvm alias create default ruby-3.2.2 && \
+    apt-get -y install --no-install-recommends curl ca-certificates gpg procps gpg-agent ruby ruby-dev && \
     echo "gem: --no-document" >> /etc/gemrc && \
     echo "gem: --no-document" >> /etc/gemrc && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19
-
-# udev daemon can't start in a container, so don't try.
-RUN mkdir -p /etc/udev/disabled
-
-RUN echo "deb file:///arvados/packages/debian12/ /" >>/etc/apt/sources.list
+    gem install --conservative --version '>= 2.4.0' bundler && \
+    mkdir -p /etc/udev/disabled && \
+    echo "deb file:///arvados/packages/debian12/ /" >>/etc/apt/sources.list
index 809f3626ca20407e064deb9417b4bfa93464671c..b58791ee91e33bb1d1589fc189edd28267d162fb 100644 (file)
@@ -2,11 +2,12 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-FROM rockylinux:8.6-minimal
+FROM rockylinux:8.8-minimal
 MAINTAINER Arvados Package Maintainers <packaging@arvados.org>
 
 # Install dependencies.
 MAINTAINER Arvados Package Maintainers <packaging@arvados.org>
 
 # Install dependencies.
-RUN microdnf --assumeyes --enablerepo=devel install \
+RUN microdnf --assumeyes module enable httpd:2.4 postgresql:10 python39:3.9 ruby:3.1 \
+ && microdnf --assumeyes --enablerepo=devel install \
     autoconf \
     automake \
     bison \
     autoconf \
     automake \
     bison \
@@ -23,10 +24,13 @@ RUN microdnf --assumeyes --enablerepo=devel install \
     make \
     openssl-devel \
     patch \
     make \
     openssl-devel \
     patch \
+    pkgconf \
     procps-ng \
     procps-ng \
-    python3 \
+    python39 \
     readline-devel \
     readline-devel \
+    redhat-rpm-config \
     ruby \
     ruby \
+    ruby-devel \
     shadow-utils \
     sqlite-devel \
     tar \
     shadow-utils \
     sqlite-devel \
     tar \
@@ -34,15 +38,8 @@ RUN microdnf --assumeyes --enablerepo=devel install \
     which \
     zlib-devel
 
     which \
     zlib-devel
 
-# Install RVM
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
 RUN touch /var/lib/rpm/* && \
 RUN touch /var/lib/rpm/* && \
-    gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install --disable-binary 2.7 -j $(grep -c processor /proc/cpuinfo) && \
-    /usr/local/rvm/bin/rvm alias create default ruby-2.7 && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19
+    echo "gem: --no-document" >> ~/.gemrc && \
+    gem install --conservative --version '>= 2.4.0' bundler
 
 COPY localrepo.repo /etc/yum.repos.d/localrepo.repo
 
 COPY localrepo.repo /etc/yum.repos.d/localrepo.repo
index df1e71e75a199527d4ac2a7e6dcd7668b68682e6..c996a8b9720ae3933c80a86ad6be93fdd06b57b6 100644 (file)
@@ -9,19 +9,7 @@ ENV DEBIAN_FRONTEND noninteractive
 
 # Install dependencies
 RUN apt-get update && \
 
 # Install dependencies
 RUN apt-get update && \
-    apt-get -y install --no-install-recommends curl ca-certificates gnupg2
-
-# Install RVM
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 2.7 -j $(grep -c processor /proc/cpuinfo) && \
-    /usr/local/rvm/bin/rvm alias create default ruby-2.7 && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19
-
-# udev daemon can't start in a container, so don't try.
-RUN mkdir -p /etc/udev/disabled
-
-RUN echo "deb [trusted=yes] file:///arvados/packages/ubuntu2004/ /" >>/etc/apt/sources.list
+    apt-get -y install --no-install-recommends curl ca-certificates gnupg2 ruby ruby-dev && \
+    gem install --conservative --version '~> 2.4.0' bundler && \
+    mkdir -p /etc/udev/disabled && \
+    echo "deb [trusted=yes] file:///arvados/packages/ubuntu2004/ /" >>/etc/apt/sources.list
index 4926a6573fb5bce58fbe994d67701e2e32b43b3f..01a0496284ba2d855261bfddaaa88f01d8a908da 100644 (file)
@@ -9,19 +9,7 @@ ENV DEBIAN_FRONTEND noninteractive
 
 # Install dependencies
 RUN apt-get update && \
 
 # Install dependencies
 RUN apt-get update && \
-    apt-get -y install --no-install-recommends curl ca-certificates gnupg2
-
-# Install RVM
-ADD generated/mpapis.asc /tmp/
-ADD generated/pkuczynski.asc /tmp/
-RUN gpg --import --no-tty /tmp/mpapis.asc && \
-    gpg --import --no-tty /tmp/pkuczynski.asc && \
-    curl -L https://get.rvm.io | bash -s stable && \
-    /usr/local/rvm/bin/rvm install 3.2.2 -j $(grep -c processor /proc/cpuinfo) && \
-    /usr/local/rvm/bin/rvm alias create default ruby-3.2.2 && \
-    /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.2.19
-
-# udev daemon can't start in a container, so don't try.
-RUN mkdir -p /etc/udev/disabled
-
-RUN echo "deb [trusted=yes] file:///arvados/packages/ubuntu2204/ /" >>/etc/apt/sources.list
+    apt-get -y install --no-install-recommends curl ca-certificates gnupg2 ruby ruby-dev && \
+    gem install --conservative --version '>= 2.4.0' bundler && \
+    mkdir -p /etc/udev/disabled && \
+    echo "deb [trusted=yes] file:///arvados/packages/ubuntu2204/ /" >>/etc/apt/sources.list
index ee855d8012d6adccfb0670492f73152b5c78e5be..62b30c37d1f0963f0dccc4425c85c7e63a429a5e 100755 (executable)
@@ -29,4 +29,4 @@ case "$TARGET" in
         ;;
 esac
 
         ;;
 esac
 
-/usr/local/rvm/bin/rvm-exec default bundle list >"$ARV_PACKAGES_DIR/$PACKAGE_NAME.gems"
+bundle list >"$ARV_PACKAGES_DIR/$PACKAGE_NAME.gems"
index cd41f1d920f9e787a3b5bd75fdfea5e6c83fd8ea..b6d7fec46876cd027ef1d34926d6563dd364cefc 100755 (executable)
@@ -30,6 +30,10 @@ diff "$ARV_PACKAGES_DIR/$1".{before,after} >"$ARV_PACKAGES_DIR/$1.diff" || true
 mkdir -p /tmp/opts
 cd /tmp/opts
 
 mkdir -p /tmp/opts
 cd /tmp/opts
 
+# Install other packages alongside to test for build id conflicts.
+# This line can be removed after we have test-provision-rocky8, #21426.
+microdnf --assumeyes install arvados-client arvados-server python3-arvados-python-client
+
 rpm2cpio $(ls -t "$ARV_PACKAGES_DIR/$1"-*.rpm | head -n1) | cpio -idm 2>/dev/null
 
 if [[ "$DEBUG" != "0" ]]; then
 rpm2cpio $(ls -t "$ARV_PACKAGES_DIR/$1"-*.rpm | head -n1) | cpio -idm 2>/dev/null
 
 if [[ "$DEBUG" != "0" ]]; then
diff --git a/build/pypkg_info.py b/build/pypkg_info.py
new file mode 100644 (file)
index 0000000..45f8d16
--- /dev/null
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+"""pypkg_info.py - Introspect installed Python packages
+
+This tool can read metadata about any Python package installed in the current
+environment and report it out in various formats. We use this mainly to pass
+information through when building distribution packages.
+"""
+
+import argparse
+import enum
+import importlib.metadata
+import os
+import sys
+
+from pathlib import PurePath
+
+class RawFormat:
+    def format_metadata(self, key, value):
+        return value
+
+    def format_path(self, path):
+        return str(path)
+
+
+class FPMFormat(RawFormat):
+    PYTHON_METADATA_MAP = {
+        'summary': 'description',
+    }
+
+    def format_metadata(self, key, value):
+        key = key.lower()
+        key = self.PYTHON_METADATA_MAP.get(key, key)
+        return f'--{key}={value}'
+
+
+class Formats(enum.Enum):
+    RAW = RawFormat
+    FPM = FPMFormat
+
+    @classmethod
+    def from_arg(cls, arg):
+        try:
+            return cls[arg.upper()]
+        except KeyError:
+            raise ValueError(f"unknown format {arg!r}") from None
+
+
+def report_binfiles(args):
+    bin_names = [
+        PurePath('bin', path.name)
+        for pkg_name in args.package_names
+        for path in importlib.metadata.distribution(pkg_name).files
+        if path.parts[-3:-1] == ('..', 'bin')
+    ]
+    fmt = args.format.value().format_path
+    return (fmt(path) for path in bin_names)
+
+def report_metadata(args):
+    dist = importlib.metadata.distribution(args.package_name)
+    fmt = args.format.value().format_metadata
+    for key in args.metadata_key:
+        yield fmt(key, dist.metadata.get(key, ''))
+
+def unescape_str(arg):
+    arg = arg.replace('\'', '\\\'')
+    return eval(f"'''{arg}'''", {})
+
+def parse_arguments(arglist=None):
+    parser = argparse.ArgumentParser()
+    parser.set_defaults(action=None)
+    format_names = ', '.join(fmt.name.lower() for fmt in Formats)
+    parser.add_argument(
+        '--format', '-f',
+        choices=list(Formats),
+        default=Formats.RAW,
+        type=Formats.from_arg,
+        help=f"Output format. Choices are: {format_names}",
+    )
+    parser.add_argument(
+        '--delimiter', '-d',
+        default='\n',
+        type=unescape_str,
+        help="Line ending. Python backslash escapes are supported. Default newline.",
+    )
+    subparsers = parser.add_subparsers()
+
+    binfiles = subparsers.add_parser('binfiles')
+    binfiles.set_defaults(action=report_binfiles)
+    binfiles.add_argument(
+        'package_names',
+        nargs=argparse.ONE_OR_MORE,
+    )
+
+    metadata = subparsers.add_parser('metadata')
+    metadata.set_defaults(action=report_metadata)
+    metadata.add_argument(
+        'package_name',
+    )
+    metadata.add_argument(
+        'metadata_key',
+        nargs=argparse.ONE_OR_MORE,
+    )
+
+    args = parser.parse_args()
+    if args.action is None:
+        parser.error("subcommand is required")
+    return args
+
+def main(arglist=None):
+    args = parse_arguments(arglist)
+    try:
+        for line in args.action(args):
+            print(line, end=args.delimiter)
+    except importlib.metadata.PackageNotFoundError as error:
+        print(f"error: package not found: {error.args[0]}", file=sys.stderr)
+        return os.EX_NOTFOUND
+    else:
+        return os.EX_OK
+
+if __name__ == '__main__':
+    exit(main())
index 6ac2539f8e68e7c229cf7c1e4859674605c3c1d4..08772ce02fb3b747c8eafd31dcab45153cb950bc 100644 (file)
@@ -13,5 +13,4 @@ Since our build process is a tower of shell scripts, concatenating files seemed
 postinst.sh lets the early parts define a few hooks to control behavior:
 
 * After it installs the core configuration files (database.yml, application.yml, and production.rb) to /etc/arvados/server, it calls setup_extra_conffiles.  By default this is a noop function (in step2.sh).
 postinst.sh lets the early parts define a few hooks to control behavior:
 
 * After it installs the core configuration files (database.yml, application.yml, and production.rb) to /etc/arvados/server, it calls setup_extra_conffiles.  By default this is a noop function (in step2.sh).
-* Before it restarts nginx, it calls setup_before_nginx_restart.  By default this is a noop function (in step2.sh).  API server defines this to set up the internal git repository, if necessary.
 * $RAILSPKG_DATABASE_LOAD_TASK defines the Rake task to load the database.  API server uses db:structure:load.  Workbench doesn't set this, which causes the postinst to skip all database work.
 * $RAILSPKG_DATABASE_LOAD_TASK defines the Rake task to load the database.  API server uses db:structure:load.  Workbench doesn't set this, which causes the postinst to skip all database work.
index a0e356ce327cba01b876b1330c7430462c317542..493147d52138a80a0e7d5d3ff16ff30281d6ae07 100644 (file)
@@ -16,23 +16,3 @@ setup_extra_conffiles() {
   # can still be there, left over from a previous version of the API server package.
   rm -f $RELEASE_PATH/config/initializers/omniauth.rb
 }
   # can still be there, left over from a previous version of the API server package.
   rm -f $RELEASE_PATH/config/initializers/omniauth.rb
 }
-
-setup_before_nginx_restart() {
-  # initialize git_internal_dir
-  # usually /var/lib/arvados/internal.git (set in application.default.yml )
-  if [ "$APPLICATION_READY" = "1" ]; then
-      GIT_INTERNAL_DIR=$($COMMAND_PREFIX bin/rake config:dump 2>&1 | grep GitInternalDir | awk '{ print $2 }' |tr -d '"')
-      if [ ! -e "$GIT_INTERNAL_DIR" ]; then
-        run_and_report "Creating git_internal_dir '$GIT_INTERNAL_DIR'" \
-          mkdir -p "$GIT_INTERNAL_DIR"
-        run_and_report "Initializing git_internal_dir '$GIT_INTERNAL_DIR'" \
-          git init --quiet --bare $GIT_INTERNAL_DIR
-      else
-        echo "Initializing git_internal_dir $GIT_INTERNAL_DIR: directory exists, skipped."
-      fi
-      run_and_report "Making sure '$GIT_INTERNAL_DIR' has the right permission" \
-         chown -R "$WWW_OWNER:" "$GIT_INTERNAL_DIR"
-  else
-      echo "Initializing git_internal_dir... skipped."
-  fi
-}
index e317f85aaff27ac246885c76263ed5365d75cbc2..b5f78f988cda7f7097dc05c526fb58dadd170e7d 100644 (file)
@@ -10,12 +10,6 @@ set -e
 DATABASE_READY=1
 APPLICATION_READY=1
 
 DATABASE_READY=1
 APPLICATION_READY=1
 
-if [ -s "$HOME/.rvm/scripts/rvm" ] || [ -s "/usr/local/rvm/scripts/rvm" ]; then
-    COMMAND_PREFIX="/usr/local/rvm/bin/rvm-exec default"
-else
-    COMMAND_PREFIX=
-fi
-
 report_not_ready() {
     local ready_flag="$1"; shift
     local config_file="$1"; shift
 report_not_ready() {
     local ready_flag="$1"; shift
     local config_file="$1"; shift
@@ -125,17 +119,17 @@ setup_conffile() {
 }
 
 prepare_database() {
 }
 
 prepare_database() {
-  DB_MIGRATE_STATUS=`$COMMAND_PREFIX bin/rake db:migrate:status 2>&1 || true`
+  DB_MIGRATE_STATUS=`bin/rake db:migrate:status 2>&1 || true`
   if echo "$DB_MIGRATE_STATUS" | grep -qF 'Schema migrations table does not exist yet.'; then
       # The database exists, but the migrations table doesn't.
   if echo "$DB_MIGRATE_STATUS" | grep -qF 'Schema migrations table does not exist yet.'; then
       # The database exists, but the migrations table doesn't.
-      run_and_report "Setting up database" $COMMAND_PREFIX bin/rake \
+      run_and_report "Setting up database" bin/rake \
                      "$RAILSPKG_DATABASE_LOAD_TASK" db:seed
   elif echo "$DB_MIGRATE_STATUS" | grep -q '^database: '; then
       run_and_report "Running db:migrate" \
                      "$RAILSPKG_DATABASE_LOAD_TASK" db:seed
   elif echo "$DB_MIGRATE_STATUS" | grep -q '^database: '; then
       run_and_report "Running db:migrate" \
-                     $COMMAND_PREFIX bin/rake db:migrate
+                     bin/rake db:migrate
   elif echo "$DB_MIGRATE_STATUS" | grep -q 'database .* does not exist'; then
       if ! run_and_report "Running db:setup" \
   elif echo "$DB_MIGRATE_STATUS" | grep -q 'database .* does not exist'; then
       if ! run_and_report "Running db:setup" \
-           $COMMAND_PREFIX bin/rake db:setup 2>/dev/null; then
+           bin/rake db:setup 2>/dev/null; then
           echo "Warning: unable to set up database." >&2
           DATABASE_READY=0
       fi
           echo "Warning: unable to set up database." >&2
           DATABASE_READY=0
       fi
@@ -198,15 +192,26 @@ configure_version() {
   cd "$RELEASE_PATH"
   export RAILS_ENV=production
 
   cd "$RELEASE_PATH"
   export RAILS_ENV=production
 
-  if ! $COMMAND_PREFIX bundle --version >/dev/null 2>&1; then
-      run_and_report "Installing bundler" $COMMAND_PREFIX gem install bundler --version 2.2.19 --no-document
+  run_and_report "Installing bundler" gem install --conservative --version '~> 2.4.0' bundler
+  local bundle="$(gem contents --version '~> 2.4.0' bundler | grep '/exe/bundle$' | tail -n1)"
+  if ! [ -x "$bundle" ]; then
+      echo "Error: failed to find \`bundle\` command after installing bundler gem" >&2
+      return 1
   fi
 
   fi
 
+  local bundle_path="$SHARED_PATH/vendor_bundle"
   run_and_report "Running bundle config set --local path $SHARED_PATH/vendor_bundle" \
   run_and_report "Running bundle config set --local path $SHARED_PATH/vendor_bundle" \
-      $COMMAND_PREFIX bin/bundle config set --local path $SHARED_PATH/vendor_bundle
-
-  run_and_report "Running bundle install" \
-      $COMMAND_PREFIX bin/bundle install --local --quiet
+                 "$bundle" config set --local path "$bundle_path"
+
+  # As of April 2024/Bundler 2.4, `bundle install` tends not to install gems
+  # which are already installed system-wide, which causes bundle activation to
+  # fail later. Work around this by installing all gems manually.
+  find vendor/cache -maxdepth 1 -name '*.gem' -print0 \
+      | run_and_report "Installing bundle gems" xargs -0r \
+                       gem install --conservative --ignore-dependencies --local --quiet \
+                       --install-dir="$bundle_path/ruby/$(ruby -e 'puts RUBY_VERSION')"
+  run_and_report "Running bundle install" "$bundle" install --prefer-local --quiet
+  run_and_report "Verifying bundle is complete" "$bundle" exec true
 
   echo -n "Ensuring directory and file permissions ..."
   # Ensure correct ownership of a few files
 
   echo -n "Ensuring directory and file permissions ..."
   # Ensure correct ownership of a few files
@@ -236,15 +241,13 @@ configure_version() {
       # warn about config errors (deprecated/removed keys from
       # previous version, etc)
       run_and_report "Checking configuration for completeness" \
       # warn about config errors (deprecated/removed keys from
       # previous version, etc)
       run_and_report "Checking configuration for completeness" \
-                     $COMMAND_PREFIX bin/rake config:check || APPLICATION_READY=0
+                     bin/rake config:check || APPLICATION_READY=0
   else
       APPLICATION_READY=0
   fi
 
   chown -R "$WWW_OWNER:" $RELEASE_PATH/tmp
 
   else
       APPLICATION_READY=0
   fi
 
   chown -R "$WWW_OWNER:" $RELEASE_PATH/tmp
 
-  setup_before_nginx_restart
-
   if [ -n "$SERVICE_MANAGER" ]; then
       service_command "$SERVICE_MANAGER" restart "$WEB_SERVICE"
   fi
   if [ -n "$SERVICE_MANAGER" ]; then
       service_command "$SERVICE_MANAGER" restart "$WEB_SERVICE"
   fi
index 41c9cd71e366ff5d14973638ad48547bcc98ba3a..4af0e4a01b8c3edf855dd15c0d21c353d4658ee5 100644 (file)
@@ -26,9 +26,6 @@ SHARED_PATH=$INSTALL_PATH/shared
 if ! type setup_extra_conffiles >/dev/null 2>&1; then
     setup_extra_conffiles() { return; }
 fi
 if ! type setup_extra_conffiles >/dev/null 2>&1; then
     setup_extra_conffiles() { return; }
 fi
-if ! type setup_before_nginx_restart >/dev/null 2>&1; then
-    setup_before_nginx_restart() { return; }
-fi
 
 if [ -e /run/systemd/system ]; then
     USING_SYSTEMD=1
 
 if [ -e /run/systemd/system ]; then
     USING_SYSTEMD=1
index 37fe7052413c95b118f97b7a390f5bbfb14dee0f..b1801dd307a1ac34591e5ef2b0169bdba5a92ddb 100755 (executable)
@@ -198,7 +198,7 @@ if [[ -n "$test_packages" ]]; then
 else
   IMAGE="arvados/build:$TARGET"
   if [[ "$COMMAND" != "" ]]; then
 else
   IMAGE="arvados/build:$TARGET"
   if [[ "$COMMAND" != "" ]]; then
-    COMMAND="/usr/local/rvm/bin/rvm-exec default bash /jenkins/$COMMAND --target $TARGET$DEBUG"
+    COMMAND="bash /jenkins/$COMMAND --target $TARGET$DEBUG"
   fi
 fi
 
   fi
 fi
 
@@ -206,11 +206,10 @@ JENKINS_DIR=$(dirname "$(readlink -e "$0")")
 
 if [[ "$SKIP_DOCKER_BUILD" != 1 ]] ; then
     if [[ -n "$test_packages" ]]; then
 
 if [[ "$SKIP_DOCKER_BUILD" != 1 ]] ; then
     if [[ -n "$test_packages" ]]; then
-       pushd "$JENKINS_DIR/package-test-dockerfiles"
-       make "$TARGET/generated"
+           pushd "$JENKINS_DIR/package-test-dockerfiles"
     else
     else
-       pushd "$JENKINS_DIR/package-build-dockerfiles"
-       make "$TARGET/generated"
+           pushd "$JENKINS_DIR/package-build-dockerfiles"
+           make "$TARGET/generated"
     fi
 
     GOVERSION=$(grep 'const goversion =' $WORKSPACE/lib/install/deps.go |awk -F'"' '{print $2}')
     fi
 
     GOVERSION=$(grep 'const goversion =' $WORKSPACE/lib/install/deps.go |awk -F'"' '{print $2}')
@@ -231,7 +230,6 @@ if test -z "$packages" ; then
         arvados-dispatch-cloud
         arvados-dispatch-lsf
         arvados-docker-cleaner
         arvados-dispatch-cloud
         arvados-dispatch-lsf
         arvados-docker-cleaner
-        arvados-git-httpd
         arvados-health
         arvados-server
         arvados-src
         arvados-health
         arvados-server
         arvados-src
@@ -267,7 +265,8 @@ mkdir -p "$WORKSPACE/services/api/vendor/cache-$TARGET"
 docker_volume_args=(
     -v "$JENKINS_DIR:/jenkins"
     -v "$WORKSPACE:/arvados"
 docker_volume_args=(
     -v "$JENKINS_DIR:/jenkins"
     -v "$WORKSPACE:/arvados"
-    -v /arvados/services/api/vendor/bundle
+    --tmpfs /arvados/services/api/.bundle:rw,noexec,nosuid,size=1m
+    --tmpfs /arvados/services/api/vendor:rw,exec,nosuid,size=1g
     -v "$WORKSPACE/services/api/vendor/cache-$TARGET:/arvados/services/api/vendor/cache"
 )
 
     -v "$WORKSPACE/services/api/vendor/cache-$TARGET:/arvados/services/api/vendor/cache"
 )
 
index 599fe7cf965d8274fbc4841170c866ba42e40895..285bb11d71f8c53fcda75db667a12f26e365dc46 100755 (executable)
@@ -165,13 +165,6 @@ if [ $RUBY -eq 0 ] && [ $PYTHON -eq 0 ]; then
   exit 0
 fi
 
   exit 0
 fi
 
-if [[ -f /etc/profile.d/rvm.sh ]]; then
-    source /etc/profile.d/rvm.sh
-    GEM="rvm-exec default gem"
-else
-    GEM=gem
-fi
-
 # Make all files world-readable -- jenkins runs with umask 027, and has checked
 # out our git tree here
 chmod o+r "$WORKSPACE" -R
 # Make all files world-readable -- jenkins runs with umask 027, and has checked
 # out our git tree here
 chmod o+r "$WORKSPACE" -R
index 77ce054318eb24c1437a2eeeaacd1e7d793f51b1..588ebedb92c55d2d558f1478ad8a29731dd4fcc8 100755 (executable)
@@ -102,7 +102,7 @@ elif [[ ! -d "$WORKSPACE/build/package-build-dockerfiles/$TARGET" ]]; then
 fi
 
 if [[ "$COMMAND" != "" ]]; then
 fi
 
 if [[ "$COMMAND" != "" ]]; then
-  COMMAND="/usr/local/rvm/bin/rvm-exec default bash /jenkins/$COMMAND --target $TARGET"
+  COMMAND="bash /jenkins/$COMMAND --target $TARGET"
 fi
 
 STDOUT_IF_DEBUG=/dev/null
 fi
 
 STDOUT_IF_DEBUG=/dev/null
@@ -181,13 +181,6 @@ fi
 debug_echo "$0 is running from $RUN_BUILD_PACKAGES_PATH"
 debug_echo "Workspace is $WORKSPACE"
 
 debug_echo "$0 is running from $RUN_BUILD_PACKAGES_PATH"
 debug_echo "Workspace is $WORKSPACE"
 
-if [[ -f /etc/profile.d/rvm.sh ]]; then
-    source /etc/profile.d/rvm.sh
-    GEM="rvm-exec default gem"
-else
-    GEM=gem
-fi
-
 # Make all files world-readable -- jenkins runs with umask 027, and has checked
 # out our git tree here
 chmod o+r "$WORKSPACE" -R
 # Make all files world-readable -- jenkins runs with umask 027, and has checked
 # out our git tree here
 chmod o+r "$WORKSPACE" -R
@@ -213,7 +206,7 @@ git config --global --add safe.directory /arvados
 # Ruby gems
 debug_echo -e "\nRuby gems\n"
 
 # Ruby gems
 debug_echo -e "\nRuby gems\n"
 
-FPM_GEM_PREFIX=$($GEM environment gemdir)
+FPM_GEM_PREFIX=$(gem environment gemdir)
 
 cd "$WORKSPACE/sdk/ruby" || exit 1
 handle_ruby_gem arvados
 
 cd "$WORKSPACE/sdk/ruby" || exit 1
 handle_ruby_gem arvados
@@ -242,8 +235,6 @@ package_go_binary cmd/arvados-server arvados-dispatch-cloud "$FORMAT" "$ARCH" \
     "Arvados cluster cloud dispatch"
 package_go_binary cmd/arvados-server arvados-dispatch-lsf "$FORMAT" "$ARCH" \
     "Dispatch Arvados containers to an LSF cluster"
     "Arvados cluster cloud dispatch"
 package_go_binary cmd/arvados-server arvados-dispatch-lsf "$FORMAT" "$ARCH" \
     "Dispatch Arvados containers to an LSF cluster"
-package_go_binary cmd/arvados-server arvados-git-httpd "$FORMAT" "$ARCH" \
-    "Provide authenticated http access to Arvados-hosted git repositories"
 package_go_binary services/crunch-dispatch-local crunch-dispatch-local "$FORMAT" "$ARCH" \
     "Dispatch Crunch containers on the local system"
 package_go_binary cmd/arvados-server crunch-dispatch-slurm "$FORMAT" "$ARCH" \
 package_go_binary services/crunch-dispatch-local crunch-dispatch-local "$FORMAT" "$ARCH" \
     "Dispatch Crunch containers on the local system"
 package_go_binary cmd/arvados-server crunch-dispatch-slurm "$FORMAT" "$ARCH" \
@@ -278,27 +269,17 @@ package_go_so lib/pam pam_arvados.so libpam-arvados-go "$FORMAT" "$ARCH" \
 # Python packages
 debug_echo -e "\nPython packages\n"
 
 # Python packages
 debug_echo -e "\nPython packages\n"
 
-# The Python SDK - Python3 package
+# Before a Python package can be built, its dependencies must already be built.
+# This list is ordered accordingly.
+setup_build_virtualenv
+fpm_build_virtualenv cwltest "==2.3.20230108193615" "$FORMAT" "$ARCH"
 fpm_build_virtualenv "arvados-python-client" "sdk/python" "$FORMAT" "$ARCH"
 fpm_build_virtualenv "arvados-python-client" "sdk/python" "$FORMAT" "$ARCH"
-
-# Arvados cwl runner - Python3 package
-fpm_build_virtualenv "arvados-cwl-runner" "sdk/cwl" "$FORMAT" "$ARCH"
-
-# The FUSE driver - Python3 package
-fpm_build_virtualenv "arvados-fuse" "services/fuse" "$FORMAT" "$ARCH"
-
-# The Arvados crunchstat-summary tool
 fpm_build_virtualenv "crunchstat-summary" "tools/crunchstat-summary" "$FORMAT" "$ARCH"
 fpm_build_virtualenv "crunchstat-summary" "tools/crunchstat-summary" "$FORMAT" "$ARCH"
-
-# The Docker image cleaner
+fpm_build_virtualenv "arvados-cwl-runner" "sdk/cwl" "$FORMAT" "$ARCH"
 fpm_build_virtualenv "arvados-docker-cleaner" "services/dockercleaner" "$FORMAT" "$ARCH"
 fpm_build_virtualenv "arvados-docker-cleaner" "services/dockercleaner" "$FORMAT" "$ARCH"
-
-# The Arvados user activity tool
+fpm_build_virtualenv "arvados-fuse" "services/fuse" "$FORMAT" "$ARCH"
 fpm_build_virtualenv "arvados-user-activity" "tools/user-activity" "$FORMAT" "$ARCH"
 
 fpm_build_virtualenv "arvados-user-activity" "tools/user-activity" "$FORMAT" "$ARCH"
 
-# The cwltest package, which lives out of tree
-handle_cwltest "$FORMAT" "$ARCH"
-
 # Workbench2
 package_workbench2
 
 # Workbench2
 package_workbench2
 
index a395db8b773b30a781a5606e736ec92c4b33c875..6fdc4aafcd748f97b98ef551578f5c4a9bb624bd 100755 (executable)
@@ -111,7 +111,7 @@ handle_ruby_gem() {
         find -maxdepth 1 -name "${gem_name}-*.gem" -delete
 
         # -q appears to be broken in gem version 2.2.2
         find -maxdepth 1 -name "${gem_name}-*.gem" -delete
 
         # -q appears to be broken in gem version 2.2.2
-        $GEM build "$gem_name.gemspec" $DASHQ_UNLESS_DEBUG >"$STDOUT_IF_DEBUG" 2>"$STDERR_IF_DEBUG"
+        gem build "$gem_name.gemspec" $DASHQ_UNLESS_DEBUG >"$STDOUT_IF_DEBUG" 2>"$STDERR_IF_DEBUG"
     fi
 }
 
     fi
 }
 
@@ -121,8 +121,11 @@ package_workbench2() {
     local src=services/workbench2
     local dst=/var/www/arvados-workbench2/workbench2
     local description="Arvados Workbench 2"
     local src=services/workbench2
     local dst=/var/www/arvados-workbench2/workbench2
     local description="Arvados Workbench 2"
-    local version="$(version_from_git)"
+    if [[ -n "$ONLY_BUILD" ]] && [[ "$pkgname" != "$ONLY_BUILD" ]] ; then
+        return 0
+    fi
     cd "$WORKSPACE/$src"
     cd "$WORKSPACE/$src"
+    local version="$(version_from_git)"
     rm -rf ./build
     NODE_ENV=production yarn install
     VERSION="$version" BUILD_NUMBER="$(default_iteration "$pkgname" "$version" yarn)" GIT_COMMIT="$(git rev-parse HEAD | head -c9)" yarn build
     rm -rf ./build
     NODE_ENV=production yarn install
     VERSION="$version" BUILD_NUMBER="$(default_iteration "$pkgname" "$version" yarn)" GIT_COMMIT="$(git rev-parse HEAD | head -c9)" yarn build
@@ -262,6 +265,13 @@ package_go_binary_worker() {
       binpath="$GOPATH/bin/linux_${target_arch}/${basename}"
     fi
 
       binpath="$GOPATH/bin/linux_${target_arch}/${basename}"
     fi
 
+    case "$package_format" in
+        # As of April 2024 we package identical Go binaries under different
+        # packages and names. This upsets the build id database, so don't
+        # register ourselves there.
+        rpm) switches+=(--rpm-rpmbuild-define="_build_id_links none") ;;
+    esac
+
     systemd_unit="$WORKSPACE/${src_path}/${prog}.service"
     if [[ -e "${systemd_unit}" ]]; then
         switches+=(
     systemd_unit="$WORKSPACE/${src_path}/${prog}.service"
     if [[ -e "${systemd_unit}" ]]; then
         switches+=(
@@ -506,8 +516,38 @@ handle_rails_package() {
         cd "$srcdir"
         mkdir -p tmp
         git rev-parse HEAD >git-commit.version
         cd "$srcdir"
         mkdir -p tmp
         git rev-parse HEAD >git-commit.version
+        # Please make sure you read `bundle help config` carefully before you
+        # modify any of these settings. Some of their names are not intuitive.
+        #
+        # `bundle cache` caches from Git and paths, not just rubygems.org.
         bundle config set cache_all true
         bundle config set cache_all true
-        bundle package
+        # Disallow changes to Gemfile.
+        bundle config set deployment true
+        # Avoid loading system-wide gems (although this seems to not work 100%).
+        bundle config set disable_shared_gems true
+        # `bundle cache` only downloads gems, doesn't install them.
+        # Our Rails postinst script does the install step.
+        bundle config set no_install true
+        # As of April 2024/Bundler 2.4, `bundle cache` seems to skip downloading
+        # gems that are already available system-wide... and then it complains
+        # that your bundle is incomplete. Work around this by fetching gems
+        # manually.
+        # TODO: Once all our supported distros have Ruby 3+, we can modify
+        # the awk script to print "NAME:VERSION" output, and pipe that directly
+        # to `xargs -0r gem fetch` for reduced overhead.
+        mkdir -p vendor/cache
+        awk -- '
+BEGIN { OFS="\0"; ORS="\0"; }
+(/^[A-Z ]*$/) { level1=$0; }
+(/^  [[:alpha:]]+:$/) { level2=substr($0, 3, length($0) - 3); next; }
+(/^ {0,3}[[:alpha:]]/) { level2=""; next; }
+(level1 == "GEM" && level2 == "specs" && NF == 2 && $1 ~ /^[[:alpha:]][-_[:alnum:]]*$/ && $2 ~ /\([[:digit:]]+[-_+.[:alnum:]]*\)$/) {
+    print "--version", substr($2, 2, length($2) - 2), $1;
+}
+' Gemfile.lock | env -C vendor/cache xargs -0r --max-args=3 gem fetch
+        # Despite the bug, we still run `bundle cache` to make sure Bundler is
+        # happy for later steps.
+        bundle cache
     )
     if [[ 0 != "$?" ]] || ! cd "$WORKSPACE/packages/$TARGET"; then
         echo "ERROR: $pkgname package prep failed" >&2
     )
     if [[ 0 != "$?" ]] || ! cd "$WORKSPACE/packages/$TARGET"; then
         echo "ERROR: $pkgname package prep failed" >&2
@@ -566,34 +606,6 @@ handle_api_server () {
   fi
 }
 
   fi
 }
 
-# Usage: handle_cwltest [deb|rpm] [amd64|arm64]
-handle_cwltest () {
-  local package_format="$1"; shift
-  local target_arch="${1:-amd64}"; shift
-
-  if [[ -n "$ONLY_BUILD" ]] && [[ "$ONLY_BUILD" != "python3-cwltest" ]] ; then
-    debug_echo -e "Skipping build of cwltest package."
-    return 0
-  fi
-  cd "$WORKSPACE"
-  if [[ -e "$WORKSPACE/cwltest" ]]; then
-    rm -rf "$WORKSPACE/cwltest"
-  fi
-  git clone https://github.com/common-workflow-language/cwltest.git
-
-  # The subsequent release of cwltest confirms that files exist on disk, since
-  # our files are in Keep, all the tests fail.
-  # We should add [optional] Arvados support to cwltest so it can access
-  # Keep but for the time being just package the last working version.
-  (cd cwltest && git checkout 2.3.20230108193615)
-
-  # signal to our build script that we want a cwltest executable installed in /usr/bin/
-  mkdir cwltest/bin && touch cwltest/bin/cwltest
-  fpm_build_virtualenv "cwltest" "cwltest" "$package_format" "$target_arch"
-  cd "$WORKSPACE"
-  rm -rf "$WORKSPACE/cwltest"
-}
-
 # Usage: handle_arvados_src
 handle_arvados_src () {
   if [[ -n "$ONLY_BUILD" ]] && [[ "$ONLY_BUILD" != "arvados-src" ]] ; then
 # Usage: handle_arvados_src
 handle_arvados_src () {
   if [[ -n "$ONLY_BUILD" ]] && [[ "$ONLY_BUILD" != "arvados-src" ]] ; then
@@ -629,6 +641,13 @@ handle_arvados_src () {
   )
 }
 
   )
 }
 
+setup_build_virtualenv() {
+    PYTHON_BUILDROOT="$(mktemp --directory --tmpdir pybuild.XXXXXXXX)"
+    "$PYTHON3_EXECUTABLE" -m venv "$PYTHON_BUILDROOT/venv"
+    "$PYTHON_BUILDROOT/venv/bin/pip" install --upgrade build piprepo setuptools wheel
+    mkdir "$PYTHON_BUILDROOT/wheelhouse"
+}
+
 # Build python packages with a virtualenv built-in
 # Usage: fpm_build_virtualenv arvados-python-client sdk/python [deb|rpm] [amd64|arm64]
 fpm_build_virtualenv () {
 # Build python packages with a virtualenv built-in
 # Usage: fpm_build_virtualenv arvados-python-client sdk/python [deb|rpm] [amd64|arm64]
 fpm_build_virtualenv () {
@@ -638,27 +657,6 @@ fpm_build_virtualenv () {
   local target_arch="${1:-amd64}"; shift
 
   native_arch=$(get_native_arch)
   local target_arch="${1:-amd64}"; shift
 
   native_arch=$(get_native_arch)
-
-  if [[ "$pkg" != "arvados-docker-cleaner" ]]; then
-    PYTHON_PKG=$PYTHON3_PKG_PREFIX-$pkg
-  else
-    # Exception to our package naming convention
-    PYTHON_PKG=$pkg
-  fi
-
-  if [[ -n "$ONLY_BUILD" ]] && [[ "$PYTHON_PKG" != "$ONLY_BUILD" ]]; then
-    # arvados-python-client sdist should always be built if we are building a
-    # python package.
-    if [[ "$ONLY_BUILD" != "python3-arvados-cwl-runner" ]] &&
-       [[ "$ONLY_BUILD" != "python3-arvados-fuse" ]] &&
-       [[ "$ONLY_BUILD" != "python3-crunchstat-summary" ]] &&
-       [[ "$ONLY_BUILD" != "arvados-docker-cleaner" ]] &&
-       [[ "$ONLY_BUILD" != "python3-arvados-user-activity" ]]; then
-      debug_echo -e "Skipping build of $pkg package."
-      return 0
-    fi
-  fi
-
   if [[ -n "$target_arch" ]] && [[ "$native_arch" == "$target_arch" ]]; then
       fpm_build_virtualenv_worker "$pkg" "$pkg_dir" "$package_format" "$native_arch" "$target_arch"
   elif [[ -z "$target_arch" ]]; then
   if [[ -n "$target_arch" ]] && [[ "$native_arch" == "$target_arch" ]]; then
       fpm_build_virtualenv_worker "$pkg" "$pkg_dir" "$package_format" "$native_arch" "$target_arch"
   elif [[ -z "$target_arch" ]]; then
@@ -699,91 +697,106 @@ fpm_build_virtualenv_worker () {
     PYTHON_PKG=$PKG
   fi
 
     PYTHON_PKG=$PKG
   fi
 
-  cd $WORKSPACE/$PKG_DIR
+  # We must always add a wheel to our repository, even if we're not building
+  # this distro package, because it might be a dependency for a later
+  # package we do build.
+  if [[ "$PKG_DIR" =~ ^.=[0-9]+\. ]]; then
+      # Not source to build, but a version to download.
+      # The rest of the function expects a filesystem path, so set one afterwards.
+      "$PYTHON_BUILDROOT/venv/bin/pip" download --dest="$PYTHON_BUILDROOT/wheelhouse" "$PKG$PKG_DIR" \
+          && PKG_DIR="$PYTHON_BUILDROOT/nonexistent"
+  else
+      # Make PKG_DIR absolute.
+      PKG_DIR="$(env -C "$WORKSPACE" readlink -e "$PKG_DIR")"
+      if [[ -e "$PKG_DIR/pyproject.toml" ]]; then
+          "$PYTHON_BUILDROOT/venv/bin/python" -m build --outdir="$PYTHON_BUILDROOT/wheelhouse" "$PKG_DIR"
+      else
+          env -C "$PKG_DIR" "$PYTHON_BUILDROOT/venv/bin/python" setup.py bdist_wheel --dist-dir="$PYTHON_BUILDROOT/wheelhouse"
+      fi
+  fi
+  if [[ $? -ne 0 ]]; then
+    printf "Error, unable to download/build wheel for %s @ %s" "$PKG" "$PKG_DIR"
+    exit 1
+  elif ! "$PYTHON_BUILDROOT/venv/bin/piprepo" build "$PYTHON_BUILDROOT/wheelhouse"; then
+    printf "Error, unable to update local wheel repository"
+    exit 1
+  fi
+
+  if [[ -n "$ONLY_BUILD" ]] && [[ "$PYTHON_PKG" != "$ONLY_BUILD" ]] && [[ "$PKG" != "$ONLY_BUILD" ]]; then
+    return 0
+  fi
 
 
-  rm -rf dist/*
-  local venv_dir="dist/build/usr/lib/$PYTHON_PKG"
+  local venv_dir="$PYTHON_BUILDROOT/$PYTHON_PKG"
   echo "Creating virtualenv..."
   if ! "$PYTHON3_EXECUTABLE" -m venv "$venv_dir"; then
     printf "Error, unable to run\n  %s -m venv %s\n" "$PYTHON3_EXECUTABLE" "$venv_dir"
     exit 1
   echo "Creating virtualenv..."
   if ! "$PYTHON3_EXECUTABLE" -m venv "$venv_dir"; then
     printf "Error, unable to run\n  %s -m venv %s\n" "$PYTHON3_EXECUTABLE" "$venv_dir"
     exit 1
-  fi
-
-  local venv_py="$venv_dir/bin/python$PYTHON3_VERSION"
-  if ! "$venv_py" -m pip install --upgrade $DASHQ_UNLESS_DEBUG $CACHE_FLAG pip setuptools wheel; then
-    printf "Error, unable to upgrade pip, setuptools, and wheel with
-  %s -m pip install --upgrade $DASHQ_UNLESS_DEBUG $CACHE_FLAG pip setuptools wheel
-" "$venv_py"
+  # We must have the dependency resolver introduced in late 2020 for the rest
+  # of our install process to work.
+  # <https://blog.python.org/2020/11/pip-20-3-release-new-resolver.html>
+  elif ! "$venv_dir/bin/pip" install "pip>=20.3"; then
+    printf "Error, unable to run\n  %s/bin/pip install 'pip>=20.3'\n" "$venv_dir"
     exit 1
   fi
 
     exit 1
   fi
 
-  # filter a useless warning (when building the cwltest package) from the stderr output
-  if ! "$venv_py" setup.py $DASHQ_UNLESS_DEBUG sdist 2> >(grep -v 'warning: no previously-included files matching'); then
-    echo "Error, unable to run $venv_py setup.py sdist for $PKG"
+  local pip_wheel="$(ls --sort=time --reverse "$PYTHON_BUILDROOT/wheelhouse/$(echo "$PKG" | sed s/-/_/g)-"*.whl | tail -n1)"
+  if [[ -z "$pip_wheel" ]]; then
+    printf "Error, unable to find built wheel for $PKG"
+    exit 1
+  elif ! "$venv_dir/bin/pip" install $DASHQ_UNLESS_DEBUG $CACHE_FLAG --extra-index-url="file://$PYTHON_BUILDROOT/wheelhouse/simple" "$pip_wheel"; then
+    printf "Error, unable to run
+  %s/bin/pip install $DASHQ_UNLESS_DEBUG $CACHE_FLAG --extra-index-url=file://%s %s
+" "$venv_dir" "$PYTHON_BUILDROOT/wheelhouse/simple" "$pip_wheel"
     exit 1
   fi
 
     exit 1
   fi
 
-  if [[ "arvados-python-client" == "$PKG" ]]; then
-    PYSDK_PATH="-f $(pwd)/dist/"
-  fi
-
-  if [[ -n "$ONLY_BUILD" ]] && [[ "$PYTHON_PKG" != "$ONLY_BUILD" ]] && [[ "$PKG" != "$ONLY_BUILD" ]]; then
-    return 0
-  fi
-
-  # Determine the package version from the generated sdist archive
-  if [[ -n "$ARVADOS_BUILDING_VERSION" ]] ; then
-      UNFILTERED_PYTHON_VERSION=$ARVADOS_BUILDING_VERSION
-      PYTHON_VERSION=$(echo -n $ARVADOS_BUILDING_VERSION | sed s/~dev/.dev/g | sed s/~rc/rc/g)
-  else
-      PYTHON_VERSION=$(awk '($1 == "Version:"){print $2}' *.egg-info/PKG-INFO)
-      UNFILTERED_PYTHON_VERSION=$(echo -n $PYTHON_VERSION | sed s/\.dev/~dev/g |sed 's/\([0-9]\)rc/\1~rc/g')
-  fi
+  # Determine the package version from the wheel
+  PYTHON_VERSION="$("$venv_dir/bin/python" "$WORKSPACE/build/pypkg_info.py" metadata "$PKG" Version)"
+  UNFILTERED_PYTHON_VERSION="$(echo "$PYTHON_VERSION" | sed 's/\.dev/~dev/; s/\([0-9]\)rc/\1~rc/')"
 
   # See if we actually need to build this package; does it exist already?
   # We can't do this earlier than here, because we need PYTHON_VERSION.
   if ! test_package_presence "$PYTHON_PKG" "$UNFILTERED_PYTHON_VERSION" python3 "$ARVADOS_BUILDING_ITERATION" "$target_arch"; then
     return 0
   fi
 
   # See if we actually need to build this package; does it exist already?
   # We can't do this earlier than here, because we need PYTHON_VERSION.
   if ! test_package_presence "$PYTHON_PKG" "$UNFILTERED_PYTHON_VERSION" python3 "$ARVADOS_BUILDING_ITERATION" "$target_arch"; then
     return 0
   fi
-
   echo "Building $package_format ($target_arch) package for $PKG from $PKG_DIR"
 
   echo "Building $package_format ($target_arch) package for $PKG from $PKG_DIR"
 
-  local sdist_path="$(ls dist/*.tar.gz)"
-  if ! "$venv_py" -m pip install $DASHQ_UNLESS_DEBUG $CACHE_FLAG $PYSDK_PATH "$sdist_path"; then
-    printf "Error, unable to run
-  %s -m pip install $DASHQ_UNLESS_DEBUG $CACHE_FLAG %s %s
-" "$venv_py" "$PYSDK_PATH" "$sdist_path"
-    exit 1
-  fi
-
-  pushd "$venv_dir" >$STDOUT_IF_DEBUG
-
   # Replace the shebang lines in all python scripts, and handle the activate
   # scripts too. This is a functional replacement of the 237 line
   # virtualenv_tools.py script that doesn't work in python3 without serious
   # patching, minus the parts we don't need (modifying pyc files, etc).
   # Replace the shebang lines in all python scripts, and handle the activate
   # scripts too. This is a functional replacement of the 237 line
   # virtualenv_tools.py script that doesn't work in python3 without serious
   # patching, minus the parts we don't need (modifying pyc files, etc).
-  local sys_venv_dir="${venv_dir#dist/build/}"
+  local sys_venv_dir="/usr/lib/$PYTHON_PKG"
   local sys_venv_py="$sys_venv_dir/bin/python$PYTHON3_VERSION"
   local sys_venv_py="$sys_venv_dir/bin/python$PYTHON3_VERSION"
-  for binfile in `ls bin/`; do
-    if file --mime "bin/$binfile" | grep -q binary; then
+  find "$venv_dir/bin" -type f | while read binfile; do
+    if file --mime "$binfile" | grep -q binary; then
       :  # Nothing to do for binary files
       :  # Nothing to do for binary files
-    elif [[ "$binfile" =~ ^activate(.csh|.fish|)$ ]]; then
-      sed -ri "s@VIRTUAL_ENV(=| )\".*\"@VIRTUAL_ENV\\1\"/$sys_venv_dir\"@" "bin/$binfile"
+    elif [[ "$binfile" =~ /activate(.csh|.fish|)$ ]]; then
+      sed -ri "s@VIRTUAL_ENV(=| )\".*\"@VIRTUAL_ENV\\1\"$sys_venv_dir\"@" "$binfile"
     else
       # Replace shebang line
     else
       # Replace shebang line
-      sed -ri "1 s@^#\![^[:space:]]+/bin/python[0-9.]*@#\!/$sys_venv_py@" "bin/$binfile"
+      sed -ri "1 s@^#\![^[:space:]]+/bin/python[0-9.]*@#\!$sys_venv_py@" "$binfile"
     fi
   done
 
     fi
   done
 
-  popd >$STDOUT_IF_DEBUG
-  cd dist
-
-  find build -iname '*.py[co]' -delete
-
-  # Finally, generate the package
-  echo "Creating package..."
-
-  declare -a COMMAND_ARR=("fpm" "-s" "dir" "-t" "$package_format")
+  # Using `env -C` sets the directory where the package is built.
+  # Using `fpm --chdir` sets the root directory for source arguments.
+  declare -a COMMAND_ARR=(
+      env -C "$PYTHON_BUILDROOT" fpm
+      --chdir="$venv_dir"
+      --name="$PYTHON_PKG"
+      --version="$UNFILTERED_PYTHON_VERSION"
+      --input-type=dir
+      --output-type="$package_format"
+      --depends="$PYTHON3_PACKAGE"
+      --iteration="$ARVADOS_BUILDING_ITERATION"
+      --replaces="python-$PKG"
+      --url="https://arvados.org"
+  )
+  # Append fpm flags corresponding to Python package metadata.
+  readarray -d "" -O "${#COMMAND_ARR[@]}" -t COMMAND_ARR < \
+            <("$venv_dir/bin/python3" "$WORKSPACE/build/pypkg_info.py" \
+                                      --delimiter=\\0 --format=fpm \
+                                      metadata "$PKG" License Summary)
 
   if [[ -n "$target_arch" ]] && [[ "$target_arch" != "amd64" ]]; then
     COMMAND_ARR+=("-a$target_arch")
 
   if [[ -n "$target_arch" ]] && [[ "$target_arch" != "amd64" ]]; then
     COMMAND_ARR+=("-a$target_arch")
@@ -797,32 +810,16 @@ fpm_build_virtualenv_worker () {
     COMMAND_ARR+=('--vendor' "$VENDOR")
   fi
 
     COMMAND_ARR+=('--vendor' "$VENDOR")
   fi
 
-  COMMAND_ARR+=('--url' 'https://arvados.org')
-
-  # Get description
-  DESCRIPTION=`grep '\sdescription' $WORKSPACE/$PKG_DIR/setup.py|cut -f2 -d=|sed -e "s/[',\\"]//g"`
-  COMMAND_ARR+=('--description' "$DESCRIPTION")
-
-  # Get license string
-  LICENSE_STRING=`grep license $WORKSPACE/$PKG_DIR/setup.py|cut -f2 -d=|sed -e "s/[',\\"]//g"`
-  COMMAND_ARR+=('--license' "$LICENSE_STRING")
-
   if [[ "$DEBUG" != "0" ]]; then
     COMMAND_ARR+=('--verbose' '--log' 'info')
   fi
 
   if [[ "$DEBUG" != "0" ]]; then
     COMMAND_ARR+=('--verbose' '--log' 'info')
   fi
 
-  COMMAND_ARR+=('-v' $(echo -n "$PYTHON_VERSION" | sed s/.dev/~dev/g | sed s/rc/~rc/g))
-  COMMAND_ARR+=('--iteration' "$ARVADOS_BUILDING_ITERATION")
-  COMMAND_ARR+=('-n' "$PYTHON_PKG")
-  COMMAND_ARR+=('-C' "build")
-
-  systemd_unit="$WORKSPACE/$PKG_DIR/$PKG.service"
+  systemd_unit="$PKG_DIR/$PKG.service"
   if [[ -e "${systemd_unit}" ]]; then
     COMMAND_ARR+=('--after-install' "${WORKSPACE}/build/go-python-package-scripts/postinst")
     COMMAND_ARR+=('--before-remove' "${WORKSPACE}/build/go-python-package-scripts/prerm")
   fi
 
   if [[ -e "${systemd_unit}" ]]; then
     COMMAND_ARR+=('--after-install' "${WORKSPACE}/build/go-python-package-scripts/postinst")
     COMMAND_ARR+=('--before-remove' "${WORKSPACE}/build/go-python-package-scripts/prerm")
   fi
 
-  COMMAND_ARR+=('--depends' "$PYTHON3_PACKAGE")
   case "$package_format" in
       deb)
           COMMAND_ARR+=(
   case "$package_format" in
       deb)
           COMMAND_ARR+=(
@@ -845,7 +842,7 @@ fpm_build_virtualenv_worker () {
   declare -a fpm_args=()
   declare -a fpm_depends=()
 
   declare -a fpm_args=()
   declare -a fpm_depends=()
 
-  fpminfo="$WORKSPACE/$PKG_DIR/fpm-info.sh"
+  fpminfo="$PKG_DIR/fpm-info.sh"
   if [[ -e "$fpminfo" ]]; then
     echo "Loading fpm overrides from $fpminfo"
     if ! source "$fpminfo"; then
   if [[ -e "$fpminfo" ]]; then
     echo "Loading fpm overrides from $fpminfo"
     if ! source "$fpminfo"; then
@@ -858,37 +855,24 @@ fpm_build_virtualenv_worker () {
     COMMAND_ARR+=('--depends' "$i")
   done
 
     COMMAND_ARR+=('--depends' "$i")
   done
 
-  for i in "${fpm_depends[@]}"; do
-    COMMAND_ARR+=('--replaces' "python-$PKG")
-  done
-
   # make sure the systemd service file ends up in the right place
   # used by arvados-docker-cleaner
   if [[ -e "${systemd_unit}" ]]; then
   # make sure the systemd service file ends up in the right place
   # used by arvados-docker-cleaner
   if [[ -e "${systemd_unit}" ]]; then
-    COMMAND_ARR+=("$sys_venv_dir/share/doc/$PKG/$PKG.service=/lib/systemd/system/$PKG.service")
+    COMMAND_ARR+=("share/doc/$PKG/$PKG.service=/lib/systemd/system/$PKG.service")
   fi
 
   COMMAND_ARR+=("${fpm_args[@]}")
 
   fi
 
   COMMAND_ARR+=("${fpm_args[@]}")
 
-  # Make sure to install all our package binaries in /usr/bin. We have to
-  # walk $WORKSPACE/$PKG_DIR/bin rather than $venv_dir/bin to get the list
-  # because the latter also includes scripts installed by all the
-  # dependencies in the virtualenv, which may conflict with other
-  # packages. We have to take the copies of our binaries from the latter
-  # directory, though, because those are the ones we rewrote the shebang
-  # line of, above.
-  if [[ -e "$WORKSPACE/$PKG_DIR/bin" ]]; then
-    for binary in `ls $WORKSPACE/$PKG_DIR/bin`; do
-      COMMAND_ARR+=("$sys_venv_dir/bin/$binary=/usr/bin/")
-    done
-  fi
+  while read -d "" binpath; do
+      COMMAND_ARR+=("$binpath=/usr/$binpath")
+  done < <("$venv_dir/bin/python3" "$WORKSPACE/build/pypkg_info.py" --delimiter=\\0 binfiles "$PKG")
 
   # the python3-arvados-cwl-runner package comes with cwltool, expose that version
 
   # the python3-arvados-cwl-runner package comes with cwltool, expose that version
-  if [[ -e "$WORKSPACE/$PKG_DIR/$venv_dir/bin/cwltool" ]]; then
-    COMMAND_ARR+=("$sys_venv_dir/bin/cwltool=/usr/bin/")
+  if [[ "$PKG" == arvados-cwl-runner ]]; then
+    COMMAND_ARR+=("bin/cwltool=/usr/bin/cwltool")
   fi
 
   fi
 
-  COMMAND_ARR+=(".")
+  COMMAND_ARR+=(".=$sys_venv_dir")
 
   debug_echo -e "\n${COMMAND_ARR[@]}\n"
 
 
   debug_echo -e "\n${COMMAND_ARR[@]}\n"
 
@@ -901,8 +885,8 @@ fpm_build_virtualenv_worker () {
     echo
     echo -e "\n${COMMAND_ARR[@]}\n"
   else
     echo
     echo -e "\n${COMMAND_ARR[@]}\n"
   else
-    echo `ls *$package_format`
-    mv $WORKSPACE/$PKG_DIR/dist/*$package_format $WORKSPACE/packages/$TARGET/
+    ls "$PYTHON_BUILDROOT"/*."$package_format"
+    mv "$PYTHON_BUILDROOT"/*."$package_format" "$WORKSPACE/packages/$TARGET/"
   fi
   echo
 }
   fi
   echo
 }
index 1f28915a29269f19936ddd8a40085cc82b8c8be3..0eb421454e80dca7914b04c67266d563af957853 100755 (executable)
@@ -21,7 +21,8 @@ Options:
 --skip install Do not run any install steps. Just run tests.
                You should provide GOPATH, GEMHOME, and VENVDIR options
                from a previous invocation if you use this option.
 --skip install Do not run any install steps. Just run tests.
                You should provide GOPATH, GEMHOME, and VENVDIR options
                from a previous invocation if you use this option.
---only FOO     Do not test anything except the FOO component.
+--only FOO     Do not test anything except the FOO component. If given
+               more than once, all specified test suites are run.
 --temp DIR     Install components and dependencies under DIR instead of
                making a new temporary directory. Implies --leave-temp.
 --leave-temp   Do not remove GOPATH, virtualenv, and other temp dirs at exit.
 --temp DIR     Install components and dependencies under DIR instead of
                making a new temporary directory. Implies --leave-temp.
 --leave-temp   Do not remove GOPATH, virtualenv, and other temp dirs at exit.
@@ -29,16 +30,17 @@ Options:
                subsequent invocations.
 --repeat N     Repeat each install/test step until it succeeds N times.
 --retry        Prompt to retry if an install or test suite fails.
                subsequent invocations.
 --repeat N     Repeat each install/test step until it succeeds N times.
 --retry        Prompt to retry if an install or test suite fails.
---only-install Run specific install step
+--only-install Run specific install step. If given more than once,
+               all but the last are ignored.
 --short        Skip (or scale down) some slow tests.
 --interactive  Set up, then prompt for test/install steps to perform.
 WORKSPACE=path Arvados source tree to test.
 CONFIGSRC=path Dir with config.yml file containing PostgreSQL section for use by tests.
 services/api_test="TEST=test/functional/arvados/v1/collections_controller_test.rb"
                Restrict apiserver tests to the given file
 --short        Skip (or scale down) some slow tests.
 --interactive  Set up, then prompt for test/install steps to perform.
 WORKSPACE=path Arvados source tree to test.
 CONFIGSRC=path Dir with config.yml file containing PostgreSQL section for use by tests.
 services/api_test="TEST=test/functional/arvados/v1/collections_controller_test.rb"
                Restrict apiserver tests to the given file
-sdk/python_test="--test-suite tests.test_keep_locator"
+sdk/python_test="tests/test_api.py::ArvadosApiTest"
                Restrict Python SDK tests to the given class
                Restrict Python SDK tests to the given class
-services/githttpd_test="-check.vv"
+lib/dispatchcloud_test="-check.vv"
                Show all log messages, even when tests pass (also works
                with services/keepstore_test etc.)
 ARVADOS_DEBUG=1
                Show all log messages, even when tests pass (also works
                with services/keepstore_test etc.)
 ARVADOS_DEBUG=1
@@ -85,7 +87,6 @@ lib/mount
 lib/pam
 lib/service
 services/api
 lib/pam
 lib/service
 services/api
-services/githttpd
 services/dockercleaner
 services/fuse
 services/fuse:py3
 services/dockercleaner
 services/fuse
 services/fuse:py3
@@ -192,7 +193,7 @@ sanity_checks() {
         || fatal "Locale '${LANG}' is broken/missing. Try: echo ${LANG} | sudo tee -a /etc/locale.gen && sudo locale-gen"
     echo -n 'ruby: '
     ruby -v \
         || fatal "Locale '${LANG}' is broken/missing. Try: echo ${LANG} | sudo tee -a /etc/locale.gen && sudo locale-gen"
     echo -n 'ruby: '
     ruby -v \
-        || fatal "No ruby. Install >=2.1.9 (using rbenv, rvm, or source)"
+        || fatal "No ruby. Install >=2.7 from package or source"
     echo -n 'go: '
     go version \
         || fatal "No go binary. See http://golang.org/doc/install"
     echo -n 'go: '
     go version \
         || fatal "No go binary. See http://golang.org/doc/install"
@@ -219,9 +220,6 @@ sanity_checks() {
     echo -n 'nginx: '
     PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" nginx -v \
         || fatal "No nginx. Try: apt-get install nginx"
     echo -n 'nginx: '
     PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" nginx -v \
         || fatal "No nginx. Try: apt-get install nginx"
-    echo -n 'gitolite: '
-    which gitolite \
-        || fatal "No gitolite. Try: apt-get install gitolite3"
     echo -n 'npm: '
     npm --version \
         || fatal "No npm. Try: wget -O- https://nodejs.org/dist/v12.22.12/node-v12.22.12-linux-x64.tar.xz | sudo tar -C /usr/local -xJf - && sudo ln -s ../node-v12.22.12-linux-x64/bin/{node,npm} /usr/local/bin/"
     echo -n 'npm: '
     npm --version \
         || fatal "No npm. Try: wget -O- https://nodejs.org/dist/v12.22.12/node-v12.22.12-linux-x64.tar.xz | sudo tar -C /usr/local -xJf - && sudo ln -s ../node-v12.22.12-linux-x64/bin/{node,npm} /usr/local/bin/"
@@ -393,7 +391,7 @@ start_services() {
         return 0
     fi
     . "$VENV3DIR/bin/activate"
         return 0
     fi
     . "$VENV3DIR/bin/activate"
-    echo 'Starting API, controller, keepproxy, keep-web, githttpd, ws, and nginx ssl proxy...'
+    echo 'Starting API, controller, keepproxy, keep-web, ws, and nginx ssl proxy...'
     if [[ ! -d "$WORKSPACE/services/api/log" ]]; then
         mkdir -p "$WORKSPACE/services/api/log"
     fi
     if [[ ! -d "$WORKSPACE/services/api/log" ]]; then
         mkdir -p "$WORKSPACE/services/api/log"
     fi
@@ -421,9 +419,6 @@ start_services() {
         && python3 sdk/python/tests/run_test_server.py start_keep-web \
         && checkpidfile keep-web \
         && checkhealth WebDAV \
         && python3 sdk/python/tests/run_test_server.py start_keep-web \
         && checkpidfile keep-web \
         && checkhealth WebDAV \
-        && python3 sdk/python/tests/run_test_server.py start_githttpd \
-        && checkpidfile githttpd \
-        && checkhealth GitHTTP \
         && python3 sdk/python/tests/run_test_server.py start_ws \
         && checkpidfile ws \
         && export ARVADOS_TEST_PROXY_SERVICES=1 \
         && python3 sdk/python/tests/run_test_server.py start_ws \
         && checkpidfile ws \
         && export ARVADOS_TEST_PROXY_SERVICES=1 \
@@ -444,7 +439,6 @@ stop_services() {
     . "$VENV3DIR/bin/activate" || return
     cd "$WORKSPACE" \
         && python3 sdk/python/tests/run_test_server.py stop_nginx \
     . "$VENV3DIR/bin/activate" || return
     cd "$WORKSPACE" \
         && python3 sdk/python/tests/run_test_server.py stop_nginx \
-        && python3 sdk/python/tests/run_test_server.py stop_githttpd \
         && python3 sdk/python/tests/run_test_server.py stop_ws \
         && python3 sdk/python/tests/run_test_server.py stop_keep-web \
         && python3 sdk/python/tests/run_test_server.py stop_keep_proxy \
         && python3 sdk/python/tests/run_test_server.py stop_ws \
         && python3 sdk/python/tests/run_test_server.py stop_keep-web \
         && python3 sdk/python/tests/run_test_server.py stop_keep_proxy \
@@ -466,98 +460,36 @@ interrupt() {
 trap interrupt INT
 
 setup_ruby_environment() {
 trap interrupt INT
 
 setup_ruby_environment() {
-    if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
-        source "$HOME/.rvm/scripts/rvm"
-        using_rvm=true
-    elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
-        source "/usr/local/rvm/scripts/rvm"
-        using_rvm=true
-    else
-        using_rvm=false
-    fi
-
-    if [[ "$using_rvm" == true ]]; then
-        # If rvm is in use, we can't just put separate "dependencies"
-        # and "gems-under-test" paths to GEM_PATH: passenger resets
-        # the environment to the "current gemset", which would lose
-        # our GEM_PATH and prevent our test suites from running ruby
-        # programs (for example, the Workbench test suite could not
-        # boot an API server or run arv). Instead, we have to make an
-        # rvm gemset and use it for everything.
-
-        [[ `type rvm | head -n1` == "rvm is a function" ]] \
-            || fatal 'rvm check'
-
-        # Put rvm's favorite path back in first place (overriding
-        # virtualenv, which just put itself there). Ignore rvm's
-        # complaint about not being in first place already.
-        rvm use @default 2>/dev/null
-
-        # Create (if needed) and switch to an @arvados-tests-* gemset,
-        # salting the gemset name so it doesn't interfere with
-        # concurrent builds in other workspaces. Leave the choice of
-        # ruby to the caller.
-        gemset="arvados-tests-$(echo -n "${WORKSPACE}" | md5sum | head -c16)"
-        rvm use "@${gemset}" --create \
-            || fatal 'rvm gemset setup'
-
-        rvm env
-        (bundle version | grep -q 2.2.19) || gem install --no-document bundler -v 2.2.19
-        bundle="$(which bundle)"
-        echo "$bundle"
-        "$bundle" version | grep 2.2.19 || fatal 'install bundler'
-    else
-        # When our "bundle install"s need to install new gems to
-        # satisfy dependencies, we want them to go where "gem install
-        # --user-install" would put them. (However, if the caller has
-        # already set GEM_HOME, we assume that's where dependencies
-        # should be installed, and we should leave it alone.)
-
-        if [ -z "$GEM_HOME" ]; then
-            user_gempath="$(gem env gempath)"
-            export GEM_HOME="${user_gempath%%:*}"
-        fi
-        PATH="$(gem env gemdir)/bin:$PATH"
-
-        # When we build and install our own gems, we install them in our
-        # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
-        # PATH so integration tests prefer them over other versions that
-        # happen to be installed in $user_gempath, system dirs, etc.
-
-        tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
-        PATH="$tmpdir_gem_home/bin:$PATH"
-        export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
-
-        echo "Will install dependencies to $(gem env gemdir)"
-        echo "Will install bundler and arvados gems to $tmpdir_gem_home"
-        echo "Gem search path is GEM_PATH=$GEM_PATH"
-        bundle="bundle"
-        (
-            export HOME=$GEMHOME
-            versions=(2.2.19)
-            for v in ${versions[@]}; do
-                if ! gem list --installed --version "${v}" bundler >/dev/null; then
-                    gem install --no-document --user $(for v in ${versions[@]}; do echo bundler:${v}; done)
-                    break
-                fi
-            done
-            "$bundle" version | tee /dev/stderr | grep -q 'version 2'
-        ) || fatal 'install bundler'
-       if test -d /var/lib/arvados-arvbox/ ; then
-           # Inside arvbox, use bundler-installed binstubs.  The
-           # system bundler and rail's own bin/bundle refuse to work.
-           # I don't know why.
-           bundle=binstubs/bundle
-       fi
+    # When our "bundle install"s need to install new gems to
+    # satisfy dependencies, we want them to go where "gem install
+    # --user-install" would put them. (However, if the caller has
+    # already set GEM_HOME, we assume that's where dependencies
+    # should be installed, and we should leave it alone.)
+
+    if [ -z "$GEM_HOME" ]; then
+        user_gempath="$(gem env gempath)"
+        export GEM_HOME="${user_gempath%%:*}"
     fi
     fi
+    PATH="$(gem env gemdir)/bin:$PATH"
+
+    # When we build and install our own gems, we install them in our
+    # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
+    # PATH so integration tests prefer them over other versions that
+    # happen to be installed in $user_gempath, system dirs, etc.
+
+    tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
+    PATH="$tmpdir_gem_home/bin:$PATH"
+    export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
+
+    echo "Will install dependencies to $(gem env gemdir)"
+    echo "Will install bundler and arvados gems to $tmpdir_gem_home"
+    echo "Gem search path is GEM_PATH=$GEM_PATH"
+    gem install --user --no-document --conservative --version '~> 2.4.0' bundler \
+        || fatal 'install bundler'
 }
 
 with_test_gemset() {
 }
 
 with_test_gemset() {
-    if [[ "$using_rvm" == true ]]; then
-        "$@"
-    else
-        GEM_HOME="$tmpdir_gem_home" GEM_PATH="$tmpdir_gem_home" "$@"
-    fi
+    GEM_HOME="$tmpdir_gem_home" GEM_PATH="$tmpdir_gem_home" "$@"
 }
 
 gem_uninstall_if_exists() {
 }
 
 gem_uninstall_if_exists() {
@@ -622,8 +554,6 @@ initialize() {
 
     unset http_proxy https_proxy no_proxy
 
 
     unset http_proxy https_proxy no_proxy
 
-    # Note: this must be the last time we change PATH, otherwise rvm will
-    # whine a lot.
     setup_ruby_environment
 
     echo "PATH is $PATH"
     setup_ruby_environment
 
     echo "PATH is $PATH"
@@ -636,13 +566,16 @@ install_env() {
     setup_virtualenv "$VENV3DIR"
     . "$VENV3DIR/bin/activate"
 
     setup_virtualenv "$VENV3DIR"
     . "$VENV3DIR/bin/activate"
 
+    # parameterized and pytest are direct dependencies of Python tests.
     # PyYAML is a test requirement used by run_test_server.py and needed for
     # other, non-Python tests.
     # pdoc is needed to build PySDK documentation.
     # PyYAML is a test requirement used by run_test_server.py and needed for
     # other, non-Python tests.
     # pdoc is needed to build PySDK documentation.
+    # wheel modernizes the venv (as of early 2024) and makes it more closely
+    # match our package build environment.
     # We run `setup.py build` first to generate _version.py.
     # We run `setup.py build` first to generate _version.py.
-    env -C "$WORKSPACE/sdk/python" python3 setup.py build \
-        && python3 -m pip install "$WORKSPACE/sdk/python" \
-        && python3 -m pip install PyYAML pdoc \
+    pip install parameterized pytest PyYAML pdoc wheel \
+        && env -C "$WORKSPACE/sdk/python" python3 setup.py build \
+        && pip install "$WORKSPACE/sdk/python" \
         || fatal "installing Python SDK and related dependencies failed"
 }
 
         || fatal "installing Python SDK and related dependencies failed"
 }
 
@@ -689,6 +622,7 @@ do_test() {
             check_arvados_config "$1"
             ;;
         gofmt \
             check_arvados_config "$1"
             ;;
         gofmt \
+            | arvados_version.py \
             | cmd/arvados-package \
             | doc \
             | lib/boot \
             | cmd/arvados-package \
             | doc \
             | lib/boot \
@@ -768,7 +702,7 @@ do_test_once() {
     elif [[ "$2" == "pip" ]]
     then
         tries=0
     elif [[ "$2" == "pip" ]]
     then
         tries=0
-        cd "$WORKSPACE/$1" && while :
+        cd "$WORKSPACE/$1" && pip install . && while :
         do
             tries=$((${tries}+1))
             # $3 can name a path directory for us to use, including trailing
         do
             tries=$((${tries}+1))
             # $3 can name a path directory for us to use, including trailing
@@ -776,11 +710,13 @@ do_test_once() {
             if [[ -e "${3}activate" ]]; then
                 . "${3}activate"
             fi
             if [[ -e "${3}activate" ]]; then
                 . "${3}activate"
             fi
-            python setup.py ${short:+--short-tests-only} test ${testargs[$1]}
+            python3 -m pytest ${testargs[$1]}
             result=$?
             result=$?
-            if [[ ${tries} < 3 && ${result} == 137 ]]
+            # pytest uses exit code 2 to mean "test collection failed."
+            # See discussion in FUSE's IntegrationTest and MountTestBase.
+            if [[ ${tries} < 3 && ${result} == 2 ]]
             then
             then
-                printf '\n*****\n%s tests killed -- retrying\n*****\n\n' "$1"
+                printf '\n*****\n%s tests exited with code 2 -- retrying\n*****\n\n' "$1"
                 continue
             else
                 break
                 continue
             else
                 break
@@ -817,7 +753,7 @@ check_arvados_config() {
 }
 
 do_install() {
 }
 
 do_install() {
-    if [[ -n "${skip[install]}" || ( -n "${only_install}" && "${only_install}" != "${1}" && "${only_install}" != "${2}" ) ]]; then
+    if [[ -n ${skip["install_$1"]} || -n "${skip[install]}" || ( -n "${only_install}" && "${only_install}" != "${1}" && "${only_install}" != "${2}" ) ]]; then
         return 0
     fi
     check_arvados_config "$1"
         return 0
     fi
     check_arvados_config "$1"
@@ -870,11 +806,11 @@ bundle_install_trylocal() {
     (
         set -e
         echo "(Running bundle install --local. 'could not find package' messages are OK.)"
     (
         set -e
         echo "(Running bundle install --local. 'could not find package' messages are OK.)"
-        if ! "$bundle" install --local --no-deployment; then
+        if ! bundle install --local --no-deployment; then
             echo "(Running bundle install again, without --local.)"
             echo "(Running bundle install again, without --local.)"
-            "$bundle" install --no-deployment
+            bundle install --no-deployment
         fi
         fi
-        "$bundle" package
+        bundle package
     )
 }
 
     )
 }
 
@@ -948,15 +884,6 @@ install_services/api() {
         ) || return 1
     fi
 
         ) || return 1
     fi
 
-    cd "$WORKSPACE/services/api" \
-        && rm -rf tmp/git \
-        && mkdir -p tmp/git \
-        && cd tmp/git \
-        && tar xf ../../test/test.git.tar \
-        && mkdir -p internal.git \
-        && git --git-dir internal.git init \
-            || return 1
-
     (
         set -ex
         cd "$WORKSPACE/services/api"
     (
         set -ex
         cd "$WORKSPACE/services/api"
@@ -992,7 +919,7 @@ install_services/workbench2() {
 test_doc() {
     local arvados_api_host=pirca.arvadosapi.com && \
         env -C "$WORKSPACE/doc" \
 test_doc() {
     local arvados_api_host=pirca.arvadosapi.com && \
         env -C "$WORKSPACE/doc" \
-        "$bundle" exec rake linkchecker \
+        bundle exec rake linkchecker \
         arvados_api_host="$arvados_api_host" \
         arvados_workbench_host="https://workbench.$arvados_api_host" \
         baseurl="file://$WORKSPACE/doc/.site/" \
         arvados_api_host="$arvados_api_host" \
         arvados_workbench_host="https://workbench.$arvados_api_host" \
         baseurl="file://$WORKSPACE/doc/.site/" \
@@ -1006,15 +933,32 @@ test_gofmt() {
     go vet -composites=false ./...
 }
 
     go vet -composites=false ./...
 }
 
+test_arvados_version.py() {
+    local orig_fn=""
+    local fail_count=0
+    while read -d "" fn; do
+        if [[ -z "$orig_fn" ]]; then
+            orig_fn="$fn"
+        elif ! cmp "$orig_fn" "$fn"; then
+            fail_count=$(( $fail_count + 1 ))
+            printf "FAIL: %s and %s are not identical\n" "$orig_fn" "$fn"
+        fi
+    done < <(git -C "$WORKSPACE" ls-files -z | grep -z '/arvados_version\.py$')
+    case "$orig_fn" in
+        "") return 66 ;;  # EX_NOINPUT
+        *) return "$fail_count" ;;
+    esac
+}
+
 test_services/api() {
     rm -f "$WORKSPACE/services/api/git-commit.version"
     cd "$WORKSPACE/services/api" \
 test_services/api() {
     rm -f "$WORKSPACE/services/api/git-commit.version"
     cd "$WORKSPACE/services/api" \
-        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} "$bundle" exec rake test TESTOPTS=\'-v -d\' ${testargs[services/api]}
+        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} bundle exec rake test TESTOPTS=\'-v -d\' ${testargs[services/api]}
 }
 
 test_sdk/ruby() {
     cd "$WORKSPACE/sdk/ruby" \
 }
 
 test_sdk/ruby() {
     cd "$WORKSPACE/sdk/ruby" \
-        && "$bundle" exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
+        && bundle exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
 }
 
 test_sdk/ruby-google-api-client() {
 }
 
 test_sdk/ruby-google-api-client() {
@@ -1032,7 +976,7 @@ test_sdk/R() {
 test_sdk/cli() {
     cd "$WORKSPACE/sdk/cli" \
         && mkdir -p /tmp/keep \
 test_sdk/cli() {
     cd "$WORKSPACE/sdk/cli" \
         && mkdir -p /tmp/keep \
-        && KEEP_LOCAL_STORE=/tmp/keep "$bundle" exec rake test TESTOPTS=-v ${testargs[sdk/cli]}
+        && KEEP_LOCAL_STORE=/tmp/keep bundle exec rake test TESTOPTS=-v ${testargs[sdk/cli]}
 }
 
 test_sdk/java-v2() {
 }
 
 test_sdk/java-v2() {
@@ -1041,7 +985,7 @@ test_sdk/java-v2() {
 
 test_services/login-sync() {
     cd "$WORKSPACE/services/login-sync" \
 
 test_services/login-sync() {
     cd "$WORKSPACE/services/login-sync" \
-        && "$bundle" exec rake test TESTOPTS=-v ${testargs[services/login-sync]}
+        && bundle exec rake test TESTOPTS=-v ${testargs[services/login-sync]}
 }
 
 test_services/workbench2_units() {
 }
 
 test_services/workbench2_units() {
@@ -1056,7 +1000,6 @@ install_deps() {
     # Install parts needed by test suites
     do_install env
     do_install cmd/arvados-server go
     # Install parts needed by test suites
     do_install env
     do_install cmd/arvados-server go
-    do_install sdk/python pip "${VENV3DIR}/bin/"
     do_install tools/crunchstat-summary pip "${VENV3DIR}/bin/"
     do_install sdk/ruby-google-api-client
     do_install sdk/ruby
     do_install tools/crunchstat-summary pip "${VENV3DIR}/bin/"
     do_install sdk/ruby-google-api-client
     do_install sdk/ruby
@@ -1095,15 +1038,8 @@ install_all() {
 test_all() {
     stop_services
     do_test services/api
 test_all() {
     stop_services
     do_test services/api
-
-    # Shortcut for when we're only running apiserver tests. This saves a bit of time,
-    # because we don't need to start up the api server for subsequent tests.
-    if [ ! -z "$only" ] && [ "$only" == "services/api" ]; then
-        rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
-        exit_cleanly
-    fi
-
     do_test gofmt
     do_test gofmt
+    do_test arvados_version.py
     do_test doc
     do_test sdk/ruby-google-api-client
     do_test sdk/ruby
     do_test doc
     do_test sdk/ruby-google-api-client
     do_test sdk/ruby
@@ -1172,7 +1108,7 @@ if [[ -z ${interactive} ]]; then
 else
     skip=()
     only=()
 else
     skip=()
     only=()
-    only_install=()
+    only_install=""
     if [[ -e "$VENV3DIR/bin/activate" ]]; then stop_services; fi
     setnextcmd() {
         if [[ "$TERM" = dumb ]]; then
     if [[ -e "$VENV3DIR/bin/activate" ]]; then stop_services; fi
     setnextcmd() {
         if [[ "$TERM" = dumb ]]; then
index 19d13437c898baad0fb2ceaea363c56ef7318d44..62630a6a01b0651d8701aa08f3f22469a75ad405 100644 (file)
@@ -37,19 +37,9 @@ var (
                "container":                cli.APICall,
                "container_request":        cli.APICall,
                "group":                    cli.APICall,
                "container":                cli.APICall,
                "container_request":        cli.APICall,
                "group":                    cli.APICall,
-               "human":                    cli.APICall,
-               "job":                      cli.APICall,
-               "job_task":                 cli.APICall,
-               "keep_disk":                cli.APICall,
                "keep_service":             cli.APICall,
                "link":                     cli.APICall,
                "log":                      cli.APICall,
                "keep_service":             cli.APICall,
                "link":                     cli.APICall,
                "log":                      cli.APICall,
-               "node":                     cli.APICall,
-               "pipeline_instance":        cli.APICall,
-               "pipeline_template":        cli.APICall,
-               "repository":               cli.APICall,
-               "specimen":                 cli.APICall,
-               "trait":                    cli.APICall,
                "user_agreement":           cli.APICall,
                "user":                     cli.APICall,
                "virtual_machine":          cli.APICall,
                "user_agreement":           cli.APICall,
                "user":                     cli.APICall,
                "virtual_machine":          cli.APICall,
diff --git a/cmd/arvados-server/arvados-git-httpd.service b/cmd/arvados-server/arvados-git-httpd.service
deleted file mode 100644 (file)
index 517a75c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-[Unit]
-Description=Arvados git server
-Documentation=https://doc.arvados.org/
-After=network.target
-AssertPathExists=/etc/arvados/config.yml
-StartLimitIntervalSec=0
-
-[Service]
-Type=notify
-EnvironmentFile=-/etc/arvados/environment
-ExecStart=/usr/bin/arvados-git-httpd
-# Set a reasonable default for the open file limit
-LimitNOFILE=65536
-Restart=always
-RestartSec=1
-RestartPreventExitStatus=2
-
-[Install]
-WantedBy=multi-user.target
index c02b8fb57cf90cc318604d0d24063751c7da90d1..0f267a9b408f70afe2f370f172447fbf7128db2c 100644 (file)
@@ -30,7 +30,6 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/health"
        dispatchslurm "git.arvados.org/arvados.git/services/crunch-dispatch-slurm"
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/health"
        dispatchslurm "git.arvados.org/arvados.git/services/crunch-dispatch-slurm"
-       "git.arvados.org/arvados.git/services/githttpd"
        keepbalance "git.arvados.org/arvados.git/services/keep-balance"
        keepweb "git.arvados.org/arvados.git/services/keep-web"
        "git.arvados.org/arvados.git/services/keepproxy"
        keepbalance "git.arvados.org/arvados.git/services/keep-balance"
        keepweb "git.arvados.org/arvados.git/services/keep-web"
        "git.arvados.org/arvados.git/services/keepproxy"
@@ -57,7 +56,6 @@ var (
                "dispatch-cloud":     dispatchcloud.Command,
                "dispatch-lsf":       lsf.DispatchCommand,
                "dispatch-slurm":     dispatchslurm.Command,
                "dispatch-cloud":     dispatchcloud.Command,
                "dispatch-lsf":       lsf.DispatchCommand,
                "dispatch-slurm":     dispatchslurm.Command,
-               "git-httpd":          githttpd.Command,
                "health":             healthCommand,
                "install":            install.Command,
                "init":               install.InitCommand,
                "health":             healthCommand,
                "install":            install.Command,
                "init":               install.InitCommand,
index 053922a24a4890ad4c26f21dd0f34036aaf5aa36..d64bc4b7dc5463880606737ea6ec18218f7ff16c 100644 (file)
@@ -141,7 +141,6 @@ navbar:
       - api/methods/pipeline_instances.html.textile.liquid
       - api/methods/pipeline_templates.html.textile.liquid
       - api/methods/nodes.html.textile.liquid
       - api/methods/pipeline_instances.html.textile.liquid
       - api/methods/pipeline_templates.html.textile.liquid
       - api/methods/nodes.html.textile.liquid
-      - api/methods/repositories.html.textile.liquid
       - api/methods/keep_disks.html.textile.liquid
     - Metadata for bioinformatics (legacy):
       - api/methods/humans.html.textile.liquid
       - api/methods/keep_disks.html.textile.liquid
     - Metadata for bioinformatics (legacy):
       - api/methods/humans.html.textile.liquid
@@ -243,13 +242,9 @@ navbar:
       - install/install-ws.html.textile.liquid
       - install/install-workbench2-app.html.textile.liquid
       - install/workbench.html.textile.liquid
       - install/install-ws.html.textile.liquid
       - install/install-workbench2-app.html.textile.liquid
       - install/workbench.html.textile.liquid
-#      - install/install-composer.html.textile.liquid
     - Additional services:
       - install/install-shell-server.html.textile.liquid
       - install/install-webshell.html.textile.liquid
     - Additional services:
       - install/install-shell-server.html.textile.liquid
       - install/install-webshell.html.textile.liquid
-      - install/install-arv-git-httpd.html.textile.liquid
-#    - Containers API (all):
-#      - install/install-jobs-image.html.textile.liquid
     - Containers API (cloud):
       - install/crunch2-cloud/install-compute-node.html.textile.liquid
       - install/crunch2-cloud/install-dispatch-cloud.html.textile.liquid
     - Containers API (cloud):
       - install/crunch2-cloud/install-compute-node.html.textile.liquid
       - install/crunch2-cloud/install-dispatch-cloud.html.textile.liquid
diff --git a/doc/_includes/_container_glob_patterns.liquid b/doc/_includes/_container_glob_patterns.liquid
new file mode 100644 (file)
index 0000000..4015e16
--- /dev/null
@@ -0,0 +1,27 @@
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+h2. Glob patterns
+
+Each pattern in the @output_glob@ array can include the following special terms:
+
+table(table table-bordered table-condensed).
+|@*@|matches any sequence of non-@/@ characters|
+|@?@|matches any single non-@/@ character|
+|@[abcde]@ or @[a-e]@|matches any non-@/@ character in @abcde@|
+|@[^abcde]@ or @[^a-e]@ or
+@[!abcde]@ or @[!a-e]@|matches any non-@/@ character other than @abcde@|
+|@/**/@|matches zero or more levels of subdirectories|
+|@**/@|at the beginning of a pattern, matches zero or more directories|
+|@/**@|at the end of a pattern, matches any file in any subdirectory|
+
+Example patterns:
+
+table(table table-bordered table-condensed).
+|@*.txt@|matches files with extension @.txt@ at the top level|
+|@foo/**@|matches the entire tree rooted at @foo@ in the top level|
+|@**/fo[og]@|matches all files named @foo@ or @fog@ anywhere in the tree|
+|@foo/**/*.txt@|matches all files with extension @.txt@ anywhere in the tree rooted at @foo@ in the top level|
index 5d5bc9e9d7c3b30ac51fb0b8d434d08a519bef66..4381d9fedf0f91ccf25e644bdb17548e90d9d479 100644 (file)
@@ -6,14 +6,7 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 
 Ruby 2.7 or newer is required.
 
 
 Ruby 2.7 or newer is required.
 
-* "Option 1: Install from packages":#packages
-* "Option 2: Install with RVM":#rvm
-
-h2(#packages). Option 1: Install from packages
-
-h3. Alma/CentOS/Red Hat/Rocky
-
-Version 7 of these distributions does not provide a new enough Ruby version.  Use "RVM":#rvm to install Ruby 2.7 or newer.
+h2. Alma/CentOS/Red Hat/Rocky
 
 Version 8 of these distributions provides Ruby 2.7. You can install it by running:
 
 
 Version 8 of these distributions provides Ruby 2.7. You can install it by running:
 
@@ -22,63 +15,10 @@ Version 8 of these distributions provides Ruby 2.7. You can install it by runnin
 # <span class="userinput">dnf install --enablerepo=devel ruby ruby-devel</span></code></pre>
 </notextile>
 
 # <span class="userinput">dnf install --enablerepo=devel ruby ruby-devel</span></code></pre>
 </notextile>
 
-h3. Debian and Ubuntu
-
-Debian 10 (buster) and Ubuntu 18.04 (bionic) ship with Ruby 2.5, which is too old for Arvados. Use "RVM":#rvm to install Ruby 2.7 or newer.
+h2. Debian and Ubuntu
 
 Debian 11 (bullseye) and Ubuntu 20.04 (focal) and later ship with Ruby 2.7 or newer, which is sufficient for Arvados.
 
 <notextile>
 <pre><code># <span class="userinput">apt-get --no-install-recommends install ruby ruby-dev</span></code></pre>
 </notextile>
 
 Debian 11 (bullseye) and Ubuntu 20.04 (focal) and later ship with Ruby 2.7 or newer, which is sufficient for Arvados.
 
 <notextile>
 <pre><code># <span class="userinput">apt-get --no-install-recommends install ruby ruby-dev</span></code></pre>
 </notextile>
-
-h2(#rvm). Option 2: Install with RVM
-
-{% include 'notebox_begin_warning' %}
-We do not recommend using RVM unless the Ruby version provided by your OS distribution is older than 2.7.
-{% include 'notebox_end' %}
-
-h3. Install gpg and curl
-
-h4. CentOS/Red Hat 7
-
-<pre>
-yum install gpg curl which findutils procps
-</pre>
-
-{% comment %}
-To build ruby 3.2.2 on CentOS 7, add: "yum --enablerepo=powertools install libyaml-devel"
-{% endcomment %}
-
-h4. Alma/CentOS/Red Hat/Rocky 8+
-
-<pre>
-dnf install gpg curl which findutils procps
-</pre>
-
-h4. Debian and Ubuntu
-
-<pre>
-apt-get --no-install-recommends install gpg curl ca-certificates dirmngr procps
-</pre>
-
-h3. Install RVM, Ruby and Bundler
-
-<notextile>
-<pre><code><span class="userinput">gpg --keyserver pgp.mit.edu --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
-\curl -sSL https://get.rvm.io | bash -s stable --ruby=2.7.7
-</span></code></pre></notextile>
-
-This command installs the Ruby 2.7.7 release, as well as the @gem@ and @bundle@ commands.
-
-To use Ruby installed from RVM, load it in an open shell like this:
-
-<notextile>
-<pre><code><span class="userinput">source /usr/local/rvm/scripts/rvm
-</span></code></pre></notextile>
-
-Alternately you can use @rvm-exec@ (the first parameter is the ruby version to use, or "default"), for example:
-
-<notextile>
-<pre><code><span class="userinput">rvm-exec default ruby -v
-</span></code></pre></notextile>
index 86e05be8663cc6053f72a6565bf5a0b628095b1a..f22f7d35514afd1822b02cd996e5ff417131b794 100644 (file)
@@ -24,15 +24,6 @@ At container startup, the target path will have the same directory structure as
  "kind":"collection",
  "uuid":"..."
 }</code></pre>|
  "kind":"collection",
  "uuid":"..."
 }</code></pre>|
-|Git tree|@git_tree@|@"uuid"@ must be the UUID of an Arvados-hosted git repository.
-@"commit"@ must be a full 40-character commit hash.
-@"path"@, if provided, must be "/".
-At container startup, the target path will have the source tree indicated by the given commit. The @.git@ metadata directory _will not_ be available.|<pre><code>{
- "kind":"git_tree",
- "uuid":"zzzzz-s0uqq-xxxxxxxxxxxxxxx",
- "commit":"f315c59f90934cccae6381e72bba59d27ba42099"
-}
-</code></pre>|
 |Temporary directory|@tmp@|@"capacity"@: capacity (in bytes) of the storage device.
 @"device_type"@ (optional, default "network"): one of @{"ram", "ssd", "disk", "network"}@ indicating the acceptable level of performance. (*note: not yet implemented as of v1.5*)
 At container startup, the target path will be empty. When the container finishes, the content will be discarded. This will be backed by a storage mechanism no slower than the specified type.|<pre><code>{
 |Temporary directory|@tmp@|@"capacity"@: capacity (in bytes) of the storage device.
 @"device_type"@ (optional, default "network"): one of @{"ram", "ssd", "disk", "network"}@ indicating the acceptable level of performance. (*note: not yet implemented as of v1.5*)
 At container startup, the target path will be empty. When the container finishes, the content will be discarded. This will be backed by a storage mechanism no slower than the specified type.|<pre><code>{
index 8cb09f135f23dbbc8b4dccecf4643f99f72e8136..4bf72c1b31f48fcd654ed13a49b28067d8eb31b7 100644 (file)
@@ -5,7 +5,7 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
 
 {% endcomment %}
 
 
-Arvados requires a public SSH key in order to securely log in to an Arvados VM instance, or to access an Arvados Git repository. The three sections below help you get started:
+Arvados requires a public SSH key in order to securely log in to an Arvados VM instance. The three sections below help you get started:
 
 # "Getting your SSH key":#gettingkey
 # "Adding your key to Arvados Workbench":#workbench
 
 # "Getting your SSH key":#gettingkey
 # "Adding your key to Arvados Workbench":#workbench
diff --git a/doc/_includes/_tutorial_git_repo_expectations.liquid b/doc/_includes/_tutorial_git_repo_expectations.liquid
deleted file mode 100644 (file)
index 8a172de..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin' %}
-This tutorial assumes that you have a working Arvados repository. If you do not have a repository created, you can follow the instructions in the "Adding a new repository":{{site.baseurl}}/user/tutorials/add-new-repository.html page. We will use the *$USER/tutorial* repository created in that page as the example.
-{% include 'notebox_end' %}
index 3cf6e79722a4ae03b9f55b4b6fd8fd891fc34c03..e2d1404332dd08f3b1898cc7d9301750933dd817 100644 (file)
@@ -31,7 +31,6 @@ table(table table-bordered table-condensed).
 |controller     |yes                    |yes|yes ^2,4^|InternalURLs used by reverse proxy and container shell connections|
 |arvados-dispatch-cloud|no              |yes|no ^3^|InternalURLs only used to expose Prometheus metrics|
 |arvados-dispatch-lsf|no                |yes|no ^3^|InternalURLs only used to expose Prometheus metrics|
 |controller     |yes                    |yes|yes ^2,4^|InternalURLs used by reverse proxy and container shell connections|
 |arvados-dispatch-cloud|no              |yes|no ^3^|InternalURLs only used to expose Prometheus metrics|
 |arvados-dispatch-lsf|no                |yes|no ^3^|InternalURLs only used to expose Prometheus metrics|
-|git-http       |yes                    |yes|no ^2^|InternalURLs only used by reverse proxy (e.g. Nginx)|
 |git-ssh        |yes                    |no |no    ||
 |keepproxy      |yes                    |yes|no ^2^|InternalURLs only used by reverse proxy (e.g. Nginx)|
 |keepstore      |no                     |yes|yes   |All clients connect to InternalURLs|
 |git-ssh        |yes                    |no |no    ||
 |keepproxy      |yes                    |yes|no ^2^|InternalURLs only used by reverse proxy (e.g. Nginx)|
 |keepstore      |no                     |yes|yes   |All clients connect to InternalURLs|
@@ -174,10 +173,7 @@ server {
   index  index.html index.htm index.php;
 
   passenger_enabled on;
   index  index.html index.htm index.php;
 
   passenger_enabled on;
-
-  # If you are using RVM, uncomment the line below.
-  # If you're using system ruby, leave it commented out.
-  #passenger_ruby /usr/local/rvm/wrappers/default/ruby;
+  passenger_preload_bundler on;
 
   # This value effectively limits the size of API objects users can
   # create, especially collections.  If you change this, you should
 
   # This value effectively limits the size of API objects users can
   # create, especially collections.  If you change this, you should
index fff94cb55f63eee113884a0762ea50d4b3b3470d..601d26c5cb26973c7fa17fa9fec7973ccbeda911 100644 (file)
@@ -25,7 +25,6 @@ table(table table-bordered table-condensed table-hover){width:40em}.
 |arvados-controller|✓|
 |arvados-dispatch-cloud|✓|
 |arvados-dispatch-lsf|✓|
 |arvados-controller|✓|
 |arvados-dispatch-cloud|✓|
 |arvados-dispatch-lsf|✓|
-|arvados-git-httpd||
 |arvados-ws|✓|
 |composer||
 |keepproxy|✓|
 |arvados-ws|✓|
 |composer||
 |keepproxy|✓|
index a4939b740cba561025297a44e748511f2de7be0b..5650c5038d9a65999519212914c07c24103efa3c 100644 (file)
@@ -21,7 +21,6 @@ h2. API server and other services
 The following services also support monitoring.
 
 * API server
 The following services also support monitoring.
 
 * API server
-* arvados-git-httpd
 * controller
 * keep-balance
 * keepproxy
 * controller
 * keep-balance
 * keepproxy
index ed9fbbd7ae33f292697143af09363feb18cb5cc5..113536ff587b4d331ec5c0c8f6bc2164ab8e8594 100644 (file)
@@ -35,9 +35,7 @@ table(table table-bordered table-condensed table-hover).
 |arvados-controller|✓|
 |arvados-dispatch-cloud|✓|
 |arvados-dispatch-lsf|✓|
 |arvados-controller|✓|
 |arvados-dispatch-cloud|✓|
 |arvados-dispatch-lsf|✓|
-|arvados-git-httpd||
 |arvados-ws|✓|
 |arvados-ws|✓|
-|composer||
 |keepproxy|✓|
 |keepstore|✓|
 |keep-balance|✓|
 |keepproxy|✓|
 |keepstore|✓|
 |keep-balance|✓|
index 64a113b6f8aba840814119be4486de02870984dc..320f188812c6c6a27727c9122a9eb4790d5ba466 100644 (file)
@@ -142,7 +142,7 @@ We have introduced a small exception to the previous behavior of "Arvados API to
 
 h3. Deprecated/legacy APIs slated for removal
 
 
 h3. Deprecated/legacy APIs slated for removal
 
-The legacy APIs "humans":../api/methods/humans.html, "specimens":../api/methods/specimens.html, "traits":../api/methods/traits.html, "jobs":../api/methods/jobs.html, "job_tasks":../api/methods/job_tasks.html, "pipeline_instances":../api/methods/pipeline_instances.html, "pipeline_templates":../api/methods/pipeline_templates.html, "nodes":../api/methods/nodes.html, "repositories":../api/methods/repositories.html, and "keep_disks":../api/methods/keep_disks.html are deprecated and will be removed in a future major version of Arvados.
+The legacy APIs "humans":https://doc.arvados.org/v2.7/api/methods/humans.html, "specimens":https://doc.arvados.org/v2.7/api/methods/specimens.html, "traits":https://doc.arvados.org/v2.7/api/methods/traits.html, "jobs":https://doc.arvados.org/v2.7/api/methods/jobs.html, "job_tasks":https://doc.arvados.org/v2.7/api/methods/job_tasks.html, "pipeline_instances":https://doc.arvados.org/v2.7/api/methods/pipeline_instances.html, "pipeline_templates":https://doc.arvados.org/v2.7/api/methods/pipeline_templates.html, "nodes":https://doc.arvados.org/v2.7/api/methods/nodes.html, "repositories":https://doc.arvados.org/v2.7/api/methods/repositories.html, and "keep_disks":https://doc.arvados.org/v2.7/api/methods/keep_disks.html are deprecated and will be removed in a future major version of Arvados.
 
 In addition, the @default_owner_uuid@, @api_client_id@, and @user_id@ fields of "api_client_authorizations":../api/methods/api_client_authorizations.html are deprecated and will be removed from @api_client_authorization@ responses in a future major version of Arvados.  This should not affect clients as  @default_owner_uuid@ was never implemented, and @api_client_id@ and @user_id@ returned internal ids that were not meaningful or usable with any other API call.
 
 
 In addition, the @default_owner_uuid@, @api_client_id@, and @user_id@ fields of "api_client_authorizations":../api/methods/api_client_authorizations.html are deprecated and will be removed from @api_client_authorization@ responses in a future major version of Arvados.  This should not affect clients as  @default_owner_uuid@ was never implemented, and @api_client_id@ and @user_id@ returned internal ids that were not meaningful or usable with any other API call.
 
index c2d4743ddfdf5b58372ac9b31dfff9452eb2db26..dea705ddaa9d6710118e1dd98e13684dde5c5c83 100644 (file)
@@ -144,23 +144,3 @@ read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
 }
 EOF
 </pre>
 }
 EOF
 </pre>
-
-h3. Git repository
-
-Give @$user_uuid@ permission to commit to @$repo_uuid@ as @$repo_username@
-
-<pre>
-user_uuid=xxxxxxxchangeme
-repo_uuid=xxxxxxxchangeme
-repo_username=xxxxxxxchangeme
-
-read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
-{
-"tail_uuid":"$user_uuid",
-"head_uuid":"$repo_uuid",
-"link_class":"permission",
-"name":"can_write",
-"properties":{"username":"$repo_username"}
-}
-EOF
-</pre>
index 7d30ee88d1e70cbca7eb046e967e337abd154ac0..994081901ca86ebd9e5e65d9129eb4f14b956f52 100644 (file)
@@ -60,7 +60,6 @@ notextile. <div class="spaced-out">
 # A new user record is not set up, and not active.  An inactive user cannot create or update any object, but can read Arvados objects that the user account has permission to read (such as publicly available items readable by the "anonymous" user).
 # Using Workbench or the "command line":{{site.baseurl}}/admin/user-management-cli.html , the admin invokes @setup@ on the user.  The setup method adds the user to the "All users" group.
 - If "Users.AutoSetupNewUsers":config.html is true, this happens automatically during user creation, so in that case new users start at step (3).
 # A new user record is not set up, and not active.  An inactive user cannot create or update any object, but can read Arvados objects that the user account has permission to read (such as publicly available items readable by the "anonymous" user).
 # Using Workbench or the "command line":{{site.baseurl}}/admin/user-management-cli.html , the admin invokes @setup@ on the user.  The setup method adds the user to the "All users" group.
 - If "Users.AutoSetupNewUsers":config.html is true, this happens automatically during user creation, so in that case new users start at step (3).
-- If "Users.AutoSetupNewUsersWithRepository":config.html is true, a new git repo is created for the user.
 - If "Users.AutoSetupNewUsersWithVmUUID":config.html is set, the user is given login permission to the specified shell node
 # User is set up, but still not yet active.  The browser presents "user agreements":#user_agreements (if any) and then invokes the user @activate@ method on the user's behalf.
 # The user @activate@ method checks that all "user agreements":#user_agreements are signed.  If so, or there are no user agreements, the user is activated.
 - If "Users.AutoSetupNewUsersWithVmUUID":config.html is set, the user is given login permission to the specified shell node
 # User is set up, but still not yet active.  The browser presents "user agreements":#user_agreements (if any) and then invokes the user @activate@ method on the user's behalf.
 # The user @activate@ method checks that all "user agreements":#user_agreements are signed.  If so, or there are no user agreements, the user is activated.
diff --git a/doc/api/crunch-scripts.html.textile.liquid b/doc/api/crunch-scripts.html.textile.liquid
deleted file mode 100644 (file)
index a0d244d..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: Concepts
-title: Crunch scripts
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":methods/container_requests.html
-{% include 'notebox_end' %}
-
-h2. Crunch scripts
-
-A crunch script is responsible for completing a single JobTask. In doing so, it will:
-
-* (optionally) read some input from Keep
-* (optionally) store some output in Keep
-* (optionally) create some new JobTasks and add them to the current Job
-* (optionally) update the current JobTask record with the "output" attribute set to a Keep locator or a fragment of a manifest
-* update the current JobTask record with the "success" attribute set to True
-
-A task's context is provided in environment variables.
-
-table(table table-bordered table-condensed).
-|Environment variable|Description|
-|@JOB_UUID@|UUID of the current "Job":methods/jobs.html|
-|@TASK_UUID@|UUID of the current "JobTask":methods/job_tasks.html|
-|@ARVADOS_API_HOST@|Hostname and port number of API server|
-|@ARVADOS_API_TOKEN@|Authentication token to use with API calls made by the current task|
-
-The crunch script typically uses the Python SDK (or another suitable client library / SDK) to connect to the Arvados service and retrieve the rest of the details about the current job and task.
-
-The Python SDK has some shortcuts for common operations.
-
-In general, a crunch script can access information about the current job and task like this:
-
-<pre>
-import arvados
-import os
-
-job = arvados.api().jobs().get(uuid=os.environ['JOB_UUID']).execute()
-$sys.stderr.write("script_parameters['foo'] == %s"
-                  % job['script_parameters']['foo'])
-
-task = arvados.api().job_tasks().get(uuid=os.environ['TASK_UUID']).execute()
-$sys.stderr.write("current task sequence number is %d"
-                  % task['sequence'])
-</pre>
index 1c269fb3e613cf0c8d03c2ac99fbc25f20a9b7e7..38eb4909befd84e444d4bb7aa464a1b3de5d8e39 100644 (file)
@@ -49,6 +49,8 @@ table(table table-bordered table-condensed).
 |cwd|string|Initial working directory, given as an absolute path (in the container) or a path relative to the WORKDIR given in the image's Dockerfile.|Required.|
 |command|array of strings|Command to execute in the container.|Required. e.g., @["echo","hello"]@|
 |output_path|string|Path to a directory or file inside the container that should be preserved as container's output when it finishes. This path must be one of the mount targets. For best performance, point output_path to a writable collection mount.  See "Pre-populate output using Mount points":#pre-populate-output for details regarding optional output pre-population using mount points and "Symlinks in output":#symlinks-in-output for additional details.|Required.|
 |cwd|string|Initial working directory, given as an absolute path (in the container) or a path relative to the WORKDIR given in the image's Dockerfile.|Required.|
 |command|array of strings|Command to execute in the container.|Required. e.g., @["echo","hello"]@|
 |output_path|string|Path to a directory or file inside the container that should be preserved as container's output when it finishes. This path must be one of the mount targets. For best performance, point output_path to a writable collection mount.  See "Pre-populate output using Mount points":#pre-populate-output for details regarding optional output pre-population using mount points and "Symlinks in output":#symlinks-in-output for additional details.|Required.|
+|output_glob|array of strings|Glob patterns determining which files (of those present in the output directory when the container finishes) will be included in the output collection. If multiple patterns are given, files that match any pattern are included. If null or empty, all files will be included.|e.g., @["**/*.vcf", "**/*.vcf.gz"]@
+See "Glob patterns":#glob_patterns for more details.|
 |output_name|string|Desired name for the output collection. If null or empty, a name will be assigned automatically.||
 |output_ttl|integer|Desired lifetime for the output collection, in seconds. If zero, the output collection will not be deleted automatically.||
 |priority|integer|Range 0-1000.  Indicate scheduling order preference.|Clients are expected to submit container requests with zero priority in order to preview the container that will be used to satisfy it. Priority can be null if and only if state!="Committed".  See "below for more details":#priority .|
 |output_name|string|Desired name for the output collection. If null or empty, a name will be assigned automatically.||
 |output_ttl|integer|Desired lifetime for the output collection, in seconds. If zero, the output collection will not be deleted automatically.||
 |priority|integer|Range 0-1000.  Indicate scheduling order preference.|Clients are expected to submit container requests with zero priority in order to preview the container that will be used to satisfy it. Priority can be null if and only if state!="Committed".  See "below for more details":#priority .|
@@ -138,6 +140,8 @@ h2(#runtime_constraints). {% include 'container_runtime_constraints' %}
 
 h2(#scheduling_parameters). {% include 'container_scheduling_parameters' %}
 
 
 h2(#scheduling_parameters). {% include 'container_scheduling_parameters' %}
 
+h2(#glob_patterns). {% include 'container_glob_patterns' %}
+
 h2(#container_reuse). Container reuse
 
 When a container request is "Committed", the system will try to find and reuse an existing Container with the same command, cwd, environment, output_path, container_image, mounts, secret_mounts, runtime_constraints, runtime_user_uuid, and runtime_auth_scopes being requested.
 h2(#container_reuse). Container reuse
 
 When a container request is "Committed", the system will try to find and reuse an existing Container with the same command, cwd, environment, output_path, container_image, mounts, secret_mounts, runtime_constraints, runtime_user_uuid, and runtime_auth_scopes being requested.
index 1d2fed768cdf78d158abe346866bf926bdb762c3..776d4098c5774153b576498b47bf2aabf7ead247 100644 (file)
@@ -30,9 +30,10 @@ table(table table-bordered table-condensed).
 |finished_at|datetime|When this container finished.|Null if container has not yet finished.|
 |log|string|Portable data hash of a collection containing the log messages produced when executing the container.|Null if container has not yet started. The Crunch system will periodically update this field for a running container.|
 |environment|hash|Environment variables and values that should be set in the container environment (@docker run --env@). This augments and (when conflicts exist) overrides environment variables given in the image's Dockerfile.|Must be equal to a ContainerRequest's environment in order to satisfy the ContainerRequest.|
 |finished_at|datetime|When this container finished.|Null if container has not yet finished.|
 |log|string|Portable data hash of a collection containing the log messages produced when executing the container.|Null if container has not yet started. The Crunch system will periodically update this field for a running container.|
 |environment|hash|Environment variables and values that should be set in the container environment (@docker run --env@). This augments and (when conflicts exist) overrides environment variables given in the image's Dockerfile.|Must be equal to a ContainerRequest's environment in order to satisfy the ContainerRequest.|
-|cwd|string|Initial working directory.|Must be equal to a ContainerRequest's cwd in order to satisfy the ContainerRequest|
+|cwd|string|Initial working directory.|Must be equal to a ContainerRequest's cwd in order to satisfy the ContainerRequest.|
 |command|array of strings|Command to execute.| Must be equal to a ContainerRequest's command in order to satisfy the ContainerRequest.|
 |output_path|string|Path to a directory or file inside the container that should be preserved as this container's output when it finishes.|Must be equal to a ContainerRequest's output_path in order to satisfy the ContainerRequest.|
 |command|array of strings|Command to execute.| Must be equal to a ContainerRequest's command in order to satisfy the ContainerRequest.|
 |output_path|string|Path to a directory or file inside the container that should be preserved as this container's output when it finishes.|Must be equal to a ContainerRequest's output_path in order to satisfy the ContainerRequest.|
+|output_glob|array of strings|Glob patterns determining which files will be included in the output collection. See corresponding attribute in the "container_requests resource":container_requests.html.|Must be equal to a ContainerRequest's output_glob in order to satisfy the ContainerRequest. See "Glob patterns":#glob_patterns for more details.|
 |mounts|hash|Must contain the same keys as the ContainerRequest being satisfied. Each value must be within the range of values described in the ContainerRequest at the time the Container is assigned to the ContainerRequest.|See "Mount types":#mount_types for more details.|
 |secret_mounts|hash|Must contain the same keys as the ContainerRequest being satisfied. Each value must be within the range of values described in the ContainerRequest at the time the Container is assigned to the ContainerRequest.|Not returned in API responses. Reset to empty when state is "Complete" or "Cancelled".|
 |runtime_constraints|hash|Compute resources, and access to the outside world, that are / were available to the container.
 |mounts|hash|Must contain the same keys as the ContainerRequest being satisfied. Each value must be within the range of values described in the ContainerRequest at the time the Container is assigned to the ContainerRequest.|See "Mount types":#mount_types for more details.|
 |secret_mounts|hash|Must contain the same keys as the ContainerRequest being satisfied. Each value must be within the range of values described in the ContainerRequest at the time the Container is assigned to the ContainerRequest.|Not returned in API responses. Reset to empty when state is "Complete" or "Cancelled".|
 |runtime_constraints|hash|Compute resources, and access to the outside world, that are / were available to the container.
@@ -97,6 +98,8 @@ table(table table-bordered table-condensed).
 
 h2(#scheduling_parameters). {% include 'container_scheduling_parameters' %}
 
 
 h2(#scheduling_parameters). {% include 'container_scheduling_parameters' %}
 
+h2(#glob_patterns). {% include 'container_glob_patterns' %}
+
 h2. Methods
 
 See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
 h2. Methods
 
 See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
diff --git a/doc/api/methods/humans.html.textile.liquid b/doc/api/methods/humans.html.textile.liquid
deleted file mode 100644 (file)
index 1c33821..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "humans"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and is slated to be removed entirely in a future major release of Arvados.  The recommended way to store metadata is with "'properties' field on collections and projects.":../properties.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/humans@
-
-Object type: @7a9it@
-
-Example UUID: @zzzzz-7a9it-0123456789abcde@
-
-h2. Resource
-
-A metadata record that may be used to represent a human subject.
-
-Each Human has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|properties|hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Human.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|human|object||query||
-
-h3. delete
-
-Delete an existing Human.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Human in question.|path||
-
-h3. get
-
-Gets a Human's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Human in question.|path||
-
-h3. list
-
-List humans.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Human.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Human in question.|path||
-|human|object||query||
diff --git a/doc/api/methods/job_tasks.html.textile.liquid b/doc/api/methods/job_tasks.html.textile.liquid
deleted file mode 100644 (file)
index 880fe56..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "job_tasks"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":container_requests.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/job_tasks@
-
-Object type: @ot0gb@
-
-Example UUID: @zzzzz-ot0gb-0123456789abcde@
-
-h2. Resource
-
-Deprecated.
-
-A job task is a individually scheduled unit of work executed as part of an overall job.
-
-Each JobTask has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|sequence|integer|Execution sequence.
-A step cannot be run until all steps with lower sequence numbers have completed.
-Job steps with the same sequence number can be run in any order.||
-|parameters|hash|||
-|output|text|||
-|progress|float|||
-|success|boolean|Is null if the task has neither completed successfully nor failed permanently.||
-
-The following attributes should not be updated by anyone other than the job manager:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Notes|
-|qsequence|integer|Order of arrival|0-based|
-|job_uuid|string|||
-|created_by_job_task_uuid|string|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new JobTask.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|job_task|object||query||
-
-h3. delete
-
-Delete an existing JobTask.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the JobTask in question.|path||
-
-h3. get
-
-Gets a JobTask's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the JobTask in question.|path||
-
-h3. list
-
-List job_tasks.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing JobTask.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the JobTask in question.|path||
-|job_task|object||query||
diff --git a/doc/api/methods/jobs.html.textile.liquid b/doc/api/methods/jobs.html.textile.liquid
deleted file mode 100644 (file)
index 75d7368..0000000
+++ /dev/null
@@ -1,290 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "jobs"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":container_requests.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/jobs@
-
-Object type: @8i9sb@
-
-Example UUID: @zzzzz-8i9sb-0123456789abcde@
-
-h2. Resource
-
-A job describes a work order to be executed by the Arvados cluster.
-
-Each job has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Notes|
-|script|string|The filename of the job script.|This program will be invoked by Crunch for each job task. It is given as a path to an executable file, relative to the @/crunch_scripts@ directory in the Git tree specified by the _repository_ and _script_version_ attributes.|
-|script_parameters|hash|The input parameters for the job.|Conventionally, one of the parameters is called @"input"@. Typically, some parameter values are collection UUIDs. Ultimately, though, the significance of parameters is left entirely up to the script itself.|
-|repository|string|Git repository name or URL.|Source of the repository where the given script_version is to be found. This can be given as the name of a locally hosted repository, or as a publicly accessible URL starting with @git://@, @http://@, or @https://@.
-Examples:
-@yourusername/yourrepo@
-@https://github.com/arvados/arvados.git@|
-|script_version|string|Git commit|During a **create** transaction, this is the Git branch, tag, or hash supplied by the client. Before the job starts, Arvados updates it to the full 40-character SHA-1 hash of the commit used by the job.
-See "Specifying Git versions":#script_version below for more detail about acceptable ways to specify a commit.|
-|cancelled_by_client_uuid|string|API client ID|Is null if job has not been cancelled|
-|cancelled_by_user_uuid|string|Authenticated user ID|Is null if job has not been cancelled|
-|cancelled_at|datetime|When job was cancelled|Is null if job has not been cancelled|
-|started_at|datetime|When job started running|Is null if job has not [yet] started|
-|finished_at|datetime|When job finished running|Is null if job has not [yet] finished|
-|running|boolean|Whether the job is running||
-|success|boolean|Whether the job indicated successful completion|Is null if job has not finished|
-|is_locked_by_uuid|string|UUID of the user who has locked this job|Is null if job is not locked. The system user locks the job when starting the job, in order to prevent job attributes from being altered.|
-|node_uuids|array|List of UUID strings for node objects that have been assigned to this job||
-|log|string|Collection UUID|Is null if the job has not finished. After the job runs, the given collection contains a text file with log messages provided by the @arv-crunch-job@ task scheduler as well as the standard error streams provided by the task processes.|
-|tasks_summary|hash|Summary of task completion states.|Example: @{"done":0,"running":4,"todo":2,"failed":0}@|
-|output|string|Collection UUID|Is null if the job has not finished.|
-|nondeterministic|boolean|The job is expected to produce different results if run more than once.|If true, this job will not be considered as a candidate for automatic re-use when submitting subsequent identical jobs.|
-|submit_id|string|Unique ID provided by client when job was submitted|Optional. This can be used by a client to make the "jobs.create":{{site.baseurl}}/api/methods/jobs.html#create method idempotent.|
-|priority|string|||
-|arvados_sdk_version|string|Git commit hash that specifies the SDK version to use from the Arvados repository|This is set by searching the Arvados repository for a match for the arvados_sdk_version runtime constraint.|
-|docker_image_locator|string|Portable data hash of the collection that contains the Docker image to use|This is set by searching readable collections for a match for the docker_image runtime constraint.|
-|runtime_constraints|hash|Constraints that must be satisfied by the job/task scheduler in order to run the job.|See below.|
-|components|hash|Name and uuid pairs representing the child work units of this job. The uuids can be of different object types.|Example components hash: @{"name1": "zzzzz-8i9sb-xyz...", "name2": "zzzzz-d1hrv-xyz...",}@|
-
-h3(#script_version). Specifying Git versions
-
-The script_version attribute and arvados_sdk_version runtime constraint are typically given as a branch, tag, or commit hash, but there are many more ways to specify a Git commit. The "specifying revisions" section of the "gitrevisions manual page":http://git-scm.com/docs/gitrevisions.html has a definitive list. Arvados accepts Git versions in any format listed there that names a single commit (not a tree, a blob, or a range of commits). However, some kinds of names can be expected to resolve differently in Arvados than they do in your local repository. For example, <code>HEAD@{1}</code> refers to the local reflog, and @origin/main@ typically refers to a remote branch: neither is likely to work as desired if given as a Git version.
-
-h3. Runtime constraints
-
-table(table table-bordered table-condensed).
-|_. Key|_. Type|_. Description|_. Implemented|
-|arvados_sdk_version|string|The Git version of the SDKs to use from the Arvados git repository.  See "Specifying Git versions":#script_version for more detail about acceptable ways to specify a commit.  If you use this, you must also specify a @docker_image@ constraint (see below).  In order to install the Python SDK successfully, Crunch must be able to find and run virtualenv inside the container.|&#10003;|
-|docker_image|string|The Docker image that this Job needs to run.  If specified, Crunch will create a Docker container from this image, and run the Job's script inside that.  The Keep mount and work directories will be available as volumes inside this container.  The image must be uploaded to Arvados using @arv keep docker@.  You may specify the image in any format that Docker accepts, such as @arvados/jobs@, @debian:latest@, or the Docker image id.  Alternatively, you may specify the portable data hash of the image Collection.|&#10003;|
-|min_nodes|integer||&#10003;|
-|max_nodes|integer|||
-|min_cores_per_node|integer|Require that each node assigned to this Job have the specified number of CPU cores|&#10003;|
-|min_ram_mb_per_node|integer|Require that each node assigned to this Job have the specified amount of real memory (in MiB)|&#10003;|
-|min_scratch_mb_per_node|integer|Require that each node assigned to this Job have the specified amount of scratch storage available (in MiB)|&#10003;|
-|max_tasks_per_node|integer|Maximum simultaneous tasks on a single node|&#10003;|
-|keep_cache_mb_per_task|integer|Size of file data buffer for per-task Keep directory ($TASK_KEEPMOUNT), in MiB.  Default is 256 MiB.  Increase this to reduce cache thrashing in situtations such as accessing multiple large (64+ MiB) files at the same time, or accessing different parts of a large file at the same time.|&#10003;|
-|min_ram_per_task|integer|Minimum real memory (KiB) per task||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. cancel
-
-Cancel a job that is queued or running.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string||path||
-
-h3(#create). create
-
-Create a new Job.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|job|object|Job resource|request body||
-|minimum_script_version |string     |Git branch, tag, or commit hash specifying the minimum acceptable script version (earliest ancestor) to consider when deciding whether to re-use a past job.[1]|query|@"c3e86c9"@|
-|exclude_script_versions|array of strings|Git commit branches, tags, or hashes to exclude when deciding whether to re-use a past job.|query|@["8f03c71","8f03c71"]@
-@["badtag1","badtag2"]@|
-|filters|array of arrays|Conditions to find Jobs to reuse.|query||
-|find_or_create         |boolean    |Before creating, look for an existing job that has identical script, script_version, and script_parameters to those in the present job, has nondeterministic=false, and did not fail (it could be queued, running, or completed). If such a job exists, respond with the existing job instead of submitting a new one.|query|@false@|
-
-When a job is submitted to the queue using the **create** method, the @script_version@ attribute is updated to a full 40-character Git commit hash based on the current content of the specified repository. If @script_version@ cannot be resolved, the job submission is rejected.
-
-fn1. See the "note about specifying Git commits":#script_version for more detail.
-
-h4. Specialized filters
-
-Special filter operations are available for specific Job columns.
-
-* @script_version@ @in git@ @REFSPEC@, @arvados_sdk_version@ @in git@ @REFSPEC@<br>Resolve @REFSPEC@ to a list of Git commits, and match jobs with a @script_version@ or @arvados_sdk_version@ in that list.  When creating a job and filtering @script_version@, the search will find commits between @REFSPEC@ and the submitted job's @script_version@; all other searches will find commits between @REFSPEC@ and HEAD.  This list may include parallel branches if there is more than one path between @REFSPEC@ and the end commit in the graph.  Use @not in@ or @not in git@ filters (below) to blacklist specific commits.
-
-* @script_version@ @not in git@ @REFSPEC@, @arvados_sdk_version@ @not in git@ @REFSPEC@<br>Resolve @REFSPEC@ to a list of Git commits, and match jobs with a @script_version@ or @arvados_sdk_version@ not in that list.
-
-* @docker_image_locator@ @in docker@ @SEARCH@<br>@SEARCH@ can be a Docker image hash, a repository name, or a repository name and tag separated by a colon (@:@).  The server will find collections that contain a Docker image that match that search criteria, then match jobs with a @docker_image_locator@ in that list.
-
-* @docker_image_locator@ @not in docker@ @SEARCH@<br>Negate the @in docker@ filter.
-
-h4. Reusing jobs
-
-Because Arvados records the exact version of the script, input parameters, and runtime environment that was used to run the job, if the script is deterministic (meaning that the same code version is guaranteed to produce the same outputs from the same inputs) then it is possible to re-use the results of past jobs, and avoid re-running the computation to save time.  Arvados uses the following algorithm to determine if a past job can be re-used:
-
-notextile. <div class="spaced-out">
-
-# If @find_or_create@ is false or omitted, create a new job and skip the rest of these steps.
-# If @filters@ are specified, find jobs that match those filters. If any filters are given, there must be at least one filter on the @repository@ attribute and one on the @script@ attribute: otherwise an error is returned.
-# If @filters@ are not specified, find jobs with the same @repository@ and @script@, with a @script_version@ between @minimum_script_version@ and @script_version@ inclusively (excluding @excluded_script_versions@), and a @docker_image_locator@ with the latest Collection that matches the submitted job's @docker_image@ constraint.  If the submitted job includes an @arvados_sdk_version@ constraint, jobs must have an @arvados_sdk_version@ between that refspec and HEAD to be found. *This form is deprecated: use filters instead.*
-# If the found jobs include a completed job, and all found completed jobs have consistent output, return one of them.  Which specific job is returned is undefined.
-# If the found jobs only include incomplete jobs, return one of them.  Which specific job is returned is undefined.
-# If no job has been returned so far, create and return a new job.
-
-</div>
-
-h4. Examples
-
-Run the script "crunch_scripts/hash.py" in the repository "you" using the "main" commit.  Arvados should re-use a previous job if the script_version of the previous job is the same as the current "main" commit. This works irrespective of whether the previous job was submitted using the name "main", a different branch name or tag indicating the same commit, a SHA-1 commit hash, etc.
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "find_or_create": true
-}
-</pre></notextile>
-
-Run using exactly the version "d00220fb38d4b85ca8fc28a8151702a2b9d1dec5". Arvados should re-use a previous job if the "script_version" of that job is also "d00220fb38d4b85ca8fc28a8151702a2b9d1dec5".
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "d00220fb38d4b85ca8fc28a8151702a2b9d1dec5",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "find_or_create": true
-}
-</pre></notextile>
-
-Arvados should re-use a previous job if the "script_version" of the previous job is between "earlier_version_tag" and the "main" commit (inclusive), but not the commit indicated by "blacklisted_version_tag". If there are no previous jobs matching these criteria, run the job using the "main" commit.
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "minimum_script_version": "earlier_version_tag",
-  "exclude_script_versions": ["blacklisted_version_tag"],
-  "find_or_create": true
-}
-</pre></notextile>
-
-The same behavior, using filters:
-
-<notextile><pre>
-{
-  "job": {
-    "script": "hash.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  },
-  "filters": [["script", "=", "hash.py"],
-              ["repository", "=", "<b>you</b>/<b>you</b>"],
-              ["script_version", "in git", "earlier_version_tag"],
-              ["script_version", "not in git", "blacklisted_version_tag"]],
-  "find_or_create": true
-}
-</pre></notextile>
-
-Run the script "crunch_scripts/monte-carlo.py" in the repository "you/you" using the current "main" commit. Because it is marked as "nondeterministic", this job will not be considered as a suitable candidate for future job submissions that use the "find_or_create" feature.
-
-<notextile><pre>
-{
-  "job": {
-    "script": "monte-carlo.py",
-    "repository": "<b>you</b>/<b>you</b>",
-    "script_version": "main",
-    "nondeterministic": true,
-    "script_parameters": {
-      "input": "c1bad4b39ca5a924e481008009d94e32+210"
-    }
-  }
-}
-</pre></notextile>
-
-h3. delete
-
-Delete an existing Job.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Job in question.|path||
-
-h3. get
-
-Gets a Job's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Job in question.|path||
-
-h3. list
-
-List jobs.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-See the create method documentation for more information about Job-specific filters.
-
-h3. log_tail_follow
-
-log_tail_follow jobs
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string||path||
-|buffer_size|integer (default 8192)||query||
-
-h3. queue
-
-Get the current job queue.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|order|string||query||
-|filters|array||query||
-
-This method is equivalent to the "list method":#list, except that the results are restricted to queued jobs (i.e., jobs that have not yet been started or cancelled) and order defaults to queue priority.
-
-h3. update
-
-Update attributes of an existing Job.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Job in question.|path||
-|job|object||query||
diff --git a/doc/api/methods/keep_disks.html.textile.liquid b/doc/api/methods/keep_disks.html.textile.liquid
deleted file mode 100644 (file)
index 9a82a3e..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "keep_disks"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "keep services.":keep_services.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/keep_disks@
-
-Object type: @penuu@
-
-Example UUID: @zzzzz-penuu-0123456789abcde@
-
-h2. Resource
-
-Obsoleted by "keep_services":{{site.baseurl}}/api/methods/keep_services.html
-
-Each KeepDisk has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|ping_secret|string|||
-|node_uuid|string|||
-|filesystem_uuid|string|||
-|bytes_total|integer|||
-|bytes_free|integer|||
-|is_readable|boolean|||
-|is_writable|boolean|||
-|last_read_at|datetime|||
-|last_write_at|datetime|||
-|last_ping_at|datetime|||
-|keep_service_uuid|string|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new KeepDisk.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|keep_disk|object||query||
-
-h3. delete
-
-Delete an existing KeepDisk.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the KeepDisk in question.|path||
-
-h3. get
-
-Gets a KeepDisk's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the KeepDisk in question.|path||
-
-h3. list
-
-List keep_disks.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. ping
-
-ping keep_disks
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|ping_secret|string||query||
-{background:#ccffcc}.|service_port|string||query||
-{background:#ccffcc}.|service_ssl_flag|string||query||
-|filesystem_uuid|string||query||
-|node_uuid|string||query||
-|service_host|string||query||
-|uuid|string||query||
-
-h3. update
-
-Update attributes of an existing KeepDisk.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the KeepDisk in question.|path||
-|keep_disk|object||query||
diff --git a/doc/api/methods/nodes.html.textile.liquid b/doc/api/methods/nodes.html.textile.liquid
deleted file mode 100644 (file)
index b29527c..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "nodes"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "cloud dispatcher API.":../dispatch.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/nodes@
-
-Object type: @7ekkf@
-
-Example UUID: @zzzzz-7ekkf-0123456789abcde@
-
-h2. Resource
-
-Node resources list compute nodes on which Crunch may schedule work.
-
-Each Node has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|slot_number|integer|||
-|hostname|string|||
-|domain|string|||
-|ip_address|string|||
-|job_uuid|string|The UUID of the job that this node is assigned to work on.  If you do not have permission to read the job, this will be null.||
-|first_ping_at|datetime|||
-|last_ping_at|datetime|||
-|info|hash|Sensitive information about the node (only visible to admin) such as 'ping_secret' and 'ec2_instance_id'. May be used in queries using "subproperty filters":{{site.baseurl}}/api/methods.html#subpropertyfilters||
-|properties|hash|Public information about the node, such as 'total_cpu_cores', 'total_ram_mb', and 'total_scratch_mb'.  May be used in queries using "subproperty filters":{{site.baseurl}}/api/methods.html#subpropertyfilters||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|node|object||query||
-
-h3. delete
-
-Delete an existing Node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Node in question.|path||
-
-h3. get
-
-Gets a Node's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Node in question.|path||
-
-h3. list
-
-List nodes.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. ping
-
-Process a ping from a compute node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|ping_secret|string||query||
-{background:#ccffcc}.|uuid|string||path||
-
-h3. update
-
-Update attributes of an existing Node.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Node in question.|path||
-|node|object||query||
-
-To remove a node's job assignment, update the node object's @job_uuid@ to null.
diff --git a/doc/api/methods/pipeline_instances.html.textile.liquid b/doc/api/methods/pipeline_instances.html.textile.liquid
deleted file mode 100644 (file)
index e19dfba..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "pipeline_instances"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "container requests.":container_requests.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/pipeline_instances@
-
-Object type: @d1hrv@
-
-Example UUID: @zzzzz-d1hrv-0123456789abcde@
-
-h2. Resource
-
-Deprecated.  A pipeline instance is a collection of jobs managed by @arvados-run-pipeline-instance@.
-
-Each PipelineInstance has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|pipeline_template_uuid|string|The "pipeline template":pipeline_templates.html that this instance was created from.||
-|name|string|||
-|components|hash|||
-|success|boolean|||
-|active|boolean|||
-|properties|Hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new PipelineInstance.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|pipeline_instance|object||query||
-
-h3. delete
-
-Delete an existing PipelineInstance.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineInstance in question.|path||
-
-h3. get
-
-Gets a PipelineInstance's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineInstance in question.|path||
-
-h3. list
-
-List pipeline_instances.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing PipelineInstance.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineInstance in question.|path||
-|pipeline_instance|object||query||
diff --git a/doc/api/methods/pipeline_templates.html.textile.liquid b/doc/api/methods/pipeline_templates.html.textile.liquid
deleted file mode 100644 (file)
index ddbe8ad..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "pipeline_templates"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "registered workflows.":workflows.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/pipeline_templates@
-
-Object type: @p5p6p@
-
-Example UUID: @zzzzz-p5p6p-0123456789abcde@
-
-h2. Resource
-
-Deprecated.  A pipeline template is a collection of jobs that can be instantiated as a pipeline_instance.
-
-Each PipelineTemplate has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|name|string|||
-|components|hash|||
-
-The pipeline template consists of "name" and "components".
-
-table(table table-bordered table-condensed).
-|_. Attribute    |_. Type |_. Accepted values                           |_. Required|_. Description|
-|name            |string  |any                                          |yes        |The human-readable name of the pipeline template.|
-|components      |object  |JSON object containing job submission objects|yes        |The component jobs that make up the pipeline, with the component name as the key. |
-
-h3. Components
-
-The components field of the pipeline template is a JSON object which describes the individual steps that make up the pipeline.  Each component is an Arvados job submission.  "Parameters for job submissions are described on the job method page.":{{site.baseurl}}/api/methods/jobs.html#create  In addition, a component can have the following parameters:
-
-table(table table-bordered table-condensed).
-|_. Attribute    |_. Type          |_. Accepted values |_. Required|_. Description|
-|output_name     |string or boolean|string or false    |no         |If a string is provided, use this name for the output collection of this component.  If the value is false, do not create a permanent output collection (an temporary intermediate collection will still be created).  If not provided, a default name will be assigned to the output.|
-
-h3. Script parameters
-
-When used in a pipeline, each parameter in the 'script_parameters' attribute of a component job can specify that the input parameter must be supplied by the user, or the input parameter should be linked to the output of another component.  To do this, the value of the parameter should be JSON object containing one of the following attributes:
-
-table(table table-bordered table-condensed).
-|_. Attribute    |_. Type |_. Accepted values                               |_. Description|
-|default         |any     |any                                              |The default value for this parameter.|
-|required        |boolean |true or false                                    |Specifies whether the parameter is required to have a value or not.|
-|dataclass       |string  |One of 'Collection', 'File' [1], 'number', or 'text' |Data type of this parameter.|
-|search_for      |string  |any string                                       |Substring to use as a default search string when choosing inputs.|
-|output_of       |string  |the name of another component in the pipeline    |Specifies that the value of this parameter should be set to the 'output' attribute of the job that corresponds to the specified component.|
-|title           |string  |any string                                       |User friendly title to display when choosing parameter values|
-|description     |string  |any string                                       |Extended text description for describing expected/valid values for the script parameter|
-|link_name       |string  |any string                                       |User friendly name to display for the parameter value instead of the actual parameter value|
-
-The 'output_of' parameter is especially important, as this is how components are actually linked together to form a pipeline.  Component jobs that depend on the output of other components do not run until the parent job completes and has produced output.  If the parent job fails, the entire pipeline fails.
-
-fn1. The 'File' type refers to a specific file within a Keep collection in the form 'collection_hash/filename', for example '887cd41e9c613463eab2f0d885c6dd96+83/bob.txt'.
-
-The 'search_for' parameter is meaningful only when input dataclass of type Collection or File is used. If a value is provided, this will be preloaded into the input data chooser dialog in Workbench. For example, if your input dataclass is a File and you are interested in a certain filename extention, you can preconfigure it in this attribute.
-
-h3. Examples
-
-This is a pipeline named "Filter MD5 hash values" with two components, "do_hash" and "filter".  The "input" script parameter of the "do_hash" component is required to be filled in by the user, and the expected data type is "Collection".  This also specifies that the "input" script parameter of the "filter" component is the output of "do_hash", so "filter" will not run until "do_hash" completes successfully.  When the pipeline runs, past jobs that meet the criteria described above may be substituted for either or both components to avoid redundant computation.
-
-<notextile><pre>
-{
-  "name": "Filter MD5 hash values",
-  "components": {
-    "do_hash": {
-      "script": "hash.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "required": true,
-          "dataclass": "Collection",
-          "search_for": ".fastq.gz",
-          "title":"Please select a fastq file"
-        }
-      },
-    },
-    "filter": {
-      "script": "0-filter.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "output_of": "do_hash"
-        }
-      },
-    }
-  }
-}
-</pre></notextile>
-
-This pipeline consists of three components.  The components "thing1" and "thing2" both depend on "cat_in_the_hat".  Once the "cat_in_the_hat" job is complete, both "thing1" and "thing2" can run in parallel, because they do not depend on each other.
-
-<notextile><pre>
-{
-  "name": "Wreck the house",
-  "components": {
-    "cat_in_the_hat": {
-      "script": "cat.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": { }
-    },
-    "thing1": {
-      "script": "thing1.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "output_of": "cat_in_the_hat"
-        }
-      },
-    },
-    "thing2": {
-      "script": "thing2.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "input": {
-          "output_of": "cat_in_the_hat"
-        }
-      },
-    },
-  }
-}
-</pre></notextile>
-
-This pipeline consists of three components.  The component "cleanup" depends on "thing1" and "thing2".  Both "thing1" and "thing2" are started immediately and can run in parallel, because they do not depend on each other, but "cleanup" cannot begin until both "thing1" and "thing2" have completed.
-
-<notextile><pre>
-{
-  "name": "Clean the house",
-  "components": {
-    "thing1": {
-      "script": "thing1.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": { }
-    },
-    "thing2": {
-      "script": "thing2.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": { }
-    },
-    "cleanup": {
-      "script": "cleanup.py",
-      "repository": "<b>you</b>/<b>you</b>",
-      "script_version": "main",
-      "script_parameters": {
-        "mess1": {
-          "output_of": "thing1"
-        },
-        "mess2": {
-          "output_of": "thing2"
-        }
-      }
-    }
-  }
-}
-</pre></notextile>
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new PipelineTemplate.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|pipeline_template|object||query||
-
-h3. delete
-
-Delete an existing PipelineTemplate.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineTemplate in question.|path||
-
-h3. get
-
-Gets a PipelineTemplate's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineTemplate in question.|path||
-
-h3. list
-
-List pipeline_templates.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing PipelineTemplate.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the PipelineTemplate in question.|path||
-|pipeline_template|object||query||
diff --git a/doc/api/methods/repositories.html.textile.liquid b/doc/api/methods/repositories.html.textile.liquid
deleted file mode 100644 (file)
index b2b2cab..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "repositories"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and slated to be removed entirely in a future major release of Arvados.  It is replaced by "collection versioning.":collections.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/repositories@
-
-Object type: @s0uqq@
-
-Example UUID: @zzzzz-s0uqq-0123456789abcde@
-
-h2. Resource
-
-The repositories resource lists git repositories managed by Arvados.
-
-Each Repository has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|name|string|The name of the repository on disk.  Repository names must begin with a letter and contain only alphanumerics.  Unless the repository is owned by the system user, the name must begin with the owner's username, then be separated from the base repository name with @/@.  You may not create a repository that is owned by a user without a username.|@username/project1@|
-|clone_urls|array|URLs from which the repository can be cloned. Read-only.|@["git@git.zzzzz.arvadosapi.com:foo/bar.git",
- "https://git.zzzzz.arvadosapi.com/foo/bar.git"]@|
-|fetch_url|string|URL suggested as a fetch-url in git config. Deprecated. Read-only.||
-|push_url|string|URL suggested as a push-url in git config. Deprecated. Read-only.||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Repository.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|repository|object||query||
-
-h3. delete
-
-Delete an existing Repository.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Repository in question.|path||
-
-h3. get
-
-Gets a Repository's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Repository in question.|path||
-
-h3. get_all_permissions
-
-get_all_permissions repositories
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-
-h3. list
-
-List repositories.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Repository.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Repository in question.|path||
-|repository|object||query||
diff --git a/doc/api/methods/specimens.html.textile.liquid b/doc/api/methods/specimens.html.textile.liquid
deleted file mode 100644 (file)
index 3820eeb..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "specimens"
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and is slated to be removed entirely in a future major release of Arvados.  The recommended way to store metadata is with "'properties' field on collections and projects.":../properties.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/specimens@
-
-Object type: @j58dm@
-
-Example UUID: @zzzzz-j58dm-0123456789abcde@
-
-h2. Resource
-
-A metadata record that may be used to represent a biological specimen.
-
-Each Specimen has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|material|string|||
-|properties|hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Specimen.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|specimen|object||query||
-
-h3. delete
-
-Delete an existing Specimen.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Specimen in question.|path||
-
-h3. get
-
-Gets a Specimen's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Specimen in question.|path||
-
-h3. list
-
-List specimens.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Specimen.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Specimen in question.|path||
-|specimen|object||query||
diff --git a/doc/api/methods/traits.html.textile.liquid b/doc/api/methods/traits.html.textile.liquid
deleted file mode 100644 (file)
index 4e356b9..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
----
-layout: default
-navsection: api
-navmenu: API Methods
-title: "traits"
-
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-{% include 'notebox_begin_warning' %}
-This is a legacy API.  This endpoint is deprecated, disabled by default in new installations, and is slated to be removed entirely in a future major release of Arvados.  The recommended way to store metadata is with "'properties' field on collections and projects.":../properties.html
-{% include 'notebox_end' %}
-
-API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/traits@
-
-Object type: @q1cn2@
-
-Example UUID: @zzzzz-q1cn2-0123456789abcde@
-
-h2. Resource
-
-A metadata record that may be used to represent a genotype or phenotype trait.
-
-Each Trait has, in addition to the "Common resource fields":{{site.baseurl}}/api/resources.html:
-
-table(table table-bordered table-condensed).
-|_. Attribute|_. Type|_. Description|_. Example|
-|name|string|||
-|properties|hash|||
-
-h2. Methods
-
-See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update@.
-
-Required arguments are displayed in %{background:#ccffcc}green%.
-
-h3. create
-
-Create a new Trait.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-|trait|object||query||
-
-h3. delete
-
-Delete an existing Trait.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Trait in question.|path||
-
-h3. get
-
-Gets a Trait's metadata by UUID.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Trait in question.|path||
-
-h3. list
-
-List traits.
-
-See "common resource list method.":{{site.baseurl}}/api/methods.html#index
-
-h3. update
-
-Update attributes of an existing Trait.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the Trait in question.|path||
-|trait|object||query||
index f5405c16e17cb84a7f082c2daa563aede25f5569..230a9be8f5244045640a1e433a8348fbe40e4bc9 100644 (file)
@@ -19,7 +19,6 @@ Located in @arvados/services@.
 table(table table-bordered table-condensed).
 |_. Component|_. Description|
 |api|The API server is the core of Arvados.  It is backed by a Postgres database and manages information such as metadata for storage, a record of submitted compute jobs, users, groups, and associated permissions.|
 table(table table-bordered table-condensed).
 |_. Component|_. Description|
 |api|The API server is the core of Arvados.  It is backed by a Postgres database and manages information such as metadata for storage, a record of submitted compute jobs, users, groups, and associated permissions.|
-|arvados-git-httpd|Provides a git+http interface to Arvados-managed git repositories, with permissions and authentication based on an Arvados API token.|
 |arvados-dispatch-cloud|Provide elastic computing by creating and destroying cloud based virtual machines on compute demand.|
 |crunch-dispatch-local|Get compute requests submitted to the API server and execute them locally.|
 |crunch-dispatch-slurm|Get compute requests submitted to the API server and submit them to slurm.|
 |arvados-dispatch-cloud|Provide elastic computing by creating and destroying cloud based virtual machines on compute demand.|
 |crunch-dispatch-local|Get compute requests submitted to the API server and execute them locally.|
 |crunch-dispatch-slurm|Get compute requests submitted to the API server and submit them to slurm.|
diff --git a/doc/images/add-new-repository.png b/doc/images/add-new-repository.png
deleted file mode 100644 (file)
index d62a986..0000000
Binary files a/doc/images/add-new-repository.png and /dev/null differ
index 20e1c48eeea5d98ae067484ab6c1877bf67df7df..7265e2d5ccad5308195537a95e8eb675fa19ba9c 100644 (file)
@@ -28,7 +28,7 @@ $ <span class="userinput">./arvbox start localdemo</span>
 
 Arvados-in-a-box starting
 
 
 Arvados-in-a-box starting
 
-Waiting for workbench2 websockets workbench webshell keep-web controller keepproxy api keepstore1 arv-git-httpd keepstore0 sdk vm ...
+Waiting for workbench2 websockets workbench webshell keep-web controller keepproxy api keepstore1 keepstore0 sdk vm ...
 
 ...
 
 
 ...
 
index 06f94a8a5f11f26329151fbe64f63a2d6d4c0589..95cfca8e6d384ae61766a51731a8074cc470ffb1 100644 (file)
@@ -30,7 +30,7 @@ h2(#dependencies). Install dependencies
 # "Install PostgreSQL":install-postgresql.html
 # "Install Ruby and Bundler":ruby.html
 # "Install nginx":nginx.html
 # "Install PostgreSQL":install-postgresql.html
 # "Install Ruby and Bundler":ruby.html
 # "Install nginx":nginx.html
-# "Install Phusion Passenger":https://www.phusionpassenger.com/library/walkthroughs/deploy/ruby/ownserver/nginx/oss/install_passenger_main.html
+# "Install Phusion Passenger":https://www.phusionpassenger.com/docs/tutorials/deploy_to_production/installations/oss/ownserver/ruby/nginx/
 
 h2(#database-setup). Set up database
 
 
 h2(#database-setup). Set up database
 
@@ -178,10 +178,7 @@ server {
   index  index.html index.htm index.php;
 
   passenger_enabled on;
   index  index.html index.htm index.php;
 
   passenger_enabled on;
-
-  # <span class="userinput">If you are using RVM, uncomment the line below.</span>
-  # <span class="userinput">If you're using system ruby, leave it commented out.</span>
-  #passenger_ruby /usr/local/rvm/wrappers/default/ruby;
+  passenger_preload_bundler on;
 
   # This value effectively limits the size of API objects users can
   # create, especially collections.  If you change this, you should
 
   # This value effectively limits the size of API objects users can
   # create, especially collections.  If you change this, you should
diff --git a/doc/install/install-arv-git-httpd.html.textile.liquid b/doc/install/install-arv-git-httpd.html.textile.liquid
deleted file mode 100644 (file)
index 476c890..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Install the Git server
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-# "Introduction":#introduction
-# "Install dependencies":#dependencies
-# "Create "git" user and storage directory":#create
-# "Install gitolite":#gitolite
-# "Configure gitolite":#config-gitolite
-# "Configure git synchronization":#sync
-# "Update config.yml":#update-config
-# "Update nginx configuration":#update-nginx
-# "Install arvados-git-httpd package":#install-packages
-# "Restart the API server and controller":#restart-api
-# "Confirm working installation":#confirm-working
-
-h2(#introduction). Introduction
-
-Arvados support for git repository management enables using Arvados permissions to control access to git repositories.  Users can create their own private and public git repositories and share them with others.
-
-The git hosting setup involves three components.
-* The "arvados-git-sync.rb" script polls the API server for the current list of repositories, creates bare repositories, and updates the local permission cache used by gitolite.
-* Gitolite provides SSH access.  Users authenticate by SSH keys.
-* arvados-git-http provides HTTPS access.  Users authenticate by Arvados tokens.
-
-Git services must be installed on the same host as the Arvados Rails API server.
-
-h2(#dependencies). Install dependencies
-
-h3. Alma/CentOS/Red Hat/Rocky
-
-<notextile>
-<pre><code># <span class="userinput">dnf install git perl-Data-Dumper openssh-server</span>
-</code></pre>
-</notextile>
-
-h3. Debian and Ubuntu
-
-<notextile>
-<pre><code># <span class="userinput">apt-get --no-install-recommends install git openssh-server</span>
-</code></pre>
-</notextile>
-
-h2(#create). Create "git" user and storage directory
-
-Gitolite and some additional scripts will be installed in @/var/lib/arvados/git@, which means hosted repository data will be stored in @/var/lib/arvados/git/repositories@. If you choose to install gitolite in a different location, make sure to update the @git_repositories_dir@ entry in your API server's @application.yml@ file accordingly: for example, if you install gitolite at @/data/gitolite@ then your @git_repositories_dir@ will be @/data/gitolite/repositories@.
-
-A new UNIX account called "git" will own the files. This makes git URLs look familiar to users (<code>git@[...]:username/reponame.git</code>).
-
-On Debian- or Red Hat-based systems:
-
-<notextile>
-<pre><code>gitserver:~$ <span class="userinput">sudo mkdir -p /var/lib/arvados/git</span>
-gitserver:~$ <span class="userinput">sudo useradd --comment git --home-dir /var/lib/arvados/git git</span>
-gitserver:~$ <span class="userinput">sudo chown -R git:git ~git</span>
-</code></pre>
-</notextile>
-
-The git user needs its own SSH key. (It must be able to run <code>ssh git@localhost</code> from scripts.)
-
-<notextile>
-<pre><code>gitserver:~$ <span class="userinput">sudo -u git -i bash</span>
-git@gitserver:~$ <span class="userinput">ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa</span>
-git@gitserver:~$ <span class="userinput">cp .ssh/id_rsa.pub .ssh/authorized_keys</span>
-git@gitserver:~$ <span class="userinput">ssh -o stricthostkeychecking=no localhost cat .ssh/id_rsa.pub</span>
-Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7aBIDAAgMQN16Pg6eHmvc+D+6TljwCGr4YGUBphSdVb25UyBCeAEgzqRiqy0IjQR2BLtSirXr+1SJAcQfBgI/jwR7FG+YIzJ4ND9JFEfcpq20FvWnMMQ6XD3y3xrZ1/h/RdBNwy4QCqjiXuxDpDB7VNP9/oeAzoATPZGhqjPfNS+RRVEQpC6BzZdsR+S838E53URguBOf9yrPwdHvosZn7VC0akeWQerHqaBIpSfDMtaM4+9s1Gdsz0iP85rtj/6U/K/XOuv2CZsuVZZ52nu3soHnEX2nx2IaXMS3L8Z+lfOXB2T6EaJgXF7Z9ME5K1tx9TSNTRcYCiKztXLNLSbp git@gitserver
-git@gitserver:~$ <span class="userinput">rm .ssh/authorized_keys</span>
-</code></pre>
-</notextile>
-
-h2(#gitolite). Install gitolite
-
-Check "https://github.com/sitaramc/gitolite/tags":https://github.com/sitaramc/gitolite/tags for the latest stable version. This guide was tested with @v3.6.11@. _Versions below 3.0 are missing some features needed by Arvados, and should not be used._
-
-Download and install the version you selected.
-
-<notextile>
-<pre><code>$ <span class="userinput">sudo -u git -i bash</span>
-git@gitserver:~$ <span class="userinput">echo 'PATH=$HOME/bin:$PATH' &gt;.profile</span>
-git@gitserver:~$ <span class="userinput">. .profile</span>
-git@gitserver:~$ <span class="userinput">git clone --branch <b>v3.6.11</b> https://github.com/sitaramc/gitolite</span>
-...
-Note: checking out '5d24ae666bfd2fa9093d67c840eb8d686992083f'.
-...
-git@gitserver:~$ <span class="userinput">mkdir bin</span>
-git@gitserver:~$ <span class="userinput">gitolite/install -ln ~git/bin</span>
-git@gitserver:~$ <span class="userinput">bin/gitolite setup -pk .ssh/id_rsa.pub</span>
-Initialized empty Git repository in /var/lib/arvados/git/repositories/gitolite-admin.git/
-Initialized empty Git repository in /var/lib/arvados/git/repositories/testing.git/
-WARNING: /var/lib/arvados/git/.ssh/authorized_keys missing; creating a new one
-    (this is normal on a brand new install)
-</code></pre>
-</notextile>
-
-_If this didn't go well, more detail about installing gitolite, and information about how it works, can be found on the "gitolite home page":http://gitolite.com/._
-
-Clone the gitolite-admin repository. The arvados-git-sync.rb script works by editing the files in this working directory and pushing them to gitolite. Here we make sure "git push" won't produce any errors or warnings.
-
-<notextile>
-<pre><code>git@gitserver:~$ <span class="userinput">git clone git@localhost:gitolite-admin</span>
-Cloning into 'gitolite-admin'...
-remote: Counting objects: 6, done.
-remote: Compressing objects: 100% (4/4), done.
-remote: Total 6 (delta 0), reused 0 (delta 0)
-Receiving objects: 100% (6/6), done.
-Checking connectivity... done.
-git@gitserver:~$ <span class="userinput">cd gitolite-admin</span>
-git@gitserver:~/gitolite-admin$ <span class="userinput">git config user.email arvados</span>
-git@gitserver:~/gitolite-admin$ <span class="userinput">git config user.name arvados</span>
-git@gitserver:~/gitolite-admin$ <span class="userinput">git config push.default simple</span>
-git@gitserver:~/gitolite-admin$ <span class="userinput">git push</span>
-Everything up-to-date
-</code></pre>
-</notextile>
-
-h2(#config-gitolite). Configure gitolite
-
-Configure gitolite to look up a repository name like @username/reponame.git@ and find the appropriate bare repository storage directory.
-
-Add the following lines to the top of @~git/.gitolite.rc@:
-
-<notextile>
-<pre><code><span class="userinput">my $repo_aliases;
-my $aliases_src = "$ENV{HOME}/.gitolite/arvadosaliases.pl";
-if ($ENV{HOME} && (-e $aliases_src)) {
-    $repo_aliases = do $aliases_src;
-}
-$repo_aliases ||= {};
-</span></code></pre>
-</notextile>
-
-Add the following lines inside the section that begins @%RC = (@:
-
-<notextile>
-<pre><code><span class="userinput">    REPO_ALIASES => $repo_aliases,
-</span></code></pre>
-</notextile>
-
-Inside that section, adjust the 'UMASK' setting to @022@, to ensure the API server has permission to read repositories:
-
-<notextile>
-<pre><code>    UMASK => <span class="userinput">022</span>,
-</code></pre>
-</notextile>
-
-Uncomment the 'Alias' line in the section that begins @ENABLE => [@:
-
-<notextile>
-<pre><code><span class="userinput">            # access a repo by another (possibly legacy) name
-            'Alias',
-</span></code></pre>
-</notextile>
-
-h2(#sync). Configure git synchronization
-
-Create a configuration file @/var/www/arvados-api/current/config/arvados-clients.yml@ using the following template, filling in the appropriate values for your system.
-* For @arvados_api_token@, use @SystemRootToken@
-* For @gitolite_arvados_git_user_key@, provide the public key you generated above, i.e., the contents of @~git/.ssh/id_rsa.pub@.
-
-<notextile>
-<pre><code>production:
-  gitolite_url: /var/lib/arvados/git/repositories/gitolite-admin.git
-  gitolite_tmp: /var/lib/arvados/git
-  arvados_api_host: <span class="userinput">ClusterID.example.com</span>
-  arvados_api_token: "<span class="userinput">zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz</span>"
-  arvados_api_host_insecure: <span class="userinput">false</span>
-  gitolite_arvados_git_user_key: "<span class="userinput">ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7aBIDAAgMQN16Pg6eHmvc+D+6TljwCGr4YGUBphSdVb25UyBCeAEgzqRiqy0IjQR2BLtSirXr+1SJAcQfBgI/jwR7FG+YIzJ4ND9JFEfcpq20FvWnMMQ6XD3y3xrZ1/h/RdBNwy4QCqjiXuxDpDB7VNP9/oeAzoATPZGhqjPfNS+RRVEQpC6BzZdsR+S838E53URguBOf9yrPwdHvosZn7VC0akeWQerHqaBIpSfDMtaM4+9s1Gdsz0iP85rtj/6U/K/XOuv2CZsuVZZ52nu3soHnEX2nx2IaXMS3L8Z+lfOXB2T6EaJgXF7Z9ME5K1tx9TSNTRcYCiKztXLNLSbp git@gitserver</span>"
-</code></pre>
-</notextile>
-
-<pre>
-$ sudo chown git:git /var/www/arvados-api/current/config/arvados-clients.yml
-$ sudo chmod og-rwx /var/www/arvados-api/current/config/arvados-clients.yml
-</pre>
-
-h3. Test configuration
-
-notextile. <pre><code>$ <span class="userinput">sudo -u git -i bash -c 'cd /var/www/arvados-api/current && bin/bundle exec script/arvados-git-sync.rb production'</span></code></pre>
-
-h3. Enable the synchronization script
-
-The API server package includes a script that retrieves the current set of repository names and permissions from the API, writes them to @arvadosaliases.pl@ in a format usable by gitolite, and triggers gitolite hooks which create new empty repositories if needed. This script should run every 2 to 5 minutes.
-
-Create @/etc/cron.d/arvados-git-sync@ with the following content:
-
-<notextile>
-<pre><code><span class="userinput">*/5 * * * * git cd /var/www/arvados-api/current && bin/bundle exec script/arvados-git-sync.rb production</span>
-</code></pre>
-</notextile>
-
-h2(#update-config). Update config.yml
-
-Edit the cluster config at @config.yml@ .
-
-<notextile>
-<pre><code>    Services:
-      GitSSH:
-        ExternalURL: "<span class="userinput">ssh://git@git.ClusterID.example.com</span>"
-      GitHTTP:
-        ExternalURL: <span class="userinput">https://git.ClusterID.example.com/</span>
-        InternalURLs:
-         "http://localhost:9001": {}
-    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>
-
-h2(#update-nginx). Update nginx configuration
-
-Use a text editor to create a new file @/etc/nginx/conf.d/arvados-git.conf@ with the following configuration.  Options that need attention are marked in <span class="userinput">red</span>.
-
-<notextile>
-<pre><code>upstream arvados-git-httpd {
-  server                  127.0.0.1:<span class="userinput">9001</span>;
-}
-server {
-  listen                  443 ssl;
-  server_name             git.<span class="userinput">ClusterID.example.com</span>;
-  proxy_connect_timeout   90s;
-  proxy_read_timeout      300s;
-
-  ssl_certificate         <span class="userinput">/YOUR/PATH/TO/cert.pem</span>;
-  ssl_certificate_key     <span class="userinput">/YOUR/PATH/TO/cert.key</span>;
-
-  # The server needs to accept potentially large refpacks from push clients.
-  client_max_body_size 128m;
-
-  location  / {
-    proxy_pass            http://arvados-git-httpd;
-  }
-}
-</code></pre>
-</notextile>
-
-h2(#install-packages). Install the arvados-git-httpd package
-
-The arvados-git-httpd package provides HTTP access, using Arvados authentication tokens instead of passwords. It must be installed on the system where your git repositories are stored.
-
-h3. Alma/CentOS/Red Hat/Rocky
-
-<notextile>
-<pre><code># <span class="userinput">dnf install arvados-git-httpd</span>
-</code></pre>
-</notextile>
-
-h3. Debian and Ubuntu
-
-<notextile>
-<pre><code># <span class="userinput">apt-get --no-install-recommends install arvados-git-httpd</span>
-</code></pre>
-</notextile>
-
-h2(#restart-api). Restart the API server and controller
-
-After adding Workbench to the Services section, make sure the cluster config file is up to date on the API server host, and restart the API server and controller processes to ensure the changes are applied.
-
-<notextile>
-<pre><code># <span class="userinput">systemctl restart nginx arvados-controller</span>
-</code></pre>
-</notextile>
-
-h2(#confirm-working). Confirm working installation
-
-Create 'testrepo' in the Arvados database.
-
-<notextile>
-<pre><code>~$ <span class="userinput">arv --format=uuid repository create --repository '{"name":"myusername/testrepo"}'</span>
-</code></pre></notextile>
-
-The arvados-git-sync cron job will notice the new repository record and create a repository on disk.  Because it is on a timer (default 5 minutes) you may have to wait a minute or two for it to show up.
-
-h3. SSH
-
-Before you do this, go to Workbench and choose *SSH Keys* from the menu, and upload your public key.  Arvados uses the public key to identify you when you access the git repo.
-
-<notextile>
-<pre><code>~$ <span class="userinput">git clone git@git.ClusterID.example.com:username/testrepo.git</span>
-</code></pre>
-</notextile>
-
-h3. HTTP
-
-Set up git credential helpers as described in "install shell server":install-shell-server.html#config-git for the git command to use your API token instead of prompting you for a username and password.
-
-<notextile>
-<pre><code>~$ <span class="userinput">git clone https://git.ClusterID.example.com/username/testrepo.git</span>
-</code></pre>
-</notextile>
diff --git a/doc/install/install-composer.html.textile.liquid b/doc/install/install-composer.html.textile.liquid
deleted file mode 100644 (file)
index 58ba5d0..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Install Composer
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-Arvados Composer is a web-based javascript application for building Common Workflow Languge (CWL) Workflows.
-
-# "Install dependencies":#dependencies
-# "Update config.yml":#update-config
-# "Update Nginx configuration":#update-nginx
-# "Install arvados-composer":#install-packages
-# "Restart the API server and controller":#restart-api
-# "Confirm working installation":#confirm-working
-
-h2(#dependencies). Install dependencies
-
-In addition to Arvados core services, Composer requires "Arvados hosted git repositories":install-arv-git-httpd.html which are used for storing workflow files.
-
-h2(#configure). Update config.yml
-
-Edit @config.yml@ and set @Services.Composer.ExternalURL@ to the location from which it is served:
-
-<notextile>
-<pre><code>    Services:
-      Composer:
-        ExternalURL: <span class="userinput">https://workbench.CusterID.example.com/composer</span></code></pre>
-</notextile>
-
-h2(#update-nginx). Update nginx configuration
-
-Composer may be served from the same host as Workbench.  Composer communicates directly with the Arvados API server.  It does not require its own backend and should be served as a static file.
-
-Add the following @location@ sections to @/etc/nginx/conf.d/arvados-workbench.conf@ .
-
-<notextile>
-<pre><code>server {
-  [...]
-
-  location /composer {
-    root   /var/www/arvados-composer;
-    index  index.html;
-  }
-
-  location /composer/composer.yml {
-    return 200 '{ "API_HOST": "<span class="userinput">ClusterID.example.com</span>" }';
-  }
-}
-</code></pre>
-</notextile>
-
-{% assign arvados_component = 'arvados-composer' %}
-
-{% include 'install_packages' %}
-
-{% include 'restart_api' %}
-
-h2(#confirm-working). Confirm working installation
-
-Visit @https://workbench.ClusterID.example.com/composer@ in a browser.  You should be able to log in using the login method you configured previously.
diff --git a/doc/install/install-jobs-image.html.textile.liquid b/doc/install/install-jobs-image.html.textile.liquid
deleted file mode 100644 (file)
index efd8c96..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
----
-layout: default
-navsection: installguide
-title: Install arvados/jobs image
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-h2. Create a project for Docker images
-
-Here we create a default project for the standard Arvados Docker images, and give all users read access to it. The project is owned by the system user.
-
-<notextile>
-<pre><code>~$ <span class="userinput">uuid_prefix=$(arv --format=uuid user current | cut -d- -f1)</span>
-~$ <span class="userinput">project_uuid=$(arv --format=uuid group create --group '{"owner_uuid":"'$uuid_prefix'-tpzed-000000000000000", "group_class":"project", "name":"Arvados Standard Docker Images"}')</span>
-~$ <span class="userinput">echo "Arvados project uuid is '$project_uuid'"</span>
-~$ <span class="userinput">read -rd $'\000' newlink &lt;&lt;EOF; arv link create --link "$newlink"</span>
-<span class="userinput">{
- "tail_uuid":"${uuid_prefix}-j7d0g-fffffffffffffff",
- "head_uuid":"$project_uuid",
- "link_class":"permission",
- "name":"can_read"
-}
-EOF</span>
-</code></pre></notextile>
-
-h2. Import the arvados/jobs docker image
-
-In order to start workflows from workbench, there needs to be Docker image @arvados/jobs@ tagged with the version of Arvados you are installing. The following command downloads the latest arvados/jobs image from Docker Hub, loads it into Keep.  In this example @$project_uuid@ should be the UUID of the "Arvados Standard Docker Images" project.
-
-<notextile>
-<pre><code>~$ <span class="userinput">arv-keepdocker --pull arvados/jobs latest --project-uuid $project_uuid</span>
-</code></pre></notextile>
-
-If the image needs to be downloaded from Docker Hub, the command can take a few minutes to complete, depending on available network bandwidth.
index 8819b0210f94609fc883eb2c55be05ce5a98c575..ba179f82ddc9bd92a870acba0e1176396f4aab7c 100644 (file)
@@ -47,7 +47,6 @@ table(table table-bordered table-condensed).
 |\3=. *Additional services*|
 |"Websockets server":install-ws.html |Event distribution server.|Required to view streaming container logs in Workbench.|
 |"Shell server":install-shell-server.html |Grant Arvados users access to Unix shell accounts on dedicated shell nodes.|Optional.|
 |\3=. *Additional services*|
 |"Websockets server":install-ws.html |Event distribution server.|Required to view streaming container logs in Workbench.|
 |"Shell server":install-shell-server.html |Grant Arvados users access to Unix shell accounts on dedicated shell nodes.|Optional.|
-|"Git server":install-arv-git-httpd.html |Arvados-hosted git repositories, with Arvados-token based authentication.|Optional|
 |\3=. *Crunch (running containers)*|
 |"arvados-dispatch-cloud":crunch2-cloud/install-dispatch-cloud.html |Run analysis workflows on cloud by allocating and freeing cloud VM instances on demand.|Optional|
 |"crunch-dispatch-slurm":crunch2-slurm/install-dispatch.html |Run analysis workflows distributed across a Slurm cluster.|Optional|
 |\3=. *Crunch (running containers)*|
 |"arvados-dispatch-cloud":crunch2-cloud/install-dispatch-cloud.html |Run analysis workflows on cloud by allocating and freeing cloud VM instances on demand.|Optional|
 |"crunch-dispatch-slurm":crunch2-slurm/install-dispatch.html |Run analysis workflows distributed across a Slurm cluster.|Optional|
@@ -96,7 +95,7 @@ For a production installation, this is a reasonable starting point:
 <div class="offset1">
 table(table table-bordered table-condensed).
 |_. Function|_. Number of nodes|_. Recommended specs|
 <div class="offset1">
 table(table table-bordered table-condensed).
 |_. Function|_. Number of nodes|_. Recommended specs|
-|PostgreSQL database, Arvados API server, Arvados controller, Git, Websockets, Container dispatcher|1|16+ GiB RAM, 4+ cores, fast disk for database|
+|PostgreSQL database, Arvados API server, Arvados controller, Websockets, Container dispatcher|1|16+ GiB RAM, 4+ cores, fast disk for database|
 |Workbench, Keepproxy, Keep-web, Keep-balance|1|8 GiB RAM, 2+ cores|
 |Keepstore servers ^1^|2+|4 GiB RAM|
 |Compute worker nodes ^1^|0+ |Depends on workload; scaled dynamically in the cloud|
 |Workbench, Keepproxy, Keep-web, Keep-balance|1|8 GiB RAM, 2+ cores|
 |Keepstore servers ^1^|2+|4 GiB RAM|
 |Compute worker nodes ^1^|0+ |Depends on workload; scaled dynamically in the cloud|
@@ -138,7 +137,6 @@ It is possible to use custom DNS names for the Arvados services.
 table(table table-bordered table-condensed).
 |_. Function|_. DNS name|
 |Arvados API|@ClusterID.example.com@|
 table(table table-bordered table-condensed).
 |_. Function|_. DNS name|
 |Arvados API|@ClusterID.example.com@|
-|Arvados Git server|git.@ClusterID.example.com@|
 |Arvados Webshell|webshell.@ClusterID.example.com@|
 |Arvados Websockets endpoint|ws.@ClusterID.example.com@|
 |Arvados Workbench|workbench.@ClusterID.example.com@|
 |Arvados Webshell|webshell.@ClusterID.example.com@|
 |Arvados Websockets endpoint|ws.@ClusterID.example.com@|
 |Arvados Workbench|workbench.@ClusterID.example.com@|
index f864f37563ba42b83cb8e7c54fbfcae2c425b3e6..9520c08397dfb650cfebbc2941756129e0cd580b 100644 (file)
@@ -12,7 +12,6 @@ SPDX-License-Identifier: CC-BY-SA-3.0
 # "Introduction":#introduction
 # "Install Dependencies and SDKs":#dependencies
 # "Install git and curl":#install-packages
 # "Introduction":#introduction
 # "Install Dependencies and SDKs":#dependencies
 # "Install git and curl":#install-packages
-# "Update Git Config":#config-git
 # "Create record for VM":#vm-record
 # "Install arvados-login-sync":#arvados-login-sync
 # "Confirm working installation":#confirm-working
 # "Create record for VM":#vm-record
 # "Install arvados-login-sync":#arvados-login-sync
 # "Confirm working installation":#confirm-working
@@ -44,17 +43,6 @@ h2(#dependencies). Install Dependencies and SDKs
 
 {% include 'install_packages' %}
 
 
 {% include 'install_packages' %}
 
-h2(#config-git). Update Git Config
-
-Configure git to use the ARVADOS_API_TOKEN environment variable to authenticate to arvados-git-httpd. We use the @--system@ flag so it takes effect for all current and future user accounts. It does not affect git's behavior when connecting to other git servers.
-
-<notextile>
-<pre>
-<code># <span class="userinput">git config --system 'credential.https://git.<b>ClusterID.example.com</b>/.username' none</span></code>
-<code># <span class="userinput">git config --system 'credential.https://git.<b>ClusterID.example.com</b>/.helper' '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'</span></code>
-</pre>
-</notextile>
-
 h2(#vm-record). Create record for VM
 
 As an admin, create an Arvados virtual_machine object representing this shell server. This will return a uuid.
 h2(#vm-record). Create record for VM
 
 As an admin, create an Arvados virtual_machine object representing this shell server. This will return a uuid.
index a3cdd03300c0f9722611492175c216880301a888..ddf9e5d797d9b84158433fa5b83aa414646624d0 100644 (file)
@@ -392,16 +392,22 @@ This will install and configure Arvados on all the nodes.  It will take a while
 
 h2(#test-install). Confirm the cluster is working
 
 
 h2(#test-install). Confirm the cluster is working
 
-When everything has finished, you can run the diagnostics.
+When everything has finished, you can run the diagnostics. There's a couple ways of doing this listed below.
 
 
-Depending on where you are running the installer, you need to provide @-internal-client@ or @-external-client@.
+h3. Running diagnostics from the same system as the installer
 
 
-If you are running the diagnostics from one of the Arvados machines inside the private network, you want @-internal-client@ .
+The requirements to run diagnostics are having @arvados-client@ and @docker@ installed. If this is not possible you can run them on your Arvados shell node as explained in the next section.
 
 
-You are an "external client" if you running the diagnostics from your workstation outside of the private network.
+Depending on where you are running the installer, you need to provide @-internal-client@ or @-external-client@. If you are running the installer from a host connected to the Arvados private network, use @-internal-client@. Otherwise, use @-external-client@.
 
 <pre><code class="userinput">./installer.sh diagnostics (-internal-client|-external-client)</code></pre>
 
 
 <pre><code class="userinput">./installer.sh diagnostics (-internal-client|-external-client)</code></pre>
 
+h3. Running diagnostics from a cluster node
+
+You can run the diagnostics from the cluster's shell node. This has the advantage that you don't need to manage any software on your local system, but might not be a possibility if your Arvados cluster doesn't include a shell node.
+
+<pre><code class="userinput">./installer.sh diagnostics-internal</code></pre>
+
 h3(#debugging). Debugging issues
 
 The installer records log files for each deployment.
 h3(#debugging). Debugging issues
 
 The installer records log files for each deployment.
index ea10c830bc44006363d6270e91cb9bf951b40c38..827f1d08768fae86085cae8b44c35d620ffa0fff 100644 (file)
@@ -31,12 +31,12 @@ Available flags:
 Use 'arv subcommand|resource --help' to get more information about a particular
 command or resource.
 
 Use 'arv subcommand|resource --help' to get more information about a particular
 command or resource.
 
-Available subcommands: copy, create, edit, keep, pipeline, run, tag, ws
+Available subcommands: copy, create, edit, keep, run, tag, ws
 
 
-Available resources: api_client_authorization, api_client, authorized_key,
-collection, user_agreement, group, job_task, link, log, keep_disk,
-pipeline_instance, node, repository, specimen, pipeline_template, user,
-virtual_machine, trait, human, job, keep_service
+Available resources: api_client_authorization, api_client,
+authorized_key, collection, container, container_request,
+user_agreement, group, keep_service, link, log, user, virtual_machine,
+workflow
 
 Additional options:
   -e, --version       Print version and exit
 
 Additional options:
   -e, --version       Print version and exit
diff --git a/doc/user/tutorials/add-new-repository.html.textile.liquid b/doc/user/tutorials/add-new-repository.html.textile.liquid
deleted file mode 100644 (file)
index 6046e7d..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
----
-layout: default
-navsection: userguide
-title: Adding a new Arvados git repository
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-Arvados supports managing git repositories. You can access these repositories using your Arvados credentials and share them with other Arvados users.
-
-{% include 'tutorial_expectations' %}
-
-h2. Setting up Git
-
-Before you start using Git and arvados repositories, you should do some basic configuration (you only need to do this the first time):
-
-<notextile>
-<pre><code>~$ <span class="userinput">git config --global user.name "Your Name"</span>
-~$ <span class="userinput">git config --global user.email $USER@example.com</span></code></pre>
-</notextile>
-
-h2. Add "tutorial" repository
-
-On the Arvados Workbench, click on the dropdown menu icon <i class="fa fa-lg fa-user"></i> (Account Management) in the upper right corner of the top navigation menu to access the user settings menu, and click on the menu item *Repositories*.
-
-In the *Repositories* page, you will see the <span class="btn btn-sm btn-primary">+ NEW REPOSITORY</span> button.
-
-!{width: 100%;}{{ site.baseurl }}/images/repositories-panel.png!
-
-Click the <span class="btn btn-sm btn-primary">+ NEW REPOSITORY</span> button to open the popup to add a new Arvados repository. You will see a text box where you can enter the name of the repository. Enter *tutorial* in this text box and click on *Create*.
-
-{% include 'notebox_begin' %}
-The name you enter here must begin with a letter and can only contain alphanumeric characters.
-{% include 'notebox_end' %}
-
-!{width: 100%;}{{ site.baseurl }}/images/add-new-repository.png!
-
-This will create a new repository with the name @$USER/tutorial@. It can be accessed using the URL <notextile><code>https://git.{{ site.arvados_api_host }}/$USER/tutorial.git</code></notextile> or <notextile><code>git@git.{{ site.arvados_api_host }}:$USER/tutorial.git</code></notextile>
-
-Back in the *Repositories* page, you should see the @$USER/tutorial@ repository listed in the name column with these URLs.
-
-!{display: block;margin-left: 25px;margin-right: auto;}{{ site.baseurl }}/images/added-new-repository.png!
-
-You are now ready to use this *tutorial* repository to run your crunch scripts.
diff --git a/doc/user/tutorials/git-arvados-guide.html.textile.liquid b/doc/user/tutorials/git-arvados-guide.html.textile.liquid
deleted file mode 100644 (file)
index a4ac2a5..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
----
-layout: default
-navsection: userguide
-title: Working with an Arvados git repository
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-This tutorial describes how to work with an Arvados-managed git repository. Working with an Arvados git repository is very similar to working with other public git repositories.
-
-{% include 'tutorial_expectations' %}
-
-{% include 'tutorial_git_repo_expectations' %}
-
-h2. Cloning a git repository
-
-Before you start using Git, you should do some basic configuration (you only need to do this the first time):
-
-<notextile>
-<pre><code>~$ <span class="userinput">git config --global user.name "Your Name"</span>
-~$ <span class="userinput">git config --global user.email $USER@example.com</span></code></pre>
-</notextile>
-
-On the Arvados Workbench, click on the dropdown menu icon <i class="fa fa-lg fa-user"></i> in the upper right corner of the top navigation menu to access the Account Management menu, and click on the menu item *Repositories*. In the *Repositories* page, you should see the @$USER/tutorial@ repository listed in the *name* column.  Next to *name* is the column *URL*. Copy the *URL* value associated with your repository.  This should look like <notextile><code>https://git.{{ site.arvados_api_host }}/$USER/tutorial.git</code></notextile>. Alternatively, you can use <notextile><code>git@git.{{ site.arvados_api_host }}:$USER/tutorial.git</code></notextile>
-
-Next, on the Arvados virtual machine, clone your Git repository:
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd $HOME</span> # (or wherever you want to install)
-~$ <span class="userinput">git clone https://git.{{ site.arvados_api_host }}/$USER/tutorial.git</span>
-Cloning into 'tutorial'...</code></pre>
-</notextile>
-
-This will create a Git repository in the directory called @tutorial@ in your home directory. Say yes when prompted to continue with connection.
-Ignore any warning that you are cloning an empty repository.
-
-*Note:* If you are prompted for username and password when you try to git clone using this command, you may first need to update your git configuration. Execute the following commands to update your git configuration.
-
-<notextile>
-<pre>
-<code>~$ <span class="userinput">git config 'credential.https://git.{{ site.arvados_api_host }}/.username' none</span></code>
-<code>~$ <span class="userinput">git config 'credential.https://git.{{ site.arvados_api_host }}/.helper' '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'</span></code>
-</pre>
-</notextile>
-
-h2. Creating a git branch in an Arvados repository
-
-Create a git branch named *tutorial_branch* in the *tutorial* Arvados git repository.
-
-<notextile>
-<pre><code>~$ <span class="userinput">cd tutorial</span>
-~/tutorial$ <span class="userinput">git checkout -b tutorial_branch</span>
-</code></pre>
-</notextile>
-
-h2. Adding scripts to an Arvados repository
-
-A git repository is a good place to store the CWL workflows that you run on Arvados.
-
-First, create a simple CWL CommandLineTool:
-
-notextile. <pre>~/tutorials$ <code class="userinput">nano hello.cwl</code></pre>
-
-<notextile> {% code tutorial_hello_cwl as yaml %} </notextile>
-
-Next, add the file to the git repository.  This tells @git@ that the file should be included on the next commit.
-
-notextile. <pre><code>~/tutorial$ <span class="userinput">git add hello.cwl</span></code></pre>
-
-Next, commit your changes.  All staged changes are recorded into the local git repository:
-
-<notextile>
-<pre><code>~/tutorial$ <span class="userinput">git commit -m "my first script"</span>
-</code></pre>
-</notextile>
-
-Finally, upload your changes to the remote repository:
-
-<notextile>
-<pre><code>~/tutorial/crunch_scripts$ <span class="userinput">git push origin tutorial_branch</span>
-</code></pre>
-</notextile>
-
-The same steps can be used to add any of your custom bash, R, or python scripts to an Arvados repository.
diff --git a/go.mod b/go.mod
index 0011d7970f0c47811cc47b3474feea3ad9c6048b..aaad05dab2dd8099c696b26de7196886b3ed7cde 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -11,11 +11,12 @@ require (
        github.com/arvados/cgofuse v1.2.0-arvados1
        github.com/aws/aws-sdk-go v1.44.174
        github.com/aws/aws-sdk-go-v2 v0.23.0
        github.com/arvados/cgofuse v1.2.0-arvados1
        github.com/aws/aws-sdk-go v1.44.174
        github.com/aws/aws-sdk-go-v2 v0.23.0
+       github.com/bmatcuk/doublestar/v4 v4.6.1
        github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092
        github.com/coreos/go-oidc/v3 v3.5.0
        github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
        github.com/creack/pty v1.1.18
        github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092
        github.com/coreos/go-oidc/v3 v3.5.0
        github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
        github.com/creack/pty v1.1.18
-       github.com/docker/docker v24.0.7+incompatible
+       github.com/docker/docker v24.0.9+incompatible
        github.com/dustin/go-humanize v1.0.0
        github.com/fsnotify/fsnotify v1.4.9
        github.com/ghodss/yaml v1.0.0
        github.com/dustin/go-humanize v1.0.0
        github.com/fsnotify/fsnotify v1.4.9
        github.com/ghodss/yaml v1.0.0
@@ -37,10 +38,10 @@ require (
        github.com/prometheus/client_model v0.3.0
        github.com/prometheus/common v0.39.0
        github.com/sirupsen/logrus v1.8.1
        github.com/prometheus/client_model v0.3.0
        github.com/prometheus/common v0.39.0
        github.com/sirupsen/logrus v1.8.1
-       golang.org/x/crypto v0.17.0
-       golang.org/x/net v0.19.0
+       golang.org/x/crypto v0.22.0
+       golang.org/x/net v0.24.0
        golang.org/x/oauth2 v0.11.0
        golang.org/x/oauth2 v0.11.0
-       golang.org/x/sys v0.15.0
+       golang.org/x/sys v0.19.0
        google.golang.org/api v0.126.0
        gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
        gopkg.in/square/go-jose.v2 v2.5.1
        google.golang.org/api v0.126.0
        gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
        gopkg.in/square/go-jose.v2 v2.5.1
@@ -74,7 +75,7 @@ require (
        github.com/docker/go-units v0.4.0 // indirect
        github.com/gliderlabs/ssh v0.2.2 // indirect
        github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect
        github.com/docker/go-units v0.4.0 // indirect
        github.com/gliderlabs/ssh v0.2.2 // indirect
        github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect
-       github.com/go-jose/go-jose/v3 v3.0.1 // indirect
+       github.com/go-jose/go-jose/v3 v3.0.3 // indirect
        github.com/golang-jwt/jwt/v4 v4.1.0 // indirect
        github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
        github.com/golang/protobuf v1.5.3 // indirect
        github.com/golang-jwt/jwt/v4 v4.1.0 // indirect
        github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
        github.com/golang/protobuf v1.5.3 // indirect
@@ -98,7 +99,7 @@ require (
        github.com/pkg/errors v0.9.1 // indirect
        github.com/prometheus/procfs v0.9.0 // indirect
        github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
        github.com/pkg/errors v0.9.1 // indirect
        github.com/prometheus/procfs v0.9.0 // indirect
        github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
-       github.com/satori/go.uuid v1.2.1-0.20180103174451-36e9d2ebbde5 // indirect
+       github.com/satori/go.uuid v1.2.1-0.20180404165556-75cca531ea76 // indirect
        github.com/sergi/go-diff v1.0.0 // indirect
        github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 // indirect
        github.com/src-d/gcfg v1.3.0 // indirect
        github.com/sergi/go-diff v1.0.0 // indirect
        github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 // indirect
        github.com/src-d/gcfg v1.3.0 // indirect
@@ -110,7 +111,7 @@ require (
        google.golang.org/appengine v1.6.7 // indirect
        google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
        google.golang.org/grpc v1.59.0 // indirect
        google.golang.org/appengine v1.6.7 // indirect
        google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
        google.golang.org/grpc v1.59.0 // indirect
-       google.golang.org/protobuf v1.31.0 // indirect
+       google.golang.org/protobuf v1.33.0 // indirect
        gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
        gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 // indirect
        gopkg.in/warnings.v0 v0.1.2 // indirect
        gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
        gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 // indirect
        gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/go.sum b/go.sum
index fb2fe5e3f04d56129fd32a7a7fc5240ca2bb22d0..d5857898172b8cf93abdeab71df0a669372e96e5 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -59,6 +59,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
+github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092 h1:0Di2onNnlN5PAyWPbqlPyN45eOQ+QW/J9eqLynt4IV4=
 github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092/go.mod h1:8IzBjZCRSnsvM6MJMG8HNNtnzMl48H22rbJL2kRUJ0Y=
 github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092 h1:0Di2onNnlN5PAyWPbqlPyN45eOQ+QW/J9eqLynt4IV4=
 github.com/bradleypeabody/godap v0.0.0-20170216002349-c249933bc092/go.mod h1:8IzBjZCRSnsvM6MJMG8HNNtnzMl48H22rbJL2kRUJ0Y=
@@ -89,8 +91,8 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
 github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
 github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
 github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
-github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
+github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o=
 github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o=
 github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
@@ -113,8 +115,8 @@ github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev
 github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
 github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
 github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
-github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
-github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
+github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
+github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
 github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
 github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -156,6 +158,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
 github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
 github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
@@ -246,8 +249,8 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
 github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
 github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
-github.com/satori/go.uuid v1.2.1-0.20180103174451-36e9d2ebbde5 h1:Jw7W4WMfQDxsXvfeFSaS2cHlY7bAF4MGrgnbd0+Uo78=
-github.com/satori/go.uuid v1.2.1-0.20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/satori/go.uuid v1.2.1-0.20180404165556-75cca531ea76 h1:ofyVTM1w4iyKwaQIlRR6Ip06mXXx5Cnz7a4mTGYq1hE=
+github.com/satori/go.uuid v1.2.1-0.20180404165556-75cca531ea76/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0=
@@ -287,8 +290,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -296,6 +300,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -316,8 +321,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
@@ -330,6 +337,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -348,13 +356,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
-golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -363,6 +377,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
@@ -420,8 +436,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
index 9f1091eac39177f15855de7b7e8ccf2b0ac443f3..338a6b5bcc494b1fdfbcd50c3263fed119ceb916 100644 (file)
@@ -74,7 +74,6 @@ func (runNginx) Run(ctx context.Context, fail func(error), super *Supervisor) er
                {"KEEPWEB", super.cluster.Services.WebDAV},
                {"KEEPWEBDL", super.cluster.Services.WebDAVDownload},
                {"KEEPPROXY", super.cluster.Services.Keepproxy},
                {"KEEPWEB", super.cluster.Services.WebDAV},
                {"KEEPWEBDL", super.cluster.Services.WebDAVDownload},
                {"KEEPPROXY", super.cluster.Services.Keepproxy},
-               {"GIT", super.cluster.Services.GitHTTP},
                {"HEALTH", super.cluster.Services.Health},
                {"WORKBENCH1", super.cluster.Services.Workbench1},
                {"WORKBENCH2", super.cluster.Services.Workbench2},
                {"HEALTH", super.cluster.Services.Health},
                {"WORKBENCH1", super.cluster.Services.Workbench1},
                {"WORKBENCH2", super.cluster.Services.Workbench2},
@@ -135,7 +134,7 @@ func (runNginx) Run(ctx context.Context, fail func(error), super *Supervisor) er
                }
        }
 
                }
        }
 
-       configs := "error_log stderr info; "
+       configs := "error_log stderr warn; "
        configs += "pid " + filepath.Join(super.wwwtempdir, "nginx.pid") + "; "
        configs += "user www-data; "
 
        configs += "pid " + filepath.Join(super.wwwtempdir, "nginx.pid") + "; "
        configs += "user www-data; "
 
index 5367337e81a1d0e605160c9c0dd789c9282a21f2..bf2ca2a78b5c25e8f5652e51ae7260040af98b3c 100644 (file)
@@ -84,14 +84,9 @@ func (runner installPassenger) Run(ctx context.Context, fail func(error), super
        if err != nil {
                return err
        }
        if err != nil {
                return err
        }
-       for _, version := range []string{"2.2.19"} {
-               if !strings.Contains(buf.String(), "("+version+")") {
-                       err = super.RunProgram(ctx, appdir, runOptions{}, "gem", "install", "--user", "--conservative", "--no-document", "bundler:2.2.19")
-                       if err != nil {
-                               return err
-                       }
-                       break
-               }
+       err = super.RunProgram(ctx, appdir, runOptions{}, "gem", "install", "--user", "--conservative", "--no-document", "--version", "~> 2.4.0", "bundler")
+       if err != nil {
+               return err
        }
        err = super.RunProgram(ctx, appdir, runOptions{}, "bundle", "config", "--set", "local", "path", filepath.Join(os.Getenv("HOME"), ".gem"))
        if err != nil {
        }
        err = super.RunProgram(ctx, appdir, runOptions{}, "bundle", "config", "--set", "local", "path", filepath.Join(os.Getenv("HOME"), ".gem"))
        if err != nil {
index ac269b933abd226551441e977e4ce0f3daea896a..67649e75de55132692767184e05cbb932b5f97e1 100644 (file)
@@ -366,7 +366,6 @@ func (super *Supervisor) runCluster() error {
                runNginx{},
                railsDatabase{},
                runServiceCommand{name: "controller", svc: super.cluster.Services.Controller, depends: []supervisedTask{railsDatabase{}}},
                runNginx{},
                railsDatabase{},
                runServiceCommand{name: "controller", svc: super.cluster.Services.Controller, depends: []supervisedTask{railsDatabase{}}},
-               runServiceCommand{name: "git-httpd", svc: super.cluster.Services.GitHTTP},
                runServiceCommand{name: "health", svc: super.cluster.Services.Health},
                runServiceCommand{name: "keepproxy", svc: super.cluster.Services.Keepproxy, depends: []supervisedTask{runPassenger{src: "services/api"}}},
                runServiceCommand{name: "keepstore", svc: super.cluster.Services.Keepstore},
                runServiceCommand{name: "health", svc: super.cluster.Services.Health},
                runServiceCommand{name: "keepproxy", svc: super.cluster.Services.Keepproxy, depends: []supervisedTask{runPassenger{src: "services/api"}}},
                runServiceCommand{name: "keepstore", svc: super.cluster.Services.Keepstore},
@@ -821,7 +820,6 @@ func (super *Supervisor) autofillConfig() error {
        for _, svc := range []*arvados.Service{
                &super.cluster.Services.Controller,
                &super.cluster.Services.DispatchCloud,
        for _, svc := range []*arvados.Service{
                &super.cluster.Services.Controller,
                &super.cluster.Services.DispatchCloud,
-               &super.cluster.Services.GitHTTP,
                &super.cluster.Services.Health,
                &super.cluster.Services.Keepproxy,
                &super.cluster.Services.Keepstore,
                &super.cluster.Services.Health,
                &super.cluster.Services.Keepproxy,
                &super.cluster.Services.Keepstore,
@@ -839,7 +837,6 @@ func (super *Supervisor) autofillConfig() error {
                        }
                        host := net.JoinHostPort(defaultExtHost, port)
                        if svc == &super.cluster.Services.Controller ||
                        }
                        host := net.JoinHostPort(defaultExtHost, port)
                        if svc == &super.cluster.Services.Controller ||
-                               svc == &super.cluster.Services.GitHTTP ||
                                svc == &super.cluster.Services.Health ||
                                svc == &super.cluster.Services.Keepproxy ||
                                svc == &super.cluster.Services.WebDAV ||
                                svc == &super.cluster.Services.Health ||
                                svc == &super.cluster.Services.Keepproxy ||
                                svc == &super.cluster.Services.WebDAV ||
index a3ae4fd56bbc179f67fc1f21e6c9cdb2db5c43df..b045553b23bd5997c8934422b00ba75b51152dad 100644 (file)
@@ -74,12 +74,6 @@ Clusters:
       Keepbalance:
         InternalURLs: {SAMPLE: {ListenURL: ""}}
         ExternalURL: ""
       Keepbalance:
         InternalURLs: {SAMPLE: {ListenURL: ""}}
         ExternalURL: ""
-      GitHTTP:
-        InternalURLs: {SAMPLE: {ListenURL: ""}}
-        ExternalURL: ""
-      GitSSH:
-        InternalURLs: {SAMPLE: {ListenURL: ""}}
-        ExternalURL: ""
       DispatchCloud:
         InternalURLs: {SAMPLE: {ListenURL: ""}}
         ExternalURL: ""
       DispatchCloud:
         InternalURLs: {SAMPLE: {ListenURL: ""}}
         ExternalURL: ""
@@ -340,7 +334,6 @@ Clusters:
       # AutoSetupUsernameBlacklist is a list of usernames to be blacklisted for auto setup.
       AutoSetupNewUsers: false
       AutoSetupNewUsersWithVmUUID: ""
       # AutoSetupUsernameBlacklist is a list of usernames to be blacklisted for auto setup.
       AutoSetupNewUsers: false
       AutoSetupNewUsersWithVmUUID: ""
-      AutoSetupNewUsersWithRepository: false
       AutoSetupUsernameBlacklist:
         arvados: {}
         git: {}
       AutoSetupUsernameBlacklist:
         arvados: {}
         git: {}
@@ -801,6 +794,14 @@ Clusters:
       # load on the API server and you don't need it.
       WebDAVLogEvents: true
 
       # load on the API server and you don't need it.
       WebDAVLogEvents: true
 
+      # Per-connection output buffer for WebDAV downloads. May improve
+      # throughput for large files, particularly when storage volumes
+      # have high latency.
+      #
+      # Size be specified as a number of bytes ("0") or with units
+      # ("128KiB", "1 MB").
+      WebDAVOutputBuffer: 0
+
     Login:
       # One of the following mechanisms (Google, PAM, LDAP, or
       # LoginCluster) should be enabled; see
     Login:
       # One of the following mechanisms (Google, PAM, LDAP, or
       # LoginCluster) should be enabled; see
@@ -1050,24 +1051,6 @@ Clusters:
       # production use.
       TrustPrivateNetworks: false
 
       # production use.
       TrustPrivateNetworks: false
 
-    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
-      # {git_repositories_dir}/arvados/.git
-      Repositories: /var/lib/arvados/git/repositories
-
     TLS:
       # Use "file:///var/lib/acme/live/example.com/cert" and
       # ".../privkey" to load externally managed certificates.
     TLS:
       # Use "file:///var/lib/acme/live/example.com/cert" and
       # ".../privkey" to load externally managed certificates.
@@ -1327,47 +1310,6 @@ Clusters:
         SbatchArgumentsList: []
         SbatchEnvironmentVariables:
           SAMPLE: ""
         SbatchArgumentsList: []
         SbatchEnvironmentVariables:
           SAMPLE: ""
-        Managed:
-          # Path to dns server configuration directory
-          # (e.g. /etc/unbound.d/conf.d). If false, do not write any config
-          # files or touch restart.txt (see below).
-          DNSServerConfDir: ""
-
-          # Template file for the dns server host snippets. See
-          # unbound.template in this directory for an example. If false, do
-          # not write any config files.
-          DNSServerConfTemplate: ""
-
-          # String to write to {dns_server_conf_dir}/restart.txt (with a
-          # trailing newline) after updating local data. If false, do not
-          # open or write the restart.txt file.
-          DNSServerReloadCommand: ""
-
-          # Command to run after each DNS update. Template variables will be
-          # substituted; see the "unbound" example below. If false, do not run
-          # a command.
-          DNSServerUpdateCommand: ""
-
-          ComputeNodeDomain: ""
-          ComputeNodeNameservers:
-            "192.168.1.1": {}
-            SAMPLE: {}
-
-          # Hostname to assign to a compute node when it sends a "ping" and the
-          # hostname in its Node record is nil.
-          # During bootstrapping, the "ping" script is expected to notice the
-          # hostname given in the ping response, and update its unix hostname
-          # accordingly.
-          # If false, leave the hostname alone (this is appropriate if your compute
-          # nodes' hostnames are already assigned by some other mechanism).
-          #
-          # One way or another, the hostnames of your node records should agree
-          # with your DNS records and your /etc/slurm-llnl/slurm.conf files.
-          #
-          # Example for compute0000, compute0001, ....:
-          # assign_node_hostname: compute%<slot_number>04d
-          # (See http://ruby-doc.org/core-2.2.2/Kernel.html#method-i-format for more.)
-          AssignNodeHostname: "compute%<slot_number>d"
 
       LSF:
         # Arguments to bsub when submitting Arvados containers as LSF jobs.
 
       LSF:
         # Arguments to bsub when submitting Arvados containers as LSF jobs.
@@ -1435,12 +1377,6 @@ Clusters:
         # 'false' -- disable the Jobs API despite presence of existing records.
         Enable: 'auto'
 
         # 'false' -- disable the Jobs API despite presence of existing records.
         Enable: 'auto'
 
-        # 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
-        # {git_repositories_dir}/arvados/.git
-        GitInternalDir: /var/lib/arvados/internal.git
-
       CloudVMs:
         # Enable the cloud scheduler.
         Enable: false
       CloudVMs:
         # Enable the cloud scheduler.
         Enable: false
index d518b3414ad193795179f7e9776bccc26fedc129..0db3de7fc99d987cce1cf43dd5a982c590653272 100644 (file)
@@ -510,56 +510,6 @@ func (ldr *Loader) loadOldKeepWebConfig(cfg *arvados.Config) error {
        return nil
 }
 
        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("arvados-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)
-
-       if oc.Listen != nil {
-               cluster.Services.GitHTTP.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
-       }
-       if oc.ManagementToken != nil {
-               cluster.ManagementToken = *oc.ManagementToken
-       }
-       if oc.GitCommand != nil {
-               cluster.Git.GitCommand = *oc.GitCommand
-       }
-       if oc.GitoliteHome != nil {
-               cluster.Git.GitoliteHome = *oc.GitoliteHome
-       }
-       if oc.RepoRoot != nil {
-               cluster.Git.Repositories = *oc.RepoRoot
-       }
-
-       cfg.Clusters[cluster.ClusterID] = *cluster
-       return nil
-}
-
 const defaultKeepBalanceConfigPath = "/etc/arvados/keep-balance/keep-balance.yml"
 
 type oldKeepBalanceConfig struct {
 const defaultKeepBalanceConfigPath = "/etc/arvados/keep-balance/keep-balance.yml"
 
 type oldKeepBalanceConfig struct {
index e06a1f231d96887467759087c14f8fb74b4b3e87..f73a92be5c44c4425aef83a7a8ae76fdba081d92 100644 (file)
@@ -283,52 +283,6 @@ func fmtKeepproxyConfig(param string, debugLog bool) string {
 `, debugLog, param)
 }
 
 `, 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.Assert(err, check.IsNil)
-       c.Assert(cluster, check.NotNil)
-       c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com", Path: "/"})
-       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{})
-}
-
-// Tests fix for https://dev.arvados.org/issues/15642
-func (s *LoadSuite) TestLegacyArvGitHttpdConfigDoesntDisableMissingItems(c *check.C) {
-       content := []byte(`
-{
-       "Client": {
-               "Scheme": "",
-               "APIHost": "example.com",
-               "AuthToken": "abcdefg",
-       }
-}
-`)
-       cluster, err := testLoadLegacyConfig(content, "-legacy-git-httpd-config", c)
-       c.Assert(err, check.IsNil)
-       // The resulting ManagementToken should be the one set up on the test server.
-       c.Check(cluster.ManagementToken, check.Equals, TestServerManagementToken)
-}
-
 func (s *LoadSuite) TestLegacyKeepBalanceConfig(c *check.C) {
        f := "-legacy-keepbalance-config"
        content := []byte(fmtKeepBalanceConfig(""))
 func (s *LoadSuite) TestLegacyKeepBalanceConfig(c *check.C) {
        f := "-legacy-keepbalance-config"
        content := []byte(fmtKeepBalanceConfig(""))
index 4b6c142ff2e29f41bcf2b843ac6479b54dd436aa..3c1e6bc00822315967f49320289ba31be09b9fc3 100644 (file)
@@ -59,107 +59,106 @@ func ExportJSON(w io.Writer, cluster *arvados.Cluster) error {
 // exists.
 var whitelist = map[string]bool{
        // | sort -t'"' -k2,2
 // exists.
 var whitelist = map[string]bool{
        // | sort -t'"' -k2,2
-       "API":                                      true,
-       "API.AsyncPermissionsUpdateInterval":       false,
-       "API.DisabledAPIs":                         false,
-       "API.FreezeProjectRequiresDescription":     true,
-       "API.FreezeProjectRequiresProperties":      true,
-       "API.FreezeProjectRequiresProperties.*":    true,
-       "API.KeepServiceRequestTimeout":            false,
-       "API.LockBeforeUpdate":                     false,
-       "API.LogCreateRequestFraction":             false,
-       "API.MaxConcurrentRailsRequests":           false,
-       "API.MaxConcurrentRequests":                false,
-       "API.MaxGatewayTunnels":                    false,
-       "API.MaxIndexDatabaseRead":                 false,
-       "API.MaxItemsPerResponse":                  true,
-       "API.MaxKeepBlobBuffers":                   false,
-       "API.MaxQueuedRequests":                    false,
-       "API.MaxQueueTimeForLockRequests":          false,
-       "API.MaxRequestAmplification":              false,
-       "API.MaxRequestSize":                       true,
-       "API.MaxTokenLifetime":                     false,
-       "API.RequestTimeout":                       true,
-       "API.SendTimeout":                          true,
-       "API.UnfreezeProjectRequiresAdmin":         true,
-       "API.VocabularyPath":                       false,
-       "API.WebsocketClientEventQueue":            false,
-       "API.WebsocketServerEventQueue":            false,
-       "AuditLogs":                                false,
-       "AuditLogs.MaxAge":                         false,
-       "AuditLogs.MaxDeleteBatch":                 false,
-       "AuditLogs.UnloggedAttributes":             false,
-       "ClusterID":                                true,
-       "Collections":                              true,
-       "Collections.BalanceCollectionBatch":       false,
-       "Collections.BalanceCollectionBuffers":     false,
-       "Collections.BalancePeriod":                false,
-       "Collections.BalancePullLimit":             false,
-       "Collections.BalanceTimeout":               false,
-       "Collections.BalanceTrashLimit":            false,
-       "Collections.BalanceUpdateLimit":           false,
-       "Collections.BlobDeleteConcurrency":        false,
-       "Collections.BlobMissingReport":            false,
-       "Collections.BlobReplicateConcurrency":     false,
-       "Collections.BlobSigning":                  true,
-       "Collections.BlobSigningKey":               false,
-       "Collections.BlobSigningTTL":               true,
-       "Collections.BlobTrash":                    false,
-       "Collections.BlobTrashCheckInterval":       false,
-       "Collections.BlobTrashConcurrency":         false,
-       "Collections.BlobTrashLifetime":            false,
-       "Collections.CollectionVersioning":         true,
-       "Collections.DefaultReplication":           true,
-       "Collections.DefaultTrashLifetime":         true,
-       "Collections.ForwardSlashNameSubstitution": true,
-       "Collections.KeepproxyPermission":          false,
-       "Collections.ManagedProperties":            true,
-       "Collections.ManagedProperties.*":          true,
-       "Collections.ManagedProperties.*.*":        true,
-       "Collections.PreserveVersionIfIdle":        true,
-       "Collections.S3FolderObjects":              true,
-       "Collections.TrashSweepInterval":           false,
-       "Collections.TrustAllContent":              true,
-       "Collections.WebDAVCache":                  false,
-       "Collections.WebDAVLogEvents":              false,
-       "Collections.WebDAVPermission":             false,
-       "Containers":                               true,
-       "Containers.AlwaysUsePreemptibleInstances": true,
-       "Containers.CloudVMs":                      false,
-       "Containers.CrunchRunArgumentsList":        false,
-       "Containers.CrunchRunCommand":              false,
-       "Containers.DefaultKeepCacheRAM":           true,
-       "Containers.DispatchPrivateKey":            false,
-       "Containers.JobsAPI":                       true,
-       "Containers.JobsAPI.Enable":                true,
-       "Containers.JobsAPI.GitInternalDir":        false,
-       "Containers.LocalKeepBlobBuffersPerVCPU":   false,
-       "Containers.LocalKeepLogsToContainerLog":   false,
-       "Containers.Logging":                       false,
-       "Containers.LogReuseDecisions":             false,
-       "Containers.LSF":                           false,
-       "Containers.MaxDispatchAttempts":           false,
-       "Containers.MaximumPriceFactor":            true,
-       "Containers.MaxRetryAttempts":              true,
-       "Containers.MinRetryPeriod":                true,
-       "Containers.PreemptiblePriceFactor":        false,
-       "Containers.ReserveExtraRAM":               true,
-       "Containers.RuntimeEngine":                 true,
-       "Containers.ShellAccess":                   true,
-       "Containers.ShellAccess.Admin":             true,
-       "Containers.ShellAccess.User":              true,
-       "Containers.SLURM":                         false,
-       "Containers.StaleLockTimeout":              false,
-       "Containers.SupportedDockerImageFormats":   true,
-       "Containers.SupportedDockerImageFormats.*": true,
-       "Git":                                  false,
-       "InstanceTypes":                        true,
-       "InstanceTypes.*":                      true,
-       "InstanceTypes.*.*":                    true,
-       "InstanceTypes.*.*.*":                  true,
-       "Login":                                true,
-       "Login.Google":                         true,
-       "Login.Google.AlternateEmailAddresses": false,
+       "API":                                                 true,
+       "API.AsyncPermissionsUpdateInterval":                  false,
+       "API.DisabledAPIs":                                    false,
+       "API.FreezeProjectRequiresDescription":                true,
+       "API.FreezeProjectRequiresProperties":                 true,
+       "API.FreezeProjectRequiresProperties.*":               true,
+       "API.KeepServiceRequestTimeout":                       false,
+       "API.LockBeforeUpdate":                                false,
+       "API.LogCreateRequestFraction":                        false,
+       "API.MaxConcurrentRailsRequests":                      false,
+       "API.MaxConcurrentRequests":                           false,
+       "API.MaxGatewayTunnels":                               false,
+       "API.MaxIndexDatabaseRead":                            false,
+       "API.MaxItemsPerResponse":                             true,
+       "API.MaxKeepBlobBuffers":                              false,
+       "API.MaxQueuedRequests":                               false,
+       "API.MaxQueueTimeForLockRequests":                     false,
+       "API.MaxRequestAmplification":                         false,
+       "API.MaxRequestSize":                                  true,
+       "API.MaxTokenLifetime":                                false,
+       "API.RequestTimeout":                                  true,
+       "API.SendTimeout":                                     true,
+       "API.UnfreezeProjectRequiresAdmin":                    true,
+       "API.VocabularyPath":                                  false,
+       "API.WebsocketClientEventQueue":                       false,
+       "API.WebsocketServerEventQueue":                       false,
+       "AuditLogs":                                           false,
+       "AuditLogs.MaxAge":                                    false,
+       "AuditLogs.MaxDeleteBatch":                            false,
+       "AuditLogs.UnloggedAttributes":                        false,
+       "ClusterID":                                           true,
+       "Collections":                                         true,
+       "Collections.BalanceCollectionBatch":                  false,
+       "Collections.BalanceCollectionBuffers":                false,
+       "Collections.BalancePeriod":                           false,
+       "Collections.BalancePullLimit":                        false,
+       "Collections.BalanceTimeout":                          false,
+       "Collections.BalanceTrashLimit":                       false,
+       "Collections.BalanceUpdateLimit":                      false,
+       "Collections.BlobDeleteConcurrency":                   false,
+       "Collections.BlobMissingReport":                       false,
+       "Collections.BlobReplicateConcurrency":                false,
+       "Collections.BlobSigning":                             true,
+       "Collections.BlobSigningKey":                          false,
+       "Collections.BlobSigningTTL":                          true,
+       "Collections.BlobTrash":                               false,
+       "Collections.BlobTrashCheckInterval":                  false,
+       "Collections.BlobTrashConcurrency":                    false,
+       "Collections.BlobTrashLifetime":                       false,
+       "Collections.CollectionVersioning":                    true,
+       "Collections.DefaultReplication":                      true,
+       "Collections.DefaultTrashLifetime":                    true,
+       "Collections.ForwardSlashNameSubstitution":            true,
+       "Collections.KeepproxyPermission":                     false,
+       "Collections.ManagedProperties":                       true,
+       "Collections.ManagedProperties.*":                     true,
+       "Collections.ManagedProperties.*.*":                   true,
+       "Collections.PreserveVersionIfIdle":                   true,
+       "Collections.S3FolderObjects":                         true,
+       "Collections.TrashSweepInterval":                      false,
+       "Collections.TrustAllContent":                         true,
+       "Collections.WebDAVCache":                             false,
+       "Collections.WebDAVLogEvents":                         false,
+       "Collections.WebDAVOutputBuffer":                      false,
+       "Collections.WebDAVPermission":                        false,
+       "Containers":                                          true,
+       "Containers.AlwaysUsePreemptibleInstances":            true,
+       "Containers.CloudVMs":                                 false,
+       "Containers.CrunchRunArgumentsList":                   false,
+       "Containers.CrunchRunCommand":                         false,
+       "Containers.DefaultKeepCacheRAM":                      true,
+       "Containers.DispatchPrivateKey":                       false,
+       "Containers.JobsAPI":                                  true,
+       "Containers.JobsAPI.Enable":                           true,
+       "Containers.LocalKeepBlobBuffersPerVCPU":              false,
+       "Containers.LocalKeepLogsToContainerLog":              false,
+       "Containers.Logging":                                  false,
+       "Containers.LogReuseDecisions":                        false,
+       "Containers.LSF":                                      false,
+       "Containers.MaxDispatchAttempts":                      false,
+       "Containers.MaximumPriceFactor":                       true,
+       "Containers.MaxRetryAttempts":                         true,
+       "Containers.MinRetryPeriod":                           true,
+       "Containers.PreemptiblePriceFactor":                   false,
+       "Containers.ReserveExtraRAM":                          true,
+       "Containers.RuntimeEngine":                            true,
+       "Containers.ShellAccess":                              true,
+       "Containers.ShellAccess.Admin":                        true,
+       "Containers.ShellAccess.User":                         true,
+       "Containers.SLURM":                                    false,
+       "Containers.StaleLockTimeout":                         false,
+       "Containers.SupportedDockerImageFormats":              true,
+       "Containers.SupportedDockerImageFormats.*":            true,
+       "InstanceTypes":                                       true,
+       "InstanceTypes.*":                                     true,
+       "InstanceTypes.*.*":                                   true,
+       "InstanceTypes.*.*.*":                                 true,
+       "Login":                                               true,
+       "Login.Google":                                        true,
+       "Login.Google.AlternateEmailAddresses":                false,
        "Login.Google.AuthenticationRequestParameters":        false,
        "Login.Google.ClientID":                               false,
        "Login.Google.ClientSecret":                           false,
        "Login.Google.AuthenticationRequestParameters":        false,
        "Login.Google.ClientID":                               false,
        "Login.Google.ClientSecret":                           false,
@@ -242,7 +241,6 @@ var whitelist = map[string]bool{
        "Users.AutoAdminFirstUser":                            false,
        "Users.AutoAdminUserWithEmail":                        false,
        "Users.AutoSetupNewUsers":                             false,
        "Users.AutoAdminFirstUser":                            false,
        "Users.AutoAdminUserWithEmail":                        false,
        "Users.AutoSetupNewUsers":                             false,
-       "Users.AutoSetupNewUsersWithRepository":               false,
        "Users.AutoSetupNewUsersWithVmUUID":                   false,
        "Users.AutoSetupUsernameBlacklist":                    false,
        "Users.CanCreateRoleGroups":                           true,
        "Users.AutoSetupNewUsersWithVmUUID":                   false,
        "Users.AutoSetupUsernameBlacklist":                    false,
        "Users.CanCreateRoleGroups":                           true,
index d504f7796c323f12598a0af4429e289390e22e52..00c8e286832f0f0c3a593c41cf08e919a054ad32 100644 (file)
@@ -48,7 +48,6 @@ type Loader struct {
        CrunchDispatchSlurmPath string
        WebsocketPath           string
        KeepproxyPath           string
        CrunchDispatchSlurmPath string
        WebsocketPath           string
        KeepproxyPath           string
-       GitHttpdPath            string
        KeepBalancePath         string
 
        configdata []byte
        KeepBalancePath         string
 
        configdata []byte
@@ -88,7 +87,6 @@ func (ldr *Loader) SetupFlags(flagset *flag.FlagSet) {
                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.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 arvados-git-httpd configuration `file`")
                flagset.StringVar(&ldr.KeepBalancePath, "legacy-keepbalance-config", defaultKeepBalanceConfigPath, "Legacy keep-balance configuration `file`")
                flagset.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files")
        }
                flagset.StringVar(&ldr.KeepBalancePath, "legacy-keepbalance-config", defaultKeepBalanceConfigPath, "Legacy keep-balance configuration `file`")
                flagset.BoolVar(&ldr.SkipLegacy, "skip-legacy", false, "Don't load legacy config files")
        }
@@ -168,9 +166,6 @@ func (ldr *Loader) MungeLegacyConfigArgs(lgr logrus.FieldLogger, args []string,
        if legacyConfigArg != "-legacy-keepproxy-config" {
                ldr.KeepproxyPath = ""
        }
        if legacyConfigArg != "-legacy-keepproxy-config" {
                ldr.KeepproxyPath = ""
        }
-       if legacyConfigArg != "-legacy-git-httpd-config" {
-               ldr.GitHttpdPath = ""
-       }
        if legacyConfigArg != "-legacy-keepbalance-config" {
                ldr.KeepBalancePath = ""
        }
        if legacyConfigArg != "-legacy-keepbalance-config" {
                ldr.KeepBalancePath = ""
        }
@@ -296,7 +291,6 @@ func (ldr *Loader) Load() (*arvados.Config, error) {
                        ldr.loadOldCrunchDispatchSlurmConfig,
                        ldr.loadOldWebsocketConfig,
                        ldr.loadOldKeepproxyConfig,
                        ldr.loadOldCrunchDispatchSlurmConfig,
                        ldr.loadOldWebsocketConfig,
                        ldr.loadOldKeepproxyConfig,
-                       ldr.loadOldGitHttpdConfig,
                        ldr.loadOldKeepBalanceConfig,
                )
        }
                        ldr.loadOldKeepBalanceConfig,
                )
        }
index 949cc56dd24cc34b71a8f7ef8ea7ac1d15df6e29..c2cbfec008fc19d7c3615a1f73be512307629226 100644 (file)
@@ -605,26 +605,6 @@ func (conn *Conn) LogDelete(ctx context.Context, options arvados.DeleteOptions)
        return conn.chooseBackend(options.UUID).LogDelete(ctx, options)
 }
 
        return conn.chooseBackend(options.UUID).LogDelete(ctx, options)
 }
 
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-       return conn.generated_SpecimenList(ctx, options)
-}
-
-func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
-       return conn.chooseBackend(options.ClusterID).SpecimenCreate(ctx, options)
-}
-
-func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
-       return conn.chooseBackend(options.UUID).SpecimenUpdate(ctx, options)
-}
-
-func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
-       return conn.chooseBackend(options.UUID).SpecimenGet(ctx, options)
-}
-
-func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
-       return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
-}
-
 func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
        return conn.local.SysTrashSweep(ctx, options)
 }
 func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
        return conn.local.SysTrashSweep(ctx, options)
 }
index 2dc2918f79ec8330dcfbe316af1ec728c3e27f07..079d908f0dba40eb0a64750f2bcb46c22cb3c84f 100644 (file)
@@ -53,7 +53,7 @@ func main() {
                defer out.Close()
                out.Write(regexp.MustCompile(`(?ms)^.*package .*?import.*?\n\)\n`).Find(buf))
                io.WriteString(out, "//\n// -- this file is auto-generated -- do not edit -- edit list.go and run \"go generate\" instead --\n//\n\n")
                defer out.Close()
                out.Write(regexp.MustCompile(`(?ms)^.*package .*?import.*?\n\)\n`).Find(buf))
                io.WriteString(out, "//\n// -- this file is auto-generated -- do not edit -- edit list.go and run \"go generate\" instead --\n//\n\n")
-               for _, t := range []string{"AuthorizedKey", "Container", "ContainerRequest", "Group", "Specimen", "User", "Link", "Log", "APIClientAuthorization"} {
+               for _, t := range []string{"AuthorizedKey", "Container", "ContainerRequest", "Group", "User", "Link", "Log", "APIClientAuthorization"} {
                        _, err := out.Write(bytes.ReplaceAll(orig, []byte("Collection"), []byte(t)))
                        if err != nil {
                                panic(err)
                        _, err := out.Write(bytes.ReplaceAll(orig, []byte("Collection"), []byte(t)))
                        if err != nil {
                                panic(err)
index 8c8666fea122787d91bf71305421d121e00ae7f9..95f2f650fce0e28b81eba21069f95f5afe6dc682 100755 (executable)
@@ -181,47 +181,6 @@ func (conn *Conn) generated_GroupList(ctx context.Context, options arvados.ListO
        return merged, err
 }
 
        return merged, err
 }
 
-func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-       var mtx sync.Mutex
-       var merged arvados.SpecimenList
-       var needSort atomic.Value
-       needSort.Store(false)
-       err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
-               options.ForwardedFor = conn.cluster.ClusterID + "-" + options.ForwardedFor
-               cl, err := backend.SpecimenList(ctx, options)
-               if err != nil {
-                       return nil, err
-               }
-               mtx.Lock()
-               defer mtx.Unlock()
-               if len(merged.Items) == 0 {
-                       merged = cl
-               } else if len(cl.Items) > 0 {
-                       merged.Items = append(merged.Items, cl.Items...)
-                       needSort.Store(true)
-               }
-               uuids := make([]string, 0, len(cl.Items))
-               for _, item := range cl.Items {
-                       uuids = append(uuids, item.UUID)
-               }
-               return uuids, nil
-       })
-       if needSort.Load().(bool) {
-               // Apply the default/implied order, "modified_at desc"
-               sort.Slice(merged.Items, func(i, j int) bool {
-                       mi, mj := merged.Items[i].ModifiedAt, merged.Items[j].ModifiedAt
-                       return mj.Before(mi)
-               })
-       }
-       if merged.Items == nil {
-               // Return empty results as [], not null
-               // (https://github.com/golang/go/issues/27589 might be
-               // a better solution in the future)
-               merged.Items = []arvados.Specimen{}
-       }
-       return merged, err
-}
-
 func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
        var mtx sync.Mutex
        var merged arvados.UserList
 func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
        var mtx sync.Mutex
        var merged arvados.UserList
index eef0443b9a931afa1d5a0160a6cce8e67cc9c419..7d5e693f5b67a45019773adb98c09ebe31befd80 100644 (file)
@@ -568,8 +568,10 @@ func (s *HandlerSuite) CheckObjectType(c *check.C, url string, token string, ski
        req.Header.Set("Authorization", "Bearer "+token)
        resp := httptest.NewRecorder()
        s.handler.ServeHTTP(resp, req)
        req.Header.Set("Authorization", "Bearer "+token)
        resp := httptest.NewRecorder()
        s.handler.ServeHTTP(resp, req)
-       c.Assert(resp.Code, check.Equals, http.StatusOK,
-               check.Commentf("Wasn't able to get data from the controller at %q: %q", url, resp.Body.String()))
+       if !c.Check(resp.Code, check.Equals, http.StatusOK,
+               check.Commentf("Wasn't able to get data from the controller at %q: %q", url, resp.Body.String())) {
+               return
+       }
        err = json.Unmarshal(resp.Body.Bytes(), &proxied)
        c.Check(err, check.Equals, nil)
 
        err = json.Unmarshal(resp.Body.Bytes(), &proxied)
        c.Check(err, check.Equals, nil)
 
@@ -581,9 +583,11 @@ func (s *HandlerSuite) CheckObjectType(c *check.C, url string, token string, ski
        }
        resp2, err := client.Get(s.cluster.Services.RailsAPI.ExternalURL.String() + url + "/?api_token=" + token)
        c.Check(err, check.Equals, nil)
        }
        resp2, err := client.Get(s.cluster.Services.RailsAPI.ExternalURL.String() + url + "/?api_token=" + token)
        c.Check(err, check.Equals, nil)
-       c.Assert(resp2.StatusCode, check.Equals, http.StatusOK,
-               check.Commentf("Wasn't able to get data from the RailsAPI at %q", url))
        defer resp2.Body.Close()
        defer resp2.Body.Close()
+       if !c.Check(resp2.StatusCode, check.Equals, http.StatusOK,
+               check.Commentf("Wasn't able to get data from the RailsAPI at %q", url)) {
+               return
+       }
        db, err := ioutil.ReadAll(resp2.Body)
        c.Check(err, check.Equals, nil)
        err = json.Unmarshal(db, &direct)
        db, err := ioutil.ReadAll(resp2.Body)
        c.Check(err, check.Equals, nil)
        err = json.Unmarshal(db, &direct)
@@ -647,9 +651,7 @@ func (s *HandlerSuite) TestGetObjects(c *check.C) {
                "groups/" + arvadostest.AProjectUUID:                           nil,
                "keep_services/" + ksUUID:                                      nil,
                "links/" + arvadostest.ActiveUserCanReadAllUsersLinkUUID:       nil,
                "groups/" + arvadostest.AProjectUUID:                           nil,
                "keep_services/" + ksUUID:                                      nil,
                "links/" + arvadostest.ActiveUserCanReadAllUsersLinkUUID:       nil,
-               "logs/" + arvadostest.CrunchstatForRunningJobLogUUID:           nil,
-               "nodes/" + arvadostest.IdleNodeUUID:                            nil,
-               "repositories/" + arvadostest.ArvadosRepoUUID:                  nil,
+               "logs/" + arvadostest.CrunchstatForRunningContainerLogUUID:     nil,
                "users/" + arvadostest.ActiveUserUUID:                          {"href": true},
                "virtual_machines/" + arvadostest.TestVMUUID:                   nil,
                "workflows/" + arvadostest.WorkflowWithDefinitionYAMLUUID:      nil,
                "users/" + arvadostest.ActiveUserUUID:                          {"href": true},
                "virtual_machines/" + arvadostest.TestVMUUID:                   nil,
                "workflows/" + arvadostest.WorkflowWithDefinitionYAMLUUID:      nil,
index 53e6a90b8f2fee1d18237c157ccef0474b703227..6b603f178ecb77bffe9f9999f99282e31c6fb66c 100644 (file)
@@ -28,6 +28,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvadostest"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
        "git.arvados.org/arvados.git/sdk/go/arvadostest"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
+       "git.arvados.org/arvados.git/sdk/go/keepclient"
        check "gopkg.in/check.v1"
 )
 
        check "gopkg.in/check.v1"
 )
 
@@ -167,6 +168,20 @@ func (s *IntegrationSuite) TestDefaultStorageClassesOnCollections(c *check.C) {
        c.Assert(coll.StorageClassesDesired, check.DeepEquals, kc.DefaultStorageClasses)
 }
 
        c.Assert(coll.StorageClassesDesired, check.DeepEquals, kc.DefaultStorageClasses)
 }
 
+func (s *IntegrationSuite) createTestCollectionManifest(c *check.C, ac *arvados.Client, kc *keepclient.KeepClient, content string) string {
+       fs, err := (&arvados.Collection{}).FileSystem(ac, kc)
+       c.Assert(err, check.IsNil)
+       f, err := fs.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
+       c.Assert(err, check.IsNil)
+       _, err = io.WriteString(f, content)
+       c.Assert(err, check.IsNil)
+       err = f.Close()
+       c.Assert(err, check.IsNil)
+       mtxt, err := fs.MarshalManifest(".")
+       c.Assert(err, check.IsNil)
+       return mtxt
+}
+
 func (s *IntegrationSuite) TestGetCollectionByPDH(c *check.C) {
        conn1 := s.super.Conn("z1111")
        rootctx1, _, _ := s.super.RootClients("z1111")
 func (s *IntegrationSuite) TestGetCollectionByPDH(c *check.C) {
        conn1 := s.super.Conn("z1111")
        rootctx1, _, _ := s.super.RootClients("z1111")
@@ -175,34 +190,70 @@ func (s *IntegrationSuite) TestGetCollectionByPDH(c *check.C) {
 
        // Create the collection to find its PDH (but don't save it
        // anywhere yet)
 
        // Create the collection to find its PDH (but don't save it
        // anywhere yet)
-       var coll1 arvados.Collection
-       fs1, err := coll1.FileSystem(ac1, kc1)
-       c.Assert(err, check.IsNil)
-       f, err := fs1.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
-       c.Assert(err, check.IsNil)
-       _, err = io.WriteString(f, "IntegrationSuite.TestGetCollectionByPDH")
-       c.Assert(err, check.IsNil)
-       err = f.Close()
-       c.Assert(err, check.IsNil)
-       mtxt, err := fs1.MarshalManifest(".")
-       c.Assert(err, check.IsNil)
+       mtxt := s.createTestCollectionManifest(c, ac1, kc1, c.TestName())
        pdh := arvados.PortableDataHash(mtxt)
 
        // Looking up the PDH before saving returns 404 if cycle
        // detection is working.
        pdh := arvados.PortableDataHash(mtxt)
 
        // Looking up the PDH before saving returns 404 if cycle
        // detection is working.
-       _, err = conn1.CollectionGet(userctx1, arvados.GetOptions{UUID: pdh})
+       _, err := conn1.CollectionGet(userctx1, arvados.GetOptions{UUID: pdh})
        c.Assert(err, check.ErrorMatches, `.*404 Not Found.*`)
 
        // Save the collection on cluster z1111.
        c.Assert(err, check.ErrorMatches, `.*404 Not Found.*`)
 
        // Save the collection on cluster z1111.
-       coll1, err = conn1.CollectionCreate(userctx1, arvados.CreateOptions{Attrs: map[string]interface{}{
+       _, err = conn1.CollectionCreate(userctx1, arvados.CreateOptions{Attrs: map[string]interface{}{
                "manifest_text": mtxt,
        }})
        c.Assert(err, check.IsNil)
 
        // Retrieve the collection from cluster z3333.
                "manifest_text": mtxt,
        }})
        c.Assert(err, check.IsNil)
 
        // Retrieve the collection from cluster z3333.
-       coll, err := conn3.CollectionGet(userctx1, arvados.GetOptions{UUID: pdh})
+       coll2, err := conn3.CollectionGet(userctx1, arvados.GetOptions{UUID: pdh})
        c.Check(err, check.IsNil)
        c.Check(err, check.IsNil)
-       c.Check(coll.PortableDataHash, check.Equals, pdh)
+       c.Check(coll2.PortableDataHash, check.Equals, pdh)
+}
+
+func (s *IntegrationSuite) TestFederation_Write1Read2(c *check.C) {
+       s.testFederationCollectionAccess(c, "z1111", "z2222")
+}
+
+func (s *IntegrationSuite) TestFederation_Write2Read1(c *check.C) {
+       s.testFederationCollectionAccess(c, "z2222", "z1111")
+}
+
+func (s *IntegrationSuite) TestFederation_Write2Read3(c *check.C) {
+       s.testFederationCollectionAccess(c, "z2222", "z3333")
+}
+
+func (s *IntegrationSuite) testFederationCollectionAccess(c *check.C, writeCluster, readCluster string) {
+       conn1 := s.super.Conn("z1111")
+       rootctx1, _, _ := s.super.RootClients("z1111")
+       _, ac1, _, _ := s.super.UserClients("z1111", rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
+
+       connW := s.super.Conn(writeCluster)
+       userctxW, acW, kcW := s.super.ClientsWithToken(writeCluster, ac1.AuthToken)
+       kcW.DiskCacheSize = keepclient.DiskCacheDisabled
+       connR := s.super.Conn(readCluster)
+       userctxR, acR, kcR := s.super.ClientsWithToken(readCluster, ac1.AuthToken)
+       kcR.DiskCacheSize = keepclient.DiskCacheDisabled
+
+       filedata := fmt.Sprintf("%s: write to %s, read from %s", c.TestName(), writeCluster, readCluster)
+       mtxt := s.createTestCollectionManifest(c, acW, kcW, filedata)
+       collW, err := connW.CollectionCreate(userctxW, arvados.CreateOptions{Attrs: map[string]interface{}{
+               "manifest_text": mtxt,
+       }})
+       c.Assert(err, check.IsNil)
+
+       collR, err := connR.CollectionGet(userctxR, arvados.GetOptions{UUID: collW.UUID})
+       if !c.Check(err, check.IsNil) {
+               return
+       }
+       fsR, err := collR.FileSystem(acR, kcR)
+       if !c.Check(err, check.IsNil) {
+               return
+       }
+       buf, err := fs.ReadFile(arvados.FS(fsR), "test.txt")
+       if !c.Check(err, check.IsNil) {
+               return
+       }
+       c.Check(string(buf), check.Equals, filedata)
 }
 
 // Tests bug #18004
 }
 
 // Tests bug #18004
@@ -553,8 +604,6 @@ func (s *IntegrationSuite) TestRequestIDHeader(c *check.C) {
 
        coll, err := conn1.CollectionCreate(userctx1, arvados.CreateOptions{})
        c.Check(err, check.IsNil)
 
        coll, err := conn1.CollectionCreate(userctx1, arvados.CreateOptions{})
        c.Check(err, check.IsNil)
-       specimen, err := conn1.SpecimenCreate(userctx1, arvados.CreateOptions{})
-       c.Check(err, check.IsNil)
 
        tests := []struct {
                path            string
 
        tests := []struct {
                path            string
@@ -567,8 +616,6 @@ func (s *IntegrationSuite) TestRequestIDHeader(c *check.C) {
                {"/arvados/v1/nonexistant", true, true},
                {"/arvados/v1/collections/" + coll.UUID, false, false},
                {"/arvados/v1/collections/" + coll.UUID, true, false},
                {"/arvados/v1/nonexistant", true, true},
                {"/arvados/v1/collections/" + coll.UUID, false, false},
                {"/arvados/v1/collections/" + coll.UUID, true, false},
-               {"/arvados/v1/specimens/" + specimen.UUID, false, false},
-               {"/arvados/v1/specimens/" + specimen.UUID, true, false},
                // new code path (lib/controller/router etc) - single-cluster request
                {"/arvados/v1/collections/z1111-4zz18-0123456789abcde", false, true},
                {"/arvados/v1/collections/z1111-4zz18-0123456789abcde", true, true},
                // new code path (lib/controller/router etc) - single-cluster request
                {"/arvados/v1/collections/z1111-4zz18-0123456789abcde", false, true},
                {"/arvados/v1/collections/z1111-4zz18-0123456789abcde", true, true},
@@ -576,8 +623,8 @@ func (s *IntegrationSuite) TestRequestIDHeader(c *check.C) {
                {"/arvados/v1/collections/z2222-4zz18-0123456789abcde", false, true},
                {"/arvados/v1/collections/z2222-4zz18-0123456789abcde", true, true},
                // old code path (proxyRailsAPI) - single-cluster request
                {"/arvados/v1/collections/z2222-4zz18-0123456789abcde", false, true},
                {"/arvados/v1/collections/z2222-4zz18-0123456789abcde", true, true},
                // old code path (proxyRailsAPI) - single-cluster request
-               {"/arvados/v1/specimens/z1111-j58dm-0123456789abcde", false, true},
-               {"/arvados/v1/specimens/z1111-j58dm-0123456789abcde", true, true},
+               {"/arvados/v1/containers/z1111-dz642-0123456789abcde", false, true},
+               {"/arvados/v1/containers/z1111-dz642-0123456789abcde", true, true},
                // old code path (setupProxyRemoteCluster) - federated request
                {"/arvados/v1/workflows/z2222-7fd4e-0123456789abcde", false, true},
                {"/arvados/v1/workflows/z2222-7fd4e-0123456789abcde", true, true},
                // old code path (setupProxyRemoteCluster) - federated request
                {"/arvados/v1/workflows/z2222-7fd4e-0123456789abcde", false, true},
                {"/arvados/v1/workflows/z2222-7fd4e-0123456789abcde", true, true},
@@ -779,7 +826,6 @@ func (s *IntegrationSuite) TestFederatedApiClientAuthHandling(c *check.C) {
 // Test for bug #18076
 func (s *IntegrationSuite) TestStaleCachedUserRecord(c *check.C) {
        rootctx1, _, _ := s.super.RootClients("z1111")
 // Test for bug #18076
 func (s *IntegrationSuite) TestStaleCachedUserRecord(c *check.C) {
        rootctx1, _, _ := s.super.RootClients("z1111")
-       _, rootclnt3, _ := s.super.RootClients("z3333")
        conn1 := s.super.Conn("z1111")
        conn3 := s.super.Conn("z3333")
 
        conn1 := s.super.Conn("z1111")
        conn3 := s.super.Conn("z3333")
 
@@ -791,92 +837,69 @@ func (s *IntegrationSuite) TestStaleCachedUserRecord(c *check.C) {
                        check.Commentf("incorrect LoginCluster config on cluster %q", cls))
        }
 
                        check.Commentf("incorrect LoginCluster config on cluster %q", cls))
        }
 
-       for testCaseNr, testCase := range []struct {
-               name           string
-               withRepository bool
-       }{
-               {"User without local repository", false},
-               {"User with local repository", true},
-       } {
-               c.Log(c.TestName() + " " + testCase.name)
-               // Create some users, request them on the federated cluster so they're cached.
-               var users []arvados.User
-               for userNr := 0; userNr < 2; userNr++ {
-                       _, _, _, user := s.super.UserClients("z1111",
-                               rootctx1,
-                               c,
-                               conn1,
-                               fmt.Sprintf("user%d%d@example.com", testCaseNr, userNr),
-                               true)
-                       c.Assert(user.Username, check.Not(check.Equals), "")
-                       users = append(users, user)
-
-                       lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
-                       c.Assert(err, check.Equals, nil)
-                       userFound := false
-                       for _, fedUser := range lst.Items {
-                               if fedUser.UUID == user.UUID {
-                                       c.Assert(fedUser.Username, check.Equals, user.Username)
-                                       userFound = true
-                                       break
-                               }
-                       }
-                       c.Assert(userFound, check.Equals, true)
-
-                       if testCase.withRepository {
-                               var repo interface{}
-                               err = rootclnt3.RequestAndDecode(
-                                       &repo, "POST", "arvados/v1/repositories", nil,
-                                       map[string]interface{}{
-                                               "repository": map[string]string{
-                                                       "name":       fmt.Sprintf("%s/test", user.Username),
-                                                       "owner_uuid": user.UUID,
-                                               },
-                                       },
-                               )
-                               c.Assert(err, check.IsNil)
-                       }
-               }
-
-               // Swap the usernames
-               _, err := conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
-                       UUID: users[0].UUID,
-                       Attrs: map[string]interface{}{
-                               "username": "",
-                       },
-               })
-               c.Assert(err, check.Equals, nil)
-               _, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
-                       UUID: users[1].UUID,
-                       Attrs: map[string]interface{}{
-                               "username": users[0].Username,
-                       },
-               })
-               c.Assert(err, check.Equals, nil)
-               _, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
-                       UUID: users[0].UUID,
-                       Attrs: map[string]interface{}{
-                               "username": users[1].Username,
-                       },
-               })
-               c.Assert(err, check.Equals, nil)
+       // Create some users, request them on the federated cluster so they're cached.
+       var users []arvados.User
+       for userNr := 0; userNr < 2; userNr++ {
+               _, _, _, user := s.super.UserClients("z1111",
+                       rootctx1,
+                       c,
+                       conn1,
+                       fmt.Sprintf("user0%d@example.com", userNr),
+                       true)
+               c.Assert(user.Username, check.Not(check.Equals), "")
+               users = append(users, user)
 
 
-               // Re-request the list on the federated cluster & check for updates
                lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
                c.Assert(err, check.Equals, nil)
                lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
                c.Assert(err, check.Equals, nil)
-               var user0Found, user1Found bool
-               for _, user := range lst.Items {
-                       if user.UUID == users[0].UUID {
-                               user0Found = true
-                               c.Assert(user.Username, check.Equals, users[1].Username)
-                       } else if user.UUID == users[1].UUID {
-                               user1Found = true
-                               c.Assert(user.Username, check.Equals, users[0].Username)
+               userFound := false
+               for _, fedUser := range lst.Items {
+                       if fedUser.UUID == user.UUID {
+                               c.Assert(fedUser.Username, check.Equals, user.Username)
+                               userFound = true
+                               break
                        }
                }
                        }
                }
-               c.Assert(user0Found, check.Equals, true)
-               c.Assert(user1Found, check.Equals, true)
+               c.Assert(userFound, check.Equals, true)
+       }
+
+       // Swap the usernames
+       _, err := conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+               UUID: users[0].UUID,
+               Attrs: map[string]interface{}{
+                       "username": "",
+               },
+       })
+       c.Assert(err, check.Equals, nil)
+       _, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+               UUID: users[1].UUID,
+               Attrs: map[string]interface{}{
+                       "username": users[0].Username,
+               },
+       })
+       c.Assert(err, check.Equals, nil)
+       _, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+               UUID: users[0].UUID,
+               Attrs: map[string]interface{}{
+                       "username": users[1].Username,
+               },
+       })
+       c.Assert(err, check.Equals, nil)
+
+       // Re-request the list on the federated cluster & check for updates
+       lst, err := conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
+       c.Assert(err, check.Equals, nil)
+       var user0Found, user1Found bool
+       for _, user := range lst.Items {
+               if user.UUID == users[0].UUID {
+                       user0Found = true
+                       c.Assert(user.Username, check.Equals, users[1].Username)
+               } else if user.UUID == users[1].UUID {
+                       user1Found = true
+                       c.Assert(user.Username, check.Equals, users[0].Username)
+               }
        }
        }
+       c.Assert(user0Found, check.Equals, true)
+       c.Assert(user1Found, check.Equals, true)
 }
 
 // Test for bug #16263
 }
 
 // Test for bug #16263
index 42b34355935db015e72d7de3e8b7a6398ffc0680..97b84df73544f59ac873790041950bdafb156a11 100644 (file)
@@ -143,11 +143,6 @@ var infixMap = map[string]interface{}{
        "xvhdp": arvados.ContainerRequest{},
        "dz642": arvados.Container{},
        "j7d0g": arvados.Group{},
        "xvhdp": arvados.ContainerRequest{},
        "dz642": arvados.Container{},
        "j7d0g": arvados.Group{},
-       "8i9sb": arvados.Job{},
-       "d1hrv": arvados.PipelineInstance{},
-       "p5p6p": arvados.PipelineTemplate{},
-       "j58dm": arvados.Specimen{},
-       "q1cn2": arvados.Trait{},
        "7fd4e": arvados.Workflow{},
 }
 
        "7fd4e": arvados.Workflow{},
 }
 
index 054bcffaf7ecf33b12965bb8e0d0be2d9590e1e0..39c7d871d8e4116694242698f63fcc7ee24e68a3 100644 (file)
@@ -472,41 +472,6 @@ func (rtr *router) addRoutes() {
                                return rtr.backend.LogDelete(ctx, *opts.(*arvados.DeleteOptions))
                        },
                },
                                return rtr.backend.LogDelete(ctx, *opts.(*arvados.DeleteOptions))
                        },
                },
-               {
-                       arvados.EndpointSpecimenCreate,
-                       func() interface{} { return &arvados.CreateOptions{} },
-                       func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.SpecimenCreate(ctx, *opts.(*arvados.CreateOptions))
-                       },
-               },
-               {
-                       arvados.EndpointSpecimenUpdate,
-                       func() interface{} { return &arvados.UpdateOptions{} },
-                       func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.SpecimenUpdate(ctx, *opts.(*arvados.UpdateOptions))
-                       },
-               },
-               {
-                       arvados.EndpointSpecimenGet,
-                       func() interface{} { return &arvados.GetOptions{} },
-                       func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.SpecimenGet(ctx, *opts.(*arvados.GetOptions))
-                       },
-               },
-               {
-                       arvados.EndpointSpecimenList,
-                       func() interface{} { return &arvados.ListOptions{Limit: -1} },
-                       func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.SpecimenList(ctx, *opts.(*arvados.ListOptions))
-                       },
-               },
-               {
-                       arvados.EndpointSpecimenDelete,
-                       func() interface{} { return &arvados.DeleteOptions{} },
-                       func(ctx context.Context, opts interface{}) (interface{}, error) {
-                               return rtr.backend.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
-                       },
-               },
                {
                        arvados.EndpointAPIClientAuthorizationCreate,
                        func() interface{} { return &arvados.CreateOptions{} },
                {
                        arvados.EndpointAPIClientAuthorizationCreate,
                        func() interface{} { return &arvados.CreateOptions{} },
index c6be679a256cb2e860d5ce179646e3378219d1c6..3125ae29be67ac57966088b6d0951c387c0c6b4e 100644 (file)
@@ -682,41 +682,6 @@ func (conn *Conn) LogDelete(ctx context.Context, options arvados.DeleteOptions)
        return resp, err
 }
 
        return resp, err
 }
 
-func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
-       ep := arvados.EndpointSpecimenCreate
-       var resp arvados.Specimen
-       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-       return resp, err
-}
-
-func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
-       ep := arvados.EndpointSpecimenUpdate
-       var resp arvados.Specimen
-       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-       return resp, err
-}
-
-func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
-       ep := arvados.EndpointSpecimenGet
-       var resp arvados.Specimen
-       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-       return resp, err
-}
-
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-       ep := arvados.EndpointSpecimenList
-       var resp arvados.SpecimenList
-       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-       return resp, err
-}
-
-func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
-       ep := arvados.EndpointSpecimenDelete
-       var resp arvados.Specimen
-       err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
-       return resp, err
-}
-
 func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
        ep := arvados.EndpointSysTrashSweep
        var resp struct{}
 func (conn *Conn) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
        ep := arvados.EndpointSysTrashSweep
        var resp struct{}
index 0d1200fe12096843c8696a809d7220b4490acff9..ed26e041173e31a1ccbd0493426106d46dfcf40a 100644 (file)
@@ -100,23 +100,24 @@ func (s *RPCSuite) TestCollectionCreate(c *check.C) {
        c.Check(coll.UUID, check.HasLen, 27)
 }
 
        c.Check(coll.UUID, check.HasLen, 27)
 }
 
-func (s *RPCSuite) TestSpecimenCRUD(c *check.C) {
+func (s *RPCSuite) TestGroupCRUD(c *check.C) {
        s.setupConn(c, os.Getenv("ARVADOS_TEST_API_HOST"))
        s.setupConn(c, os.Getenv("ARVADOS_TEST_API_HOST"))
-       sp, err := s.conn.SpecimenCreate(s.ctx, arvados.CreateOptions{Attrs: map[string]interface{}{
-               "owner_uuid": arvadostest.ActiveUserUUID,
-               "properties": map[string]string{"foo": "bar"},
+       sp, err := s.conn.GroupCreate(s.ctx, arvados.CreateOptions{Attrs: map[string]interface{}{
+               "group_class": "project",
+               "owner_uuid":  arvadostest.ActiveUserUUID,
+               "properties":  map[string]string{"foo": "bar"},
        }})
        c.Check(err, check.IsNil)
        c.Check(sp.UUID, check.HasLen, 27)
        c.Check(sp.Properties, check.HasLen, 1)
        c.Check(sp.Properties["foo"], check.Equals, "bar")
 
        }})
        c.Check(err, check.IsNil)
        c.Check(sp.UUID, check.HasLen, 27)
        c.Check(sp.Properties, check.HasLen, 1)
        c.Check(sp.Properties["foo"], check.Equals, "bar")
 
-       spGet, err := s.conn.SpecimenGet(s.ctx, arvados.GetOptions{UUID: sp.UUID})
+       spGet, err := s.conn.GroupGet(s.ctx, arvados.GetOptions{UUID: sp.UUID})
        c.Check(err, check.IsNil)
        c.Check(spGet.UUID, check.Equals, sp.UUID)
        c.Check(spGet.Properties["foo"], check.Equals, "bar")
 
        c.Check(err, check.IsNil)
        c.Check(spGet.UUID, check.Equals, sp.UUID)
        c.Check(spGet.Properties["foo"], check.Equals, "bar")
 
-       spList, err := s.conn.SpecimenList(s.ctx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
+       spList, err := s.conn.GroupList(s.ctx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
        c.Check(err, check.IsNil)
        c.Check(spList.ItemsAvailable, check.Equals, 1)
        c.Assert(spList.Items, check.HasLen, 1)
        c.Check(err, check.IsNil)
        c.Check(spList.ItemsAvailable, check.Equals, 1)
        c.Assert(spList.Items, check.HasLen, 1)
@@ -124,12 +125,12 @@ func (s *RPCSuite) TestSpecimenCRUD(c *check.C) {
        c.Check(spList.Items[0].Properties["foo"], check.Equals, "bar")
 
        anonCtx := context.WithValue(context.Background(), contextKeyTestTokens, []string{arvadostest.AnonymousToken})
        c.Check(spList.Items[0].Properties["foo"], check.Equals, "bar")
 
        anonCtx := context.WithValue(context.Background(), contextKeyTestTokens, []string{arvadostest.AnonymousToken})
-       spList, err = s.conn.SpecimenList(anonCtx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
+       spList, err = s.conn.GroupList(anonCtx, arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", sp.UUID}}})
        c.Check(err, check.IsNil)
        c.Check(spList.ItemsAvailable, check.Equals, 0)
        c.Check(spList.Items, check.HasLen, 0)
 
        c.Check(err, check.IsNil)
        c.Check(spList.ItemsAvailable, check.Equals, 0)
        c.Check(spList.Items, check.HasLen, 0)
 
-       spDel, err := s.conn.SpecimenDelete(s.ctx, arvados.DeleteOptions{UUID: sp.UUID})
+       spDel, err := s.conn.GroupDelete(s.ctx, arvados.DeleteOptions{UUID: sp.UUID})
        c.Check(err, check.IsNil)
        c.Check(spDel.UUID, check.Equals, sp.UUID)
 }
        c.Check(err, check.IsNil)
        c.Check(spDel.UUID, check.Equals, sp.UUID)
 }
index a081c5d325dc2c3f8954de873dc26bf332e12066..b411948733cf79d21d6e463ee84ff1d130b99a02 100644 (file)
@@ -9,6 +9,7 @@ import (
        "errors"
        "fmt"
        "io"
        "errors"
        "fmt"
        "io"
+       "io/fs"
        "os"
        "path/filepath"
        "sort"
        "os"
        "path/filepath"
        "sort"
@@ -17,6 +18,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/keepclient"
        "git.arvados.org/arvados.git/sdk/go/manifest"
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/keepclient"
        "git.arvados.org/arvados.git/sdk/go/manifest"
+       "github.com/bmatcuk/doublestar/v4"
 )
 
 type printfer interface {
 )
 
 type printfer interface {
@@ -54,6 +56,7 @@ type copier struct {
        keepClient    IKeepClient
        hostOutputDir string
        ctrOutputDir  string
        keepClient    IKeepClient
        hostOutputDir string
        ctrOutputDir  string
+       globs         []string
        bindmounts    map[string]bindmount
        mounts        map[string]arvados.Mount
        secretMounts  map[string]arvados.Mount
        bindmounts    map[string]bindmount
        mounts        map[string]arvados.Mount
        secretMounts  map[string]arvados.Mount
@@ -72,16 +75,29 @@ func (cp *copier) Copy() (string, error) {
        if err != nil {
                return "", fmt.Errorf("error scanning files to copy to output: %v", err)
        }
        if err != nil {
                return "", fmt.Errorf("error scanning files to copy to output: %v", err)
        }
-       fs, err := (&arvados.Collection{ManifestText: cp.manifest}).FileSystem(cp.client, cp.keepClient)
+       collfs, err := (&arvados.Collection{ManifestText: cp.manifest}).FileSystem(cp.client, cp.keepClient)
        if err != nil {
                return "", fmt.Errorf("error creating Collection.FileSystem: %v", err)
        }
        if err != nil {
                return "", fmt.Errorf("error creating Collection.FileSystem: %v", err)
        }
+
+       // Remove files/dirs that don't match globs (the ones that
+       // were added during cp.walkMount() by copying subtree
+       // manifests into cp.manifest).
+       err = cp.applyGlobsToCollectionFS(collfs)
+       if err != nil {
+               return "", fmt.Errorf("error while removing non-matching files from output collection: %w", err)
+       }
+       // Remove files/dirs that don't match globs (the ones that are
+       // stored on the local filesystem and would need to be copied
+       // in copyFile() below).
+       cp.applyGlobsToFilesAndDirs()
        for _, d := range cp.dirs {
        for _, d := range cp.dirs {
-               err = fs.Mkdir(d, 0777)
+               err = collfs.Mkdir(d, 0777)
                if err != nil && err != os.ErrExist {
                        return "", fmt.Errorf("error making directory %q in output collection: %v", d, err)
                }
        }
                if err != nil && err != os.ErrExist {
                        return "", fmt.Errorf("error making directory %q in output collection: %v", d, err)
                }
        }
+
        var unflushed int64
        var lastparentdir string
        for _, f := range cp.files {
        var unflushed int64
        var lastparentdir string
        for _, f := range cp.files {
@@ -91,20 +107,184 @@ func (cp *copier) Copy() (string, error) {
                // open so f's data can be packed with it).
                dir, _ := filepath.Split(f.dst)
                if dir != lastparentdir || unflushed > keepclient.BLOCKSIZE {
                // open so f's data can be packed with it).
                dir, _ := filepath.Split(f.dst)
                if dir != lastparentdir || unflushed > keepclient.BLOCKSIZE {
-                       if err := fs.Flush("/"+lastparentdir, dir != lastparentdir); err != nil {
+                       if err := collfs.Flush("/"+lastparentdir, dir != lastparentdir); err != nil {
                                return "", fmt.Errorf("error flushing output collection file data: %v", err)
                        }
                        unflushed = 0
                }
                lastparentdir = dir
 
                                return "", fmt.Errorf("error flushing output collection file data: %v", err)
                        }
                        unflushed = 0
                }
                lastparentdir = dir
 
-               n, err := cp.copyFile(fs, f)
+               n, err := cp.copyFile(collfs, f)
                if err != nil {
                        return "", fmt.Errorf("error copying file %q into output collection: %v", f, err)
                }
                unflushed += n
        }
                if err != nil {
                        return "", fmt.Errorf("error copying file %q into output collection: %v", f, err)
                }
                unflushed += n
        }
-       return fs.MarshalManifest(".")
+       return collfs.MarshalManifest(".")
+}
+
+func (cp *copier) matchGlobs(path string, isDir bool) bool {
+       // An entry in the top level of the output directory looks
+       // like "/foo", but globs look like "foo", so we strip the
+       // leading "/" before matching.
+       path = strings.TrimLeft(path, "/")
+       for _, glob := range cp.globs {
+               if !isDir && strings.HasSuffix(glob, "/**") {
+                       // doublestar.Match("f*/**", "ff") and
+                       // doublestar.Match("f*/**", "ff/gg") both
+                       // return true, but (to be compatible with
+                       // bash shopt) "ff" should match only if it is
+                       // a directory.
+                       //
+                       // To avoid errant matches, we add the file's
+                       // basename to the end of the pattern:
+                       //
+                       // Match("f*/**/ff", "ff") => false
+                       // Match("f*/**/gg", "ff/gg") => true
+                       //
+                       // Of course, we need to escape basename in
+                       // case it contains *, ?, \, etc.
+                       _, name := filepath.Split(path)
+                       escapedName := strings.TrimSuffix(strings.Replace(name, "", "\\", -1), "\\")
+                       if match, _ := doublestar.Match(glob+"/"+escapedName, path); match {
+                               return true
+                       }
+               } else if match, _ := doublestar.Match(glob, path); match {
+                       return true
+               } else if isDir {
+                       // Workaround doublestar bug (v4.6.1).
+                       // "foo*/**" should match "foo", but does not,
+                       // because isZeroLengthPattern does not accept
+                       // "*/**" as a zero length pattern.
+                       if trunc := strings.TrimSuffix(glob, "*/**"); trunc != glob {
+                               if match, _ := doublestar.Match(trunc, path); match {
+                                       return true
+                               }
+                       }
+               }
+       }
+       return false
+}
+
+// Delete entries from cp.files that do not match cp.globs.
+//
+// Delete entries from cp.dirs that do not match cp.globs.
+//
+// Ensure parent/ancestor directories of remaining cp.files and
+// cp.dirs entries are still present in cp.dirs, even if they do not
+// match cp.globs themselves.
+func (cp *copier) applyGlobsToFilesAndDirs() {
+       if len(cp.globs) == 0 {
+               return
+       }
+       keepdirs := make(map[string]bool)
+       for _, path := range cp.dirs {
+               if cp.matchGlobs(path, true) {
+                       keepdirs[path] = true
+               }
+       }
+       for path := range keepdirs {
+               for i, c := range path {
+                       if i > 0 && c == '/' {
+                               keepdirs[path[:i]] = true
+                       }
+               }
+       }
+       var keepfiles []filetodo
+       for _, file := range cp.files {
+               if cp.matchGlobs(file.dst, false) {
+                       keepfiles = append(keepfiles, file)
+               }
+       }
+       for _, file := range keepfiles {
+               for i, c := range file.dst {
+                       if i > 0 && c == '/' {
+                               keepdirs[file.dst[:i]] = true
+                       }
+               }
+       }
+       cp.dirs = nil
+       for path := range keepdirs {
+               cp.dirs = append(cp.dirs, path)
+       }
+       sort.Strings(cp.dirs)
+       cp.files = keepfiles
+}
+
+// Delete files in collfs that do not match cp.globs.  Also delete
+// directories that are empty (after deleting non-matching files) and
+// do not match cp.globs themselves.
+func (cp *copier) applyGlobsToCollectionFS(collfs arvados.CollectionFileSystem) error {
+       if len(cp.globs) == 0 {
+               return nil
+       }
+       include := make(map[string]bool)
+       err := fs.WalkDir(arvados.FS(collfs), "", func(path string, ent fs.DirEntry, err error) error {
+               if cp.matchGlobs(path, ent.IsDir()) {
+                       for i, c := range path {
+                               if i > 0 && c == '/' {
+                                       include[path[:i]] = true
+                               }
+                       }
+                       include[path] = true
+               }
+               return nil
+       })
+       if err != nil {
+               return err
+       }
+       err = fs.WalkDir(arvados.FS(collfs), "", func(path string, ent fs.DirEntry, err error) error {
+               if err != nil || path == "" {
+                       return err
+               }
+               if !include[path] {
+                       err := collfs.RemoveAll(path)
+                       if err != nil {
+                               return err
+                       }
+                       if ent.IsDir() {
+                               return fs.SkipDir
+                       }
+               }
+               return nil
+       })
+       return err
+}
+
+// Return true if it's possible for any descendant of the given path
+// to match anything in cp.globs.  Used by walkMount to avoid loading
+// collections that are mounted underneath ctrOutputPath but excluded
+// by globs.
+func (cp *copier) subtreeCouldMatch(path string) bool {
+       if len(cp.globs) == 0 {
+               return true
+       }
+       pathdepth := 1 + strings.Count(path, "/")
+       for _, glob := range cp.globs {
+               globdepth := 0
+               lastsep := 0
+               for i, c := range glob {
+                       if c != '/' || !doublestar.ValidatePattern(glob[:i]) {
+                               // Escaped "/", or "/" in a character
+                               // class, is not a path separator.
+                               continue
+                       }
+                       if glob[lastsep:i] == "**" {
+                               return true
+                       }
+                       lastsep = i + 1
+                       if globdepth++; globdepth == pathdepth {
+                               if match, _ := doublestar.Match(glob[:i]+"/*", path+"/z"); match {
+                                       return true
+                               }
+                               break
+                       }
+               }
+               if globdepth < pathdepth && glob[lastsep:] == "**" {
+                       return true
+               }
+       }
+       return false
 }
 
 func (cp *copier) copyFile(fs arvados.CollectionFileSystem, f filetodo) (int64, error) {
 }
 
 func (cp *copier) copyFile(fs arvados.CollectionFileSystem, f filetodo) (int64, error) {
@@ -161,9 +341,8 @@ func (cp *copier) walkMount(dest, src string, maxSymlinks int, walkMountsBelow b
        // copy, relative to its mount point -- ".", "./foo.txt", ...
        srcRelPath := filepath.Join(".", srcMount.Path, src[len(srcRoot):])
 
        // copy, relative to its mount point -- ".", "./foo.txt", ...
        srcRelPath := filepath.Join(".", srcMount.Path, src[len(srcRoot):])
 
-       // outputRelPath is the path relative in the output directory
-       // that corresponds to the path in the output collection where
-       // the file will go, for logging
+       // outputRelPath is the destination path relative to the
+       // output directory. Used for logging and glob matching.
        var outputRelPath = ""
        if strings.HasPrefix(src, cp.ctrOutputDir) {
                outputRelPath = strings.TrimPrefix(src[len(cp.ctrOutputDir):], "/")
        var outputRelPath = ""
        if strings.HasPrefix(src, cp.ctrOutputDir) {
                outputRelPath = strings.TrimPrefix(src[len(cp.ctrOutputDir):], "/")
@@ -177,6 +356,9 @@ func (cp *copier) walkMount(dest, src string, maxSymlinks int, walkMountsBelow b
 
        switch {
        case srcMount.ExcludeFromOutput:
 
        switch {
        case srcMount.ExcludeFromOutput:
+       case outputRelPath != "*" && !cp.subtreeCouldMatch(outputRelPath):
+               cp.logger.Printf("not copying %q because contents cannot match output globs", outputRelPath)
+               return nil
        case srcMount.Kind == "tmp":
                // Handle by walking the host filesystem.
                return cp.walkHostFS(dest, src, maxSymlinks, walkMountsBelow)
        case srcMount.Kind == "tmp":
                // Handle by walking the host filesystem.
                return cp.walkHostFS(dest, src, maxSymlinks, walkMountsBelow)
@@ -328,6 +510,8 @@ func (cp *copier) walkHostFS(dest, src string, maxSymlinks int, includeMounts bo
                                // (...except mount types that are
                                // handled as regular files.)
                                continue
                                // (...except mount types that are
                                // handled as regular files.)
                                continue
+                       } else if isMount && !cp.subtreeCouldMatch(src[len(cp.ctrOutputDir)+1:]) {
+                               continue
                        }
                        err = cp.walkHostFS(dest, src, maxSymlinks, false)
                        if err != nil {
                        }
                        err = cp.walkHostFS(dest, src, maxSymlinks, false)
                        if err != nil {
index c8936d1a9f53f31b218441ac24820ed906de2e00..486bf6fa635784eedc78dc326ad0f4dabaf7144e 100644 (file)
@@ -7,8 +7,9 @@ package crunchrun
 import (
        "bytes"
        "io"
 import (
        "bytes"
        "io"
-       "io/ioutil"
+       "io/fs"
        "os"
        "os"
+       "sort"
        "syscall"
 
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "syscall"
 
        "git.arvados.org/arvados.git/sdk/go/arvados"
@@ -115,9 +116,7 @@ func (s *copierSuite) TestSymlinkToMountedCollection(c *check.C) {
        }
 
        // simulate mounted writable collection
        }
 
        // simulate mounted writable collection
-       bindtmp, err := ioutil.TempDir("", "crunch-run.test.")
-       c.Assert(err, check.IsNil)
-       defer os.RemoveAll(bindtmp)
+       bindtmp := c.MkDir()
        f, err := os.OpenFile(bindtmp+"/.arvados#collection", os.O_CREATE|os.O_WRONLY, 0644)
        c.Assert(err, check.IsNil)
        _, err = io.WriteString(f, `{"manifest_text":". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"}`)
        f, err := os.OpenFile(bindtmp+"/.arvados#collection", os.O_CREATE|os.O_WRONLY, 0644)
        c.Assert(err, check.IsNil)
        _, err = io.WriteString(f, `{"manifest_text":". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"}`)
@@ -215,6 +214,142 @@ func (s *copierSuite) TestWritableMountBelow(c *check.C) {
        })
 }
 
        })
 }
 
+// Check some glob-matching edge cases. In particular, check that
+// patterns like "foo/**" do not match regular files named "foo"
+// (unless of course they are inside a directory named "foo").
+func (s *copierSuite) TestMatchGlobs(c *check.C) {
+       s.cp.globs = []string{"foo*/**"}
+       c.Check(s.cp.matchGlobs("foo", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("food", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("foo", false), check.Equals, false)
+       c.Check(s.cp.matchGlobs("food", false), check.Equals, false)
+       c.Check(s.cp.matchGlobs("foo/bar", false), check.Equals, true)
+       c.Check(s.cp.matchGlobs("food/bar", false), check.Equals, true)
+       c.Check(s.cp.matchGlobs("foo/bar", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("food/bar", true), check.Equals, true)
+
+       s.cp.globs = []string{"ba[!/]/foo*/**"}
+       c.Check(s.cp.matchGlobs("bar/foo", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("bar/food", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("bar/foo", false), check.Equals, false)
+       c.Check(s.cp.matchGlobs("bar/food", false), check.Equals, false)
+       c.Check(s.cp.matchGlobs("bar/foo/z\\[", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("bar/food/z\\[", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("bar/foo/z\\[", false), check.Equals, true)
+       c.Check(s.cp.matchGlobs("bar/food/z\\[", false), check.Equals, true)
+
+       s.cp.globs = []string{"waz/**/foo*/**"}
+       c.Check(s.cp.matchGlobs("waz/quux/foo", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("waz/quux/food", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("waz/quux/foo", false), check.Equals, false)
+       c.Check(s.cp.matchGlobs("waz/quux/food", false), check.Equals, false)
+       c.Check(s.cp.matchGlobs("waz/quux/foo/foo", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("waz/quux/food/foo", true), check.Equals, true)
+       c.Check(s.cp.matchGlobs("waz/quux/foo/foo", false), check.Equals, true)
+       c.Check(s.cp.matchGlobs("waz/quux/food/foo", false), check.Equals, true)
+
+       s.cp.globs = []string{"foo/**/*"}
+       c.Check(s.cp.matchGlobs("foo", false), check.Equals, false)
+       c.Check(s.cp.matchGlobs("foo/bar", false), check.Equals, true)
+       c.Check(s.cp.matchGlobs("foo/bar/baz", false), check.Equals, true)
+       c.Check(s.cp.matchGlobs("foo/bar/baz/waz", false), check.Equals, true)
+}
+
+func (s *copierSuite) TestSubtreeCouldMatch(c *check.C) {
+       for _, trial := range []struct {
+               mount string // relative to output dir
+               glob  string
+               could bool
+       }{
+               {mount: "abc", glob: "*"},
+               {mount: "abc", glob: "abc/*", could: true},
+               {mount: "abc", glob: "a*/**", could: true},
+               {mount: "abc", glob: "**", could: true},
+               {mount: "abc", glob: "*/*", could: true},
+               {mount: "abc", glob: "**/*.txt", could: true},
+               {mount: "abc/def", glob: "*"},
+               {mount: "abc/def", glob: "*/*"},
+               {mount: "abc/def", glob: "*/*.txt"},
+               {mount: "abc/def", glob: "*/*/*", could: true},
+               {mount: "abc/def", glob: "**", could: true},
+               {mount: "abc/def", glob: "**/bar", could: true},
+               {mount: "abc/def", glob: "abc/**", could: true},
+               {mount: "abc/def/ghi", glob: "*c/**/bar", could: true},
+               {mount: "abc/def/ghi", glob: "*c/*f/bar"},
+               {mount: "abc/def/ghi", glob: "abc/d[^/]f/ghi/*", could: true},
+       } {
+               c.Logf("=== %+v", trial)
+               got := (&copier{
+                       globs: []string{trial.glob},
+               }).subtreeCouldMatch(trial.mount)
+               c.Check(got, check.Equals, trial.could)
+       }
+}
+
+func (s *copierSuite) TestMountBelowExcludedByGlob(c *check.C) {
+       bindtmp := c.MkDir()
+       s.cp.mounts["/ctr/outdir/include/includer"] = arvados.Mount{
+               Kind:             "collection",
+               PortableDataHash: arvadostest.FooCollectionPDH,
+       }
+       s.cp.mounts["/ctr/outdir/include/includew"] = arvados.Mount{
+               Kind:             "collection",
+               PortableDataHash: arvadostest.FooCollectionPDH,
+               Writable:         true,
+       }
+       s.cp.mounts["/ctr/outdir/exclude/excluder"] = arvados.Mount{
+               Kind:             "collection",
+               PortableDataHash: arvadostest.FooCollectionPDH,
+       }
+       s.cp.mounts["/ctr/outdir/exclude/excludew"] = arvados.Mount{
+               Kind:             "collection",
+               PortableDataHash: arvadostest.FooCollectionPDH,
+               Writable:         true,
+       }
+       s.cp.mounts["/ctr/outdir/nonexistent/collection"] = arvados.Mount{
+               // As extra assurance, plant a collection that will
+               // fail if copier attempts to load its manifest.  (For
+               // performance reasons it's important that copier
+               // doesn't try to load the manifest before deciding
+               // not to copy the contents.)
+               Kind:             "collection",
+               PortableDataHash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+1234",
+       }
+       s.cp.globs = []string{
+               "?ncl*/*r/*",
+               "*/?ncl*/**",
+       }
+       c.Assert(os.MkdirAll(s.cp.hostOutputDir+"/include/includer", 0755), check.IsNil)
+       c.Assert(os.MkdirAll(s.cp.hostOutputDir+"/include/includew", 0755), check.IsNil)
+       c.Assert(os.MkdirAll(s.cp.hostOutputDir+"/exclude/excluder", 0755), check.IsNil)
+       c.Assert(os.MkdirAll(s.cp.hostOutputDir+"/exclude/excludew", 0755), check.IsNil)
+       s.writeFileInOutputDir(c, "include/includew/foo", "foo")
+       s.writeFileInOutputDir(c, "exclude/excludew/foo", "foo")
+       s.cp.bindmounts = map[string]bindmount{
+               "/ctr/outdir/include/includew": bindmount{HostPath: bindtmp, ReadOnly: false},
+       }
+       s.cp.bindmounts = map[string]bindmount{
+               "/ctr/outdir/include/excludew": bindmount{HostPath: bindtmp, ReadOnly: false},
+       }
+
+       err := s.cp.walkMount("", s.cp.ctrOutputDir, 10, true)
+       c.Check(err, check.IsNil)
+       c.Log(s.log.String())
+
+       // Note it's OK that "/exclude" is not excluded by walkMount:
+       // it is just a local filesystem directory, not a mount point
+       // that's expensive to walk.  In real-life usage, it will be
+       // removed from cp.dirs before any copying happens.
+       c.Check(s.cp.dirs, check.DeepEquals, []string{"/exclude", "/include", "/include/includew"})
+       c.Check(s.cp.files, check.DeepEquals, []filetodo{
+               {src: s.cp.hostOutputDir + "/include/includew/foo", dst: "/include/includew/foo", size: 3},
+       })
+       c.Check(s.cp.manifest, check.Matches, `(?ms).*\./include/includer .*`)
+       c.Check(s.cp.manifest, check.Not(check.Matches), `(?ms).*exclude.*`)
+       c.Check(s.log.String(), check.Matches, `(?ms).*not copying \\"exclude/excluder\\".*`)
+       c.Check(s.log.String(), check.Matches, `(?ms).*not copying \\"nonexistent/collection\\".*`)
+}
+
 func (s *copierSuite) writeFileInOutputDir(c *check.C, path, data string) {
        f, err := os.OpenFile(s.cp.hostOutputDir+"/"+path, os.O_CREATE|os.O_WRONLY, 0644)
        c.Assert(err, check.IsNil)
 func (s *copierSuite) writeFileInOutputDir(c *check.C, path, data string) {
        f, err := os.OpenFile(s.cp.hostOutputDir+"/"+path, os.O_CREATE|os.O_WRONLY, 0644)
        c.Assert(err, check.IsNil)
@@ -222,3 +357,184 @@ func (s *copierSuite) writeFileInOutputDir(c *check.C, path, data string) {
        c.Assert(err, check.IsNil)
        c.Assert(f.Close(), check.IsNil)
 }
        c.Assert(err, check.IsNil)
        c.Assert(f.Close(), check.IsNil)
 }
+
+// applyGlobsToFilesAndDirs uses the same glob-matching code as
+// applyGlobsToCollectionFS, so we don't need to test all of the same
+// glob-matching behavior covered in TestApplyGlobsToCollectionFS.  We
+// do need to check that (a) the glob is actually being used to filter
+// out files, and (b) non-matching dirs still included if and only if
+// they are ancestors of matching files.
+func (s *copierSuite) TestApplyGlobsToFilesAndDirs(c *check.C) {
+       dirs := []string{"dir1", "dir1/dir11", "dir1/dir12", "dir2"}
+       files := []string{"dir1/file11", "dir1/dir11/file111", "dir2/file2"}
+       for _, trial := range []struct {
+               globs []string
+               dirs  []string
+               files []string
+       }{
+               {
+                       globs: []string{},
+                       dirs:  append([]string{}, dirs...),
+                       files: append([]string{}, files...),
+               },
+               {
+                       globs: []string{"**"},
+                       dirs:  append([]string{}, dirs...),
+                       files: append([]string{}, files...),
+               },
+               {
+                       globs: []string{"**/file111"},
+                       dirs:  []string{"dir1", "dir1/dir11"},
+                       files: []string{"dir1/dir11/file111"},
+               },
+               {
+                       globs: []string{"nothing"},
+                       dirs:  nil,
+                       files: nil,
+               },
+               {
+                       globs: []string{"**/dir12"},
+                       dirs:  []string{"dir1", "dir1/dir12"},
+                       files: nil,
+               },
+               {
+                       globs: []string{"**/file*"},
+                       dirs:  []string{"dir1", "dir1/dir11", "dir2"},
+                       files: append([]string{}, files...),
+               },
+               {
+                       globs: []string{"**/dir1[12]"},
+                       dirs:  []string{"dir1", "dir1/dir11", "dir1/dir12"},
+                       files: nil,
+               },
+               {
+                       globs: []string{"**/dir1[^2]"},
+                       dirs:  []string{"dir1", "dir1/dir11"},
+                       files: nil,
+               },
+               {
+                       globs: []string{"dir1/**"},
+                       dirs:  []string{"dir1", "dir1/dir11", "dir1/dir12"},
+                       files: []string{"dir1/file11", "dir1/dir11/file111"},
+               },
+       } {
+               c.Logf("=== globs: %q", trial.globs)
+               cp := copier{
+                       globs: trial.globs,
+                       dirs:  dirs,
+               }
+               for _, path := range files {
+                       cp.files = append(cp.files, filetodo{dst: path})
+               }
+               cp.applyGlobsToFilesAndDirs()
+               var gotFiles []string
+               for _, file := range cp.files {
+                       gotFiles = append(gotFiles, file.dst)
+               }
+               c.Check(cp.dirs, check.DeepEquals, trial.dirs)
+               c.Check(gotFiles, check.DeepEquals, trial.files)
+       }
+}
+
+func (s *copierSuite) TestApplyGlobsToCollectionFS(c *check.C) {
+       for _, trial := range []struct {
+               globs  []string
+               expect []string
+       }{
+               {
+                       globs:  nil,
+                       expect: []string{"foo", "bar", "baz/quux", "baz/parent1/item1"},
+               },
+               {
+                       globs:  []string{"foo"},
+                       expect: []string{"foo"},
+               },
+               {
+                       globs:  []string{"baz/parent1/item1"},
+                       expect: []string{"baz/parent1/item1"},
+               },
+               {
+                       globs:  []string{"**"},
+                       expect: []string{"foo", "bar", "baz/quux", "baz/parent1/item1"},
+               },
+               {
+                       globs:  []string{"**/*"},
+                       expect: []string{"foo", "bar", "baz/quux", "baz/parent1/item1"},
+               },
+               {
+                       globs:  []string{"*"},
+                       expect: []string{"foo", "bar"},
+               },
+               {
+                       globs:  []string{"baz"},
+                       expect: nil,
+               },
+               {
+                       globs:  []string{"b*/**"},
+                       expect: []string{"baz/quux", "baz/parent1/item1"},
+               },
+               {
+                       globs:  []string{"baz"},
+                       expect: nil,
+               },
+               {
+                       globs:  []string{"baz/**"},
+                       expect: []string{"baz/quux", "baz/parent1/item1"},
+               },
+               {
+                       globs:  []string{"baz/*"},
+                       expect: []string{"baz/quux"},
+               },
+               {
+                       globs:  []string{"baz/**/*uu?"},
+                       expect: []string{"baz/quux"},
+               },
+               {
+                       globs:  []string{"**/*m1"},
+                       expect: []string{"baz/parent1/item1"},
+               },
+               {
+                       globs:  []string{"*/*/*/**/*1"},
+                       expect: nil,
+               },
+               {
+                       globs:  []string{"f*", "**/q*"},
+                       expect: []string{"foo", "baz/quux"},
+               },
+               {
+                       globs:  []string{"\\"}, // invalid pattern matches nothing
+                       expect: nil,
+               },
+               {
+                       globs:  []string{"\\", "foo"},
+                       expect: []string{"foo"},
+               },
+               {
+                       globs:  []string{"foo/**"},
+                       expect: nil,
+               },
+               {
+                       globs:  []string{"foo*/**"},
+                       expect: nil,
+               },
+       } {
+               c.Logf("=== globs: %q", trial.globs)
+               collfs, err := (&arvados.Collection{ManifestText: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo 0:0:bar 0:0:baz/quux 0:0:baz/parent1/item1\n"}).FileSystem(nil, nil)
+               c.Assert(err, check.IsNil)
+               cp := copier{globs: trial.globs}
+               err = cp.applyGlobsToCollectionFS(collfs)
+               if !c.Check(err, check.IsNil) {
+                       continue
+               }
+               var got []string
+               fs.WalkDir(arvados.FS(collfs), "", func(path string, ent fs.DirEntry, err error) error {
+                       if !ent.IsDir() {
+                               got = append(got, path)
+                       }
+                       return nil
+               })
+               sort.Strings(got)
+               sort.Strings(trial.expect)
+               c.Check(got, check.DeepEquals, trial.expect)
+       }
+}
index bde13424dd2db954a631a9fe3a70e236a2873b62..f8941162ec089515fd8ad2708f32e72162bc771e 100644 (file)
@@ -626,17 +626,6 @@ func (runner *ContainerRunner) SetupMounts() (map[string]bindmount, error) {
                                // OutputPath is a staging directory.
                                bindmounts[bind] = bindmount{HostPath: tmpfn, ReadOnly: true}
                        }
                                // OutputPath is a staging directory.
                                bindmounts[bind] = bindmount{HostPath: tmpfn, ReadOnly: true}
                        }
-
-               case mnt.Kind == "git_tree":
-                       tmpdir, err := runner.MkTempDir(runner.parentTemp, "git_tree")
-                       if err != nil {
-                               return nil, fmt.Errorf("creating temp dir: %v", err)
-                       }
-                       err = gitMount(mnt).extractTree(runner.containerClient, tmpdir, token)
-                       if err != nil {
-                               return nil, err
-                       }
-                       bindmounts[bind] = bindmount{HostPath: tmpdir, ReadOnly: true}
                }
        }
 
                }
        }
 
@@ -852,51 +841,36 @@ func (runner *ContainerRunner) LogHostInfo() (err error) {
 
 // LogContainerRecord gets and saves the raw JSON container record from the API server
 func (runner *ContainerRunner) LogContainerRecord() error {
 
 // LogContainerRecord gets and saves the raw JSON container record from the API server
 func (runner *ContainerRunner) LogContainerRecord() error {
-       logged, err := runner.logAPIResponse("container", "containers", map[string]interface{}{"filters": [][]string{{"uuid", "=", runner.Container.UUID}}}, nil)
+       logged, err := runner.logAPIResponse("container", "containers", map[string]interface{}{"filters": [][]string{{"uuid", "=", runner.Container.UUID}}})
        if !logged && err == nil {
                err = fmt.Errorf("error: no container record found for %s", runner.Container.UUID)
        }
        return err
 }
 
        if !logged && err == nil {
                err = fmt.Errorf("error: no container record found for %s", runner.Container.UUID)
        }
        return err
 }
 
-// LogNodeRecord logs the current host's InstanceType config entry (or
-// the arvados#node record, if running via crunch-dispatch-slurm).
+// LogNodeRecord logs the current host's InstanceType config entry, if
+// running via arvados-dispatch-cloud.
 func (runner *ContainerRunner) LogNodeRecord() error {
 func (runner *ContainerRunner) LogNodeRecord() error {
-       if it := os.Getenv("InstanceType"); it != "" {
-               // Dispatched via arvados-dispatch-cloud. Save
-               // InstanceType config fragment received from
-               // dispatcher on stdin.
-               w, err := runner.LogCollection.OpenFile("node.json", os.O_CREATE|os.O_WRONLY, 0666)
-               if err != nil {
-                       return err
-               }
-               defer w.Close()
-               _, err = io.WriteString(w, it)
-               if err != nil {
-                       return err
-               }
-               return w.Close()
+       it := os.Getenv("InstanceType")
+       if it == "" {
+               // Not dispatched by arvados-dispatch-cloud.
+               return nil
        }
        }
-       // Dispatched via crunch-dispatch-slurm. Look up
-       // apiserver's node record corresponding to
-       // $SLURMD_NODENAME.
-       hostname := os.Getenv("SLURMD_NODENAME")
-       if hostname == "" {
-               hostname, _ = os.Hostname()
+       // Save InstanceType config fragment received from dispatcher
+       // on stdin.
+       w, err := runner.LogCollection.OpenFile("node.json", os.O_CREATE|os.O_WRONLY, 0666)
+       if err != nil {
+               return err
        }
        }
-       _, err := runner.logAPIResponse("node", "nodes", map[string]interface{}{"filters": [][]string{{"hostname", "=", hostname}}}, func(resp interface{}) {
-               // The "info" field has admin-only info when
-               // obtained with a privileged token, and
-               // should not be logged.
-               node, ok := resp.(map[string]interface{})
-               if ok {
-                       delete(node, "info")
-               }
-       })
-       return err
+       defer w.Close()
+       _, err = io.WriteString(w, it)
+       if err != nil {
+               return err
+       }
+       return w.Close()
 }
 
 }
 
-func (runner *ContainerRunner) logAPIResponse(label, path string, params map[string]interface{}, munge func(interface{})) (logged bool, err error) {
+func (runner *ContainerRunner) logAPIResponse(label, path string, params map[string]interface{}) (logged bool, err error) {
        writer, err := runner.LogCollection.OpenFile(label+".json", os.O_CREATE|os.O_WRONLY, 0666)
        if err != nil {
                return false, err
        writer, err := runner.LogCollection.OpenFile(label+".json", os.O_CREATE|os.O_WRONLY, 0666)
        if err != nil {
                return false, err
@@ -926,9 +900,6 @@ func (runner *ContainerRunner) logAPIResponse(label, path string, params map[str
        } else if len(items) < 1 {
                return false, nil
        }
        } else if len(items) < 1 {
                return false, nil
        }
-       if munge != nil {
-               munge(items[0])
-       }
        // Re-encode it using indentation to improve readability
        enc := json.NewEncoder(w)
        enc.SetIndent("", "    ")
        // Re-encode it using indentation to improve readability
        enc := json.NewEncoder(w)
        enc.SetIndent("", "    ")
@@ -1365,6 +1336,7 @@ func (runner *ContainerRunner) CaptureOutput(bindmounts map[string]bindmount) er
                keepClient:    runner.ContainerKeepClient,
                hostOutputDir: runner.HostOutputDir,
                ctrOutputDir:  runner.Container.OutputPath,
                keepClient:    runner.ContainerKeepClient,
                hostOutputDir: runner.HostOutputDir,
                ctrOutputDir:  runner.Container.OutputPath,
+               globs:         runner.Container.OutputGlob,
                bindmounts:    bindmounts,
                mounts:        runner.Container.Mounts,
                secretMounts:  runner.SecretMounts,
                bindmounts:    bindmounts,
                mounts:        runner.Container.Mounts,
                secretMounts:  runner.SecretMounts,
@@ -2256,9 +2228,14 @@ func startLocalKeepstore(configData ConfigData, logbuf io.Writer) (*exec.Cmd, er
        }
 
        // Rather than have an alternate way to tell keepstore how
        }
 
        // Rather than have an alternate way to tell keepstore how
-       // many buffers to use when starting it this way, we just
-       // modify the cluster configuration that we feed it on stdin.
-       configData.Cluster.API.MaxKeepBlobBuffers = configData.KeepBuffers
+       // many buffers to use, etc., when starting it this way, we
+       // just modify the cluster configuration that we feed it on
+       // stdin.
+       ccfg := *configData.Cluster
+       ccfg.API.MaxKeepBlobBuffers = configData.KeepBuffers
+       ccfg.Collections.BlobTrash = false
+       ccfg.Collections.BlobTrashConcurrency = 0
+       ccfg.Collections.BlobDeleteConcurrency = 0
 
        localaddr := localKeepstoreAddr()
        ln, err := net.Listen("tcp", net.JoinHostPort(localaddr, "0"))
 
        localaddr := localKeepstoreAddr()
        ln, err := net.Listen("tcp", net.JoinHostPort(localaddr, "0"))
@@ -2278,7 +2255,7 @@ func startLocalKeepstore(configData ConfigData, logbuf io.Writer) (*exec.Cmd, er
        var confJSON bytes.Buffer
        err = json.NewEncoder(&confJSON).Encode(arvados.Config{
                Clusters: map[string]arvados.Cluster{
        var confJSON bytes.Buffer
        err = json.NewEncoder(&confJSON).Encode(arvados.Config{
                Clusters: map[string]arvados.Cluster{
-                       configData.Cluster.ClusterID: *configData.Cluster,
+                       ccfg.ClusterID: ccfg,
                },
        })
        if err != nil {
                },
        })
        if err != nil {
index 276dd366617e71a17bd7acb907a30941459ce7a5..5cb982e1bbf9e5de77df1903f278878212a4d443 100644 (file)
@@ -12,6 +12,7 @@ import (
        "errors"
        "fmt"
        "io"
        "errors"
        "fmt"
        "io"
+       "io/fs"
        "io/ioutil"
        "log"
        "math/rand"
        "io/ioutil"
        "log"
        "math/rand"
@@ -39,8 +40,6 @@ import (
        "git.arvados.org/arvados.git/sdk/go/manifest"
 
        . "gopkg.in/check.v1"
        "git.arvados.org/arvados.git/sdk/go/manifest"
 
        . "gopkg.in/check.v1"
-       git_client "gopkg.in/src-d/go-git.v4/plumbing/transport/client"
-       git_http "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
 )
 
 // Gocheck boilerplate
 )
 
 // Gocheck boilerplate
@@ -113,6 +112,7 @@ func (s *TestSuite) SetUpTest(c *C) {
        err = ioutil.WriteFile(s.keepmount+"/by_id/"+fakeInputCollectionPDH+"/input.json", []byte(`{"input":true}`), 0644)
        c.Assert(err, IsNil)
        s.runner.ArvMountPoint = s.keepmount
        err = ioutil.WriteFile(s.keepmount+"/by_id/"+fakeInputCollectionPDH+"/input.json", []byte(`{"input":true}`), 0644)
        c.Assert(err, IsNil)
        s.runner.ArvMountPoint = s.keepmount
+       os.Setenv("InstanceType", `{"ProviderType":"a1.2xlarge","Price":1.2}`)
 }
 
 type ArvTestClient struct {
 }
 
 type ArvTestClient struct {
@@ -129,8 +129,8 @@ type ArvTestClient struct {
 
 type KeepTestClient struct {
        Called         bool
 
 type KeepTestClient struct {
        Called         bool
-       Content        []byte
        StorageClasses []string
        StorageClasses []string
+       blocks         sync.Map
 }
 
 type stubExecutor struct {
 }
 
 type stubExecutor struct {
@@ -358,18 +358,30 @@ func (client *KeepTestClient) LocalLocator(locator string) (string, error) {
 }
 
 func (client *KeepTestClient) BlockWrite(_ context.Context, opts arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
 }
 
 func (client *KeepTestClient) BlockWrite(_ context.Context, opts arvados.BlockWriteOptions) (arvados.BlockWriteResponse, error) {
-       client.Content = opts.Data
+       locator := fmt.Sprintf("%x+%d", md5.Sum(opts.Data), len(opts.Data))
+       client.blocks.Store(locator, append([]byte(nil), opts.Data...))
        return arvados.BlockWriteResponse{
        return arvados.BlockWriteResponse{
-               Locator: fmt.Sprintf("%x+%d", md5.Sum(opts.Data), len(opts.Data)),
+               Locator: locator,
        }, nil
 }
 
        }, nil
 }
 
-func (client *KeepTestClient) ReadAt(string, []byte, int) (int, error) {
-       return 0, errors.New("not implemented")
+func (client *KeepTestClient) ReadAt(locator string, dst []byte, offset int) (int, error) {
+       loaded, ok := client.blocks.Load(locator)
+       if !ok {
+               return 0, os.ErrNotExist
+       }
+       data := loaded.([]byte)
+       if offset >= len(data) {
+               return 0, io.EOF
+       }
+       return copy(dst, data[offset:]), nil
 }
 
 func (client *KeepTestClient) Close() {
 }
 
 func (client *KeepTestClient) Close() {
-       client.Content = nil
+       client.blocks.Range(func(locator, value interface{}) bool {
+               client.blocks.Delete(locator)
+               return true
+       })
 }
 
 func (client *KeepTestClient) SetStorageClasses(sc []string) {
 }
 
 func (client *KeepTestClient) SetStorageClasses(sc []string) {
@@ -591,7 +603,7 @@ func (ErrorReader) Seek(int64, int) (int64, error) {
        return 0, errors.New("ErrorReader")
 }
 
        return 0, errors.New("ErrorReader")
 }
 
-func (KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
+func (*KeepReadErrorTestClient) ManifestFileReader(m manifest.Manifest, filename string) (arvados.File, error) {
        return ErrorReader{}, nil
 }
 
        return ErrorReader{}, nil
 }
 
@@ -1003,9 +1015,8 @@ func (s *TestSuite) TestCrunchstat(c *C) {
 }
 
 func (s *TestSuite) TestNodeInfoLog(c *C) {
 }
 
 func (s *TestSuite) TestNodeInfoLog(c *C) {
-       os.Setenv("SLURMD_NODENAME", "compute2")
        s.fullRunHelper(c, `{
        s.fullRunHelper(c, `{
-               "command": ["sleep", "1"],
+               "command": ["true"],
                "container_image": "`+arvadostest.DockerImage112PDH+`",
                "cwd": ".",
                "environment": {},
                "container_image": "`+arvadostest.DockerImage112PDH+`",
                "cwd": ".",
                "environment": {},
@@ -1015,18 +1026,17 @@ func (s *TestSuite) TestNodeInfoLog(c *C) {
                "runtime_constraints": {},
                "state": "Locked"
        }`, nil, func() int {
                "runtime_constraints": {},
                "state": "Locked"
        }`, nil, func() int {
-               time.Sleep(time.Second)
                return 0
        })
 
        c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
        c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
 
                return 0
        })
 
        c.Check(s.api.CalledWith("container.exit_code", 0), NotNil)
        c.Check(s.api.CalledWith("container.state", "Complete"), NotNil)
 
-       c.Assert(s.api.Logs["node"], NotNil)
-       json := s.api.Logs["node"].String()
-       c.Check(json, Matches, `(?ms).*"uuid": *"zzzzz-7ekkf-2z3mc76g2q73aio".*`)
-       c.Check(json, Matches, `(?ms).*"total_cpu_cores": *16.*`)
-       c.Check(json, Not(Matches), `(?ms).*"info":.*`)
+       buf, err := fs.ReadFile(arvados.FS(s.runner.LogCollection), "/node.json")
+       c.Assert(err, IsNil)
+       json := string(buf)
+       c.Check(json, Matches, `(?ms).*"ProviderType": *"a1\.2xlarge".*`)
+       c.Check(json, Matches, `(?ms).*"Price": *1\.2.*`)
 
        c.Assert(s.api.Logs["node-info"], NotNil)
        json = s.api.Logs["node-info"].String()
 
        c.Assert(s.api.Logs["node-info"], NotNil)
        json = s.api.Logs["node-info"].String()
@@ -1740,54 +1750,6 @@ func (s *TestSuite) TestSetupMounts(c *C) {
                cr.CleanupDirs()
                checkEmpty()
        }
                cr.CleanupDirs()
                checkEmpty()
        }
-
-       // git_tree mounts
-       {
-               i = 0
-               cr.ArvMountPoint = ""
-               git_client.InstallProtocol("https", git_http.NewClient(arvados.InsecureHTTPClient))
-               cr.token = arvadostest.ActiveToken
-               cr.Container.Mounts = make(map[string]arvados.Mount)
-               cr.Container.Mounts = map[string]arvados.Mount{
-                       "/tip": {
-                               Kind:   "git_tree",
-                               UUID:   arvadostest.Repository2UUID,
-                               Commit: "fd3531f42995344f36c30b79f55f27b502f3d344",
-                               Path:   "/",
-                       },
-                       "/non-tip": {
-                               Kind:   "git_tree",
-                               UUID:   arvadostest.Repository2UUID,
-                               Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-                               Path:   "/",
-                       },
-               }
-               cr.Container.OutputPath = "/tmp"
-
-               bindmounts, err := cr.SetupMounts()
-               c.Check(err, IsNil)
-
-               for path, mount := range bindmounts {
-                       c.Check(mount.ReadOnly, Equals, !cr.Container.Mounts[path].Writable, Commentf("%s %#v", path, mount))
-               }
-
-               data, err := ioutil.ReadFile(bindmounts["/tip"].HostPath + "/dir1/dir2/file with mode 0644")
-               c.Check(err, IsNil)
-               c.Check(string(data), Equals, "\000\001\002\003")
-               _, err = ioutil.ReadFile(bindmounts["/tip"].HostPath + "/file only on testbranch")
-               c.Check(err, FitsTypeOf, &os.PathError{})
-               c.Check(os.IsNotExist(err), Equals, true)
-
-               data, err = ioutil.ReadFile(bindmounts["/non-tip"].HostPath + "/dir1/dir2/file with mode 0644")
-               c.Check(err, IsNil)
-               c.Check(string(data), Equals, "\000\001\002\003")
-               data, err = ioutil.ReadFile(bindmounts["/non-tip"].HostPath + "/file only on testbranch")
-               c.Check(err, IsNil)
-               c.Check(string(data), Equals, "testfile\n")
-
-               cr.CleanupDirs()
-               checkEmpty()
-       }
 }
 
 func (s *TestSuite) TestStdout(c *C) {
 }
 
 func (s *TestSuite) TestStdout(c *C) {
@@ -2386,7 +2348,6 @@ func (s *TestSuite) TestCalculateCost(c *C) {
        // hasn't found any data), cost is calculated based on
        // InstanceType env var
        os.Setenv("InstanceType", `{"Price":1.2}`)
        // hasn't found any data), cost is calculated based on
        // InstanceType env var
        os.Setenv("InstanceType", `{"Price":1.2}`)
-       defer os.Unsetenv("InstanceType")
        cost = cr.calculateCost(now)
        c.Check(cost, Equals, 1.2)
 
        cost = cr.calculateCost(now)
        c.Check(cost, Equals, 1.2)
 
@@ -2432,7 +2393,6 @@ func (s *TestSuite) TestSIGUSR2CostUpdate(c *C) {
        c.Assert(err, IsNil)
 
        os.Setenv("InstanceType", `{"Price":2.2}`)
        c.Assert(err, IsNil)
 
        os.Setenv("InstanceType", `{"Price":2.2}`)
-       defer os.Unsetenv("InstanceType")
        defer func(s string) { lockdir = s }(lockdir)
        lockdir = c.MkDir()
 
        defer func(s string) { lockdir = s }(lockdir)
        lockdir = c.MkDir()
 
diff --git a/lib/crunchrun/git_mount.go b/lib/crunchrun/git_mount.go
deleted file mode 100644 (file)
index 561ea18..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package crunchrun
-
-import (
-       "fmt"
-       "net/url"
-       "os"
-       "path/filepath"
-       "regexp"
-
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "gopkg.in/src-d/go-billy.v4/osfs"
-       git "gopkg.in/src-d/go-git.v4"
-       git_config "gopkg.in/src-d/go-git.v4/config"
-       git_plumbing "gopkg.in/src-d/go-git.v4/plumbing"
-       git_http "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
-       "gopkg.in/src-d/go-git.v4/storage/memory"
-)
-
-type gitMount arvados.Mount
-
-var (
-       sha1re     = regexp.MustCompile(`^[0-9a-f]{40}$`)
-       repoUUIDre = regexp.MustCompile(`^[0-9a-z]{5}-s0uqq-[0-9a-z]{15}$`)
-)
-
-func (gm gitMount) validate() error {
-       if gm.Path != "" && gm.Path != "/" {
-               return fmt.Errorf("cannot mount git_tree with path %q -- only \"/\" is supported", gm.Path)
-       }
-       if !sha1re.MatchString(gm.Commit) {
-               return fmt.Errorf("cannot mount git_tree with commit %q -- must be a 40-char SHA1", gm.Commit)
-       }
-       if gm.RepositoryName != "" || gm.GitURL != "" {
-               return fmt.Errorf("cannot mount git_tree -- repository_name and git_url must be empty")
-       }
-       if !repoUUIDre.MatchString(gm.UUID) {
-               return fmt.Errorf("cannot mount git_tree with uuid %q -- must be a repository UUID", gm.UUID)
-       }
-       if gm.Writable {
-               return fmt.Errorf("writable git_tree mount is not supported")
-       }
-       return nil
-}
-
-// ExtractTree extracts the specified tree into dir, which is an
-// existing empty local directory.
-func (gm gitMount) extractTree(ac *arvados.Client, dir string, token string) error {
-       err := gm.validate()
-       if err != nil {
-               return err
-       }
-       dd, err := ac.DiscoveryDocument()
-       if err != nil {
-               return fmt.Errorf("error getting discovery document: %w", err)
-       }
-       u, err := url.Parse(dd.GitURL)
-       if err != nil {
-               return fmt.Errorf("parse gitUrl %q: %s", dd.GitURL, err)
-       }
-       u, err = u.Parse("/" + gm.UUID + ".git")
-       if err != nil {
-               return fmt.Errorf("build git url from %q, %q: %s", dd.GitURL, gm.UUID, err)
-       }
-       store := memory.NewStorage()
-       repo, err := git.Init(store, osfs.New(dir))
-       if err != nil {
-               return fmt.Errorf("init repo: %s", err)
-       }
-       _, err = repo.CreateRemote(&git_config.RemoteConfig{
-               Name: "origin",
-               URLs: []string{u.String()},
-       })
-       if err != nil {
-               return fmt.Errorf("create remote %q: %s", u.String(), err)
-       }
-       err = repo.Fetch(&git.FetchOptions{
-               RemoteName: "origin",
-               Auth: &git_http.BasicAuth{
-                       Username: "none",
-                       Password: token,
-               },
-       })
-       if err != nil {
-               return fmt.Errorf("git fetch %q: %s", u.String(), err)
-       }
-       wt, err := repo.Worktree()
-       if err != nil {
-               return fmt.Errorf("worktree failed: %s", err)
-       }
-       err = wt.Checkout(&git.CheckoutOptions{
-               Hash: git_plumbing.NewHash(gm.Commit),
-       })
-       if err != nil {
-               return fmt.Errorf("checkout failed: %s", err)
-       }
-       err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
-               if err != nil {
-                       return err
-               }
-               // copy user rx bits to group and other, in case
-               // prevailing umask is more restrictive than 022
-               mode := info.Mode()
-               mode = mode | ((mode >> 3) & 050) | ((mode >> 6) & 5)
-               return os.Chmod(path, mode)
-       })
-       if err != nil {
-               return fmt.Errorf("chmod -R %q: %s", dir, err)
-       }
-       return nil
-}
diff --git a/lib/crunchrun/git_mount_test.go b/lib/crunchrun/git_mount_test.go
deleted file mode 100644 (file)
index ac98dcc..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package crunchrun
-
-import (
-       "io/ioutil"
-       "os"
-       "path/filepath"
-
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/arvadostest"
-       check "gopkg.in/check.v1"
-       git_client "gopkg.in/src-d/go-git.v4/plumbing/transport/client"
-       git_http "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
-)
-
-type GitMountSuite struct {
-       tmpdir string
-}
-
-var _ = check.Suite(&GitMountSuite{})
-
-func (s *GitMountSuite) SetUpTest(c *check.C) {
-       var err error
-       s.tmpdir, err = ioutil.TempDir("", "")
-       c.Assert(err, check.IsNil)
-       git_client.InstallProtocol("https", git_http.NewClient(arvados.InsecureHTTPClient))
-}
-
-func (s *GitMountSuite) TearDownTest(c *check.C) {
-       err := os.RemoveAll(s.tmpdir)
-       c.Check(err, check.IsNil)
-}
-
-// Commit fd3531f is crunch-run-tree-test
-func (s *GitMountSuite) TestExtractTree(c *check.C) {
-       gm := gitMount{
-               Path:   "/",
-               UUID:   arvadostest.Repository2UUID,
-               Commit: "fd3531f42995344f36c30b79f55f27b502f3d344",
-       }
-       ac := arvados.NewClientFromEnv()
-       err := gm.extractTree(ac, s.tmpdir, arvadostest.ActiveToken)
-       c.Check(err, check.IsNil)
-
-       fnm := filepath.Join(s.tmpdir, "dir1/dir2/file with mode 0644")
-       data, err := ioutil.ReadFile(fnm)
-       c.Check(err, check.IsNil)
-       c.Check(data, check.DeepEquals, []byte{0, 1, 2, 3})
-       fi, err := os.Stat(fnm)
-       c.Check(err, check.IsNil)
-       if err == nil {
-               c.Check(fi.Mode(), check.Equals, os.FileMode(0644))
-       }
-
-       fnm = filepath.Join(s.tmpdir, "dir1/dir2/file with mode 0755")
-       data, err = ioutil.ReadFile(fnm)
-       c.Check(err, check.IsNil)
-       c.Check(string(data), check.DeepEquals, "#!/bin/sh\nexec echo OK\n")
-       fi, err = os.Stat(fnm)
-       c.Check(err, check.IsNil)
-       if err == nil {
-               c.Check(fi.Mode(), check.Equals, os.FileMode(0755))
-       }
-
-       // Ensure there's no extra stuff like a ".git" dir
-       s.checkTmpdirContents(c, []string{"dir1"})
-
-       // Ensure tmpdir is world-readable and world-executable so the
-       // UID inside the container can use it.
-       fi, err = os.Stat(s.tmpdir)
-       c.Check(err, check.IsNil)
-       c.Check(fi.Mode()&os.ModePerm, check.Equals, os.FileMode(0755))
-}
-
-// Commit 5ebfab0 is not the tip of any branch or tag, but is
-// reachable in branch "crunch-run-non-tip-test".
-func (s *GitMountSuite) TestExtractNonTipCommit(c *check.C) {
-       gm := gitMount{
-               UUID:   arvadostest.Repository2UUID,
-               Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-       }
-       err := gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-       c.Check(err, check.IsNil)
-
-       fnm := filepath.Join(s.tmpdir, "file only on testbranch")
-       data, err := ioutil.ReadFile(fnm)
-       c.Check(err, check.IsNil)
-       c.Check(string(data), check.DeepEquals, "testfile\n")
-}
-
-func (s *GitMountSuite) TestNonexistentRepository(c *check.C) {
-       gm := gitMount{
-               Path:   "/",
-               UUID:   "zzzzz-s0uqq-nonexistentrepo",
-               Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-       }
-       err := gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-       c.Check(err, check.NotNil)
-       c.Check(err, check.ErrorMatches, ".*repository not found.*")
-
-       s.checkTmpdirContents(c, []string{})
-}
-
-func (s *GitMountSuite) TestNonexistentCommit(c *check.C) {
-       gm := gitMount{
-               Path:   "/",
-               UUID:   arvadostest.Repository2UUID,
-               Commit: "bb66b6bb6b6bbb6b6b6b66b6b6b6b6b6b6b6b66b",
-       }
-       err := gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-       c.Check(err, check.NotNil)
-       c.Check(err, check.ErrorMatches, ".*object not found.*")
-
-       s.checkTmpdirContents(c, []string{})
-}
-
-func (s *GitMountSuite) TestGitUrlDiscoveryFails(c *check.C) {
-       delete(discoveryMap, "gitUrl")
-       gm := gitMount{
-               Path:   "/",
-               UUID:   arvadostest.Repository2UUID,
-               Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-       }
-       err := gm.extractTree(&arvados.Client{}, s.tmpdir, arvadostest.ActiveToken)
-       c.Check(err, check.ErrorMatches, ".*error getting discovery doc.*")
-}
-
-func (s *GitMountSuite) TestInvalid(c *check.C) {
-       for _, trial := range []struct {
-               gm      gitMount
-               matcher string
-       }{
-               {
-                       gm: gitMount{
-                               Path:   "/",
-                               UUID:   arvadostest.Repository2UUID,
-                               Commit: "abc123",
-                       },
-                       matcher: ".*SHA1.*",
-               },
-               {
-                       gm: gitMount{
-                               Path:           "/",
-                               UUID:           arvadostest.Repository2UUID,
-                               RepositoryName: arvadostest.Repository2Name,
-                               Commit:         "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-                       },
-                       matcher: ".*repository_name.*",
-               },
-               {
-                       gm: gitMount{
-                               Path:   "/",
-                               GitURL: "https://localhost:0/" + arvadostest.Repository2Name + ".git",
-                               Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-                       },
-                       matcher: ".*git_url.*",
-               },
-               {
-                       gm: gitMount{
-                               Path:   "/dir1/",
-                               UUID:   arvadostest.Repository2UUID,
-                               Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-                       },
-                       matcher: ".*path.*",
-               },
-               {
-                       gm: gitMount{
-                               Path:   "/",
-                               Commit: "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-                       },
-                       matcher: ".*UUID.*",
-               },
-               {
-                       gm: gitMount{
-                               Path:     "/",
-                               UUID:     arvadostest.Repository2UUID,
-                               Commit:   "5ebfab0522851df01fec11ec55a6d0f4877b542e",
-                               Writable: true,
-                       },
-                       matcher: ".*writable.*",
-               },
-       } {
-               err := trial.gm.extractTree(arvados.NewClientFromEnv(), s.tmpdir, arvadostest.ActiveToken)
-               c.Check(err, check.NotNil)
-               s.checkTmpdirContents(c, []string{})
-
-               err = trial.gm.validate()
-               c.Check(err, check.ErrorMatches, trial.matcher)
-       }
-}
-
-func (s *GitMountSuite) checkTmpdirContents(c *check.C, expect []string) {
-       f, err := os.Open(s.tmpdir)
-       c.Check(err, check.IsNil)
-       names, err := f.Readdirnames(-1)
-       c.Check(err, check.IsNil)
-       c.Check(names, check.DeepEquals, expect)
-}
index 4f0100b2677f956b1af9dadcbd5b6082a1be0ab0..38c589f698118469f238cdb181832d48d4127f8e 100644 (file)
@@ -148,6 +148,7 @@ func (s *integrationSuite) setup(c *C) {
                "state":               s.cr.State,
                "command":             s.cr.Command,
                "output_path":         s.cr.OutputPath,
                "state":               s.cr.State,
                "command":             s.cr.Command,
                "output_path":         s.cr.OutputPath,
+               "output_glob":         s.cr.OutputGlob,
                "container_image":     s.cr.ContainerImage,
                "mounts":              s.cr.Mounts,
                "runtime_constraints": s.cr.RuntimeConstraints,
                "container_image":     s.cr.ContainerImage,
                "mounts":              s.cr.Mounts,
                "runtime_constraints": s.cr.RuntimeConstraints,
@@ -221,6 +222,8 @@ func (s *integrationSuite) TestRunTrivialContainerWithLocalKeepstore(c *C) {
                if trial.logConfig == "none" {
                        c.Check(logExists, Equals, false)
                } else {
                if trial.logConfig == "none" {
                        c.Check(logExists, Equals, false)
                } else {
+                       c.Check(log, Matches, `(?ms).*not running trash worker.*`)
+                       c.Check(log, Matches, `(?ms).*not running trash emptier.*`)
                        c.Check(log, trial.matchGetReq, `(?ms).*"reqMethod":"GET".*`)
                        c.Check(log, trial.matchPutReq, `(?ms).*"reqMethod":"PUT".*,"reqPath":"0e3bcff26d51c895a60ea0d4585e134d".*`)
                }
                        c.Check(log, trial.matchGetReq, `(?ms).*"reqMethod":"GET".*`)
                        c.Check(log, trial.matchPutReq, `(?ms).*"reqMethod":"PUT".*,"reqPath":"0e3bcff26d51c895a60ea0d4585e134d".*`)
                }
@@ -272,6 +275,19 @@ func (s *integrationSuite) TestRunTrivialContainerWithNoLocalKeepstore(c *C) {
        c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*loaded config file \Q`+os.Getenv("ARVADOS_CONFIG")+`\E\n.*`)
 }
 
        c.Check(s.logFiles["crunch-run.txt"], Matches, `(?ms).*loaded config file \Q`+os.Getenv("ARVADOS_CONFIG")+`\E\n.*`)
 }
 
+func (s *integrationSuite) TestRunTrivialContainerWithOutputGlob(c *C) {
+       s.cr.OutputGlob = []string{"js?n"}
+       s.testRunTrivialContainer(c)
+       fs, err := s.outputCollection.FileSystem(s.client, s.kc)
+       c.Assert(err, IsNil)
+       _, err = fs.Stat("json")
+       c.Check(err, IsNil)
+       _, err = fs.Stat("inputfile")
+       c.Check(err, Equals, os.ErrNotExist)
+       _, err = fs.Stat("emptydir")
+       c.Check(err, Equals, os.ErrNotExist)
+}
+
 func (s *integrationSuite) testRunTrivialContainer(c *C) {
        if err := exec.Command("which", s.engine).Run(); err != nil {
                c.Skip(fmt.Sprintf("%s: %s", s.engine, err))
 func (s *integrationSuite) testRunTrivialContainer(c *C) {
        if err := exec.Command("which", s.engine).Run(); err != nil {
                c.Skip(fmt.Sprintf("%s: %s", s.engine, err))
@@ -321,34 +337,37 @@ func (s *integrationSuite) testRunTrivialContainer(c *C) {
        var output arvados.Collection
        err = s.client.RequestAndDecode(&output, "GET", "arvados/v1/collections/"+s.cr.OutputUUID, nil, nil)
        c.Assert(err, IsNil)
        var output arvados.Collection
        err = s.client.RequestAndDecode(&output, "GET", "arvados/v1/collections/"+s.cr.OutputUUID, nil, nil)
        c.Assert(err, IsNil)
-       fs, err = output.FileSystem(s.client, s.kc)
-       c.Assert(err, IsNil)
-       if f, err := fs.Open("inputfile"); c.Check(err, IsNil) {
-               defer f.Close()
-               buf, err := ioutil.ReadAll(f)
-               c.Check(err, IsNil)
-               c.Check(string(buf), Equals, "inputdata")
-       }
-       if f, err := fs.Open("json"); c.Check(err, IsNil) {
-               defer f.Close()
-               buf, err := ioutil.ReadAll(f)
-               c.Check(err, IsNil)
-               c.Check(string(buf), Equals, `["foo",{"foo":"bar"},null]`)
-       }
-       if fi, err := fs.Stat("emptydir"); c.Check(err, IsNil) {
-               c.Check(fi.IsDir(), Equals, true)
-       }
-       if d, err := fs.Open("emptydir"); c.Check(err, IsNil) {
-               defer d.Close()
-               fis, err := d.Readdir(-1)
+       s.outputCollection = output
+
+       if len(s.cr.OutputGlob) == 0 {
+               fs, err = output.FileSystem(s.client, s.kc)
                c.Assert(err, IsNil)
                c.Assert(err, IsNil)
-               // crunch-run still saves a ".keep" file to preserve
-               // empty dirs even though that shouldn't be
-               // necessary. Ideally we would do:
-               // c.Check(fis, HasLen, 0)
-               for _, fi := range fis {
-                       c.Check(fi.Name(), Equals, ".keep")
+               if f, err := fs.Open("inputfile"); c.Check(err, IsNil) {
+                       defer f.Close()
+                       buf, err := ioutil.ReadAll(f)
+                       c.Check(err, IsNil)
+                       c.Check(string(buf), Equals, "inputdata")
+               }
+               if f, err := fs.Open("json"); c.Check(err, IsNil) {
+                       defer f.Close()
+                       buf, err := ioutil.ReadAll(f)
+                       c.Check(err, IsNil)
+                       c.Check(string(buf), Equals, `["foo",{"foo":"bar"},null]`)
+               }
+               if fi, err := fs.Stat("emptydir"); c.Check(err, IsNil) {
+                       c.Check(fi.IsDir(), Equals, true)
+               }
+               if d, err := fs.Open("emptydir"); c.Check(err, IsNil) {
+                       defer d.Close()
+                       fis, err := d.Readdir(-1)
+                       c.Assert(err, IsNil)
+                       // crunch-run still saves a ".keep" file to preserve
+                       // empty dirs even though that shouldn't be
+                       // necessary. Ideally we would do:
+                       // c.Check(fis, HasLen, 0)
+                       for _, fi := range fis {
+                               c.Check(fi.Name(), Equals, ".keep")
+                       }
                }
        }
                }
        }
-       s.outputCollection = output
 }
 }
index 42f165fd756b8027e7a9df880ac910db9fa0b2b5..ee3320c7c34b0ac0b2fa012ad8b8a5276ccdf176 100644 (file)
@@ -67,7 +67,7 @@ func (s *LoggingTestSuite) TestWriteLogs(c *C) {
 
        c.Check(api.Content[0]["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
        c.Check(api.Content[0]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext)
 
        c.Check(api.Content[0]["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
        c.Check(api.Content[0]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"], Equals, logtext)
-       c.Check(string(kc.Content), Equals, logtext)
+       s.checkWroteBlock(c, kc, "74561df9ae65ee9f35d5661d42454264+83", logtext)
 }
 
 func (s *LoggingTestSuite) TestWriteLogsLarge(c *C) {
 }
 
 func (s *LoggingTestSuite) TestWriteLogsLarge(c *C) {
@@ -224,7 +224,14 @@ func (s *LoggingTestSuite) testWriteLogsWithRateLimit(c *C, throttleParam string
        c.Check(api.Content[0]["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
        stderrLog := api.Content[0]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]
        c.Check(true, Equals, strings.Contains(stderrLog, expected))
        c.Check(api.Content[0]["log"].(arvadosclient.Dict)["event_type"], Equals, "crunch-run")
        stderrLog := api.Content[0]["log"].(arvadosclient.Dict)["properties"].(map[string]string)["text"]
        c.Check(true, Equals, strings.Contains(stderrLog, expected))
-       c.Check(string(kc.Content), Equals, logtext)
+       s.checkWroteBlock(c, kc, "74561df9ae65ee9f35d5661d42454264+83", logtext)
+}
+
+func (s *LoggingTestSuite) checkWroteBlock(c *C, kc *KeepTestClient, locator, expect string) {
+       buf := make([]byte, len([]byte(expect))+1)
+       n, err := kc.ReadAt(locator, buf, 0)
+       c.Check(err, IsNil)
+       c.Check(string(buf[:n]), Equals, expect)
 }
 
 type filterSuite struct{}
 }
 
 type filterSuite struct{}
index 30dd6ee83b4139ef9bb9078e6bc4ae851e6b6b31..0fd3b3eca2ceee767c0a6550e15dc3b7a8c8dd81 100644 (file)
@@ -10,6 +10,7 @@ import (
        "context"
        "crypto/sha256"
        _ "embed"
        "context"
        "crypto/sha256"
        _ "embed"
+       "encoding/json"
        "flag"
        "fmt"
        "io"
        "flag"
        "fmt"
        "io"
@@ -19,6 +20,7 @@ import (
        "net/url"
        "os"
        "os/exec"
        "net/url"
        "os"
        "os/exec"
+       "regexp"
        "strings"
        "time"
 
        "strings"
        "time"
 
@@ -457,6 +459,7 @@ func (diag *diagnoser) runtests() {
        }
        defer os.RemoveAll(tempdir)
 
        }
        defer os.RemoveAll(tempdir)
 
+       var imageSHA2 string
        var dockerImageData []byte
        if diag.dockerImage != "" || diag.priority < 1 {
                // We won't be using the self-built docker image, so
        var dockerImageData []byte
        if diag.dockerImage != "" || diag.priority < 1 {
                // We won't be using the self-built docker image, so
@@ -465,6 +468,14 @@ func (diag *diagnoser) runtests() {
                // upload/download, whether or not we're using it as a
                // docker image.
                dockerImageData = HelloWorldDockerImage
                // upload/download, whether or not we're using it as a
                // docker image.
                dockerImageData = HelloWorldDockerImage
+
+               if diag.priority > 0 {
+                       imageSHA2, err = getSHA2FromImageData(dockerImageData)
+                       if err != nil {
+                               diag.errorf("internal error/bug: %s", err)
+                               return
+                       }
+               }
        } else if selfbin, err := os.Readlink("/proc/self/exe"); err != nil {
                diag.errorf("readlink /proc/self/exe: %s", err)
                return
        } else if selfbin, err := os.Readlink("/proc/self/exe"); err != nil {
                diag.errorf("readlink /proc/self/exe: %s", err)
                return
@@ -499,7 +510,18 @@ func (diag *diagnoser) runtests() {
                }
                diag.infof("arvados-client version: %s", checkversion)
 
                }
                diag.infof("arvados-client version: %s", checkversion)
 
-               buf, err := exec.Command("docker", "save", tag).Output()
+               buf, err := exec.Command("docker", "inspect", "--format={{.Id}}", tag).Output()
+               if err != nil {
+                       diag.errorf("docker inspect --format={{.Id}} %s: %s", tag, err)
+                       return
+               }
+               imageSHA2 = min64HexDigits.FindString(string(buf))
+               if len(imageSHA2) != 64 {
+                       diag.errorf("docker inspect --format={{.Id}} output %q does not seem to contain sha256 digest", buf)
+                       return
+               }
+
+               buf, err = exec.Command("docker", "save", tag).Output()
                if err != nil {
                        diag.errorf("docker save %s: %s", tag, err)
                        return
                if err != nil {
                        diag.errorf("docker save %s: %s", tag, err)
                        return
@@ -508,29 +530,6 @@ func (diag *diagnoser) runtests() {
                dockerImageData = buf
        }
 
                dockerImageData = buf
        }
 
-       // Read image tarball to find image ID, so we can upload it as
-       // "sha256:{...}.tar"
-       var imageSHA2 string
-       {
-               tr := tar.NewReader(bytes.NewReader(dockerImageData))
-               for {
-                       hdr, err := tr.Next()
-                       if err == io.EOF {
-                               break
-                       }
-                       if err != nil {
-                               diag.errorf("internal error/bug: cannot read docker image tar file: %s", err)
-                               return
-                       }
-                       if s := strings.TrimSuffix(hdr.Name, ".json"); len(s) == 64 && s != hdr.Name {
-                               imageSHA2 = s
-                       }
-               }
-               if imageSHA2 == "" {
-                       diag.errorf("internal error/bug: cannot find {sha256}.json file in docker image tar file")
-                       return
-               }
-       }
        tarfilename := "sha256:" + imageSHA2 + ".tar"
 
        diag.dotest(100, "uploading file via webdav", func() error {
        tarfilename := "sha256:" + imageSHA2 + ".tar"
 
        diag.dotest(100, "uploading file via webdav", func() error {
@@ -810,3 +809,36 @@ func (diag *diagnoser) runtests() {
                return nil
        })
 }
                return nil
        })
 }
+
+func getSHA2FromImageData(dockerImageData []byte) (string, error) {
+       tr := tar.NewReader(bytes.NewReader(dockerImageData))
+       for {
+               hdr, err := tr.Next()
+               if err == io.EOF {
+                       return "", fmt.Errorf("cannot find manifest.json in docker image tar file")
+               }
+               if err != nil {
+                       return "", fmt.Errorf("cannot read docker image tar file: %s", err)
+               }
+               if hdr.Name != "manifest.json" {
+                       continue
+               }
+               var manifest []struct {
+                       Config string
+               }
+               err = json.NewDecoder(tr).Decode(&manifest)
+               if err != nil {
+                       return "", fmt.Errorf("cannot read manifest.json from docker image tar file: %s", err)
+               }
+               if len(manifest) == 0 {
+                       return "", fmt.Errorf("manifest.json is empty")
+               }
+               s := min64HexDigits.FindString(manifest[0].Config)
+               if len(s) != 64 {
+                       return "", fmt.Errorf("found manifest.json but .[0].Config %q does not seem to contain sha256 digest", manifest[0].Config)
+               }
+               return s, nil
+       }
+}
+
+var min64HexDigits = regexp.MustCompile(`[0-9a-f]{64,}`)
diff --git a/lib/diagnostics/docker_image_test.go b/lib/diagnostics/docker_image_test.go
new file mode 100644 (file)
index 0000000..ace4a2c
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package diagnostics
+
+import (
+       "testing"
+
+       . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) {
+       TestingT(t)
+}
+
+var _ = Suite(&suite{})
+
+type suite struct{}
+
+func (*suite) TestGetSHA2FromImageData(c *C) {
+       imageSHA2, err := getSHA2FromImageData(HelloWorldDockerImage)
+       c.Check(err, IsNil)
+       c.Check(imageSHA2, Matches, `[0-9a-f]{64}`)
+}
index 08312d738c15d86871b8626357e6cd6e308a140f..b27a14a5010d561961ca1719a58c98b25d298ab8 100644 (file)
@@ -35,7 +35,7 @@ const goversion = "1.20.6"
 
 const (
        defaultRubyVersion        = "3.2.2"
 
 const (
        defaultRubyVersion        = "3.2.2"
-       defaultBundlerVersion     = "2.2.19"
+       defaultBundlerVersion     = "~> 2.4.0"
        defaultSingularityVersion = "3.10.4"
        pjsversion                = "1.9.8"
        geckoversion              = "0.24.0"
        defaultSingularityVersion = "3.10.4"
        pjsversion                = "1.9.8"
        geckoversion              = "0.24.0"
@@ -123,7 +123,7 @@ func (inst *installCommand) RunCommand(prog string, args []string, stdin io.Read
                fmt.Fprintf(stderr, "invalid argument %q for -ruby-version\n", inst.RubyVersion)
                return 2
        }
                fmt.Fprintf(stderr, "invalid argument %q for -ruby-version\n", inst.RubyVersion)
                return 2
        }
-       if ok, _ := regexp.MatchString(`^\d`, inst.BundlerVersion); !ok {
+       if ok, _ := regexp.MatchString(`^ *(|~>|[<>!=]=) *\d`, inst.BundlerVersion); !ok {
                fmt.Fprintf(stderr, "invalid argument %q for -bundler-version\n", inst.BundlerVersion)
                return 2
        }
                fmt.Fprintf(stderr, "invalid argument %q for -bundler-version\n", inst.BundlerVersion)
                return 2
        }
@@ -351,7 +351,7 @@ make install
 if [[ "$rubyversion" > "3" ]]; then
   /var/lib/arvados/bin/gem update --no-document --system 3.4.21
 fi
 if [[ "$rubyversion" > "3" ]]; then
   /var/lib/arvados/bin/gem update --no-document --system 3.4.21
 fi
-/var/lib/arvados/bin/gem install bundler --no-document
+/var/lib/arvados/bin/gem install --conservative --no-document --version '`+inst.BundlerVersion+`' bundler
 `, stdout, stderr)
                if err != nil {
                        return 1
 `, stdout, stderr)
                if err != nil {
                        return 1
@@ -901,7 +901,6 @@ func prodpkgs(osv osversion) []string {
                "curl",
                "fuse",
                "git",
                "curl",
                "fuse",
                "git",
-               "gitolite3",
                "graphviz",
                "haveged",
                "libcurl3-gnutls",
                "graphviz",
                "haveged",
                "libcurl3-gnutls",
index d9b74f6a0630600680f8bc3516f97376e72924b7..12ffdd7af39b80d5dfc525d379cc6690ca0e683d 100644 (file)
@@ -230,10 +230,6 @@ func (initcmd *initCommand) RunCommand(prog string, args []string, stdin io.Read
       Keepbalance:
         InternalURLs:
           "http://0.0.0.0:9019/": {}
       Keepbalance:
         InternalURLs:
           "http://0.0.0.0:9019/": {}
-      GitHTTP:
-        InternalURLs:
-          "http://0.0.0.0:9005/": {}
-        ExternalURL: {{printf "%q" ( print "https://" .Domain ":4445/" ) }}
       DispatchCloud:
         InternalURLs:
           "http://0.0.0.0:9006/": {}
       DispatchCloud:
         InternalURLs:
           "http://0.0.0.0:9006/": {}
index 82e95fe0b4c38b8ab0e7cfa49ab6c17da386da00..9ed0acfb8f7151e805c7269dceacb696f425c07b 100644 (file)
@@ -80,9 +80,9 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
        loader := config.NewLoader(stdin, log)
        loader.SetupFlags(flags)
 
        loader := config.NewLoader(stdin, log)
        loader.SetupFlags(flags)
 
-       // prog is [keepstore, keep-web, git-httpd, ...]  but the
+       // prog is [keepstore, keep-web, ...]  but the
        // legacy config flags are [-legacy-keepstore-config,
        // legacy config flags are [-legacy-keepstore-config,
-       // -legacy-keepweb-config, -legacy-git-httpd-config, ...]
+       // -legacy-keepweb-config, ...]
        legacyFlag := "-legacy-" + strings.Replace(prog, "keep-", "keep", 1) + "-config"
        args = loader.MungeLegacyConfigArgs(log, args, legacyFlag)
 
        legacyFlag := "-legacy-" + strings.Replace(prog, "keep-", "keep", 1) + "-config"
        args = loader.MungeLegacyConfigArgs(log, args, legacyFlag)
 
index 7e13488758b10f5ec9f2ac5a61ec31dfaa1ba4f8..30d91b4094265d098b1344b2310470e184565794 100644 (file)
@@ -6,9 +6,6 @@
 # Implement cwl-runner interface for submitting and running work on Arvados, using
 # the Crunch containers API.
 
 # Implement cwl-runner interface for submitting and running work on Arvados, using
 # the Crunch containers API.
 
-from future.utils import viewitems
-from builtins import str
-
 import argparse
 import importlib.metadata
 import importlib.resources
 import argparse
 import importlib.metadata
 import importlib.resources
@@ -333,7 +330,7 @@ def main(args=sys.argv[1:],
 
     add_arv_hints()
 
 
     add_arv_hints()
 
-    for key, val in viewitems(cwltool.argparser.get_default_args()):
+    for key, val in cwltool.argparser.get_default_args().items():
         if not hasattr(arvargs, key):
             setattr(arvargs, key, val)
 
         if not hasattr(arvargs, key):
             setattr(arvargs, key, val)
 
index c3b914ba996a795623c5c9a1f155a2b11098b4d9..34b79d67b47aaf0a51ed70a3fd99e2d16457abde 100644 (file)
@@ -2,10 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-from builtins import str
-
 import logging
 import json
 import os
 import logging
 import json
 import os
index c592b83dc7739b142fb51ffff25a630a5494f5fc..dae68459bc3f6a00ea5c3bab3de03dbc64663a4e 100644 (file)
@@ -2,18 +2,15 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from past.builtins import basestring
-from future.utils import viewitems
-
 import os
 import json
 import copy
 import logging
 import urllib
 import os
 import json
 import copy
 import logging
 import urllib
-from io import StringIO
 import sys
 import re
 
 import sys
 import re
 
+from io import StringIO
 from typing import (MutableSequence, MutableMapping)
 
 from ruamel.yaml import YAML
 from typing import (MutableSequence, MutableMapping)
 
 from ruamel.yaml import YAML
@@ -588,7 +585,7 @@ class ArvadosWorkflowStep(WorkflowStep):
         runtimeContext = runtimeContext.copy()
         runtimeContext.toplevel = True  # Preserve behavior for #13365
 
         runtimeContext = runtimeContext.copy()
         runtimeContext.toplevel = True  # Preserve behavior for #13365
 
-        builder = make_builder({shortname(k): v for k,v in viewitems(joborder)}, self.hints, self.requirements,
+        builder = make_builder({shortname(k): v for k, v in joborder.items()}, self.hints, self.requirements,
                                runtimeContext, self.metadata)
         runtimeContext = set_cluster_target(self.tool, self.arvrunner, builder, runtimeContext)
         return super(ArvadosWorkflowStep, self).job(joborder, output_callback, runtimeContext)
                                runtimeContext, self.metadata)
         runtimeContext = set_cluster_target(self.tool, self.arvrunner, builder, runtimeContext)
         return super(ArvadosWorkflowStep, self).job(joborder, output_callback, runtimeContext)
@@ -655,7 +652,7 @@ class ArvadosWorkflow(Workflow):
                                 dyn = False
                                 for k in max_res_pars + sum_res_pars:
                                     if k in req:
                                 dyn = False
                                 for k in max_res_pars + sum_res_pars:
                                     if k in req:
-                                        if isinstance(req[k], basestring):
+                                        if isinstance(req[k], str):
                                             if item["id"] == "#main":
                                                 # only the top-level requirements/hints may contain expressions
                                                 self.dynamic_resource_req.append(req)
                                             if item["id"] == "#main":
                                                 # only the top-level requirements/hints may contain expressions
                                                 self.dynamic_resource_req.append(req)
index 5c1241976597286eba0edbda62690a431d349c0f..98c9f3a5df9f93cae890b25664a15f4296cb7bb8 100644 (file)
@@ -2,11 +2,10 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future.utils import viewvalues
-
 import re
 import re
-from cwltool.errors import WorkflowException
+
 from collections import deque
 from collections import deque
+from cwltool.errors import WorkflowException
 
 def done(self, record, tmpdir, outdir, keepdir):
     cols = [
 
 def done(self, record, tmpdir, outdir, keepdir):
     cols = [
index 432b380aabcd90c4c91ff3d7d72a9af29ab52823..240e014e5ab70b266e1a332e6685dee5396d2be1 100644 (file)
@@ -2,12 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import division
-from builtins import next
-from builtins import object
-from builtins import str
-from future.utils import viewvalues, viewitems
-
 import argparse
 import logging
 import os
 import argparse
 import logging
 import os
@@ -421,7 +415,7 @@ The 'jobs' API is no longer supported.
             if obj.get("class") == "InplaceUpdateRequirement":
                 if obj["inplaceUpdate"] and parentfield == "requirements":
                     raise SourceLine(obj, "class", UnsupportedRequirement).makeError("InplaceUpdateRequirement not supported for keep collections.")
             if obj.get("class") == "InplaceUpdateRequirement":
                 if obj["inplaceUpdate"] and parentfield == "requirements":
                     raise SourceLine(obj, "class", UnsupportedRequirement).makeError("InplaceUpdateRequirement not supported for keep collections.")
-            for k,v in viewitems(obj):
+            for k,v in obj.items():
                 self.check_features(v, parentfield=k)
         elif isinstance(obj, list):
             for i,v in enumerate(obj):
                 self.check_features(v, parentfield=k)
         elif isinstance(obj, list):
             for i,v in enumerate(obj):
index a5e9db0cfe41c27e8cf4a6aeaacc95820ba48aeb..a88380b468cfa3100725b79d248a370b8c136e38 100644 (file)
@@ -2,12 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-from builtins import object
-from builtins import str
-from future.utils import viewvalues
-
 import fnmatch
 import os
 import errno
 import fnmatch
 import os
 import errno
@@ -314,7 +308,7 @@ def collectionResolver(api_client, document_loader, uri, num_retries=4):
 
     if pipeline_template_uuid_pattern.match(uri):
         pt = api_client.pipeline_templates().get(uuid=uri).execute(num_retries=num_retries)
 
     if pipeline_template_uuid_pattern.match(uri):
         pt = api_client.pipeline_templates().get(uuid=uri).execute(num_retries=num_retries)
-        return u"keep:" + viewvalues(pt["components"])[0]["script_parameters"]["cwl:tool"]
+        return u"keep:" + next(pt["components"].values())["script_parameters"]["cwl:tool"]
 
     p = uri.split("/")
     if arvados.util.keep_locator_pattern.match(p[0]):
 
     p = uri.split("/")
     if arvados.util.keep_locator_pattern.match(p[0]):
index 448facf776823c68f5c706cc0ec1707460222cf7..ac6df543ad054dcc35f282a8a8510872da4bf36c 100644 (file)
@@ -2,12 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-from builtins import str
-from past.builtins import basestring
-from future.utils import viewitems
-
 import re
 import logging
 import uuid
 import re
 import logging
 import uuid
@@ -72,7 +66,7 @@ class ArvPathMapper(PathMapper):
 
         debug = logger.isEnabledFor(logging.DEBUG)
 
 
         debug = logger.isEnabledFor(logging.DEBUG)
 
-        if isinstance(src, basestring) and src.startswith("keep:"):
+        if isinstance(src, str) and src.startswith("keep:"):
             if collection_pdh_pattern.match(src):
                 self._pathmap[src] = MapperEnt(src, self.collection_pattern % urllib.parse.unquote(src[5:]), srcobj["class"], True)
 
             if collection_pdh_pattern.match(src):
                 self._pathmap[src] = MapperEnt(src, self.collection_pattern % urllib.parse.unquote(src[5:]), srcobj["class"], True)
 
@@ -346,7 +340,7 @@ class StagingPathMapper(PathMapper):
         # Overridden to maintain the use case of mapping by source (identifier) to
         # target regardless of how the map is structured interally.
         def getMapperEnt(src):
         # Overridden to maintain the use case of mapping by source (identifier) to
         # target regardless of how the map is structured interally.
         def getMapperEnt(src):
-            for k,v in viewitems(self._pathmap):
+            for k,v in self._pathmap.items():
                 if (v.type != "CreateFile" and v.resolved == src) or (v.type == "CreateFile" and k == src):
                     return v
 
                 if (v.type != "CreateFile" and v.resolved == src) or (v.type == "CreateFile" and k == src):
                     return v
 
@@ -365,7 +359,7 @@ class VwdPathMapper(StagingPathMapper):
         # with any secondary files.
         self.visitlisting(referenced_files, self.stagedir, basedir)
 
         # with any secondary files.
         self.visitlisting(referenced_files, self.stagedir, basedir)
 
-        for path, (ab, tgt, type, staged) in viewitems(self._pathmap):
+        for path, (ab, tgt, type, staged) in self._pathmap.items():
             if type in ("File", "Directory") and ab.startswith("keep:"):
                 self._pathmap[path] = MapperEnt("$(task.keep)/%s" % ab[5:], tgt, type, staged)
 
             if type in ("File", "Directory") and ab.startswith("keep:"):
                 self._pathmap[path] = MapperEnt("$(task.keep)/%s" % ab[5:], tgt, type, staged)
 
index cc3ea969df99cb000119cc60ffd5c29c28656d01..39f475fe8d30e6fda700e2f3c965577745b0638f 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import object
-
 import time
 import uuid
 
 import time
 import uuid
 
index 437aa39eb86dc7453fee161c53d85686fbe00f5c..259294a36e6ccbf87df87c6124eda124aef03d0b 100644 (file)
@@ -2,11 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-from future.utils import  viewvalues, viewitems
-from past.builtins import basestring
-
 import os
 import sys
 import re
 import os
 import sys
 import re
@@ -72,6 +67,7 @@ from . import done
 from . context import ArvRuntimeContext
 from .perf import Perf
 
 from . context import ArvRuntimeContext
 from .perf import Perf
 
+basestring = (bytes, str)
 logger = logging.getLogger('arvados.cwl-runner')
 metrics = logging.getLogger('arvados.cwl-runner.metrics')
 
 logger = logging.getLogger('arvados.cwl-runner')
 metrics = logging.getLogger('arvados.cwl-runner.metrics')
 
@@ -103,7 +99,7 @@ def find_defaults(d, op):
         if "default" in d:
             op(d)
         else:
         if "default" in d:
             op(d)
         else:
-            for i in viewvalues(d):
+            for i in d.values():
                 find_defaults(i, op)
 
 def make_builder(joborder, hints, requirements, runtimeContext, metadata):
                 find_defaults(i, op)
 
 def make_builder(joborder, hints, requirements, runtimeContext, metadata):
@@ -567,7 +563,7 @@ def packed_workflow(arvrunner, tool, merged_map, runtimeContext, git_info):
                   rewrite_out=rewrites,
                   loader=tool.doc_loader)
 
                   rewrite_out=rewrites,
                   loader=tool.doc_loader)
 
-    rewrite_to_orig = {v: k for k,v in viewitems(rewrites)}
+    rewrite_to_orig = {v: k for k,v in rewrites.items()}
 
     def visit(v, cur_id):
         if isinstance(v, dict):
 
     def visit(v, cur_id):
         if isinstance(v, dict):
index a78dbfcf2b23c1eb89b17bdd6812d8f42e078a2b..ca20de4c5813d4d0a9bf7dc6a1e74ce7ac68c43c 100644 (file)
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
+#
+# This file runs in one of three modes:
+#
+# 1. If the ARVADOS_BUILDING_VERSION environment variable is set, it writes
+#    _version.py and generates dependencies based on that value.
+# 2. If running from an arvados Git checkout, it writes _version.py
+#    and generates dependencies from Git.
+# 3. Otherwise, we expect this is source previously generated from Git, and
+#    it reads _version.py and generates dependencies from it.
 
 
-import subprocess
-import time
 import os
 import re
 import os
 import re
+import runpy
+import subprocess
 import sys
 
 import sys
 
-SETUP_DIR = os.path.dirname(os.path.abspath(__file__))
-VERSION_PATHS = {
-        SETUP_DIR,
-        os.path.abspath(os.path.join(SETUP_DIR, "../python")),
-        os.path.abspath(os.path.join(SETUP_DIR, "../../tools/crunchstat-summary")),
-        os.path.abspath(os.path.join(SETUP_DIR, "../../build/version-at-commit.sh"))
-        }
+from pathlib import Path
+
+# These maps explain the relationships between different Python modules in
+# the arvados repository. We use these to help generate setup.py.
+PACKAGE_DEPENDENCY_MAP = {
+    'arvados-cwl-runner': ['arvados-python-client', 'crunchstat_summary'],
+    'arvados-user-activity': ['arvados-python-client'],
+    'arvados_fuse': ['arvados-python-client'],
+    'crunchstat_summary': ['arvados-python-client'],
+}
+PACKAGE_MODULE_MAP = {
+    'arvados-cwl-runner': 'arvados_cwl',
+    'arvados-docker-cleaner': 'arvados_docker',
+    'arvados-python-client': 'arvados',
+    'arvados-user-activity': 'arvados_user_activity',
+    'arvados_fuse': 'arvados_fuse',
+    'crunchstat_summary': 'crunchstat_summary',
+}
+PACKAGE_SRCPATH_MAP = {
+    'arvados-cwl-runner': Path('sdk', 'cwl'),
+    'arvados-docker-cleaner': Path('services', 'dockercleaner'),
+    'arvados-python-client': Path('sdk', 'python'),
+    'arvados-user-activity': Path('tools', 'user-activity'),
+    'arvados_fuse': Path('services', 'fuse'),
+    'crunchstat_summary': Path('tools', 'crunchstat-summary'),
+}
+
+ENV_VERSION = os.environ.get("ARVADOS_BUILDING_VERSION")
+SETUP_DIR = Path(__file__).absolute().parent
+try:
+    REPO_PATH = Path(subprocess.check_output(
+        ['git', '-C', str(SETUP_DIR), 'rev-parse', '--show-toplevel'],
+        stderr=subprocess.DEVNULL,
+        text=True,
+    ).rstrip('\n'))
+except (subprocess.CalledProcessError, OSError):
+    REPO_PATH = None
+else:
+    # Verify this is the arvados monorepo
+    if all((REPO_PATH / path).exists() for path in PACKAGE_SRCPATH_MAP.values()):
+        PACKAGE_NAME, = (
+            pkg_name for pkg_name, path in PACKAGE_SRCPATH_MAP.items()
+            if (REPO_PATH / path) == SETUP_DIR
+        )
+        MODULE_NAME = PACKAGE_MODULE_MAP[PACKAGE_NAME]
+        VERSION_SCRIPT_PATH = Path(REPO_PATH, 'build', 'version-at-commit.sh')
+    else:
+        REPO_PATH = None
+if REPO_PATH is None:
+    (PACKAGE_NAME, MODULE_NAME), = (
+        (pkg_name, mod_name)
+        for pkg_name, mod_name in PACKAGE_MODULE_MAP.items()
+        if (SETUP_DIR / mod_name).is_dir()
+    )
+
+def git_log_output(path, *args):
+    return subprocess.check_output(
+        ['git', '-C', str(REPO_PATH),
+         'log', '--first-parent', '--max-count=1',
+         *args, str(path)],
+        text=True,
+    ).rstrip('\n')
 
 def choose_version_from():
 
 def choose_version_from():
-    ts = {}
-    for path in VERSION_PATHS:
-        ts[subprocess.check_output(
-            ['git', 'log', '--first-parent', '--max-count=1',
-             '--format=format:%ct', path]).strip()] = path
-
-    sorted_ts = sorted(ts.items())
-    getver = sorted_ts[-1][1]
-    print("Using "+getver+" for version number calculation of "+SETUP_DIR, file=sys.stderr)
+    ver_paths = [SETUP_DIR, VERSION_SCRIPT_PATH, *(
+        PACKAGE_SRCPATH_MAP[pkg]
+        for pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ())
+    )]
+    getver = max(ver_paths, key=lambda path: git_log_output(path, '--format=format:%ct'))
+    print(f"Using {getver} for version number calculation of {SETUP_DIR}", file=sys.stderr)
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
-    myhash = subprocess.check_output(['git', 'log', '-n1', '--first-parent',
-                                       '--format=%H', curdir]).strip()
-    myversion = subprocess.check_output([SETUP_DIR+'/../../build/version-at-commit.sh', myhash]).strip().decode()
-    return myversion
+    myhash = git_log_output(curdir, '--format=%H')
+    return subprocess.check_output(
+        [str(VERSION_SCRIPT_PATH), myhash],
+        text=True,
+    ).rstrip('\n')
 
 def save_version(setup_dir, module, v):
 
 def save_version(setup_dir, module, v):
-    v = v.replace("~dev", ".dev").replace("~rc", "rc")
-    with open(os.path.join(setup_dir, module, "_version.py"), 'wt') as fp:
-        return fp.write("__version__ = '%s'\n" % v)
+    with Path(setup_dir, module, '_version.py').open('w') as fp:
+        print(f"__version__ = {v!r}", file=fp)
 
 def read_version(setup_dir, module):
 
 def read_version(setup_dir, module):
-    with open(os.path.join(setup_dir, module, "_version.py"), 'rt') as fp:
-        return re.match("__version__ = '(.*)'$", fp.read()).groups()[0]
-
-def get_version(setup_dir, module):
-    env_version = os.environ.get("ARVADOS_BUILDING_VERSION")
+    file_vars = runpy.run_path(Path(setup_dir, module, '_version.py'))
+    return file_vars['__version__']
 
 
-    if env_version:
-        save_version(setup_dir, module, env_version)
+def get_version(setup_dir=SETUP_DIR, module=MODULE_NAME):
+    if ENV_VERSION:
+        version = ENV_VERSION
+    elif REPO_PATH is None:
+        return read_version(setup_dir, module)
     else:
     else:
-        try:
-            save_version(setup_dir, module, git_version_at_commit())
-        except (subprocess.CalledProcessError, OSError) as err:
-            print("ERROR: {0}".format(err), file=sys.stderr)
-            pass
+        version = git_version_at_commit()
+    version = version.replace("~dev", ".dev").replace("~rc", "rc")
+    save_version(setup_dir, module, version)
+    return version
 
 
-    return read_version(setup_dir, module)
+def iter_dependencies(version=None):
+    if version is None:
+        version = get_version()
+    # A packaged development release should be installed with other
+    # development packages built from the same source, but those
+    # dependencies may have earlier "dev" versions (read: less recent
+    # Git commit timestamps). This compatible version dependency
+    # expresses that as closely as possible. Allowing versions
+    # compatible with .dev0 allows any development release.
+    # Regular expression borrowed partially from
+    # <https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers-regex>
+    dep_ver, match_count = re.subn(r'\.dev(0|[1-9][0-9]*)$', '.dev0', version, 1)
+    dep_op = '~=' if match_count else '=='
+    for dep_pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ()):
+        yield f'{dep_pkg}{dep_op}{dep_ver}'
 
 # Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
 if __name__ == '__main__':
 
 # Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
 if __name__ == '__main__':
-    print(get_version(SETUP_DIR, "arvados_cwl"))
+    print(get_version())
diff --git a/sdk/cwl/pytest.ini b/sdk/cwl/pytest.ini
new file mode 120000 (symlink)
index 0000000..05a82db
--- /dev/null
@@ -0,0 +1 @@
+../../sdk/python/pytest.ini
\ No newline at end of file
index 043b52cb814067f573423044a88d34b823f72d20..5d8486f64fe4db3a30457b790beb7d1a1a37633b 100644 (file)
@@ -3,22 +3,14 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
 import os
 import sys
 
 from setuptools import setup, find_packages
 
 import os
 import sys
 
 from setuptools import setup, find_packages
 
-SETUP_DIR = os.path.dirname(__file__) or '.'
-README = os.path.join(SETUP_DIR, 'README.rst')
-
 import arvados_version
 import arvados_version
-version = arvados_version.get_version(SETUP_DIR, "arvados_cwl")
-if os.environ.get('ARVADOS_BUILDING_VERSION', False):
-    pysdk_dep = "=={}".format(version)
-else:
-    # On dev releases, arvados-python-client may have a different timestamp
-    pysdk_dep = "<={}".format(version)
+version = arvados_version.get_version()
+README = os.path.join(arvados_version.SETUP_DIR, 'README.rst')
 
 setup(name='arvados-cwl-runner',
       version=version,
 
 setup(name='arvados-cwl-runner',
       version=version,
@@ -36,14 +28,10 @@ setup(name='arvados-cwl-runner',
       # file to determine what version of cwltool and schema-salad to
       # build.
       install_requires=[
       # file to determine what version of cwltool and schema-salad to
       # build.
       install_requires=[
+          *arvados_version.iter_dependencies(version),
           'cwltool==3.1.20230601100705',
           'schema-salad==8.4.20230601112322',
           'cwltool==3.1.20230601100705',
           'schema-salad==8.4.20230601112322',
-          'arvados-python-client{}'.format(pysdk_dep),
-          'crunchstat-summary{}'.format(pysdk_dep),
           'ciso8601 >= 2.0.0',
           'ciso8601 >= 2.0.0',
-          'networkx < 2.6',
-          'msgpack==1.0.3',
-          'importlib-metadata<5',
           'setuptools>=40.3.0',
       ],
       data_files=[
           'setuptools>=40.3.0',
       ],
       data_files=[
@@ -54,8 +42,5 @@ setup(name='arvados-cwl-runner',
           'Programming Language :: Python :: 3',
       ],
       test_suite='tests',
           'Programming Language :: Python :: 3',
       ],
       test_suite='tests',
-      tests_require=[
-          'mock>=1.0,<4',
-      ],
       zip_safe=True,
 )
       zip_safe=True,
 )
index 1458772a3f65f22dfe494df7ddbe55d7ca308f2e..b3338939edd983850ec7d426fa32619176cc4d84 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
 import arvados
 import json
 
 import arvados
 import json
 
index 40bb843b2980877a0dbe10f18b41463c255609e5..6fe90813e7a720c9ba5c11d9650af34e49ee9cdd 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
 import arvados
 import json
 
 import arvados
 import json
 
index e45bd72642df7e735bdc0d5529286536bf536de8..43c20dc03d1e2dd0df46b5d4200b81b2c7cb7107 100644 (file)
@@ -2,5 +2,4 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
 print("Hello world")
 print("Hello world")
index 04e67b7dbd48aaefec075e77c2a8836d81cb2631..3c49b87a868cbbb5c7bbb6a89df8f6c5d94441f8 100644 (file)
@@ -2,13 +2,10 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import object
-
 import difflib
 import json
 import re
 
 import difflib
 import json
 import re
 
-
 class JsonDiffMatcher(object):
     """Raise AssertionError with a readable JSON diff when not __eq__().
 
 class JsonDiffMatcher(object):
     """Raise AssertionError with a readable JSON diff when not __eq__().
 
index b95b8eb67bbc4d83b357fcb209bccaf6ebf7dca4..885ee165b0ab248ae20f5b1bfdf84577ad335af0 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import str
-from builtins import object
-
 import arvados_cwl
 import arvados_cwl.context
 import arvados_cwl.util
 import arvados_cwl
 import arvados_cwl.context
 import arvados_cwl.util
@@ -12,7 +9,6 @@ import arvados_cwl.util
 import copy
 import arvados.config
 import logging
 import copy
 import arvados.config
 import logging
-import mock
 import unittest
 import os
 import functools
 import unittest
 import os
 import functools
@@ -25,6 +21,8 @@ from schema_salad.ref_resolver import Loader
 from schema_salad.sourceline import cmap
 import io
 
 from schema_salad.sourceline import cmap
 import io
 
+from unittest import mock
+
 from .matcher import JsonDiffMatcher, StripYAMLComments
 from .mock_discovery import get_rootDesc
 
 from .matcher import JsonDiffMatcher, StripYAMLComments
 from .mock_discovery import get_rootDesc
 
index 28a5915b11c44e531110a53b0df9b66d5e7dc256..8ad735fddc6659dbc855003a9a4a0c04a32d6cbc 100644 (file)
@@ -73,7 +73,7 @@ def check_contents(group, wf_uuid):
         raise Exception("Couldn't find collection containing expected "+expect_file)
 
 
         raise Exception("Couldn't find collection containing expected "+expect_file)
 
 
-def test_create():
+def check_create():
     group = api.groups().create(body={"group": {"name": "test-19070-project-1", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
     group = api.groups().create(body={"group": {"name": "test-19070-project-1", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
@@ -90,7 +90,7 @@ def test_create():
         api.groups().delete(uuid=group["uuid"]).execute()
 
 
         api.groups().delete(uuid=group["uuid"]).execute()
 
 
-def test_update():
+def check_update():
     group = api.groups().create(body={"group": {"name": "test-19070-project-2", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
     group = api.groups().create(body={"group": {"name": "test-19070-project-2", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
@@ -132,7 +132,7 @@ def test_update():
         api.groups().delete(uuid=group["uuid"]).execute()
 
 
         api.groups().delete(uuid=group["uuid"]).execute()
 
 
-def test_execute():
+def check_execute():
     group = api.groups().create(body={"group": {"name": "test-19070-project-3", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
     group = api.groups().create(body={"group": {"name": "test-19070-project-3", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
@@ -193,6 +193,6 @@ def test_execute():
         api.groups().delete(uuid=group["uuid"]).execute()
 
 if __name__ == '__main__':
         api.groups().delete(uuid=group["uuid"]).execute()
 
 if __name__ == '__main__':
-    test_create()
-    test_update()
-    test_execute()
+    check_create()
+    check_update()
+    check_execute()
index f83612a8b01186d822eb00728a76d31569408ced..c086f0e832f29526a4880908bf7176e5d9c39737 100644 (file)
@@ -3,13 +3,14 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
-import mock
 import sys
 import unittest
 import json
 import logging
 import os
 
 import sys
 import unittest
 import json
 import logging
 import os
 
+from unittest import mock
+
 import arvados
 import arvados.keep
 import arvados.collection
 import arvados
 import arvados.keep
 import arvados.collection
index dd1da0b524429bb2038fe56fab86b8f2abe7c6b7..eb39d801fe67dc91b0fd459e82871a93be50af4a 100644 (file)
@@ -2,17 +2,15 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-
 import functools
 import json
 import logging
 import functools
 import json
 import logging
-import mock
 import os
 import io
 import unittest
 
 import os
 import io
 import unittest
 
+from unittest import mock
+
 import arvados
 import arvados_cwl
 import arvados_cwl.executor
 import arvados
 import arvados_cwl
 import arvados_cwl.executor
index 194092db7a0461e4174d12df3cd88c5a32a3cafc..1a13fc7079ba50b2fabfcebc49beb8c091809437 100644 (file)
@@ -3,13 +3,14 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
-import mock
 import sys
 import unittest
 import json
 import logging
 import os
 
 import sys
 import unittest
 import json
 import logging
 import os
 
+from unittest import mock
+
 import arvados
 import arvados.keep
 import arvados.collection
 import arvados
 import arvados.keep
 import arvados.collection
index 3219eac989f5580658c20ae255fb38a179e720a0..0e829eeb92fe5ba2929101d498d1a83079172164 100644 (file)
@@ -7,7 +7,7 @@ import subprocess
 
 api = arvados.api()
 
 
 api = arvados.api()
 
-def test_execute():
+def check_execute():
     group = api.groups().create(body={"group": {"name": "test-17004-project", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
     group = api.groups().create(body={"group": {"name": "test-17004-project", "group_class": "project"}}, ensure_unique_name=True).execute()
     try:
         contents = api.groups().contents(uuid=group["uuid"]).execute()
@@ -34,4 +34,4 @@ def test_execute():
         api.groups().delete(uuid=group["uuid"]).execute()
 
 if __name__ == '__main__':
         api.groups().delete(uuid=group["uuid"]).execute()
 
 if __name__ == '__main__':
-    test_execute()
+    check_execute()
index c8bf1279511cd8591104af5b196b4938dd71eb88..5f5fffb46519adf7eb681e461c6991d051f25ddc 100644 (file)
@@ -2,12 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-from builtins import object
-from builtins import str
-from future.utils import viewvalues
-
 import copy
 import io
 import itertools
 import copy
 import io
 import itertools
@@ -15,23 +9,14 @@ import functools
 import hashlib
 import json
 import logging
 import hashlib
 import json
 import logging
-import mock
 import sys
 import unittest
 import cwltool.process
 import re
 import os
 
 import sys
 import unittest
 import cwltool.process
 import re
 import os
 
-from io import BytesIO
-
-# StringIO.StringIO and io.StringIO have different behavior write() is
-# called with both python2 (byte) strings and unicode strings
-# (specifically there's some logging in cwltool that causes trouble).
-# This isn't a problem on python3 because all string are unicode.
-if sys.version_info[0] < 3:
-    from StringIO import StringIO
-else:
-    from io import StringIO
+from io import BytesIO, StringIO
+from unittest import mock
 
 import arvados
 import arvados.collection
 
 import arvados
 import arvados.collection
@@ -142,7 +127,7 @@ def stubs(wfdetails=('submit_wf.cwl', None)):
                 return CollectionExecute(created_collections[uuid])
 
             def collection_getstub(created_collections, uuid):
                 return CollectionExecute(created_collections[uuid])
 
             def collection_getstub(created_collections, uuid):
-                for v in viewvalues(created_collections):
+                for v in created_collections.values():
                     if uuid in (v["uuid"], v["portable_data_hash"]):
                         return CollectionExecute(v)
 
                     if uuid in (v["uuid"], v["portable_data_hash"]):
                         return CollectionExecute(v)
 
index 05e5116d722fb75a59973cb4bfc0373999dff50d..bf53f8912e4446eff82011b397e9675807f31b2a 100644 (file)
@@ -3,7 +3,6 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
-import mock
 import sys
 import unittest
 import json
 import sys
 import unittest
 import json
@@ -11,6 +10,8 @@ import logging
 import os
 import threading
 
 import os
 import threading
 
+from unittest import mock
+
 from cwltool.task_queue import TaskQueue
 
 def success_task():
 from cwltool.task_queue import TaskQueue
 
 def success_task():
index 86a053ea484835980c791d4a728824919d01f78f..08bca55e3dc8aefba0963259759a4055df8a84d6 100644 (file)
@@ -3,13 +3,14 @@
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
 # SPDX-License-Identifier: Apache-2.0
 
 import functools
-import mock
 import sys
 import unittest
 import json
 import logging
 import os
 
 import sys
 import unittest
 import json
 import logging
 import os
 
+from unittest import mock
+
 import arvados
 import arvados.keep
 import arvados.collection
 import arvados
 import arvados.keep
 import arvados.collection
index bf3d6fe0ef3de8d46a1f372f39dbe0607fd8aef9..9e94ec42f90043eee3cc5aca6165c3b03ecf6204 100644 (file)
@@ -2,12 +2,11 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import bytes
-
-import unittest
-import mock
 import datetime
 import httplib2
 import datetime
 import httplib2
+import unittest
+
+from unittest import mock
 
 from arvados_cwl.util import *
 from arvados.errors import ApiError
 
 from arvados_cwl.util import *
 from arvados.errors import ApiError
index b4322a809320b7be5823296f6cb72a39d4273f24..8cc4d6fb911e8dcc90411167e03d1e99af60c873 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-from __future__ import division
-
 import arvados
 import sys
 import os
 import arvados
 import sys
 import os
index c3d0ea8aef676b3d3c57ce0bfbbcbe129b7689ac..dd1a6c4c3278c3a5306536314d171f2326fb26ba 100644 (file)
@@ -42,11 +42,6 @@ var (
        EndpointCollectionDelete                = APIEndpoint{"DELETE", "arvados/v1/collections/{uuid}", ""}
        EndpointCollectionTrash                 = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/trash", ""}
        EndpointCollectionUntrash               = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/untrash", ""}
        EndpointCollectionDelete                = APIEndpoint{"DELETE", "arvados/v1/collections/{uuid}", ""}
        EndpointCollectionTrash                 = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/trash", ""}
        EndpointCollectionUntrash               = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/untrash", ""}
-       EndpointSpecimenCreate                  = APIEndpoint{"POST", "arvados/v1/specimens", "specimen"}
-       EndpointSpecimenUpdate                  = APIEndpoint{"PATCH", "arvados/v1/specimens/{uuid}", "specimen"}
-       EndpointSpecimenGet                     = APIEndpoint{"GET", "arvados/v1/specimens/{uuid}", ""}
-       EndpointSpecimenList                    = APIEndpoint{"GET", "arvados/v1/specimens", ""}
-       EndpointSpecimenDelete                  = APIEndpoint{"DELETE", "arvados/v1/specimens/{uuid}", ""}
        EndpointContainerCreate                 = APIEndpoint{"POST", "arvados/v1/containers", "container"}
        EndpointContainerUpdate                 = APIEndpoint{"PATCH", "arvados/v1/containers/{uuid}", "container"}
        EndpointContainerPriorityUpdate         = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/update_priority", "container"}
        EndpointContainerCreate                 = APIEndpoint{"POST", "arvados/v1/containers", "container"}
        EndpointContainerUpdate                 = APIEndpoint{"PATCH", "arvados/v1/containers/{uuid}", "container"}
        EndpointContainerPriorityUpdate         = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/update_priority", "container"}
@@ -332,11 +327,6 @@ type API interface {
        LogGet(ctx context.Context, options GetOptions) (Log, error)
        LogList(ctx context.Context, options ListOptions) (LogList, error)
        LogDelete(ctx context.Context, options DeleteOptions) (Log, error)
        LogGet(ctx context.Context, options GetOptions) (Log, error)
        LogList(ctx context.Context, options ListOptions) (LogList, error)
        LogDelete(ctx context.Context, options DeleteOptions) (Log, error)
-       SpecimenCreate(ctx context.Context, options CreateOptions) (Specimen, error)
-       SpecimenUpdate(ctx context.Context, options UpdateOptions) (Specimen, error)
-       SpecimenGet(ctx context.Context, options GetOptions) (Specimen, error)
-       SpecimenList(ctx context.Context, options ListOptions) (SpecimenList, error)
-       SpecimenDelete(ctx context.Context, options DeleteOptions) (Specimen, error)
        SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error)
        UserCreate(ctx context.Context, options CreateOptions) (User, error)
        UserUpdate(ctx context.Context, options UpdateOptions) (User, error)
        SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error)
        UserCreate(ctx context.Context, options CreateOptions) (User, error)
        UserUpdate(ctx context.Context, options UpdateOptions) (User, error)
index 389fe4e4841b0006405aee66a697f607c414ce0d..1e9616c428064c6e5bdbf30c7a3b4d46d79956bf 100644 (file)
@@ -104,28 +104,57 @@ type CollectionList struct {
        Limit          int          `json:"limit"`
 }
 
        Limit          int          `json:"limit"`
 }
 
-var (
-       blkRe = regexp.MustCompile(`^ [0-9a-f]{32}\+\d+`)
-       tokRe = regexp.MustCompile(` ?[^ ]*`)
-)
-
 // PortableDataHash computes the portable data hash of the given
 // manifest.
 func PortableDataHash(mt string) string {
 // PortableDataHash computes the portable data hash of the given
 // manifest.
 func PortableDataHash(mt string) string {
+       // To calculate the PDH, we write the manifest to an md5 hash
+       // func, except we skip the "extra" part of block tokens that
+       // look like "abcdef0123456789abcdef0123456789+12345+extra".
+       //
+       // This code is simplified by the facts that (A) all block
+       // tokens -- even the first and last in a stream -- are
+       // preceded and followed by a space character; and (B) all
+       // non-block tokens either start with '.'  or contain ':'.
+       //
+       // A regexp-based approach (like the one this replaced) would
+       // be more readable, but very slow.
        h := md5.New()
        size := 0
        h := md5.New()
        size := 0
-       _ = tokRe.ReplaceAllFunc([]byte(mt), func(tok []byte) []byte {
-               if m := blkRe.Find(tok); m != nil {
-                       // write hash+size, ignore remaining block hints
-                       tok = m
+       todo := []byte(mt)
+       for len(todo) > 0 {
+               // sp is the end of the current token (note that if
+               // the current token is the last file token in a
+               // stream, we'll also include the \n and the dirname
+               // token on the next line, which is perfectly fine for
+               // our purposes).
+               sp := bytes.IndexByte(todo, ' ')
+               if sp < 0 {
+                       // Last token of the manifest, which is never
+                       // a block token.
+                       n, _ := h.Write(todo)
+                       size += n
+                       break
                }
                }
-               n, err := h.Write(tok)
-               if err != nil {
-                       panic(err)
+               if sp >= 34 && todo[32] == '+' && bytes.IndexByte(todo[:32], ':') == -1 && todo[0] != '.' {
+                       // todo[:sp] is a block token.
+                       sizeend := bytes.IndexByte(todo[33:sp], '+')
+                       if sizeend < 0 {
+                               // "hash+size"
+                               sizeend = sp
+                       } else {
+                               // "hash+size+extra"
+                               sizeend += 33
+                       }
+                       n, _ := h.Write(todo[:sizeend])
+                       h.Write([]byte{' '})
+                       size += n + 1
+               } else {
+                       // todo[:sp] is not a block token.
+                       n, _ := h.Write(todo[:sp+1])
+                       size += n
                }
                }
-               size += n
-               return nil
-       })
+               todo = todo[sp+1:]
+       }
        return fmt.Sprintf("%x+%d", h.Sum(nil), size)
 }
 
        return fmt.Sprintf("%x+%d", h.Sum(nil), size)
 }
 
index 698ee20d8c6bcc58119a02e0330f19ca0e7a64ee..6725611fd0dbd8bd8905c68f1cf3e91e585c7fe0 100644 (file)
@@ -159,11 +159,7 @@ type Cluster struct {
                KeepproxyPermission UploadDownloadRolePermissions
                WebDAVPermission    UploadDownloadRolePermissions
                WebDAVLogEvents     bool
                KeepproxyPermission UploadDownloadRolePermissions
                WebDAVPermission    UploadDownloadRolePermissions
                WebDAVLogEvents     bool
-       }
-       Git struct {
-               GitCommand   string
-               GitoliteHome string
-               Repositories string
+               WebDAVOutputBuffer  ByteSize
        }
        Login struct {
                LDAP struct {
        }
        Login struct {
                LDAP struct {
@@ -247,7 +243,6 @@ type Cluster struct {
                AutoAdminFirstUser                    bool
                AutoAdminUserWithEmail                string
                AutoSetupNewUsers                     bool
                AutoAdminFirstUser                    bool
                AutoAdminUserWithEmail                string
                AutoSetupNewUsers                     bool
-               AutoSetupNewUsersWithRepository       bool
                AutoSetupNewUsersWithVmUUID           string
                AutoSetupUsernameBlacklist            StringSet
                EmailSubjectPrefix                    string
                AutoSetupNewUsersWithVmUUID           string
                AutoSetupUsernameBlacklist            StringSet
                EmailSubjectPrefix                    string
@@ -355,8 +350,6 @@ type Services struct {
        DispatchCloud  Service
        DispatchLSF    Service
        DispatchSLURM  Service
        DispatchCloud  Service
        DispatchLSF    Service
        DispatchSLURM  Service
-       GitHTTP        Service
-       GitSSH         Service
        Health         Service
        Keepbalance    Service
        Keepproxy      Service
        Health         Service
        Keepbalance    Service
        Keepproxy      Service
@@ -510,8 +503,7 @@ type ContainersConfig struct {
        LocalKeepLogsToContainerLog   string
 
        JobsAPI struct {
        LocalKeepLogsToContainerLog   string
 
        JobsAPI struct {
-               Enable         string
-               GitInternalDir string
+               Enable string
        }
        Logging struct {
                MaxAge                       Duration
        }
        Logging struct {
                MaxAge                       Duration
@@ -534,15 +526,6 @@ type ContainersConfig struct {
                PrioritySpread             int64
                SbatchArgumentsList        []string
                SbatchEnvironmentVariables map[string]string
                PrioritySpread             int64
                SbatchArgumentsList        []string
                SbatchEnvironmentVariables map[string]string
-               Managed                    struct {
-                       DNSServerConfDir       string
-                       DNSServerConfTemplate  string
-                       DNSServerReloadCommand string
-                       DNSServerUpdateCommand string
-                       ComputeNodeDomain      string
-                       ComputeNodeNameservers StringSet
-                       AssignNodeHostname     string
-               }
        }
        LSF struct {
                BsubSudoUser       string
        }
        LSF struct {
                BsubSudoUser       string
@@ -669,7 +652,6 @@ const (
        ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
        ServiceNameDispatchLSF   ServiceName = "arvados-dispatch-lsf"
        ServiceNameDispatchSLURM ServiceName = "crunch-dispatch-slurm"
        ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
        ServiceNameDispatchLSF   ServiceName = "arvados-dispatch-lsf"
        ServiceNameDispatchSLURM ServiceName = "crunch-dispatch-slurm"
-       ServiceNameGitHTTP       ServiceName = "arvados-git-httpd"
        ServiceNameHealth        ServiceName = "arvados-health"
        ServiceNameKeepbalance   ServiceName = "keep-balance"
        ServiceNameKeepproxy     ServiceName = "keepproxy"
        ServiceNameHealth        ServiceName = "arvados-health"
        ServiceNameKeepbalance   ServiceName = "keep-balance"
        ServiceNameKeepproxy     ServiceName = "keepproxy"
@@ -689,7 +671,6 @@ func (svcs Services) Map() map[ServiceName]Service {
                ServiceNameDispatchCloud: svcs.DispatchCloud,
                ServiceNameDispatchLSF:   svcs.DispatchLSF,
                ServiceNameDispatchSLURM: svcs.DispatchSLURM,
                ServiceNameDispatchCloud: svcs.DispatchCloud,
                ServiceNameDispatchLSF:   svcs.DispatchLSF,
                ServiceNameDispatchSLURM: svcs.DispatchSLURM,
-               ServiceNameGitHTTP:       svcs.GitHTTP,
                ServiceNameHealth:        svcs.Health,
                ServiceNameKeepbalance:   svcs.Keepbalance,
                ServiceNameKeepproxy:     svcs.Keepproxy,
                ServiceNameHealth:        svcs.Health,
                ServiceNameKeepbalance:   svcs.Keepbalance,
                ServiceNameKeepproxy:     svcs.Keepproxy,
index 91c8fbfe2936d972b8c5f196467072a9d7715b84..70038a18f79a82dca29b3dc937f2edfcc2fbb12a 100644 (file)
@@ -23,6 +23,7 @@ type Container struct {
        Mounts                    map[string]Mount       `json:"mounts"`
        Output                    string                 `json:"output"`
        OutputPath                string                 `json:"output_path"`
        Mounts                    map[string]Mount       `json:"mounts"`
        Output                    string                 `json:"output"`
        OutputPath                string                 `json:"output_path"`
+       OutputGlob                []string               `json:"output_glob"`
        Priority                  int64                  `json:"priority"`
        RuntimeConstraints        RuntimeConstraints     `json:"runtime_constraints"`
        State                     ContainerState         `json:"state"`
        Priority                  int64                  `json:"priority"`
        RuntimeConstraints        RuntimeConstraints     `json:"runtime_constraints"`
        State                     ContainerState         `json:"state"`
@@ -68,6 +69,7 @@ type ContainerRequest struct {
        Cwd                     string                 `json:"cwd"`
        Command                 []string               `json:"command"`
        OutputPath              string                 `json:"output_path"`
        Cwd                     string                 `json:"cwd"`
        Command                 []string               `json:"command"`
        OutputPath              string                 `json:"output_path"`
+       OutputGlob              []string               `json:"output_glob"`
        OutputName              string                 `json:"output_name"`
        OutputTTL               int                    `json:"output_ttl"`
        Priority                int                    `json:"priority"`
        OutputName              string                 `json:"output_name"`
        OutputTTL               int                    `json:"output_ttl"`
        Priority                int                    `json:"priority"`
@@ -94,9 +96,6 @@ type Mount struct {
        Content           interface{} `json:"content"`
        ExcludeFromOutput bool        `json:"exclude_from_output"`
        Capacity          int64       `json:"capacity"`
        Content           interface{} `json:"content"`
        ExcludeFromOutput bool        `json:"exclude_from_output"`
        Capacity          int64       `json:"capacity"`
-       Commit            string      `json:"commit"`          // only if kind=="git_tree"
-       RepositoryName    string      `json:"repository_name"` // only if kind=="git_tree"
-       GitURL            string      `json:"git_url"`         // only if kind=="git_tree"
 }
 
 type CUDARuntimeConstraints struct {
 }
 
 type CUDARuntimeConstraints struct {
index 052cc1aa37581aca2351200c5444304ce912571c..101fade74b1045abfd644de6abaf56ed4848724a 100644 (file)
@@ -1358,6 +1358,10 @@ func (dn *dirnode) loadManifest(txt string) error {
        }
        streams = streams[:len(streams)-1]
        segments := []storedSegment{}
        }
        streams = streams[:len(streams)-1]
        segments := []storedSegment{}
+       // streamoffset[n] is the position in the stream of the nth
+       // block, i.e., âˆ‘ segments[j].size âˆ€ 0≤j<n. We ensure
+       // len(streamoffset) == len(segments) + 1.
+       streamoffset := []int64{0}
        // To reduce allocs, we reuse a single "pathparts" slice
        // (pre-split on "/" separators) for the duration of this
        // func.
        // To reduce allocs, we reuse a single "pathparts" slice
        // (pre-split on "/" separators) for the duration of this
        // func.
@@ -1385,10 +1389,11 @@ func (dn *dirnode) loadManifest(txt string) error {
        }
        for i, stream := range streams {
                lineno := i + 1
        }
        for i, stream := range streams {
                lineno := i + 1
+               fnodeCache := make(map[string]*filenode)
                var anyFileTokens bool
                var anyFileTokens bool
-               var pos int64
                var segIdx int
                segments = segments[:0]
                var segIdx int
                segments = segments[:0]
+               streamoffset = streamoffset[:1]
                pathparts = nil
                streamparts := 0
                for i, token := range bytes.Split(stream, []byte{' '}) {
                pathparts = nil
                streamparts := 0
                for i, token := range bytes.Split(stream, []byte{' '}) {
@@ -1408,6 +1413,7 @@ func (dn *dirnode) loadManifest(txt string) error {
                                if err != nil || length < 0 {
                                        return fmt.Errorf("line %d: bad locator %q", lineno, token)
                                }
                                if err != nil || length < 0 {
                                        return fmt.Errorf("line %d: bad locator %q", lineno, token)
                                }
+                               streamoffset = append(streamoffset, streamoffset[len(segments)]+int64(length))
                                segments = append(segments, storedSegment{
                                        locator: string(token),
                                        size:    int(length),
                                segments = append(segments, storedSegment{
                                        locator: string(token),
                                        size:    int(length),
@@ -1431,49 +1437,64 @@ func (dn *dirnode) loadManifest(txt string) error {
                        if err != nil || length < 0 {
                                return fmt.Errorf("line %d: bad file segment %q", lineno, token)
                        }
                        if err != nil || length < 0 {
                                return fmt.Errorf("line %d: bad file segment %q", lineno, token)
                        }
-                       if !bytes.ContainsAny(toks[2], `\/`) {
-                               // optimization for a common case
-                               pathparts = append(pathparts[:streamparts], string(toks[2]))
-                       } else {
-                               pathparts = append(pathparts[:streamparts], strings.Split(manifestUnescape(string(toks[2])), "/")...)
+                       fnode, cached := fnodeCache[string(toks[2])]
+                       if !cached {
+                               if !bytes.ContainsAny(toks[2], `\/`) {
+                                       // optimization for a common case
+                                       pathparts = append(pathparts[:streamparts], string(toks[2]))
+                               } else {
+                                       pathparts = append(pathparts[:streamparts], strings.Split(manifestUnescape(string(toks[2])), "/")...)
+                               }
+                               fnode, err = dn.createFileAndParents(pathparts)
+                               if err != nil {
+                                       return fmt.Errorf("line %d: cannot use name %q with length %d: %s", lineno, toks[2], length, err)
+                               }
+                               fnodeCache[string(toks[2])] = fnode
                        }
                        }
-                       fnode, err := dn.createFileAndParents(pathparts)
-                       if fnode == nil && err == nil && length == 0 {
+                       if fnode == nil {
+                               // name matches an existing directory
+                               if length != 0 {
+                                       return fmt.Errorf("line %d: cannot use name %q with length %d: is a directory", lineno, toks[2], length)
+                               }
                                // Special case: an empty file used as
                                // a marker to preserve an otherwise
                                // empty directory in a manifest.
                                continue
                        }
                                // Special case: an empty file used as
                                // a marker to preserve an otherwise
                                // empty directory in a manifest.
                                continue
                        }
-                       if err != nil || (fnode == nil && length != 0) {
-                               return fmt.Errorf("line %d: cannot use name %q with length %d: %s", lineno, toks[2], length, err)
-                       }
                        // Map the stream offset/range coordinates to
                        // block/offset/range coordinates and add
                        // corresponding storedSegments to the filenode
                        // Map the stream offset/range coordinates to
                        // block/offset/range coordinates and add
                        // corresponding storedSegments to the filenode
-                       if pos > offset {
-                               // Can't continue where we left off.
-                               // TODO: binary search instead of
-                               // rewinding all the way (but this
-                               // situation might be rare anyway)
-                               segIdx, pos = 0, 0
+                       if segIdx < len(segments) && streamoffset[segIdx] <= offset && streamoffset[segIdx+1] > offset {
+                               // common case with an easy
+                               // optimization: start where the
+                               // previous segment ended
+                       } else if guess := int(offset >> 26); guess >= 0 && guess < len(segments) && streamoffset[guess] <= offset && streamoffset[guess+1] > offset {
+                               // another common case with an easy
+                               // optimization: all blocks are 64 MiB
+                               // (or close enough)
+                               segIdx = guess
+                       } else {
+                               // general case
+                               segIdx = sort.Search(len(segments), func(i int) bool {
+                                       return streamoffset[i+1] > offset
+                               })
                        }
                        for ; segIdx < len(segments); segIdx++ {
                        }
                        for ; segIdx < len(segments); segIdx++ {
-                               seg := segments[segIdx]
-                               next := pos + int64(seg.Len())
-                               if next <= offset || seg.Len() == 0 {
-                                       pos = next
-                                       continue
-                               }
-                               if pos >= offset+length {
+                               blkStart := streamoffset[segIdx]
+                               if blkStart >= offset+length {
                                        break
                                }
                                        break
                                }
+                               seg := &segments[segIdx]
+                               if seg.size == 0 {
+                                       continue
+                               }
                                var blkOff int
                                var blkOff int
-                               if pos < offset {
-                                       blkOff = int(offset - pos)
+                               if blkStart < offset {
+                                       blkOff = int(offset - blkStart)
                                }
                                }
-                               blkLen := seg.Len() - blkOff
-                               if pos+int64(blkOff+blkLen) > offset+length {
-                                       blkLen = int(offset + length - pos - int64(blkOff))
+                               blkLen := seg.size - blkOff
+                               if blkStart+int64(seg.size) > offset+length {
+                                       blkLen = int(offset + length - blkStart - int64(blkOff))
                                }
                                fnode.appendSegment(storedSegment{
                                        kc:      dn.fs,
                                }
                                fnode.appendSegment(storedSegment{
                                        kc:      dn.fs,
@@ -1482,14 +1503,9 @@ func (dn *dirnode) loadManifest(txt string) error {
                                        offset:  blkOff,
                                        length:  blkLen,
                                })
                                        offset:  blkOff,
                                        length:  blkLen,
                                })
-                               if next > offset+length {
-                                       break
-                               } else {
-                                       pos = next
-                               }
                        }
                        }
-                       if segIdx == len(segments) && pos < offset+length {
-                               return fmt.Errorf("line %d: invalid segment in %d-byte stream: %q", lineno, pos, token)
+                       if segIdx == len(segments) && streamoffset[segIdx] < offset+length {
+                               return fmt.Errorf("line %d: invalid segment in %d-byte stream: %q", lineno, streamoffset[segIdx], token)
                        }
                }
                if !anyFileTokens {
                        }
                }
                if !anyFileTokens {
index a29371b76c2aa29790b82cd8d8e1e38603dcf991..b57f9aa30f7bbd6c6ad6c62693ffb4e9947a566d 100644 (file)
@@ -1639,29 +1639,71 @@ type CollectionFSUnitSuite struct{}
 var _ = check.Suite(&CollectionFSUnitSuite{})
 
 // expect ~2 seconds to load a manifest with 256K files
 var _ = check.Suite(&CollectionFSUnitSuite{})
 
 // expect ~2 seconds to load a manifest with 256K files
-func (s *CollectionFSUnitSuite) TestLargeManifest(c *check.C) {
+func (s *CollectionFSUnitSuite) TestLargeManifest_ManyFiles(c *check.C) {
        if testing.Short() {
                c.Skip("slow")
        }
        if testing.Short() {
                c.Skip("slow")
        }
+       s.testLargeManifest(c, 512, 512, 1, 0)
+}
 
 
-       const (
-               dirCount  = 512
-               fileCount = 512
-       )
+func (s *CollectionFSUnitSuite) TestLargeManifest_LargeFiles(c *check.C) {
+       if testing.Short() {
+               c.Skip("slow")
+       }
+       s.testLargeManifest(c, 1, 800, 1000, 0)
+}
 
 
+func (s *CollectionFSUnitSuite) TestLargeManifest_InterleavedFiles(c *check.C) {
+       if testing.Short() {
+               c.Skip("slow")
+       }
+       // Timing figures here are from a dev host, (0)->(1)->(2)->(3)
+       // (0) no optimizations (main branch commit ea697fb1e8)
+       // (1) resolve streampos->blkidx with binary search
+       // (2) ...and rewrite PortableDataHash() without regexp
+       // (3) ...and use fnodeCache in loadManifest
+       s.testLargeManifest(c, 1, 800, 100, 4<<20) // 127s    -> 12s  -> 2.5s -> 1.5s
+       s.testLargeManifest(c, 1, 50, 1000, 4<<20) // 44s     -> 10s  -> 1.5s -> 0.8s
+       s.testLargeManifest(c, 1, 200, 100, 4<<20) // 13s     -> 4s   -> 0.6s -> 0.3s
+       s.testLargeManifest(c, 1, 200, 150, 4<<20) // 26s     -> 4s   -> 1s   -> 0.5s
+       s.testLargeManifest(c, 1, 200, 200, 4<<20) // 38s     -> 6s   -> 1.3s -> 0.7s
+       s.testLargeManifest(c, 1, 200, 225, 4<<20) // 46s     -> 7s   -> 1.5s -> 1s
+       s.testLargeManifest(c, 1, 400, 400, 4<<20) // 477s    -> 24s  -> 5s   -> 3s
+       // s.testLargeManifest(c, 1, 800, 1000, 4<<20) // timeout -> 186s -> 28s  -> 17s
+}
+
+func (s *CollectionFSUnitSuite) testLargeManifest(c *check.C, dirCount, filesPerDir, blocksPerFile, interleaveChunk int) {
+       t0 := time.Now()
+       const blksize = 1 << 26
+       c.Logf("%s building manifest with dirCount=%d filesPerDir=%d blocksPerFile=%d", time.Now(), dirCount, filesPerDir, blocksPerFile)
        mb := bytes.NewBuffer(make([]byte, 0, 40000000))
        mb := bytes.NewBuffer(make([]byte, 0, 40000000))
+       blkid := 0
        for i := 0; i < dirCount; i++ {
                fmt.Fprintf(mb, "./dir%d", i)
        for i := 0; i < dirCount; i++ {
                fmt.Fprintf(mb, "./dir%d", i)
-               for j := 0; j <= fileCount; j++ {
-                       fmt.Fprintf(mb, " %032x+42+A%040x@%08x", j, j, j)
+               for j := 0; j < filesPerDir; j++ {
+                       for k := 0; k < blocksPerFile; k++ {
+                               blkid++
+                               fmt.Fprintf(mb, " %032x+%d+A%040x@%08x", blkid, blksize, blkid, blkid)
+                       }
                }
                }
-               for j := 0; j < fileCount; j++ {
-                       fmt.Fprintf(mb, " %d:%d:dir%d/file%d", j*42+21, 42, j, j)
+               for j := 0; j < filesPerDir; j++ {
+                       if interleaveChunk == 0 {
+                               fmt.Fprintf(mb, " %d:%d:dir%d/file%d", (filesPerDir-j-1)*blocksPerFile*blksize, blocksPerFile*blksize, j, j)
+                               continue
+                       }
+                       for todo := int64(blocksPerFile) * int64(blksize); todo > 0; todo -= int64(interleaveChunk) {
+                               size := int64(interleaveChunk)
+                               if size > todo {
+                                       size = todo
+                               }
+                               offset := rand.Int63n(int64(blocksPerFile)*int64(blksize)*int64(filesPerDir) - size)
+                               fmt.Fprintf(mb, " %d:%d:dir%d/file%d", offset, size, j, j)
+                       }
                }
                mb.Write([]byte{'\n'})
        }
        coll := Collection{ManifestText: mb.String()}
                }
                mb.Write([]byte{'\n'})
        }
        coll := Collection{ManifestText: mb.String()}
-       c.Logf("%s built", time.Now())
+       c.Logf("%s built manifest size=%d", time.Now(), mb.Len())
 
        var memstats runtime.MemStats
        runtime.ReadMemStats(&memstats)
 
        var memstats runtime.MemStats
        runtime.ReadMemStats(&memstats)
@@ -1670,17 +1712,28 @@ func (s *CollectionFSUnitSuite) TestLargeManifest(c *check.C) {
        f, err := coll.FileSystem(NewClientFromEnv(), &keepClientStub{})
        c.Check(err, check.IsNil)
        c.Logf("%s loaded", time.Now())
        f, err := coll.FileSystem(NewClientFromEnv(), &keepClientStub{})
        c.Check(err, check.IsNil)
        c.Logf("%s loaded", time.Now())
-       c.Check(f.Size(), check.Equals, int64(42*dirCount*fileCount))
+       c.Check(f.Size(), check.Equals, int64(dirCount*filesPerDir*blocksPerFile*blksize))
 
 
+       // Stat() and OpenFile() each file. This mimics the behavior
+       // of webdav propfind, which opens each file even when just
+       // listing directory entries.
        for i := 0; i < dirCount; i++ {
        for i := 0; i < dirCount; i++ {
-               for j := 0; j < fileCount; j++ {
-                       f.Stat(fmt.Sprintf("./dir%d/dir%d/file%d", i, j, j))
+               for j := 0; j < filesPerDir; j++ {
+                       fnm := fmt.Sprintf("./dir%d/dir%d/file%d", i, j, j)
+                       fi, err := f.Stat(fnm)
+                       c.Assert(err, check.IsNil)
+                       c.Check(fi.IsDir(), check.Equals, false)
+                       f, err := f.OpenFile(fnm, os.O_RDONLY, 0)
+                       c.Assert(err, check.IsNil)
+                       f.Close()
                }
        }
                }
        }
-       c.Logf("%s Stat() x %d", time.Now(), dirCount*fileCount)
+       c.Logf("%s OpenFile() x %d", time.Now(), dirCount*filesPerDir)
 
        runtime.ReadMemStats(&memstats)
        c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
 
        runtime.ReadMemStats(&memstats)
        c.Logf("%s Alloc=%d Sys=%d", time.Now(), memstats.Alloc, memstats.Sys)
+       c.Logf("%s MemorySize=%d", time.Now(), f.MemorySize())
+       c.Logf("%s ... test duration %s", time.Now(), time.Now().Sub(t0))
 }
 
 // Gocheck boilerplate
 }
 
 // Gocheck boilerplate
diff --git a/sdk/go/arvados/specimen.go b/sdk/go/arvados/specimen.go
deleted file mode 100644 (file)
index b561fb2..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: Apache-2.0
-
-package arvados
-
-import "time"
-
-type Specimen struct {
-       UUID                 string                 `json:"uuid"`
-       OwnerUUID            string                 `json:"owner_uuid"`
-       CreatedAt            time.Time              `json:"created_at"`
-       ModifiedAt           time.Time              `json:"modified_at"`
-       ModifiedByClientUUID string                 `json:"modified_by_client_uuid"`
-       ModifiedByUserUUID   string                 `json:"modified_by_user_uuid"`
-       Properties           map[string]interface{} `json:"properties"`
-}
-
-type SpecimenList struct {
-       Items          []Specimen `json:"items"`
-       ItemsAvailable int        `json:"items_available"`
-       Offset         int        `json:"offset"`
-       Limit          int        `json:"limit"`
-}
index bb7867aef7e35d283c5e47adb68873492bda609c..4272f0f759ff59403edba05fe553fab95dc2e54e 100644 (file)
@@ -13,8 +13,8 @@ import (
 // A ClientPool is a pool of ArvadosClients. This is useful for
 // applications that make API calls using a dynamic set of tokens,
 // like web services that pass through their own clients'
 // A ClientPool is a pool of ArvadosClients. This is useful for
 // applications that make API calls using a dynamic set of tokens,
 // like web services that pass through their own clients'
-// credentials. See arvados-git-httpd for an example, and sync.Pool
-// for more information about garbage collection.
+// credentials. See sync.Pool for more information about garbage
+// collection.
 type ClientPool struct {
        // Initialize new clients by copying this one.
        Prototype *ArvadosClient
 type ClientPool struct {
        // Initialize new clients by copying this one.
        Prototype *ArvadosClient
index e1827b5d1f7995e3c3e01baa52ef016f349dcd95..658874c6d71480f4afdda12c8b3a9db8ad6e55f3 100644 (file)
@@ -264,26 +264,6 @@ func (as *APIStub) LogDelete(ctx context.Context, options arvados.DeleteOptions)
        as.appendCall(ctx, as.LogDelete, options)
        return arvados.Log{}, as.Error
 }
        as.appendCall(ctx, as.LogDelete, options)
        return arvados.Log{}, as.Error
 }
-func (as *APIStub) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
-       as.appendCall(ctx, as.SpecimenCreate, options)
-       return arvados.Specimen{}, as.Error
-}
-func (as *APIStub) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
-       as.appendCall(ctx, as.SpecimenUpdate, options)
-       return arvados.Specimen{}, as.Error
-}
-func (as *APIStub) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
-       as.appendCall(ctx, as.SpecimenGet, options)
-       return arvados.Specimen{}, as.Error
-}
-func (as *APIStub) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
-       as.appendCall(ctx, as.SpecimenList, options)
-       return arvados.SpecimenList{}, as.Error
-}
-func (as *APIStub) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
-       as.appendCall(ctx, as.SpecimenDelete, options)
-       return arvados.Specimen{}, as.Error
-}
 func (as *APIStub) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
        as.appendCall(ctx, as.SysTrashSweep, options)
        return struct{}{}, as.Error
 func (as *APIStub) SysTrashSweep(ctx context.Context, options struct{}) (struct{}, error) {
        as.appendCall(ctx, as.SysTrashSweep, options)
        return struct{}{}, as.Error
index 3b8a618fea099255434033751a156ab62d9a02d8..c70af45b5923ffa32f3852189b86a84b39144003 100644 (file)
@@ -101,7 +101,7 @@ const (
 
        AdminAuthorizedKeysUUID = "zzzzz-fngyi-12nc9ov4osp8nae"
 
 
        AdminAuthorizedKeysUUID = "zzzzz-fngyi-12nc9ov4osp8nae"
 
-       CrunchstatForRunningJobLogUUID = "zzzzz-57u5n-tmymyrojrbtnxh1"
+       CrunchstatForRunningContainerLogUUID = "zzzzz-57u5n-containerlog006"
 
        IdleNodeUUID = "zzzzz-7ekkf-2z3mc76g2q73aio"
 
 
        IdleNodeUUID = "zzzzz-7ekkf-2z3mc76g2q73aio"
 
index f76f7b8ea80a45ba9d908e485d2dcae8b9eca300..d9f3faf034ffb0bb4cdc29481ed72186f46ddc20 100644 (file)
@@ -372,7 +372,6 @@ func (s *AggregatorSuite) setAllServiceURLs(listen string) {
                &svcs.DispatchCloud,
                &svcs.DispatchLSF,
                &svcs.DispatchSLURM,
                &svcs.DispatchCloud,
                &svcs.DispatchLSF,
                &svcs.DispatchSLURM,
-               &svcs.GitHTTP,
                &svcs.Keepbalance,
                &svcs.Keepproxy,
                &svcs.Keepstore,
                &svcs.Keepbalance,
                &svcs.Keepproxy,
                &svcs.Keepstore,
index d97a2d1fcd2096a7f44983bbc7349ce11c24d307..1720096aee96fbbf4689cc743f1bb2776a118e14 100644 (file)
@@ -100,6 +100,8 @@ const (
        XKeepReplicasStored          = "X-Keep-Replicas-Stored"
        XKeepStorageClasses          = "X-Keep-Storage-Classes"
        XKeepStorageClassesConfirmed = "X-Keep-Storage-Classes-Confirmed"
        XKeepReplicasStored          = "X-Keep-Replicas-Stored"
        XKeepStorageClasses          = "X-Keep-Storage-Classes"
        XKeepStorageClassesConfirmed = "X-Keep-Storage-Classes-Confirmed"
+       XKeepSignature               = "X-Keep-Signature"
+       XKeepLocator                 = "X-Keep-Locator"
 )
 
 type HTTPClient interface {
 )
 
 type HTTPClient interface {
index 232c88d0678eff48e64c366f386f549e62ed9c74..ef187f666300e95c5f1a03a28e8d3aea18905c8c 100644 (file)
         }
       }
     },
         }
       }
     },
-    "humans": {
+    "keep_services": {
       "methods": {
         "get": {
       "methods": {
         "get": {
-          "id": "arvados.humans.get",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.get",
+          "path": "keep_services/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "Gets a Human's metadata by UUID.",
+          "description": "Gets a KeepService's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Human in question.",
+              "description": "The UUID of the KeepService in question.",
               "required": true,
               "location": "path"
             }
               "required": true,
               "location": "path"
             }
             "uuid"
           ],
           "response": {
             "uuid"
           ],
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "index": {
           ]
         },
         "index": {
-          "id": "arvados.humans.list",
-          "path": "humans",
+          "id": "arvados.keep_services.list",
+          "path": "keep_services",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List Humans.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Humans. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#humanList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "HumanList"
+            "$ref": "KeepServiceList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "create": {
           ]
         },
         "create": {
-          "id": "arvados.humans.create",
-          "path": "humans",
+          "id": "arvados.keep_services.create",
+          "path": "keep_services",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "Create a new Human.",
+          "description": "Create a new KeepService.",
           "parameters": {
             "select": {
               "type": "array",
           "parameters": {
             "select": {
               "type": "array",
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "human": {
-                "$ref": "Human"
+              "keep_service": {
+                "$ref": "KeepService"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.humans.update",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.update",
+          "path": "keep_services/{uuid}",
           "httpMethod": "PUT",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Human.",
+          "description": "Update attributes of an existing KeepService.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Human in question.",
+              "description": "The UUID of the KeepService in question.",
               "required": true,
               "location": "path"
             },
               "required": true,
               "location": "path"
             },
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "human": {
-                "$ref": "Human"
+              "keep_service": {
+                "$ref": "KeepService"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.humans.delete",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.delete",
+          "path": "keep_services/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Human.",
+          "description": "Delete an existing KeepService.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Human in question.",
+              "description": "The UUID of the KeepService in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "accessible": {
+          "id": "arvados.keep_services.accessible",
+          "path": "keep_services/accessible",
+          "httpMethod": "GET",
+          "description": "accessible keep_services",
+          "parameters": {},
+          "response": {
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.humans.list",
-          "path": "humans",
+          "id": "arvados.keep_services.list",
+          "path": "keep_services",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List Humans.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Humans. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#humanList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "HumanList"
+            "$ref": "KeepServiceList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "show": {
           ]
         },
         "show": {
-          "id": "arvados.humans.show",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.show",
+          "path": "keep_services/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "show humans",
+          "description": "show keep_services",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.humans.destroy",
-          "path": "humans/{uuid}",
+          "id": "arvados.keep_services.destroy",
+          "path": "keep_services/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "destroy humans",
+          "description": "destroy keep_services",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Human"
+            "$ref": "KeepService"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
         }
       }
     },
         }
       }
     },
-    "jobs": {
+    "links": {
       "methods": {
         "get": {
       "methods": {
         "get": {
-          "id": "arvados.jobs.get",
-          "path": "jobs/{uuid}",
+          "id": "arvados.links.get",
+          "path": "links/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "Gets a Job's metadata by UUID.",
+          "description": "Gets a Link's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Job in question.",
+              "description": "The UUID of the Link in question.",
               "required": true,
               "location": "path"
             }
               "required": true,
               "location": "path"
             }
             "uuid"
           ],
           "response": {
             "uuid"
           ],
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "index": {
           ]
         },
         "index": {
-          "id": "arvados.jobs.list",
-          "path": "jobs",
+          "id": "arvados.links.list",
+          "path": "links",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List Jobs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Jobs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "JobList"
+            "$ref": "LinkList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "create": {
           ]
         },
         "create": {
-          "id": "arvados.jobs.create",
-          "path": "jobs",
+          "id": "arvados.links.create",
+          "path": "links",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "Create a new Job.",
+          "description": "Create a new Link.",
           "parameters": {
             "select": {
               "type": "array",
           "parameters": {
             "select": {
               "type": "array",
               "description": "Create object on a remote federated cluster instead of the current one.",
               "location": "query",
               "required": false
               "description": "Create object on a remote federated cluster instead of the current one.",
               "location": "query",
               "required": false
-            },
-            "find_or_create": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "minimum_script_version": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "exclude_script_versions": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
             }
           },
           "request": {
             "required": true,
             "properties": {
             }
           },
           "request": {
             "required": true,
             "properties": {
-              "job": {
-                "$ref": "Job"
+              "link": {
+                "$ref": "Link"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.jobs.update",
-          "path": "jobs/{uuid}",
+          "id": "arvados.links.update",
+          "path": "links/{uuid}",
           "httpMethod": "PUT",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Job.",
+          "description": "Update attributes of an existing Link.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Job in question.",
+              "description": "The UUID of the Link in question.",
               "required": true,
               "location": "path"
             },
               "required": true,
               "location": "path"
             },
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "job": {
-                "$ref": "Job"
+              "link": {
+                "$ref": "Link"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.jobs.delete",
-          "path": "jobs/{uuid}",
+          "id": "arvados.links.delete",
+          "path": "links/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Job.",
+          "description": "Delete an existing Link.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Job in question.",
+              "description": "The UUID of the Link in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "queue": {
-          "id": "arvados.jobs.queue",
-          "path": "jobs/queue",
+        "list": {
+          "id": "arvados.links.list",
+          "path": "links",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "queue jobs",
+          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "LinkList"
           },
           "scopes": [
           },
           "scopes": [
-            "https://api.arvados.org/auth/arvados"
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
           ]
         },
           ]
         },
-        "queue_size": {
-          "id": "arvados.jobs.queue_size",
-          "path": "jobs/queue_size",
+        "show": {
+          "id": "arvados.links.show",
+          "path": "links/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "queue_size jobs",
-          "parameters": {},
-          "response": {
-            "$ref": "Job"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "cancel": {
-          "id": "arvados.jobs.cancel",
-          "path": "jobs/{uuid}/cancel",
-          "httpMethod": "POST",
-          "description": "cancel jobs",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Job"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "lock": {
-          "id": "arvados.jobs.lock",
-          "path": "jobs/{uuid}/lock",
-          "httpMethod": "POST",
-          "description": "lock jobs",
+          "description": "show links",
           "parameters": {
             "uuid": {
               "type": "string",
               "description": "",
               "required": true,
               "location": "path"
           "parameters": {
             "uuid": {
               "type": "string",
               "description": "",
               "required": true,
               "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Job"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.jobs.list",
-          "path": "jobs",
-          "httpMethod": "GET",
-          "description": "List Jobs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Jobs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
             },
             "select": {
               "type": "array",
             },
             "select": {
               "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
+              "description": "Attributes of the object to return in the response.",
               "required": false,
               "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
               "location": "query"
             }
           },
           "response": {
               "location": "query"
             }
           },
           "response": {
-            "$ref": "JobList"
+            "$ref": "Link"
           },
           "scopes": [
           },
           "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
+            "https://api.arvados.org/auth/arvados"
           ]
         },
           ]
         },
-        "show": {
-          "id": "arvados.jobs.show",
-          "path": "jobs/{uuid}",
-          "httpMethod": "GET",
-          "description": "show jobs",
+        "destroy": {
+          "id": "arvados.links.destroy",
+          "path": "links/{uuid}",
+          "httpMethod": "DELETE",
+          "description": "destroy links",
           "parameters": {
             "uuid": {
               "type": "string",
               "description": "",
               "required": true,
               "location": "path"
           "parameters": {
             "uuid": {
               "type": "string",
               "description": "",
               "required": true,
               "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "destroy": {
-          "id": "arvados.jobs.destroy",
-          "path": "jobs/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy jobs",
+        "get_permissions": {
+          "id": "arvados.links.get_permissions",
+          "path": "permissions/{uuid}",
+          "httpMethod": "GET",
+          "description": "get_permissions links",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Job"
+            "$ref": "Link"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
         }
       }
     },
         }
       }
     },
-    "job_tasks": {
+    "logs": {
       "methods": {
         "get": {
       "methods": {
         "get": {
-          "id": "arvados.job_tasks.get",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.get",
+          "path": "logs/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "Gets a JobTask's metadata by UUID.",
+          "description": "Gets a Log's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the JobTask in question.",
+              "description": "The UUID of the Log in question.",
               "required": true,
               "location": "path"
             }
               "required": true,
               "location": "path"
             }
             "uuid"
           ],
           "response": {
             "uuid"
           ],
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "index": {
           ]
         },
         "index": {
-          "id": "arvados.job_tasks.list",
-          "path": "job_tasks",
+          "id": "arvados.logs.list",
+          "path": "logs",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List JobTasks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching JobTasks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobTaskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "JobTaskList"
+            "$ref": "LogList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "create": {
           ]
         },
         "create": {
-          "id": "arvados.job_tasks.create",
-          "path": "job_tasks",
+          "id": "arvados.logs.create",
+          "path": "logs",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "Create a new JobTask.",
+          "description": "Create a new Log.",
           "parameters": {
             "select": {
               "type": "array",
           "parameters": {
             "select": {
               "type": "array",
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "job_task": {
-                "$ref": "JobTask"
+              "log": {
+                "$ref": "Log"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.job_tasks.update",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.update",
+          "path": "logs/{uuid}",
           "httpMethod": "PUT",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing JobTask.",
+          "description": "Update attributes of an existing Log.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the JobTask in question.",
+              "description": "The UUID of the Log in question.",
               "required": true,
               "location": "path"
             },
               "required": true,
               "location": "path"
             },
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "job_task": {
-                "$ref": "JobTask"
-              }
+              "log": {
+                "$ref": "Log"
+              }
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.job_tasks.delete",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.delete",
+          "path": "logs/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "Delete an existing JobTask.",
+          "description": "Delete an existing Log.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the JobTask in question.",
+              "description": "The UUID of the Log in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.job_tasks.list",
-          "path": "job_tasks",
+          "id": "arvados.logs.list",
+          "path": "logs",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List JobTasks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching JobTasks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#jobTaskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "JobTaskList"
+            "$ref": "LogList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "show": {
           ]
         },
         "show": {
-          "id": "arvados.job_tasks.show",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.show",
+          "path": "logs/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "show job_tasks",
+          "description": "show logs",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.job_tasks.destroy",
-          "path": "job_tasks/{uuid}",
+          "id": "arvados.logs.destroy",
+          "path": "logs/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "destroy job_tasks",
+          "description": "destroy logs",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "JobTask"
+            "$ref": "Log"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
         }
       }
     },
         }
       }
     },
-    "keep_disks": {
+    "users": {
       "methods": {
         "get": {
       "methods": {
         "get": {
-          "id": "arvados.keep_disks.get",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.get",
+          "path": "users/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "Gets a KeepDisk's metadata by UUID.",
+          "description": "Gets a User's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepDisk in question.",
+              "description": "The UUID of the User in question.",
               "required": true,
               "location": "path"
             }
               "required": true,
               "location": "path"
             }
             "uuid"
           ],
           "response": {
             "uuid"
           ],
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "index": {
           ]
         },
         "index": {
-          "id": "arvados.keep_disks.list",
-          "path": "keep_disks",
+          "id": "arvados.users.list",
+          "path": "users",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List KeepDisks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepDisks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepDiskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepDiskList"
+            "$ref": "UserList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "create": {
           ]
         },
         "create": {
-          "id": "arvados.keep_disks.create",
-          "path": "keep_disks",
+          "id": "arvados.users.create",
+          "path": "users",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "Create a new KeepDisk.",
+          "description": "Create a new User.",
           "parameters": {
             "select": {
               "type": "array",
           "parameters": {
             "select": {
               "type": "array",
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "keep_disk": {
-                "$ref": "KeepDisk"
+              "user": {
+                "$ref": "User"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.keep_disks.update",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.update",
+          "path": "users/{uuid}",
           "httpMethod": "PUT",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing KeepDisk.",
+          "description": "Update attributes of an existing User.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepDisk in question.",
+              "description": "The UUID of the User in question.",
               "required": true,
               "location": "path"
             },
               "required": true,
               "location": "path"
             },
               "description": "Attributes of the updated object to return in the response.",
               "required": false,
               "location": "query"
               "description": "Attributes of the updated object to return in the response.",
               "required": false,
               "location": "query"
+            },
+            "bypass_federation": {
+              "type": "boolean",
+              "required": false,
+              "default": "false",
+              "description": "",
+              "location": "query"
             }
           },
           "request": {
             "required": true,
             "properties": {
             }
           },
           "request": {
             "required": true,
             "properties": {
-              "keep_disk": {
-                "$ref": "KeepDisk"
+              "user": {
+                "$ref": "User"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.keep_disks.delete",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.delete",
+          "path": "users/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "Delete an existing KeepDisk.",
+          "description": "Delete an existing User.",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "The UUID of the User in question.",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "current": {
+          "id": "arvados.users.current",
+          "path": "users/current",
+          "httpMethod": "GET",
+          "description": "current users",
+          "parameters": {},
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "system": {
+          "id": "arvados.users.system",
+          "path": "users/system",
+          "httpMethod": "GET",
+          "description": "system users",
+          "parameters": {},
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "activate": {
+          "id": "arvados.users.activate",
+          "path": "users/{uuid}/activate",
+          "httpMethod": "POST",
+          "description": "activate users",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepDisk in question.",
+              "description": "",
               "required": true,
               "location": "path"
             }
           },
           "response": {
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "ping": {
-          "id": "arvados.keep_disks.ping",
-          "path": "keep_disks/ping",
+        "setup": {
+          "id": "arvados.users.setup",
+          "path": "users/setup",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "ping keep_disks",
+          "description": "setup users",
           "parameters": {
             "uuid": {
           "parameters": {
             "uuid": {
+              "type": "string",
+              "required": false,
+              "description": "",
+              "location": "query"
+            },
+            "user": {
+              "type": "object",
               "required": false,
               "required": false,
+              "description": "",
+              "location": "query"
+            },
+            "repo_name": {
               "type": "string",
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
               "description": "",
               "location": "query"
             },
-            "ping_secret": {
-              "required": true,
+            "vm_uuid": {
               "type": "string",
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
               "description": "",
               "location": "query"
             },
-            "node_uuid": {
+            "send_notification_email": {
+              "type": "boolean",
               "required": false,
               "required": false,
+              "default": "false",
+              "description": "",
+              "location": "query"
+            }
+          },
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "unsetup": {
+          "id": "arvados.users.unsetup",
+          "path": "users/{uuid}/unsetup",
+          "httpMethod": "POST",
+          "description": "unsetup users",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "User"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "merge": {
+          "id": "arvados.users.merge",
+          "path": "users/merge",
+          "httpMethod": "POST",
+          "description": "merge users",
+          "parameters": {
+            "new_owner_uuid": {
               "type": "string",
               "type": "string",
+              "required": true,
               "description": "",
               "location": "query"
             },
               "description": "",
               "location": "query"
             },
-            "filesystem_uuid": {
-              "required": false,
+            "new_user_token": {
               "type": "string",
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
               "description": "",
               "location": "query"
             },
-            "service_host": {
+            "redirect_to_new_user": {
+              "type": "boolean",
               "required": false,
               "required": false,
-              "type": "string",
+              "default": "false",
               "description": "",
               "location": "query"
             },
               "description": "",
               "location": "query"
             },
-            "service_port": {
-              "required": true,
+            "old_user_uuid": {
               "type": "string",
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             },
               "description": "",
               "location": "query"
             },
-            "service_ssl_flag": {
-              "required": true,
+            "new_user_uuid": {
               "type": "string",
               "type": "string",
+              "required": false,
               "description": "",
               "location": "query"
             }
           },
           "response": {
               "description": "",
               "location": "query"
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.keep_disks.list",
-          "path": "keep_disks",
+          "id": "arvados.users.list",
+          "path": "users",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List KeepDisks.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepDisks. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepDiskList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepDiskList"
+            "$ref": "UserList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "show": {
           ]
         },
         "show": {
-          "id": "arvados.keep_disks.show",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.show",
+          "path": "users/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "show keep_disks",
+          "description": "show users",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.keep_disks.destroy",
-          "path": "keep_disks/{uuid}",
+          "id": "arvados.users.destroy",
+          "path": "users/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "destroy keep_disks",
+          "description": "destroy users",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepDisk"
+            "$ref": "User"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
         }
       }
     },
         }
       }
     },
-    "keep_services": {
+    "user_agreements": {
       "methods": {
         "get": {
       "methods": {
         "get": {
-          "id": "arvados.keep_services.get",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.get",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "Gets a KeepService's metadata by UUID.",
+          "description": "Gets a UserAgreement's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepService in question.",
+              "description": "The UUID of the UserAgreement in question.",
               "required": true,
               "location": "path"
             }
               "required": true,
               "location": "path"
             }
             "uuid"
           ],
           "response": {
             "uuid"
           ],
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "index": {
           ]
         },
         "index": {
-          "id": "arvados.keep_services.list",
-          "path": "keep_services",
+          "id": "arvados.user_agreements.list",
+          "path": "user_agreements",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepServiceList"
+            "$ref": "UserAgreementList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "create": {
           ]
         },
         "create": {
-          "id": "arvados.keep_services.create",
-          "path": "keep_services",
+          "id": "arvados.user_agreements.create",
+          "path": "user_agreements",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "Create a new KeepService.",
+          "description": "Create a new UserAgreement.",
           "parameters": {
             "select": {
               "type": "array",
           "parameters": {
             "select": {
               "type": "array",
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "keep_service": {
-                "$ref": "KeepService"
+              "user_agreement": {
+                "$ref": "UserAgreement"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.keep_services.update",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.update",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "PUT",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing KeepService.",
+          "description": "Update attributes of an existing UserAgreement.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepService in question.",
+              "description": "The UUID of the UserAgreement in question.",
               "required": true,
               "location": "path"
             },
               "required": true,
               "location": "path"
             },
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "keep_service": {
-                "$ref": "KeepService"
+              "user_agreement": {
+                "$ref": "UserAgreement"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.keep_services.delete",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.delete",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "Delete an existing KeepService.",
+          "description": "Delete an existing UserAgreement.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the KeepService in question.",
+              "description": "The UUID of the UserAgreement in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "accessible": {
-          "id": "arvados.keep_services.accessible",
-          "path": "keep_services/accessible",
+        "signatures": {
+          "id": "arvados.user_agreements.signatures",
+          "path": "user_agreements/signatures",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "accessible keep_services",
+          "description": "signatures user_agreements",
           "parameters": {},
           "response": {
           "parameters": {},
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
-        "list": {
-          "id": "arvados.keep_services.list",
-          "path": "keep_services",
+        "sign": {
+          "id": "arvados.user_agreements.sign",
+          "path": "user_agreements/sign",
+          "httpMethod": "POST",
+          "description": "sign user_agreements",
+          "parameters": {},
+          "response": {
+            "$ref": "UserAgreement"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "list": {
+          "id": "arvados.user_agreements.list",
+          "path": "user_agreements",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List KeepServices.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching KeepServices. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#keepServiceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepServiceList"
+            "$ref": "UserAgreementList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
             "https://api.arvados.org/auth/arvados.readonly"
           ]
         },
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
             "https://api.arvados.org/auth/arvados.readonly"
           ]
         },
+        "new": {
+          "id": "arvados.user_agreements.new",
+          "path": "user_agreements/new",
+          "httpMethod": "GET",
+          "description": "new user_agreements",
+          "parameters": {},
+          "response": {
+            "$ref": "UserAgreement"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
         "show": {
         "show": {
-          "id": "arvados.keep_services.show",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.show",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "show keep_services",
+          "description": "show user_agreements",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.keep_services.destroy",
-          "path": "keep_services/{uuid}",
+          "id": "arvados.user_agreements.destroy",
+          "path": "user_agreements/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "destroy keep_services",
+          "description": "destroy user_agreements",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "KeepService"
+            "$ref": "UserAgreement"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
         }
       }
     },
         }
       }
     },
-    "links": {
+    "virtual_machines": {
       "methods": {
         "get": {
       "methods": {
         "get": {
-          "id": "arvados.links.get",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.get",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "Gets a Link's metadata by UUID.",
+          "description": "Gets a VirtualMachine's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Link in question.",
+              "description": "The UUID of the VirtualMachine in question.",
               "required": true,
               "location": "path"
             }
               "required": true,
               "location": "path"
             }
             "uuid"
           ],
           "response": {
             "uuid"
           ],
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "index": {
           ]
         },
         "index": {
-          "id": "arvados.links.list",
-          "path": "links",
+          "id": "arvados.virtual_machines.list",
+          "path": "virtual_machines",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "LinkList"
+            "$ref": "VirtualMachineList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "create": {
           ]
         },
         "create": {
-          "id": "arvados.links.create",
-          "path": "links",
+          "id": "arvados.virtual_machines.create",
+          "path": "virtual_machines",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "Create a new Link.",
+          "description": "Create a new VirtualMachine.",
           "parameters": {
             "select": {
               "type": "array",
           "parameters": {
             "select": {
               "type": "array",
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "link": {
-                "$ref": "Link"
+              "virtual_machine": {
+                "$ref": "VirtualMachine"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.links.update",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.update",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "PUT",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Link.",
+          "description": "Update attributes of an existing VirtualMachine.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Link in question.",
+              "description": "The UUID of the VirtualMachine in question.",
               "required": true,
               "location": "path"
             },
               "required": true,
               "location": "path"
             },
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "link": {
-                "$ref": "Link"
+              "virtual_machine": {
+                "$ref": "VirtualMachine"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.links.delete",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.delete",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Link.",
+          "description": "Delete an existing VirtualMachine.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Link in question.",
+              "description": "The UUID of the VirtualMachine in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "logins": {
+          "id": "arvados.virtual_machines.logins",
+          "path": "virtual_machines/{uuid}/logins",
+          "httpMethod": "GET",
+          "description": "logins virtual_machines",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "VirtualMachine"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
+        },
+        "get_all_logins": {
+          "id": "arvados.virtual_machines.get_all_logins",
+          "path": "virtual_machines/get_all_logins",
+          "httpMethod": "GET",
+          "description": "get_all_logins virtual_machines",
+          "parameters": {},
+          "response": {
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.links.list",
-          "path": "links",
+          "id": "arvados.virtual_machines.list",
+          "path": "virtual_machines",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List Links.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Links. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#linkList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "LinkList"
+            "$ref": "VirtualMachineList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "show": {
           ]
         },
         "show": {
-          "id": "arvados.links.show",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.show",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "show links",
+          "description": "show virtual_machines",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.links.destroy",
-          "path": "links/{uuid}",
+          "id": "arvados.virtual_machines.destroy",
+          "path": "virtual_machines/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "destroy links",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Link"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "get_permissions": {
-          "id": "arvados.links.get_permissions",
-          "path": "permissions/{uuid}",
-          "httpMethod": "GET",
-          "description": "get_permissions links",
+          "description": "destroy virtual_machines",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Link"
+            "$ref": "VirtualMachine"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
         }
       }
     },
         }
       }
     },
-    "logs": {
+    "workflows": {
       "methods": {
         "get": {
       "methods": {
         "get": {
-          "id": "arvados.logs.get",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.get",
+          "path": "workflows/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "Gets a Log's metadata by UUID.",
+          "description": "Gets a Workflow's metadata by UUID.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Log in question.",
+              "description": "The UUID of the Workflow in question.",
               "required": true,
               "location": "path"
             }
               "required": true,
               "location": "path"
             }
             "uuid"
           ],
           "response": {
             "uuid"
           ],
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "index": {
           ]
         },
         "index": {
-          "id": "arvados.logs.list",
-          "path": "logs",
+          "id": "arvados.workflows.list",
+          "path": "workflows",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "LogList"
+            "$ref": "WorkflowList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "create": {
           ]
         },
         "create": {
-          "id": "arvados.logs.create",
-          "path": "logs",
+          "id": "arvados.workflows.create",
+          "path": "workflows",
           "httpMethod": "POST",
           "httpMethod": "POST",
-          "description": "Create a new Log.",
+          "description": "Create a new Workflow.",
           "parameters": {
             "select": {
               "type": "array",
           "parameters": {
             "select": {
               "type": "array",
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "log": {
-                "$ref": "Log"
+              "workflow": {
+                "$ref": "Workflow"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "update": {
-          "id": "arvados.logs.update",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.update",
+          "path": "workflows/{uuid}",
           "httpMethod": "PUT",
           "httpMethod": "PUT",
-          "description": "Update attributes of an existing Log.",
+          "description": "Update attributes of an existing Workflow.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Log in question.",
+              "description": "The UUID of the Workflow in question.",
               "required": true,
               "location": "path"
             },
               "required": true,
               "location": "path"
             },
           "request": {
             "required": true,
             "properties": {
           "request": {
             "required": true,
             "properties": {
-              "log": {
-                "$ref": "Log"
+              "workflow": {
+                "$ref": "Workflow"
               }
             }
           },
           "response": {
               }
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "delete": {
-          "id": "arvados.logs.delete",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.delete",
+          "path": "workflows/{uuid}",
           "httpMethod": "DELETE",
           "httpMethod": "DELETE",
-          "description": "Delete an existing Log.",
+          "description": "Delete an existing Workflow.",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
-              "description": "The UUID of the Log in question.",
+              "description": "The UUID of the Workflow in question.",
               "required": true,
               "location": "path"
             }
           },
           "response": {
               "required": true,
               "location": "path"
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "list": {
-          "id": "arvados.logs.list",
-          "path": "logs",
+          "id": "arvados.workflows.list",
+          "path": "workflows",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "List Logs.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Logs. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#logList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
+          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
           "parameters": {
             "filters": {
               "type": "array",
           "parameters": {
             "filters": {
               "type": "array",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "LogList"
+            "$ref": "WorkflowList"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados",
           ]
         },
         "show": {
           ]
         },
         "show": {
-          "id": "arvados.logs.show",
-          "path": "logs/{uuid}",
+          "id": "arvados.workflows.show",
+          "path": "workflows/{uuid}",
           "httpMethod": "GET",
           "httpMethod": "GET",
-          "description": "show logs",
+          "description": "show workflows",
           "parameters": {
             "uuid": {
               "type": "string",
           "parameters": {
             "uuid": {
               "type": "string",
             }
           },
           "response": {
             }
           },
           "response": {
-            "$ref": "Log"
+            "$ref": "Workflow"
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
           },
           "scopes": [
             "https://api.arvados.org/auth/arvados"
           ]
         },
         "destroy": {
-          "id": "arvados.logs.destroy",
-          "path": "logs/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy logs",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Log"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "nodes": {
-      "methods": {
-        "get": {
-          "id": "arvados.nodes.get",
-          "path": "nodes/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Node's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Node in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.nodes.list",
-          "path": "nodes",
-          "httpMethod": "GET",
-          "description": "List Nodes.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Nodes. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#nodeList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "NodeList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.nodes.create",
-          "path": "nodes",
-          "httpMethod": "POST",
-          "description": "Create a new Node.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "assign_slot": {
-              "required": false,
-              "type": "boolean",
-              "description": "assign slot and hostname",
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "node": {
-                "$ref": "Node"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.nodes.update",
-          "path": "nodes/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Node.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Node in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "assign_slot": {
-              "required": false,
-              "type": "boolean",
-              "description": "assign slot and hostname",
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "node": {
-                "$ref": "Node"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.nodes.delete",
-          "path": "nodes/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Node.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Node in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "ping": {
-          "id": "arvados.nodes.ping",
-          "path": "nodes/{uuid}/ping",
-          "httpMethod": "POST",
-          "description": "ping nodes",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "ping_secret": {
-              "required": true,
-              "type": "string",
-              "description": "",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.nodes.list",
-          "path": "nodes",
-          "httpMethod": "GET",
-          "description": "List Nodes.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Nodes. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#nodeList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "NodeList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.nodes.show",
-          "path": "nodes/{uuid}",
-          "httpMethod": "GET",
-          "description": "show nodes",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.nodes.destroy",
-          "path": "nodes/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy nodes",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Node"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "pipeline_instances": {
-      "methods": {
-        "get": {
-          "id": "arvados.pipeline_instances.get",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a PipelineInstance's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineInstance in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.pipeline_instances.list",
-          "path": "pipeline_instances",
-          "httpMethod": "GET",
-          "description": "List PipelineInstances.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineInstances. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineInstanceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstanceList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.pipeline_instances.create",
-          "path": "pipeline_instances",
-          "httpMethod": "POST",
-          "description": "Create a new PipelineInstance.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_instance": {
-                "$ref": "PipelineInstance"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.pipeline_instances.update",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing PipelineInstance.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineInstance in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_instance": {
-                "$ref": "PipelineInstance"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.pipeline_instances.delete",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing PipelineInstance.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineInstance in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "cancel": {
-          "id": "arvados.pipeline_instances.cancel",
-          "path": "pipeline_instances/{uuid}/cancel",
-          "httpMethod": "POST",
-          "description": "cancel pipeline_instances",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.pipeline_instances.list",
-          "path": "pipeline_instances",
-          "httpMethod": "GET",
-          "description": "List PipelineInstances.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineInstances. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineInstanceList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstanceList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.pipeline_instances.show",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "GET",
-          "description": "show pipeline_instances",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.pipeline_instances.destroy",
-          "path": "pipeline_instances/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy pipeline_instances",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineInstance"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "pipeline_templates": {
-      "methods": {
-        "get": {
-          "id": "arvados.pipeline_templates.get",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a PipelineTemplate's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineTemplate in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.pipeline_templates.list",
-          "path": "pipeline_templates",
-          "httpMethod": "GET",
-          "description": "List PipelineTemplates.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineTemplates. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineTemplateList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplateList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.pipeline_templates.create",
-          "path": "pipeline_templates",
-          "httpMethod": "POST",
-          "description": "Create a new PipelineTemplate.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_template": {
-                "$ref": "PipelineTemplate"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.pipeline_templates.update",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing PipelineTemplate.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineTemplate in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "pipeline_template": {
-                "$ref": "PipelineTemplate"
-              }
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.pipeline_templates.delete",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing PipelineTemplate.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the PipelineTemplate in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.pipeline_templates.list",
-          "path": "pipeline_templates",
-          "httpMethod": "GET",
-          "description": "List PipelineTemplates.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching PipelineTemplates. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#pipelineTemplateList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplateList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.pipeline_templates.show",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "GET",
-          "description": "show pipeline_templates",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.pipeline_templates.destroy",
-          "path": "pipeline_templates/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy pipeline_templates",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "PipelineTemplate"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "repositories": {
-      "methods": {
-        "get": {
-          "id": "arvados.repositories.get",
-          "path": "repositories/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Repository's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Repository in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.repositories.list",
-          "path": "repositories",
-          "httpMethod": "GET",
-          "description": "List Repositories.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Repositories. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#repositoryList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "RepositoryList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.repositories.create",
-          "path": "repositories",
-          "httpMethod": "POST",
-          "description": "Create a new Repository.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "repository": {
-                "$ref": "Repository"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.repositories.update",
-          "path": "repositories/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Repository.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Repository in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "repository": {
-                "$ref": "Repository"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.repositories.delete",
-          "path": "repositories/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Repository.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Repository in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "get_all_permissions": {
-          "id": "arvados.repositories.get_all_permissions",
-          "path": "repositories/get_all_permissions",
-          "httpMethod": "GET",
-          "description": "get_all_permissions repositories",
-          "parameters": {},
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.repositories.list",
-          "path": "repositories",
-          "httpMethod": "GET",
-          "description": "List Repositories.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Repositories. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#repositoryList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "RepositoryList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.repositories.show",
-          "path": "repositories/{uuid}",
-          "httpMethod": "GET",
-          "description": "show repositories",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.repositories.destroy",
-          "path": "repositories/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy repositories",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Repository"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "specimens": {
-      "methods": {
-        "get": {
-          "id": "arvados.specimens.get",
-          "path": "specimens/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Specimen's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Specimen in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.specimens.list",
-          "path": "specimens",
-          "httpMethod": "GET",
-          "description": "List Specimens.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Specimens. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#specimenList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "SpecimenList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.specimens.create",
-          "path": "specimens",
-          "httpMethod": "POST",
-          "description": "Create a new Specimen.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "specimen": {
-                "$ref": "Specimen"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.specimens.update",
-          "path": "specimens/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Specimen.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Specimen in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "specimen": {
-                "$ref": "Specimen"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.specimens.delete",
-          "path": "specimens/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Specimen.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Specimen in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.specimens.list",
-          "path": "specimens",
-          "httpMethod": "GET",
-          "description": "List Specimens.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Specimens. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#specimenList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "SpecimenList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.specimens.show",
-          "path": "specimens/{uuid}",
-          "httpMethod": "GET",
-          "description": "show specimens",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.specimens.destroy",
-          "path": "specimens/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy specimens",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Specimen"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "traits": {
-      "methods": {
-        "get": {
-          "id": "arvados.traits.get",
-          "path": "traits/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Trait's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Trait in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.traits.list",
-          "path": "traits",
-          "httpMethod": "GET",
-          "description": "List Traits.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Traits. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#traitList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "TraitList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.traits.create",
-          "path": "traits",
-          "httpMethod": "POST",
-          "description": "Create a new Trait.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "trait": {
-                "$ref": "Trait"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.traits.update",
-          "path": "traits/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Trait.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Trait in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "trait": {
-                "$ref": "Trait"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.traits.delete",
-          "path": "traits/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Trait.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Trait in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.traits.list",
-          "path": "traits",
-          "httpMethod": "GET",
-          "description": "List Traits.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Traits. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#traitList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "TraitList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.traits.show",
-          "path": "traits/{uuid}",
-          "httpMethod": "GET",
-          "description": "show traits",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.traits.destroy",
-          "path": "traits/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy traits",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Trait"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "users": {
-      "methods": {
-        "get": {
-          "id": "arvados.users.get",
-          "path": "users/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a User's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the User in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.users.list",
-          "path": "users",
-          "httpMethod": "GET",
-          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.users.create",
-          "path": "users",
-          "httpMethod": "POST",
-          "description": "Create a new User.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user": {
-                "$ref": "User"
-              }
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.users.update",
-          "path": "users/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing User.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the User in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user": {
-                "$ref": "User"
-              }
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.users.delete",
-          "path": "users/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing User.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the User in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "current": {
-          "id": "arvados.users.current",
-          "path": "users/current",
-          "httpMethod": "GET",
-          "description": "current users",
-          "parameters": {},
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "system": {
-          "id": "arvados.users.system",
-          "path": "users/system",
-          "httpMethod": "GET",
-          "description": "system users",
-          "parameters": {},
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "activate": {
-          "id": "arvados.users.activate",
-          "path": "users/{uuid}/activate",
-          "httpMethod": "POST",
-          "description": "activate users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "setup": {
-          "id": "arvados.users.setup",
-          "path": "users/setup",
-          "httpMethod": "POST",
-          "description": "setup users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "user": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "repo_name": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "vm_uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "send_notification_email": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "unsetup": {
-          "id": "arvados.users.unsetup",
-          "path": "users/{uuid}/unsetup",
-          "httpMethod": "POST",
-          "description": "unsetup users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "merge": {
-          "id": "arvados.users.merge",
-          "path": "users/merge",
-          "httpMethod": "POST",
-          "description": "merge users",
-          "parameters": {
-            "new_owner_uuid": {
-              "type": "string",
-              "required": true,
-              "description": "",
-              "location": "query"
-            },
-            "new_user_token": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "redirect_to_new_user": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "old_user_uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "new_user_uuid": {
-              "type": "string",
-              "required": false,
-              "description": "",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.users.list",
-          "path": "users",
-          "httpMethod": "GET",
-          "description": "List Users.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Users. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.users.show",
-          "path": "users/{uuid}",
-          "httpMethod": "GET",
-          "description": "show users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.users.destroy",
-          "path": "users/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy users",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "User"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "user_agreements": {
-      "methods": {
-        "get": {
-          "id": "arvados.user_agreements.get",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a UserAgreement's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the UserAgreement in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.user_agreements.list",
-          "path": "user_agreements",
-          "httpMethod": "GET",
-          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreementList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.user_agreements.create",
-          "path": "user_agreements",
-          "httpMethod": "POST",
-          "description": "Create a new UserAgreement.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user_agreement": {
-                "$ref": "UserAgreement"
-              }
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.user_agreements.update",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing UserAgreement.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the UserAgreement in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "user_agreement": {
-                "$ref": "UserAgreement"
-              }
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.user_agreements.delete",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing UserAgreement.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the UserAgreement in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "signatures": {
-          "id": "arvados.user_agreements.signatures",
-          "path": "user_agreements/signatures",
-          "httpMethod": "GET",
-          "description": "signatures user_agreements",
-          "parameters": {},
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "sign": {
-          "id": "arvados.user_agreements.sign",
-          "path": "user_agreements/sign",
-          "httpMethod": "POST",
-          "description": "sign user_agreements",
-          "parameters": {},
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.user_agreements.list",
-          "path": "user_agreements",
-          "httpMethod": "GET",
-          "description": "List UserAgreements.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching UserAgreements. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#userAgreementList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreementList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "new": {
-          "id": "arvados.user_agreements.new",
-          "path": "user_agreements/new",
-          "httpMethod": "GET",
-          "description": "new user_agreements",
-          "parameters": {},
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "show": {
-          "id": "arvados.user_agreements.show",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "GET",
-          "description": "show user_agreements",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.user_agreements.destroy",
-          "path": "user_agreements/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy user_agreements",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "UserAgreement"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "virtual_machines": {
-      "methods": {
-        "get": {
-          "id": "arvados.virtual_machines.get",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a VirtualMachine's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the VirtualMachine in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.virtual_machines.list",
-          "path": "virtual_machines",
-          "httpMethod": "GET",
-          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachineList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.virtual_machines.create",
-          "path": "virtual_machines",
-          "httpMethod": "POST",
-          "description": "Create a new VirtualMachine.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "virtual_machine": {
-                "$ref": "VirtualMachine"
-              }
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.virtual_machines.update",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing VirtualMachine.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the VirtualMachine in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "virtual_machine": {
-                "$ref": "VirtualMachine"
-              }
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.virtual_machines.delete",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing VirtualMachine.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the VirtualMachine in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "logins": {
-          "id": "arvados.virtual_machines.logins",
-          "path": "virtual_machines/{uuid}/logins",
-          "httpMethod": "GET",
-          "description": "logins virtual_machines",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "get_all_logins": {
-          "id": "arvados.virtual_machines.get_all_logins",
-          "path": "virtual_machines/get_all_logins",
-          "httpMethod": "GET",
-          "description": "get_all_logins virtual_machines",
-          "parameters": {},
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.virtual_machines.list",
-          "path": "virtual_machines",
-          "httpMethod": "GET",
-          "description": "List VirtualMachines.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching VirtualMachines. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#virtualMachineList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachineList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.virtual_machines.show",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "GET",
-          "description": "show virtual_machines",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.virtual_machines.destroy",
-          "path": "virtual_machines/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy virtual_machines",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "VirtualMachine"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "workflows": {
-      "methods": {
-        "get": {
-          "id": "arvados.workflows.get",
-          "path": "workflows/{uuid}",
-          "httpMethod": "GET",
-          "description": "Gets a Workflow's metadata by UUID.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Workflow in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "parameterOrder": [
-            "uuid"
-          ],
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "index": {
-          "id": "arvados.workflows.list",
-          "path": "workflows",
-          "httpMethod": "GET",
-          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "WorkflowList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "create": {
-          "id": "arvados.workflows.create",
-          "path": "workflows",
-          "httpMethod": "POST",
-          "description": "Create a new Workflow.",
-          "parameters": {
-            "select": {
-              "type": "array",
-              "description": "Attributes of the new object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "ensure_unique_name": {
-              "type": "boolean",
-              "description": "Adjust name to ensure uniqueness instead of returning an error on (owner_uuid, name) collision.",
-              "location": "query",
-              "required": false,
-              "default": "false"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "Create object on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "workflow": {
-                "$ref": "Workflow"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "update": {
-          "id": "arvados.workflows.update",
-          "path": "workflows/{uuid}",
-          "httpMethod": "PUT",
-          "description": "Update attributes of an existing Workflow.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Workflow in question.",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the updated object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "request": {
-            "required": true,
-            "properties": {
-              "workflow": {
-                "$ref": "Workflow"
-              }
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "delete": {
-          "id": "arvados.workflows.delete",
-          "path": "workflows/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "Delete an existing Workflow.",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "The UUID of the Workflow in question.",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "list": {
-          "id": "arvados.workflows.list",
-          "path": "workflows",
-          "httpMethod": "GET",
-          "description": "List Workflows.\n\n                   The <code>list</code> method returns a\n                   <a href=\"/api/resources.html\">resource list</a> of\n                   matching Workflows. For example:\n\n                   <pre>\n                   {\n                    \"kind\":\"arvados#workflowList\",\n                    \"etag\":\"\",\n                    \"self_link\":\"\",\n                    \"next_page_token\":\"\",\n                    \"next_link\":\"\",\n                    \"items\":[\n                       ...\n                    ],\n                    \"items_available\":745,\n                    \"_profile\":{\n                     \"request_time\":0.157236317\n                    }\n                    </pre>",
-          "parameters": {
-            "filters": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "where": {
-              "type": "object",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "order": {
-              "type": "array",
-              "required": false,
-              "description": "",
-              "location": "query"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of each object to return in the response.",
-              "required": false,
-              "location": "query"
-            },
-            "distinct": {
-              "type": "boolean",
-              "required": false,
-              "default": "false",
-              "description": "",
-              "location": "query"
-            },
-            "limit": {
-              "type": "integer",
-              "required": false,
-              "default": "100",
-              "description": "",
-              "location": "query"
-            },
-            "offset": {
-              "type": "integer",
-              "required": false,
-              "default": "0",
-              "description": "",
-              "location": "query"
-            },
-            "count": {
-              "type": "string",
-              "required": false,
-              "default": "exact",
-              "description": "",
-              "location": "query"
-            },
-            "cluster_id": {
-              "type": "string",
-              "description": "List objects on a remote federated cluster instead of the current one.",
-              "location": "query",
-              "required": false
-            },
-            "bypass_federation": {
-              "type": "boolean",
-              "required": false,
-              "description": "bypass federation behavior, list items from local instance database only",
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "WorkflowList"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        },
-        "show": {
-          "id": "arvados.workflows.show",
-          "path": "workflows/{uuid}",
-          "httpMethod": "GET",
-          "description": "show workflows",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            },
-            "select": {
-              "type": "array",
-              "description": "Attributes of the object to return in the response.",
-              "required": false,
-              "location": "query"
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        },
-        "destroy": {
-          "id": "arvados.workflows.destroy",
-          "path": "workflows/{uuid}",
-          "httpMethod": "DELETE",
-          "description": "destroy workflows",
-          "parameters": {
-            "uuid": {
-              "type": "string",
-              "description": "",
-              "required": true,
-              "location": "path"
-            }
-          },
-          "response": {
-            "$ref": "Workflow"
-          },
-          "scopes": [
-            "https://api.arvados.org/auth/arvados"
-          ]
-        }
-      }
-    },
-    "configs": {
-      "methods": {
-        "get": {
-          "id": "arvados.configs.get",
-          "path": "config",
-          "httpMethod": "GET",
-          "description": "Get public config",
-          "parameters": {},
-          "parameterOrder": [],
-          "response": {},
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        }
-      }
-    },
-    "vocabularies": {
-      "methods": {
-        "get": {
-          "id": "arvados.vocabularies.get",
-          "path": "vocabulary",
-          "httpMethod": "GET",
-          "description": "Get vocabulary definition",
-          "parameters": {},
-          "parameterOrder": [],
-          "response": {},
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        }
-      }
-    },
-    "sys": {
-      "methods": {
-        "get": {
-          "id": "arvados.sys.trash_sweep",
-          "path": "sys/trash_sweep",
-          "httpMethod": "POST",
-          "description": "apply scheduled trash and delete operations",
-          "parameters": {},
-          "parameterOrder": [],
-          "response": {},
-          "scopes": [
-            "https://api.arvados.org/auth/arvados",
-            "https://api.arvados.org/auth/arvados.readonly"
-          ]
-        }
-      }
-    }
-  },
-  "revision": "20231117",
-  "schemas": {
-    "ApiClientList": {
-      "id": "ApiClientList",
-      "description": "ApiClient list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#apiClientList.",
-          "default": "arvados#apiClientList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of ApiClients.",
-          "items": {
-            "$ref": "ApiClient"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of ApiClients."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of ApiClients."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "ApiClient": {
-      "id": "ApiClient",
-      "description": "ApiClient",
-      "type": "object",
-      "uuidPrefix": "ozdt8",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "name": {
-          "type": "string"
-        },
-        "url_prefix": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "is_trusted": {
-          "type": "boolean"
-        }
-      }
-    },
-    "ApiClientAuthorizationList": {
-      "id": "ApiClientAuthorizationList",
-      "description": "ApiClientAuthorization list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#apiClientAuthorizationList.",
-          "default": "arvados#apiClientAuthorizationList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of ApiClientAuthorizations.",
-          "items": {
-            "$ref": "ApiClientAuthorization"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of ApiClientAuthorizations."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of ApiClientAuthorizations."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "ApiClientAuthorization": {
-      "id": "ApiClientAuthorization",
-      "description": "ApiClientAuthorization",
-      "type": "object",
-      "uuidPrefix": "gj3su",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "api_token": {
-          "type": "string"
-        },
-        "api_client_id": {
-          "type": "integer"
-        },
-        "user_id": {
-          "type": "integer"
-        },
-        "created_by_ip_address": {
-          "type": "string"
-        },
-        "last_used_by_ip_address": {
-          "type": "string"
-        },
-        "last_used_at": {
-          "type": "datetime"
-        },
-        "expires_at": {
-          "type": "datetime"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "default_owner_uuid": {
-          "type": "string"
-        },
-        "scopes": {
-          "type": "Array"
-        }
-      }
-    },
-    "AuthorizedKeyList": {
-      "id": "AuthorizedKeyList",
-      "description": "AuthorizedKey list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#authorizedKeyList.",
-          "default": "arvados#authorizedKeyList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of AuthorizedKeys.",
-          "items": {
-            "$ref": "AuthorizedKey"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of AuthorizedKeys."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of AuthorizedKeys."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "AuthorizedKey": {
-      "id": "AuthorizedKey",
-      "description": "AuthorizedKey",
-      "type": "object",
-      "uuidPrefix": "fngyi",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "name": {
-          "type": "string"
-        },
-        "key_type": {
-          "type": "string"
-        },
-        "authorized_user_uuid": {
-          "type": "string"
-        },
-        "public_key": {
-          "type": "text"
-        },
-        "expires_at": {
-          "type": "datetime"
-        },
-        "created_at": {
-          "type": "datetime"
-        }
-      }
-    },
-    "CollectionList": {
-      "id": "CollectionList",
-      "description": "Collection list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#collectionList.",
-          "default": "arvados#collectionList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Collections.",
-          "items": {
-            "$ref": "Collection"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Collections."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Collections."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Collection": {
-      "id": "Collection",
-      "description": "Collection",
-      "type": "object",
-      "uuidPrefix": "4zz18",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "portable_data_hash": {
-          "type": "string"
-        },
-        "replication_desired": {
-          "type": "integer"
-        },
-        "replication_confirmed_at": {
-          "type": "datetime"
-        },
-        "replication_confirmed": {
-          "type": "integer"
-        },
-        "manifest_text": {
-          "type": "text"
-        },
-        "name": {
-          "type": "string"
-        },
-        "description": {
-          "type": "string"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "delete_at": {
-          "type": "datetime"
-        },
-        "trash_at": {
-          "type": "datetime"
-        },
-        "is_trashed": {
-          "type": "boolean"
-        },
-        "storage_classes_desired": {
-          "type": "Array"
-        },
-        "storage_classes_confirmed": {
-          "type": "Array"
-        },
-        "storage_classes_confirmed_at": {
-          "type": "datetime"
-        },
-        "current_version_uuid": {
-          "type": "string"
-        },
-        "version": {
-          "type": "integer"
-        },
-        "preserve_version": {
-          "type": "boolean"
-        },
-        "file_count": {
-          "type": "integer"
-        },
-        "file_size_total": {
-          "type": "integer"
-        }
-      }
-    },
-    "ContainerList": {
-      "id": "ContainerList",
-      "description": "Container list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#containerList.",
-          "default": "arvados#containerList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Containers.",
-          "items": {
-            "$ref": "Container"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Containers."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Containers."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Container": {
-      "id": "Container",
-      "description": "Container",
-      "type": "object",
-      "uuidPrefix": "dz642",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "state": {
-          "type": "string"
-        },
-        "started_at": {
-          "type": "datetime"
-        },
-        "finished_at": {
-          "type": "datetime"
-        },
-        "log": {
-          "type": "string"
-        },
-        "environment": {
-          "type": "Hash"
-        },
-        "cwd": {
-          "type": "string"
-        },
-        "command": {
-          "type": "Array"
-        },
-        "output_path": {
-          "type": "string"
-        },
-        "mounts": {
-          "type": "Hash"
-        },
-        "runtime_constraints": {
-          "type": "Hash"
-        },
-        "output": {
-          "type": "string"
-        },
-        "container_image": {
-          "type": "string"
-        },
-        "progress": {
-          "type": "float"
-        },
-        "priority": {
-          "type": "integer"
-        },
-        "exit_code": {
-          "type": "integer"
-        },
-        "auth_uuid": {
-          "type": "string"
-        },
-        "locked_by_uuid": {
-          "type": "string"
-        },
-        "scheduling_parameters": {
-          "type": "Hash"
-        },
-        "runtime_status": {
-          "type": "Hash"
-        },
-        "runtime_user_uuid": {
-          "type": "text"
-        },
-        "runtime_auth_scopes": {
-          "type": "Array"
-        },
-        "lock_count": {
-          "type": "integer"
-        },
-        "gateway_address": {
-          "type": "string"
-        },
-        "interactive_session_started": {
-          "type": "boolean"
-        },
-        "output_storage_classes": {
-          "type": "Array"
-        },
-        "output_properties": {
-          "type": "Hash"
-        },
-        "cost": {
-          "type": "float"
-        },
-        "subrequests_cost": {
-          "type": "float"
-        }
-      }
-    },
-    "ContainerRequestList": {
-      "id": "ContainerRequestList",
-      "description": "ContainerRequest list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#containerRequestList.",
-          "default": "arvados#containerRequestList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of ContainerRequests.",
-          "items": {
-            "$ref": "ContainerRequest"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of ContainerRequests."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of ContainerRequests."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "ContainerRequest": {
-      "id": "ContainerRequest",
-      "description": "ContainerRequest",
-      "type": "object",
-      "uuidPrefix": "xvhdp",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "name": {
-          "type": "string"
-        },
-        "description": {
-          "type": "text"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "state": {
-          "type": "string"
-        },
-        "requesting_container_uuid": {
-          "type": "string"
-        },
-        "container_uuid": {
-          "type": "string"
-        },
-        "container_count_max": {
-          "type": "integer"
-        },
-        "mounts": {
-          "type": "Hash"
-        },
-        "runtime_constraints": {
-          "type": "Hash"
-        },
-        "container_image": {
-          "type": "string"
-        },
-        "environment": {
-          "type": "Hash"
-        },
-        "cwd": {
-          "type": "string"
-        },
-        "command": {
-          "type": "Array"
-        },
-        "output_path": {
-          "type": "string"
-        },
-        "priority": {
-          "type": "integer"
-        },
-        "expires_at": {
-          "type": "datetime"
-        },
-        "filters": {
-          "type": "text"
-        },
-        "container_count": {
-          "type": "integer"
-        },
-        "use_existing": {
-          "type": "boolean"
-        },
-        "scheduling_parameters": {
-          "type": "Hash"
-        },
-        "output_uuid": {
-          "type": "string"
-        },
-        "log_uuid": {
-          "type": "string"
-        },
-        "output_name": {
-          "type": "string"
-        },
-        "output_ttl": {
-          "type": "integer"
-        },
-        "output_storage_classes": {
-          "type": "Array"
-        },
-        "output_properties": {
-          "type": "Hash"
-        },
-        "cumulative_cost": {
-          "type": "float"
-        }
-      }
-    },
-    "GroupList": {
-      "id": "GroupList",
-      "description": "Group list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#groupList.",
-          "default": "arvados#groupList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Groups.",
-          "items": {
-            "$ref": "Group"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Groups."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Groups."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Group": {
-      "id": "Group",
-      "description": "Group",
-      "type": "object",
-      "uuidPrefix": "j7d0g",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "created_at": {
-          "type": "datetime"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "name": {
-          "type": "string"
-        },
-        "description": {
-          "type": "string"
-        },
-        "group_class": {
-          "type": "string"
-        },
-        "trash_at": {
-          "type": "datetime"
-        },
-        "is_trashed": {
-          "type": "boolean"
-        },
-        "delete_at": {
-          "type": "datetime"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "frozen_by_uuid": {
-          "type": "string"
+          "id": "arvados.workflows.destroy",
+          "path": "workflows/{uuid}",
+          "httpMethod": "DELETE",
+          "description": "destroy workflows",
+          "parameters": {
+            "uuid": {
+              "type": "string",
+              "description": "",
+              "required": true,
+              "location": "path"
+            }
+          },
+          "response": {
+            "$ref": "Workflow"
+          },
+          "scopes": [
+            "https://api.arvados.org/auth/arvados"
+          ]
         }
       }
     },
         }
       }
     },
-    "HumanList": {
-      "id": "HumanList",
-      "description": "Human list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#humanList.",
-          "default": "arvados#humanList"
-        },
-        "etag": {
-          "type": "string",
-          "description": "List version."
-        },
-        "items": {
-          "type": "array",
-          "description": "The list of Humans.",
-          "items": {
-            "$ref": "Human"
-          }
-        },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Humans."
-        },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Humans."
-        },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
+    "configs": {
+      "methods": {
+        "get": {
+          "id": "arvados.configs.get",
+          "path": "config",
+          "httpMethod": "GET",
+          "description": "Get public config",
+          "parameters": {},
+          "parameterOrder": [],
+          "response": {},
+          "scopes": [
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
+          ]
         }
       }
     },
         }
       }
     },
-    "Human": {
-      "id": "Human",
-      "description": "Human",
-      "type": "object",
-      "uuidPrefix": "7a9it",
-      "properties": {
-        "uuid": {
-          "type": "string"
-        },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
-        },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "properties": {
-          "type": "Hash"
-        },
-        "created_at": {
-          "type": "datetime"
+    "vocabularies": {
+      "methods": {
+        "get": {
+          "id": "arvados.vocabularies.get",
+          "path": "vocabulary",
+          "httpMethod": "GET",
+          "description": "Get vocabulary definition",
+          "parameters": {},
+          "parameterOrder": [],
+          "response": {},
+          "scopes": [
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
+          ]
         }
       }
     },
         }
       }
     },
-    "JobList": {
-      "id": "JobList",
-      "description": "Job list",
+    "sys": {
+      "methods": {
+        "get": {
+          "id": "arvados.sys.trash_sweep",
+          "path": "sys/trash_sweep",
+          "httpMethod": "POST",
+          "description": "apply scheduled trash and delete operations",
+          "parameters": {},
+          "parameterOrder": [],
+          "response": {},
+          "scopes": [
+            "https://api.arvados.org/auth/arvados",
+            "https://api.arvados.org/auth/arvados.readonly"
+          ]
+        }
+      }
+    }
+  },
+  "revision": "20231117",
+  "schemas": {
+    "ApiClientList": {
+      "id": "ApiClientList",
+      "description": "ApiClient list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#jobList.",
-          "default": "arvados#jobList"
+          "description": "Object type. Always arvados#apiClientList.",
+          "default": "arvados#apiClientList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of Jobs.",
+          "description": "The list of ApiClients.",
           "items": {
           "items": {
-            "$ref": "Job"
+            "$ref": "ApiClient"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Jobs."
+          "description": "A link to the next page of ApiClients."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Jobs."
+          "description": "The page token for the next page of ApiClients."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "Job": {
-      "id": "Job",
-      "description": "Job",
+    "ApiClient": {
+      "id": "ApiClient",
+      "description": "ApiClient",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "8i9sb",
+      "uuidPrefix": "ozdt8",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "modified_at": {
           "type": "datetime"
         },
         "modified_at": {
           "type": "datetime"
         },
-        "submit_id": {
-          "type": "string"
-        },
-        "script": {
-          "type": "string"
-        },
-        "script_version": {
-          "type": "string"
-        },
-        "script_parameters": {
-          "type": "Hash"
-        },
-        "cancelled_by_client_uuid": {
-          "type": "string"
-        },
-        "cancelled_by_user_uuid": {
+        "name": {
           "type": "string"
         },
           "type": "string"
         },
-        "cancelled_at": {
-          "type": "datetime"
-        },
-        "started_at": {
-          "type": "datetime"
-        },
-        "finished_at": {
-          "type": "datetime"
-        },
-        "running": {
-          "type": "boolean"
-        },
-        "success": {
-          "type": "boolean"
-        },
-        "output": {
+        "url_prefix": {
           "type": "string"
         },
         "created_at": {
           "type": "datetime"
         },
           "type": "string"
         },
         "created_at": {
           "type": "datetime"
         },
-        "is_locked_by_uuid": {
-          "type": "string"
-        },
-        "log": {
-          "type": "string"
-        },
-        "tasks_summary": {
-          "type": "Hash"
-        },
-        "runtime_constraints": {
-          "type": "Hash"
-        },
-        "nondeterministic": {
+        "is_trusted": {
           "type": "boolean"
           "type": "boolean"
-        },
-        "repository": {
-          "type": "string"
-        },
-        "supplied_script_version": {
-          "type": "string"
-        },
-        "docker_image_locator": {
-          "type": "string"
-        },
-        "priority": {
-          "type": "integer"
-        },
-        "description": {
-          "type": "string"
-        },
-        "state": {
-          "type": "string"
-        },
-        "arvados_sdk_version": {
-          "type": "string"
-        },
-        "components": {
-          "type": "Hash"
         }
       }
     },
         }
       }
     },
-    "JobTaskList": {
-      "id": "JobTaskList",
-      "description": "JobTask list",
+    "ApiClientAuthorizationList": {
+      "id": "ApiClientAuthorizationList",
+      "description": "ApiClientAuthorization list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#jobTaskList.",
-          "default": "arvados#jobTaskList"
+          "description": "Object type. Always arvados#apiClientAuthorizationList.",
+          "default": "arvados#apiClientAuthorizationList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of JobTasks.",
+          "description": "The list of ApiClientAuthorizations.",
           "items": {
           "items": {
-            "$ref": "JobTask"
+            "$ref": "ApiClientAuthorization"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of JobTasks."
+          "description": "A link to the next page of ApiClientAuthorizations."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of JobTasks."
+          "description": "The page token for the next page of ApiClientAuthorizations."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "JobTask": {
-      "id": "JobTask",
-      "description": "JobTask",
+    "ApiClientAuthorization": {
+      "id": "ApiClientAuthorization",
+      "description": "ApiClientAuthorization",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "ot0gb",
+      "uuidPrefix": "gj3su",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
           "type": "string",
           "description": "Object version."
         },
           "type": "string",
           "description": "Object version."
         },
-        "owner_uuid": {
-          "type": "string"
-        },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
-        "modified_at": {
-          "type": "datetime"
-        },
-        "job_uuid": {
+        "api_token": {
           "type": "string"
         },
           "type": "string"
         },
-        "sequence": {
+        "api_client_id": {
           "type": "integer"
         },
           "type": "integer"
         },
-        "parameters": {
-          "type": "Hash"
-        },
-        "output": {
-          "type": "text"
-        },
-        "progress": {
-          "type": "float"
-        },
-        "success": {
-          "type": "boolean"
+        "user_id": {
+          "type": "integer"
         },
         },
-        "created_at": {
-          "type": "datetime"
+        "created_by_ip_address": {
+          "type": "string"
         },
         },
-        "created_by_job_task_uuid": {
+        "last_used_by_ip_address": {
           "type": "string"
         },
           "type": "string"
         },
-        "qsequence": {
-          "type": "integer"
+        "last_used_at": {
+          "type": "datetime"
         },
         },
-        "started_at": {
+        "expires_at": {
           "type": "datetime"
         },
           "type": "datetime"
         },
-        "finished_at": {
+        "created_at": {
           "type": "datetime"
           "type": "datetime"
+        },
+        "default_owner_uuid": {
+          "type": "string"
+        },
+        "scopes": {
+          "type": "Array"
         }
       }
     },
         }
       }
     },
-    "KeepDiskList": {
-      "id": "KeepDiskList",
-      "description": "KeepDisk list",
+    "AuthorizedKeyList": {
+      "id": "AuthorizedKeyList",
+      "description": "AuthorizedKey list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#keepDiskList.",
-          "default": "arvados#keepDiskList"
+          "description": "Object type. Always arvados#authorizedKeyList.",
+          "default": "arvados#authorizedKeyList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of KeepDisks.",
+          "description": "The list of AuthorizedKeys.",
           "items": {
           "items": {
-            "$ref": "KeepDisk"
+            "$ref": "AuthorizedKey"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of KeepDisks."
+          "description": "A link to the next page of AuthorizedKeys."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of KeepDisks."
+          "description": "The page token for the next page of AuthorizedKeys."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "KeepDisk": {
-      "id": "KeepDisk",
-      "description": "KeepDisk",
+    "AuthorizedKey": {
+      "id": "AuthorizedKey",
+      "description": "AuthorizedKey",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "penuu",
+      "uuidPrefix": "fngyi",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "modified_at": {
           "type": "datetime"
         },
         "modified_at": {
           "type": "datetime"
         },
-        "node_uuid": {
+        "name": {
           "type": "string"
         },
           "type": "string"
         },
-        "filesystem_uuid": {
+        "key_type": {
           "type": "string"
         },
           "type": "string"
         },
-        "bytes_total": {
-          "type": "integer"
-        },
-        "bytes_free": {
-          "type": "integer"
-        },
-        "is_readable": {
-          "type": "boolean"
-        },
-        "is_writable": {
-          "type": "boolean"
-        },
-        "last_read_at": {
-          "type": "datetime"
+        "authorized_user_uuid": {
+          "type": "string"
         },
         },
-        "last_write_at": {
-          "type": "datetime"
+        "public_key": {
+          "type": "text"
         },
         },
-        "last_ping_at": {
+        "expires_at": {
           "type": "datetime"
         },
         "created_at": {
           "type": "datetime"
           "type": "datetime"
         },
         "created_at": {
           "type": "datetime"
-        },
-        "keep_service_uuid": {
-          "type": "string"
         }
       }
     },
         }
       }
     },
-    "KeepServiceList": {
-      "id": "KeepServiceList",
-      "description": "KeepService list",
+    "CollectionList": {
+      "id": "CollectionList",
+      "description": "Collection list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#keepServiceList.",
-          "default": "arvados#keepServiceList"
+          "description": "Object type. Always arvados#collectionList.",
+          "default": "arvados#collectionList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of KeepServices.",
+          "description": "The list of Collections.",
           "items": {
           "items": {
-            "$ref": "KeepService"
+            "$ref": "Collection"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of KeepServices."
+          "description": "A link to the next page of Collections."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of KeepServices."
+          "description": "The page token for the next page of Collections."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "KeepService": {
-      "id": "KeepService",
-      "description": "KeepService",
+    "Collection": {
+      "id": "Collection",
+      "description": "Collection",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "bi6l4",
+      "uuidPrefix": "4zz18",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "owner_uuid": {
           "type": "string"
         },
         "owner_uuid": {
           "type": "string"
         },
+        "created_at": {
+          "type": "datetime"
+        },
         "modified_by_client_uuid": {
           "type": "string"
         },
         "modified_by_client_uuid": {
           "type": "string"
         },
         "modified_at": {
           "type": "datetime"
         },
         "modified_at": {
           "type": "datetime"
         },
-        "service_host": {
+        "portable_data_hash": {
           "type": "string"
         },
           "type": "string"
         },
-        "service_port": {
+        "replication_desired": {
           "type": "integer"
         },
           "type": "integer"
         },
-        "service_ssl_flag": {
-          "type": "boolean"
+        "replication_confirmed_at": {
+          "type": "datetime"
         },
         },
-        "service_type": {
+        "replication_confirmed": {
+          "type": "integer"
+        },
+        "manifest_text": {
+          "type": "text"
+        },
+        "name": {
           "type": "string"
         },
           "type": "string"
         },
-        "created_at": {
+        "description": {
+          "type": "string"
+        },
+        "properties": {
+          "type": "Hash"
+        },
+        "delete_at": {
           "type": "datetime"
         },
           "type": "datetime"
         },
-        "read_only": {
+        "trash_at": {
+          "type": "datetime"
+        },
+        "is_trashed": {
+          "type": "boolean"
+        },
+        "storage_classes_desired": {
+          "type": "Array"
+        },
+        "storage_classes_confirmed": {
+          "type": "Array"
+        },
+        "storage_classes_confirmed_at": {
+          "type": "datetime"
+        },
+        "current_version_uuid": {
+          "type": "string"
+        },
+        "version": {
+          "type": "integer"
+        },
+        "preserve_version": {
           "type": "boolean"
           "type": "boolean"
+        },
+        "file_count": {
+          "type": "integer"
+        },
+        "file_size_total": {
+          "type": "integer"
         }
       }
     },
         }
       }
     },
-    "LinkList": {
-      "id": "LinkList",
-      "description": "Link list",
+    "ContainerList": {
+      "id": "ContainerList",
+      "description": "Container list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#linkList.",
-          "default": "arvados#linkList"
+          "description": "Object type. Always arvados#containerList.",
+          "default": "arvados#containerList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of Links.",
+          "description": "The list of Containers.",
           "items": {
           "items": {
-            "$ref": "Link"
+            "$ref": "Container"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Links."
+          "description": "A link to the next page of Containers."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Links."
+          "description": "The page token for the next page of Containers."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "Link": {
-      "id": "Link",
-      "description": "Link",
+    "Container": {
+      "id": "Container",
+      "description": "Container",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "o0j2j",
+      "uuidPrefix": "dz642",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "created_at": {
           "type": "datetime"
         },
         "created_at": {
           "type": "datetime"
         },
+        "modified_at": {
+          "type": "datetime"
+        },
         "modified_by_client_uuid": {
           "type": "string"
         },
         "modified_by_user_uuid": {
           "type": "string"
         },
         "modified_by_client_uuid": {
           "type": "string"
         },
         "modified_by_user_uuid": {
           "type": "string"
         },
-        "modified_at": {
+        "state": {
+          "type": "string"
+        },
+        "started_at": {
           "type": "datetime"
         },
           "type": "datetime"
         },
-        "tail_uuid": {
-          "type": "string"
+        "finished_at": {
+          "type": "datetime"
         },
         },
-        "link_class": {
+        "log": {
           "type": "string"
         },
           "type": "string"
         },
-        "name": {
-          "type": "string"
+        "environment": {
+          "type": "Hash"
         },
         },
-        "head_uuid": {
+        "cwd": {
           "type": "string"
         },
           "type": "string"
         },
-        "properties": {
-          "type": "Hash"
-        }
-      }
-    },
-    "LogList": {
-      "id": "LogList",
-      "description": "Log list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#logList.",
-          "default": "arvados#logList"
+        "command": {
+          "type": "Array"
         },
         },
-        "etag": {
-          "type": "string",
-          "description": "List version."
+        "output_path": {
+          "type": "string"
         },
         },
-        "items": {
-          "type": "array",
-          "description": "The list of Logs.",
-          "items": {
-            "$ref": "Log"
-          }
+        "mounts": {
+          "type": "Hash"
         },
         },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of Logs."
+        "runtime_constraints": {
+          "type": "Hash"
         },
         },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of Logs."
+        "output": {
+          "type": "string"
         },
         },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "Log": {
-      "id": "Log",
-      "description": "Log",
-      "type": "object",
-      "uuidPrefix": "57u5n",
-      "properties": {
-        "uuid": {
+        "container_image": {
           "type": "string"
         },
           "type": "string"
         },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
+        "progress": {
+          "type": "float"
         },
         },
-        "id": {
+        "priority": {
           "type": "integer"
         },
           "type": "integer"
         },
-        "owner_uuid": {
-          "type": "string"
+        "exit_code": {
+          "type": "integer"
         },
         },
-        "modified_by_client_uuid": {
+        "auth_uuid": {
           "type": "string"
         },
           "type": "string"
         },
-        "modified_by_user_uuid": {
+        "locked_by_uuid": {
           "type": "string"
         },
           "type": "string"
         },
-        "object_uuid": {
-          "type": "string"
+        "scheduling_parameters": {
+          "type": "Hash"
         },
         },
-        "event_at": {
-          "type": "datetime"
+        "runtime_status": {
+          "type": "Hash"
         },
         },
-        "event_type": {
+        "runtime_user_uuid": {
+          "type": "text"
+        },
+        "runtime_auth_scopes": {
+          "type": "Array"
+        },
+        "lock_count": {
+          "type": "integer"
+        },
+        "gateway_address": {
           "type": "string"
         },
           "type": "string"
         },
-        "summary": {
-          "type": "text"
+        "interactive_session_started": {
+          "type": "boolean"
+        },
+        "output_storage_classes": {
+          "type": "Array"
         },
         },
-        "properties": {
+        "output_properties": {
           "type": "Hash"
         },
           "type": "Hash"
         },
-        "created_at": {
-          "type": "datetime"
+        "cost": {
+          "type": "float"
         },
         },
-        "modified_at": {
-          "type": "datetime"
+        "subrequests_cost": {
+          "type": "float"
         },
         },
-        "object_owner_uuid": {
-          "type": "string"
+        "output_glob": {
+          "type": "Array"
         }
       }
     },
         }
       }
     },
-    "NodeList": {
-      "id": "NodeList",
-      "description": "Node list",
+    "ContainerRequestList": {
+      "id": "ContainerRequestList",
+      "description": "ContainerRequest list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#nodeList.",
-          "default": "arvados#nodeList"
+          "description": "Object type. Always arvados#containerRequestList.",
+          "default": "arvados#containerRequestList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of Nodes.",
+          "description": "The list of ContainerRequests.",
           "items": {
           "items": {
-            "$ref": "Node"
+            "$ref": "ContainerRequest"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Nodes."
+          "description": "A link to the next page of ContainerRequests."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Nodes."
+          "description": "The page token for the next page of ContainerRequests."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "Node": {
-      "id": "Node",
-      "description": "Node",
+    "ContainerRequest": {
+      "id": "ContainerRequest",
+      "description": "ContainerRequest",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "7ekkf",
+      "uuidPrefix": "xvhdp",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "created_at": {
           "type": "datetime"
         },
         "created_at": {
           "type": "datetime"
         },
-        "modified_by_client_uuid": {
-          "type": "string"
-        },
-        "modified_by_user_uuid": {
-          "type": "string"
-        },
         "modified_at": {
           "type": "datetime"
         },
         "modified_at": {
           "type": "datetime"
         },
-        "slot_number": {
-          "type": "integer"
-        },
-        "hostname": {
+        "modified_by_client_uuid": {
           "type": "string"
         },
           "type": "string"
         },
-        "domain": {
+        "modified_by_user_uuid": {
           "type": "string"
         },
           "type": "string"
         },
-        "ip_address": {
+        "name": {
           "type": "string"
         },
           "type": "string"
         },
-        "last_ping_at": {
-          "type": "datetime"
+        "description": {
+          "type": "text"
         },
         "properties": {
           "type": "Hash"
         },
         },
         "properties": {
           "type": "Hash"
         },
-        "job_uuid": {
+        "state": {
           "type": "string"
           "type": "string"
-        }
-      }
-    },
-    "PipelineInstanceList": {
-      "id": "PipelineInstanceList",
-      "description": "PipelineInstance list",
-      "type": "object",
-      "properties": {
-        "kind": {
-          "type": "string",
-          "description": "Object type. Always arvados#pipelineInstanceList.",
-          "default": "arvados#pipelineInstanceList"
         },
         },
-        "etag": {
-          "type": "string",
-          "description": "List version."
+        "requesting_container_uuid": {
+          "type": "string"
         },
         },
-        "items": {
-          "type": "array",
-          "description": "The list of PipelineInstances.",
-          "items": {
-            "$ref": "PipelineInstance"
-          }
+        "container_uuid": {
+          "type": "string"
         },
         },
-        "next_link": {
-          "type": "string",
-          "description": "A link to the next page of PipelineInstances."
+        "container_count_max": {
+          "type": "integer"
         },
         },
-        "next_page_token": {
-          "type": "string",
-          "description": "The page token for the next page of PipelineInstances."
+        "mounts": {
+          "type": "Hash"
         },
         },
-        "selfLink": {
-          "type": "string",
-          "description": "A link back to this list."
-        }
-      }
-    },
-    "PipelineInstance": {
-      "id": "PipelineInstance",
-      "description": "PipelineInstance",
-      "type": "object",
-      "uuidPrefix": "d1hrv",
-      "properties": {
-        "uuid": {
+        "runtime_constraints": {
+          "type": "Hash"
+        },
+        "container_image": {
           "type": "string"
         },
           "type": "string"
         },
-        "etag": {
-          "type": "string",
-          "description": "Object version."
+        "environment": {
+          "type": "Hash"
         },
         },
-        "owner_uuid": {
+        "cwd": {
           "type": "string"
         },
           "type": "string"
         },
-        "created_at": {
-          "type": "datetime"
+        "command": {
+          "type": "Array"
         },
         },
-        "modified_by_client_uuid": {
+        "output_path": {
           "type": "string"
         },
           "type": "string"
         },
-        "modified_by_user_uuid": {
-          "type": "string"
+        "priority": {
+          "type": "integer"
         },
         },
-        "modified_at": {
+        "expires_at": {
           "type": "datetime"
         },
           "type": "datetime"
         },
-        "pipeline_template_uuid": {
-          "type": "string"
+        "filters": {
+          "type": "text"
         },
         },
-        "name": {
-          "type": "string"
+        "container_count": {
+          "type": "integer"
         },
         },
-        "components": {
-          "type": "Hash"
+        "use_existing": {
+          "type": "boolean"
         },
         },
-        "properties": {
+        "scheduling_parameters": {
           "type": "Hash"
         },
           "type": "Hash"
         },
-        "state": {
+        "output_uuid": {
           "type": "string"
         },
           "type": "string"
         },
-        "components_summary": {
-          "type": "Hash"
+        "log_uuid": {
+          "type": "string"
         },
         },
-        "started_at": {
-          "type": "datetime"
+        "output_name": {
+          "type": "string"
         },
         },
-        "finished_at": {
-          "type": "datetime"
+        "output_ttl": {
+          "type": "integer"
         },
         },
-        "description": {
-          "type": "string"
+        "output_storage_classes": {
+          "type": "Array"
+        },
+        "output_properties": {
+          "type": "Hash"
+        },
+        "cumulative_cost": {
+          "type": "float"
+        },
+        "output_glob": {
+          "type": "Array"
         }
       }
     },
         }
       }
     },
-    "PipelineTemplateList": {
-      "id": "PipelineTemplateList",
-      "description": "PipelineTemplate list",
+    "GroupList": {
+      "id": "GroupList",
+      "description": "Group list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#pipelineTemplateList.",
-          "default": "arvados#pipelineTemplateList"
+          "description": "Object type. Always arvados#groupList.",
+          "default": "arvados#groupList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of PipelineTemplates.",
+          "description": "The list of Groups.",
           "items": {
           "items": {
-            "$ref": "PipelineTemplate"
+            "$ref": "Group"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of PipelineTemplates."
+          "description": "A link to the next page of Groups."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of PipelineTemplates."
+          "description": "The page token for the next page of Groups."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "PipelineTemplate": {
-      "id": "PipelineTemplate",
-      "description": "PipelineTemplate",
+    "Group": {
+      "id": "Group",
+      "description": "Group",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "p5p6p",
+      "uuidPrefix": "j7d0g",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "name": {
           "type": "string"
         },
         "name": {
           "type": "string"
         },
-        "components": {
+        "description": {
+          "type": "string"
+        },
+        "group_class": {
+          "type": "string"
+        },
+        "trash_at": {
+          "type": "datetime"
+        },
+        "is_trashed": {
+          "type": "boolean"
+        },
+        "delete_at": {
+          "type": "datetime"
+        },
+        "properties": {
           "type": "Hash"
         },
           "type": "Hash"
         },
-        "description": {
+        "frozen_by_uuid": {
           "type": "string"
         }
       }
     },
           "type": "string"
         }
       }
     },
-    "RepositoryList": {
-      "id": "RepositoryList",
-      "description": "Repository list",
+    "KeepServiceList": {
+      "id": "KeepServiceList",
+      "description": "KeepService list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#repositoryList.",
-          "default": "arvados#repositoryList"
+          "description": "Object type. Always arvados#keepServiceList.",
+          "default": "arvados#keepServiceList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of Repositories.",
+          "description": "The list of KeepServices.",
           "items": {
           "items": {
-            "$ref": "Repository"
+            "$ref": "KeepService"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Repositories."
+          "description": "A link to the next page of KeepServices."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Repositories."
+          "description": "The page token for the next page of KeepServices."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "Repository": {
-      "id": "Repository",
-      "description": "Repository",
+    "KeepService": {
+      "id": "KeepService",
+      "description": "KeepService",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "s0uqq",
+      "uuidPrefix": "bi6l4",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "modified_at": {
           "type": "datetime"
         },
         "modified_at": {
           "type": "datetime"
         },
-        "name": {
+        "service_host": {
+          "type": "string"
+        },
+        "service_port": {
+          "type": "integer"
+        },
+        "service_ssl_flag": {
+          "type": "boolean"
+        },
+        "service_type": {
           "type": "string"
         },
         "created_at": {
           "type": "datetime"
           "type": "string"
         },
         "created_at": {
           "type": "datetime"
+        },
+        "read_only": {
+          "type": "boolean"
         }
       }
     },
         }
       }
     },
-    "SpecimenList": {
-      "id": "SpecimenList",
-      "description": "Specimen list",
+    "LinkList": {
+      "id": "LinkList",
+      "description": "Link list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#specimenList.",
-          "default": "arvados#specimenList"
+          "description": "Object type. Always arvados#linkList.",
+          "default": "arvados#linkList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of Specimens.",
+          "description": "The list of Links.",
           "items": {
           "items": {
-            "$ref": "Specimen"
+            "$ref": "Link"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Specimens."
+          "description": "A link to the next page of Links."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Specimens."
+          "description": "The page token for the next page of Links."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "Specimen": {
-      "id": "Specimen",
-      "description": "Specimen",
+    "Link": {
+      "id": "Link",
+      "description": "Link",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "j58dm",
+      "uuidPrefix": "o0j2j",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
         "modified_at": {
           "type": "datetime"
         },
         "modified_at": {
           "type": "datetime"
         },
-        "material": {
+        "tail_uuid": {
+          "type": "string"
+        },
+        "link_class": {
+          "type": "string"
+        },
+        "name": {
+          "type": "string"
+        },
+        "head_uuid": {
           "type": "string"
         },
         "properties": {
           "type": "string"
         },
         "properties": {
         }
       }
     },
         }
       }
     },
-    "TraitList": {
-      "id": "TraitList",
-      "description": "Trait list",
+    "LogList": {
+      "id": "LogList",
+      "description": "Log list",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
       "type": "object",
       "properties": {
         "kind": {
           "type": "string",
-          "description": "Object type. Always arvados#traitList.",
-          "default": "arvados#traitList"
+          "description": "Object type. Always arvados#logList.",
+          "default": "arvados#logList"
         },
         "etag": {
           "type": "string",
         },
         "etag": {
           "type": "string",
         },
         "items": {
           "type": "array",
         },
         "items": {
           "type": "array",
-          "description": "The list of Traits.",
+          "description": "The list of Logs.",
           "items": {
           "items": {
-            "$ref": "Trait"
+            "$ref": "Log"
           }
         },
         "next_link": {
           "type": "string",
           }
         },
         "next_link": {
           "type": "string",
-          "description": "A link to the next page of Traits."
+          "description": "A link to the next page of Logs."
         },
         "next_page_token": {
           "type": "string",
         },
         "next_page_token": {
           "type": "string",
-          "description": "The page token for the next page of Traits."
+          "description": "The page token for the next page of Logs."
         },
         "selfLink": {
           "type": "string",
         },
         "selfLink": {
           "type": "string",
         }
       }
     },
         }
       }
     },
-    "Trait": {
-      "id": "Trait",
-      "description": "Trait",
+    "Log": {
+      "id": "Log",
+      "description": "Log",
       "type": "object",
       "type": "object",
-      "uuidPrefix": "q1cn2",
+      "uuidPrefix": "57u5n",
       "properties": {
         "uuid": {
           "type": "string"
       "properties": {
         "uuid": {
           "type": "string"
           "type": "string",
           "description": "Object version."
         },
           "type": "string",
           "description": "Object version."
         },
+        "id": {
+          "type": "integer"
+        },
         "owner_uuid": {
           "type": "string"
         },
         "owner_uuid": {
           "type": "string"
         },
         "modified_by_user_uuid": {
           "type": "string"
         },
         "modified_by_user_uuid": {
           "type": "string"
         },
-        "modified_at": {
+        "object_uuid": {
+          "type": "string"
+        },
+        "event_at": {
           "type": "datetime"
         },
           "type": "datetime"
         },
-        "name": {
+        "event_type": {
           "type": "string"
         },
           "type": "string"
         },
+        "summary": {
+          "type": "text"
+        },
         "properties": {
           "type": "Hash"
         },
         "created_at": {
           "type": "datetime"
         "properties": {
           "type": "Hash"
         },
         "created_at": {
           "type": "datetime"
+        },
+        "modified_at": {
+          "type": "datetime"
+        },
+        "object_owner_uuid": {
+          "type": "string"
         }
       }
     },
         }
       }
     },
index c72b82be1cef2ae9778a5e99217e304f7c59f828..81dd027f201d42b17915b2d630309950d989b403 100644 (file)
@@ -2,11 +2,10 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from . import config
-
 import re
 
 import re
 
+from . import config
+
 def escape(path):
     return re.sub(r'[\\:\000-\040]', lambda m: "\\%03o" % ord(m.group(0)), path)
 
 def escape(path):
     return re.sub(r'[\\:\000-\040]', lambda m: "\\%03o" % ord(m.group(0)), path)
 
index bb245ab2bf3f0cce8e374e8215a3f73200e9bded..75678457c96b8039b52d912988615223b77b0cef 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import division
-from builtins import object
 import logging
 
 _logger = logging.getLogger('arvados.ranges')
 import logging
 
 _logger = logging.getLogger('arvados.ranges')
index 4b95835aac0f25a57fd999a2c5f9cff0e54014e1..de2025435151d8f04eb7e8e7d7c74084083c8256 100644 (file)
@@ -2,13 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from __future__ import division
-from future import standard_library
-from future.utils import listitems, listvalues
-standard_library.install_aliases()
-from builtins import range
-from builtins import object
 import bz2
 import collections
 import copy
 import bz2
 import collections
 import copy
@@ -491,7 +484,7 @@ class _BlockManager(object):
         self._put_queue = None
         self._put_threads = None
         self.lock = threading.Lock()
         self._put_queue = None
         self._put_threads = None
         self.lock = threading.Lock()
-        self.prefetch_enabled = True
+        self.prefetch_lookahead = self._keep.num_prefetch_threads
         self.num_put_threads = put_threads or _BlockManager.DEFAULT_PUT_THREADS
         self.copies = copies
         self.storage_classes = storage_classes_func or (lambda: [])
         self.num_put_threads = put_threads or _BlockManager.DEFAULT_PUT_THREADS
         self.copies = copies
         self.storage_classes = storage_classes_func or (lambda: [])
@@ -620,7 +613,7 @@ class _BlockManager(object):
         # A WRITABLE block with its owner.closed() implies that its
         # size is <= KEEP_BLOCK_SIZE/2.
         try:
         # A WRITABLE block with its owner.closed() implies that its
         # size is <= KEEP_BLOCK_SIZE/2.
         try:
-            small_blocks = [b for b in listvalues(self._bufferblocks)
+            small_blocks = [b for b in self._bufferblocks.values()
                             if b.state() == _BufferBlock.WRITABLE and b.owner.closed()]
         except AttributeError:
             # Writable blocks without owner shouldn't exist.
                             if b.state() == _BufferBlock.WRITABLE and b.owner.closed()]
         except AttributeError:
             # Writable blocks without owner shouldn't exist.
@@ -763,7 +756,7 @@ class _BlockManager(object):
         self.repack_small_blocks(force=True, sync=True)
 
         with self.lock:
         self.repack_small_blocks(force=True, sync=True)
 
         with self.lock:
-            items = listitems(self._bufferblocks)
+            items = list(self._bufferblocks.items())
 
         for k,v in items:
             if v.state() != _BufferBlock.COMMITTED and v.owner:
 
         for k,v in items:
             if v.state() != _BufferBlock.COMMITTED and v.owner:
@@ -803,7 +796,7 @@ class _BlockManager(object):
         """Initiate a background download of a block.
         """
 
         """Initiate a background download of a block.
         """
 
-        if not self.prefetch_enabled:
+        if not self.prefetch_lookahead:
             return
 
         with self.lock:
             return
 
         with self.lock:
@@ -825,7 +818,7 @@ class ArvadosFile(object):
     """
 
     __slots__ = ('parent', 'name', '_writers', '_committed',
     """
 
     __slots__ = ('parent', 'name', '_writers', '_committed',
-                 '_segments', 'lock', '_current_bblock', 'fuse_entry')
+                 '_segments', 'lock', '_current_bblock', 'fuse_entry', '_read_counter')
 
     def __init__(self, parent, name, stream=[], segments=[]):
         """
 
     def __init__(self, parent, name, stream=[], segments=[]):
         """
@@ -846,6 +839,7 @@ class ArvadosFile(object):
         for s in segments:
             self._add_segment(stream, s.locator, s.range_size)
         self._current_bblock = None
         for s in segments:
             self._add_segment(stream, s.locator, s.range_size)
         self._current_bblock = None
+        self._read_counter = 0
 
     def writable(self):
         return self.parent.writable()
 
     def writable(self):
         return self.parent.writable()
@@ -1060,7 +1054,25 @@ class ArvadosFile(object):
             if size == 0 or offset >= self.size():
                 return b''
             readsegs = locators_and_ranges(self._segments, offset, size)
             if size == 0 or offset >= self.size():
                 return b''
             readsegs = locators_and_ranges(self._segments, offset, size)
-            prefetch = locators_and_ranges(self._segments, offset + size, config.KEEP_BLOCK_SIZE * self.parent._my_block_manager()._keep.num_prefetch_threads, limit=32)
+
+            prefetch = None
+            prefetch_lookahead = self.parent._my_block_manager().prefetch_lookahead
+            if prefetch_lookahead:
+                # Doing prefetch on every read() call is surprisingly expensive
+                # when we're trying to deliver data at 600+ MiBps and want
+                # the read() fast path to be as lightweight as possible.
+                #
+                # Only prefetching every 128 read operations
+                # dramatically reduces the overhead while still
+                # getting the benefit of prefetching (e.g. when
+                # reading 128 KiB at a time, it checks for prefetch
+                # every 16 MiB).
+                self._read_counter = (self._read_counter+1) % 128
+                if self._read_counter == 1:
+                    prefetch = locators_and_ranges(self._segments,
+                                                   offset + size,
+                                                   config.KEEP_BLOCK_SIZE * prefetch_lookahead,
+                                                   limit=(1+prefetch_lookahead))
 
         locs = set()
         data = []
 
         locs = set()
         data = []
@@ -1068,17 +1080,21 @@ class ArvadosFile(object):
             block = self.parent._my_block_manager().get_block_contents(lr.locator, num_retries=num_retries, cache_only=(bool(data) and not exact))
             if block:
                 blockview = memoryview(block)
             block = self.parent._my_block_manager().get_block_contents(lr.locator, num_retries=num_retries, cache_only=(bool(data) and not exact))
             if block:
                 blockview = memoryview(block)
-                data.append(blockview[lr.segment_offset:lr.segment_offset+lr.segment_size].tobytes())
+                data.append(blockview[lr.segment_offset:lr.segment_offset+lr.segment_size])
                 locs.add(lr.locator)
             else:
                 break
 
                 locs.add(lr.locator)
             else:
                 break
 
-        for lr in prefetch:
-            if lr.locator not in locs:
-                self.parent._my_block_manager().block_prefetch(lr.locator)
-                locs.add(lr.locator)
+        if prefetch:
+            for lr in prefetch:
+                if lr.locator not in locs:
+                    self.parent._my_block_manager().block_prefetch(lr.locator)
+                    locs.add(lr.locator)
 
 
-        return b''.join(data)
+        if len(data) == 1:
+            return data[0]
+        else:
+            return b''.join(data)
 
     @must_be_writable
     @synchronized
 
     @must_be_writable
     @synchronized
index 85f2b89ea2b7368a2fb509120228538b9e9eb109..115547cf9394f8317dd5b71e3e19299f6819bcf8 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import object
 import errno
 import hashlib
 import os
 import errno
 import hashlib
 import os
index 9e6bd06071b5653a560da72a0f666217d7c0102c..1050d4c09301b317fa2e9fd216f81c6c491e23b9 100644 (file)
@@ -12,11 +12,6 @@ cookbook for [an introduction to using the Collection class][cookbook].
 [cookbook]: https://doc.arvados.org/sdk/python/cookbook.html#working-with-collections
 """
 
 [cookbook]: https://doc.arvados.org/sdk/python/cookbook.html#working-with-collections
 """
 
-from __future__ import absolute_import
-from future.utils import listitems, listvalues, viewkeys
-from builtins import str
-from past.builtins import basestring
-from builtins import object
 import ciso8601
 import datetime
 import errno
 import ciso8601
 import datetime
 import errno
@@ -419,7 +414,7 @@ class RichCollectionBase(CollectionBase):
         if value == self._committed:
             return
         if value:
         if value == self._committed:
             return
         if value:
-            for k,v in listitems(self._items):
+            for k,v in self._items.items():
                 v.set_committed(True)
             self._committed = True
         else:
                 v.set_committed(True)
             self._committed = True
         else:
@@ -434,7 +429,7 @@ class RichCollectionBase(CollectionBase):
         This method does not recurse. It only iterates the contents of this
         collection's corresponding stream.
         """
         This method does not recurse. It only iterates the contents of this
         collection's corresponding stream.
         """
-        return iter(viewkeys(self._items))
+        return iter(self._items)
 
     @synchronized
     def __getitem__(self, k: str) -> CollectionItem:
 
     @synchronized
     def __getitem__(self, k: str) -> CollectionItem:
@@ -492,7 +487,7 @@ class RichCollectionBase(CollectionBase):
         `arvados.arvfile.ArvadosFile` for every file, directly within this
         collection's stream.  This method does not recurse.
         """
         `arvados.arvfile.ArvadosFile` for every file, directly within this
         collection's stream.  This method does not recurse.
         """
-        return listvalues(self._items)
+        return list(self._items.values())
 
     @synchronized
     def items(self) -> List[Tuple[str, CollectionItem]]:
 
     @synchronized
     def items(self) -> List[Tuple[str, CollectionItem]]:
@@ -502,7 +497,7 @@ class RichCollectionBase(CollectionBase):
         `arvados.arvfile.ArvadosFile` for every file, directly within this
         collection's stream.  This method does not recurse.
         """
         `arvados.arvfile.ArvadosFile` for every file, directly within this
         collection's stream.  This method does not recurse.
         """
-        return listitems(self._items)
+        return list(self._items.items())
 
     def exists(self, path: str) -> bool:
         """Indicate whether this collection includes an item at `path`
 
     def exists(self, path: str) -> bool:
         """Indicate whether this collection includes an item at `path`
@@ -548,7 +543,7 @@ class RichCollectionBase(CollectionBase):
             item.remove(pathcomponents[1], recursive=recursive)
 
     def _clonefrom(self, source):
             item.remove(pathcomponents[1], recursive=recursive)
 
     def _clonefrom(self, source):
-        for k,v in listitems(source):
+        for k,v in source.items():
             self._items[k] = v.clone(self, k)
 
     def clone(self):
             self._items[k] = v.clone(self, k)
 
     def clone(self):
@@ -612,7 +607,7 @@ class RichCollectionBase(CollectionBase):
             source_collection = self
 
         # Find the object
             source_collection = self
 
         # Find the object
-        if isinstance(source, basestring):
+        if isinstance(source, str):
             source_obj = source_collection.find(source)
             if source_obj is None:
                 raise IOError(errno.ENOENT, "File not found", source)
             source_obj = source_collection.find(source)
             if source_obj is None:
                 raise IOError(errno.ENOENT, "File not found", source)
@@ -1024,7 +1019,7 @@ class RichCollectionBase(CollectionBase):
     @synchronized
     def flush(self) -> None:
         """Upload any pending data to Keep"""
     @synchronized
     def flush(self) -> None:
         """Upload any pending data to Keep"""
-        for e in listvalues(self):
+        for e in self.values():
             e.flush()
 
 
             e.flush()
 
 
@@ -2258,7 +2253,7 @@ class ResumableCollectionWriter(CollectionWriter):
         return writer
 
     def check_dependencies(self):
         return writer
 
     def check_dependencies(self):
-        for path, orig_stat in listitems(self._dependencies):
+        for path, orig_stat in self._dependencies.items():
             if not S_ISREG(orig_stat[ST_MODE]):
                 raise errors.StaleWriterStateError(u"{} not file".format(path))
             try:
             if not S_ISREG(orig_stat[ST_MODE]):
                 raise errors.StaleWriterStateError(u"{} not file".format(path))
             try:
index 7f5245db863acd0c9c446ce6328b58f237125a3c..eccf488efb4dca5bfadb5613336d55035238c7fa 100755 (executable)
 # instances src and dst.  If either of these files is not found,
 # arv-copy will issue an error.
 
 # instances src and dst.  If either of these files is not found,
 # arv-copy will issue an error.
 
-from __future__ import division
-from future import standard_library
-from future.utils import listvalues
-standard_library.install_aliases()
-from past.builtins import basestring
-from builtins import object
 import argparse
 import contextlib
 import getpass
 import argparse
 import contextlib
 import getpass
@@ -47,7 +41,6 @@ import arvados.util
 import arvados.commands._util as arv_cmd
 import arvados.commands.keepdocker
 import arvados.http_to_keep
 import arvados.commands._util as arv_cmd
 import arvados.commands.keepdocker
 import arvados.http_to_keep
-import ruamel.yaml as yaml
 
 from arvados._version import __version__
 
 
 from arvados._version import __version__
 
@@ -168,7 +161,7 @@ def main():
         exit(1)
 
     # Clean up any outstanding temp git repositories.
         exit(1)
 
     # Clean up any outstanding temp git repositories.
-    for d in listvalues(local_repo_dir):
+    for d in local_repo_dir.values():
         shutil.rmtree(d, ignore_errors=True)
 
     if not result:
         shutil.rmtree(d, ignore_errors=True)
 
     if not result:
@@ -258,10 +251,10 @@ def filter_iter(arg):
     Pass in a filter field that can either be a string or list.
     This will iterate elements as if the field had been written as a list.
     """
     Pass in a filter field that can either be a string or list.
     This will iterate elements as if the field had been written as a list.
     """
-    if isinstance(arg, basestring):
-        return iter((arg,))
+    if isinstance(arg, str):
+        yield arg
     else:
     else:
-        return iter(arg)
+        yield from arg
 
 def migrate_repository_filter(repo_filter, src_repository, dst_repository):
     """Update a single repository filter in-place for the destination.
 
 def migrate_repository_filter(repo_filter, src_repository, dst_repository):
     """Update a single repository filter in-place for the destination.
@@ -409,7 +402,7 @@ def copy_collections(obj, src, dst, args):
                 collections_copied[src_id] = dst_col['uuid']
         return collections_copied[src_id]
 
                 collections_copied[src_id] = dst_col['uuid']
         return collections_copied[src_id]
 
-    if isinstance(obj, basestring):
+    if isinstance(obj, str):
         # Copy any collections identified in this string to dst, replacing
         # them with the dst uuids as necessary.
         obj = arvados.util.portable_data_hash_pattern.sub(copy_collection_fn, obj)
         # Copy any collections identified in this string to dst, replacing
         # them with the dst uuids as necessary.
         obj = arvados.util.portable_data_hash_pattern.sub(copy_collection_fn, obj)
@@ -736,58 +729,6 @@ def copy_collection(obj_uuid, src, dst, args):
     c['manifest_text'] = dst_manifest.getvalue()
     return create_collection_from(c, src, dst, args)
 
     c['manifest_text'] = dst_manifest.getvalue()
     return create_collection_from(c, src, dst, args)
 
-def select_git_url(api, repo_name, retries, allow_insecure_http, allow_insecure_http_opt):
-    r = api.repositories().list(
-        filters=[['name', '=', repo_name]]).execute(num_retries=retries)
-    if r['items_available'] != 1:
-        raise Exception('cannot identify repo {}; {} repos found'
-                        .format(repo_name, r['items_available']))
-
-    https_url = [c for c in r['items'][0]["clone_urls"] if c.startswith("https:")]
-    http_url = [c for c in r['items'][0]["clone_urls"] if c.startswith("http:")]
-    other_url = [c for c in r['items'][0]["clone_urls"] if not c.startswith("http")]
-
-    priority = https_url + other_url + http_url
-
-    for url in priority:
-        if url.startswith("http"):
-            u = urllib.parse.urlsplit(url)
-            baseurl = urllib.parse.urlunsplit((u.scheme, u.netloc, "", "", ""))
-            git_config = ["-c", "credential.%s/.username=none" % baseurl,
-                          "-c", "credential.%s/.helper=!cred(){ cat >/dev/null; if [ \"$1\" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred" % baseurl]
-        else:
-            git_config = []
-
-        try:
-            logger.debug("trying %s", url)
-            subprocess.run(
-                ['git', *git_config, 'ls-remote', url],
-                check=True,
-                env={
-                    'ARVADOS_API_TOKEN': api.api_token,
-                    'GIT_ASKPASS': '/bin/false',
-                    'HOME': os.environ['HOME'],
-                },
-                stdout=subprocess.DEVNULL,
-            )
-        except subprocess.CalledProcessError:
-            pass
-        else:
-            git_url = url
-            break
-    else:
-        raise Exception('Cannot access git repository, tried {}'
-                        .format(priority))
-
-    if git_url.startswith("http:"):
-        if allow_insecure_http:
-            logger.warning("Using insecure git url %s but will allow this because %s", git_url, allow_insecure_http_opt)
-        else:
-            raise Exception("Refusing to use insecure git url %s, use %s if you really want this." % (git_url, allow_insecure_http_opt))
-
-    return (git_url, git_config)
-
-
 def copy_docker_image(docker_image, docker_image_tag, src, dst, args):
     """Copy the docker image identified by docker_image and
     docker_image_tag from src to dst. Create appropriate
 def copy_docker_image(docker_image, docker_image_tag, src, dst, args):
     """Copy the docker image identified by docker_image and
     docker_image_tag from src to dst. Create appropriate
index ac038f5040a8081ecd4dabfe38ed36eef289bca0..d67f5cc453c0334d3f9646b0bb7831fcea891b7d 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-from __future__ import division
-
 import argparse
 import collections
 import logging
 import argparse
 import collections
 import logging
index 2fef419ee8e66863a6a7cf92e985bf741f267d56..24d6bc50457c9bda8073e04442444a23b09cd8a0 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-from __future__ import division
 import argparse
 import time
 import sys
 import argparse
 import time
 import sys
index 0e732eafde87223a3b3c3522f4f02e089324d711..d1961c8c8ac9face6513ec47d8897ee5a326466b 100644 (file)
@@ -2,10 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import division
-from future.utils import listitems, listvalues
-from builtins import str
-from builtins import object
 import argparse
 import arvados
 import arvados.collection
 import argparse
 import arvados
 import arvados.collection
@@ -696,7 +692,7 @@ class ArvPutUploadJob(object):
         Recursively get the total size of the collection
         """
         size = 0
         Recursively get the total size of the collection
         """
         size = 0
-        for item in listvalues(collection):
+        for item in collection.values():
             if isinstance(item, arvados.collection.Collection) or isinstance(item, arvados.collection.Subcollection):
                 size += self._collection_size(item)
             else:
             if isinstance(item, arvados.collection.Collection) or isinstance(item, arvados.collection.Subcollection):
                 size += self._collection_size(item)
             else:
@@ -978,7 +974,7 @@ class ArvPutUploadJob(object):
     def collection_file_paths(self, col, path_prefix='.'):
         """Return a list of file paths by recursively go through the entire collection `col`"""
         file_paths = []
     def collection_file_paths(self, col, path_prefix='.'):
         """Return a list of file paths by recursively go through the entire collection `col`"""
         file_paths = []
-        for name, item in listitems(col):
+        for name, item in col.items():
             if isinstance(item, arvados.arvfile.ArvadosFile):
                 file_paths.append(os.path.join(path_prefix, name))
             elif isinstance(item, arvados.collection.Subcollection):
             if isinstance(item, arvados.arvfile.ArvadosFile):
                 file_paths.append(os.path.join(path_prefix, name))
             elif isinstance(item, arvados.collection.Subcollection):
@@ -1058,7 +1054,7 @@ class ArvPutUploadJob(object):
                     locators.append(loc)
                 return locators
         elif isinstance(item, arvados.collection.Collection):
                     locators.append(loc)
                 return locators
         elif isinstance(item, arvados.collection.Collection):
-            l = [self._datablocks_on_item(x) for x in listvalues(item)]
+            l = [self._datablocks_on_item(x) for x in item.values()]
             # Fast list flattener method taken from:
             # http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
             return [loc for sublist in l for loc in sublist]
             # Fast list flattener method taken from:
             # http://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
             return [loc for sublist in l for loc in sublist]
index 0fe05da22bb4c4ca18ff4997f4ece05865ea2fd0..474111d882801725ce4e5f324e61676469e83fb0 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from __future__ import print_function
-from __future__ import absolute_import
-from builtins import range
-from past.builtins import basestring
-from builtins import object
 import arvados
 import arvados.commands.ws as ws
 import argparse
 import arvados
 import arvados.commands.ws as ws
 import argparse
index 04a90cf20b8b1556819a47166c37f44d66b0dfd6..3508682399b84850f71719366502ee431429f20b 100644 (file)
@@ -2,16 +2,16 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-import sys
-import logging
 import argparse
 import argparse
-import arvados
 import json
 import json
+import logging
+import signal
+import sys
+
+import arvados
 from arvados.events import subscribe
 from arvados._version import __version__
 from . import _util as arv_cmd
 from arvados.events import subscribe
 from arvados._version import __version__
 from . import _util as arv_cmd
-import signal
 
 def main(arguments=None):
     logger = logging.getLogger('arvados.arv-ws')
 
 def main(arguments=None):
     logger = logging.getLogger('arvados.arv-ws')
index 6dd144c43b4c60226724d7c5348dcfc42e747e1f..57cf2e01ef4c078ef1172a16ef90c9268c6eb3b4 100644 (file)
@@ -2,9 +2,9 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import object
 import json
 import os
 import json
 import os
+
 from . import util
 
 class TaskOutputDir(object):
 from . import util
 
 class TaskOutputDir(object):
index f8fca5780332e41ec1f894759b27df5c0bffd1a1..528a7d28b58146af1a33eac0ada4b746a9eaa12d 100644 (file)
@@ -13,6 +13,7 @@ import time
 import errno
 import logging
 import weakref
 import errno
 import logging
 import weakref
+import collections
 
 _logger = logging.getLogger('arvados.keep')
 
 
 _logger = logging.getLogger('arvados.keep')
 
@@ -31,6 +32,15 @@ class DiskCacheSlot(object):
 
     def get(self):
         self.ready.wait()
 
     def get(self):
         self.ready.wait()
+        # 'content' can None, an empty byte string, or a nonempty mmap
+        # region.  If it is an mmap region, we want to advise the
+        # kernel we're going to use it.  This nudges the kernel to
+        # re-read most or all of the block if necessary (instead of
+        # just a few pages at a time), reducing the number of page
+        # faults and improving performance by 4x compared to not
+        # calling madvise.
+        if self.content:
+            self.content.madvise(mmap.MADV_WILLNEED)
         return self.content
 
     def set(self, value):
         return self.content
 
     def set(self, value):
@@ -39,18 +49,18 @@ class DiskCacheSlot(object):
             if value is None:
                 self.content = None
                 self.ready.set()
             if value is None:
                 self.content = None
                 self.ready.set()
-                return
+                return False
 
             if len(value) == 0:
                 # Can't mmap a 0 length file
                 self.content = b''
                 self.ready.set()
 
             if len(value) == 0:
                 # Can't mmap a 0 length file
                 self.content = b''
                 self.ready.set()
-                return
+                return True
 
             if self.content is not None:
                 # Has been set already
                 self.ready.set()
 
             if self.content is not None:
                 # Has been set already
                 self.ready.set()
-                return
+                return False
 
             blockdir = os.path.join(self.cachedir, self.locator[0:3])
             os.makedirs(blockdir, mode=0o700, exist_ok=True)
 
             blockdir = os.path.join(self.cachedir, self.locator[0:3])
             os.makedirs(blockdir, mode=0o700, exist_ok=True)
@@ -73,6 +83,7 @@ class DiskCacheSlot(object):
             self.content = mmap.mmap(self.filehandle.fileno(), 0, access=mmap.ACCESS_READ)
             # only set the event when mmap is successful
             self.ready.set()
             self.content = mmap.mmap(self.filehandle.fileno(), 0, access=mmap.ACCESS_READ)
             # only set the event when mmap is successful
             self.ready.set()
+            return True
         finally:
             if tmpfile is not None:
                 # If the tempfile hasn't been renamed on disk yet, try to delete it.
         finally:
             if tmpfile is not None:
                 # If the tempfile hasn't been renamed on disk yet, try to delete it.
@@ -95,65 +106,61 @@ class DiskCacheSlot(object):
             return len(self.content)
 
     def evict(self):
             return len(self.content)
 
     def evict(self):
-        if self.content is not None and len(self.content) > 0:
-            # The mmap region might be in use when we decided to evict
-            # it.  This can happen if the cache is too small.
-            #
-            # If we call close() now, it'll throw an error if
-            # something tries to access it.
-            #
-            # However, we don't need to explicitly call mmap.close()
-            #
-            # I confirmed in mmapmodule.c that that both close
-            # and deallocate do the same thing:
+        if not self.content:
+            return
+
+        # The mmap region might be in use when we decided to evict
+        # it.  This can happen if the cache is too small.
+        #
+        # If we call close() now, it'll throw an error if
+        # something tries to access it.
+        #
+        # However, we don't need to explicitly call mmap.close()
+        #
+        # I confirmed in mmapmodule.c that that both close
+        # and deallocate do the same thing:
+        #
+        # a) close the file descriptor
+        # b) unmap the memory range
+        #
+        # So we can forget it in the cache and delete the file on
+        # disk, and it will tear it down after any other
+        # lingering Python references to the mapped memory are
+        # gone.
+
+        blockdir = os.path.join(self.cachedir, self.locator[0:3])
+        final = os.path.join(blockdir, self.locator) + cacheblock_suffix
+        try:
+            fcntl.flock(self.filehandle, fcntl.LOCK_UN)
+
+            # try to get an exclusive lock, this ensures other
+            # processes are not using the block.  It is
+            # nonblocking and will throw an exception if we
+            # can't get it, which is fine because that means
+            # we just won't try to delete it.
             #
             #
-            # a) close the file descriptor
-            # b) unmap the memory range
+            # I should note here, the file locking is not
+            # strictly necessary, we could just remove it and
+            # the kernel would ensure that the underlying
+            # inode remains available as long as other
+            # processes still have the file open.  However, if
+            # you have multiple processes sharing the cache
+            # and deleting each other's files, you'll end up
+            # with a bunch of ghost files that don't show up
+            # in the file system but are still taking up
+            # space, which isn't particularly user friendly.
+            # The locking strategy ensures that cache blocks
+            # in use remain visible.
             #
             #
-            # So we can forget it in the cache and delete the file on
-            # disk, and it will tear it down after any other
-            # lingering Python references to the mapped memory are
-            # gone.
-
-            blockdir = os.path.join(self.cachedir, self.locator[0:3])
-            final = os.path.join(blockdir, self.locator) + cacheblock_suffix
-            try:
-                fcntl.flock(self.filehandle, fcntl.LOCK_UN)
-
-                # try to get an exclusive lock, this ensures other
-                # processes are not using the block.  It is
-                # nonblocking and will throw an exception if we
-                # can't get it, which is fine because that means
-                # we just won't try to delete it.
-                #
-                # I should note here, the file locking is not
-                # strictly necessary, we could just remove it and
-                # the kernel would ensure that the underlying
-                # inode remains available as long as other
-                # processes still have the file open.  However, if
-                # you have multiple processes sharing the cache
-                # and deleting each other's files, you'll end up
-                # with a bunch of ghost files that don't show up
-                # in the file system but are still taking up
-                # space, which isn't particularly user friendly.
-                # The locking strategy ensures that cache blocks
-                # in use remain visible.
-                #
-                fcntl.flock(self.filehandle, fcntl.LOCK_EX | fcntl.LOCK_NB)
-
-                os.remove(final)
-                return True
-            except OSError:
-                pass
-            finally:
-                self.filehandle = None
-                self.linger = weakref.ref(self.content)
-                self.content = None
-        return False
+            fcntl.flock(self.filehandle, fcntl.LOCK_EX | fcntl.LOCK_NB)
 
 
-    def gone(self):
-        # Test if an evicted object is lingering
-        return self.content is None and (self.linger is None or self.linger() is None)
+            os.remove(final)
+            return True
+        except OSError:
+            pass
+        finally:
+            self.filehandle = None
+            self.content = None
 
     @staticmethod
     def get_from_disk(locator, cachedir):
 
     @staticmethod
     def get_from_disk(locator, cachedir):
@@ -237,13 +244,13 @@ class DiskCacheSlot(object):
 
         # Map in all the files we found, up to maxslots, if we exceed
         # maxslots, start throwing things out.
 
         # Map in all the files we found, up to maxslots, if we exceed
         # maxslots, start throwing things out.
-        cachelist = []
+        cachelist: collections.OrderedDict = collections.OrderedDict()
         for b in blocks:
             got = DiskCacheSlot.get_from_disk(b[0], cachedir)
             if got is None:
                 continue
             if len(cachelist) < maxslots:
         for b in blocks:
             got = DiskCacheSlot.get_from_disk(b[0], cachedir)
             if got is None:
                 continue
             if len(cachelist) < maxslots:
-                cachelist.append(got)
+                cachelist[got.locator] = got
             else:
                 # we found more blocks than maxslots, try to
                 # throw it out of the cache.
             else:
                 # we found more blocks than maxslots, try to
                 # throw it out of the cache.
index 4b00f7df8b912d95488d86879e3a29b83c067fec..5501b84271ed4e5519dde750bc50535c420a2c3e 100644 (file)
@@ -2,16 +2,7 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from __future__ import division
 import copy
 import copy
-from future import standard_library
-from future.utils import native_str
-standard_library.install_aliases()
-from builtins import next
-from builtins import str
-from builtins import range
-from builtins import object
 import collections
 import datetime
 import hashlib
 import collections
 import datetime
 import hashlib
@@ -28,15 +19,11 @@ import ssl
 import sys
 import threading
 import resource
 import sys
 import threading
 import resource
-from . import timer
 import urllib.parse
 import traceback
 import weakref
 
 import urllib.parse
 import traceback
 import weakref
 
-if sys.version_info >= (3, 0):
-    from io import BytesIO
-else:
-    from cStringIO import StringIO as BytesIO
+from io import BytesIO
 
 import arvados
 import arvados.config as config
 
 import arvados
 import arvados.config as config
@@ -45,11 +32,11 @@ import arvados.retry as retry
 import arvados.util
 import arvados.diskcache
 from arvados._pycurlhelper import PyCurlHelper
 import arvados.util
 import arvados.diskcache
 from arvados._pycurlhelper import PyCurlHelper
+from . import timer
 
 _logger = logging.getLogger('arvados.keep')
 global_client_object = None
 
 
 _logger = logging.getLogger('arvados.keep')
 global_client_object = None
 
-
 # Monkey patch TCP constants when not available (apple). Values sourced from:
 # http://www.opensource.apple.com/source/xnu/xnu-2422.115.4/bsd/netinet/tcp.h
 if sys.platform == 'darwin':
 # Monkey patch TCP constants when not available (apple). Values sourced from:
 # http://www.opensource.apple.com/source/xnu/xnu-2422.115.4/bsd/netinet/tcp.h
 if sys.platform == 'darwin':
@@ -60,7 +47,6 @@ if sys.platform == 'darwin':
     if not hasattr(socket, 'TCP_KEEPCNT'):
         socket.TCP_KEEPCNT = 0x102
 
     if not hasattr(socket, 'TCP_KEEPCNT'):
         socket.TCP_KEEPCNT = 0x102
 
-
 class KeepLocator(object):
     EPOCH_DATETIME = datetime.datetime.utcfromtimestamp(0)
     HINT_RE = re.compile(r'^[A-Z][A-Za-z0-9@_-]+$')
 class KeepLocator(object):
     EPOCH_DATETIME = datetime.datetime.utcfromtimestamp(0)
     HINT_RE = re.compile(r'^[A-Z][A-Za-z0-9@_-]+$')
@@ -85,7 +71,7 @@ class KeepLocator(object):
 
     def __str__(self):
         return '+'.join(
 
     def __str__(self):
         return '+'.join(
-            native_str(s)
+            str(s)
             for s in [self.md5sum, self.size,
                       self.permission_hint()] + self.hints
             if s is not None)
             for s in [self.md5sum, self.size,
                       self.permission_hint()] + self.hints
             if s is not None)
@@ -182,7 +168,7 @@ class Keep(object):
 class KeepBlockCache(object):
     def __init__(self, cache_max=0, max_slots=0, disk_cache=False, disk_cache_dir=None):
         self.cache_max = cache_max
 class KeepBlockCache(object):
     def __init__(self, cache_max=0, max_slots=0, disk_cache=False, disk_cache_dir=None):
         self.cache_max = cache_max
-        self._cache = []
+        self._cache = collections.OrderedDict()
         self._cache_lock = threading.Lock()
         self._max_slots = max_slots
         self._disk_cache = disk_cache
         self._cache_lock = threading.Lock()
         self._max_slots = max_slots
         self._disk_cache = disk_cache
@@ -233,11 +219,13 @@ class KeepBlockCache(object):
 
         self.cache_max = max(self.cache_max, 64 * 1024 * 1024)
 
 
         self.cache_max = max(self.cache_max, 64 * 1024 * 1024)
 
+        self.cache_total = 0
         if self._disk_cache:
             self._cache = arvados.diskcache.DiskCacheSlot.init_cache(self._disk_cache_dir, self._max_slots)
         if self._disk_cache:
             self._cache = arvados.diskcache.DiskCacheSlot.init_cache(self._disk_cache_dir, self._max_slots)
+            for slot in self._cache.values():
+                self.cache_total += slot.size()
             self.cap_cache()
 
             self.cap_cache()
 
-
     class CacheSlot(object):
         __slots__ = ("locator", "ready", "content")
 
     class CacheSlot(object):
         __slots__ = ("locator", "ready", "content")
 
@@ -251,8 +239,11 @@ class KeepBlockCache(object):
             return self.content
 
         def set(self, value):
             return self.content
 
         def set(self, value):
+            if self.content is not None:
+                return False
             self.content = value
             self.ready.set()
             self.content = value
             self.ready.set()
+            return True
 
         def size(self):
             if self.content is None:
 
         def size(self):
             if self.content is None:
@@ -262,42 +253,25 @@ class KeepBlockCache(object):
 
         def evict(self):
             self.content = None
 
         def evict(self):
             self.content = None
-            return self.gone()
 
 
-        def gone(self):
-            return (self.content is None)
 
     def _resize_cache(self, cache_max, max_slots):
         # Try and make sure the contents of the cache do not exceed
         # the supplied maximums.
 
 
     def _resize_cache(self, cache_max, max_slots):
         # Try and make sure the contents of the cache do not exceed
         # the supplied maximums.
 
-        # Select all slots except those where ready.is_set() and content is
-        # None (that means there was an error reading the block).
-        self._cache = [c for c in self._cache if not (c.ready.is_set() and c.content is None)]
-        sm = sum([slot.size() for slot in self._cache])
-        while len(self._cache) > 0 and (sm > cache_max or len(self._cache) > max_slots):
-            for i in range(len(self._cache)-1, -1, -1):
-                # start from the back, find a slot that is a candidate to evict
-                if self._cache[i].ready.is_set():
-                    sz = self._cache[i].size()
-
-                    # If evict returns false it means the
-                    # underlying disk cache couldn't lock the file
-                    # for deletion because another process was using
-                    # it. Don't count it as reducing the amount
-                    # of data in the cache, find something else to
-                    # throw out.
-                    if self._cache[i].evict():
-                        sm -= sz
-
-                    # check to make sure the underlying data is gone
-                    if self._cache[i].gone():
-                        # either way we forget about it.  either the
-                        # other process will delete it, or if we need
-                        # it again and it is still there, we'll find
-                        # it on disk.
-                        del self._cache[i]
-                    break
+        if self.cache_total <= cache_max and len(self._cache) <= max_slots:
+            return
+
+        _evict_candidates = collections.deque(self._cache.values())
+        while _evict_candidates and (self.cache_total > cache_max or len(self._cache) > max_slots):
+            slot = _evict_candidates.popleft()
+            if not slot.ready.is_set():
+                continue
+
+            sz = slot.size()
+            slot.evict()
+            self.cache_total -= sz
+            del self._cache[slot.locator]
 
 
     def cap_cache(self):
 
 
     def cap_cache(self):
@@ -308,19 +282,19 @@ class KeepBlockCache(object):
 
     def _get(self, locator):
         # Test if the locator is already in the cache
 
     def _get(self, locator):
         # Test if the locator is already in the cache
-        for i in range(0, len(self._cache)):
-            if self._cache[i].locator == locator:
-                n = self._cache[i]
-                if i != 0:
-                    # move it to the front
-                    del self._cache[i]
-                    self._cache.insert(0, n)
-                return n
+        if locator in self._cache:
+            n = self._cache[locator]
+            if n.ready.is_set() and n.content is None:
+                del self._cache[n.locator]
+                return None
+            self._cache.move_to_end(locator)
+            return n
         if self._disk_cache:
             # see if it exists on disk
             n = arvados.diskcache.DiskCacheSlot.get_from_disk(locator, self._disk_cache_dir)
             if n is not None:
         if self._disk_cache:
             # see if it exists on disk
             n = arvados.diskcache.DiskCacheSlot.get_from_disk(locator, self._disk_cache_dir)
             if n is not None:
-                self._cache.insert(0, n)
+                self._cache[n.locator] = n
+                self.cache_total += n.size()
                 return n
         return None
 
                 return n
         return None
 
@@ -350,12 +324,13 @@ class KeepBlockCache(object):
                     n = arvados.diskcache.DiskCacheSlot(locator, self._disk_cache_dir)
                 else:
                     n = KeepBlockCache.CacheSlot(locator)
                     n = arvados.diskcache.DiskCacheSlot(locator, self._disk_cache_dir)
                 else:
                     n = KeepBlockCache.CacheSlot(locator)
-                self._cache.insert(0, n)
+                self._cache[n.locator] = n
                 return n, True
 
     def set(self, slot, blob):
         try:
                 return n, True
 
     def set(self, slot, blob):
         try:
-            slot.set(blob)
+            if slot.set(blob):
+                self.cache_total += slot.size()
             return
         except OSError as e:
             if e.errno == errno.ENOMEM:
             return
         except OSError as e:
             if e.errno == errno.ENOMEM:
@@ -365,7 +340,7 @@ class KeepBlockCache(object):
             elif e.errno == errno.ENOSPC:
                 # Reduce disk max space to current - 256 MiB, cap cache and retry
                 with self._cache_lock:
             elif e.errno == errno.ENOSPC:
                 # Reduce disk max space to current - 256 MiB, cap cache and retry
                 with self._cache_lock:
-                    sm = sum([st.size() for st in self._cache])
+                    sm = sum(st.size() for st in self._cache.values())
                     self.cache_max = max((256 * 1024 * 1024), sm - (256 * 1024 * 1024))
             elif e.errno == errno.ENODEV:
                 _logger.error("Unable to use disk cache: The underlying filesystem does not support memory mapping.")
                     self.cache_max = max((256 * 1024 * 1024), sm - (256 * 1024 * 1024))
             elif e.errno == errno.ENODEV:
                 _logger.error("Unable to use disk cache: The underlying filesystem does not support memory mapping.")
@@ -383,7 +358,8 @@ class KeepBlockCache(object):
             # exception handler adjusts limits downward in some cases
             # to free up resources, which would make the operation
             # succeed.
             # exception handler adjusts limits downward in some cases
             # to free up resources, which would make the operation
             # succeed.
-            slot.set(blob)
+            if slot.set(blob):
+                self.cache_total += slot.size()
         except Exception as e:
             # It failed again.  Give up.
             slot.set(None)
         except Exception as e:
             # It failed again.  Give up.
             slot.set(None)
@@ -924,7 +900,10 @@ class KeepClient(object):
         self.misses_counter = Counter()
         self._storage_classes_unsupported_warning = False
         self._default_classes = []
         self.misses_counter = Counter()
         self._storage_classes_unsupported_warning = False
         self._default_classes = []
-        self.num_prefetch_threads = num_prefetch_threads or 2
+        if num_prefetch_threads is not None:
+            self.num_prefetch_threads = num_prefetch_threads
+        else:
+            self.num_prefetch_threads = 2
         self._prefetch_queue = None
         self._prefetch_threads = None
 
         self._prefetch_queue = None
         self._prefetch_threads = None
 
@@ -1188,6 +1167,8 @@ class KeepClient(object):
                         # result, so if it is already in flight return
                         # immediately.  Clear 'slot' to prevent
                         # finally block from calling slot.set()
                         # result, so if it is already in flight return
                         # immediately.  Clear 'slot' to prevent
                         # finally block from calling slot.set()
+                        if slot.ready.is_set():
+                            slot.get()
                         slot = None
                         return None
 
                         slot = None
                         return None
 
@@ -1426,6 +1407,9 @@ class KeepClient(object):
         does not block.
         """
 
         does not block.
         """
 
+        if self.block_cache.get(locator) is not None:
+            return
+
         self._start_prefetch_threads()
         self._prefetch_queue.put(locator)
 
         self._start_prefetch_threads()
         self._prefetch_queue.put(locator)
 
index 37cd5d7db89f626c24560e0e965408d7de1191aa..ff541e5716c8598ea36ad13e5d61132643a77461 100644 (file)
@@ -2,10 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-from __future__ import absolute_import
-from future.utils import listvalues
-from builtins import object
 import collections
 import hashlib
 import os
 import collections
 import hashlib
 import os
@@ -70,7 +66,7 @@ class StreamReader(object):
         return self._files
 
     def all_files(self):
         return self._files
 
     def all_files(self):
-        return listvalues(self._files)
+        return list(self._files.values())
 
     def size(self):
         n = self._data_locators[-1]
 
     def size(self):
         n = self._data_locators[-1]
@@ -105,5 +101,5 @@ class StreamReader(object):
             manifest_text.extend([d.locator for d in self._data_locators])
         manifest_text.extend([' '.join(["{}:{}:{}".format(seg.locator, seg.range_size, f.name.replace(' ', '\\040'))
                                         for seg in f.segments])
             manifest_text.extend([d.locator for d in self._data_locators])
         manifest_text.extend([' '.join(["{}:{}:{}".format(seg.locator, seg.range_size, f.name.replace(' ', '\\040'))
                                         for seg in f.segments])
-                              for f in listvalues(self._files)])
+                              for f in self._files.values()])
         return ' '.join(manifest_text) + '\n'
         return ' '.join(manifest_text) + '\n'
index 97bc38add054cb994a751009745f2a44a905fc8d..39dbc7874eaaed8d72e6f7980e70af39168b69b5 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-from builtins import object
 import time
 
 class Timer(object):
 import time
 
 class Timer(object):
index 092131d930aeddf880eae21a521d59f4122b7404..ca20de4c5813d4d0a9bf7dc6a1e74ce7ac68c43c 100644 (file)
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
+#
+# This file runs in one of three modes:
+#
+# 1. If the ARVADOS_BUILDING_VERSION environment variable is set, it writes
+#    _version.py and generates dependencies based on that value.
+# 2. If running from an arvados Git checkout, it writes _version.py
+#    and generates dependencies from Git.
+# 3. Otherwise, we expect this is source previously generated from Git, and
+#    it reads _version.py and generates dependencies from it.
 
 
-import subprocess
-import time
 import os
 import re
 import os
 import re
+import runpy
+import subprocess
 import sys
 
 import sys
 
-SETUP_DIR = os.path.dirname(os.path.abspath(__file__))
-VERSION_PATHS = {
-        SETUP_DIR,
-        os.path.abspath(os.path.join(SETUP_DIR, "../../build/version-at-commit.sh"))
-        }
+from pathlib import Path
+
+# These maps explain the relationships between different Python modules in
+# the arvados repository. We use these to help generate setup.py.
+PACKAGE_DEPENDENCY_MAP = {
+    'arvados-cwl-runner': ['arvados-python-client', 'crunchstat_summary'],
+    'arvados-user-activity': ['arvados-python-client'],
+    'arvados_fuse': ['arvados-python-client'],
+    'crunchstat_summary': ['arvados-python-client'],
+}
+PACKAGE_MODULE_MAP = {
+    'arvados-cwl-runner': 'arvados_cwl',
+    'arvados-docker-cleaner': 'arvados_docker',
+    'arvados-python-client': 'arvados',
+    'arvados-user-activity': 'arvados_user_activity',
+    'arvados_fuse': 'arvados_fuse',
+    'crunchstat_summary': 'crunchstat_summary',
+}
+PACKAGE_SRCPATH_MAP = {
+    'arvados-cwl-runner': Path('sdk', 'cwl'),
+    'arvados-docker-cleaner': Path('services', 'dockercleaner'),
+    'arvados-python-client': Path('sdk', 'python'),
+    'arvados-user-activity': Path('tools', 'user-activity'),
+    'arvados_fuse': Path('services', 'fuse'),
+    'crunchstat_summary': Path('tools', 'crunchstat-summary'),
+}
+
+ENV_VERSION = os.environ.get("ARVADOS_BUILDING_VERSION")
+SETUP_DIR = Path(__file__).absolute().parent
+try:
+    REPO_PATH = Path(subprocess.check_output(
+        ['git', '-C', str(SETUP_DIR), 'rev-parse', '--show-toplevel'],
+        stderr=subprocess.DEVNULL,
+        text=True,
+    ).rstrip('\n'))
+except (subprocess.CalledProcessError, OSError):
+    REPO_PATH = None
+else:
+    # Verify this is the arvados monorepo
+    if all((REPO_PATH / path).exists() for path in PACKAGE_SRCPATH_MAP.values()):
+        PACKAGE_NAME, = (
+            pkg_name for pkg_name, path in PACKAGE_SRCPATH_MAP.items()
+            if (REPO_PATH / path) == SETUP_DIR
+        )
+        MODULE_NAME = PACKAGE_MODULE_MAP[PACKAGE_NAME]
+        VERSION_SCRIPT_PATH = Path(REPO_PATH, 'build', 'version-at-commit.sh')
+    else:
+        REPO_PATH = None
+if REPO_PATH is None:
+    (PACKAGE_NAME, MODULE_NAME), = (
+        (pkg_name, mod_name)
+        for pkg_name, mod_name in PACKAGE_MODULE_MAP.items()
+        if (SETUP_DIR / mod_name).is_dir()
+    )
+
+def git_log_output(path, *args):
+    return subprocess.check_output(
+        ['git', '-C', str(REPO_PATH),
+         'log', '--first-parent', '--max-count=1',
+         *args, str(path)],
+        text=True,
+    ).rstrip('\n')
 
 def choose_version_from():
 
 def choose_version_from():
-    ts = {}
-    for path in VERSION_PATHS:
-        ts[subprocess.check_output(
-            ['git', 'log', '--first-parent', '--max-count=1',
-             '--format=format:%ct', path]).strip()] = path
-
-    sorted_ts = sorted(ts.items())
-    getver = sorted_ts[-1][1]
-    print("Using "+getver+" for version number calculation of "+SETUP_DIR, file=sys.stderr)
+    ver_paths = [SETUP_DIR, VERSION_SCRIPT_PATH, *(
+        PACKAGE_SRCPATH_MAP[pkg]
+        for pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ())
+    )]
+    getver = max(ver_paths, key=lambda path: git_log_output(path, '--format=format:%ct'))
+    print(f"Using {getver} for version number calculation of {SETUP_DIR}", file=sys.stderr)
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
-    myhash = subprocess.check_output(['git', 'log', '-n1', '--first-parent',
-                                       '--format=%H', curdir]).strip()
-    myversion = subprocess.check_output([SETUP_DIR+'/../../build/version-at-commit.sh', myhash]).strip().decode()
-    return myversion
+    myhash = git_log_output(curdir, '--format=%H')
+    return subprocess.check_output(
+        [str(VERSION_SCRIPT_PATH), myhash],
+        text=True,
+    ).rstrip('\n')
 
 def save_version(setup_dir, module, v):
 
 def save_version(setup_dir, module, v):
-    v = v.replace("~dev", ".dev").replace("~rc", "rc")
-    with open(os.path.join(setup_dir, module, "_version.py"), 'wt') as fp:
-        return fp.write("__version__ = '%s'\n" % v)
+    with Path(setup_dir, module, '_version.py').open('w') as fp:
+        print(f"__version__ = {v!r}", file=fp)
 
 def read_version(setup_dir, module):
 
 def read_version(setup_dir, module):
-    with open(os.path.join(setup_dir, module, "_version.py"), 'rt') as fp:
-        return re.match("__version__ = '(.*)'$", fp.read()).groups()[0]
-
-def get_version(setup_dir, module):
-    env_version = os.environ.get("ARVADOS_BUILDING_VERSION")
+    file_vars = runpy.run_path(Path(setup_dir, module, '_version.py'))
+    return file_vars['__version__']
 
 
-    if env_version:
-        save_version(setup_dir, module, env_version)
+def get_version(setup_dir=SETUP_DIR, module=MODULE_NAME):
+    if ENV_VERSION:
+        version = ENV_VERSION
+    elif REPO_PATH is None:
+        return read_version(setup_dir, module)
     else:
     else:
-        try:
-            save_version(setup_dir, module, git_version_at_commit())
-        except (subprocess.CalledProcessError, OSError) as err:
-            print("ERROR: {0}".format(err), file=sys.stderr)
-            pass
+        version = git_version_at_commit()
+    version = version.replace("~dev", ".dev").replace("~rc", "rc")
+    save_version(setup_dir, module, version)
+    return version
 
 
-    return read_version(setup_dir, module)
+def iter_dependencies(version=None):
+    if version is None:
+        version = get_version()
+    # A packaged development release should be installed with other
+    # development packages built from the same source, but those
+    # dependencies may have earlier "dev" versions (read: less recent
+    # Git commit timestamps). This compatible version dependency
+    # expresses that as closely as possible. Allowing versions
+    # compatible with .dev0 allows any development release.
+    # Regular expression borrowed partially from
+    # <https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers-regex>
+    dep_ver, match_count = re.subn(r'\.dev(0|[1-9][0-9]*)$', '.dev0', version, 1)
+    dep_op = '~=' if match_count else '=='
+    for dep_pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ()):
+        yield f'{dep_pkg}{dep_op}{dep_ver}'
 
 # Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
 if __name__ == '__main__':
 
 # Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
 if __name__ == '__main__':
-    print(get_version(SETUP_DIR, "arvados"))
+    print(get_version())
diff --git a/sdk/python/pytest.ini b/sdk/python/pytest.ini
new file mode 100644 (file)
index 0000000..9b1bbfd
--- /dev/null
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+[pytest]
+testpaths =
+  tests
index e3d66aa472e655e4dbbd92d755c8adf28f9b8f0b..dc1026906087f040dfdf258b1f57894b48b0b8c2 100644 (file)
@@ -3,7 +3,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
 import os
 import sys
 import re
 import os
 import sys
 import re
@@ -12,16 +11,9 @@ from pathlib import Path
 from setuptools import setup, find_packages
 from setuptools.command import build_py
 
 from setuptools import setup, find_packages
 from setuptools.command import build_py
 
-SETUP_DIR = os.path.dirname(__file__) or '.'
-README = os.path.join(SETUP_DIR, 'README.rst')
-
 import arvados_version
 import arvados_version
-version = arvados_version.get_version(SETUP_DIR, "arvados")
-
-short_tests_only = False
-if '--short-tests-only' in sys.argv:
-    short_tests_only = True
-    sys.argv.remove('--short-tests-only')
+version = arvados_version.get_version()
+README = os.path.join(arvados_version.SETUP_DIR, 'README.rst')
 
 class BuildPython(build_py.build_py):
     """Extend setuptools `build_py` to generate API documentation
 
 class BuildPython(build_py.build_py):
     """Extend setuptools `build_py` to generate API documentation
@@ -115,16 +107,12 @@ setup(name='arvados-python-client',
           ('share/doc/arvados-python-client', ['LICENSE-2.0.txt', 'README.rst']),
       ],
       install_requires=[
           ('share/doc/arvados-python-client', ['LICENSE-2.0.txt', 'README.rst']),
       ],
       install_requires=[
+          *arvados_version.iter_dependencies(version),
           'ciso8601 >=2.0.0',
           'ciso8601 >=2.0.0',
-          'future',
-          'google-api-core <2.11.0', # 2.11.0rc1 is incompatible with google-auth<2
           'google-api-python-client >=2.1.0',
           'google-api-python-client >=2.1.0',
-          'google-auth <2',
-          'httplib2 >=0.9.2, <0.20.2',
-          'protobuf <4.0.0dev',
-          'pycurl >=7.19.5.1, <7.45.0',
-          'pyparsing <3',
-          'ruamel.yaml >=0.15.54, <0.17.22',
+          'google-auth',
+          'httplib2 >=0.9.2',
+          'pycurl >=7.19.5.1',
           'setuptools >=40.3.0',
           'websockets >=11.0',
       ],
           'setuptools >=40.3.0',
           'websockets >=11.0',
       ],
@@ -133,6 +121,6 @@ setup(name='arvados-python-client',
           'Programming Language :: Python :: 3',
       ],
       test_suite='tests',
           'Programming Language :: Python :: 3',
       ],
       test_suite='tests',
-      tests_require=['pbr<1.7.0', 'mock>=1.0,<4', 'PyYAML', 'parameterized'],
+      tests_require=['PyYAML', 'parameterized'],
       zip_safe=False
       )
       zip_safe=False
       )
index 35e85d11951e83d82d4156c703466bb92df12340..1f1f796a9c92a59e9743572a54b431488ccfc854 100644 (file)
@@ -2,11 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-from builtins import str
-from builtins import range
-from builtins import object
 import arvados
 import contextlib
 import errno
 import arvados
 import contextlib
 import errno
@@ -14,7 +9,6 @@ import hashlib
 import http.client
 import httplib2
 import io
 import http.client
 import httplib2
 import io
-import mock
 import os
 import pycurl
 import queue
 import os
 import pycurl
 import queue
@@ -23,11 +17,8 @@ import sys
 import tempfile
 import unittest
 
 import tempfile
 import unittest
 
-if sys.version_info >= (3, 0):
-    from io import StringIO, BytesIO
-else:
-    from cStringIO import StringIO
-    BytesIO = StringIO
+from io import StringIO, BytesIO
+from unittest import mock
 
 # Use this hostname when you want to make sure the traffic will be
 # instantly refused.  100::/64 is a dedicated black hole.
 
 # Use this hostname when you want to make sure the traffic will be
 # instantly refused.  100::/64 is a dedicated black hole.
index 6be8d8b6465b0720bf594341536ac4b0cfb3c212..a4deb5384b0e65c75972244345e7f8fe13036ee5 100644 (file)
@@ -2,10 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import division
-from future import standard_library
-standard_library.install_aliases()
-from builtins import str
 import http.server
 import hashlib
 import os
 import http.server
 import hashlib
 import os
@@ -20,9 +16,7 @@ from . import arvados_testutil as tutil
 
 _debug = os.environ.get('ARVADOS_DEBUG', None)
 
 
 _debug = os.environ.get('ARVADOS_DEBUG', None)
 
-
 class StubKeepServers(tutil.ApiClientMock):
 class StubKeepServers(tutil.ApiClientMock):
-
     def setUp(self):
         super(StubKeepServers, self).setUp()
         sock = socket.socket()
     def setUp(self):
         super(StubKeepServers, self).setUp()
         sock = socket.socket()
index 050d69093c69fe36c9d5ab654f3cc8609c888100..c1945d03d972cbc5b4f65cda0c577d19f6d20927 100644 (file)
@@ -2,10 +2,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from builtins import range
-from builtins import object
 import arvados
 import arvados
+
 from . import arvados_testutil as tutil
 
 class ManifestExamples(object):
 from . import arvados_testutil as tutil
 
 class ManifestExamples(object):
index 446b95ca42c61400640a67ed632c492b64f8238a..a382d643ef39b1b688f8a803cde763b02477dc9e 100644 (file)
@@ -46,22 +46,6 @@ http {
       proxy_http_version 1.1;
     }
   }
       proxy_http_version 1.1;
     }
   }
-  upstream arv-git-http {
-    server {{UPSTREAMHOST}}:{{GITPORT}};
-  }
-  server {
-    listen {{LISTENHOST}}:{{GITSSLPORT}} ssl;
-    server_name arv-git-http git.*;
-    ssl_certificate "{{SSLCERT}}";
-    ssl_certificate_key "{{SSLKEY}}";
-    location  / {
-      proxy_pass http://arv-git-http;
-      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;
-    }
-  }
   upstream keepproxy {
     server {{UPSTREAMHOST}}:{{KEEPPROXYPORT}};
   }
   upstream keepproxy {
     server {{UPSTREAMHOST}}:{{KEEPPROXYPORT}};
   }
index 65015dc87298d7b60042098fa0519a613cffa086..9e54b1f5d2a296e2bb44e7b6082f79ca0b5e0f55 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-from __future__ import absolute_import
-from builtins import range
 import unittest
 
 from .performance_profiler import profiled
 import unittest
 
 from .performance_profiler import profiled
index 787837b72334cbd58f52981d9b1d45ee216bdffb..b17f569b4d8f002d1af45f44843c1fbb608d95c0 100644 (file)
@@ -328,13 +328,6 @@ def run(leave_running_atexit=False):
     if not os.path.exists('tmp/logs'):
         os.makedirs('tmp/logs')
 
     if not os.path.exists('tmp/logs'):
         os.makedirs('tmp/logs')
 
-    # Install the git repository fixtures.
-    gitdir = os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git')
-    gittarball = os.path.join(SERVICES_SRC_DIR, 'api', 'test', 'test.git.tar')
-    if not os.path.isdir(gitdir):
-        os.makedirs(gitdir)
-    subprocess.check_output(['tar', '-xC', gitdir, '-f', gittarball])
-
     # Customizing the passenger config template is the only documented
     # way to override the default passenger_stat_throttle_rate (10 s).
     # In the testing environment, we want restart.txt to take effect
     # Customizing the passenger config template is the only documented
     # way to override the default passenger_stat_throttle_rate (10 s).
     # In the testing environment, we want restart.txt to take effect
@@ -524,8 +517,6 @@ def run_keep(num_servers=2, **kwargs):
 
     for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
         api.keep_services().delete(uuid=d['uuid']).execute()
 
     for d in api.keep_services().list(filters=[['service_type','=','disk']]).execute()['items']:
         api.keep_services().delete(uuid=d['uuid']).execute()
-    for d in api.keep_disks().list().execute()['items']:
-        api.keep_disks().delete(uuid=d['uuid']).execute()
 
     for d in range(0, num_servers):
         port = _start_keep(d, **kwargs)
 
     for d in range(0, num_servers):
         port = _start_keep(d, **kwargs)
@@ -536,9 +527,6 @@ def run_keep(num_servers=2, **kwargs):
             'service_type': 'disk',
             'service_ssl_flag': False,
         }}).execute()
             'service_type': 'disk',
             'service_ssl_flag': False,
         }}).execute()
-        api.keep_disks().create(body={
-            'keep_disk': {'keep_service_uuid': svc['uuid'] }
-        }).execute()
 
     # If keepproxy and/or keep-web is running, send SIGHUP to make
     # them discover the new keepstore services.
 
     # If keepproxy and/or keep-web is running, send SIGHUP to make
     # them discover the new keepstore services.
@@ -599,27 +587,6 @@ def stop_keep_proxy():
         return
     kill_server_pid(_pidfile('keepproxy'))
 
         return
     kill_server_pid(_pidfile('keepproxy'))
 
-def run_arv_git_httpd():
-    if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
-        return
-    stop_arv_git_httpd()
-
-    gitport = internal_port_from_config("GitHTTP")
-    env = os.environ.copy()
-    env.pop('ARVADOS_API_TOKEN', None)
-    logf = open(_logfilename('githttpd'), WRITE_MODE)
-    agh = subprocess.Popen(['arvados-server', 'git-httpd'],
-        env=env, stdin=open('/dev/null'), stdout=logf, stderr=logf)
-    _detachedSubprocesses.append(agh)
-    with open(_pidfile('githttpd'), 'w') as f:
-        f.write(str(agh.pid))
-    _wait_until_port_listens(gitport)
-
-def stop_arv_git_httpd():
-    if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
-        return
-    kill_server_pid(_pidfile('githttpd'))
-
 def run_keep_web():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
         return
 def run_keep_web():
     if 'ARVADOS_TEST_PROXY_SERVICES' in os.environ:
         return
@@ -656,8 +623,6 @@ def run_nginx():
     nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
     nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
     nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
     nginxconf['KEEPWEBSSLPORT'] = external_port_from_config("WebDAV")
     nginxconf['KEEPPROXYPORT'] = internal_port_from_config("Keepproxy")
     nginxconf['KEEPPROXYSSLPORT'] = external_port_from_config("Keepproxy")
-    nginxconf['GITPORT'] = internal_port_from_config("GitHTTP")
-    nginxconf['GITSSLPORT'] = external_port_from_config("GitHTTP")
     nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
     nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
     nginxconf['WSPORT'] = internal_port_from_config("Websocket")
     nginxconf['HEALTHPORT'] = internal_port_from_config("Health")
     nginxconf['HEALTHSSLPORT'] = external_port_from_config("Health")
     nginxconf['WSPORT'] = internal_port_from_config("Websocket")
@@ -685,7 +650,7 @@ def run_nginx():
 
     nginx = subprocess.Popen(
         ['nginx',
 
     nginx = subprocess.Popen(
         ['nginx',
-         '-g', 'error_log stderr info; pid '+_pidfile('nginx')+';',
+         '-g', 'error_log stderr notice; pid '+_pidfile('nginx')+';',
          '-c', conffile],
         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
     _detachedSubprocesses.append(nginx)
          '-c', conffile],
         env=env, stdin=open('/dev/null'), stdout=sys.stderr)
     _detachedSubprocesses.append(nginx)
@@ -700,8 +665,6 @@ def setup_config():
     workbench1_external_port = find_available_port()
     workbench2_port = find_available_port()
     workbench2_external_port = find_available_port()
     workbench1_external_port = find_available_port()
     workbench2_port = find_available_port()
     workbench2_external_port = find_available_port()
-    git_httpd_port = find_available_port()
-    git_httpd_external_port = find_available_port()
     health_httpd_port = find_available_port()
     health_httpd_external_port = find_available_port()
     keepproxy_port = find_available_port()
     health_httpd_port = find_available_port()
     health_httpd_external_port = find_available_port()
     keepproxy_port = find_available_port()
@@ -755,12 +718,6 @@ def setup_config():
                 "http://%s:%s"%(localhost, workbench2_port): {},
             },
         },
                 "http://%s:%s"%(localhost, workbench2_port): {},
             },
         },
-        "GitHTTP": {
-            "ExternalURL": "https://%s:%s" % (localhost, git_httpd_external_port),
-            "InternalURLs": {
-                "http://%s:%s"%(localhost, git_httpd_port): {}
-            },
-        },
         "Health": {
             "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
             "InternalURLs": {
         "Health": {
             "ExternalURL": "https://%s:%s" % (localhost, health_httpd_external_port),
             "InternalURLs": {
@@ -833,13 +790,7 @@ def setup_config():
                     "ForwardSlashNameSubstitution": "/",
                     "TrashSweepInterval": "-1s",
                 },
                     "ForwardSlashNameSubstitution": "/",
                     "TrashSweepInterval": "-1s",
                 },
-                "Git": {
-                    "Repositories": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'git', 'test'),
-                },
                 "Containers": {
                 "Containers": {
-                    "JobsAPI": {
-                        "GitInternalDir": os.path.join(SERVICES_SRC_DIR, 'api', 'tmp', 'internal.git'),
-                    },
                     "LocalKeepBlobBuffersPerVCPU": 0,
                     "Logging": {
                         "SweepInterval": 0, # disable, otherwise test cases can't acquire dblock
                     "LocalKeepBlobBuffersPerVCPU": 0,
                     "Logging": {
                         "SweepInterval": 0, # disable, otherwise test cases can't acquire dblock
@@ -971,7 +922,6 @@ if __name__ == "__main__":
         'start_keep', 'stop_keep',
         'start_keep_proxy', 'stop_keep_proxy',
         'start_keep-web', 'stop_keep-web',
         'start_keep', 'stop_keep',
         'start_keep_proxy', 'stop_keep_proxy',
         'start_keep-web', 'stop_keep-web',
-        'start_githttpd', 'stop_githttpd',
         'start_nginx', 'stop_nginx', 'setup_config',
     ]
     parser = argparse.ArgumentParser()
         'start_nginx', 'stop_nginx', 'setup_config',
     ]
     parser = argparse.ArgumentParser()
@@ -1019,10 +969,6 @@ if __name__ == "__main__":
         run_keep_proxy()
     elif args.action == 'stop_keep_proxy':
         stop_keep_proxy()
         run_keep_proxy()
     elif args.action == 'stop_keep_proxy':
         stop_keep_proxy()
-    elif args.action == 'start_githttpd':
-        run_arv_git_httpd()
-    elif args.action == 'stop_githttpd':
-        stop_arv_git_httpd()
     elif args.action == 'start_keep-web':
         run_keep_web()
     elif args.action == 'stop_keep-web':
     elif args.action == 'start_keep-web':
         run_keep_web()
     elif args.action == 'stop_keep-web':
diff --git a/sdk/python/tests/slow_test.py b/sdk/python/tests/slow_test.py
deleted file mode 100644 (file)
index ae46f4e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: Apache-2.0
-
-import __main__
-import os
-import unittest
-
-slow_test = lambda _: unittest.skipIf(
-    __main__.short_tests_only,
-    "running --short tests only")
index 0f85e5520c821dcaa7bf6690e7702cb857e3ac54..7d7cc9ba596c1d224302bd540583bcbd3aab1c99 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from builtins import str
-from builtins import range
 import arvados
 import collections
 import contextlib
 import arvados
 import collections
 import contextlib
@@ -20,7 +17,7 @@ import sys
 import unittest
 import urllib.parse as urlparse
 
 import unittest
 import urllib.parse as urlparse
 
-import mock
+from unittest import mock
 from . import run_test_server
 
 from apiclient import errors as apiclient_errors
 from . import run_test_server
 
 from apiclient import errors as apiclient_errors
@@ -70,8 +67,8 @@ class ArvadosApiTest(run_test_server.TestCaseWithServers):
         self.assertIsNot(*clients)
 
     def test_empty_list(self):
         self.assertIsNot(*clients)
 
     def test_empty_list(self):
-        answer = arvados.api('v1').humans().list(
-            filters=[['uuid', '=', None]]).execute()
+        answer = arvados.api('v1').collections().list(
+            filters=[['uuid', '=', 'abcdef']]).execute()
         self.assertEqual(answer['items_available'], len(answer['items']))
 
     def test_nonempty_list(self):
         self.assertEqual(answer['items_available'], len(answer['items']))
 
     def test_nonempty_list(self):
@@ -81,11 +78,11 @@ class ArvadosApiTest(run_test_server.TestCaseWithServers):
 
     def test_timestamp_inequality_filter(self):
         api = arvados.api('v1')
 
     def test_timestamp_inequality_filter(self):
         api = arvados.api('v1')
-        new_item = api.specimens().create(body={}).execute()
+        new_item = api.collections().create(body={}).execute()
         for operator, should_include in [
                 ['<', False], ['>', False],
                 ['<=', True], ['>=', True], ['=', True]]:
         for operator, should_include in [
                 ['<', False], ['>', False],
                 ['<=', True], ['>=', True], ['=', True]]:
-            response = api.specimens().list(filters=[
+            response = api.collections().list(filters=[
                 ['created_at', operator, new_item['created_at']],
                 # Also filter by uuid to ensure (if it matches) it's on page 0
                 ['uuid', '=', new_item['uuid']]]).execute()
                 ['created_at', operator, new_item['created_at']],
                 # Also filter by uuid to ensure (if it matches) it's on page 0
                 ['uuid', '=', new_item['uuid']]]).execute()
@@ -100,13 +97,13 @@ class ArvadosApiTest(run_test_server.TestCaseWithServers):
 
     def test_exceptions_include_errors(self):
         mock_responses = {
 
     def test_exceptions_include_errors(self):
         mock_responses = {
-            'arvados.humans.get': self.api_error_response(
+            'arvados.collections.get': self.api_error_response(
                 422, "Bad UUID format", "Bad output format"),
             }
         req_builder = apiclient_http.RequestMockBuilder(mock_responses)
         api = arvados.api('v1', requestBuilder=req_builder)
         with self.assertRaises(apiclient_errors.HttpError) as err_ctx:
                 422, "Bad UUID format", "Bad output format"),
             }
         req_builder = apiclient_http.RequestMockBuilder(mock_responses)
         api = arvados.api('v1', requestBuilder=req_builder)
         with self.assertRaises(apiclient_errors.HttpError) as err_ctx:
-            api.humans().get(uuid='xyz-xyz-abcdef').execute()
+            api.collections().get(uuid='xyz-xyz-abcdef').execute()
         err_s = str(err_ctx.exception)
         for msg in ["Bad UUID format", "Bad output format"]:
             self.assertIn(msg, err_s)
         err_s = str(err_ctx.exception)
         for msg in ["Bad UUID format", "Bad output format"]:
             self.assertIn(msg, err_s)
@@ -126,14 +123,14 @@ class ArvadosApiTest(run_test_server.TestCaseWithServers):
 
     def test_exceptions_without_errors_have_basic_info(self):
         mock_responses = {
 
     def test_exceptions_without_errors_have_basic_info(self):
         mock_responses = {
-            'arvados.humans.delete': (
+            'arvados.collections.delete': (
                 fake_httplib2_response(500, **self.ERROR_HEADERS),
                 b"")
             }
         req_builder = apiclient_http.RequestMockBuilder(mock_responses)
         api = arvados.api('v1', requestBuilder=req_builder)
         with self.assertRaises(apiclient_errors.HttpError) as err_ctx:
                 fake_httplib2_response(500, **self.ERROR_HEADERS),
                 b"")
             }
         req_builder = apiclient_http.RequestMockBuilder(mock_responses)
         api = arvados.api('v1', requestBuilder=req_builder)
         with self.assertRaises(apiclient_errors.HttpError) as err_ctx:
-            api.humans().delete(uuid='xyz-xyz-abcdef').execute()
+            api.collections().delete(uuid='xyz-xyz-abcdef').execute()
         self.assertIn("500", str(err_ctx.exception))
 
     def test_request_too_large(self):
         self.assertIn("500", str(err_ctx.exception))
 
     def test_request_too_large(self):
@@ -206,7 +203,7 @@ class ArvadosApiTest(run_test_server.TestCaseWithServers):
 
     def test_ordered_json_model(self):
         mock_responses = {
 
     def test_ordered_json_model(self):
         mock_responses = {
-            'arvados.humans.get': (
+            'arvados.collections.get': (
                 None,
                 json.dumps(collections.OrderedDict(
                     (c, int(c, 16)) for c in string.hexdigits
                 None,
                 json.dumps(collections.OrderedDict(
                     (c, int(c, 16)) for c in string.hexdigits
@@ -216,7 +213,7 @@ class ArvadosApiTest(run_test_server.TestCaseWithServers):
         req_builder = apiclient_http.RequestMockBuilder(mock_responses)
         api = arvados.api('v1',
                           requestBuilder=req_builder, model=OrderedJsonModel())
         req_builder = apiclient_http.RequestMockBuilder(mock_responses)
         api = arvados.api('v1',
                           requestBuilder=req_builder, model=OrderedJsonModel())
-        result = api.humans().get(uuid='test').execute()
+        result = api.collections().get(uuid='test').execute()
         self.assertEqual(string.hexdigits, ''.join(list(result.keys())))
 
     def test_api_is_threadsafe(self):
         self.assertEqual(string.hexdigits, ''.join(list(result.keys())))
 
     def test_api_is_threadsafe(self):
index b853b330435ac758a2a4f3c25bc02cfb5c89a12a..1af5c68e6c94934b57364b33aaacc4ee35307d2e 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
 import os
 import sys
 import tempfile
 import os
 import sys
 import tempfile
index d12739f6f69235defbfccdf0ab1701a89e6bb8a4..aefcbd7b22d319f43305686b505ba24e59aa110f 100644 (file)
@@ -2,16 +2,15 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from future.utils import listitems
 import io
 import logging
 import io
 import logging
-import mock
 import os
 import re
 import shutil
 import tempfile
 
 import os
 import re
 import shutil
 import tempfile
 
+from unittest import mock
+
 import arvados
 import arvados.collection as collection
 import arvados.commands.get as arv_get
 import arvados
 import arvados.collection as collection
 import arvados.commands.get as arv_get
@@ -51,7 +50,7 @@ class ArvadosGetTestCase(run_test_server.TestCaseWithServers,
                               }):
         api = arvados.api()
         c = collection.Collection(api_client=api)
                               }):
         api = arvados.api()
         c = collection.Collection(api_client=api)
-        for path, data in listitems(contents):
+        for path, data in contents.items():
             with c.open(path, 'wb') as f:
                 f.write(data)
         c.save_new()
             with c.open(path, 'wb') as f:
                 f.write(data)
         c.save_new()
index 9aebc0350424e0b4051d14687cf7c4376135c18c..5d23dfb378069219966a25c9fb2f8ce1a5a52437 100644 (file)
@@ -8,13 +8,14 @@ import collections.abc
 import copy
 import hashlib
 import logging
 import copy
 import hashlib
 import logging
-import mock
 import os
 import subprocess
 import sys
 import tempfile
 import unittest
 import os
 import subprocess
 import sys
 import tempfile
 import unittest
+
 from pathlib import Path
 from pathlib import Path
+from unittest import mock
 
 import parameterized
 
 
 import parameterized
 
index 635c6254ad73f9c8d8178634c0ce33c0b038b0d0..59441a74c2b2e988860f8a502f0581d1181213de 100644 (file)
@@ -2,15 +2,13 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from builtins import str
-from builtins import range
 import os
 import random
 import sys
 import os
 import random
 import sys
-import mock
 import tempfile
 
 import tempfile
 
+from unittest import mock
+
 import arvados.errors as arv_error
 import arvados.commands.ls as arv_ls
 from . import run_test_server
 import arvados.errors as arv_error
 import arvados.commands.ls as arv_ls
 from . import run_test_server
index afdf2238a71caf8fb212d19527ceab79c27f8b96..e8a3e65bdcd99a7c0a8a9ac6a19af5ebc2655aae 100644 (file)
@@ -4,19 +4,12 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from __future__ import division
-from future import standard_library
-standard_library.install_aliases()
-from builtins import str
-from builtins import range
-from functools import partial
 import apiclient
 import ciso8601
 import apiclient
 import ciso8601
+import copy
 import datetime
 import json
 import logging
 import datetime
 import json
 import logging
-import mock
 import multiprocessing
 import os
 import pwd
 import multiprocessing
 import os
 import pwd
@@ -32,6 +25,9 @@ import time
 import unittest
 import uuid
 
 import unittest
 import uuid
 
+from functools import partial
+from unittest import mock
+
 import arvados
 import arvados.commands.put as arv_put
 from . import arvados_testutil as tutil
 import arvados
 import arvados.commands.put as arv_put
 from . import arvados_testutil as tutil
@@ -573,7 +569,7 @@ class ArvPutUploadJobTest(run_test_server.TestCaseWithServers,
 class CachedManifestValidationTest(ArvadosBaseTestCase):
     class MockedPut(arv_put.ArvPutUploadJob):
         def __init__(self, cached_manifest=None):
 class CachedManifestValidationTest(ArvadosBaseTestCase):
     class MockedPut(arv_put.ArvPutUploadJob):
         def __init__(self, cached_manifest=None):
-            self._state = arv_put.ArvPutUploadJob.EMPTY_STATE
+            self._state = copy.deepcopy(arv_put.ArvPutUploadJob.EMPTY_STATE)
             self._state['manifest'] = cached_manifest
             self._api_client = mock.MagicMock()
             self.logger = mock.MagicMock()
             self._state['manifest'] = cached_manifest
             self._api_client = mock.MagicMock()
             self.logger = mock.MagicMock()
index 521c46ee34e72df4c25fc3a7f0dcbe2cdce379b6..4e67db2184d0cb4d6471228ae47534166ebee99b 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
 import os
 import sys
 import tempfile
 import os
 import sys
 import tempfile
index cf6dec1a553eec3e0bbf6b219e11eccfb0ca9b7e..6bcba9a81d6930480bbaca32d7a37cc73acbf031 100644 (file)
@@ -2,16 +2,12 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from builtins import hex
-from builtins import str
-from builtins import range
-from builtins import object
 import datetime
 import datetime
-import mock
 import os
 import os
-import unittest
 import time
 import time
+import unittest
+
+from unittest import mock
 
 import arvados
 from arvados._ranges import Range
 
 import arvados
 from arvados._ranges import Range
@@ -631,6 +627,7 @@ class ArvadosFileReaderTestCase(StreamFileReaderTestCase):
                 self.blocks = blocks
                 self.nocache = nocache
                 self._keep = ArvadosFileWriterTestCase.MockKeep({})
                 self.blocks = blocks
                 self.nocache = nocache
                 self._keep = ArvadosFileWriterTestCase.MockKeep({})
+                self.prefetch_lookahead = 0
 
             def block_prefetch(self, loc):
                 pass
 
             def block_prefetch(self, loc):
                 pass
@@ -692,8 +689,60 @@ class ArvadosFileReaderTestCase(StreamFileReaderTestCase):
         with Collection(". 2e9ec317e197819358fbc43afca7d837+8 e8dc4081b13434b45189a720b77b6818+8 0:16:count.txt\n", keep_client=keep) as c:
             r = c.open("count.txt", "rb")
             self.assertEqual(b"0123", r.read(4))
         with Collection(". 2e9ec317e197819358fbc43afca7d837+8 e8dc4081b13434b45189a720b77b6818+8 0:16:count.txt\n", keep_client=keep) as c:
             r = c.open("count.txt", "rb")
             self.assertEqual(b"0123", r.read(4))
-        self.assertIn("2e9ec317e197819358fbc43afca7d837+8", keep.requests)
-        self.assertIn("e8dc4081b13434b45189a720b77b6818+8", keep.requests)
+        self.assertEqual(["2e9ec317e197819358fbc43afca7d837+8",
+                          "e8dc4081b13434b45189a720b77b6818+8"], keep.requests)
+
+    def test_prefetch_disabled(self):
+        keep = ArvadosFileWriterTestCase.MockKeep({
+            "2e9ec317e197819358fbc43afca7d837+8": b"01234567",
+            "e8dc4081b13434b45189a720b77b6818+8": b"abcdefgh",
+        })
+        keep.num_prefetch_threads = 0
+        with Collection(". 2e9ec317e197819358fbc43afca7d837+8 e8dc4081b13434b45189a720b77b6818+8 0:16:count.txt\n", keep_client=keep) as c:
+            r = c.open("count.txt", "rb")
+            self.assertEqual(b"0123", r.read(4))
+
+        self.assertEqual(["2e9ec317e197819358fbc43afca7d837+8"], keep.requests)
+
+    def test_prefetch_first_read_only(self):
+        # test behavior that prefetch only happens every 128 reads
+        # check that it doesn't make a prefetch request on the second read
+        keep = ArvadosFileWriterTestCase.MockKeep({
+            "2e9ec317e197819358fbc43afca7d837+8": b"01234567",
+            "e8dc4081b13434b45189a720b77b6818+8": b"abcdefgh",
+        })
+        with Collection(". 2e9ec317e197819358fbc43afca7d837+8 e8dc4081b13434b45189a720b77b6818+8 0:16:count.txt\n", keep_client=keep) as c:
+            r = c.open("count.txt", "rb")
+            self.assertEqual(b"0123", r.read(4))
+            self.assertEqual(b"45", r.read(2))
+        self.assertEqual(["2e9ec317e197819358fbc43afca7d837+8",
+                          "e8dc4081b13434b45189a720b77b6818+8",
+                          "2e9ec317e197819358fbc43afca7d837+8"], keep.requests)
+        self.assertEqual(3, len(keep.requests))
+
+    def test_prefetch_again(self):
+        # test behavior that prefetch only happens every 128 reads
+        # check that it does make another prefetch request after 128 reads
+        keep = ArvadosFileWriterTestCase.MockKeep({
+            "2e9ec317e197819358fbc43afca7d837+8": b"01234567",
+            "e8dc4081b13434b45189a720b77b6818+8": b"abcdefgh",
+        })
+        with Collection(". 2e9ec317e197819358fbc43afca7d837+8 e8dc4081b13434b45189a720b77b6818+8 0:16:count.txt\n", keep_client=keep) as c:
+            r = c.open("count.txt", "rb")
+            for i in range(0, 129):
+                r.seek(0)
+                self.assertEqual(b"0123", r.read(4))
+        self.assertEqual(["2e9ec317e197819358fbc43afca7d837+8",
+                          "e8dc4081b13434b45189a720b77b6818+8",
+                          "2e9ec317e197819358fbc43afca7d837+8",
+                          "2e9ec317e197819358fbc43afca7d837+8"], keep.requests[0:4])
+        self.assertEqual(["2e9ec317e197819358fbc43afca7d837+8",
+                          "2e9ec317e197819358fbc43afca7d837+8",
+                          "2e9ec317e197819358fbc43afca7d837+8",
+                          "e8dc4081b13434b45189a720b77b6818+8"], keep.requests[127:131])
+        # gets the 1st block 129 times from keep (cache),
+        # and the 2nd block twice to get 131 requests
+        self.assertEqual(131, len(keep.requests))
 
     def test__eq__from_manifest(self):
         with Collection('. 781e5e245d69b566979b86e28d23f2c7+10 0:10:count1.txt') as c1:
 
     def test__eq__from_manifest(self):
         with Collection('. 781e5e245d69b566979b86e28d23f2c7+10 0:10:count1.txt') as c1:
index fc062e791cbf77874d35bddffa21ad1ec70a1d06..0014e94af21b8a9dc0989f2564e5846525def136 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
 import arvados
 import sys
 
 import arvados
 import sys
 
index 259acd0a3079bdeba319c46e931617c15b9b8af4..41984a5bf916dd7767855e0ca9f6b58912fd6c76 100644 (file)
@@ -2,13 +2,7 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import print_function
-from __future__ import absolute_import
-
-from builtins import str
-from builtins import range
 import hashlib
 import hashlib
-import mock
 import os
 import random
 import shutil
 import os
 import random
 import shutil
@@ -17,11 +11,12 @@ import tempfile
 import threading
 import unittest
 
 import threading
 import unittest
 
+from unittest import mock
+
 import arvados
 import arvados.cache
 from . import run_test_server
 
 import arvados
 import arvados.cache
 from . import run_test_server
 
-
 def _random(n):
     return bytearray(random.getrandbits(8) for _ in range(n))
 
 def _random(n):
     return bytearray(random.getrandbits(8) for _ in range(n))
 
index 9e753506b3550d66b8939f355496c64f6abfb031..5d574856dd2b5610dca2fb58aaf2dcf68b38165e 100644 (file)
@@ -2,12 +2,8 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-
-from builtins import object
 import arvados
 import copy
 import arvados
 import copy
-import mock
 import os
 import random
 import re
 import os
 import random
 import re
@@ -18,6 +14,8 @@ import time
 import unittest
 import parameterized
 
 import unittest
 import parameterized
 
+from unittest import mock
+
 from . import run_test_server
 from arvados._ranges import Range, LocatorAndRange
 from arvados.collection import Collection, CollectionReader
 from . import run_test_server
 from arvados._ranges import Range, LocatorAndRange
 from arvados.collection import Collection, CollectionReader
index 4ee68ba285c020e3403c9639a4bbfb49fb76a5c1..02f316bf79c4002706eb0a694b8949da71913822 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
 import traceback
 import unittest
 
 import traceback
 import unittest
 
index b4e6a0b1cd88204b2dc8f699c85d01e2c805abe1..c5f33100153df3f8e824b127d35198a05834c39c 100644 (file)
@@ -4,13 +4,14 @@
 
 import json
 import logging
 
 import json
 import logging
-import mock
 import queue
 import sys
 import threading
 import time
 import unittest
 
 import queue
 import sys
 import threading
 import time
 import unittest
 
+from unittest import mock
+
 import websockets.exceptions as ws_exc
 
 import arvados
 import websockets.exceptions as ws_exc
 
 import arvados
@@ -96,9 +97,9 @@ class WebsocketTest(run_test_server.TestCaseWithServers):
         # Create ancestor before subscribing.
         # When listening with start_time in the past, this should also be retrieved.
         # However, when start_time is omitted in subscribe, this should not be fetched.
         # Create ancestor before subscribing.
         # When listening with start_time in the past, this should also be retrieved.
         # However, when start_time is omitted in subscribe, this should not be fetched.
-        ancestor = arvados.api('v1').humans().create(body={}).execute()
+        ancestor = arvados.api('v1').collections().create(body={}).execute()
 
 
-        filters = [['object_uuid', 'is_a', 'arvados#human']]
+        filters = [['object_uuid', 'is_a', 'arvados#collection']]
         if start_time:
             filters.append(['created_at', '>=', start_time])
 
         if start_time:
             filters.append(['created_at', '>=', start_time])
 
@@ -117,11 +118,11 @@ class WebsocketTest(run_test_server.TestCaseWithServers):
             while not self.ws._skip_old_events:
                 self.assertLess(time.time(), deadline)
                 time.sleep(0.1)
             while not self.ws._skip_old_events:
                 self.assertLess(time.time(), deadline)
                 time.sleep(0.1)
-        human = arvados.api('v1').humans().create(body={}).execute()
+        collection = arvados.api('v1').collections().create(body={}).execute()
 
         want_uuids = []
         if expected > 0:
 
         want_uuids = []
         if expected > 0:
-            want_uuids.append(human['uuid'])
+            want_uuids.append(collection['uuid'])
         if expected > 1:
             want_uuids.append(ancestor['uuid'])
         log_object_uuids = []
         if expected > 1:
             want_uuids.append(ancestor['uuid'])
         log_object_uuids = []
@@ -227,7 +228,7 @@ class WebsocketTest(run_test_server.TestCaseWithServers):
         streamHandler = logging.StreamHandler(logstream)
         rootLogger.addHandler(streamHandler)
 
         streamHandler = logging.StreamHandler(logstream)
         rootLogger.addHandler(streamHandler)
 
-        filters = [['object_uuid', 'is_a', 'arvados#human']]
+        filters = [['object_uuid', 'is_a', 'arvados#collection']]
         filters.append(['created_at', '>=', self.localiso(self.TIME_PAST)])
         self.ws = arvados.events.subscribe(
             arvados.api('v1'), filters,
         filters.append(['created_at', '>=', self.localiso(self.TIME_PAST)])
         self.ws = arvados.events.subscribe(
             arvados.api('v1'), filters,
@@ -238,10 +239,10 @@ class WebsocketTest(run_test_server.TestCaseWithServers):
         self.assertEqual(200, events.get(True, 5)['status'])
 
         # create obj
         self.assertEqual(200, events.get(True, 5)['status'])
 
         # create obj
-        human = arvados.api('v1').humans().create(body={}).execute()
+        collection = arvados.api('v1').collections().create(body={}).execute()
 
         # expect an event
 
         # expect an event
-        self.assertIn(human['uuid'], events.get(True, 5)['object_uuid'])
+        self.assertIn(collection['uuid'], events.get(True, 5)['object_uuid'])
         with self.assertRaises(queue.Empty):
             self.assertEqual(events.get(True, 2), None)
 
         with self.assertRaises(queue.Empty):
             self.assertEqual(events.get(True, 2), None)
 
@@ -252,7 +253,7 @@ class WebsocketTest(run_test_server.TestCaseWithServers):
             self.ws.close()
 
         # create one more obj
             self.ws.close()
 
         # create one more obj
-        human2 = arvados.api('v1').humans().create(body={}).execute()
+        collection2 = arvados.api('v1').collections().create(body={}).execute()
 
         # (un)expect the object creation event
         if close_unexpected:
 
         # (un)expect the object creation event
         if close_unexpected:
@@ -263,8 +264,8 @@ class WebsocketTest(run_test_server.TestCaseWithServers):
                     log_object_uuids.append(event['object_uuid'])
             with self.assertRaises(queue.Empty):
                 self.assertEqual(events.get(True, 2), None)
                     log_object_uuids.append(event['object_uuid'])
             with self.assertRaises(queue.Empty):
                 self.assertEqual(events.get(True, 2), None)
-            self.assertNotIn(human['uuid'], log_object_uuids)
-            self.assertIn(human2['uuid'], log_object_uuids)
+            self.assertNotIn(collection['uuid'], log_object_uuids)
+            self.assertIn(collection2['uuid'], log_object_uuids)
         else:
             with self.assertRaises(queue.Empty):
                 self.assertEqual(events.get(True, 2), None)
         else:
             with self.assertRaises(queue.Empty):
                 self.assertEqual(events.get(True, 2), None)
index bce57eda61b7be549af205525bb0c81eba9e035d..476e2c88e9bb5abc71c52ca33cf3f783e0a51c90 100644 (file)
@@ -2,20 +2,18 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from future import standard_library
-standard_library.install_aliases()
-
 import copy
 import io
 import functools
 import hashlib
 import json
 import logging
 import copy
 import io
 import functools
 import hashlib
 import json
 import logging
-import mock
 import sys
 import unittest
 import datetime
 
 import sys
 import unittest
 import datetime
 
+from unittest import mock
+
 import arvados
 import arvados.collection
 import arvados.keep
 import arvados
 import arvados.collection
 import arvados.keep
@@ -23,8 +21,6 @@ import pycurl
 
 from arvados.http_to_keep import http_to_keep
 
 
 from arvados.http_to_keep import http_to_keep
 
-import ruamel.yaml as yaml
-
 # Turns out there was already "FakeCurl" that serves the same purpose, but
 # I wrote this before I knew that.  Whoops.
 class CurlMock:
 # Turns out there was already "FakeCurl" that serves the same purpose, but
 # I wrote this before I knew that.  Whoops.
 class CurlMock:
index 6b1ebf56c0826ee4e23523168b729855f6368bf8..2dc4363f0c836f0e4fd780d48d3b21e8f350855d 100644 (file)
@@ -2,15 +2,7 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from __future__ import division
-from future import standard_library
-standard_library.install_aliases()
-from builtins import str
-from builtins import range
-from builtins import object
 import hashlib
 import hashlib
-import mock
 import os
 import errno
 import pycurl
 import os
 import errno
 import pycurl
@@ -24,6 +16,10 @@ import tempfile
 import time
 import unittest
 import urllib.parse
 import time
 import unittest
 import urllib.parse
+import mmap
+
+from unittest import mock
+from unittest.mock import patch
 
 import parameterized
 
 
 import parameterized
 
@@ -625,122 +621,6 @@ class KeepClientCacheTestCase(unittest.TestCase, tutil.ApiClientMock, DiskCacheB
 
 
 
 
 
 
-@tutil.skip_sleep
-@parameterized.parameterized_class([{"disk_cache": True}, {"disk_cache": False}])
-class KeepStorageClassesTestCase(unittest.TestCase, tutil.ApiClientMock, DiskCacheBase):
-    disk_cache = False
-
-    def setUp(self):
-        self.api_client = self.mock_keep_services(count=2)
-        self.keep_client = arvados.KeepClient(api_client=self.api_client, block_cache=self.make_block_cache(self.disk_cache))
-        self.data = b'xyzzy'
-        self.locator = '1271ed5ef305aadabc605b1609e24c52'
-
-    def tearDown(self):
-        DiskCacheBase.tearDown(self)
-
-    def test_multiple_default_storage_classes_req_header(self):
-        api_mock = self.api_client_mock()
-        api_mock.config.return_value = {
-            'StorageClasses': {
-                'foo': { 'Default': True },
-                'bar': { 'Default': True },
-                'baz': { 'Default': False }
-            }
-        }
-        api_client = self.mock_keep_services(api_mock=api_mock, count=2)
-        keep_client = arvados.KeepClient(api_client=api_client, block_cache=self.make_block_cache(self.disk_cache))
-        resp_hdr = {
-            'x-keep-storage-classes-confirmed': 'foo=1, bar=1',
-            'x-keep-replicas-stored': 1
-        }
-        with tutil.mock_keep_responses(self.locator, 200, **resp_hdr) as mock:
-            keep_client.put(self.data, copies=1)
-            req_hdr = mock.responses[0]
-            self.assertIn(
-                'X-Keep-Storage-Classes: bar, foo', req_hdr.getopt(pycurl.HTTPHEADER))
-
-    def test_storage_classes_req_header(self):
-        self.assertEqual(
-            self.api_client.config()['StorageClasses'],
-            {'default': {'Default': True}})
-        cases = [
-            # requested, expected
-            [['foo'], 'X-Keep-Storage-Classes: foo'],
-            [['bar', 'foo'], 'X-Keep-Storage-Classes: bar, foo'],
-            [[], 'X-Keep-Storage-Classes: default'],
-            [None, 'X-Keep-Storage-Classes: default'],
-        ]
-        for req_classes, expected_header in cases:
-            headers = {'x-keep-replicas-stored': 1}
-            if req_classes is None or len(req_classes) == 0:
-                confirmed_hdr = 'default=1'
-            elif len(req_classes) > 0:
-                confirmed_hdr = ', '.join(["{}=1".format(cls) for cls in req_classes])
-            headers.update({'x-keep-storage-classes-confirmed': confirmed_hdr})
-            with tutil.mock_keep_responses(self.locator, 200, **headers) as mock:
-                self.keep_client.put(self.data, copies=1, classes=req_classes)
-                req_hdr = mock.responses[0]
-                self.assertIn(expected_header, req_hdr.getopt(pycurl.HTTPHEADER))
-
-    def test_partial_storage_classes_put(self):
-        headers = {
-            'x-keep-replicas-stored': 1,
-            'x-keep-storage-classes-confirmed': 'foo=1'}
-        with tutil.mock_keep_responses(self.locator, 200, 503, **headers) as mock:
-            with self.assertRaises(arvados.errors.KeepWriteError):
-                self.keep_client.put(self.data, copies=1, classes=['foo', 'bar'], num_retries=0)
-            # 1st request, both classes pending
-            req1_headers = mock.responses[0].getopt(pycurl.HTTPHEADER)
-            self.assertIn('X-Keep-Storage-Classes: bar, foo', req1_headers)
-            # 2nd try, 'foo' class already satisfied
-            req2_headers = mock.responses[1].getopt(pycurl.HTTPHEADER)
-            self.assertIn('X-Keep-Storage-Classes: bar', req2_headers)
-
-    def test_successful_storage_classes_put_requests(self):
-        cases = [
-            # wanted_copies, wanted_classes, confirmed_copies, confirmed_classes, expected_requests
-            [ 1, ['foo'], 1, 'foo=1', 1],
-            [ 1, ['foo'], 2, 'foo=2', 1],
-            [ 2, ['foo'], 2, 'foo=2', 1],
-            [ 2, ['foo'], 1, 'foo=1', 2],
-            [ 1, ['foo', 'bar'], 1, 'foo=1, bar=1', 1],
-            [ 1, ['foo', 'bar'], 2, 'foo=2, bar=2', 1],
-            [ 2, ['foo', 'bar'], 2, 'foo=2, bar=2', 1],
-            [ 2, ['foo', 'bar'], 1, 'foo=1, bar=1', 2],
-            [ 1, ['foo', 'bar'], 1, None, 1],
-            [ 1, ['foo'], 1, None, 1],
-            [ 2, ['foo'], 2, None, 1],
-            [ 2, ['foo'], 1, None, 2],
-        ]
-        for w_copies, w_classes, c_copies, c_classes, e_reqs in cases:
-            headers = {'x-keep-replicas-stored': c_copies}
-            if c_classes is not None:
-                headers.update({'x-keep-storage-classes-confirmed': c_classes})
-            with tutil.mock_keep_responses(self.locator, 200, 200, **headers) as mock:
-                case_desc = 'wanted_copies={}, wanted_classes="{}", confirmed_copies={}, confirmed_classes="{}", expected_requests={}'.format(w_copies, ', '.join(w_classes), c_copies, c_classes, e_reqs)
-                self.assertEqual(self.locator,
-                    self.keep_client.put(self.data, copies=w_copies, classes=w_classes),
-                    case_desc)
-                self.assertEqual(e_reqs, mock.call_count, case_desc)
-
-    def test_failed_storage_classes_put_requests(self):
-        cases = [
-            # wanted_copies, wanted_classes, confirmed_copies, confirmed_classes, return_code
-            [ 1, ['foo'], 1, 'bar=1', 200],
-            [ 1, ['foo'], 1, None, 503],
-            [ 2, ['foo'], 1, 'bar=1, foo=0', 200],
-            [ 3, ['foo'], 1, 'bar=1, foo=1', 200],
-            [ 3, ['foo', 'bar'], 1, 'bar=2, foo=1', 200],
-        ]
-        for w_copies, w_classes, c_copies, c_classes, return_code in cases:
-            headers = {'x-keep-replicas-stored': c_copies}
-            if c_classes is not None:
-                headers.update({'x-keep-storage-classes-confirmed': c_classes})
-            with tutil.mock_keep_responses(self.locator, return_code, return_code, **headers):
-                case_desc = 'wanted_copies={}, wanted_classes="{}", confirmed_copies={}, confirmed_classes="{}"'.format(w_copies, ', '.join(w_classes), c_copies, c_classes)
-                with self.assertRaises(arvados.errors.KeepWriteError, msg=case_desc):
-                    self.keep_client.put(self.data, copies=w_copies, classes=w_classes, num_retries=0)
 
 @tutil.skip_sleep
 @parameterized.parameterized_class([{"disk_cache": True}, {"disk_cache": False}])
 
 @tutil.skip_sleep
 @parameterized.parameterized_class([{"disk_cache": True}, {"disk_cache": False}])
@@ -1757,21 +1637,31 @@ class KeepDiskCacheTestCase(unittest.TestCase, tutil.ApiClientMock):
                 keep_client.get(self.locator)
 
 
                 keep_client.get(self.locator)
 
 
-    @mock.patch('mmap.mmap')
-    def test_disk_cache_retry_write_error(self, mockmmap):
+    def test_disk_cache_retry_write_error(self):
         block_cache = arvados.keep.KeepBlockCache(disk_cache=True,
                                                   disk_cache_dir=self.disk_cache_dir)
 
         keep_client = arvados.KeepClient(api_client=self.api_client, block_cache=block_cache)
 
         block_cache = arvados.keep.KeepBlockCache(disk_cache=True,
                                                   disk_cache_dir=self.disk_cache_dir)
 
         keep_client = arvados.KeepClient(api_client=self.api_client, block_cache=block_cache)
 
-        mockmmap.side_effect = (OSError(errno.ENOSPC, "no space"), self.data)
+        called = False
+        realmmap = mmap.mmap
+        def sideeffect_mmap(*args, **kwargs):
+            nonlocal called
+            if not called:
+                called = True
+                raise OSError(errno.ENOSPC, "no space")
+            else:
+                return realmmap(*args, **kwargs)
 
 
-        cache_max_before = block_cache.cache_max
+        with patch('mmap.mmap') as mockmmap:
+            mockmmap.side_effect = sideeffect_mmap
 
 
-        with tutil.mock_keep_responses(self.data, 200) as mock:
-            self.assertTrue(tutil.binary_compare(keep_client.get(self.locator), self.data))
+            cache_max_before = block_cache.cache_max
 
 
-        self.assertIsNotNone(keep_client.get_from_cache(self.locator))
+            with tutil.mock_keep_responses(self.data, 200) as mock:
+                self.assertTrue(tutil.binary_compare(keep_client.get(self.locator), self.data))
+
+            self.assertIsNotNone(keep_client.get_from_cache(self.locator))
 
         with open(os.path.join(self.disk_cache_dir, self.locator[0:3], self.locator+".keepcacheblock"), "rb") as f:
             self.assertTrue(tutil.binary_compare(f.read(), self.data))
 
         with open(os.path.join(self.disk_cache_dir, self.locator[0:3], self.locator+".keepcacheblock"), "rb") as f:
             self.assertTrue(tutil.binary_compare(f.read(), self.data))
@@ -1780,21 +1670,31 @@ class KeepDiskCacheTestCase(unittest.TestCase, tutil.ApiClientMock):
         self.assertTrue(cache_max_before > block_cache.cache_max)
 
 
         self.assertTrue(cache_max_before > block_cache.cache_max)
 
 
-    @mock.patch('mmap.mmap')
-    def test_disk_cache_retry_write_error2(self, mockmmap):
+    def test_disk_cache_retry_write_error2(self):
         block_cache = arvados.keep.KeepBlockCache(disk_cache=True,
                                                   disk_cache_dir=self.disk_cache_dir)
 
         keep_client = arvados.KeepClient(api_client=self.api_client, block_cache=block_cache)
 
         block_cache = arvados.keep.KeepBlockCache(disk_cache=True,
                                                   disk_cache_dir=self.disk_cache_dir)
 
         keep_client = arvados.KeepClient(api_client=self.api_client, block_cache=block_cache)
 
-        mockmmap.side_effect = (OSError(errno.ENOMEM, "no memory"), self.data)
+        called = False
+        realmmap = mmap.mmap
+        def sideeffect_mmap(*args, **kwargs):
+            nonlocal called
+            if not called:
+                called = True
+                raise OSError(errno.ENOMEM, "no memory")
+            else:
+                return realmmap(*args, **kwargs)
 
 
-        slots_before = block_cache._max_slots
+        with patch('mmap.mmap') as mockmmap:
+            mockmmap.side_effect = sideeffect_mmap
 
 
-        with tutil.mock_keep_responses(self.data, 200) as mock:
-            self.assertTrue(tutil.binary_compare(keep_client.get(self.locator), self.data))
+            slots_before = block_cache._max_slots
 
 
-        self.assertIsNotNone(keep_client.get_from_cache(self.locator))
+            with tutil.mock_keep_responses(self.data, 200) as mock:
+                self.assertTrue(tutil.binary_compare(keep_client.get(self.locator), self.data))
+
+            self.assertIsNotNone(keep_client.get_from_cache(self.locator))
 
         with open(os.path.join(self.disk_cache_dir, self.locator[0:3], self.locator+".keepcacheblock"), "rb") as f:
             self.assertTrue(tutil.binary_compare(f.read(), self.data))
 
         with open(os.path.join(self.disk_cache_dir, self.locator[0:3], self.locator+".keepcacheblock"), "rb") as f:
             self.assertTrue(tutil.binary_compare(f.read(), self.data))
index e47d64d3372028ee2a7ecbfdba1a543fb993f91f..bc93f403a090cdfc5e91793308393e9e7d05c61c 100644 (file)
@@ -2,10 +2,6 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import next
-from builtins import zip
-from builtins import str
-from builtins import range
 import datetime
 import itertools
 import random
 import datetime
 import itertools
 import random
index bcf784d13003a1de826e87d10f04d0a2f4a8d7d5..c6e713244cf4d1c187f522ef0eba1e0c7407df2b 100644 (file)
@@ -2,15 +2,13 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from builtins import zip
-from builtins import range
-from builtins import object
 import itertools
 import unittest
 
 import itertools
 import unittest
 
+from unittest import mock
+
 import arvados.errors as arv_error
 import arvados.retry as arv_retry
 import arvados.errors as arv_error
 import arvados.retry as arv_retry
-import mock
 
 class RetryLoopTestMixin(object):
     @staticmethod
 
 class RetryLoopTestMixin(object):
     @staticmethod
index 9389b25c88e10840b979f874369fbcbdb38f7540..a5a6bb22c143df64cea4cc36e94c62d91d7edeac 100644 (file)
@@ -2,18 +2,17 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from builtins import object
-import mock
-import os
-import unittest
 import hashlib
 import hashlib
-from . import run_test_server
 import json
 import json
-import arvados
-from . import arvados_testutil as tutil
+import os
+import unittest
+
 from apiclient import http as apiclient_http
 from apiclient import http as apiclient_http
+from unittest import mock
 
 
+import arvados
+from . import run_test_server
+from . import arvados_testutil as tutil
 
 @tutil.skip_sleep
 class ApiClientRetryTestMixin(object):
 
 @tutil.skip_sleep
 class ApiClientRetryTestMixin(object):
@@ -57,22 +56,3 @@ class ApiClientRetryTestMixin(object):
     def test_no_retry_after_immediate_success(self):
         with tutil.mock_api_responses(self.api_client, '{}', [200, 400]):
             self.run_method()
     def test_no_retry_after_immediate_success(self):
         with tutil.mock_api_responses(self.api_client, '{}', [200, 400]):
             self.run_method()
-
-
-class CurrentJobTestCase(ApiClientRetryTestMixin, unittest.TestCase):
-
-    DEFAULT_EXCEPTION = arvados.errors.ApiError
-
-    def setUp(self):
-        super(CurrentJobTestCase, self).setUp()
-        os.environ['JOB_UUID'] = 'zzzzz-zzzzz-zzzzzzzzzzzzzzz'
-        os.environ['JOB_WORK'] = '.'
-
-    def tearDown(self):
-        del os.environ['JOB_UUID']
-        del os.environ['JOB_WORK']
-        arvados._current_job = None
-        super(CurrentJobTestCase, self).tearDown()
-
-    def run_method(self):
-        arvados.current_job()
index 41add57c0e4922b49bc87b4ffaba265624413a20..4ef81c53d8448222a7ca5b1c387ebb388d78faa3 100644 (file)
@@ -2,10 +2,11 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-import mock
 import os
 import unittest
 
 import os
 import unittest
 
+from unittest import mock
+
 import arvados
 import arvados.collection
 
 import arvados
 import arvados.collection
 
diff --git a/sdk/python/tests/test_storage_classes.py b/sdk/python/tests/test_storage_classes.py
new file mode 100644 (file)
index 0000000..21bacc3
--- /dev/null
@@ -0,0 +1,128 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import arvados
+import pycurl
+
+import unittest
+import parameterized
+from . import arvados_testutil as tutil
+from .arvados_testutil import DiskCacheBase
+
+@tutil.skip_sleep
+@parameterized.parameterized_class([{"disk_cache": True}, {"disk_cache": False}])
+class KeepStorageClassesTestCase(unittest.TestCase, tutil.ApiClientMock, DiskCacheBase):
+    disk_cache = False
+
+    def setUp(self):
+        self.api_client = self.mock_keep_services(count=2)
+        self.keep_client = arvados.KeepClient(api_client=self.api_client, block_cache=self.make_block_cache(self.disk_cache))
+        self.data = b'xyzzy'
+        self.locator = '1271ed5ef305aadabc605b1609e24c52'
+
+    def tearDown(self):
+        DiskCacheBase.tearDown(self)
+
+    def test_multiple_default_storage_classes_req_header(self):
+        api_mock = self.api_client_mock()
+        api_mock.config.return_value = {
+            'StorageClasses': {
+                'foo': { 'Default': True },
+                'bar': { 'Default': True },
+                'baz': { 'Default': False }
+            }
+        }
+        api_client = self.mock_keep_services(api_mock=api_mock, count=2)
+        keep_client = arvados.KeepClient(api_client=api_client, block_cache=self.make_block_cache(self.disk_cache))
+        resp_hdr = {
+            'x-keep-storage-classes-confirmed': 'foo=1, bar=1',
+            'x-keep-replicas-stored': 1
+        }
+        with tutil.mock_keep_responses(self.locator, 200, **resp_hdr) as mock:
+            keep_client.put(self.data, copies=1)
+            req_hdr = mock.responses[0]
+            self.assertIn(
+                'X-Keep-Storage-Classes: bar, foo', req_hdr.getopt(pycurl.HTTPHEADER))
+
+    def test_storage_classes_req_header(self):
+        self.assertEqual(
+            self.api_client.config()['StorageClasses'],
+            {'default': {'Default': True}})
+        cases = [
+            # requested, expected
+            [['foo'], 'X-Keep-Storage-Classes: foo'],
+            [['bar', 'foo'], 'X-Keep-Storage-Classes: bar, foo'],
+            [[], 'X-Keep-Storage-Classes: default'],
+            [None, 'X-Keep-Storage-Classes: default'],
+        ]
+        for req_classes, expected_header in cases:
+            headers = {'x-keep-replicas-stored': 1}
+            if req_classes is None or len(req_classes) == 0:
+                confirmed_hdr = 'default=1'
+            elif len(req_classes) > 0:
+                confirmed_hdr = ', '.join(["{}=1".format(cls) for cls in req_classes])
+            headers.update({'x-keep-storage-classes-confirmed': confirmed_hdr})
+            with tutil.mock_keep_responses(self.locator, 200, **headers) as mock:
+                self.keep_client.put(self.data, copies=1, classes=req_classes)
+                req_hdr = mock.responses[0]
+                self.assertIn(expected_header, req_hdr.getopt(pycurl.HTTPHEADER))
+
+    def test_partial_storage_classes_put(self):
+        headers = {
+            'x-keep-replicas-stored': 1,
+            'x-keep-storage-classes-confirmed': 'foo=1'}
+        with tutil.mock_keep_responses(self.locator, 200, 503, **headers) as mock:
+            with self.assertRaises(arvados.errors.KeepWriteError):
+                self.keep_client.put(self.data, copies=1, classes=['foo', 'bar'], num_retries=0)
+            # 1st request, both classes pending
+            req1_headers = mock.responses[0].getopt(pycurl.HTTPHEADER)
+            self.assertIn('X-Keep-Storage-Classes: bar, foo', req1_headers)
+            # 2nd try, 'foo' class already satisfied
+            req2_headers = mock.responses[1].getopt(pycurl.HTTPHEADER)
+            self.assertIn('X-Keep-Storage-Classes: bar', req2_headers)
+
+    def test_successful_storage_classes_put_requests(self):
+        cases = [
+            # wanted_copies, wanted_classes, confirmed_copies, confirmed_classes, expected_requests
+            [ 1, ['foo'], 1, 'foo=1', 1],
+            [ 1, ['foo'], 2, 'foo=2', 1],
+            [ 2, ['foo'], 2, 'foo=2', 1],
+            [ 2, ['foo'], 1, 'foo=1', 2],
+            [ 1, ['foo', 'bar'], 1, 'foo=1, bar=1', 1],
+            [ 1, ['foo', 'bar'], 2, 'foo=2, bar=2', 1],
+            [ 2, ['foo', 'bar'], 2, 'foo=2, bar=2', 1],
+            [ 2, ['foo', 'bar'], 1, 'foo=1, bar=1', 2],
+            [ 1, ['foo', 'bar'], 1, None, 1],
+            [ 1, ['foo'], 1, None, 1],
+            [ 2, ['foo'], 2, None, 1],
+            [ 2, ['foo'], 1, None, 2],
+        ]
+        for w_copies, w_classes, c_copies, c_classes, e_reqs in cases:
+            headers = {'x-keep-replicas-stored': c_copies}
+            if c_classes is not None:
+                headers.update({'x-keep-storage-classes-confirmed': c_classes})
+            with tutil.mock_keep_responses(self.locator, 200, 200, **headers) as mock:
+                case_desc = 'wanted_copies={}, wanted_classes="{}", confirmed_copies={}, confirmed_classes="{}", expected_requests={}'.format(w_copies, ', '.join(w_classes), c_copies, c_classes, e_reqs)
+                self.assertEqual(self.locator,
+                    self.keep_client.put(self.data, copies=w_copies, classes=w_classes),
+                    case_desc)
+                self.assertEqual(e_reqs, mock.call_count, case_desc)
+
+    def test_failed_storage_classes_put_requests(self):
+        cases = [
+            # wanted_copies, wanted_classes, confirmed_copies, confirmed_classes, return_code
+            [ 1, ['foo'], 1, 'bar=1', 200],
+            [ 1, ['foo'], 1, None, 503],
+            [ 2, ['foo'], 1, 'bar=1, foo=0', 200],
+            [ 3, ['foo'], 1, 'bar=1, foo=1', 200],
+            [ 3, ['foo', 'bar'], 1, 'bar=2, foo=1', 200],
+        ]
+        for w_copies, w_classes, c_copies, c_classes, return_code in cases:
+            headers = {'x-keep-replicas-stored': c_copies}
+            if c_classes is not None:
+                headers.update({'x-keep-storage-classes-confirmed': c_classes})
+            with tutil.mock_keep_responses(self.locator, return_code, return_code, **headers):
+                case_desc = 'wanted_copies={}, wanted_classes="{}", confirmed_copies={}, confirmed_classes="{}"'.format(w_copies, ', '.join(w_classes), c_copies, c_classes)
+                with self.assertRaises(arvados.errors.KeepWriteError, msg=case_desc):
+                    self.keep_client.put(self.data, copies=w_copies, classes=w_classes, num_retries=0)
index 12a3340eab55e25593a1b9d29e2c9296e71039fe..a3f5d9ff635e58c54600397e5e398e639df44a74 100644 (file)
@@ -2,16 +2,15 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
 #
 # SPDX-License-Identifier: Apache-2.0
 
-from __future__ import absolute_import
-from builtins import object
 import bz2
 import gzip
 import io
 import bz2
 import gzip
 import io
-import mock
 import os
 import unittest
 import hashlib
 
 import os
 import unittest
 import hashlib
 
+from unittest import mock
+
 import arvados
 from arvados import StreamReader, StreamFileReader
 from arvados._ranges import Range
 import arvados
 from arvados import StreamReader, StreamFileReader
 from arvados._ranges import Range
index aa2e739e20d65849c5444471b24c5e34a5f4eac9..2f5db3b9d95bd0fbc041fd8fb8140194df6f6066 100644 (file)
@@ -4,7 +4,8 @@
 
 import arvados
 import unittest
 
 import arvados
 import unittest
-import mock
+
+from unittest import mock
 
 from arvados import api, vocabulary
 
 
 from arvados import api, vocabulary
 
index 003b886cee3f357a71ee5bd9d08e5707eecae50f..8b1bbdfc8760861f49f04a90939e80b27b03a400 100644 (file)
@@ -8,67 +8,67 @@ GIT
 GEM
   remote: https://rubygems.org/
   specs:
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (7.0.8)
-      actionpack (= 7.0.8)
-      activesupport (= 7.0.8)
+    actioncable (7.0.8.1)
+      actionpack (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
-    actionmailbox (7.0.8)
-      actionpack (= 7.0.8)
-      activejob (= 7.0.8)
-      activerecord (= 7.0.8)
-      activestorage (= 7.0.8)
-      activesupport (= 7.0.8)
+    actionmailbox (7.0.8.1)
+      actionpack (= 7.0.8.1)
+      activejob (= 7.0.8.1)
+      activerecord (= 7.0.8.1)
+      activestorage (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       mail (>= 2.7.1)
       net-imap
       net-pop
       net-smtp
       mail (>= 2.7.1)
       net-imap
       net-pop
       net-smtp
-    actionmailer (7.0.8)
-      actionpack (= 7.0.8)
-      actionview (= 7.0.8)
-      activejob (= 7.0.8)
-      activesupport (= 7.0.8)
+    actionmailer (7.0.8.1)
+      actionpack (= 7.0.8.1)
+      actionview (= 7.0.8.1)
+      activejob (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       mail (~> 2.5, >= 2.5.4)
       net-imap
       net-pop
       net-smtp
       rails-dom-testing (~> 2.0)
       mail (~> 2.5, >= 2.5.4)
       net-imap
       net-pop
       net-smtp
       rails-dom-testing (~> 2.0)
-    actionpack (7.0.8)
-      actionview (= 7.0.8)
-      activesupport (= 7.0.8)
+    actionpack (7.0.8.1)
+      actionview (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       rack (~> 2.0, >= 2.2.4)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.2.0)
       rack (~> 2.0, >= 2.2.4)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.2.0)
-    actiontext (7.0.8)
-      actionpack (= 7.0.8)
-      activerecord (= 7.0.8)
-      activestorage (= 7.0.8)
-      activesupport (= 7.0.8)
+    actiontext (7.0.8.1)
+      actionpack (= 7.0.8.1)
+      activerecord (= 7.0.8.1)
+      activestorage (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       globalid (>= 0.6.0)
       nokogiri (>= 1.8.5)
       globalid (>= 0.6.0)
       nokogiri (>= 1.8.5)
-    actionview (7.0.8)
-      activesupport (= 7.0.8)
+    actionview (7.0.8.1)
+      activesupport (= 7.0.8.1)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.1, >= 1.2.0)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.1, >= 1.2.0)
-    activejob (7.0.8)
-      activesupport (= 7.0.8)
+    activejob (7.0.8.1)
+      activesupport (= 7.0.8.1)
       globalid (>= 0.3.6)
       globalid (>= 0.3.6)
-    activemodel (7.0.8)
-      activesupport (= 7.0.8)
-    activerecord (7.0.8)
-      activemodel (= 7.0.8)
-      activesupport (= 7.0.8)
-    activestorage (7.0.8)
-      actionpack (= 7.0.8)
-      activejob (= 7.0.8)
-      activerecord (= 7.0.8)
-      activesupport (= 7.0.8)
+    activemodel (7.0.8.1)
+      activesupport (= 7.0.8.1)
+    activerecord (7.0.8.1)
+      activemodel (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
+    activestorage (7.0.8.1)
+      actionpack (= 7.0.8.1)
+      activejob (= 7.0.8.1)
+      activerecord (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       marcel (~> 1.0)
       mini_mime (>= 1.1.0)
       marcel (~> 1.0)
       mini_mime (>= 1.1.0)
-    activesupport (7.0.8)
+    activesupport (7.0.8.1)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
@@ -109,7 +109,7 @@ GEM
     byebug (11.1.3)
     concurrent-ruby (1.2.3)
     crass (1.0.6)
     byebug (11.1.3)
     concurrent-ruby (1.2.3)
     crass (1.0.6)
-    date (3.3.3)
+    date (3.3.4)
     docile (1.4.0)
     erubi (1.12.0)
     extlib (0.9.16)
     docile (1.4.0)
     erubi (1.12.0)
     extlib (0.9.16)
@@ -141,7 +141,7 @@ GEM
       os (>= 0.9, < 2.0)
       signet (>= 0.16, < 2.a)
     httpclient (2.8.3)
       os (>= 0.9, < 2.0)
       signet (>= 0.16, < 2.a)
     httpclient (2.8.3)
-    i18n (1.14.1)
+    i18n (1.14.4)
       concurrent-ruby (~> 1.0)
     jquery-rails (4.6.0)
       rails-dom-testing (>= 1, < 3)
       concurrent-ruby (~> 1.0)
     jquery-rails (4.6.0)
       rails-dom-testing (>= 1, < 3)
@@ -160,7 +160,7 @@ GEM
       railties (>= 4)
       request_store (~> 1.0)
     logstash-event (1.2.02)
       railties (>= 4)
       request_store (~> 1.0)
     logstash-event (1.2.02)
-    loofah (2.21.3)
+    loofah (2.22.0)
       crass (~> 1.0.2)
       nokogiri (>= 1.12.0)
     mail (2.8.1)
       crass (~> 1.0.2)
       nokogiri (>= 1.12.0)
     mail (2.8.1)
@@ -168,10 +168,10 @@ GEM
       net-imap
       net-pop
       net-smtp
       net-imap
       net-pop
       net-smtp
-    marcel (1.0.2)
+    marcel (1.0.4)
     method_source (1.0.0)
     mini_mime (1.1.5)
     method_source (1.0.0)
     mini_mime (1.1.5)
-    mini_portile2 (2.8.4)
+    mini_portile2 (2.8.5)
     minitest (5.10.3)
     mocha (2.1.0)
       ruby2_keywords (>= 0.0.5)
     minitest (5.10.3)
     mocha (2.1.0)
       ruby2_keywords (>= 0.0.5)
@@ -182,12 +182,12 @@ GEM
       net-protocol
     net-pop (0.1.2)
       net-protocol
       net-protocol
     net-pop (0.1.2)
       net-protocol
-    net-protocol (0.2.1)
+    net-protocol (0.2.2)
       timeout
       timeout
-    net-smtp (0.4.0)
+    net-smtp (0.5.0)
       net-protocol
       net-protocol
-    nio4r (2.5.9)
-    nokogiri (1.15.4)
+    nio4r (2.7.1)
+    nokogiri (1.15.6)
       mini_portile2 (~> 2.8.2)
       racc (~> 1.4)
     oj (3.16.1)
       mini_portile2 (~> 2.8.2)
       racc (~> 1.4)
     oj (3.16.1)
@@ -199,24 +199,24 @@ GEM
     pg (1.5.4)
     power_assert (2.0.3)
     public_suffix (5.0.4)
     pg (1.5.4)
     power_assert (2.0.3)
     public_suffix (5.0.4)
-    racc (1.7.1)
-    rack (2.2.8)
+    racc (1.7.3)
+    rack (2.2.9)
     rack-test (2.1.0)
       rack (>= 1.3)
     rack-test (2.1.0)
       rack (>= 1.3)
-    rails (7.0.8)
-      actioncable (= 7.0.8)
-      actionmailbox (= 7.0.8)
-      actionmailer (= 7.0.8)
-      actionpack (= 7.0.8)
-      actiontext (= 7.0.8)
-      actionview (= 7.0.8)
-      activejob (= 7.0.8)
-      activemodel (= 7.0.8)
-      activerecord (= 7.0.8)
-      activestorage (= 7.0.8)
-      activesupport (= 7.0.8)
+    rails (7.0.8.1)
+      actioncable (= 7.0.8.1)
+      actionmailbox (= 7.0.8.1)
+      actionmailer (= 7.0.8.1)
+      actionpack (= 7.0.8.1)
+      actiontext (= 7.0.8.1)
+      actionview (= 7.0.8.1)
+      activejob (= 7.0.8.1)
+      activemodel (= 7.0.8.1)
+      activerecord (= 7.0.8.1)
+      activestorage (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       bundler (>= 1.15.0)
       bundler (>= 1.15.0)
-      railties (= 7.0.8)
+      railties (= 7.0.8.1)
     rails-controller-testing (1.0.5)
       actionpack (>= 5.0.1.rc1)
       actionview (>= 5.0.1.rc1)
     rails-controller-testing (1.0.5)
       actionpack (>= 5.0.1.rc1)
       actionview (>= 5.0.1.rc1)
@@ -231,14 +231,14 @@ GEM
     rails-observers (0.1.5)
       activemodel (>= 4.0)
     rails-perftest (0.0.7)
     rails-observers (0.1.5)
       activemodel (>= 4.0)
     rails-perftest (0.0.7)
-    railties (7.0.8)
-      actionpack (= 7.0.8)
-      activesupport (= 7.0.8)
+    railties (7.0.8.1)
+      actionpack (= 7.0.8.1)
+      activesupport (= 7.0.8.1)
       method_source
       rake (>= 12.2)
       thor (~> 1.0)
       zeitwerk (~> 2.5)
       method_source
       rake (>= 12.2)
       thor (~> 1.0)
       zeitwerk (~> 2.5)
-    rake (13.0.6)
+    rake (13.2.1)
     rb-fsevent (0.11.2)
     rb-inotify (0.10.1)
       ffi (~> 1.0)
     rb-fsevent (0.11.2)
     rb-inotify (0.10.1)
       ffi (~> 1.0)
@@ -272,15 +272,15 @@ GEM
       sprockets (>= 3.0.0)
     test-unit (3.6.1)
       power_assert
       sprockets (>= 3.0.0)
     test-unit (3.6.1)
       power_assert
-    thor (1.2.2)
-    timeout (0.4.0)
+    thor (1.3.1)
+    timeout (0.4.1)
     tzinfo (2.0.6)
       concurrent-ruby (~> 1.0)
     webrick (1.8.1)
     websocket-driver (0.7.6)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.5)
     tzinfo (2.0.6)
       concurrent-ruby (~> 1.0)
     webrick (1.8.1)
     websocket-driver (0.7.6)
       websocket-extensions (>= 0.1.0)
     websocket-extensions (0.1.5)
-    zeitwerk (2.6.11)
+    zeitwerk (2.6.13)
     zlib (3.1.0)
 
 PLATFORMS
     zlib (3.1.0)
 
 PLATFORMS
@@ -320,4 +320,4 @@ DEPENDENCIES
   webrick
 
 BUNDLED WITH
   webrick
 
 BUNDLED WITH
-   2.4.19
+   2.4.22
diff --git a/services/api/app/assets/stylesheets/api_client_authorizations.css.scss b/services/api/app/assets/stylesheets/api_client_authorizations.css.scss
deleted file mode 100644 (file)
index ec87eb2..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the ApiClientAuthorizations controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/api_clients.css.scss b/services/api/app/assets/stylesheets/api_clients.css.scss
deleted file mode 100644 (file)
index 61d7e53..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the ApiClients controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/authorized_keys.css.scss b/services/api/app/assets/stylesheets/authorized_keys.css.scss
deleted file mode 100644 (file)
index 9eeaa89..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the AuthorizedKeys controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/collections.css.scss b/services/api/app/assets/stylesheets/collections.css.scss
deleted file mode 100644 (file)
index 7510f17..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Collections controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/commit_ancestors.css.scss b/services/api/app/assets/stylesheets/commit_ancestors.css.scss
deleted file mode 100644 (file)
index 5004f86..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the commit_ancestors controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/commits.css.scss b/services/api/app/assets/stylesheets/commits.css.scss
deleted file mode 100644 (file)
index 6b4df4d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the commits controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/groups.css.scss b/services/api/app/assets/stylesheets/groups.css.scss
deleted file mode 100644 (file)
index 905e72a..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Groups controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/humans.css.scss b/services/api/app/assets/stylesheets/humans.css.scss
deleted file mode 100644 (file)
index 29668c2..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Humans controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/job_tasks.css.scss b/services/api/app/assets/stylesheets/job_tasks.css.scss
deleted file mode 100644 (file)
index 0d4d260..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the JobTasks controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/jobs.css.scss b/services/api/app/assets/stylesheets/jobs.css.scss
deleted file mode 100644 (file)
index 53b6ca7..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Jobs controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/keep_disks.css.scss b/services/api/app/assets/stylesheets/keep_disks.css.scss
deleted file mode 100644 (file)
index 1996f11..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the KeepDisks controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/links.css.scss b/services/api/app/assets/stylesheets/links.css.scss
deleted file mode 100644 (file)
index c2e90ad..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the links controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/logs.css.scss b/services/api/app/assets/stylesheets/logs.css.scss
deleted file mode 100644 (file)
index c8b22f9..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Logs controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/nodes.css b/services/api/app/assets/stylesheets/nodes.css
deleted file mode 100644 (file)
index d1ce011..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: AGPL-3.0 */
-
-/*
-  Place all the styles related to the matching controller here.
-  They will automatically be included in application.css.
-*/
-.node-status {
-    /* unknown status - might be bad */
-    background: #ff8888;
-}
-.node-status-running .node-status {
-    background: #88ff88;
-}
-.node-status-missing .node-status {
-    background: #ff8888;
-}
-.node-status-terminated .node-status {
-    background: #ffffff;
-}
-
-.node-slurm-state {
-    /* unknown status - might be bad */
-    background: #ff8888;
-}
-.node-status-missing .node-slurm-state {
-    background: #ffffff;
-}
-.node-status-terminated .node-slurm-state {
-    background: #ffffff;
-}
-.node-status-running .node-slurm-state-alloc {
-    background: #88ff88;
-}
-.node-status-running .node-slurm-state-idle {
-    background: #ffbbbb;
-}
-.node-status-running .node-slurm-state-down {
-    background: #ff8888;
-}
diff --git a/services/api/app/assets/stylesheets/nodes.css.scss b/services/api/app/assets/stylesheets/nodes.css.scss
deleted file mode 100644 (file)
index a7b0861..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Nodes controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/pipeline_instances.css.scss b/services/api/app/assets/stylesheets/pipeline_instances.css.scss
deleted file mode 100644 (file)
index 7292a9a..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the PipelineInstances controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/pipeline_templates.css.scss b/services/api/app/assets/stylesheets/pipeline_templates.css.scss
deleted file mode 100644 (file)
index 40c0cef..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the PipelineTemplates controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/repositories.css.scss b/services/api/app/assets/stylesheets/repositories.css.scss
deleted file mode 100644 (file)
index 1dd9a16..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Repositories controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/specimens.css.scss b/services/api/app/assets/stylesheets/specimens.css.scss
deleted file mode 100644 (file)
index 60d630c..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Specimens controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/traits.css.scss b/services/api/app/assets/stylesheets/traits.css.scss
deleted file mode 100644 (file)
index 7d2f713..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the Traits controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/services/api/app/assets/stylesheets/virtual_machines.css.scss b/services/api/app/assets/stylesheets/virtual_machines.css.scss
deleted file mode 100644 (file)
index 4a94d45..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-// Place all the styles related to the VirtualMachines controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
index ad1771a87eac936c697ae6e0ef5ba1985d599fcc..155a8e88268f11c37a0d36970c699a3622751a1c 100644 (file)
@@ -172,17 +172,7 @@ class Arvados::V1::CollectionsController < ApplicationController
       end
 
       if direction == :search_up
       end
 
       if direction == :search_up
-        # Search upstream for jobs where this locator is the output of some job
-        if !Rails.configuration.API.DisabledAPIs["jobs.list"]
-          Job.readable_by(*@read_users).where(output: loc.to_s).each do |job|
-            search_edges(visited, job.uuid, :search_up)
-          end
-
-          Job.readable_by(*@read_users).where(log: loc.to_s).each do |job|
-            search_edges(visited, job.uuid, :search_up)
-          end
-        end
-
+        # Search upstream for jobs where this locator is the output of some container
         Container.readable_by(*@read_users).where(output: loc.to_s).pluck(:uuid).each do |c_uuid|
           search_edges(visited, c_uuid, :search_up)
         end
         Container.readable_by(*@read_users).where(output: loc.to_s).pluck(:uuid).each do |c_uuid|
           search_edges(visited, c_uuid, :search_up)
         end
@@ -196,17 +186,7 @@ class Arvados::V1::CollectionsController < ApplicationController
           return
         end
 
           return
         end
 
-        # Search downstream for jobs where this locator is in script_parameters
-        if !Rails.configuration.API.DisabledAPIs["jobs.list"]
-          Job.readable_by(*@read_users).where(["jobs.script_parameters like ?", "%#{loc.to_s}%"]).each do |job|
-            search_edges(visited, job.uuid, :search_down)
-          end
-
-          Job.readable_by(*@read_users).where(["jobs.docker_image_locator = ?", "#{loc.to_s}"]).each do |job|
-            search_edges(visited, job.uuid, :search_down)
-          end
-        end
-
+        # Search downstream for jobs where this locator is in mounts
         Container.readable_by(*@read_users).where([Container.full_text_trgm + " like ?", "%#{loc.to_s}%"]).select("output, log, uuid").each do |c|
           if c.output != loc.to_s && c.log != loc.to_s
             search_edges(visited, c.uuid, :search_down)
         Container.readable_by(*@read_users).where([Container.full_text_trgm + " like ?", "%#{loc.to_s}%"]).select("output, log, uuid").each do |c|
           if c.output != loc.to_s && c.log != loc.to_s
             search_edges(visited, c.uuid, :search_down)
@@ -216,21 +196,7 @@ class Arvados::V1::CollectionsController < ApplicationController
     else
       # uuid is a regular Arvados UUID
       rsc = ArvadosModel::resource_class_for_uuid uuid
     else
       # uuid is a regular Arvados UUID
       rsc = ArvadosModel::resource_class_for_uuid uuid
-      if rsc == Job
-        Job.readable_by(*@read_users).where(uuid: uuid).each do |job|
-          visited[uuid] = job.as_api_response
-          if direction == :search_up
-            # Follow upstream collections referenced in the script parameters
-            find_collections(visited, job) do |hash, col_uuid|
-              search_edges(visited, hash, :search_up) if hash
-              search_edges(visited, col_uuid, :search_up) if col_uuid
-            end
-          elsif direction == :search_down
-            # Follow downstream job output
-            search_edges(visited, job.output, direction)
-          end
-        end
-      elsif rsc == Container
+      if rsc == Container
         c = Container.readable_by(*@read_users).where(uuid: uuid).limit(1).first
         if c
           visited[uuid] = c.as_api_response
         c = Container.readable_by(*@read_users).where(uuid: uuid).limit(1).first
         if c
           visited[uuid] = c.as_api_response
@@ -266,16 +232,6 @@ class Arvados::V1::CollectionsController < ApplicationController
           if direction == :search_up
             visited[c.uuid] = c.as_api_response
 
           if direction == :search_up
             visited[c.uuid] = c.as_api_response
 
-            if !Rails.configuration.API.DisabledAPIs["jobs.list"]
-              Job.readable_by(*@read_users).where(output: c.portable_data_hash).each do |job|
-                search_edges(visited, job.uuid, :search_up)
-              end
-
-              Job.readable_by(*@read_users).where(log: c.portable_data_hash).each do |job|
-                search_edges(visited, job.uuid, :search_up)
-              end
-            end
-
             ContainerRequest.readable_by(*@read_users).where(output_uuid: uuid).pluck(:uuid).each do |cr_uuid|
               search_edges(visited, cr_uuid, :search_up)
             end
             ContainerRequest.readable_by(*@read_users).where(output_uuid: uuid).pluck(:uuid).each do |cr_uuid|
               search_edges(visited, cr_uuid, :search_up)
             end
index c362cf32d7e271c35891f10a33cf2f970503c09d..be73d39dd1b43ba2032d22afdf8837cd17e750bb 100644 (file)
@@ -218,10 +218,7 @@ class Arvados::V1::GroupsController < ApplicationController
 
     request_filters = @filters
 
 
     request_filters = @filters
 
-    klasses = [Group,
-     Job, PipelineInstance, PipelineTemplate, ContainerRequest, Workflow,
-     Collection,
-     Human, Specimen, Trait]
+    klasses = [Group, ContainerRequest, Workflow, Collection]
 
     table_names = Hash[klasses.collect { |k| [k, k.table_name] }]
 
 
     table_names = Hash[klasses.collect { |k| [k, k.table_name] }]
 
diff --git a/services/api/app/controllers/arvados/v1/humans_controller.rb b/services/api/app/controllers/arvados/v1/humans_controller.rb
deleted file mode 100644 (file)
index 88eee30..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::HumansController < ApplicationController
-end
diff --git a/services/api/app/controllers/arvados/v1/job_tasks_controller.rb b/services/api/app/controllers/arvados/v1/job_tasks_controller.rb
deleted file mode 100644 (file)
index b960d2e..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::JobTasksController < ApplicationController
-  accept_attribute_as_json :parameters, Hash
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/jobs_controller.rb b/services/api/app/controllers/arvados/v1/jobs_controller.rb
deleted file mode 100644 (file)
index 2d6b052..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::JobsController < ApplicationController
-  accept_attribute_as_json :components, Hash
-  accept_attribute_as_json :script_parameters, Hash
-  accept_attribute_as_json :runtime_constraints, Hash
-  accept_attribute_as_json :tasks_summary, Hash
-  skip_before_action :find_object_by_uuid, :only => [:queue, :queue_size]
-  skip_before_action :render_404_if_no_object, :only => [:queue, :queue_size]
-
-  include DbCurrentTime
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def cancel
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def lock
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def queue
-    @objects = []
-    index
-  end
-
-  def queue_size
-    render :json => {:queue_size => 0}
-  end
-
-  def self._create_requires_parameters
-    (super rescue {}).
-      merge({
-              find_or_create: {
-                type: 'boolean', required: false, default: false,
-              },
-              filters: {
-                type: 'array', required: false,
-              },
-              minimum_script_version: {
-                type: 'string', required: false,
-              },
-              exclude_script_versions: {
-                type: 'array', required: false,
-              },
-            })
-  end
-
-  def self._queue_requires_parameters
-    self._index_requires_parameters
-  end
-
-  protected
-
-  def load_filters_param
-    begin
-      super
-      attrs = resource_attrs rescue {}
-      @filters = Job.load_job_specific_filters attrs, @filters, @read_users
-    rescue ArgumentError => error
-      send_error(error.message)
-      false
-    else
-      true
-    end
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/keep_disks_controller.rb b/services/api/app/controllers/arvados/v1/keep_disks_controller.rb
deleted file mode 100644 (file)
index b8aa096..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::KeepDisksController < ApplicationController
-  skip_before_action :require_auth_scope, only: :ping
-  skip_before_action :render_404_if_no_object, only: :ping
-
-  def self._ping_requires_parameters
-    {
-      uuid: {required: false},
-      ping_secret: {required: true},
-      node_uuid: {required: false},
-      filesystem_uuid: {required: false},
-      service_host: {required: false},
-      service_port: {required: true},
-      service_ssl_flag: {required: true}
-    }
-  end
-
-  def ping
-    params[:service_host] ||= request.env['REMOTE_ADDR']
-    if !params[:uuid] && current_user.andand.is_admin
-      # Create a new KeepDisk and ping it.
-      @object = KeepDisk.new(filesystem_uuid: params[:filesystem_uuid])
-      @object.save!
-
-      # In the first ping from this new filesystem_uuid, we can't
-      # expect the keep node to know the ping_secret so we made sure
-      # we got an admin token. Here we add ping_secret to params so
-      # the ping call below is properly authenticated.
-      params[:ping_secret] = @object.ping_secret
-    end
-    act_as_system_user do
-      if !@object.andand.ping(params)
-        return render_not_found "object not found"
-      end
-      # Render the :superuser view (i.e., include the ping_secret) even
-      # if !current_user.is_admin. This is safe because @object.ping's
-      # success implies the ping_secret was already known by the client.
-      send_json @object.as_api_response(:superuser)
-    end
-  end
-
-  def find_objects_for_index
-    # all users can list all keep disks
-    @objects = model_class.where('1=1')
-    super
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/nodes_controller.rb b/services/api/app/controllers/arvados/v1/nodes_controller.rb
deleted file mode 100644 (file)
index 2510fd4..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::NodesController < ApplicationController
-  skip_before_action :require_auth_scope, :only => :ping
-  skip_before_action :find_object_by_uuid, :only => :ping
-  skip_before_action :render_404_if_no_object, :only => :ping
-
-  include DbCurrentTime
-
-  def self._ping_requires_parameters
-    { ping_secret: {required: true} }
-  end
-
-  def self._create_requires_parameters
-    super.merge(
-      { assign_slot: {required: false, type: 'boolean', description: 'assign slot and hostname'} })
-  end
-
-  def self._update_requires_parameters
-    super.merge(
-      { assign_slot: {required: false, type: 'boolean', description: 'assign slot and hostname'} })
-  end
-
-  def create
-    @object = model_class.new(resource_attrs)
-    @object.assign_slot if params[:assign_slot]
-    @object.save!
-    show
-  end
-
-  def update
-    if resource_attrs[:job_uuid].is_a? String
-      @object.job_readable = readable_job_uuids([resource_attrs[:job_uuid]]).any?
-    end
-    attrs_to_update = resource_attrs.reject { |k,v|
-      [:kind, :etag, :href].index k
-    }
-    @object.update!(attrs_to_update)
-    @object.assign_slot if params[:assign_slot]
-    @object.save!
-    show
-  end
-
-  def ping
-    act_as_system_user do
-      @object = Node.where(uuid: (params[:id] || params[:uuid])).first
-      if !@object
-        return render_not_found
-      end
-      ping_data = {
-        ip: params[:local_ipv4] || request.remote_ip,
-        ec2_instance_id: params[:instance_id]
-      }
-      [:ping_secret, :total_cpu_cores, :total_ram_mb, :total_scratch_mb]
-        .each do |key|
-        ping_data[key] = params[key] if params[key]
-      end
-      @object.ping(ping_data)
-      if @object.info['ping_secret'] == params[:ping_secret]
-        send_json @object.as_api_response(:superuser)
-      else
-        raise "Invalid ping_secret after ping"
-      end
-    end
-  end
-
-  def find_objects_for_index
-    if !current_user.andand.is_admin && current_user.andand.is_active
-      # active non-admin users can list nodes that are (or were
-      # recently) working
-      @objects = model_class.where('last_ping_at >= ?', db_current_time - 1.hours)
-    end
-    super
-    if @select.nil? or @select.include? 'job_uuid'
-      job_uuids = @objects.map { |n| n[:job_uuid] }.compact
-      assoc_jobs = readable_job_uuids(job_uuids)
-      @objects.each do |node|
-        node.job_readable = assoc_jobs.include?(node[:job_uuid])
-      end
-    end
-  end
-
-  protected
-
-  def readable_job_uuids(uuids)
-    Job.readable_by(*@read_users).select(:uuid).where(uuid: uuids).map(&:uuid)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/pipeline_instances_controller.rb b/services/api/app/controllers/arvados/v1/pipeline_instances_controller.rb
deleted file mode 100644 (file)
index 166f710..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::PipelineInstancesController < ApplicationController
-  accept_attribute_as_json :components, Hash
-  accept_attribute_as_json :properties, Hash
-  accept_attribute_as_json :components_summary, Hash
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-
-  def cancel
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/pipeline_templates_controller.rb b/services/api/app/controllers/arvados/v1/pipeline_templates_controller.rb
deleted file mode 100644 (file)
index 4a5e724..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::PipelineTemplatesController < ApplicationController
-  accept_attribute_as_json :components, Hash
-
-  def create
-    return send_error("Unsupported legacy jobs API",
-                      status: 400)
-  end
-end
diff --git a/services/api/app/controllers/arvados/v1/repositories_controller.rb b/services/api/app/controllers/arvados/v1/repositories_controller.rb
deleted file mode 100644 (file)
index 9dff622..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::RepositoriesController < ApplicationController
-  skip_before_action :find_object_by_uuid, :only => :get_all_permissions
-  skip_before_action :render_404_if_no_object, :only => :get_all_permissions
-  before_action :admin_required, :only => :get_all_permissions
-
-  def get_all_permissions
-    # user_aks is a map of {user_uuid => array of public keys}
-    user_aks = {}
-    # admins is an array of user_uuids
-    admins = []
-    User.
-      where('users.is_active = ? or users.uuid = ?', true, anonymous_user_uuid).
-      eager_load(:authorized_keys).find_each do |u|
-      user_aks[u.uuid] = u.authorized_keys.collect do |ak|
-        {
-          public_key: ak.public_key,
-          authorized_key_uuid: ak.uuid
-        }
-      end
-      admins << u.uuid if u.is_admin
-    end
-    all_group_permissions = User.all_group_permissions
-    @repo_info = {}
-    Repository.eager_load(:permissions).find_each do |repo|
-      @repo_info[repo.uuid] = {
-        uuid: repo.uuid,
-        name: repo.name,
-        push_url: repo.push_url,
-        fetch_url: repo.fetch_url,
-        user_permissions: {},
-      }
-      # evidence is an array of {name: 'can_xxx', user_uuid: 'x-y-z'},
-      # one entry for each piece of evidence we find in the permission
-      # database that establishes that a user can access this
-      # repository. Multiple entries can be added for a given user,
-      # possibly with different access levels; these will be compacted
-      # below.
-      evidence = []
-      repo.permissions.each do |perm|
-        if ArvadosModel::resource_class_for_uuid(perm.tail_uuid) == Group
-          # A group has permission. Each user who has access to this
-          # group also has access to the repository. Access level is
-          # min(group-to-repo permission, user-to-group permission).
-          user_aks.each do |user_uuid, _|
-            perm_mask = all_group_permissions[user_uuid].andand[perm.tail_uuid]
-            if not perm_mask
-              next
-            elsif perm_mask[:manage] and perm.name == 'can_manage'
-              evidence << {name: 'can_manage', user_uuid: user_uuid}
-            elsif perm_mask[:write] and ['can_manage', 'can_write'].index perm.name
-              evidence << {name: 'can_write', user_uuid: user_uuid}
-            elsif perm_mask[:read]
-              evidence << {name: 'can_read', user_uuid: user_uuid}
-            end
-          end
-        elsif user_aks.has_key?(perm.tail_uuid)
-          # A user has permission; the user exists; and either the
-          # user is active, or it's the special case of the anonymous
-          # user which is never "active" but is allowed to read
-          # content from public repositories.
-          evidence << {name: perm.name, user_uuid: perm.tail_uuid}
-        end
-      end
-      # Owner of the repository, and all admins, can do everything.
-      ([repo.owner_uuid] | admins).each do |user_uuid|
-        # Except: no permissions for inactive users, even if they own
-        # repositories.
-        next unless user_aks.has_key?(user_uuid)
-        evidence << {name: 'can_manage', user_uuid: user_uuid}
-      end
-      # Distill all the evidence about permissions on this repository
-      # into one hash per user, of the form {'can_xxx' => true, ...}.
-      # The hash is nil for a user who has no permissions at all on
-      # this particular repository.
-      evidence.each do |perm|
-        user_uuid = perm[:user_uuid]
-        user_perms = (@repo_info[repo.uuid][:user_permissions][user_uuid] ||= {})
-        user_perms[perm[:name]] = true
-      end
-    end
-    # Revisit each {'can_xxx' => true, ...} hash for some final
-    # cleanup to make life easier for the requestor.
-    #
-    # Add a 'gitolite_permissions' key alongside the 'can_xxx' keys,
-    # for the convenience of the gitolite config file generator.
-    #
-    # Add all lesser permissions when a greater permission is
-    # present. If the requestor only wants to know who can write, it
-    # only has to test for 'can_write' in the response.
-    @repo_info.values.each do |repo|
-      repo[:user_permissions].each do |user_uuid, user_perms|
-        if user_perms['can_manage']
-          user_perms['gitolite_permissions'] = 'RW+'
-          user_perms['can_write'] = true
-          user_perms['can_read'] = true
-        elsif user_perms['can_write']
-          user_perms['gitolite_permissions'] = 'RW+'
-          user_perms['can_read'] = true
-        elsif user_perms['can_read']
-          user_perms['gitolite_permissions'] = 'R'
-        end
-      end
-    end
-    # The response looks like
-    #   {"kind":"...",
-    #    "repositories":[r1,r2,r3,...],
-    #    "user_keys":usermap}
-    # where each of r1,r2,r3 looks like
-    #   {"uuid":"repo-uuid-1",
-    #    "name":"username/reponame",
-    #    "push_url":"...",
-    #    "user_permissions":{"user-uuid-a":{"can_read":true,"gitolite_permissions":"R"}}}
-    # and usermap looks like
-    #   {"user-uuid-a":[{"public_key":"ssh-rsa g...","authorized_key_uuid":"ak-uuid-g"},...],
-    #    "user-uuid-b":[{"public_key":"ssh-rsa h...","authorized_key_uuid":"ak-uuid-h"},...],...}
-    send_json(kind: 'arvados#RepositoryPermissionSnapshot',
-              repositories: @repo_info.values,
-              user_keys: user_aks)
-  end
-end
index 74aa4078cbea66a6da3138cfd4a46f9ece81e350..ca803fd3863c200affa9c4ad6674648730959545 100644 (file)
@@ -72,7 +72,6 @@ class Arvados::V1::SchemaController < ApplicationController
       workbenchUrl: Rails.configuration.Services.Workbench1.ExternalURL.to_s,
       workbench2Url: Rails.configuration.Services.Workbench2.ExternalURL.to_s,
       keepWebServiceUrl: Rails.configuration.Services.WebDAV.ExternalURL.to_s,
       workbenchUrl: Rails.configuration.Services.Workbench1.ExternalURL.to_s,
       workbench2Url: Rails.configuration.Services.Workbench2.ExternalURL.to_s,
       keepWebServiceUrl: Rails.configuration.Services.WebDAV.ExternalURL.to_s,
-      gitUrl: Rails.configuration.Services.GitHTTP.ExternalURL.to_s,
       parameters: {
         alt: {
           type: "string",
       parameters: {
         alt: {
           type: "string",
@@ -484,6 +483,7 @@ class Arvados::V1::SchemaController < ApplicationController
 
     Rails.configuration.API.DisabledAPIs.each do |method, _|
       ctrl, action = method.to_s.split('.', 2)
 
     Rails.configuration.API.DisabledAPIs.each do |method, _|
       ctrl, action = method.to_s.split('.', 2)
+      next if ctrl.in?(['job_tasks', 'jobs', 'keep_disks', 'nodes', 'pipeline_instances', 'pipeline_templates', 'repositories'])
       discovery[:resources][ctrl][:methods].delete(action.to_sym)
     end
     discovery
       discovery[:resources][ctrl][:methods].delete(action.to_sym)
     end
     discovery
diff --git a/services/api/app/controllers/arvados/v1/specimens_controller.rb b/services/api/app/controllers/arvados/v1/specimens_controller.rb
deleted file mode 100644 (file)
index b1e50a7..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::SpecimensController < ApplicationController
-end
diff --git a/services/api/app/controllers/arvados/v1/traits_controller.rb b/services/api/app/controllers/arvados/v1/traits_controller.rb
deleted file mode 100644 (file)
index 7aaed5c..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Arvados::V1::TraitsController < ApplicationController
-end
index 031dd2e4f92ba7c1764756027cef95db0afa5714..418295d26a6f25cfac66ad18cead7e31cbbc7b61 100644 (file)
@@ -123,8 +123,7 @@ class Arvados::V1::UsersController < ApplicationController
       full_repo_name = "#{@object.username}/#{params[:repo_name]}"
     end
 
       full_repo_name = "#{@object.username}/#{params[:repo_name]}"
     end
 
-    @response = @object.setup(repo_name: full_repo_name,
-                              vm_uuid: params[:vm_uuid],
+    @response = @object.setup(vm_uuid: params[:vm_uuid],
                               send_notification_email: params[:send_notification_email])
 
     send_json kind: "arvados#HashList", items: @response.as_api_response(nil)
                               send_notification_email: params[:send_notification_email])
 
     send_json kind: "arvados#HashList", items: @response.as_api_response(nil)
diff --git a/services/api/app/helpers/api_client_authorizations_helper.rb b/services/api/app/helpers/api_client_authorizations_helper.rb
deleted file mode 100644 (file)
index e1066ba..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module ApiClientAuthorizationsHelper
-end
diff --git a/services/api/app/helpers/api_clients_helper.rb b/services/api/app/helpers/api_clients_helper.rb
deleted file mode 100644 (file)
index 9604777..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module ApiClientsHelper
-end
diff --git a/services/api/app/helpers/authorized_keys_helper.rb b/services/api/app/helpers/authorized_keys_helper.rb
deleted file mode 100644 (file)
index 665fff7..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module AuthorizedKeysHelper
-end
diff --git a/services/api/app/helpers/collections_helper.rb b/services/api/app/helpers/collections_helper.rb
deleted file mode 100644 (file)
index ca44f47..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module CollectionsHelper
-end
diff --git a/services/api/app/helpers/commits_helper.rb b/services/api/app/helpers/commits_helper.rb
deleted file mode 100644 (file)
index fdb83a0..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module CommitsHelper
-  extend CurrentApiClient
-
-  class GitError < RequestError
-    def http_status
-      422
-    end
-  end
-
-  def self.git_check_ref_format(e)
-    if !e or e.empty? or e[0] == '-' or e[0] == '$'
-      # definitely not valid
-      false
-    else
-      `git check-ref-format --allow-onelevel #{e.shellescape}`
-      $?.success?
-    end
-  end
-
-  # Return an array of commits (each a 40-char sha1) satisfying the
-  # given criteria.
-  #
-  # Return [] if the revisions given in minimum/maximum are invalid or
-  # don't exist in the given repository.
-  #
-  # Raise ArgumentError if the given repository is invalid, does not
-  # exist, or cannot be read for any reason. (Any transient error that
-  # prevents commit ranges from resolving must raise rather than
-  # returning an empty array.)
-  #
-  # repository can be the name of a locally hosted repository or a git
-  # URL (see git-fetch(1)). Currently http, https, and git schemes are
-  # supported.
-  def self.find_commit_range repository, minimum, maximum, exclude
-    if minimum and minimum.empty?
-      minimum = nil
-    end
-
-    if minimum and !git_check_ref_format(minimum)
-      Rails.logger.warn "find_commit_range called with invalid minimum revision: '#{minimum}'"
-      return []
-    end
-
-    if maximum and !git_check_ref_format(maximum)
-      Rails.logger.warn "find_commit_range called with invalid maximum revision: '#{maximum}'"
-      return []
-    end
-
-    if !maximum
-      maximum = "HEAD"
-    end
-
-    gitdir, is_remote = git_dir_for repository
-    fetch_remote_repository gitdir, repository if is_remote
-    ENV['GIT_DIR'] = gitdir
-
-    commits = []
-
-    # Get the commit hash for the upper bound
-    max_hash = nil
-    git_max_hash_cmd = "git rev-list --max-count=1 #{maximum.shellescape} --"
-    IO.foreach("|#{git_max_hash_cmd}") do |line|
-      max_hash = line.strip
-    end
-
-    # If not found, nothing else to do
-    if !max_hash
-      Rails.logger.warn "no refs found looking for max_hash: `GIT_DIR=#{gitdir} #{git_max_hash_cmd}` returned no output"
-      return []
-    end
-
-    # If string is invalid, nothing else to do
-    if !git_check_ref_format(max_hash)
-      Rails.logger.warn "ref returned by `GIT_DIR=#{gitdir} #{git_max_hash_cmd}` was invalid for max_hash: #{max_hash}"
-      return []
-    end
-
-    resolved_exclude = nil
-    if exclude
-      resolved_exclude = []
-      exclude.each do |e|
-        if git_check_ref_format(e)
-          IO.foreach("|git rev-list --max-count=1 #{e.shellescape} --") do |line|
-            resolved_exclude.push(line.strip)
-          end
-        else
-          Rails.logger.warn "find_commit_range called with invalid exclude invalid characters: '#{exclude}'"
-          return []
-        end
-      end
-    end
-
-    if minimum
-      # Get the commit hash for the lower bound
-      min_hash = nil
-      git_min_hash_cmd = "git rev-list --max-count=1 #{minimum.shellescape} --"
-      IO.foreach("|#{git_min_hash_cmd}") do |line|
-        min_hash = line.strip
-      end
-
-      # If not found, nothing else to do
-      if !min_hash
-        Rails.logger.warn "no refs found looking for min_hash: `GIT_DIR=#{gitdir} #{git_min_hash_cmd}` returned no output"
-        return []
-      end
-
-      # If string is invalid, nothing else to do
-      if !git_check_ref_format(min_hash)
-        Rails.logger.warn "ref returned by `GIT_DIR=#{gitdir} #{git_min_hash_cmd}` was invalid for min_hash: #{min_hash}"
-        return []
-      end
-
-      # Now find all commits between them
-      IO.foreach("|git rev-list #{min_hash.shellescape}..#{max_hash.shellescape} --") do |line|
-        hash = line.strip
-        commits.push(hash) if !resolved_exclude or !resolved_exclude.include? hash
-      end
-
-      commits.push(min_hash) if !resolved_exclude or !resolved_exclude.include? min_hash
-    else
-      commits.push(max_hash) if !resolved_exclude or !resolved_exclude.include? max_hash
-    end
-
-    commits
-  end
-
-  # Given a repository (url, or name of hosted repo) and commit sha1,
-  # copy the commit into the internal git repo (if necessary), and tag
-  # it with the given tag (typically a job UUID).
-  #
-  # The repo can be a remote url, but in this case sha1 must already
-  # be present in our local cache for that repo: e.g., sha1 was just
-  # returned by find_commit_range.
-  def self.tag_in_internal_repository repo_name, sha1, tag
-    unless git_check_ref_format tag
-      raise ArgumentError.new "invalid tag #{tag}"
-    end
-    unless /^[0-9a-f]{40}$/ =~ sha1
-      raise ArgumentError.new "invalid sha1 #{sha1}"
-    end
-    src_gitdir, _ = git_dir_for repo_name
-    unless src_gitdir
-      raise ArgumentError.new "no local repository for #{repo_name}"
-    end
-    dst_gitdir = Rails.configuration.Containers.JobsAPI.GitInternalDir
-
-    begin
-      commit_in_dst = must_git(dst_gitdir, "log -n1 --format=%H #{sha1.shellescape}^{commit}").strip
-    rescue GitError
-      commit_in_dst = false
-    end
-
-    tag_cmd = "tag --force #{tag.shellescape} #{sha1.shellescape}^{commit}"
-    if commit_in_dst == sha1
-      must_git(dst_gitdir, tag_cmd)
-    else
-      # git-fetch is faster than pack-objects|unpack-objects, but
-      # git-fetch can't fetch by sha1. So we first try to fetch a
-      # branch that has the desired commit, and if that fails (there
-      # is no such branch, or the branch we choose changes under us in
-      # race), we fall back to pack|unpack.
-      begin
-        branches = must_git(src_gitdir,
-                            "branch --contains #{sha1.shellescape}")
-        m = branches.match(/^. (\w+)\n/)
-        if !m
-          raise GitError.new "commit is not on any branch"
-        end
-        branch = m[1]
-        must_git(dst_gitdir,
-                 "fetch file://#{src_gitdir.shellescape} #{branch.shellescape}")
-        # Even if all of the above steps succeeded, we might still not
-        # have the right commit due to a race, in which case tag_cmd
-        # will fail, and we'll need to fall back to pack|unpack. So
-        # don't be tempted to condense this tag_cmd and the one in the
-        # rescue block into a single attempt.
-        must_git(dst_gitdir, tag_cmd)
-      rescue GitError
-        must_pipe("echo #{sha1.shellescape}",
-                  "git --git-dir #{src_gitdir.shellescape} pack-objects -q --revs --stdout",
-                  "git --git-dir #{dst_gitdir.shellescape} unpack-objects -q")
-        must_git(dst_gitdir, tag_cmd)
-      end
-    end
-  end
-
-  protected
-
-  def self.remote_url? repo_name
-    /^(https?|git):\/\// =~ repo_name
-  end
-
-  # Return [local_git_dir, is_remote]. If is_remote, caller must use
-  # fetch_remote_repository to ensure content is up-to-date.
-  #
-  # Raises an exception if the latest content could not be fetched for
-  # any reason.
-  def self.git_dir_for repo_name
-    if remote_url? repo_name
-      return [cache_dir_for(repo_name), true]
-    end
-    repos = Repository.readable_by(current_user).where(name: repo_name)
-    if repos.count == 0
-      raise ArgumentError.new "Repository not found: '#{repo_name}'"
-    elsif repos.count > 1
-      Rails.logger.error "Multiple repositories with name=='#{repo_name}'!"
-      raise ArgumentError.new "Name conflict"
-    else
-      return [repos.first.server_path, false]
-    end
-  end
-
-  def self.cache_dir_for git_url
-    File.join(cache_dir_base, Digest::SHA1.hexdigest(git_url) + ".git").to_s
-  end
-
-  def self.cache_dir_base
-    Rails.root.join 'tmp', 'git-cache'
-  end
-
-  def self.fetch_remote_repository gitdir, git_url
-    # Caller decides which protocols are worth using. This is just a
-    # safety check to ensure we never use urls like "--flag" or wander
-    # into git's hardlink features by using bare "/path/foo" instead
-    # of "file:///path/foo".
-    unless /^[a-z]+:\/\// =~ git_url
-      raise ArgumentError.new "invalid git url #{git_url}"
-    end
-    begin
-      must_git gitdir, "branch"
-    rescue GitError => e
-      raise unless /Not a git repository/i =~ e.to_s
-      # OK, this just means we need to create a blank cache repository
-      # before fetching.
-      FileUtils.mkdir_p gitdir
-      must_git gitdir, "init"
-    end
-    must_git(gitdir,
-             "fetch --no-progress --tags --prune --force --update-head-ok #{git_url.shellescape} 'refs/heads/*:refs/heads/*'")
-  end
-
-  def self.must_git gitdir, *cmds
-    # Clear token in case a git helper tries to use it as a password.
-    orig_token = ENV['ARVADOS_API_TOKEN']
-    ENV['ARVADOS_API_TOKEN'] = ''
-    last_output = ''
-    begin
-      git = "git --git-dir #{gitdir.shellescape}"
-      cmds.each do |cmd|
-        last_output = must_pipe git+" "+cmd
-      end
-    ensure
-      ENV['ARVADOS_API_TOKEN'] = orig_token
-    end
-    return last_output
-  end
-
-  def self.must_pipe *cmds
-    cmd = cmds.join(" 2>&1 |") + " 2>&1"
-    out = IO.read("| </dev/null #{cmd}")
-    if not $?.success?
-      raise GitError.new "#{cmd}: #{$?}: #{out}"
-    end
-    return out
-  end
-end
diff --git a/services/api/app/helpers/groups_helper.rb b/services/api/app/helpers/groups_helper.rb
deleted file mode 100644 (file)
index 5464d96..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module GroupsHelper
-end
diff --git a/services/api/app/helpers/humans_helper.rb b/services/api/app/helpers/humans_helper.rb
deleted file mode 100644 (file)
index e9b4c96..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module HumansHelper
-end
diff --git a/services/api/app/helpers/job_tasks_helper.rb b/services/api/app/helpers/job_tasks_helper.rb
deleted file mode 100644 (file)
index ae78d75..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module JobTasksHelper
-end
diff --git a/services/api/app/helpers/jobs_helper.rb b/services/api/app/helpers/jobs_helper.rb
deleted file mode 100644 (file)
index ba3f715..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module JobsHelper
-end
diff --git a/services/api/app/helpers/keep_disks_helper.rb b/services/api/app/helpers/keep_disks_helper.rb
deleted file mode 100644 (file)
index 19386c9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module KeepDisksHelper
-end
diff --git a/services/api/app/helpers/links_helper.rb b/services/api/app/helpers/links_helper.rb
deleted file mode 100644 (file)
index 422a42d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module LinksHelper
-end
diff --git a/services/api/app/helpers/logs_helper.rb b/services/api/app/helpers/logs_helper.rb
deleted file mode 100644 (file)
index 9b767d3..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module LogsHelper
-end
diff --git a/services/api/app/helpers/nodes_helper.rb b/services/api/app/helpers/nodes_helper.rb
deleted file mode 100644 (file)
index cd1ecc9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module NodesHelper
-end
diff --git a/services/api/app/helpers/pipeline_instances_helper.rb b/services/api/app/helpers/pipeline_instances_helper.rb
deleted file mode 100644 (file)
index 79c2e09..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module PipelineInstancesHelper
-end
diff --git a/services/api/app/helpers/pipeline_templates_helper.rb b/services/api/app/helpers/pipeline_templates_helper.rb
deleted file mode 100644 (file)
index 135b525..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module PipelineTemplatesHelper
-end
diff --git a/services/api/app/helpers/repositories_helper.rb b/services/api/app/helpers/repositories_helper.rb
deleted file mode 100644 (file)
index 04ef0f1..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module RepositoriesHelper
-end
diff --git a/services/api/app/helpers/specimens_helper.rb b/services/api/app/helpers/specimens_helper.rb
deleted file mode 100644 (file)
index 5c7e98a..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module SpecimensHelper
-end
diff --git a/services/api/app/helpers/traits_helper.rb b/services/api/app/helpers/traits_helper.rb
deleted file mode 100644 (file)
index 35ae11d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module TraitsHelper
-end
diff --git a/services/api/app/helpers/virtual_machines_helper.rb b/services/api/app/helpers/virtual_machines_helper.rb
deleted file mode 100644 (file)
index 7d2b08f..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-module VirtualMachinesHelper
-end
index af553997e572e36d549449264f8ca2b5fbcb3661..83112786764d64377f3e6b7983fb1e3310ca59db 100644 (file)
@@ -294,6 +294,10 @@ class ApiClientAuthorization < ArvadosModel
         raise "remote cluster #{upstream_cluster_id} returned invalid token uuid #{token_uuid.inspect}"
       end
     rescue HTTPClient::BadResponseError => e
         raise "remote cluster #{upstream_cluster_id} returned invalid token uuid #{token_uuid.inspect}"
       end
     rescue HTTPClient::BadResponseError => e
+      if e.res.status_code >= 400 && e.res.status_code < 500
+        # Remote cluster does not accept this token.
+        return nil
+      end
       # CurrentApiToken#call and ApplicationController#render_error will
       # propagate the status code from the #http_status method, so define
       # that here.
       # CurrentApiToken#call and ApplicationController#render_error will
       # propagate the status code from the #http_status method, so define
       # that here.
@@ -399,8 +403,17 @@ class ApiClientAuthorization < ArvadosModel
         end
       rescue ActiveRecord::RecordNotUnique
         Rails.logger.debug("cached remote token #{token_uuid} already exists, retrying...")
         end
       rescue ActiveRecord::RecordNotUnique
         Rails.logger.debug("cached remote token #{token_uuid} already exists, retrying...")
-        # Some other request won the race: retry just once before erroring out
-        if (retries += 1) <= 1
+        # Another request won the race (trying to find_or_create the
+        # same token UUID) ...and/or... there is an expired entry with
+        # the same secret but a different UUID (e.g., the token is an
+        # OIDC access token and [a] our database has an expired cached
+        # row that was not used above, and [b] the remote cluster had
+        # deleted its expired cached row so it assigned a new UUID).
+        #
+        # Delete any conflicting row if any. Retry twice (in case we
+        # hit both of those situations at once), then give up.
+        if (retries += 1) <= 2
+          ApiClientAuthorization.where('api_token=? and uuid<>?', stored_secret, token_uuid).delete_all
           retry
         else
           Rails.logger.warn("cannot find or create cached remote token #{token_uuid}")
           retry
         else
           Rails.logger.warn("cannot find or create cached remote token #{token_uuid}")
index ee338b81ffedad646854cd4b55998937551ffa59..08dad2314e2c5383b204d60d070c39882ce657a8 100644 (file)
@@ -30,6 +30,7 @@ class Container < ArvadosModel
   serialize :runtime_constraints, Hash
   serialize :command, Array
   serialize :scheduling_parameters, Hash
   serialize :runtime_constraints, Hash
   serialize :command, Array
   serialize :scheduling_parameters, Hash
+  serialize :output_glob, Array
 
   after_find :fill_container_defaults_after_find
   before_validation :fill_field_defaults, :if => :new_record?
 
   after_find :fill_container_defaults_after_find
   before_validation :fill_field_defaults, :if => :new_record?
@@ -73,6 +74,7 @@ class Container < ArvadosModel
     t.add :mounts
     t.add :output
     t.add :output_path
     t.add :mounts
     t.add :output
     t.add :output_path
+    t.add :output_glob
     t.add :priority
     t.add :progress
     t.add :runtime_constraints
     t.add :priority
     t.add :progress
     t.add :runtime_constraints
@@ -164,6 +166,7 @@ class Container < ArvadosModel
         cwd: req.cwd,
         environment: req.environment,
         output_path: req.output_path,
         cwd: req.cwd,
         environment: req.environment,
         output_path: req.output_path,
+        output_glob: req.output_glob,
         container_image: resolve_container_image(req.container_image),
         mounts: resolve_mounts(req.mounts),
         runtime_constraints: resolve_runtime_constraints(req.runtime_constraints),
         container_image: resolve_container_image(req.container_image),
         mounts: resolve_mounts(req.mounts),
         runtime_constraints: resolve_runtime_constraints(req.runtime_constraints),
@@ -263,6 +266,9 @@ class Container < ArvadosModel
     candidates = candidates.where('output_path = ?', attrs[:output_path])
     log_reuse_info(candidates) { "after filtering on output_path #{attrs[:output_path].inspect}" }
 
     candidates = candidates.where('output_path = ?', attrs[:output_path])
     log_reuse_info(candidates) { "after filtering on output_path #{attrs[:output_path].inspect}" }
 
+    candidates = candidates.where_serialized(:output_glob, attrs[:output_glob], md5: true)
+    log_reuse_info(candidates) { "after filtering on output_glob #{attrs[:output_glob].inspect}" }
+
     image = resolve_container_image(attrs[:container_image])
     candidates = candidates.where('container_image = ?', image)
     log_reuse_info(candidates) { "after filtering on container_image #{image.inspect} (resolved from #{attrs[:container_image].inspect})" }
     image = resolve_container_image(attrs[:container_image])
     candidates = candidates.where('container_image = ?', image)
     log_reuse_info(candidates) { "after filtering on container_image #{image.inspect} (resolved from #{attrs[:container_image].inspect})" }
@@ -482,6 +488,7 @@ class Container < ArvadosModel
     self.environment ||= {}
     self.runtime_constraints ||= {}
     self.mounts ||= {}
     self.environment ||= {}
     self.runtime_constraints ||= {}
     self.mounts ||= {}
+    self.output_glob ||= []
     self.cwd ||= "."
     self.priority ||= 0
     self.scheduling_parameters ||= {}
     self.cwd ||= "."
     self.priority ||= 0
     self.scheduling_parameters ||= {}
@@ -531,11 +538,11 @@ class Container < ArvadosModel
 
     if self.new_record?
       permitted.push(:owner_uuid, :command, :container_image, :cwd,
 
     if self.new_record?
       permitted.push(:owner_uuid, :command, :container_image, :cwd,
-                     :environment, :mounts, :output_path, :priority,
-                     :runtime_constraints, :scheduling_parameters,
-                     :secret_mounts, :runtime_token,
-                     :runtime_user_uuid, :runtime_auth_scopes,
-                     :output_storage_classes)
+                     :environment, :mounts, :output_path, :output_glob,
+                     :priority, :runtime_constraints,
+                     :scheduling_parameters, :secret_mounts,
+                     :runtime_token, :runtime_user_uuid,
+                     :runtime_auth_scopes, :output_storage_classes)
     end
 
     case self.state
     end
 
     case self.state
@@ -798,6 +805,7 @@ class Container < ArvadosModel
               cwd: self.cwd,
               environment: self.environment,
               output_path: self.output_path,
               cwd: self.cwd,
               environment: self.environment,
               output_path: self.output_path,
+              output_glob: self.output_glob,
               container_image: self.container_image,
               mounts: self.mounts,
               runtime_constraints: self.runtime_constraints,
               container_image: self.container_image,
               mounts: self.mounts,
               runtime_constraints: self.runtime_constraints,
index f5789f31f684f89b2ac553de09cc199dfba005aa..9b3d427594c38c4e39cdc07934cfa928e4d8c5a6 100644 (file)
@@ -34,6 +34,7 @@ class ContainerRequest < ArvadosModel
   serialize :runtime_constraints, Hash
   serialize :command, Array
   serialize :scheduling_parameters, Hash
   serialize :runtime_constraints, Hash
   serialize :command, Array
   serialize :scheduling_parameters, Hash
+  serialize :output_glob, Array
 
   after_find :fill_container_defaults_after_find
   after_initialize { @state_was_when_initialized = self.state_was } # see finalize_if_needed
 
   after_find :fill_container_defaults_after_find
   after_initialize { @state_was_when_initialized = self.state_was } # see finalize_if_needed
@@ -73,6 +74,7 @@ class ContainerRequest < ArvadosModel
     t.add :name
     t.add :output_name
     t.add :output_path
     t.add :name
     t.add :output_name
     t.add :output_path
+    t.add :output_glob
     t.add :output_uuid
     t.add :output_ttl
     t.add :priority
     t.add :output_uuid
     t.add :output_ttl
     t.add :priority
@@ -104,7 +106,7 @@ class ContainerRequest < ArvadosModel
   AttrsPermittedAlways = [:owner_uuid, :state, :name, :description, :properties]
   AttrsPermittedBeforeCommit = [:command, :container_count_max,
   :container_image, :cwd, :environment, :filters, :mounts,
   AttrsPermittedAlways = [:owner_uuid, :state, :name, :description, :properties]
   AttrsPermittedBeforeCommit = [:command, :container_count_max,
   :container_image, :cwd, :environment, :filters, :mounts,
-  :output_path, :priority, :runtime_token,
+  :output_path, :output_glob, :priority, :runtime_token,
   :runtime_constraints, :state, :container_uuid, :use_existing,
   :scheduling_parameters, :secret_mounts, :output_name, :output_ttl,
   :output_storage_classes, :output_properties]
   :runtime_constraints, :state, :container_uuid, :use_existing,
   :scheduling_parameters, :secret_mounts, :output_name, :output_ttl,
   :output_storage_classes, :output_properties]
@@ -307,7 +309,7 @@ class ContainerRequest < ArvadosModel
   end
 
   def self.full_text_searchable_columns
   end
 
   def self.full_text_searchable_columns
-    super - ["mounts", "secret_mounts", "secret_mounts_md5", "runtime_token", "output_storage_classes"]
+    super - ["mounts", "secret_mounts", "secret_mounts_md5", "runtime_token", "output_storage_classes", "output_glob"]
   end
 
   def set_priority_zero
   end
 
   def set_priority_zero
@@ -326,6 +328,7 @@ class ContainerRequest < ArvadosModel
     self.container_count_max ||= Rails.configuration.Containers.MaxRetryAttempts
     self.scheduling_parameters ||= {}
     self.output_ttl ||= 0
     self.container_count_max ||= Rails.configuration.Containers.MaxRetryAttempts
     self.scheduling_parameters ||= {}
     self.output_ttl ||= 0
+    self.output_glob ||= []
     self.priority ||= 0
   end
 
     self.priority ||= 0
   end
 
@@ -442,6 +445,11 @@ class ContainerRequest < ArvadosModel
         errors.add(:environment, "must be an map of String to String but has entry #{k.class} to #{v.class}")
       end
     end
         errors.add(:environment, "must be an map of String to String but has entry #{k.class} to #{v.class}")
       end
     end
+    output_glob.each do |g|
+      if !g.is_a? String
+        errors.add(:output_glob, "must be an array of strings but has entry #{g.class}")
+      end
+    end
     [:mounts, :secret_mounts].each do |m|
       self[m].each do |k, v|
         if !k.is_a?(String) || !v.is_a?(Hash)
     [:mounts, :secret_mounts].each do |m|
       self[m].each do |k, v|
         if !k.is_a?(String) || !v.is_a?(Hash)
diff --git a/services/api/app/models/human.rb b/services/api/app/models/human.rb
deleted file mode 100644 (file)
index 6897282..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Human < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :properties, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :properties
-  end
-end
diff --git a/services/api/app/models/job.rb b/services/api/app/models/job.rb
deleted file mode 100644 (file)
index 029a313..0000000
+++ /dev/null
@@ -1,564 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-#
-#
-# Legacy jobs API aka crunch v1
-#
-# This is superceded by containers / container_requests (aka crunch v2)
-#
-# Arvados installations since the end of 2017 should have never
-# used jobs, and are unaffected by this change.
-#
-# 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 the crunch-dispatch.rb and
-# various other code specific to the jobs API.
-#
-# If you need to resurrect any of this code, here is the last commit
-# on master before the branch removing jobs API support:
-#
-# Wed Aug 7 14:49:38 2019 -0400 07d92519438a592d531f2c7558cd51788da262ca
-
-require 'log_reuse_info'
-require 'safe_json'
-
-class Job < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  extend CurrentApiClient
-  extend LogReuseInfo
-  serialize :components, Hash
-  serialize :script_parameters, Hash
-  serialize :runtime_constraints, Hash
-  serialize :tasks_summary, Hash
-  before_create :ensure_unique_submit_id
-  before_validation :set_priority
-  before_validation :update_state_from_old_state_attrs
-  before_validation :update_script_parameters_digest
-  validate :ensure_script_version_is_commit
-  validate :find_docker_image_locator
-  validate :find_arvados_sdk_version
-  validate :validate_status
-  validate :validate_state_change
-  validate :ensure_no_collection_uuids_in_script_params
-  before_save :tag_version_in_internal_repository
-  before_save :update_timestamps_when_state_changes
-  before_create :create_disabled
-  before_update :update_disabled
-
-  has_many(:nodes, foreign_key: 'job_uuid', primary_key: 'uuid')
-
-  class SubmitIdReused < RequestError
-  end
-
-  api_accessible :user, extend: :common do |t|
-    t.add :submit_id
-    t.add :priority
-    t.add :script
-    t.add :script_parameters
-    t.add :script_version
-    t.add :cancelled_at
-    t.add :cancelled_by_client_uuid
-    t.add :cancelled_by_user_uuid
-    t.add :started_at
-    t.add :finished_at
-    t.add :output
-    t.add :success
-    t.add :running
-    t.add :state
-    t.add :is_locked_by_uuid
-    t.add :log
-    t.add :runtime_constraints
-    t.add :tasks_summary
-    t.add :nondeterministic
-    t.add :repository
-    t.add :supplied_script_version
-    t.add :arvados_sdk_version
-    t.add :docker_image_locator
-    t.add :queue_position
-    t.add :node_uuids
-    t.add :description
-    t.add :components
-  end
-
-  # Supported states for a job
-  States = [
-            (Queued = 'Queued'),
-            (Running = 'Running'),
-            (Cancelled = 'Cancelled'),
-            (Failed = 'Failed'),
-            (Complete = 'Complete'),
-           ]
-
-  after_initialize do
-    @need_crunch_dispatch_trigger = false
-  end
-
-  def self.limit_index_columns_read
-    ["components"]
-  end
-
-  def self.protected_attributes
-    [:arvados_sdk_version, :docker_image_locator]
-  end
-
-  def assert_finished
-    update(finished_at: finished_at || db_current_time,
-                      success: success.nil? ? false : success,
-                      running: false)
-  end
-
-  def node_uuids
-    nodes.map(&:uuid)
-  end
-
-  def self.queue
-    self.where('state = ?', Queued).order('priority desc, created_at')
-  end
-
-  def queue_position
-    # We used to report this accurately, but the implementation made queue
-    # API requests O(n**2) for the size of the queue.  See #8800.
-    # We've soft-disabled it because it's not clear we even want this
-    # functionality: now that we have Node Manager with support for multiple
-    # node sizes, "queue position" tells you very little about when a job will
-    # run.
-    state == Queued ? 0 : nil
-  end
-
-  def self.running
-    self.where('running = ?', true).
-      order('priority desc, created_at')
-  end
-
-  def lock locked_by_uuid
-    with_lock do
-      unless self.state == Queued and self.is_locked_by_uuid.nil?
-        raise AlreadyLockedError
-      end
-      self.state = Running
-      self.is_locked_by_uuid = locked_by_uuid
-      self.save!
-    end
-  end
-
-  def update_script_parameters_digest
-    self.script_parameters_digest = self.class.sorted_hash_digest(script_parameters)
-  end
-
-  def self.searchable_columns operator
-    super - ["script_parameters_digest"]
-  end
-
-  def self.full_text_searchable_columns
-    super - ["script_parameters_digest"]
-  end
-
-  def self.load_job_specific_filters attrs, orig_filters, read_users
-    # Convert Job-specific @filters entries into general SQL filters.
-    script_info = {"repository" => nil, "script" => nil}
-    git_filters = Hash.new do |hash, key|
-      hash[key] = {"max_version" => "HEAD", "exclude_versions" => []}
-    end
-    filters = []
-    orig_filters.each do |attr, operator, operand|
-      if (script_info.has_key? attr) and (operator == "=")
-        if script_info[attr].nil?
-          script_info[attr] = operand
-        elsif script_info[attr] != operand
-          raise ArgumentError.new("incompatible #{attr} filters")
-        end
-      end
-      case operator
-      when "in git"
-        git_filters[attr]["min_version"] = operand
-      when "not in git"
-        git_filters[attr]["exclude_versions"] += Array.wrap(operand)
-      when "in docker", "not in docker"
-        image_hashes = Array.wrap(operand).flat_map do |search_term|
-          image_search, image_tag = search_term.split(':', 2)
-          Collection.
-            find_all_for_docker_image(image_search, image_tag, read_users, filter_compatible_format: false).
-            map(&:portable_data_hash)
-        end
-        filters << [attr, operator.sub(/ docker$/, ""), image_hashes]
-      else
-        filters << [attr, operator, operand]
-      end
-    end
-
-    # Build a real script_version filter from any "not? in git" filters.
-    git_filters.each_pair do |attr, filter|
-      case attr
-      when "script_version"
-        script_info.each_pair do |key, value|
-          if value.nil?
-            raise ArgumentError.new("script_version filter needs #{key} filter")
-          end
-        end
-        filter["repository"] = script_info["repository"]
-        if attrs[:script_version]
-          filter["max_version"] = attrs[:script_version]
-        else
-          # Using HEAD, set earlier by the hash default, is fine.
-        end
-      when "arvados_sdk_version"
-        filter["repository"] = "arvados"
-      else
-        raise ArgumentError.new("unknown attribute for git filter: #{attr}")
-      end
-      revisions = CommitsHelper::find_commit_range(filter["repository"],
-                                           filter["min_version"],
-                                           filter["max_version"],
-                                           filter["exclude_versions"])
-      if revisions.empty?
-        raise ArgumentError.
-          new("error searching #{filter['repository']} from " +
-              "'#{filter['min_version']}' to '#{filter['max_version']}', " +
-              "excluding #{filter['exclude_versions']}")
-      end
-      filters.append([attr, "in", revisions])
-    end
-
-    filters
-  end
-
-  def self.default_git_filters(attr_name, repo_name, refspec)
-    # Add a filter to @filters for `attr_name` = the latest commit available
-    # in `repo_name` at `refspec`.  No filter is added if refspec can't be
-    # resolved.
-    commits = CommitsHelper::find_commit_range(repo_name, nil, refspec, nil)
-    if commit_hash = commits.first
-      [[attr_name, "=", commit_hash]]
-    else
-      []
-    end
-  end
-
-  def cancel(cascade: false, need_transaction: true)
-    raise "No longer supported"
-  end
-
-  protected
-
-  def self.sorted_hash_digest h
-    Digest::MD5.hexdigest(Oj.dump(deep_sort_hash(h)))
-  end
-
-  def foreign_key_attributes
-    super + %w(output log)
-  end
-
-  def skip_uuid_read_permission_check
-    super + %w(cancelled_by_client_uuid)
-  end
-
-  def skip_uuid_existence_check
-    super + %w(output log)
-  end
-
-  def set_priority
-    if self.priority.nil?
-      self.priority = 0
-    end
-    true
-  end
-
-  def ensure_script_version_is_commit
-    if state == Running
-      # Apparently client has already decided to go for it. This is
-      # needed to run a local job using a local working directory
-      # instead of a commit-ish.
-      return true
-    end
-    if new_record? or repository_changed? or script_version_changed?
-      sha1 = CommitsHelper::find_commit_range(repository,
-                                      nil, script_version, nil).first
-      if not sha1
-        errors.add :script_version, "#{script_version} does not resolve to a commit"
-        return false
-      end
-      if supplied_script_version.nil? or supplied_script_version.empty?
-        self.supplied_script_version = script_version
-      end
-      self.script_version = sha1
-    end
-    true
-  end
-
-  def tag_version_in_internal_repository
-    if state == Running
-      # No point now. See ensure_script_version_is_commit.
-      true
-    elsif errors.any?
-      # Won't be saved, and script_version might not even be valid.
-      true
-    elsif new_record? or repository_changed? or script_version_changed?
-      uuid_was = uuid
-      begin
-        assign_uuid
-        CommitsHelper::tag_in_internal_repository repository, script_version, uuid
-      rescue
-        self.uuid = uuid_was
-        raise
-      end
-    end
-  end
-
-  def ensure_unique_submit_id
-    if !submit_id.nil?
-      if Job.where('submit_id=?',self.submit_id).first
-        raise SubmitIdReused.new
-      end
-    end
-    true
-  end
-
-  def resolve_runtime_constraint(key, attr_sym)
-    if ((runtime_constraints.is_a? Hash) and
-        (search = runtime_constraints[key]))
-      ok, result = yield search
-    else
-      ok, result = true, nil
-    end
-    if ok
-      send("#{attr_sym}=".to_sym, result)
-    else
-      errors.add(attr_sym, result)
-    end
-    ok
-  end
-
-  def find_arvados_sdk_version
-    resolve_runtime_constraint("arvados_sdk_version",
-                               :arvados_sdk_version) do |git_search|
-      commits = CommitsHelper::find_commit_range("arvados",
-                                         nil, git_search, nil)
-      if commits.empty?
-        [false, "#{git_search} does not resolve to a commit"]
-      elsif not runtime_constraints["docker_image"]
-        [false, "cannot be specified without a Docker image constraint"]
-      else
-        [true, commits.first]
-      end
-    end
-  end
-
-  def find_docker_image_locator
-    if runtime_constraints.is_a? Hash and Rails.configuration.Containers.JobsAPI.DefaultDockerImage != ""
-      runtime_constraints['docker_image'] ||=
-        Rails.configuration.Containers.JobsAPI.DefaultDockerImage
-    end
-
-    resolve_runtime_constraint("docker_image",
-                               :docker_image_locator) do |image_search|
-      image_tag = runtime_constraints['docker_image_tag']
-      if coll = Collection.for_latest_docker_image(image_search, image_tag)
-        [true, coll.portable_data_hash]
-      else
-        [false, "not found for #{image_search}"]
-      end
-    end
-  end
-
-  def permission_to_update
-    if is_locked_by_uuid_was and !(current_user and
-                                   (current_user.uuid == is_locked_by_uuid_was or
-                                    current_user.uuid == system_user.uuid))
-      if script_changed? or
-          script_parameters_changed? or
-          script_version_changed? or
-          (!cancelled_at_was.nil? and
-           (cancelled_by_client_uuid_changed? or
-            cancelled_by_user_uuid_changed? or
-            cancelled_at_changed?)) or
-          started_at_changed? or
-          finished_at_changed? or
-          running_changed? or
-          success_changed? or
-          output_changed? or
-          log_changed? or
-          tasks_summary_changed? or
-          (state_changed? && state != Cancelled) or
-          components_changed?
-        logger.warn "User #{current_user.uuid if current_user} tried to change protected job attributes on locked #{self.class.to_s} #{uuid_was}"
-        return false
-      end
-    end
-    if !is_locked_by_uuid_changed?
-      super
-    else
-      if !current_user
-        logger.warn "Anonymous user tried to change lock on #{self.class.to_s} #{uuid_was}"
-        false
-      elsif is_locked_by_uuid_was and is_locked_by_uuid_was != current_user.uuid
-        logger.warn "User #{current_user.uuid} tried to steal lock on #{self.class.to_s} #{uuid_was} from #{is_locked_by_uuid_was}"
-        false
-      elsif !is_locked_by_uuid.nil? and is_locked_by_uuid != current_user.uuid
-        logger.warn "User #{current_user.uuid} tried to lock #{self.class.to_s} #{uuid_was} with uuid #{is_locked_by_uuid}"
-        false
-      else
-        super
-      end
-    end
-  end
-
-  def update_modified_by_fields
-    if self.cancelled_at_changed?
-      # Ensure cancelled_at cannot be set to arbitrary non-now times,
-      # or changed once it is set.
-      if self.cancelled_at and not self.cancelled_at_was
-        self.cancelled_at = db_current_time
-        self.cancelled_by_user_uuid = current_user.uuid
-        self.cancelled_by_client_uuid = current_api_client.andand.uuid
-        @need_crunch_dispatch_trigger = true
-      else
-        self.cancelled_at = self.cancelled_at_was
-        self.cancelled_by_user_uuid = self.cancelled_by_user_uuid_was
-        self.cancelled_by_client_uuid = self.cancelled_by_client_uuid_was
-      end
-    end
-    super
-  end
-
-  def update_timestamps_when_state_changes
-    return if not (state_changed? or new_record?)
-
-    case state
-    when Running
-      self.started_at ||= db_current_time
-    when Failed, Complete
-      self.finished_at ||= db_current_time
-    when Cancelled
-      self.cancelled_at ||= db_current_time
-    end
-
-    # TODO: Remove the following case block when old "success" and
-    # "running" attrs go away. Until then, this ensures we still
-    # expose correct success/running flags to older clients, even if
-    # some new clients are writing only the new state attribute.
-    case state
-    when Queued
-      self.running = false
-      self.success = nil
-    when Running
-      self.running = true
-      self.success = nil
-    when Cancelled, Failed
-      self.running = false
-      self.success = false
-    when Complete
-      self.running = false
-      self.success = true
-    end
-    self.running ||= false # Default to false instead of nil.
-
-    @need_crunch_dispatch_trigger = true
-
-    true
-  end
-
-  def update_state_from_old_state_attrs
-    # If a client has touched the legacy state attrs, update the
-    # "state" attr to agree with the updated values of the legacy
-    # attrs.
-    #
-    # TODO: Remove this method when old "success" and "running" attrs
-    # go away.
-    if cancelled_at_changed? or
-        success_changed? or
-        running_changed? or
-        state.nil?
-      if cancelled_at
-        self.state = Cancelled
-      elsif success == false
-        self.state = Failed
-      elsif success == true
-        self.state = Complete
-      elsif running == true
-        self.state = Running
-      else
-        self.state = Queued
-      end
-    end
-    true
-  end
-
-  def validate_status
-    if self.state.in?(States)
-      true
-    else
-      errors.add :state, "#{state.inspect} must be one of: #{States.inspect}"
-      false
-    end
-  end
-
-  def validate_state_change
-    ok = true
-    if self.state_changed?
-      ok = case self.state_was
-           when nil
-             # state isn't set yet
-             true
-           when Queued
-             # Permit going from queued to any state
-             true
-           when Running
-             # From running, may only transition to a finished state
-             [Complete, Failed, Cancelled].include? self.state
-           when Complete, Failed, Cancelled
-             # Once in a finished state, don't permit any more state changes
-             false
-           else
-             # Any other state transition is also invalid
-             false
-           end
-      if not ok
-        errors.add :state, "invalid change from #{self.state_was} to #{self.state}"
-      end
-    end
-    ok
-  end
-
-  def ensure_no_collection_uuids_in_script_params
-    # Fail validation if any script_parameters field includes a string containing a
-    # collection uuid pattern.
-    if self.script_parameters_changed?
-      if recursive_hash_search(self.script_parameters, Collection.uuid_regex)
-        self.errors.add :script_parameters, "must use portable_data_hash instead of collection uuid"
-        return false
-      end
-    end
-    true
-  end
-
-  # recursive_hash_search searches recursively through hashes and
-  # arrays in 'thing' for string fields matching regular expression
-  # 'pattern'.  Returns true if pattern is found, false otherwise.
-  def recursive_hash_search thing, pattern
-    if thing.is_a? Hash
-      thing.each do |k, v|
-        return true if recursive_hash_search v, pattern
-      end
-    elsif thing.is_a? Array
-      thing.each do |k|
-        return true if recursive_hash_search k, pattern
-      end
-    elsif thing.is_a? String
-      return true if thing.match pattern
-    end
-    false
-  end
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/job_task.rb b/services/api/app/models/job_task.rb
deleted file mode 100644 (file)
index b181e76..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class JobTask < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :parameters, Hash
-  before_create :set_default_qsequence
-  after_update :delete_created_job_tasks_if_failed
-  before_create :create_disabled
-  before_update :update_disabled
-
-  api_accessible :user, extend: :common do |t|
-    t.add :job_uuid
-    t.add :created_by_job_task_uuid
-    t.add :sequence
-    t.add :qsequence
-    t.add :parameters
-    t.add :output
-    t.add :progress
-    t.add :success
-    t.add :started_at
-    t.add :finished_at
-  end
-
-  protected
-
-  def delete_created_job_tasks_if_failed
-    if self.success == false and self.success != self.success_was
-      JobTask.delete_all ['created_by_job_task_uuid = ?', self.uuid]
-    end
-  end
-
-  def set_default_qsequence
-    self.qsequence ||= self.class.connection.
-      select_value("SELECT nextval('job_tasks_qsequence_seq')")
-  end
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/keep_disk.rb b/services/api/app/models/keep_disk.rb
deleted file mode 100644 (file)
index 589936f..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class KeepDisk < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  before_validation :ensure_ping_secret
-
-  api_accessible :user, extend: :common do |t|
-    t.add :node_uuid
-    t.add :filesystem_uuid
-    t.add :bytes_total
-    t.add :bytes_free
-    t.add :is_readable
-    t.add :is_writable
-    t.add :last_read_at
-    t.add :last_write_at
-    t.add :last_ping_at
-    t.add :service_host
-    t.add :service_port
-    t.add :service_ssl_flag
-    t.add :keep_service_uuid
-  end
-  api_accessible :superuser, :extend => :user do |t|
-    t.add :ping_secret
-  end
-
-  def foreign_key_attributes
-    super.reject { |a| a == "filesystem_uuid" }
-  end
-
-  def ping(o)
-    raise "must have :service_host and :ping_secret" unless o[:service_host] and o[:ping_secret]
-
-    if o[:ping_secret] != self.ping_secret
-      logger.info "Ping: secret mismatch: received \"#{o[:ping_secret]}\" != \"#{self.ping_secret}\""
-      return nil
-    end
-
-    @bypass_arvados_authorization = true
-    self.update!(o.select { |k,v|
-                             [:bytes_total,
-                              :bytes_free,
-                              :is_readable,
-                              :is_writable,
-                              :last_read_at,
-                              :last_write_at
-                             ].collect(&:to_s).index k
-                           }.merge(last_ping_at: db_current_time))
-  end
-
-  def service_host
-    KeepService.find_by_uuid(self.keep_service_uuid).andand.service_host
-  end
-
-  def service_port
-    KeepService.find_by_uuid(self.keep_service_uuid).andand.service_port
-  end
-
-  def service_ssl_flag
-    KeepService.find_by_uuid(self.keep_service_uuid).andand.service_ssl_flag
-  end
-
-  protected
-
-  def ensure_ping_secret
-    self.ping_secret ||= rand(2**256).to_s(36)
-  end
-
-  def permission_to_update
-    @bypass_arvados_authorization or super
-  end
-
-  def permission_to_create
-    current_user and current_user.is_admin
-  end
-end
diff --git a/services/api/app/models/node.rb b/services/api/app/models/node.rb
deleted file mode 100644 (file)
index f384ba5..0000000
+++ /dev/null
@@ -1,295 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'tempfile'
-
-class Node < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-
-  # Posgresql JSONB columns should NOT be declared as serialized, Rails 5
-  # already know how to properly treat them.
-  attribute :properties, :jsonbHash, default: {}
-  attribute :info, :jsonbHash, default: {}
-
-  before_validation :ensure_ping_secret
-  after_update :dns_server_update
-
-  # Only a controller can figure out whether or not the current API tokens
-  # have access to the associated Job.  They're expected to set
-  # job_readable=true if the Job UUID can be included in the API response.
-  belongs_to :job,
-             foreign_key: 'job_uuid',
-             primary_key: 'uuid',
-             optional: true
-  attr_accessor :job_readable
-
-  UNUSED_NODE_IP = '127.40.4.0'
-  MAX_VMS = 3
-
-  api_accessible :user, :extend => :common do |t|
-    t.add :hostname
-    t.add :domain
-    t.add :ip_address
-    t.add :last_ping_at
-    t.add :slot_number
-    t.add :status
-    t.add :api_job_uuid, as: :job_uuid
-    t.add :crunch_worker_state
-    t.add :properties
-  end
-  api_accessible :superuser, :extend => :user do |t|
-    t.add :first_ping_at
-    t.add :info
-    t.add lambda { |x| Rails.configuration.Containers.SLURM.Managed.ComputeNodeNameservers.keys }, :as => :nameservers
-  end
-
-  after_initialize do
-    @bypass_arvados_authorization = false
-  end
-
-  def domain
-    super || Rails.configuration.Containers.SLURM.Managed.ComputeNodeDomain
-  end
-
-  def api_job_uuid
-    job_readable ? job_uuid : nil
-  end
-
-  def crunch_worker_state
-    return 'down' if slot_number.nil?
-    case self.info.andand['slurm_state']
-    when 'alloc', 'comp', 'mix', 'drng'
-      'busy'
-    when 'idle'
-      'idle'
-    else
-      'down'
-    end
-  end
-
-  def status
-    if !self.last_ping_at
-      if db_current_time - self.created_at > 5.minutes
-        'startup-fail'
-      else
-        'pending'
-      end
-    elsif db_current_time - self.last_ping_at > 1.hours
-      'missing'
-    else
-      'running'
-    end
-  end
-
-  def ping(o)
-    raise "must have :ip and :ping_secret" unless o[:ip] and o[:ping_secret]
-
-    if o[:ping_secret] != self.info['ping_secret']
-      logger.info "Ping: secret mismatch: received \"#{o[:ping_secret]}\" != \"#{self.info['ping_secret']}\""
-      raise ArvadosModel::UnauthorizedError.new("Incorrect ping_secret")
-    end
-
-    current_time = db_current_time
-    self.last_ping_at = current_time
-
-    @bypass_arvados_authorization = true
-
-    # Record IP address
-    if self.ip_address.nil?
-      logger.info "#{self.uuid} ip_address= #{o[:ip]}"
-      self.ip_address = o[:ip]
-      self.first_ping_at = current_time
-    end
-
-    # Record instance ID if not already known
-    if o[:ec2_instance_id]
-      if !self.info['ec2_instance_id']
-        self.info['ec2_instance_id'] = o[:ec2_instance_id]
-      elsif self.info['ec2_instance_id'] != o[:ec2_instance_id]
-        logger.debug "Multiple nodes have credentials for #{self.uuid}"
-        raise "#{self.uuid} is already running at #{self.info['ec2_instance_id']} so rejecting ping from #{o[:ec2_instance_id]}"
-      end
-    end
-
-    assign_slot
-
-    # Record other basic stats
-    ['total_cpu_cores', 'total_ram_mb', 'total_scratch_mb'].each do |key|
-      if value = (o[key] or o[key.to_sym])
-        self.properties[key] = value.to_i
-      else
-        self.properties.delete(key)
-      end
-    end
-
-    save!
-  end
-
-  def assign_slot
-    return if self.slot_number.andand > 0
-    while true
-      self.slot_number = self.class.available_slot_number
-      if self.slot_number.nil?
-        raise "No available node slots"
-      end
-      begin
-        save!
-        return assign_hostname
-      rescue ActiveRecord::RecordNotUnique
-        # try again
-      end
-    end
-  end
-
-  protected
-
-  def assign_hostname
-    if self.hostname.nil? and Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname
-      self.hostname = self.class.hostname_for_slot(self.slot_number)
-    end
-  end
-
-  def self.available_slot_number
-    # Join the sequence 1..max with the nodes table. Return the first
-    # (i.e., smallest) value that doesn't match the slot_number of any
-    # existing node.
-    connection.exec_query('SELECT n FROM generate_series(1, $1) AS slot(n)
-                          LEFT JOIN nodes ON n=slot_number
-                          WHERE slot_number IS NULL
-                          LIMIT 1',
-                          # query label:
-                          'Node.available_slot_number',
-                          # bind vars:
-                          [MAX_VMS],
-                         ).rows.first.andand.first
-  end
-
-  def ensure_ping_secret
-    self.info['ping_secret'] ||= rand(2**256).to_s(36)
-  end
-
-  def dns_server_update
-    if saved_change_to_ip_address? && ip_address
-      Node.where('id != ? and ip_address = ?',
-                 id, ip_address).each do |stale_node|
-        # One or more(!) stale node records have the same IP address
-        # as the new node. Clear the ip_address field on the stale
-        # nodes. Otherwise, we (via SLURM) might inadvertently connect
-        # to the new node using the old node's hostname.
-        stale_node.update!(ip_address: nil)
-      end
-    end
-    if hostname_before_last_save && saved_change_to_hostname?
-      self.class.dns_server_update(hostname_before_last_save, UNUSED_NODE_IP)
-    end
-    if hostname && (saved_change_to_hostname? || saved_change_to_ip_address?)
-      self.class.dns_server_update(hostname, ip_address || UNUSED_NODE_IP)
-    end
-  end
-
-  def self.dns_server_update hostname, ip_address
-    ok = true
-
-    ptr_domain = ip_address.
-      split('.').reverse.join('.').concat('.in-addr.arpa')
-
-    template_vars = {
-      hostname: hostname,
-      uuid_prefix: Rails.configuration.ClusterID,
-      ip_address: ip_address,
-      ptr_domain: ptr_domain,
-    }
-
-    if (!Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir.to_s.empty? and
-        !Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate.to_s.empty?)
-      tmpfile = nil
-      begin
-        begin
-          template = IO.read(Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate)
-        rescue IOError, SystemCallError => e
-          logger.error "Reading #{Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate}: #{e.message}"
-          raise
-        end
-
-        hostfile = File.join Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir, "#{hostname}.conf"
-        Tempfile.open(["#{hostname}-", ".conf.tmp"],
-                                 Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir) do |f|
-          tmpfile = f.path
-          f.puts template % template_vars
-        end
-        File.rename tmpfile, hostfile
-      rescue IOError, SystemCallError => e
-        logger.error "Writing #{hostfile}: #{e.message}"
-        ok = false
-      ensure
-        if tmpfile and File.file? tmpfile
-          # Cleanup remaining temporary file.
-          File.unlink tmpfile
-        end
-      end
-    end
-
-    if !Rails.configuration.Containers.SLURM.Managed.DNSServerUpdateCommand.empty?
-      cmd = Rails.configuration.Containers.SLURM.Managed.DNSServerUpdateCommand % template_vars
-      if not system cmd
-        logger.error "dns_server_update_command #{cmd.inspect} failed: #{$?}"
-        ok = false
-      end
-    end
-
-    if (!Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir.to_s.empty? and
-        !Rails.configuration.Containers.SLURM.Managed.DNSServerReloadCommand.to_s.empty?)
-      restartfile = File.join(Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir, 'restart.txt')
-      begin
-        File.open(restartfile, 'w') do |f|
-          # Typically, this is used to trigger a dns server restart
-          f.puts Rails.configuration.Containers.SLURM.Managed.DNSServerReloadCommand
-        end
-      rescue IOError, SystemCallError => e
-        logger.error "Unable to write #{restartfile}: #{e.message}"
-        ok = false
-      end
-    end
-
-    ok
-  end
-
-  def self.hostname_for_slot(slot_number)
-    config = Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname
-
-    return nil if !config
-
-    sprintf(config, {:slot_number => slot_number})
-  end
-
-  # At startup, make sure all DNS entries exist.  Otherwise, slurmctld
-  # will refuse to start.
-  if (!Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir.to_s.empty? and
-      !Rails.configuration.Containers.SLURM.Managed.DNSServerConfTemplate.to_s.empty? and
-      !Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname.empty?)
-
-    (0..MAX_VMS-1).each do |slot_number|
-      hostname = hostname_for_slot(slot_number)
-      hostfile = File.join Rails.configuration.Containers.SLURM.Managed.DNSServerConfDir, "#{hostname}.conf"
-      if !File.exist? hostfile
-        n = Node.where(:slot_number => slot_number).first
-        if n.nil? or n.ip_address.nil?
-          dns_server_update(hostname, UNUSED_NODE_IP)
-        else
-          dns_server_update(hostname, n.ip_address)
-        end
-      end
-    end
-  end
-
-  def permission_to_update
-    @bypass_arvados_authorization or super
-  end
-
-  def permission_to_create
-    current_user and current_user.is_admin
-  end
-end
diff --git a/services/api/app/models/pipeline_instance.rb b/services/api/app/models/pipeline_instance.rb
deleted file mode 100644 (file)
index 0b0af8b..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class PipelineInstance < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :components, Hash
-  serialize :properties, Hash
-  serialize :components_summary, Hash
-  belongs_to :pipeline_template,
-             foreign_key: 'pipeline_template_uuid',
-             primary_key: 'uuid',
-             optional: true
-
-  before_validation :bootstrap_components
-  before_validation :update_state
-  before_validation :verify_status
-  before_validation :update_timestamps_when_state_changes
-  before_create :set_state_before_save
-  before_save :set_state_before_save
-  before_create :create_disabled
-  before_update :update_disabled
-
-  api_accessible :user, extend: :common do |t|
-    t.add :pipeline_template_uuid
-    t.add :name
-    t.add :components
-    t.add :properties
-    t.add :state
-    t.add :components_summary
-    t.add :description
-    t.add :started_at
-    t.add :finished_at
-  end
-
-  # Supported states for a pipeline instance
-  States =
-    [
-     (New = 'New'),
-     (Ready = 'Ready'),
-     (RunningOnServer = 'RunningOnServer'),
-     (RunningOnClient = 'RunningOnClient'),
-     (Paused = 'Paused'),
-     (Failed = 'Failed'),
-     (Complete = 'Complete'),
-    ]
-
-  def self.limit_index_columns_read
-    ["components"]
-  end
-
-  # if all components have input, the pipeline is Ready
-  def components_look_ready?
-    if !self.components || self.components.empty?
-      return false
-    end
-
-    all_components_have_input = true
-    self.components.each do |name, component|
-      component['script_parameters'].andand.each do |parametername, parameter|
-        parameter = { 'value' => parameter } unless parameter.is_a? Hash
-        if parameter['value'].nil? and parameter['required']
-          if parameter['output_of']
-            next
-          end
-          all_components_have_input = false
-          break
-        end
-      end
-    end
-    return all_components_have_input
-  end
-
-  def progress_table
-    begin
-      # v0 pipeline format
-      nrow = -1
-      components['steps'].collect do |step|
-        nrow += 1
-        row = [nrow, step['name']]
-        if step['complete'] and step['complete'] != 0
-          if step['output_data_locator']
-            row << 1.0
-          else
-            row << 0.0
-          end
-        else
-          row << 0.0
-          if step['failed']
-            self.state = Failed
-          end
-        end
-        row << (step['warehousejob']['id'] rescue nil)
-        row << (step['warehousejob']['revision'] rescue nil)
-        row << step['output_data_locator']
-        row << (Time.parse(step['warehousejob']['finishtime']) rescue nil)
-        row
-      end
-    rescue
-      []
-    end
-  end
-
-  def progress_ratio
-    t = progress_table
-    return 0 if t.size < 1
-    t.collect { |r| r[2] }.inject(0.0) { |sum,a| sum += a } / t.size
-  end
-
-  def self.queue
-    self.where("state = 'RunningOnServer'")
-  end
-
-  def cancel(cascade: false, need_transaction: true)
-    raise "No longer supported"
-  end
-
-  protected
-  def bootstrap_components
-    if pipeline_template and (!components or components.empty?)
-      self.components = pipeline_template.components.deep_dup
-    end
-  end
-
-  def update_state
-    if components and progress_ratio == 1.0
-      self.state = Complete
-    end
-  end
-
-  def verify_status
-    changed_attributes = self.changed
-
-    if new_record? or 'components'.in? changed_attributes
-      self.state ||= New
-      if (self.state == New) and self.components_look_ready?
-        self.state = Ready
-      end
-    end
-
-    if !self.state.in?(States)
-      errors.add :state, "'#{state.inspect} must be one of: [#{States.join ', '}]"
-      throw(:abort)
-    end
-  end
-
-  def set_state_before_save
-    if self.components_look_ready? && (!self.state || self.state == New)
-      self.state = Ready
-    end
-  end
-
-  def update_timestamps_when_state_changes
-    return if not (state_changed? or new_record?)
-
-    case state
-    when RunningOnServer, RunningOnClient
-      self.started_at ||= db_current_time
-    when Failed, Complete
-      current_time = db_current_time
-      self.started_at ||= current_time
-      self.finished_at ||= current_time
-    end
-  end
-
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/pipeline_template.rb b/services/api/app/models/pipeline_template.rb
deleted file mode 100644 (file)
index 7c69469..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class PipelineTemplate < ArvadosModel
-  before_create :create_disabled
-  before_update :update_disabled
-
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :components, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :name
-    t.add :components
-    t.add :description
-  end
-
-  def self.limit_index_columns_read
-    ["components"]
-  end
-
-  def create_disabled
-    raise "Disabled"
-  end
-
-  def update_disabled
-    raise "Disabled"
-  end
-end
diff --git a/services/api/app/models/repository.rb b/services/api/app/models/repository.rb
deleted file mode 100644 (file)
index 46f2de6..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Repository < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-
-  # Order is important here.  We must validate the owner before we can
-  # validate the name.
-  validate :valid_owner
-  validate :name_format, :if => Proc.new { |r| r.errors[:owner_uuid].empty? }
-  validates(:name, uniqueness: true, allow_nil: false)
-
-  api_accessible :user, extend: :common do |t|
-    t.add :name
-    t.add :fetch_url
-    t.add :push_url
-    t.add :clone_urls
-  end
-
-  def self.attributes_required_columns
-    super.merge("clone_urls" => ["name"],
-                "fetch_url" => ["name"],
-                "push_url" => ["name"])
-  end
-
-  # Deprecated. Use clone_urls instead.
-  def push_url
-    ssh_clone_url
-  end
-
-  # Deprecated. Use clone_urls instead.
-  def fetch_url
-    ssh_clone_url
-  end
-
-  def clone_urls
-    [ssh_clone_url, https_clone_url].compact
-  end
-
-  def server_path
-    # Find where the repository is stored on the API server's filesystem,
-    # and return that path, or nil if not found.
-    # This method is only for the API server's internal use, and should not
-    # be exposed through the public API.  Following our current gitolite
-    # setup, it searches for repositories stored by UUID, then name; and it
-    # prefers bare repositories over checkouts.
-    [["%s.git"], ["%s", ".git"]].each do |repo_base, *join_args|
-      [:uuid, :name].each do |path_attr|
-        git_dir = File.join(Rails.configuration.Git.Repositories,
-                            repo_base % send(path_attr), *join_args)
-        return git_dir if File.exist?(git_dir)
-      end
-    end
-    nil
-  end
-
-  protected
-
-  def permission_to_update
-    if not super
-      false
-    elsif current_user.is_admin
-      true
-    elsif name_changed?
-      current_user.uuid == owner_uuid
-    else
-      true
-    end
-  end
-
-  def owner
-    User.find_by_uuid(owner_uuid)
-  end
-
-  def valid_owner
-    if owner.nil? or (owner.username.nil? and (owner.uuid != system_user_uuid))
-      errors.add(:owner_uuid, "must refer to a user with a username")
-      false
-    end
-  end
-
-  def name_format
-    if owner.uuid == system_user_uuid
-      prefix_match = ""
-      errmsg_start = "must be"
-    else
-      prefix_match = Regexp.escape(owner.username + "/")
-      errmsg_start = "must be the owner's username, then '/', then"
-    end
-    if not (/^#{prefix_match}[A-Za-z][A-Za-z0-9]*$/.match(name))
-      errors.add(:name,
-                 "#{errmsg_start} a letter followed by alphanumerics, expected pattern '#{prefix_match}[A-Za-z][A-Za-z0-9]*' but was '#{name}'")
-      false
-    end
-  end
-
-  def ssh_clone_url
-    _clone_url Rails.configuration.Services.GitSSH.andand.ExternalURL, 'ssh://git@git.%s.arvadosapi.com'
-  end
-
-  def https_clone_url
-    _clone_url Rails.configuration.Services.GitHTTP.andand.ExternalURL, 'https://git.%s.arvadosapi.com/'
-  end
-
-  def _clone_url config_var, default_base_fmt
-    if not config_var
-      return ""
-    end
-    prefix = new_record? ? Rails.configuration.ClusterID : uuid[0,5]
-    if prefix == Rails.configuration.ClusterID and config_var != URI("")
-      base = config_var
-    else
-      base = URI(default_base_fmt % prefix)
-    end
-    if base.path == ""
-      base.path = "/"
-    end
-    if base.scheme == "ssh"
-      '%s@%s:%s.git' % [base.user, base.host, name]
-    else
-      '%s%s.git' % [base, name]
-    end
-  end
-end
diff --git a/services/api/app/models/specimen.rb b/services/api/app/models/specimen.rb
deleted file mode 100644 (file)
index 32d5ed5..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Specimen < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :properties, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :material
-    t.add :properties
-  end
-
-  def properties
-    @properties ||= Hash.new
-    super
-  end
-end
diff --git a/services/api/app/models/trait.rb b/services/api/app/models/trait.rb
deleted file mode 100644 (file)
index 2d3556b..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-class Trait < ArvadosModel
-  include HasUuid
-  include KindAndEtag
-  include CommonApiTemplate
-  serialize :properties, Hash
-
-  api_accessible :user, extend: :common do |t|
-    t.add :name
-    t.add :properties
-  end
-end
index 5a95fb0b88e41efc593495bc50efeb5bd13b51b9..c104ac6fda4f432714a8ff33d8bebc0b651c55f3 100644 (file)
@@ -25,9 +25,6 @@ class User < ArvadosModel
   before_update :prevent_privilege_escalation
   before_update :prevent_inactive_admin
   before_update :prevent_nonadmin_system_root
   before_update :prevent_privilege_escalation
   before_update :prevent_inactive_admin
   before_update :prevent_nonadmin_system_root
-  before_update :verify_repositories_empty, :if => Proc.new {
-    username.nil? and username_changed?
-  }
   after_update :setup_on_activate
 
   before_create :check_auto_admin
   after_update :setup_on_activate
 
   before_create :check_auto_admin
@@ -49,16 +46,10 @@ class User < ArvadosModel
   before_update :before_ownership_change
   after_update :after_ownership_change
   after_update :send_profile_created_notification
   before_update :before_ownership_change
   after_update :after_ownership_change
   after_update :send_profile_created_notification
-  after_update :sync_repository_names, :if => Proc.new {
-    (uuid != system_user_uuid) and
-    saved_change_to_username? and
-    (not username_before_last_save.nil?)
-  }
   before_destroy :clear_permissions
   after_destroy :remove_self_from_permissions
 
   has_many :authorized_keys, foreign_key: 'authorized_user_uuid', primary_key: 'uuid'
   before_destroy :clear_permissions
   after_destroy :remove_self_from_permissions
 
   has_many :authorized_keys, foreign_key: 'authorized_user_uuid', primary_key: 'uuid'
-  has_many :repositories, foreign_key: 'owner_uuid', primary_key: 'uuid'
 
   default_scope { where('redirect_to_user_uuid is null') }
 
 
   default_scope { where('redirect_to_user_uuid is null') }
 
@@ -261,7 +252,7 @@ SELECT target_uuid, perm_level
   end
 
   # create links
   end
 
   # create links
-  def setup(repo_name: nil, vm_uuid: nil, send_notification_email: nil)
+  def setup(vm_uuid: nil, send_notification_email: nil)
     newly_invited = Link.where(tail_uuid: self.uuid,
                               head_uuid: all_users_group_uuid,
                               link_class: 'permission').empty?
     newly_invited = Link.where(tail_uuid: self.uuid,
                               head_uuid: all_users_group_uuid,
                               link_class: 'permission').empty?
@@ -271,12 +262,6 @@ SELECT target_uuid, perm_level
     # direction which makes this user visible to other users.
     group_perms = add_to_all_users_group
 
     # direction which makes this user visible to other users.
     group_perms = add_to_all_users_group
 
-    # Add git repo
-    repo_perm = if (!repo_name.nil? || Rails.configuration.Users.AutoSetupNewUsersWithRepository) and !username.nil?
-                  repo_name ||= "#{username}/#{username}"
-                  create_user_repo_link repo_name
-                end
-
     # Add virtual machine
     if vm_uuid.nil? and !Rails.configuration.Users.AutoSetupNewUsersWithVmUUID.empty?
       vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID
     # Add virtual machine
     if vm_uuid.nil? and !Rails.configuration.Users.AutoSetupNewUsersWithVmUUID.empty?
       vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID
@@ -301,10 +286,10 @@ SELECT target_uuid, perm_level
 
     forget_cached_group_perms
 
 
     forget_cached_group_perms
 
-    return [repo_perm, vm_login_perm, *group_perms, self].compact
+    return [vm_login_perm, *group_perms, self].compact
   end
 
   end
 
-  # delete user signatures, login, repo, and vm perms, and mark as inactive
+  # delete user signatures, login, and vm perms, and mark as inactive
   def unsetup
     if self.uuid == system_user_uuid
       raise "System root user cannot be deactivated"
   def unsetup
     if self.uuid == system_user_uuid
       raise "System root user cannot be deactivated"
@@ -483,30 +468,13 @@ SELECT target_uuid, perm_level
         klass.where(column => uuid).update_all(column => new_user.uuid)
       end
 
         klass.where(column => uuid).update_all(column => new_user.uuid)
       end
 
-      # Need to update repository names to new username
-      if username
-        old_repo_name_re = /^#{Regexp.escape(username)}\//
-        Repository.where(:owner_uuid => uuid).each do |repo|
-          repo.owner_uuid = new_user.uuid
-          repo_name_sub = "#{new_user.username}/"
-          name = repo.name.sub(old_repo_name_re, repo_name_sub)
-          while (conflict = Repository.where(:name => name).first) != nil
-            repo_name_sub += "migrated"
-            name = repo.name.sub(old_repo_name_re, repo_name_sub)
-          end
-          repo.name = name
-          repo.save!
-        end
-      end
-
       # References to the merged user's "home project" are updated to
       # point to new_owner_uuid.
       ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass|
         next if [ApiClientAuthorization,
                  AuthorizedKey,
                  Link,
       # References to the merged user's "home project" are updated to
       # point to new_owner_uuid.
       ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass|
         next if [ApiClientAuthorization,
                  AuthorizedKey,
                  Link,
-                 Log,
-                 Repository].include?(klass)
+                 Log].include?(klass)
         next if !klass.columns.collect(&:name).include?('owner_uuid')
         klass.where(owner_uuid: uuid).update_all(owner_uuid: new_owner_uuid)
       end
         next if !klass.columns.collect(&:name).include?('owner_uuid')
         klass.where(owner_uuid: uuid).update_all(owner_uuid: new_owner_uuid)
       end
@@ -889,24 +857,8 @@ SELECT target_uuid, perm_level
     merged
   end
 
     merged
   end
 
-  def create_user_repo_link(repo_name)
-    # repo_name is optional
-    if not repo_name
-      logger.warn ("Repository name not given for #{self.uuid}.")
-      return
-    end
-
-    repo = Repository.where(owner_uuid: uuid, name: repo_name).first_or_create!
-    logger.info { "repo uuid: " + repo[:uuid] }
-    repo_perm = Link.where(tail_uuid: uuid, head_uuid: repo.uuid,
-                           link_class: "permission",
-                           name: "can_manage").first_or_create!
-    logger.info { "repo permission: " + repo_perm[:uuid] }
-    return repo_perm
-  end
-
   # create login permission for the given vm_uuid, if it does not already exist
   # create login permission for the given vm_uuid, if it does not already exist
-  def create_vm_login_permission_link(vm_uuid, repo_name)
+  def create_vm_login_permission_link(vm_uuid, username)
     # vm uuid is optional
     return if vm_uuid == ""
 
     # vm uuid is optional
     return if vm_uuid == ""
 
@@ -924,11 +876,11 @@ SELECT target_uuid, perm_level
 
     login_perm = Link.
       where(login_attrs).
 
     login_perm = Link.
       where(login_attrs).
-      select { |link| link.properties["username"] == repo_name }.
+      select { |link| link.properties["username"] == username }.
       first
 
     login_perm ||= Link.
       first
 
     login_perm ||= Link.
-      create(login_attrs.merge(properties: {"username" => repo_name}))
+      create(login_attrs.merge(properties: {"username" => username}))
 
     logger.info { "login permission: " + login_perm[:uuid] }
     login_perm
 
     logger.info { "login permission: " + login_perm[:uuid] }
     login_perm
@@ -1001,22 +953,6 @@ SELECT target_uuid, perm_level
     end
   end
 
     end
   end
 
-  def verify_repositories_empty
-    unless repositories.first.nil?
-      errors.add(:username, "can't be unset when the user owns repositories")
-      throw(:abort)
-    end
-  end
-
-  def sync_repository_names
-    old_name_re = /^#{Regexp.escape(username_before_last_save)}\//
-    name_sub = "#{username}/"
-    repositories.find_each do |repo|
-      repo.name = repo.name.sub(old_name_re, name_sub)
-      repo.save!
-    end
-  end
-
   def identity_url_nil_if_empty
     if identity_url == ""
       self.identity_url = nil
   def identity_url_nil_if_empty
     if identity_url == ""
       self.identity_url = nil
index f8b9ff8ecdd650ceb0a556565fd600001087168d..f514fee641b4e5617b825655a669f701208ab2f5 100644 (file)
@@ -84,7 +84,6 @@ arvcfg = ConfigLoader.new
 arvcfg.declare_config "ClusterID", NonemptyString, :uuid_prefix
 arvcfg.declare_config "ManagementToken", String, :ManagementToken
 arvcfg.declare_config "SystemRootToken", String
 arvcfg.declare_config "ClusterID", NonemptyString, :uuid_prefix
 arvcfg.declare_config "ManagementToken", String, :ManagementToken
 arvcfg.declare_config "SystemRootToken", String
-arvcfg.declare_config "Git.Repositories", String, :git_repositories_dir
 arvcfg.declare_config "API.DisabledAPIs", Hash, :disable_api_methods, ->(cfg, k, v) { arrayToHash cfg, "API.DisabledAPIs", v }
 arvcfg.declare_config "API.MaxRequestSize", Integer, :max_request_size
 arvcfg.declare_config "API.MaxIndexDatabaseRead", Integer, :max_index_database_read
 arvcfg.declare_config "API.DisabledAPIs", Hash, :disable_api_methods, ->(cfg, k, v) { arrayToHash cfg, "API.DisabledAPIs", v }
 arvcfg.declare_config "API.MaxRequestSize", Integer, :max_request_size
 arvcfg.declare_config "API.MaxIndexDatabaseRead", Integer, :max_index_database_read
@@ -94,7 +93,6 @@ arvcfg.declare_config "API.RequestTimeout", ActiveSupport::Duration
 arvcfg.declare_config "API.AsyncPermissionsUpdateInterval", ActiveSupport::Duration, :async_permissions_update_interval
 arvcfg.declare_config "Users.AutoSetupNewUsers", Boolean, :auto_setup_new_users
 arvcfg.declare_config "Users.AutoSetupNewUsersWithVmUUID", String, :auto_setup_new_users_with_vm_uuid
 arvcfg.declare_config "API.AsyncPermissionsUpdateInterval", ActiveSupport::Duration, :async_permissions_update_interval
 arvcfg.declare_config "Users.AutoSetupNewUsers", Boolean, :auto_setup_new_users
 arvcfg.declare_config "Users.AutoSetupNewUsersWithVmUUID", String, :auto_setup_new_users_with_vm_uuid
-arvcfg.declare_config "Users.AutoSetupNewUsersWithRepository", Boolean, :auto_setup_new_users_with_repository
 arvcfg.declare_config "Users.AutoSetupUsernameBlacklist", Hash, :auto_setup_name_blacklist, ->(cfg, k, v) { arrayToHash cfg, "Users.AutoSetupUsernameBlacklist", v }
 arvcfg.declare_config "Users.NewUsersAreActive", Boolean, :new_users_are_active
 arvcfg.declare_config "Users.AutoAdminUserWithEmail", String, :auto_admin_user
 arvcfg.declare_config "Users.AutoSetupUsernameBlacklist", Hash, :auto_setup_name_blacklist, ->(cfg, k, v) { arrayToHash cfg, "Users.AutoSetupUsernameBlacklist", v }
 arvcfg.declare_config "Users.NewUsersAreActive", Boolean, :new_users_are_active
 arvcfg.declare_config "Users.AutoAdminUserWithEmail", String, :auto_admin_user
@@ -142,23 +140,12 @@ arvcfg.declare_config "Containers.Logging.LogPartialLineThrottlePeriod", ActiveS
 arvcfg.declare_config "Containers.Logging.LogUpdatePeriod", ActiveSupport::Duration, :crunch_log_update_period
 arvcfg.declare_config "Containers.Logging.LogUpdateSize", Integer, :crunch_log_update_size
 arvcfg.declare_config "Containers.Logging.MaxAge", ActiveSupport::Duration, :clean_container_log_rows_after
 arvcfg.declare_config "Containers.Logging.LogUpdatePeriod", ActiveSupport::Duration, :crunch_log_update_period
 arvcfg.declare_config "Containers.Logging.LogUpdateSize", Integer, :crunch_log_update_size
 arvcfg.declare_config "Containers.Logging.MaxAge", ActiveSupport::Duration, :clean_container_log_rows_after
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerConfDir", Pathname, :dns_server_conf_dir
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerConfTemplate", Pathname, :dns_server_conf_template
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerReloadCommand", String, :dns_server_reload_command
-arvcfg.declare_config "Containers.SLURM.Managed.DNSServerUpdateCommand", String, :dns_server_update_command
-arvcfg.declare_config "Containers.SLURM.Managed.ComputeNodeDomain", String, :compute_node_domain
-arvcfg.declare_config "Containers.SLURM.Managed.ComputeNodeNameservers", Hash, :compute_node_nameservers, ->(cfg, k, v) { arrayToHash cfg, "Containers.SLURM.Managed.ComputeNodeNameservers", v }
-arvcfg.declare_config "Containers.SLURM.Managed.AssignNodeHostname", String, :assign_node_hostname
-arvcfg.declare_config "Containers.JobsAPI.Enable", String, :enable_legacy_jobs_api, ->(cfg, k, v) { ConfigLoader.set_cfg cfg, "Containers.JobsAPI.Enable", v.to_s }
-arvcfg.declare_config "Containers.JobsAPI.GitInternalDir", String, :git_internal_dir
 arvcfg.declare_config "Mail.MailchimpAPIKey", String, :mailchimp_api_key
 arvcfg.declare_config "Mail.MailchimpListID", String, :mailchimp_list_id
 arvcfg.declare_config "Services.Controller.ExternalURL", URI
 arvcfg.declare_config "Services.Workbench1.ExternalURL", URI, :workbench_address
 arvcfg.declare_config "Services.Websocket.ExternalURL", URI, :websocket_address
 arvcfg.declare_config "Services.WebDAV.ExternalURL", URI, :keep_web_service_url
 arvcfg.declare_config "Mail.MailchimpAPIKey", String, :mailchimp_api_key
 arvcfg.declare_config "Mail.MailchimpListID", String, :mailchimp_list_id
 arvcfg.declare_config "Services.Controller.ExternalURL", URI
 arvcfg.declare_config "Services.Workbench1.ExternalURL", URI, :workbench_address
 arvcfg.declare_config "Services.Websocket.ExternalURL", URI, :websocket_address
 arvcfg.declare_config "Services.WebDAV.ExternalURL", URI, :keep_web_service_url
-arvcfg.declare_config "Services.GitHTTP.ExternalURL", URI, :git_repo_https_base
-arvcfg.declare_config "Services.GitSSH.ExternalURL", URI, :git_repo_ssh_base, ->(cfg, k, v) { ConfigLoader.set_cfg cfg, "Services.GitSSH.ExternalURL", "ssh://#{v}" }
 arvcfg.declare_config "RemoteClusters", Hash, :remote_hosts, ->(cfg, k, v) {
   h = if cfg["RemoteClusters"] then
         cfg["RemoteClusters"].deep_dup
 arvcfg.declare_config "RemoteClusters", Hash, :remote_hosts, ->(cfg, k, v) {
   h = if cfg["RemoteClusters"] then
         cfg["RemoteClusters"].deep_dup
index b87e86f664de7e3230331e8233744ac589e4a169..df3c057b5758f0deb76420ba7a2f6908edd199d9 100644 (file)
@@ -34,8 +34,6 @@ Rails.application.routes.draw do
         post 'trash', on: :member
         post 'untrash', on: :member
       end
         post 'trash', on: :member
         post 'untrash', on: :member
       end
-      resources :humans
-      resources :job_tasks
       resources :containers do
         get 'auth', on: :member
         post 'lock', on: :member
       resources :containers do
         get 'auth', on: :member
         post 'lock', on: :member
@@ -47,33 +45,12 @@ Rails.application.routes.draw do
       resources :container_requests do
         get 'container_status', on: :member
       end
       resources :container_requests do
         get 'container_status', on: :member
       end
-      resources :jobs do
-        get 'queue', on: :collection
-        get 'queue_size', on: :collection
-        post 'cancel', on: :member
-        post 'lock', on: :member
-      end
-      resources :keep_disks do
-        post 'ping', on: :collection
-      end
       resources :keep_services do
         get 'accessible', on: :collection
       end
       resources :links
       resources :logs
       resources :keep_services do
         get 'accessible', on: :collection
       end
       resources :links
       resources :logs
-      resources :nodes do
-        post 'ping', on: :member
-      end
-      resources :pipeline_instances do
-        post 'cancel', on: :member
-      end
-      resources :pipeline_templates
       resources :workflows
       resources :workflows
-      resources :repositories do
-        get 'get_all_permissions', on: :collection
-      end
-      resources :specimens
-      resources :traits
       resources :user_agreements do
         get 'signatures', on: :collection
         post 'sign', on: :collection
       resources :user_agreements do
         get 'signatures', on: :collection
         post 'sign', on: :collection
diff --git a/services/api/db/migrate/20240329173437_add_output_glob_to_containers.rb b/services/api/db/migrate/20240329173437_add_output_glob_to_containers.rb
new file mode 100644 (file)
index 0000000..481cad1
--- /dev/null
@@ -0,0 +1,10 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+class AddOutputGlobToContainers < ActiveRecord::Migration[7.0]
+  def change
+    add_column :containers, :output_glob, :text, default: '[]'
+    add_column :container_requests, :output_glob, :text, default: '[]'
+  end
+end
diff --git a/services/api/db/migrate/20240402162733_add_output_glob_index_to_containers.rb b/services/api/db/migrate/20240402162733_add_output_glob_index_to_containers.rb
new file mode 100644 (file)
index 0000000..00050f8
--- /dev/null
@@ -0,0 +1,17 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require './db/migrate/20161213172944_full_text_search_indexes'
+
+class AddOutputGlobIndexToContainers < ActiveRecord::Migration[4.2]
+  def up
+    ActiveRecord::Base.connection.execute 'DROP INDEX index_containers_on_reuse_columns'
+    ActiveRecord::Base.connection.execute 'CREATE INDEX index_containers_on_reuse_columns on containers (md5(command), cwd, md5(environment), output_path, md5(output_glob), container_image, md5(mounts), secret_mounts_md5, md5(runtime_constraints))'
+    FullTextSearchIndexes.new.replace_index('container_requests')
+  end
+  def down
+    ActiveRecord::Base.connection.execute 'DROP INDEX index_containers_on_reuse_columns'
+    ActiveRecord::Base.connection.execute 'CREATE INDEX index_containers_on_reuse_columns on containers (md5(command), cwd, md5(environment), output_path, container_image, md5(mounts), secret_mounts_md5, md5(runtime_constraints))'
+  end
+end
index c0d4263d97aa6bdde258688d091b5cf69fdd47af..08862c60c28c60a0bc9f3132161724cd876a3c3a 100644 (file)
@@ -568,7 +568,8 @@ CREATE TABLE public.container_requests (
     runtime_token text,
     output_storage_classes jsonb DEFAULT '["default"]'::jsonb,
     output_properties jsonb DEFAULT '{}'::jsonb,
     runtime_token text,
     output_storage_classes jsonb DEFAULT '["default"]'::jsonb,
     output_properties jsonb DEFAULT '{}'::jsonb,
-    cumulative_cost double precision DEFAULT 0.0 NOT NULL
+    cumulative_cost double precision DEFAULT 0.0 NOT NULL,
+    output_glob text DEFAULT '[]'::text
 );
 
 
 );
 
 
@@ -634,7 +635,8 @@ CREATE TABLE public.containers (
     output_storage_classes jsonb DEFAULT '["default"]'::jsonb,
     output_properties jsonb DEFAULT '{}'::jsonb,
     cost double precision DEFAULT 0.0 NOT NULL,
     output_storage_classes jsonb DEFAULT '["default"]'::jsonb,
     output_properties jsonb DEFAULT '{}'::jsonb,
     cost double precision DEFAULT 0.0 NOT NULL,
-    subrequests_cost double precision DEFAULT 0.0 NOT NULL
+    subrequests_cost double precision DEFAULT 0.0 NOT NULL,
+    output_glob text DEFAULT '[]'::text
 );
 
 
 );
 
 
@@ -1855,6 +1857,13 @@ CREATE INDEX collections_search_index ON public.collections USING btree (owner_u
 CREATE INDEX collections_trgm_text_search_idx ON public.collections USING gin (((((((((((((((((((COALESCE(owner_uuid, ''::character varying))::text || ' '::text) || (COALESCE(modified_by_client_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(modified_by_user_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(portable_data_hash, ''::character varying))::text) || ' '::text) || (COALESCE(uuid, ''::character varying))::text) || ' '::text) || (COALESCE(name, ''::character varying))::text) || ' '::text) || (COALESCE(description, ''::character varying))::text) || ' '::text) || COALESCE((properties)::text, ''::text)) || ' '::text) || COALESCE(file_names, ''::text))) public.gin_trgm_ops);
 
 
 CREATE INDEX collections_trgm_text_search_idx ON public.collections USING gin (((((((((((((((((((COALESCE(owner_uuid, ''::character varying))::text || ' '::text) || (COALESCE(modified_by_client_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(modified_by_user_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(portable_data_hash, ''::character varying))::text) || ' '::text) || (COALESCE(uuid, ''::character varying))::text) || ' '::text) || (COALESCE(name, ''::character varying))::text) || ' '::text) || (COALESCE(description, ''::character varying))::text) || ' '::text) || COALESCE((properties)::text, ''::text)) || ' '::text) || COALESCE(file_names, ''::text))) public.gin_trgm_ops);
 
 
+--
+-- Name: container_requests_full_text_search_idx; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX container_requests_full_text_search_idx ON public.container_requests USING gin (to_tsvector('english'::regconfig, substr((((((((((((((((((((((((((((((((((((((((((((((COALESCE(uuid, ''::character varying))::text || ' '::text) || (COALESCE(owner_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(modified_by_client_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(modified_by_user_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(name, ''::character varying))::text) || ' '::text) || COALESCE(description, ''::text)) || ' '::text) || COALESCE((properties)::text, ''::text)) || ' '::text) || (COALESCE(state, ''::character varying))::text) || ' '::text) || (COALESCE(requesting_container_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(container_uuid, ''::character varying))::text) || ' '::text) || COALESCE(runtime_constraints, ''::text)) || ' '::text) || (COALESCE(container_image, ''::character varying))::text) || ' '::text) || COALESCE(environment, ''::text)) || ' '::text) || (COALESCE(cwd, ''::character varying))::text) || ' '::text) || COALESCE(command, ''::text)) || ' '::text) || (COALESCE(output_path, ''::character varying))::text) || ' '::text) || COALESCE(filters, ''::text)) || ' '::text) || COALESCE(scheduling_parameters, ''::text)) || ' '::text) || (COALESCE(output_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(log_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(output_name, ''::character varying))::text) || ' '::text) || COALESCE((output_properties)::text, ''::text)) || ' '::text) || COALESCE(output_glob, ''::text)), 0, 8000)));
+
+
 --
 -- Name: container_requests_index_on_properties; Type: INDEX; Schema: public; Owner: -
 --
 --
 -- Name: container_requests_index_on_properties; Type: INDEX; Schema: public; Owner: -
 --
@@ -2167,7 +2176,7 @@ CREATE INDEX index_containers_on_queued_state ON public.containers USING btree (
 -- Name: index_containers_on_reuse_columns; Type: INDEX; Schema: public; Owner: -
 --
 
 -- Name: index_containers_on_reuse_columns; Type: INDEX; Schema: public; Owner: -
 --
 
-CREATE INDEX index_containers_on_reuse_columns ON public.containers USING btree (md5(command), cwd, md5(environment), output_path, container_image, md5(mounts), secret_mounts_md5, md5(runtime_constraints));
+CREATE INDEX index_containers_on_reuse_columns ON public.containers USING btree (md5(command), cwd, md5(environment), output_path, md5(output_glob), container_image, md5(mounts), secret_mounts_md5, md5(runtime_constraints));
 
 
 --
 
 
 --
@@ -3316,4 +3325,6 @@ INSERT INTO "schema_migrations" (version) VALUES
 ('20230815160000'),
 ('20230821000000'),
 ('20230922000000'),
 ('20230815160000'),
 ('20230821000000'),
 ('20230922000000'),
-('20231013000000');
+('20231013000000'),
+('20240329173437'),
+('20240402162733');
index e09037819c64fff413df491ce9e74e6d5febfaef..995f6f334c3ab48e7b82a14755ad0dcde7bb58ac 100644 (file)
@@ -14,11 +14,23 @@ module CanBeAnOwner
     # record when other objects refer to it.
     ActiveRecord::Base.connection.tables.each do |t|
       next if t == base.table_name
     # record when other objects refer to it.
     ActiveRecord::Base.connection.tables.each do |t|
       next if t == base.table_name
-      next if t == 'schema_migrations'
-      next if t == 'permission_refresh_lock'
-      next if t == 'ar_internal_metadata'
-      next if t == 'commit_ancestors'
-      next if t == 'commits'
+      next if t.in?([
+                      'schema_migrations',
+                      'permission_refresh_lock',
+                      'ar_internal_metadata',
+                      'commit_ancestors',
+                      'commits',
+                      'humans',
+                      'jobs',
+                      'job_tasks',
+                      'keep_disks',
+                      'nodes',
+                      'pipeline_instances',
+                      'pipeline_templates',
+                      'repositories',
+                      'specimens',
+                      'traits',
+                    ])
       klass = t.classify.constantize
       next unless klass and 'owner_uuid'.in?(klass.columns.collect(&:name))
       base.has_many(t.to_sym,
       klass = t.classify.constantize
       next unless klass and 'owner_uuid'.in?(klass.columns.collect(&:name))
       base.has_many(t.to_sym,
diff --git a/services/api/script/arvados-git-sync.rb b/services/api/script/arvados-git-sync.rb
deleted file mode 100755 (executable)
index 9f8f050..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-#!/usr/bin/env ruby
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'rubygems'
-require 'pp'
-require 'arvados'
-require 'tempfile'
-require 'yaml'
-require 'fileutils'
-
-# This script does the actual gitolite config management on disk.
-#
-# Ward Vandewege <ward@curii.com>
-
-# Default is development
-production = ARGV[0] == "production"
-
-ENV["RAILS_ENV"] = "development"
-ENV["RAILS_ENV"] = "production" if production
-
-DEBUG = 1
-
-# load and merge in the environment-specific application config info
-# if present, overriding base config parameters as specified
-path = File.absolute_path('../../config/arvados-clients.yml', __FILE__)
-if File.exist?(path) then
-  cp_config = File.open(path) do |f|
-    YAML.safe_load(f, filename: path)[ENV['RAILS_ENV']]
-  end
-else
-  puts "Please create a\n #{path}\n file"
-  exit 1
-end
-
-gitolite_url = cp_config['gitolite_url']
-gitolite_arvados_git_user_key = cp_config['gitolite_arvados_git_user_key']
-
-gitolite_tmpdir = cp_config['gitolite_tmp']
-gitolite_admin = File.join(gitolite_tmpdir, 'gitolite-admin')
-gitolite_admin_keydir = File.join(gitolite_admin, 'keydir')
-gitolite_keydir = File.join(gitolite_admin, 'keydir', 'arvados')
-
-ENV['ARVADOS_API_HOST'] = cp_config['arvados_api_host']
-ENV['ARVADOS_API_TOKEN'] = cp_config['arvados_api_token']
-if cp_config['arvados_api_host_insecure']
-  ENV['ARVADOS_API_HOST_INSECURE'] = 'true'
-else
-  ENV.delete('ARVADOS_API_HOST_INSECURE')
-end
-
-def ensure_directory(path, mode)
-  begin
-    Dir.mkdir(path, mode)
-  rescue Errno::EEXIST
-  end
-end
-
-def replace_file(path, contents)
-  unlink_now = true
-  dirname, basename = File.split(path)
-  FileUtils.mkpath(dirname)
-  new_file = Tempfile.new([basename, ".tmp"], dirname)
-  begin
-    new_file.write(contents)
-    new_file.flush
-    File.rename(new_file, path)
-    unlink_now = false
-  ensure
-    new_file.close(unlink_now)
-  end
-end
-
-def file_has_contents?(path, contents)
-  begin
-    IO.read(path) == contents
-  rescue Errno::ENOENT
-    false
-  end
-end
-
-module TrackCommitState
-  module ClassMethods
-    # Note that all classes that include TrackCommitState will have
-    # @@need_commit = true if any of them set it.  Since this flag reports
-    # a boolean state of the underlying git repository, that's OK in the
-    # current implementation.
-    @@need_commit = false
-
-    def changed?
-      @@need_commit
-    end
-
-    def ensure_in_git(path, contents)
-      unless file_has_contents?(path, contents)
-        replace_file(path, contents)
-        system("git", "add", path)
-        @@need_commit = true
-      end
-    end
-  end
-
-  def ensure_in_git(path, contents)
-    self.class.ensure_in_git(path, contents)
-  end
-
-  def self.included(base)
-    base.extend(ClassMethods)
-  end
-end
-
-class UserSSHKeys
-  include TrackCommitState
-
-  def initialize(user_keys_map, key_dir)
-    @user_keys_map = user_keys_map
-    @key_dir = key_dir
-    @installed = {}
-  end
-
-  def install(filename, pubkey)
-    unless pubkey.nil?
-      key_path = File.join(@key_dir, filename)
-      ensure_in_git(key_path, pubkey)
-    end
-    @installed[filename] = true
-  end
-
-  def ensure_keys_for_user(user_uuid)
-    return unless key_list = @user_keys_map.delete(user_uuid)
-    key_list.map { |k| k[:public_key] }.compact.each_with_index do |pubkey, ii|
-      # Handle putty-style ssh public keys
-      pubkey.sub!(/^(Comment: "r[^\n]*\n)(.*)$/m,'ssh-rsa \2 \1')
-      pubkey.sub!(/^(Comment: "d[^\n]*\n)(.*)$/m,'ssh-dss \2 \1')
-      pubkey.gsub!(/\n/,'')
-      pubkey.strip!
-      install("#{user_uuid}@#{ii}.pub", pubkey)
-    end
-  end
-
-  def installed?(filename)
-    @installed[filename]
-  end
-end
-
-class Repository
-  include TrackCommitState
-
-  @@aliases = {}
-
-  def initialize(arv_repo, user_keys)
-    @arv_repo = arv_repo
-    @user_keys = user_keys
-  end
-
-  def self.ensure_system_config(conf_root)
-    ensure_in_git(File.join(conf_root, "conf", "gitolite.conf"),
-                  %Q{include "auto/*.conf"\ninclude "admin/*.conf"\n})
-    ensure_in_git(File.join(conf_root, "arvadosaliases.pl"), alias_config)
-
-    conf_path = File.join(conf_root, "conf", "admin", "arvados.conf")
-    conf_file = %Q{
-@arvados_git_user = arvados_git_user
-
-repo gitolite-admin
-     RW           = @arvados_git_user
-
-}
-    ensure_directory(File.dirname(conf_path), 0755)
-    ensure_in_git(conf_path, conf_file)
-  end
-
-  def ensure_config(conf_root)
-    if name and (File.exist?(auto_conf_path(conf_root, name)))
-      # This gitolite installation knows the repository by name, rather than
-      # UUID.  Leave it configured that way until a separate migration is run.
-      basename = name
-    else
-      basename = uuid
-      @@aliases[name] = uuid unless name.nil?
-    end
-    conf_file = "\nrepo #{basename}\n"
-    @arv_repo[:user_permissions].sort.each do |user_uuid, perm|
-      conf_file += "\t#{perm[:gitolite_permissions]}\t= #{user_uuid}\n"
-      @user_keys.ensure_keys_for_user(user_uuid)
-    end
-    ensure_in_git(auto_conf_path(conf_root, basename), conf_file)
-  end
-
-  private
-
-  def auto_conf_path(conf_root, basename)
-    File.join(conf_root, "conf", "auto", "#{basename}.conf")
-  end
-
-  def uuid
-    @arv_repo[:uuid]
-  end
-
-  def name
-    if @arv_repo[:name].nil?
-      nil
-    else
-      @clean_name ||=
-        @arv_repo[:name].sub(/^[^A-Za-z]+/, "").gsub(/[^\w\.\/]/, "")
-    end
-  end
-
-  def self.alias_config
-    conf_s = "{\n"
-    @@aliases.sort.each do |(repo_name, repo_uuid)|
-      conf_s += "\t'#{repo_name}' \t=> '#{repo_uuid}',\n"
-    end
-    conf_s += "};\n"
-    conf_s
-  end
-end
-
-begin
-  # Get our local gitolite-admin repo up to snuff
-  if not File.exist?(gitolite_admin) then
-    ensure_directory(gitolite_tmpdir, 0700)
-    Dir.chdir(gitolite_tmpdir)
-    `git clone #{gitolite_url}`
-    Dir.chdir(gitolite_admin)
-  else
-    Dir.chdir(gitolite_admin)
-    `git pull`
-  end
-
-  arv = Arvados.new
-  permissions = arv.repository.get_all_permissions
-
-  ensure_directory(gitolite_keydir, 0700)
-  admin_user_ssh_keys = UserSSHKeys.new(permissions[:user_keys], gitolite_admin_keydir)
-  # Make sure the arvados_git_user key is installed; put it in gitolite_admin_keydir
-  # because that is where gitolite will try to put it if we do not.
-  admin_user_ssh_keys.install('arvados_git_user.pub', gitolite_arvados_git_user_key)
-
-  user_ssh_keys = UserSSHKeys.new(permissions[:user_keys], gitolite_keydir)
-  permissions[:repositories].each do |repo_record|
-    repo = Repository.new(repo_record, user_ssh_keys)
-    repo.ensure_config(gitolite_admin)
-  end
-  Repository.ensure_system_config(gitolite_admin)
-
-  # Clean up public key files that should not be present
-  Dir.chdir(gitolite_keydir)
-  stale_keys = Dir.glob('*.pub').reject do |key_file|
-    user_ssh_keys.installed?(key_file)
-  end
-  if stale_keys.any?
-    stale_keys.each { |key_file| puts "Extra file #{key_file}" }
-    system("git", "rm", "--quiet", *stale_keys)
-  end
-
-  if UserSSHKeys.changed? or Repository.changed? or stale_keys.any?
-    message = "#{Time.now().to_s}: update from API"
-    Dir.chdir(gitolite_admin)
-    `git add --all`
-    `git commit -m '#{message}'`
-    `git push`
-  end
-
-rescue => bang
-  puts "Error: " + bang.to_s
-  puts bang.backtrace.join("\n")
-  exit 1
-end
-
diff --git a/services/api/script/migrate-gitolite-to-uuid-storage.rb b/services/api/script/migrate-gitolite-to-uuid-storage.rb
deleted file mode 100755 (executable)
index 98f25ca..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-#!/usr/bin/env ruby
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-#
-# Prior to April 2015, Arvados Gitolite integration stored repositories by
-# name.  To improve user repository management, we switched to storing
-# repositories by UUID, and aliasing them to names.  This makes it easy to
-# have rich name hierarchies, and allow users to rename repositories.
-#
-# This script will migrate a name-based Gitolite configuration to a UUID-based
-# one.  To use it:
-#
-# 1. Change the value of REPOS_DIR below, if needed.
-# 2. Install this script in the same directory as `update-gitolite.rb`.
-# 3. Ensure that no *other* users can access Gitolite: edit gitolite's
-#    authorized_keys file so it only contains the arvados_git_user key,
-#    and disable the update-gitolite cron job.
-# 4. Run this script: `ruby migrate-gitolite-to-uuid-storage.rb production`.
-# 5. Undo step 3.
-
-require 'rubygems'
-require 'pp'
-require 'arvados'
-require 'tempfile'
-require 'yaml'
-
-REPOS_DIR = "/var/lib/gitolite/repositories"
-
-# Default is development
-production = ARGV[0] == "production"
-
-ENV["RAILS_ENV"] = "development"
-ENV["RAILS_ENV"] = "production" if production
-
-DEBUG = 1
-
-# load and merge in the environment-specific application config info
-# if present, overriding base config parameters as specified
-path = File.dirname(__FILE__) + '/config/arvados-clients.yml'
-if File.exist?(path) then
-  cp_config = File.open(path) do |f|
-    YAML.safe_load(f, filename: path)[ENV['RAILS_ENV']]
-  end
-else
-  puts "Please create a\n " + File.dirname(__FILE__) + "/config/arvados-clients.yml\n file"
-  exit 1
-end
-
-gitolite_url = cp_config['gitolite_url']
-gitolite_arvados_git_user_key = cp_config['gitolite_arvados_git_user_key']
-
-gitolite_tmpdir = File.join(File.absolute_path(File.dirname(__FILE__)),
-                            cp_config['gitolite_tmp'])
-gitolite_admin = File.join(gitolite_tmpdir, 'gitolite-admin')
-gitolite_keydir = File.join(gitolite_admin, 'keydir', 'arvados')
-
-ENV['ARVADOS_API_HOST'] = cp_config['arvados_api_host']
-ENV['ARVADOS_API_TOKEN'] = cp_config['arvados_api_token']
-if cp_config['arvados_api_host_insecure']
-  ENV['ARVADOS_API_HOST_INSECURE'] = 'true'
-else
-  ENV.delete('ARVADOS_API_HOST_INSECURE')
-end
-
-def ensure_directory(path, mode)
-  begin
-    Dir.mkdir(path, mode)
-  rescue Errno::EEXIST
-  end
-end
-
-def replace_file(path, contents)
-  unlink_now = true
-  dirname, basename = File.split(path)
-  new_file = Tempfile.new([basename, ".tmp"], dirname)
-  begin
-    new_file.write(contents)
-    new_file.flush
-    File.rename(new_file, path)
-    unlink_now = false
-  ensure
-    new_file.close(unlink_now)
-  end
-end
-
-def file_has_contents?(path, contents)
-  begin
-    IO.read(path) == contents
-  rescue Errno::ENOENT
-    false
-  end
-end
-
-module TrackCommitState
-  module ClassMethods
-    # Note that all classes that include TrackCommitState will have
-    # @@need_commit = true if any of them set it.  Since this flag reports
-    # a boolean state of the underlying git repository, that's OK in the
-    # current implementation.
-    @@need_commit = false
-
-    def changed?
-      @@need_commit
-    end
-
-    def ensure_in_git(path, contents)
-      unless file_has_contents?(path, contents)
-        replace_file(path, contents)
-        system("git", "add", path)
-        @@need_commit = true
-      end
-    end
-  end
-
-  def ensure_in_git(path, contents)
-    self.class.ensure_in_git(path, contents)
-  end
-
-  def self.included(base)
-    base.extend(ClassMethods)
-  end
-end
-
-class Repository
-  include TrackCommitState
-
-  @@aliases = {}
-
-  def initialize(arv_repo)
-    @arv_repo = arv_repo
-  end
-
-  def self.ensure_system_config(conf_root)
-    ensure_in_git(File.join(conf_root, "arvadosaliases.pl"), alias_config)
-  end
-
-  def self.rename_repos(repos_root)
-    @@aliases.each_pair do |uuid, name|
-      begin
-        File.rename(File.join(repos_root, "#{name}.git/"),
-                    File.join(repos_root, "#{uuid}.git"))
-      rescue Errno::ENOENT
-      end
-      if name == "arvados"
-        Dir.chdir(repos_root) { File.symlink("#{uuid}.git/", "arvados.git") }
-      end
-    end
-  end
-
-  def ensure_config(conf_root)
-    return if name.nil?
-    @@aliases[uuid] = name
-    name_conf_path = auto_conf_path(conf_root, name)
-    return unless File.exist?(name_conf_path)
-    conf_file = IO.read(name_conf_path)
-    conf_file.gsub!(/^repo #{Regexp.escape(name)}$/m, "repo #{uuid}")
-    ensure_in_git(auto_conf_path(conf_root, uuid), conf_file)
-    File.unlink(name_conf_path)
-    system("git", "rm", "--quiet", name_conf_path)
-  end
-
-  private
-
-  def auto_conf_path(conf_root, basename)
-    File.join(conf_root, "conf", "auto", "#{basename}.conf")
-  end
-
-  def uuid
-    @arv_repo[:uuid]
-  end
-
-  def name
-    if @arv_repo[:name].nil?
-      nil
-    else
-      @clean_name ||=
-        @arv_repo[:name].sub(/^[^A-Za-z]+/, "").gsub(/[^\w\.\/]/, "")
-    end
-  end
-
-  def self.alias_config
-    conf_s = "{\n"
-    @@aliases.sort.each do |(repo_name, repo_uuid)|
-      conf_s += "\t'#{repo_name}' \t=> '#{repo_uuid}',\n"
-    end
-    conf_s += "};\n"
-    conf_s
-  end
-end
-
-begin
-  # Get our local gitolite-admin repo up to snuff
-  if not File.exist?(gitolite_admin) then
-    ensure_directory(gitolite_tmpdir, 0700)
-    Dir.chdir(gitolite_tmpdir)
-    `git clone #{gitolite_url}`
-    Dir.chdir(gitolite_admin)
-  else
-    Dir.chdir(gitolite_admin)
-    `git pull`
-  end
-
-  arv = Arvados.new
-  permissions = arv.repository.get_all_permissions
-
-  permissions[:repositories].each do |repo_record|
-    repo = Repository.new(repo_record)
-    repo.ensure_config(gitolite_admin)
-  end
-  Repository.ensure_system_config(gitolite_admin)
-
-  message = "#{Time.now().to_s}: migrate to storing repositories by UUID"
-  Dir.chdir(gitolite_admin)
-  `git add --all`
-  `git commit -m '#{message}'`
-  Repository.rename_repos(REPOS_DIR)
-  `git push`
-
-rescue => bang
-  puts "Error: " + bang.to_s
-  puts bang.backtrace.join("\n")
-  exit 1
-end
-
index c6ade21f8bdf2333288581da93b0d47cab886f4c..882937034bb6dc33e9caa13bfcb52ca9b5e1c1e9 100644 (file)
@@ -128,14 +128,6 @@ active_userlist:
   expires_at: 2038-01-01 00:00:00
   scopes: ["GET /arvados/v1/users"]
 
   expires_at: 2038-01-01 00:00:00
   scopes: ["GET /arvados/v1/users"]
 
-active_specimens:
-  uuid: zzzzz-gj3su-177z32aux8dg2s1
-  api_client: untrusted
-  user: active
-  api_token: activespecimensabcdefghijklmnopqrstuvwxyz123456890
-  expires_at: 2038-01-01 00:00:00
-  scopes: ["GET /arvados/v1/specimens/"]
-
 active_apitokens:
   uuid: zzzzz-gj3su-187z32aux8dg2s1
   api_client: trusted_workbench
 active_apitokens:
   uuid: zzzzz-gj3su-187z32aux8dg2s1
   api_client: trusted_workbench
@@ -160,14 +152,21 @@ spectator:
   api_token: zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu
   expires_at: 2038-01-01 00:00:00
 
   api_token: zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu
   expires_at: 2038-01-01 00:00:00
 
-spectator_specimens:
+foo:
+  uuid: zzzzz-gj3su-fohzae5ib1aseiv
+  api_client: untrusted
+  user: user_foo_in_sharing_group
+  api_token: lokah4xip8ahgee8oof5zitah3ohdai6je9cu1uogh4bai3ohw
+  expires_at: 2038-01-01 00:00:00
+
+foo_collections:
   uuid: zzzzz-gj3su-217z32aux8dg2s1
   api_client: untrusted
   uuid: zzzzz-gj3su-217z32aux8dg2s1
   api_client: untrusted
-  user: spectator
-  api_token: spectatorspecimensabcdefghijklmnopqrstuvwxyz123245
+  user: user_foo_in_sharing_group
+  api_token: spectatorcollectionscdefghijklmnopqrstuvwxyz123245
   expires_at: 2038-01-01 00:00:00
   expires_at: 2038-01-01 00:00:00
-  scopes: ["GET /arvados/v1/specimens", "GET /arvados/v1/specimens/",
-           "POST /arvados/v1/specimens"]
+  scopes: ["GET /arvados/v1/collections", "GET /arvados/v1/collections/",
+           "POST /arvados/v1/collections"]
 
 inactive:
   uuid: zzzzz-gj3su-227z32aux8dg2s1
 
 inactive:
   uuid: zzzzz-gj3su-227z32aux8dg2s1
index 72aad1d68ee3f28e7fe853cf911ac4841cf896e0..a193150e29acaf885258820d7a4505f6ea3171b7 100644 (file)
@@ -451,22 +451,6 @@ unique_expired_collection2:
   manifest_text: ". 29d7797f1888013986899bc9083783fa+3 0:3:expired2\n"
   name: unique_expired_collection2
 
   manifest_text: ". 29d7797f1888013986899bc9083783fa+3 0:3:expired2\n"
   name: unique_expired_collection2
 
-# a collection with a log file that can be parsed by the log viewer
-# This collection hash matches the following log text:
-#    2014-01-01_12:00:01 zzzzz-8i9sb-abcdefghijklmno 0  log message 1
-#    2014-01-01_12:00:02 zzzzz-8i9sb-abcdefghijklmno 0  log message 2
-#    2014-01-01_12:00:03 zzzzz-8i9sb-abcdefghijklmno 0  log message 3
-#
-real_log_collection:
-  uuid: zzzzz-4zz18-op4e2lbej01tcvu
-  current_version_uuid: zzzzz-4zz18-op4e2lbej01tcvu
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-09-01 12:00:00
-  modified_at: 2014-09-01 12:00:00
-  portable_data_hash: 0b9a7787660e1fce4a93f33e01376ba6+81
-  manifest_text: ". cdd549ae79fe6640fa3d5c6261d8303c+195 0:195:zzzzz-8i9sb-0vsrcqi7whchuil.log.txt\n"
-  name: real_log_collection
-
 collection_in_home_project_with_same_name_as_in_aproject:
   uuid: zzzzz-4zz18-12342x4u7ftabcd
   current_version_uuid: zzzzz-4zz18-12342x4u7ftabcd
 collection_in_home_project_with_same_name_as_in_aproject:
   uuid: zzzzz-4zz18-12342x4u7ftabcd
   current_version_uuid: zzzzz-4zz18-12342x4u7ftabcd
@@ -1076,6 +1060,18 @@ collection_with_uri_prop:
   properties:
     "http://schema.org/example": "value1"
 
   properties:
     "http://schema.org/example": "value1"
 
+container_log_collection:
+  uuid: zzzzz-4zz18-logcollection00
+  current_version_uuid: zzzzz-4zz18-logcollection00
+  portable_data_hash: b1e66f713c04d28ddbaced89096f4838+210
+  owner_uuid: zzzzz-tpzed-000000000000000
+  created_at: 2020-10-29T00:51:44.075594000Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2020-10-29T00:51:44.072109000Z
+  manifest_text: ". 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n"
+  name: a real log collection for a completed container
+
 log_collection:
   uuid: zzzzz-4zz18-logcollection01
   current_version_uuid: zzzzz-4zz18-logcollection01
 log_collection:
   uuid: zzzzz-4zz18-logcollection01
   current_version_uuid: zzzzz-4zz18-logcollection01
@@ -1086,7 +1082,7 @@ log_collection:
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
   modified_at: 2020-10-29T00:51:44.072109000Z
   manifest_text: ". 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n./log\\040for\\040container\\040ce8i5-dz642-h4kd64itncdcz8l 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n"
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
   modified_at: 2020-10-29T00:51:44.072109000Z
   manifest_text: ". 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n./log\\040for\\040container\\040ce8i5-dz642-h4kd64itncdcz8l 8c12f5f5297b7337598170c6f531fcee+7882 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt\n"
-  name: a real log collection for a completed container
+  name: a real log collection for a completed container request
 
 log_collection2:
   uuid: zzzzz-4zz18-logcollection02
 
 log_collection2:
   uuid: zzzzz-4zz18-logcollection02
index 71c7a54df3981eb531f0b36d28788aa3a6d29247..3b035416cb09a3c992edde29add5075afe6b1a1f 100644 (file)
@@ -1056,6 +1056,36 @@ runtime_token:
     ram: 123
   mounts: {}
 
     ram: 123
   mounts: {}
 
+read_foo_write_bar:
+  uuid: zzzzz-xvdhp-readfoowritebar
+  owner_uuid: zzzzz-tpzed-000000000000000
+  state: Final
+  created_at: 2024-01-11 11:11:11.111111111 Z
+  updated_at: 2024-01-11 11:11:11.111111111 Z
+  modified_at: 2024-01-11 11:11:11.111111111 Z
+  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  container_image: test
+  cwd: /
+  mounts:
+    stdin:
+      kind: collection
+      portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+      path: /foo
+    stdout:
+      kind: file
+      path: /mnt/out/bar
+    /mnt/out:
+      kind: tmp
+      capacity: 1000
+  container_uuid: zzzzz-dz642-readfoowritebar
+  log_uuid: zzzzz-4zz18-logcollection01
+  output_uuid: zzzzz-4zz18-ehbhgtheo8909or
+  output_path: test
+  command: ["echo", "-n", "bar"]
+  runtime_constraints:
+    ram: 10000000
+    vcpus: 1
+
 
 # Test Helper trims the rest of the file
 
 
 # Test Helper trims the rest of the file
 
index 46bc1e50f9da3bdbc417fef55116c74601e7c319..8e40554a9bda02cbfa99eb8e3c388c3470a8d456 100644 (file)
@@ -485,3 +485,36 @@ cuda_container:
       device_count: 1
   secret_mounts: {}
   secret_mounts_md5: 99914b932bd37a50b983c5e7c90ae93b
       device_count: 1
   secret_mounts: {}
   secret_mounts_md5: 99914b932bd37a50b983c5e7c90ae93b
+
+read_foo_write_bar:
+  uuid: zzzzz-dz642-readfoowritebar
+  owner_uuid: zzzzz-tpzed-000000000000000
+  state: Complete
+  exit_code: 0
+  priority: 1
+  created_at: 2024-01-11 11:11:11.111111111 Z
+  updated_at: 2024-01-11 11:11:11.111111111 Z
+  started_at: 2024-01-11 11:11:11.111111111 Z
+  finished_at: 2024-01-12 11:12:13.111111111 Z
+  container_image: test
+  cwd: /
+  mounts:
+    stdin:
+      kind: collection
+      portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
+      path: /foo
+    stdout:
+      kind: file
+      path: /mnt/out/bar
+    /mnt/out:
+      kind: tmp
+      capacity: 1000
+  log: ea10d51bcf88862dbcc36eb292017dfd+45
+  output: fa7aeb5140e2848d39b416daeef4ffc5+45
+  output_path: test
+  command: ["echo", "-n", "bar"]
+  runtime_constraints:
+    ram: 10000000
+    vcpus: 1
+  secret_mounts: {}
+  secret_mounts_md5: 99914b932bd37a50b983c5e7c90ae93b
diff --git a/services/api/test/fixtures/humans.yml b/services/api/test/fixtures/humans.yml
deleted file mode 100644 (file)
index eee61ef..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-# File exists to ensure the table gets cleared during DatabaseController#reset
diff --git a/services/api/test/fixtures/job_tasks.yml b/services/api/test/fixtures/job_tasks.yml
deleted file mode 100644 (file)
index 6a857a0..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-running_job_task_1:
-  uuid: zzzzz-ot0gb-runningjobtask1
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  job_uuid: zzzzz-8i9sb-with2components
-
-running_job_task_2:
-  uuid: zzzzz-ot0gb-runningjobtask2
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  job_uuid: zzzzz-8i9sb-with2components
diff --git a/services/api/test/fixtures/jobs.yml b/services/api/test/fixtures/jobs.yml
deleted file mode 100644 (file)
index 54b3825..0000000
+++ /dev/null
@@ -1,768 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-running:
-  uuid: zzzzz-8i9sb-pshmckwoma9plh7
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 2.7.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.7.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script: hash
-  repository: active/foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-running_cancelled:
-  uuid: zzzzz-8i9sb-4cf0nhn6xte809j
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: <%= 1.minute.ago.to_fs(:db) %>
-  cancelled_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script: hash
-  repository: active/foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Cancelled
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-uses_nonexistent_script_version:
-  uuid: zzzzz-8i9sb-7m339pu0x9mla88
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  created_at: <%= 5.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  script: hash
-  repository: active/foo
-  running: false
-  success: true
-  output: d41d8cd98f00b204e9800998ecf8427e+0
-  priority: 0
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-foobar:
-  uuid: zzzzz-8i9sb-aceg2bnq7jt7kon
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script: hash
-  repository: active/foo
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  script_parameters:
-    input: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  running: false
-  success: true
-  output: fa7aeb5140e2848d39b416daeef4ffc5+45
-  priority: 0
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 03a43a7d84f7fb022467b876c2950acd
-
-barbaz:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykyuq
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: 1
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  running: false
-  success: true
-  repository: active/foo
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  priority: 0
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: c3d19d3ec50ac0914baa56b149640f73
-
-runningbarbaz:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykyuj
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: 1
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: <%= 2.minute.ago.to_fs(:db) %>
-  running: true
-  success: ~
-  repository: active/foo
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  priority: 0
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 1
-    done: 0
-  runtime_constraints: {}
-  state: Running
-  script_parameters_digest: c3d19d3ec50ac0914baa56b149640f73
-
-previous_job_run:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  finished_at: <%= 13.minutes.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  success: true
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-
-previous_job_run_nil_log:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykqq3
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  finished_at: <%= 13.minutes.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "3"
-  success: true
-  log: ~
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: 445702df4029b8a6e7075b451ff1256a
-
-previous_ancient_job_run:
-  uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-  created_at: <%= 366.days.ago.to_fs(:db) %>
-  finished_at: <%= 365.days.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "2"
-  success: true
-  log: d41d8cd98f00b204e9800998ecf8427e+0
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: 174dd339d44f2b259fadbab7ebdb8df9
-
-previous_docker_job_run:
-  uuid: zzzzz-8i9sb-k6emstgk4kw4yhi
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  runtime_constraints:
-    docker_image: arvados/apitestfixture
-  success: true
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  docker_image_locator: fa3c1a9cb6783f85f2ecda037e07b8c3+167
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-
-previous_ancient_docker_image_job_run:
-  uuid: zzzzz-8i9sb-t3b460aolxxuldl
-  created_at: <%= 144.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "2"
-  runtime_constraints:
-    docker_image: arvados/apitestfixture
-  success: true
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  docker_image_locator: b519d9cb706a29fc7ea24dbea2f05851+93
-  state: Complete
-  script_parameters_digest: 174dd339d44f2b259fadbab7ebdb8df9
-
-previous_job_run_with_arvados_sdk_version:
-  uuid: zzzzz-8i9sb-eoo0321or2dw2jg
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 31ce37fe365b3dc204300a3e4c396ad333ed0556
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  runtime_constraints:
-    arvados_sdk_version: commit2
-    docker_image: arvados/apitestfixture
-  arvados_sdk_version: 00634b2b8a492d6f121e3cf1d6587b821136a9a7
-  docker_image_locator: fa3c1a9cb6783f85f2ecda037e07b8c3+167
-  success: true
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-
-previous_job_run_no_output:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykppp
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "2"
-  success: true
-  output: ~
-  state: Complete
-  script_parameters_digest: 174dd339d44f2b259fadbab7ebdb8df9
-
-previous_job_run_superseded_by_hash_branch:
-  # This supplied_script_version is a branch name with later commits.
-  uuid: zzzzz-8i9sb-aeviezu5dahph3e
-  created_at: <%= 15.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/shabranchnames
-  script: testscript
-  script_version: 7387838c69a21827834586cc42b467ff6c63293b
-  supplied_script_version: 738783
-  script_parameters: {}
-  success: true
-  output: d41d8cd98f00b204e9800998ecf8427e+0
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-nondeterminisic_job_run:
-  uuid: zzzzz-8i9sb-cjs4pklxxjykyyy
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: hash2
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    an_integer: "1"
-  success: true
-  nondeterministic: true
-  state: Complete
-  script_parameters_digest: a5f03bbfb8ba88a2efe4a7852671605b
-
-nearly_finished_job:
-  uuid: zzzzz-8i9sb-2gx6rz0pjl033w3
-  created_at: <%= 14.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: arvados
-  script: doesnotexist
-  script_version: 309e25a64fe994867db8459543af372f850e25b9
-  script_parameters:
-    input: b519d9cb706a29fc7ea24dbea2f05851+249025
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  running: true
-  success: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 1
-    done: 0
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 7ea26d58a79b7f5db9f90fb1e33d3006
-
-queued:
-  uuid: zzzzz-8i9sb-grx15v5mjnsyxk7
-  created_at: <%= 1.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  started_at: ~
-  finished_at: ~
-  script: foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters: {}
-  running: ~
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: ~
-  tasks_summary: {}
-  runtime_constraints: {}
-  state: Queued
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-# A job with a log collection that can be parsed by the log viewer.
-job_with_real_log:
-  uuid: zzzzz-8i9sb-0vsrcqi7whchuil
-  created_at: 2014-09-01 12:00:00
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  log: 0b9a7787660e1fce4a93f33e01376ba6+81
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-cancelled:
-  uuid: zzzzz-8i9sb-4cf0abc123e809j
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: <%= 1.minute.ago.to_fs(:db) %>
-  cancelled_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: false
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Cancelled
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-job_in_subproject:
-  uuid: zzzzz-8i9sb-subprojectjob01
-  created_at: 2014-10-15 12:00:00
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  log: ~
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-job_in_trashed_project:
-  uuid: zzzzz-8i9sb-subprojectjob02
-  created_at: 2014-10-15 12:00:00
-  owner_uuid: zzzzz-j7d0g-trashedproject2
-  log: ~
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-running_will_be_completed:
-  uuid: zzzzz-8i9sb-rshmckwoma9pjh8
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-graph_stage1:
-  uuid: zzzzz-8i9sb-graphstage10000
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  repository: active/foo
-  script: hash
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  output: fa7aeb5140e2848d39b416daeef4ffc5+45
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-graph_stage2:
-  uuid: zzzzz-8i9sb-graphstage20000
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  repository: active/foo
-  script: hash2
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff"
-  output: 65b17c95fdbc9800fc48acda4e9dcd0b+93
-  script_parameters_digest: 4900033ec5cfaf8a63566f3664aeaa70
-
-graph_stage3:
-  uuid: zzzzz-8i9sb-graphstage30000
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  repository: active/foo
-  script: hash2
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  output: ea10d51bcf88862dbcc36eb292017dfd+45
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-
-job_with_latest_version:
-  uuid: zzzzz-8i9sb-nj8ioxnrvjtyk2b
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  script: hash
-  repository: active/foo
-  script_version: 7def43a4d3f20789dda4700f703b5514cc3ed250
-  supplied_script_version: main
-  script_parameters:
-    input: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.minute.ago.to_fs(:db) %>
-  finished_at: <%= 1.minute.ago.to_fs(:db) %>
-  running: false
-  success: true
-  output: fa7aeb5140e2848d39b416daeef4ffc5+45
-  priority: 0
-  log: ea10d51bcf88862dbcc36eb292017dfd+45
-  is_locked_by_uuid: ~
-  tasks_summary:
-    failed: 0
-    todo: 0
-    running: 0
-    done: 1
-  runtime_constraints: {}
-  state: Complete
-  script_parameters_digest: 03a43a7d84f7fb022467b876c2950acd
-
-running_job_in_publicly_accessible_project:
-  uuid: zzzzz-8i9sb-n7omg50bvt0m1nf
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/bar
-  script: running_job_script
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Running
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-
-completed_job_in_publicly_accessible_project:
-  uuid: zzzzz-8i9sb-jyq01m7in1jlofj
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: completed_job_script
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  log: zzzzz-4zz18-4en62shvi99lxd4
-  output: b519d9cb706a29fc7ea24dbea2f05851+93
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-  started_at: <%= 10.minute.ago.to_fs(:db) %>
-  finished_at: <%= 5.minute.ago.to_fs(:db) %>
-
-job_in_publicly_accessible_project_but_other_objects_elsewhere:
-  uuid: zzzzz-8i9sb-jyq01muyhgr4ofj
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  repository: active/foo
-  script: completed_job_script
-  script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
-  state: Complete
-  script_parameters:
-    input: fa7aeb5140e2848d39b416daeef4ffc5+45
-    input2: "stuff2"
-  log: zzzzz-4zz18-fy296fx3hot09f7
-  output: zzzzz-4zz18-bv31uwvy3neko21
-  script_parameters_digest: 02a085407e751d00b5dc88f1bd5e8247
-
-running_job_with_components:
-  uuid: zzzzz-8i9sb-with2components
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  started_at: <%= 3.minute.ago.to_fs(:db) %>
-  finished_at: ~
-  script: hash
-  repository: active/foo
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-jyq01m7in1jlofj
-    component2: zzzzz-d1hrv-partdonepipelin
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-
-# This main level job is in running state with one job and one pipeline instance components
-running_job_with_components_at_level_1:
-  uuid: zzzzz-8i9sb-jobcomponentsl1
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-jobcomponentsl2
-    component2: zzzzz-d1hrv-picomponentsl02
-
-# This running job, a child of level_1, has one child component
-running_job_with_components_at_level_2:
-  uuid: zzzzz-8i9sb-jobcomponentsl2
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-job1atlevel3noc
-
-# The below two running jobs, children of level_2, have no child components
-running_job_1_with_components_at_level_3:
-  uuid: zzzzz-8i9sb-job1atlevel3noc
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-
-running_job_2_with_components_at_level_3:
-  uuid: zzzzz-8i9sb-job2atlevel3noc
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-
-# The two jobs below are so confused, they have circular relationship
-running_job_1_with_circular_component_relationship:
-  uuid: zzzzz-8i9sb-job1withcirculr
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-job2withcirculr
-
-running_job_2_with_circular_component_relationship:
-  uuid: zzzzz-8i9sb-job2withcirculr
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  cancelled_at: ~
-  cancelled_by_user_uuid: ~
-  cancelled_by_client_uuid: ~
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  finished_at: ~
-  repository: active/foo
-  script: hash
-  script_version: 1de84a854e2b440dc53bf42f8548afa4c17da332
-  script_parameters_digest: 99914b932bd37a50b983c5e7c90ae93b
-  running: true
-  success: ~
-  output: ~
-  priority: 0
-  log: ~
-  is_locked_by_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  tasks_summary:
-    failed: 0
-    todo: 3
-    running: 1
-    done: 1
-  runtime_constraints: {}
-  state: Running
-  components:
-    component1: zzzzz-8i9sb-job1withcirculr
diff --git a/services/api/test/fixtures/keep_disks.yml b/services/api/test/fixtures/keep_disks.yml
deleted file mode 100644 (file)
index 5cccf49..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-nonfull:
-  uuid: zzzzz-penuu-5w2o2t1q5wy7fhn
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  node_uuid: zzzzz-7ekkf-53y36l1lu5ijveb
-  keep_service_uuid: zzzzz-bi6l4-6zhilxar6r8ey90
-  last_read_at: <%= 1.minute.ago.to_fs(:db) %>
-  last_write_at: <%= 2.minute.ago.to_fs(:db) %>
-  last_ping_at: <%= 3.minute.ago.to_fs(:db) %>
-  ping_secret: z9xz2tc69dho51g1dmkdy5fnupdhsprahcwxdbjs0zms4eo6i
-
-full:
-  uuid: zzzzz-penuu-4kmq58ui07xuftx
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  node_uuid: zzzzz-7ekkf-53y36l1lu5ijveb
-  keep_service_uuid: zzzzz-bi6l4-6zhilxar6r8ey90
-  last_read_at: <%= 1.minute.ago.to_fs(:db) %>
-  last_write_at: <%= 2.day.ago.to_fs(:db) %>
-  last_ping_at: <%= 3.minute.ago.to_fs(:db) %>
-  ping_secret: xx3ieejcufbjy4lli6yt5ig4e8w5l2hhgmbyzpzuq38gri6lj
-
-nonfull2:
-  uuid: zzzzz-penuu-1ydrih9k2er5j11
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
-  node_uuid: zzzzz-7ekkf-2z3mc76g2q73aio
-  keep_service_uuid: zzzzz-bi6l4-rsnj3c76ndxb7o0
-  last_read_at: <%= 1.minute.ago.to_fs(:db) %>
-  last_write_at: <%= 2.minute.ago.to_fs(:db) %>
-  last_ping_at: <%= 3.minute.ago.to_fs(:db) %>
-  ping_secret: 4rs260ibhdum1d242xy23qv320rlerc0j7qg9vyqnchbgmjeek
index 00d597153486ae391c255640d44dfaf93e8a71dd..61ad60451d088f15a554cf8661b28ed0f977def8 100644 (file)
@@ -254,104 +254,6 @@ baz_file_publicly_readable:
   head_uuid: zzzzz-4zz18-y9vne9npefyxh8g
   properties: {}
 
   head_uuid: zzzzz-4zz18-y9vne9npefyxh8g
   properties: {}
 
-barbaz_job_readable_by_spectator:
-  uuid: zzzzz-o0j2j-cpy7p41hpk531e1
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykyuq
-  properties: {}
-
-runningbarbaz_job_readable_by_spectator:
-  uuid: zzzzz-o0j2j-cpy7p41hpk531e2
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykyuj
-  properties: {}
-
-arvados_repository_readable_by_all_users:
-  uuid: zzzzz-o0j2j-allcanreadarvrp
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-j7d0g-fffffffffffffff
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-s0uqq-arvadosrepo0123
-  properties: {}
-
-foo_repository_readable_by_spectator:
-  uuid: zzzzz-o0j2j-cpy7p41hpk5xxx
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-s0uqq-382brsig8rp3666
-  properties: {}
-
-foo_repository_manageable_by_active:
-  uuid: zzzzz-o0j2j-8tdfjd8g0s4rn1k
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-01-24 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-01-24 20:42:26 -0800
-  updated_at: 2014-01-24 20:42:26 -0800
-  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  link_class: permission
-  name: can_manage
-  head_uuid: zzzzz-s0uqq-382brsig8rp3666
-  properties: {}
-
-repository3_readable_by_active:
-  uuid: zzzzz-o0j2j-43iem9bdtefa76g
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-09-23 13:52:46 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-09-23 13:52:46 -0400
-  updated_at: 2014-09-23 13:52:46 -0400
-  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  link_class: permission
-  name: can_read
-  head_uuid: zzzzz-s0uqq-38orljkqpyo1j61
-  properties: {}
-
-repository4_writable_by_active:
-  uuid: zzzzz-o0j2j-lio9debdt6yhkil
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-09-23 13:52:46 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-09-23 13:52:46 -0400
-  updated_at: 2014-09-23 13:52:46 -0400
-  tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  link_class: permission
-  name: can_write
-  head_uuid: zzzzz-s0uqq-38oru8hnk57ht34
-  properties: {}
-
 miniadmin_user_is_a_testusergroup_admin:
   uuid: zzzzz-o0j2j-38vvkciz7qc12j9
   owner_uuid: zzzzz-tpzed-000000000000000
 miniadmin_user_is_a_testusergroup_admin:
   uuid: zzzzz-o0j2j-38vvkciz7qc12j9
   owner_uuid: zzzzz-tpzed-000000000000000
@@ -784,81 +686,6 @@ docker_image_tag_like_hash:
   properties:
     image_timestamp: "2014-06-10T14:30:00.184019565Z"
 
   properties:
     image_timestamp: "2014-06-10T14:30:00.184019565Z"
 
-job_reader_can_read_previous_job_run:
-  # Permission link giving job_reader permission
-  # to read previous_job_run
-  uuid: zzzzz-o0j2j-8bbd851795ebafd
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-905b42d1dd4a354
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-
-job_reader_can_read_foo_repo:
-  # Permission link giving job_reader permission
-  # to read foo_repo
-  uuid: zzzzz-o0j2j-072ec05dc9487f8
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-905b42d1dd4a354
-  head_uuid: zzzzz-s0uqq-382brsig8rp3666
-
-job_reader2_can_read_job_with_components:
-  # Permission link giving job_reader2 permission
-  # to read running_job_with_components
-  uuid: zzzzz-o0j2j-jobcomps4jobrdr
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-readjobwithcomp
-  head_uuid: zzzzz-8i9sb-with2components
-
-job_reader2_can_read_pipeline_from_job_with_components:
-  # Permission link giving job_reader2 permission
-  # to read running_job_with_components
-  uuid: zzzzz-o0j2j-pi4comps4jobrdr
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-readjobwithcomp
-  head_uuid: zzzzz-d1hrv-partdonepipelin
-
-job_reader2_can_read_first_job_from_pipeline_from_job_with_components:
-  # Permission link giving job_reader2 permission
-  # to read running_job_with_components
-  uuid: zzzzz-o0j2j-job4pi4j4jobrdr
-  owner_uuid: zzzzz-tpzed-000000000000000
-  created_at: 2014-06-13 20:42:26 -0800
-  modified_by_client_uuid: zzzzz-tpzed-000000000000000
-  modified_by_user_uuid: zzzzz-tpzed-000000000000000
-  modified_at: 2014-06-13 20:42:26 -0800
-  updated_at: 2014-06-13 20:42:26 -0800
-  link_class: permission
-  name: can_read
-  tail_uuid: zzzzz-tpzed-readjobwithcomp
-  head_uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-
 baz_collection_name_in_asubproject:
   uuid: zzzzz-o0j2j-bazprojectname2
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
 baz_collection_name_in_asubproject:
   uuid: zzzzz-o0j2j-bazprojectname2
   owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
index 3b41550ae784802948e33c82b2ced53930718a6f..ee24d2ac649cf9243e5cf73a9f90e8612cc9bf32 100644 (file)
@@ -11,22 +11,22 @@ noop: # nothing happened ...to the 'spectator' user
   event_at: <%= 1.minute.ago.to_fs(:db) %>
   created_at: <%= 1.minute.ago.to_fs(:db) %>
 
   event_at: <%= 1.minute.ago.to_fs(:db) %>
   created_at: <%= 1.minute.ago.to_fs(:db) %>
 
-admin_changes_repository2: # admin changes repository2, which is owned by active user
+admin_changes_collection_owned_by_active:
   id: 2
   uuid: zzzzz-57u5n-pshmckwoma00002
   owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
   id: 2
   uuid: zzzzz-57u5n-pshmckwoma00002
   owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  object_uuid: zzzzz-2x53u-382brsig8rp3667 # repository foo
+  object_uuid: zzzzz-4zz18-bv31uwvy3neko21 # collection_owned_by_active
   object_owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
   created_at: <%= 2.minute.ago.to_fs(:db) %>
   event_at: <%= 2.minute.ago.to_fs(:db) %>
   event_type: update
 
   object_owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
   created_at: <%= 2.minute.ago.to_fs(:db) %>
   event_at: <%= 2.minute.ago.to_fs(:db) %>
   event_type: update
 
-admin_changes_specimen: # admin changes specimen owned_by_spectator
+admin_changes_collection_owned_by_foo:
   id: 3
   uuid: zzzzz-57u5n-pshmckwoma00003
   owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
   id: 3
   uuid: zzzzz-57u5n-pshmckwoma00003
   owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  object_uuid: zzzzz-2x53u-3b0xxwzlbzxq5yr # specimen owned_by_spectator
-  object_owner_uuid: zzzzz-tpzed-l1s2piq4t4mps8r # spectator user
+  object_uuid: zzzzz-4zz18-50surkhkbhsp31b # collection_owned_by_foo
+  object_owner_uuid: zzzzz-tpzed-81hsbo6mk8nl05c # foo user
   created_at: <%= 3.minute.ago.to_fs(:db) %>
   event_at: <%= 3.minute.ago.to_fs(:db) %>
   event_type: update
   created_at: <%= 3.minute.ago.to_fs(:db) %>
   event_at: <%= 3.minute.ago.to_fs(:db) %>
   event_type: update
@@ -60,101 +60,6 @@ log_owned_by_active:
   event_at: <%= 2.minute.ago.to_fs(:db) %>
   summary: non-admin use can read own logs
 
   event_at: <%= 2.minute.ago.to_fs(:db) %>
   summary: non-admin use can read own logs
 
-crunchstat_for_running_job:
-  id: 7
-  uuid: zzzzz-57u5n-tmymyrojrbtnxh1
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-8i9sb-pshmckwoma9plh7
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-8i9sb-pshmckwoma9plh7 31708 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-
-log_line_for_pipeline_in_publicly_accessible_project:
-  id: 8
-  uuid: zzzzz-57u5n-tmymyrojrjyhb45
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-d1hrv-n68vc490mloy4fi
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-d1hrv-n68vc490mloy4fi 31708 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-
-log_line_for_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
-  id: 9
-  uuid: zzzzz-57u5n-tmyhy56k9lnhb45
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-d1hrv-pisharednotobjs
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-d1hrv-pisharednotobjs 31708 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-
-crunchstat_for_previous_job:
-  id: 10
-  uuid: zzzzz-57u5n-eir3aesha3kaene
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-  event_at: 2014-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2014-11-07_23:33:41 zzzzz-8i9sb-cjs4pklxxjykqqq 11592 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2014-11-07 23:33:42.351913000 Z
-  updated_at: 2014-11-07 23:33:42.347455000 Z
-  modified_at: 2014-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-xurymjxw79nv3jz
-
-crunchstat_for_ancient_job:
-  id: 11
-  uuid: zzzzz-57u5n-ixioph7ieb5ung8
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  object_uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-  event_at: 2013-11-07 23:33:42.347455000 Z
-  event_type: stderr
-  summary: ~
-  properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
-      cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
-      0.9900 sys'
-  created_at: 2013-11-07 23:33:42.351913000 Z
-  updated_at: 2013-11-07 23:33:42.347455000 Z
-  modified_at: 2013-11-07 23:33:42.347455000 Z
-  object_owner_uuid: zzzzz-j7d0g-xurymjxw79nv3jz
-
 stderr_for_ancient_container:
   id: 12
   uuid: zzzzz-57u5n-containerlog001
 stderr_for_ancient_container:
   id: 12
   uuid: zzzzz-57u5n-containerlog001
@@ -166,7 +71,7 @@ stderr_for_ancient_container:
   event_type: stderr
   summary: ~
   properties:
   event_type: stderr
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer01 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 2.year.ago.to_fs(:db) %>
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 2.year.ago.to_fs(:db) %>
@@ -185,7 +90,7 @@ crunchstat_for_ancient_container:
   event_type: crunchstat
   summary: ~
   properties:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer01 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 2.year.ago.to_fs(:db) %>
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 2.year.ago.to_fs(:db) %>
@@ -204,7 +109,7 @@ stderr_for_previous_container:
   event_type: stderr
   summary: ~
   properties:
   event_type: stderr
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer02 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.month.ago.to_fs(:db) %>
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.month.ago.to_fs(:db) %>
@@ -223,7 +128,7 @@ crunchstat_for_previous_container:
   event_type: crunchstat
   summary: ~
   properties:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer02 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.month.ago.to_fs(:db) %>
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.month.ago.to_fs(:db) %>
@@ -242,7 +147,7 @@ stderr_for_running_container:
   event_type: crunchstat
   summary: ~
   properties:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer03 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.hour.ago.to_fs(:db) %>
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.hour.ago.to_fs(:db) %>
@@ -261,7 +166,7 @@ crunchstat_for_running_container:
   event_type: crunchstat
   summary: ~
   properties:
   event_type: crunchstat
   summary: ~
   properties:
-    text: '2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat:
+    text: '2013-11-07_23:33:41 zzzzz-dz642-logscontainer03 29610 1 stderr crunchstat:
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.hour.ago.to_fs(:db) %>
       cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
       0.9900 sys'
   created_at: <%= 1.hour.ago.to_fs(:db) %>
diff --git a/services/api/test/fixtures/nodes.yml b/services/api/test/fixtures/nodes.yml
deleted file mode 100644 (file)
index d4589ed..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-busy:
-  uuid: zzzzz-7ekkf-53y36l1lu5ijveb
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute0
-  slot_number: 0
-  domain: ""
-  ip_address: 172.17.2.172
-  last_ping_at: <%= 1.minute.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: zzzzz-8i9sb-2gx6rz0pjl033w3  # nearly_finished_job
-  properties: {}
-  info:
-    ping_secret: "48dpm3b8ijyj3jkr2yczxw0844dqd2752bhll7klodvgz9bg80"
-    slurm_state: "alloc"
-
-down:
-  uuid: zzzzz-7ekkf-2vbompg3ecc6e2s
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute1
-  slot_number: 1
-  domain: ""
-  ip_address: 172.17.2.173
-  last_ping_at: <%= 1.hour.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "2k3i71depad36ugwmlgzilbi4e8n0illb2r8l4efg9mzkb3a1k"
-
-idle:
-  uuid: zzzzz-7ekkf-2z3mc76g2q73aio
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute2
-  slot_number: 2
-  domain: ""
-  ip_address: 172.17.2.174
-  last_ping_at: <%= 2.minute.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: ~
-  info:
-    ping_secret: "69udawxvn3zzj45hs8bumvndricrha4lcpi23pd69e44soanc0"
-    slurm_state: "idle"
-  properties:
-    total_cpu_cores: 16
-
-was_idle_now_down:
-  uuid: zzzzz-7ekkf-xuzpkdasl0uzwyz
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: compute3
-  slot_number: ~
-  domain: ""
-  ip_address: 172.17.2.174
-  last_ping_at: <%= 1.hour.ago.to_fs(:db) %>
-  first_ping_at: <%= 23.hour.ago.to_fs(:db) %>
-  job_uuid: ~
-  info:
-    ping_secret: "1bd1yi0x4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
-    slurm_state: "idle"
-  properties:
-    total_cpu_cores: 16
-
-new_with_no_hostname:
-  uuid: zzzzz-7ekkf-newnohostname00
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: ~
-  slot_number: ~
-  ip_address: 172.17.2.175
-  last_ping_at: ~
-  first_ping_at: ~
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "abcdyi0x4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
-
-new_with_custom_hostname:
-  uuid: zzzzz-7ekkf-newwithhostname
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: custom1
-  slot_number: 23
-  ip_address: 172.17.2.176
-  last_ping_at: ~
-  first_ping_at: ~
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "abcdyi0x4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
-
-node_with_no_ip_address_yet:
-  uuid: zzzzz-7ekkf-nodenoipaddryet
-  owner_uuid: zzzzz-tpzed-000000000000000
-  hostname: noipaddr
-  slot_number: ~
-  last_ping_at: ~
-  first_ping_at: ~
-  job_uuid: ~
-  properties: {}
-  info:
-    ping_secret: "abcdyefg4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2"
diff --git a/services/api/test/fixtures/pipeline_instances.yml b/services/api/test/fixtures/pipeline_instances.yml
deleted file mode 100644 (file)
index 714fc60..0000000
+++ /dev/null
@@ -1,530 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-new_pipeline:
-  state: New
-  uuid: zzzzz-d1hrv-f4gneyn6br1xize
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 1.minute.ago.to_fs(:db) %>
-
-new_pipeline_in_subproject:
-  state: New
-  uuid: zzzzz-d1hrv-subprojpipeline
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  created_at: <%= 1.minute.ago.to_fs(:db) %>
-
-has_component_with_no_script_parameters:
-  state: Ready
-  uuid: zzzzz-d1hrv-1xfj6xkicf2muk2
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 10.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-
-has_component_with_empty_script_parameters:
-  state: Ready
-  uuid: zzzzz-d1hrv-jq16l10gcsnyumo
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 3.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-
-has_component_with_completed_jobs:
-  # Test that the job "started_at" and "finished_at" fields are parsed
-  # into Time fields when rendering. These jobs must *not* have their
-  # own fixtures; the point is to force the
-  # pipeline_instances_controller_test in Workbench to parse the
-  # "components" field. (The relevant code paths are also used when a
-  # user has permission to read the pipeline instance itself, but not
-  # the jobs referenced by its components hash.)
-  state: Complete
-  uuid: zzzzz-d1hrv-i3e77t9z5y8j9cc
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 11.minute.ago.to_fs(:db) %>
-  started_at: <%= 10.minute.ago.to_fs(:db) %>
-  finished_at: <%= 9.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-rft1xdewxkwgxnz
-      script_version: main
-      created_at: <%= 10.minute.ago.to_fs(:db) %>
-      started_at: <%= 10.minute.ago.to_fs(:db) %>
-      finished_at: <%= 9.minute.ago.to_fs(:db) %>
-      state: Complete
-      tasks_summary:
-        failed: 0
-        todo: 0
-        running: 0
-        done: 1
-   bar:
-    script: bar
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-r2dtbzr6bfread7
-      script_version: main
-      created_at: <%= 9.minute.ago.to_fs(:db) %>
-      started_at: <%= 9.minute.ago.to_fs(:db) %>
-      state: Running
-      tasks_summary:
-        failed: 0
-        todo: 1
-        running: 2
-        done: 3
-   baz:
-    script: baz
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-c7408rni11o7r6s
-      script_version: main
-      created_at: <%= 9.minute.ago.to_fs(:db) %>
-      state: Queued
-      tasks_summary: {}
-
-has_job:
-  name: pipeline_with_job
-  state: Ready
-  uuid: zzzzz-d1hrv-1yfj6xkidf2muk3
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 2.9.minute.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job: {
-            uuid: zzzzz-8i9sb-pshmckwoma9plh7,
-            script_version: main
-         }
-
-components_is_jobspec:
-  # Helps test that clients cope with funny-shaped components.
-  # For an example, see #3321.
-  uuid: zzzzz-d1hrv-1yfj61234abcdk4
-  created_at: <%= 4.minute.ago.to_fs(:db) %>
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  state: RunningOnServer
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-pipeline_with_tagged_collection_input:
-  name: pipeline_with_tagged_collection_input
-  state: Ready
-  uuid: zzzzz-d1hrv-1yfj61234abcdk3
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 3.2.minute.ago.to_fs(:db) %>
-  components:
-    part-one:
-      script_parameters:
-        input:
-          value: zzzzz-4zz18-znfnqtbbv4spc3w
-
-pipeline_to_merge_params:
-  name: pipeline_to_merge_params
-  state: Ready
-  uuid: zzzzz-d1hrv-1yfj6dcba4321k3
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 3.3.minute.ago.to_fs(:db) %>
-  components:
-    part-one:
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "Foo/bar pair"
-          description: "Provide a collection containing at least two files."
-    part-two:
-      script_parameters:
-        input:
-          output_of: part-one
-        integer_with_default:
-          default: 123
-        integer_with_value:
-          value: 123
-        string_with_default:
-          default: baz
-        string_with_value:
-          value: baz
-        plain_string: qux
-        array_with_default:
-          default: [1,1,2,3,5]
-        array_with_value:
-          value: [1,1,2,3,5]
-
-pipeline_with_newer_template:
-  state: Complete
-  uuid: zzzzz-d1hrv-9fm8l10i9z2kqc6
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  pipeline_template_uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  created_at: 2014-09-15 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_instance_owned_by_fuse:
-  state: Complete
-  uuid: zzzzz-d1hrv-ri9dvgkgqs9y09j
-  owner_uuid: zzzzz-tpzed-0fusedrivertest
-  pipeline_template_uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  created_at: 2014-09-16 12:00:00
-  name: "pipeline instance owned by FUSE"
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_instance_in_fuse_project:
-  state: Complete
-  uuid: zzzzz-d1hrv-scarxiyajtshq3l
-  owner_uuid: zzzzz-j7d0g-0000ownedbyfuse
-  pipeline_template_uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  created_at: 2014-09-17 12:00:00
-  name: "pipeline instance in FUSE project"
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_owned_by_active_in_aproject:
-  name: Completed pipeline in A Project
-  state: Complete
-  uuid: zzzzz-d1hrv-ju5ghi0i9z2kqc6
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-09-18 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_owned_by_active_in_home:
-  name: Completed pipeline in active user home
-  state: Complete
-  uuid: zzzzz-d1hrv-lihrbd0i9z2kqc6
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-09-19 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-
-pipeline_in_publicly_accessible_project:
-  uuid: zzzzz-d1hrv-n68vc490mloy4fi
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in publicly accessible project
-  pipeline_template_uuid: zzzzz-p5p6p-tmpltpublicproj
-  state: Complete
-  created_at: <%= 30.minute.ago.to_fs(:db) %>
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-      job:
-        uuid: zzzzz-8i9sb-jyq01m7in1jlofj
-        repository: active/foo
-        script: foo
-        script_version: main
-        script_parameters:
-          input: zzzzz-4zz18-4en62shvi99lxd4
-        log: zzzzz-4zz18-4en62shvi99lxd4
-        output: b519d9cb706a29fc7ea24dbea2f05851+93
-        state: Complete
-
-pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
-  uuid: zzzzz-d1hrv-pisharednotobjs
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in public project with other objects elsewhere
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  state: Complete
-  created_at: 2014-09-20 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-      job:
-        uuid: zzzzz-8i9sb-aceg2bnq7jt7kon
-        repository: active/foo
-        script: foo
-        script_version: main
-        script_parameters:
-          input: zzzzz-4zz18-bv31uwvy3neko21
-        log: zzzzz-4zz18-bv31uwvy3neko21
-        output: zzzzz-4zz18-bv31uwvy3neko21
-        state: Complete
-
-new_pipeline_in_publicly_accessible_project:
-  uuid: zzzzz-d1hrv-newpisharedobjs
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in New state in publicly accessible project
-  pipeline_template_uuid: zzzzz-p5p6p-tmpltpublicproj
-  state: New
-  created_at: 2014-09-21 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          value: b519d9cb706a29fc7ea24dbea2f05851+93
-
-new_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
-  uuid: zzzzz-d1hrv-newsharenotobjs
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in New state in public project with objects elsewhere
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  state: New
-  created_at: 2014-09-22 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          value: zzzzz-4zz18-bv31uwvy3neko21
-
-new_pipeline_in_publicly_accessible_project_with_dataclass_file_and_other_objects_elsewhere:
-  uuid: zzzzz-d1hrv-newsharenotfile
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  name: Pipeline in public project in New state with file type data class with objects elsewhere
-  pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  state: New
-  created_at: 2014-09-23 12:00:00
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: File
-          value: zzzzz-4zz18-bv31uwvy3neko21/bar
-
-pipeline_in_running_state:
-  name: running_with_job
-  uuid: zzzzz-d1hrv-runningpipeline
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 2.8.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.8.minute.ago.to_fs(:db) %>
-  state: RunningOnServer
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-pshmckwoma9plh7
-      script_version: main
-
-running_pipeline_with_complete_job:
-  uuid: zzzzz-d1hrv-partdonepipelin
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  state: RunningOnServer
-  created_at: <%= 15.minute.ago.to_fs(:db) %>
-  components:
-   previous:
-    job:
-      uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-   running:
-    job:
-      uuid: zzzzz-8i9sb-pshmckwoma9plh7
-
-complete_pipeline_with_two_jobs:
-  uuid: zzzzz-d1hrv-twodonepipeline
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  state: Complete
-  created_at: <%= 2.5.minute.ago.to_fs(:db) %>
-  started_at: <%= 2.minute.ago.to_fs(:db) %>
-  finished_at: <%= 1.minute.ago.to_fs(:db) %>
-  components:
-   ancient:
-    job:
-      uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-   previous:
-    job:
-      uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-
-failed_pipeline_with_two_jobs:
-  uuid: zzzzz-d1hrv-twofailpipeline
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 55.minute.ago.to_fs(:db) %>
-  state: Failed
-  components:
-   ancient:
-    job:
-      uuid: zzzzz-8i9sb-ahd7cie8jah9qui
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-   previous:
-    job:
-      uuid: zzzzz-8i9sb-cjs4pklxxjykqqq
-      log: zzzzz-4zz18-op4e2lbej01tcvu
-
-# This pipeline is a child of another running job and has it's own running children
-job_child_pipeline_with_components_at_level_2:
-  state: RunningOnServer
-  uuid: zzzzz-d1hrv-picomponentsl02
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: <%= 12.hour.ago.to_fs(:db) %>
-  started_at: <%= 12.hour.ago.to_fs(:db) %>
-  components:
-   foo:
-    script: foo
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-job1atlevel3noc
-      script_version: main
-      created_at: <%= 12.hour.ago.to_fs(:db) %>
-      started_at: <%= 12.hour.ago.to_fs(:db) %>
-      state: Running
-      tasks_summary:
-        failed: 0
-        todo: 0
-        running: 1
-        done: 1
-   bar:
-    script: bar
-    script_version: main
-    script_parameters: {}
-    job:
-      uuid: zzzzz-8i9sb-job2atlevel3noc
-      script_version: main
-      created_at: <%= 12.hour.ago.to_fs(:db) %>
-      started_at: <%= 12.hour.ago.to_fs(:db) %>
-      state: Running
-      tasks_summary:
-        failed: 0
-        todo: 1
-        running: 2
-        done: 3
-
-# Test Helper trims the rest of the file
-
-# Do not add your fixtures below this line as the rest of this file will be trimmed by test_helper
-
-# pipelines in project_with_10_pipelines
-<% for i in 1..10 do %>
-pipeline_<%=i%>_of_10:
-  name: pipeline_<%= i %>
-  uuid: zzzzz-d1hrv-10pipelines0<%= i.to_s.rjust(3, '0') %>
-  owner_uuid: zzzzz-j7d0g-000010pipelines
-  created_at: <%= (2*(i-1)).hour.ago.to_fs(:db) %>
-  started_at: <%= (2*(i-1)).hour.ago.to_fs(:db) %>
-  finished_at: <%= (i-1).minute.ago.to_fs(:db) %>
-  state: Failed
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-      job:
-        state: Failed
-<% end %>
-
-# pipelines in project_with_2_pipelines_and_60_crs
-<% for i in 1..2 do %>
-pipeline_<%=i%>_of_2_pipelines_and_60_crs:
-  name: pipeline_<%= i %>
-  state: New
-  uuid: zzzzz-d1hrv-abcgneyn6brx<%= i.to_s.rjust(3, '0') %>
-  owner_uuid: zzzzz-j7d0g-nnncrspipelines
-  created_at: <%= i.minute.ago.to_fs(:db) %>
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-<% end %>
-
-# pipelines in project_with_25_pipelines
-<% for i in 1..25 do %>
-pipeline_<%=i%>_of_25:
-  name: pipeline_<%=i%>
-  state: Failed
-  uuid: zzzzz-d1hrv-25pipelines0<%= i.to_s.rjust(3, '0') %>
-  owner_uuid: zzzzz-j7d0g-000025pipelines
-  created_at: <%= i.hour.ago.to_fs(:db) %>
-  started_at: <%= i.hour.ago.to_fs(:db) %>
-  finished_at: <%= i.minute.ago.to_fs(:db) %>
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo instance input
-<% end %>
-
-# Do not add your fixtures below this line as the rest of this file will be trimmed by test_helper
diff --git a/services/api/test/fixtures/pipeline_templates.yml b/services/api/test/fixtures/pipeline_templates.yml
deleted file mode 100644 (file)
index 0c185ee..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-two_part:
-  uuid: zzzzz-p5p6p-aox0k0ofxrystgw
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Two Part Pipeline Template
-  components:
-    part-one:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "Foo/bar pair"
-    part-two:
-      script: bar
-      script_version: main
-      script_parameters:
-        input:
-          output_of: part-one
-        integer_with_default:
-          default: 123
-        integer_with_value:
-          value: 123
-        string_with_default:
-          default: baz
-        string_with_value:
-          value: baz
-        plain_string: qux
-        array_with_default: # important to test repeating values in the array!
-          default: [1,1,2,3,5]
-        array_with_value: # important to test repeating values in the array!
-          value: [1,1,2,3,5]
-
-components_is_jobspec:
-  # Helps test that clients cope with funny-shaped components.
-  # For an example, see #3321.
-  uuid: zzzzz-p5p6p-jobspeccomponts
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline Template with Jobspec Components
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-parameter_with_search:
-  uuid: zzzzz-p5p6p-paramwsearch345
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline Template with Input Parameter with Search
-  components:
-    with-search:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "Foo/bar pair"
-          description: "Provide a collection containing at least two files."
-          search_for: sometime  # Matches baz_collection_in_asubproject
-
-new_pipeline_template:
-  # This template must include components that are not
-  # present in the pipeline instance 'pipeline_with_newer_template',
-  # at least one of which has a script_parameter that is a hash
-  # with a 'dataclass' field (ticket #4000)
-  uuid: zzzzz-p5p6p-vq4wuvy84xvaq2r
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-09-14 12:00:00
-  modified_at: 2014-09-16 12:00:00
-  name: Pipeline Template Newer Than Instance
-  components:
-    foo:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: foo template input
-    bar:
-      script: bar
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: bar template input
-
-pipeline_template_in_fuse_project:
-  uuid: zzzzz-p5p6p-templinfuseproj
-  owner_uuid: zzzzz-j7d0g-0000ownedbyfuse
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-0fusedrivertest
-  name: pipeline template in FUSE project
-  components:
-    foo_component:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "default input"
-          description: "input collection"
-
-template_with_dataclass_file:
-  uuid: zzzzz-p5p6p-k0xoa0ofxrystgw
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Two Part Template with dataclass File
-  components:
-    part-one:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: File
-          title: "Foo/bar pair"
-          description: "Provide an input file"
-    part-two:
-      script: bar
-      script_version: main
-      script_parameters:
-        input:
-          output_of: part-one
-        integer_with_default:
-          default: 123
-        integer_with_value:
-          value: 123
-        string_with_default:
-          default: baz
-        string_with_value:
-          value: baz
-        plain_string: qux
-        array_with_default: # important to test repeating values in the array!
-          default: [1,1,2,3,5]
-        array_with_value: # important to test repeating values in the array!
-          value: [1,1,2,3,5]
-
-template_with_dataclass_number:
-  uuid: zzzzz-p5p6p-numbertemplatea
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2015-01-14 12:35:04 -0400
-  updated_at: 2015-01-14 12:35:04 -0400
-  modified_at: 2015-01-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Template with dataclass number
-  components:
-    work:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: number
-          title: "Input number"
-
-pipeline_template_in_publicly_accessible_project:
-  uuid: zzzzz-p5p6p-tmpltpublicproj
-  owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline template in publicly accessible project
-  components:
-    foo_component:
-      script: foo
-      script_version: main
-      script_parameters:
-        input:
-          required: true
-          dataclass: Collection
-          title: "default input"
-          description: "input collection"
-
-# Used to test renaming when removed from the "aproject" subproject
-# while another such object with same name exists in home project.
-template_in_active_user_home_project_to_test_unique_key_violation:
-  uuid: zzzzz-p5p6p-templatsamenam1
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2013-04-14 12:35:04 -0400
-  updated_at: 2013-04-14 12:35:04 -0400
-  modified_at: 2013-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Template to test owner uuid and name unique key violation upon removal
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-template_in_asubproject_with_same_name_as_one_in_active_user_home:
-  uuid: zzzzz-p5p6p-templatsamenam2
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  created_at: 2013-04-14 12:35:04 -0400
-  updated_at: 2013-04-14 12:35:04 -0400
-  modified_at: 2013-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Template to test owner uuid and name unique key violation upon removal
-  components:
-    script: foo
-    script_version: main
-    script_parameters:
-      input:
-        required: true
-        dataclass: Collection
-        title: "Foo/bar pair"
-        description: "Provide a collection containing at least two files."
-
-workflow_with_input_defaults:
-  uuid: zzzzz-p5p6p-aox0k0ofxrystg2
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-14 12:35:04 -0400
-  updated_at: 2014-04-14 12:35:04 -0400
-  modified_at: 2014-04-14 12:35:04 -0400
-  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
-  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  name: Pipeline with default input specifications
-  components:
-    part-one:
-      script: foo
-      script_version: main
-      script_parameters:
-        ex_string:
-          required: true
-          dataclass: string
-        ex_string_def:
-          required: true
-          dataclass: string
-          default: hello-testing-123
diff --git a/services/api/test/fixtures/repositories.yml b/services/api/test/fixtures/repositories.yml
deleted file mode 100644 (file)
index e4fe71e..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-crunch_dispatch_test:
-  uuid: zzzzz-s0uqq-382brsig8rp3665
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/crunchdispatchtest
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-arvados:
-  uuid: zzzzz-s0uqq-arvadosrepo0123
-  owner_uuid: zzzzz-tpzed-000000000000000 # root
-  name: arvados
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-foo:
-  uuid: zzzzz-s0uqq-382brsig8rp3666
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/foo
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-repository2:
-  uuid: zzzzz-s0uqq-382brsig8rp3667
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/foo2
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-repository3:
-  uuid: zzzzz-s0uqq-38orljkqpyo1j61
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  name: admin/foo3
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-repository4:
-  uuid: zzzzz-s0uqq-38oru8hnk57ht34
-  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f # admin user
-  name: admin/foo4
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
-
-has_branch_with_commit_hash_name:
-  uuid: zzzzz-s0uqq-382brsig8rp3668
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz # active user
-  name: active/shabranchnames
-  created_at: 2015-01-01T00:00:00.123456Z
-  modified_at: 2015-01-01T00:00:00.123456Z
diff --git a/services/api/test/fixtures/specimens.yml b/services/api/test/fixtures/specimens.yml
deleted file mode 100644 (file)
index bcae020..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-owned_by_active_user:
-  uuid: zzzzz-j58dm-3zx463qyo0k4xrn
-  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-owned_by_private_group:
-  uuid: zzzzz-j58dm-5m3qwg45g3nlpu6
-  owner_uuid: zzzzz-j7d0g-rew6elm53kancon
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-owned_by_spectator:
-  uuid: zzzzz-j58dm-3b0xxwzlbzxq5yr
-  owner_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-in_aproject:
-  uuid: zzzzz-j58dm-7r18rnd5nzhg5yk
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
-
-in_asubproject:
-  uuid: zzzzz-j58dm-c40lddwcqqr1ffs
-  owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
-  created_at: 2014-04-21 15:37:48 -0400
-  modified_at: 2014-04-21 15:37:48 -0400
diff --git a/services/api/test/fixtures/traits.yml b/services/api/test/fixtures/traits.yml
deleted file mode 100644 (file)
index 83beb70..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-owned_by_aproject_with_no_name:
-  uuid: zzzzz-q1cn2-ypsjlol9dofwijz
-  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
-  created_at: 2014-05-05 04:11:52 -0400
-  modified_at: 2014-05-05 04:11:52 -0400
index af7882141e31973c7e28d0f42da16abb088ed88c..2a64e9c5e25fb7a3ee6125a225726221a8af8a34 100644 (file)
@@ -13,8 +13,8 @@ class ApplicationControllerTest < ActionController::TestCase
 
   setup do
     # These tests are meant to check behavior in ApplicationController.
 
   setup do
     # These tests are meant to check behavior in ApplicationController.
-    # We instantiate a small concrete controller for convenience.
-    @controller = Arvados::V1::SpecimensController.new
+    # We instantiate an arbitrary concrete controller.
+    @controller = Arvados::V1::CollectionsController.new
     @start_stamp = now_timestamp
   end
 
     @start_stamp = now_timestamp
   end
 
@@ -42,13 +42,13 @@ class ApplicationControllerTest < ActionController::TestCase
 
   test "requesting object without read permission returns 404 error" do
     authorize_with :spectator
 
   test "requesting object without read permission returns 404 error" do
     authorize_with :spectator
-    get(:show, params: {id: specimens(:owned_by_active_user).uuid})
+    get(:show, params: {id: collections(:collection_owned_by_active).uuid})
     check_404
   end
 
   test "submitting bad object returns error" do
     authorize_with :spectator
     check_404
   end
 
   test "submitting bad object returns error" do
     authorize_with :spectator
-    post(:create, params: {specimen: {badattr: "badvalue"}})
+    post(:create, params: {collection: {badattr: "badvalue"}})
     assert_response 422
     check_error_token
   end
     assert_response 422
     check_error_token
   end
index 43797035bce8c1531717006245b62ea2e89af9d8..3f65b934f5102f2c6641fd7c048989ece9dc1a12 100644 (file)
@@ -516,14 +516,10 @@ EOS
 
   test "get full provenance for baz file" do
     authorize_with :active
 
   test "get full provenance for baz file" do
     authorize_with :active
-    get :provenance, params: {id: 'ea10d51bcf88862dbcc36eb292017dfd+45'}
+    get :provenance, params: {id: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'}
     assert_response :success
     resp = JSON.parse(@response.body)
     assert_response :success
     resp = JSON.parse(@response.body)
-    assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
-    assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
-    assert_not_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
-    assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq'] # bar->baz
-    assert_not_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon'] # foo->bar
+    assert_not_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # baz collection
   end
 
   test "get no provenance for foo file" do
   end
 
   test "get no provenance for foo file" do
@@ -540,10 +536,7 @@ EOS
     assert_response :success
     resp = JSON.parse(@response.body)
     assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
     assert_response :success
     resp = JSON.parse(@response.body)
     assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
-    assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
-    assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq']     # bar->baz
-    assert_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon']         # foo->bar
-    assert_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
+    assert_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # foo->bar
   end
 
   test "search collections with 'any' operator" do
   end
 
   test "search collections with 'any' operator" do
diff --git a/services/api/test/functional/arvados/v1/commits_controller_test.rb b/services/api/test/functional/arvados/v1/commits_controller_test.rb
deleted file mode 100644 (file)
index bf285b0..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::CommitsControllerTest < ActionController::TestCase
-end
index ee7f716c806ba77c7f449c5f970b16051cf56661..6e167bb91ea9fb19e579c332edfb2597e0560981 100644 (file)
@@ -65,12 +65,12 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_equal 0, json_response['items_available']
   end
 
     assert_equal 0, json_response['items_available']
   end
 
-  def check_project_contents_response disabled_kinds=[]
+  def check_project_contents_response
     assert_response :success
     assert_operator 2, :<=, json_response['items_available']
     assert_operator 2, :<=, json_response['items'].count
     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
     assert_response :success
     assert_operator 2, :<=, json_response['items_available']
     assert_operator 2, :<=, json_response['items'].count
     kinds = json_response['items'].collect { |i| i['kind'] }.uniq
-    expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job' - disabled_kinds
+    expect_kinds = %w'arvados#group'
     assert_equal expect_kinds, (expect_kinds & kinds)
 
     json_response['items'].each do |i|
     assert_equal expect_kinds, (expect_kinds & kinds)
 
     json_response['items'].each do |i|
@@ -79,10 +79,6 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
                "group#contents returned a non-project group")
       end
     end
                "group#contents returned a non-project group")
       end
     end
-
-    disabled_kinds.each do |d|
-      assert_equal true, !kinds.include?(d)
-    end
   end
 
   test 'get group-owned objects' do
   end
 
   test 'get group-owned objects' do
@@ -107,17 +103,17 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     authorize_with :project_viewer
     get :contents, params: {
       format: :json,
     authorize_with :project_viewer
     get :contents, params: {
       format: :json,
-      filters: [['uuid', 'is_a', 'arvados#specimen']]
+      filters: [['uuid', 'is_a', 'arvados#collection']]
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
-    [[:in_aproject, true],
-     [:in_asubproject, true],
-     [:owned_by_private_group, false]].each do |specimen_fixture, should_find|
+    [[:foo_collection_in_aproject, true],
+     [:baz_collection_name_in_asubproject, true],
+     [:collection_not_readable_by_active, false]].each do |collection_fixture, should_find|
       if should_find
       if should_find
-        assert_includes found_uuids, specimens(specimen_fixture).uuid, "did not find specimen fixture '#{specimen_fixture}'"
+        assert_includes found_uuids, collections(collection_fixture).uuid, "did not find collection fixture '#{collection_fixture}'"
       else
       else
-        refute_includes found_uuids, specimens(specimen_fixture).uuid, "found specimen fixture '#{specimen_fixture}'"
+        refute_includes found_uuids, collections(collection_fixture).uuid, "found collection fixture '#{collection_fixture}'"
       end
     end
   end
       end
     end
   end
@@ -150,8 +146,8 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
     }
     assert_response :success
     found_uuids = json_response['items'].collect { |i| i['uuid'] }
-    assert_includes found_uuids, specimens(:owned_by_active_user).uuid, "specimen did not appear in home project"
-    refute_includes found_uuids, specimens(:in_asubproject).uuid, "specimen appeared unexpectedly in home project"
+    assert_includes found_uuids, collections(:collection_owned_by_active).uuid, "collection did not appear in home project"
+    refute_includes found_uuids, collections(:foo_collection_in_aproject).uuid, "collection appeared unexpectedly in home project"
   end
 
   test "list collections in home project" do
   end
 
   test "list collections in home project" do
@@ -279,20 +275,20 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
   test "user with project read permission can't rename items in it" do
     authorize_with :project_viewer
 
   test "user with project read permission can't rename items in it" do
     authorize_with :project_viewer
-    @controller = Arvados::V1::LinksController.new
+    @controller = Arvados::V1::CollectionsController.new
     post :update, params: {
     post :update, params: {
-      id: jobs(:running).uuid,
+      id: collections(:collection_to_search_for_in_aproject).uuid,
       name: "Denied test name",
     }
     assert_includes(403..404, response.status)
   end
 
   test "user with project read permission can't remove items from it" do
       name: "Denied test name",
     }
     assert_includes(403..404, response.status)
   end
 
   test "user with project read permission can't remove items from it" do
-    @controller = Arvados::V1::PipelineTemplatesController.new
+    @controller = Arvados::V1::CollectionsController.new
     authorize_with :project_viewer
     post :update, params: {
     authorize_with :project_viewer
     post :update, params: {
-      id: pipeline_templates(:two_part).uuid,
-      pipeline_template: {
+      id: collections(:collection_to_search_for_in_aproject).uuid,
+      collection: {
         owner_uuid: users(:project_viewer).uuid,
       }
     }
         owner_uuid: users(:project_viewer).uuid,
       }
     }
@@ -339,8 +335,8 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
       select: ["uuid", "storage_classes_desired"]
     }
     assert_response :success
       select: ["uuid", "storage_classes_desired"]
     }
     assert_response :success
-    assert_equal 17, json_response['items_available']
-    assert_equal 17, json_response['items'].count
+    assert_equal 6, json_response['items_available']
+    assert_equal 6, json_response['items'].count
     json_response['items'].each do |item|
       # Expect collections to have a storage_classes field, other items should not.
       if item["kind"] == "arvados#collection"
     json_response['items'].each do |item|
       # Expect collections to have a storage_classes field, other items should not.
       if item["kind"] == "arvados#collection"
@@ -480,9 +476,9 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
 
   [
     [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
 
   [
     [['owner_uuid', '!=', 'zzzzz-tpzed-xurymjxw79nv3jz'], 200,
-        'zzzzz-d1hrv-subprojpipeline', 'zzzzz-d1hrv-1xfj6xkicf2muk2'],
-    [["pipeline_instances.state", "not in", ["Complete", "Failed"]], 200,
-        'zzzzz-d1hrv-1xfj6xkicf2muk2', 'zzzzz-d1hrv-i3e77t9z5y8j9cc'],
+        'zzzzz-j7d0g-publicfavorites', 'zzzzz-xvhdp-cr4queuedcontnr'],
+    [["container_requests.state", "not in", ["Final"]], 200,
+        'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4completedctr'],
     [['container_requests.requesting_container_uuid', '=', nil], 200,
         'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
     [['container_requests.no_such_column', '=', nil], 422],
     [['container_requests.requesting_container_uuid', '=', nil], 200,
         'zzzzz-xvhdp-cr4queuedcontnr', 'zzzzz-xvhdp-cr4requestercn2'],
     [['container_requests.no_such_column', '=', nil], 422],
@@ -503,25 +499,17 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     end
   end
 
     end
   end
 
-  test 'get contents with jobs and pipeline instances disabled' do
-    Rails.configuration.API.DisabledAPIs = ConfigLoader.to_OrderedOptions(
-      {'jobs.index'=>{}, 'pipeline_instances.index'=>{}})
-
-    authorize_with :active
-    get :contents, params: {
-      id: groups(:aproject).uuid,
-      format: :json,
-    }
-    check_project_contents_response %w'arvados#pipelineInstance arvados#job'
-  end
-
   test 'get contents with low max_index_database_read' do
     # Some result will certainly have at least 12 bytes in a
   test 'get contents with low max_index_database_read' do
     # Some result will certainly have at least 12 bytes in a
-    # restricted column
+    # restricted column.
+    #
+    # We cannot use collections.manifest_text to test this, because
+    # GroupsController refuses to select manifest_text, because
+    # controller doesn't sign manifests in a groups#contents response.
     Rails.configuration.API.MaxIndexDatabaseRead = 12
     authorize_with :active
     get :contents, params: {
     Rails.configuration.API.MaxIndexDatabaseRead = 12
     authorize_with :active
     get :contents, params: {
-          id: groups(:aproject).uuid,
+          uuid: users(:active).uuid,
           format: :json,
         }
     assert_response :success
           format: :json,
         }
     assert_response :success
diff --git a/services/api/test/functional/arvados/v1/humans_controller_test.rb b/services/api/test/functional/arvados/v1/humans_controller_test.rb
deleted file mode 100644 (file)
index d73fb30..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::HumansControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/job_reuse_controller_test.rb b/services/api/test/functional/arvados/v1/job_reuse_controller_test.rb
deleted file mode 100644 (file)
index 46cfac5..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-class Arvados::V1::JobReuseControllerTest < ActionController::TestCase
-  fixtures :repositories, :users, :jobs, :links, :collections
-
-  setup do
-    @controller = Arvados::V1::JobsController.new
-    authorize_with :active
-  end
-
-  BASE_FILTERS = {
-    'repository' => ['=', 'active/foo'],
-    'script' => ['=', 'hash'],
-    'script_version' => ['in git', 'main'],
-    'docker_image_locator' => ['=', nil],
-    'arvados_sdk_version' => ['=', nil],
-  }
-
-  def filters_from_hash(hash)
-    hash.each_pair.map { |name, filter| [name] + filter }
-  end
-
-  test "find Job with script version range" do
-    get :index, params: {
-      filters: [["repository", "=", "active/foo"],
-                ["script", "=", "hash"],
-                ["script_version", "in git", "tag1"]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "find Job with script version range exclusions" do
-    get :index, params: {
-      filters: [["repository", "=", "active/foo"],
-                ["script", "=", "hash"],
-                ["script_version", "not in git", "tag1"]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "find Job with Docker image range" do
-    get :index, params: {
-      filters: [["docker_image_locator", "in docker",
-                 "arvados/apitestfixture"]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "find Job with Docker image using reader tokens" do
-    authorize_with :inactive
-    get(:index, params: {
-          filters: [["docker_image_locator", "in docker",
-                     "arvados/apitestfixture"]],
-          reader_tokens: [api_token(:active)],
-        })
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "'in docker' filter accepts arrays" do
-    get :index, params: {
-      filters: [["docker_image_locator", "in docker",
-                ["_nonesuchname_", "arvados/apitestfixture"]]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-  end
-
-  test "'not in docker' filter accepts arrays" do
-    get :index, params: {
-      filters: [["docker_image_locator", "not in docker",
-                ["_nonesuchname_", "arvados/apitestfixture"]]]
-    }
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    assert_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_job_run).uuid)
-    refute_includes(assigns(:objects).map { |job| job.uuid },
-                    jobs(:previous_docker_job_run).uuid)
-  end
-
-end
diff --git a/services/api/test/functional/arvados/v1/job_tasks_controller_test.rb b/services/api/test/functional/arvados/v1/job_tasks_controller_test.rb
deleted file mode 100644 (file)
index d6f4347..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::JobTasksControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/jobs_controller_test.rb b/services/api/test/functional/arvados/v1/jobs_controller_test.rb
deleted file mode 100644 (file)
index 9298f23..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-class Arvados::V1::JobsControllerTest < ActionController::TestCase
-
-  test "search jobs by uuid with >= query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '>=', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal false, !!found.index('zzzzz-8i9sb-4cf0nhn6xte809j')
-  end
-
-  test "search jobs by uuid with <= query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '<=', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal true, !!found.index('zzzzz-8i9sb-4cf0nhn6xte809j')
-  end
-
-  test "search jobs by uuid with >= and <= query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '>=', 'zzzzz-8i9sb-pshmckwoma9plh7'],
-              ['uuid', '<=', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal found, ['zzzzz-8i9sb-pshmckwoma9plh7']
-  end
-
-  test "search jobs by uuid with < query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', '<', 'zzzzz-8i9sb-pshmckwoma9plh7']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal false, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal true, !!found.index('zzzzz-8i9sb-4cf0nhn6xte809j')
-  end
-
-  test "search jobs by uuid with like query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', 'like', '%hmckwoma9pl%']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal found, ['zzzzz-8i9sb-pshmckwoma9plh7']
-  end
-
-  test "search jobs by uuid with 'in' query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', 'in', ['zzzzz-8i9sb-4cf0nhn6xte809j',
-                                'zzzzz-8i9sb-pshmckwoma9plh7']]]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal found.sort, ['zzzzz-8i9sb-4cf0nhn6xte809j',
-                              'zzzzz-8i9sb-pshmckwoma9plh7']
-  end
-
-  test "search jobs by uuid with 'not in' query" do
-    exclude_uuids = [jobs(:running).uuid,
-                     jobs(:running_cancelled).uuid]
-    authorize_with :active
-    get :index, params: {
-      filters: [['uuid', 'not in', exclude_uuids]]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_not_empty found, "'not in' query returned nothing"
-    assert_empty(found & exclude_uuids,
-                 "'not in' query returned uuids I asked not to get")
-  end
-
-  ['=', '!='].each do |operator|
-    [['uuid', 'zzzzz-8i9sb-pshmckwoma9plh7'],
-     ['output', nil]].each do |attr, operand|
-      test "search jobs with #{attr} #{operator} #{operand.inspect} query" do
-        authorize_with :active
-        get :index, params: {
-          filters: [[attr, operator, operand]]
-        }
-        assert_response :success
-        values = assigns(:objects).collect { |x| x.send(attr) }
-        assert_not_empty values, "query should return non-empty result"
-        if operator == '='
-          assert_empty values - [operand], "query results do not satisfy query"
-        else
-          assert_empty values & [operand], "query results do not satisfy query"
-        end
-      end
-    end
-  end
-
-  test "search jobs by started_at with < query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '<', Time.now.to_s]]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-  end
-
-  test "search jobs by started_at with > query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '>', Time.now.to_s]]
-    }
-    assert_response :success
-    assert_equal 0, assigns(:objects).count
-  end
-
-  test "search jobs by started_at with >= query on metric date" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '>=', '2014-01-01']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-  end
-
-  test "search jobs by started_at with >= query on metric date and time" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['started_at', '>=', '2014-01-01 01:23:45']]
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-  end
-
-  test "search jobs with 'any' operator" do
-    authorize_with :active
-    get :index, params: {
-      where: { any: ['contains', 'pshmckw'] }
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal 0, found.index('zzzzz-8i9sb-pshmckwoma9plh7')
-    assert_equal 1, found.count
-  end
-
-  test "search jobs by nonexistent column with < query" do
-    authorize_with :active
-    get :index, params: {
-      filters: [['is_borked', '<', 'fizzbuzz']]
-    }
-    assert_response 422
-  end
-
-  [:spectator, :admin].each_with_index do |which_token, i|
-    test "get job queue as #{which_token} user" do
-      authorize_with which_token
-      get :queue
-      assert_response :success
-      assert_equal 0, assigns(:objects).count
-    end
-  end
-
-  test "job includes assigned nodes" do
-    authorize_with :active
-    get :show, params: {id: jobs(:nearly_finished_job).uuid}
-    assert_response :success
-    assert_equal([nodes(:busy).uuid], json_response["node_uuids"])
-  end
-
-  test 'get job with components' do
-    authorize_with :active
-    get :show, params: {id: jobs(:running_job_with_components).uuid}
-    assert_response :success
-    assert_not_nil json_response["components"]
-    assert_equal ["component1", "component2"], json_response["components"].keys
-  end
-end
diff --git a/services/api/test/functional/arvados/v1/keep_disks_controller_test.rb b/services/api/test/functional/arvados/v1/keep_disks_controller_test.rb
deleted file mode 100644 (file)
index 9da9d01..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::KeepDisksControllerTest < ActionController::TestCase
-
-  def default_ping_opts
-    {ping_secret: '', service_ssl_flag: false, service_port: 1234}
-  end
-
-  test "add keep disk with admin token" do
-    authorize_with :admin
-    post :ping, params: default_ping_opts.
-      merge(filesystem_uuid: 'eb1e77a1-db84-4193-b6e6-ca2894f67d5f')
-    assert_response :success
-    assert_not_nil assigns(:object)
-    new_keep_disk = JSON.parse(@response.body)
-    assert_not_nil new_keep_disk['uuid']
-    assert_not_nil new_keep_disk['ping_secret']
-    assert_not_equal '', new_keep_disk['ping_secret']
-  end
-
-  [
-    {},
-    {filesystem_uuid: ''},
-  ].each do |opts|
-    test "add keep disk with[out] filesystem_uuid #{opts}" do
-      authorize_with :admin
-      post :ping, params: default_ping_opts.merge(opts)
-      assert_response :success
-      assert_not_nil JSON.parse(@response.body)['uuid']
-    end
-  end
-
-  test "refuse to add keep disk without admin token" do
-    post :ping, params: default_ping_opts
-    assert_response 404
-  end
-
-  test "ping keep disk" do
-    post :ping, params: default_ping_opts.
-      merge(id: keep_disks(:nonfull).uuid,
-            ping_secret: keep_disks(:nonfull).ping_secret,
-            filesystem_uuid: keep_disks(:nonfull).filesystem_uuid)
-    assert_response :success
-    assert_not_nil assigns(:object)
-    keep_disk = JSON.parse(@response.body)
-    assert_not_nil keep_disk['uuid']
-    assert_not_nil keep_disk['ping_secret']
-  end
-
-  test "admin should get index with ping_secret" do
-    authorize_with :admin
-    get :index
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, items.size
-    assert_not_nil items[0]['ping_secret']
-  end
-
-  # inactive user sees keep disks
-  test "inactive user should get index" do
-    authorize_with :inactive
-    get :index
-    assert_response :success
-    items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, items.size
-
-    # Check these are still included
-    assert items[0]['service_host']
-    assert items[0]['service_port']
-  end
-
-  # active user sees non-secret attributes of keep disks
-  test "active user should get non-empty index with no ping_secret" do
-    authorize_with :active
-    get :index
-    assert_response :success
-    items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, items.size
-    items.each do |item|
-      assert_nil item['ping_secret']
-      assert_not_nil item['is_readable']
-      assert_not_nil item['is_writable']
-      assert_not_nil item['service_host']
-      assert_not_nil item['service_port']
-    end
-  end
-
-  test "search keep_services with 'any' operator" do
-    authorize_with :active
-    get :index, params: {
-      where: { any: ['contains', 'o2t1q5w'] }
-    }
-    assert_response :success
-    found = assigns(:objects).collect(&:uuid)
-    assert_equal true, !!found.index('zzzzz-penuu-5w2o2t1q5wy7fhn')
-  end
-end
diff --git a/services/api/test/functional/arvados/v1/nodes_controller_test.rb b/services/api/test/functional/arvados/v1/nodes_controller_test.rb
deleted file mode 100644 (file)
index 47f6c5f..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::NodesControllerTest < ActionController::TestCase
-
-  test "should get index with ping_secret" do
-    authorize_with :admin
-    get :index
-    assert_response :success
-    assert_not_nil assigns(:objects)
-    node_items = JSON.parse(@response.body)['items']
-    assert_not_equal 0, node_items.size
-    assert_not_nil node_items[0]['info'].andand['ping_secret']
-  end
-
-  # inactive user does not see any nodes
-  test "inactive user should get empty index" do
-    authorize_with :inactive
-    get :index
-    assert_response :success
-    assert_equal 0, json_response['items'].size
-    assert_equal 0, json_response['items_available']
-  end
-
-  # active user sees non-secret attributes of up and recently-up nodes
-  test "active user should get non-empty index with no ping_secret" do
-    authorize_with :active
-    get :index
-    assert_response :success
-    assert_operator 0, :<, json_response['items_available']
-    node_items = json_response['items']
-    assert_operator 0, :<, node_items.size
-    found_busy_node = false
-    node_items.each do |node|
-      assert_nil node['info'].andand['ping_secret']
-      assert_not_nil node['crunch_worker_state']
-      if node['uuid'] == nodes(:busy).uuid
-        found_busy_node = true
-        assert_equal 'busy', node['crunch_worker_state']
-      end
-    end
-    assert_equal true, found_busy_node
-  end
-
-  test "node should ping with ping_secret and no token" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-2z3mc76g2q73aio',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.174',
-      ping_secret: '69udawxvn3zzj45hs8bumvndricrha4lcpi23pd69e44soanc0'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-2z3mc76g2q73aio', response['uuid']
-    # Ensure we are getting the "superuser" attributes, too
-    assert_not_nil response['first_ping_at'], '"first_ping_at" attr missing'
-    assert_not_nil response['info'], '"info" attr missing'
-    assert_not_nil response['nameservers'], '"nameservers" attr missing'
-  end
-
-  test "node should fail ping with invalid ping_secret" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-2z3mc76g2q73aio',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.174',
-      ping_secret: 'dricrha4lcpi23pd69e44soanc069udawxvn3zzj45hs8bumvn'
-    }
-    assert_response 401
-  end
-
-  test "create node" do
-    authorize_with :admin
-    post :create, params: {node: {}}
-    assert_response :success
-    assert_not_nil json_response['uuid']
-    assert_not_nil json_response['info'].is_a? Hash
-    assert_not_nil json_response['info']['ping_secret']
-    assert_nil json_response['slot_number']
-    assert_nil json_response['hostname']
-  end
-
-  test "create node and assign slot" do
-    authorize_with :admin
-    post :create, params: {node: {}, assign_slot: true}
-    assert_response :success
-    assert_not_nil json_response['uuid']
-    assert_not_nil json_response['info'].is_a? Hash
-    assert_not_nil json_response['info']['ping_secret']
-    assert_operator 0, :<, json_response['slot_number']
-    n = json_response['slot_number']
-    assert_equal "compute#{n}", json_response['hostname']
-
-    node = Node.where(uuid: json_response['uuid']).first
-    assert_equal n, node.slot_number
-    assert_equal "compute#{n}", node.hostname
-  end
-
-  test "update node and assign slot" do
-    authorize_with :admin
-    node = nodes(:new_with_no_hostname)
-    post :update, params: {id: node.uuid, node: {}, assign_slot: true}
-    assert_response :success
-    assert_operator 0, :<, json_response['slot_number']
-    n = json_response['slot_number']
-    assert_equal "compute#{n}", json_response['hostname']
-
-    node.reload
-    assert_equal n, node.slot_number
-    assert_equal "compute#{n}", node.hostname
-  end
-
-  test "update node and assign slot, don't clobber hostname" do
-    authorize_with :admin
-    node = nodes(:new_with_custom_hostname)
-    post :update, params: {id: node.uuid, node: {}, assign_slot: true}
-    assert_response :success
-    assert_operator 0, :<, json_response['slot_number']
-    n = json_response['slot_number']
-    assert_equal "custom1", json_response['hostname']
-  end
-
-  test "ping adds node stats to info" do
-    authorize_with :admin
-    node = nodes(:idle)
-    post :ping, params: {
-      id: node.uuid,
-      ping_secret: node.info['ping_secret'],
-      total_cpu_cores: 32,
-      total_ram_mb: 1024,
-      total_scratch_mb: 2048
-    }
-    assert_response :success
-    info = JSON.parse(@response.body)['info']
-    properties = JSON.parse(@response.body)['properties']
-    assert_equal(node.info['ping_secret'], info['ping_secret'])
-    assert_equal(32, properties['total_cpu_cores'].to_i)
-    assert_equal(1024, properties['total_ram_mb'].to_i)
-    assert_equal(2048, properties['total_scratch_mb'].to_i)
-  end
-
-  test "active user can see their assigned job" do
-    authorize_with :active
-    get :show, params: {id: nodes(:busy).uuid}
-    assert_response :success
-    assert_equal(jobs(:nearly_finished_job).uuid, json_response["job_uuid"])
-  end
-
-  test "user without job read permission can't see job" do
-    authorize_with :spectator
-    get :show, params: {id: nodes(:busy).uuid}
-    assert_response :success
-    assert_nil(json_response["job"], "spectator can see node's assigned job")
-  end
-
-  [:admin, :spectator].each do |user|
-    test "select param does not break node list for #{user}" do
-      authorize_with user
-      get :index, params: {select: ['domain']}
-      assert_response :success
-      assert_operator 0, :<, json_response['items_available']
-    end
-  end
-
-  test "admin can associate a job with a node" do
-    changed_node = nodes(:idle)
-    assigned_job = jobs(:queued)
-    authorize_with :admin
-    post :update, params: {
-      id: changed_node.uuid,
-      node: {job_uuid: assigned_job.uuid},
-    }
-    assert_response :success
-    assert_equal(changed_node.hostname, json_response["hostname"],
-                 "hostname mismatch after defining job")
-    assert_equal(assigned_job.uuid, json_response["job_uuid"],
-                 "mismatch in node's assigned job UUID")
-  end
-
-  test "non-admin can't associate a job with a node" do
-    authorize_with :active
-    post :update, params: {
-      id: nodes(:idle).uuid,
-      node: {job_uuid: jobs(:queued).uuid},
-    }
-    assert_response 403
-  end
-
-  test "admin can unassign a job from a node" do
-    changed_node = nodes(:busy)
-    authorize_with :admin
-    post :update, params: {
-      id: changed_node.uuid,
-      node: {job_uuid: nil},
-    }
-    assert_response :success
-    assert_equal(changed_node.hostname, json_response["hostname"],
-                 "hostname mismatch after defining job")
-    assert_nil(json_response["job_uuid"],
-               "node still has job assignment after update")
-  end
-
-  test "non-admin can't unassign a job from a node" do
-    authorize_with :project_viewer
-    post :update, params: {
-      id: nodes(:busy).uuid,
-      node: {job_uuid: nil},
-    }
-    assert_response 403
-  end
-
-  test "node should fail ping with invalid hostname config format" do
-    Rails.configuration.Containers.SLURM.Managed.AssignNodeHostname = 'compute%<slot_number>04'  # should end with "04d"
-    post :ping, params: {
-      id: nodes(:new_with_no_hostname).uuid,
-      ping_secret: nodes(:new_with_no_hostname).info['ping_secret'],
-    }
-    assert_response 422
-  end
-
-  test "first ping should set ip addr using local_ipv4 when provided" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-nodenoipaddryet',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.172',
-      ping_secret: 'abcdyefg4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-nodenoipaddryet', response['uuid']
-    assert_equal '172.17.2.172', response['ip_address']
-  end
-
-  test "first ping should set ip addr using remote_ip when local_ipv4 is not provided" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-nodenoipaddryet',
-      instance_id: 'i-0000000',
-      ping_secret: 'abcdyefg4lb5q4gzqqtrnq30oyj08r8dtdimmanbqw49z1anz2'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-nodenoipaddryet', response['uuid']
-    assert_equal request.remote_ip, response['ip_address']
-  end
-
-  test "future pings should not change previous ip address" do
-    post :ping, params: {
-      id: 'zzzzz-7ekkf-2z3mc76g2q73aio',
-      instance_id: 'i-0000000',
-      local_ipv4: '172.17.2.175',
-      ping_secret: '69udawxvn3zzj45hs8bumvndricrha4lcpi23pd69e44soanc0'
-    }
-    assert_response :success
-    response = JSON.parse(@response.body)
-    assert_equal 'zzzzz-7ekkf-2z3mc76g2q73aio', response['uuid']
-    assert_equal '172.17.2.174', response['ip_address']   # original ip address is not overwritten
-  end
-end
diff --git a/services/api/test/functional/arvados/v1/pipeline_instances_controller_test.rb b/services/api/test/functional/arvados/v1/pipeline_instances_controller_test.rb
deleted file mode 100644 (file)
index e455354..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::PipelineInstancesControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/pipeline_templates_controller_test.rb b/services/api/test/functional/arvados/v1/pipeline_templates_controller_test.rb
deleted file mode 100644 (file)
index 992749c..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::PipelineTemplatesControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/repositories_controller_test.rb b/services/api/test/functional/arvados/v1/repositories_controller_test.rb
deleted file mode 100644 (file)
index 84bd846..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::RepositoriesControllerTest < ActionController::TestCase
-  test "should get_all_logins with admin token" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-  end
-
-  test "should get_all_logins with non-admin token" do
-    authorize_with :active
-    get :get_all_permissions
-    assert_response 403
-  end
-
-  test "get_all_permissions gives RW to repository owner" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    ok = false
-    json_response['repositories'].each do |repo|
-      if repo['uuid'] == repositories(:repository2).uuid
-        if repo['user_permissions'][users(:active).uuid]['can_write']
-          ok = true
-        end
-      end
-    end
-    assert_equal(true, ok,
-                 "No permission on own repo '@{repositories(:repository2).uuid}'")
-  end
-
-  test "get_all_permissions takes into account is_admin flag" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    json_response['repositories'].each do |repo|
-      assert_not_nil(repo['user_permissions'][users(:admin).uuid],
-                     "Admin user is not listed in perms for #{repo['uuid']}")
-      assert_equal(true,
-                   repo['user_permissions'][users(:admin).uuid]['can_write'],
-                   "Admin has no perms for #{repo['uuid']}")
-    end
-  end
-
-  test "get_all_permissions takes into account is_active flag" do
-    act_as_user users(:active) do
-      Repository.create! name: 'active/testrepo'
-    end
-    act_as_system_user do
-      u = users(:active)
-      u.unsetup
-      u.save!
-    end
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    json_response['repositories'].each do |r|
-      r['user_permissions'].each do |user_uuid, perms|
-        refute_equal user_uuid, users(:active).uuid
-      end
-    end
-  end
-
-  test "get_all_permissions does not give any access to user without permission" do
-    viewer_uuid = users(:project_viewer).uuid
-    assert_equal(authorized_keys(:project_viewer).authorized_user_uuid,
-                 viewer_uuid,
-                 "project_viewer must have an authorized_key for this test to work")
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    readable_repos = json_response["repositories"].select do |repo|
-      repo["user_permissions"].has_key?(viewer_uuid)
-    end
-    assert_equal(["arvados"], readable_repos.map { |r| r["name"] },
-                 "project_viewer should only have permissions on public repos")
-  end
-
-  test "get_all_permissions gives gitolite R to user with read-only access" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    found_it = false
-    assert_equal(authorized_keys(:spectator).authorized_user_uuid,
-                 users(:spectator).uuid,
-                 "spectator must have an authorized_key for this test to work")
-    json_response['repositories'].each do |repo|
-      next unless repo['uuid'] == repositories(:foo).uuid
-      assert_equal('R',
-                   repo['user_permissions'][users(:spectator).uuid]['gitolite_permissions'],
-                   "spectator user should have just R access to #{repo['uuid']}")
-      found_it = true
-    end
-    assert_equal true, found_it, "spectator user does not have R on foo repo"
-  end
-
-  test "get_all_permissions provides admin and active user keys" do
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    [:active, :admin].each do |u|
-      assert_equal(1, json_response['user_keys'][users(u).uuid].andand.count,
-                   "expected 1 key for #{u} (#{users(u).uuid})")
-      assert_equal(json_response['user_keys'][users(u).uuid][0]['public_key'],
-                   authorized_keys(u).public_key,
-                   "response public_key does not match fixture #{u}.")
-    end
-  end
-
-  test "get_all_permissions lists all repos regardless of permissions" do
-    act_as_system_user do
-      # Create repos that could potentially be left out of the
-      # permission list by accident.
-
-      # No authorized_key, no username (this can't even be done
-      # without skipping validations)
-      r = Repository.create name: 'root/testrepo'
-      assert r.save validate: false
-
-      r = Repository.create name: 'invalid username / repo name', owner_uuid: users(:inactive).uuid
-      assert r.save validate: false
-    end
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    assert_equal(Repository.count, json_response["repositories"].size)
-  end
-
-  test "get_all_permissions lists user permissions for users with no authorized keys" do
-    authorize_with :admin
-    AuthorizedKey.destroy_all
-    get :get_all_permissions
-    assert_response :success
-    assert_equal(Repository.count, json_response["repositories"].size)
-    repos_with_perms = []
-    json_response['repositories'].each do |repo|
-      if repo['user_permissions'].any?
-        repos_with_perms << repo['uuid']
-      end
-    end
-    assert_not_empty repos_with_perms, 'permissions are missing'
-  end
-
-  # Ensure get_all_permissions correctly describes what the normal
-  # permission system would do.
-  test "get_all_permissions obeys group permissions" do
-    act_as_user system_user do
-      r = Repository.create!(name: 'admin/groupcanwrite', owner_uuid: users(:admin).uuid)
-      g = Group.create!(group_class: 'role', name: 'repo-writers')
-      u1 = users(:active)
-      u2 = users(:spectator)
-      Link.create!(tail_uuid: g.uuid, head_uuid: r.uuid, link_class: 'permission', name: 'can_manage')
-      Link.create!(tail_uuid: u1.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_write')
-      Link.create!(tail_uuid: u2.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_read')
-
-      r = Repository.create!(name: 'admin/groupreadonly', owner_uuid: users(:admin).uuid)
-      g = Group.create!(group_class: 'role', name: 'repo-readers')
-      u1 = users(:active)
-      u2 = users(:spectator)
-      Link.create!(tail_uuid: g.uuid, head_uuid: r.uuid, link_class: 'permission', name: 'can_read')
-      Link.create!(tail_uuid: u1.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_write')
-      Link.create!(tail_uuid: u2.uuid, head_uuid: g.uuid, link_class: 'permission', name: 'can_read')
-    end
-    authorize_with :admin
-    get :get_all_permissions
-    assert_response :success
-    json_response['repositories'].each do |repo|
-      repo['user_permissions'].each do |user_uuid, perms|
-        u = User.find_by_uuid(user_uuid)
-        if perms['can_read']
-          assert u.can? read: repo['uuid']
-          assert_match(/R/, perms['gitolite_permissions'])
-        else
-          refute_match(/R/, perms['gitolite_permissions'])
-        end
-        if perms['can_write']
-          assert u.can? write: repo['uuid']
-          assert_match(/RW\+/, perms['gitolite_permissions'])
-        else
-          refute_match(/W/, perms['gitolite_permissions'])
-        end
-        if perms['can_manage']
-          assert u.can? manage: repo['uuid']
-          assert_match(/RW\+/, perms['gitolite_permissions'])
-        end
-      end
-    end
-  end
-
-  test "default index includes fetch_url" do
-    authorize_with :active
-    get(:index)
-    assert_response :success
-    assert_includes(json_response["items"].map { |r| r["fetch_url"] },
-                    "git@git.zzzzz.arvadosapi.com:active/foo.git")
-  end
-
-  [
-    {cfg: "GitSSH.ExternalURL", cfgval: URI("ssh://git@example.com"), match: %r"^git@example.com:"},
-    {cfg: "GitSSH.ExternalURL", cfgval: URI(""), match: %r"^git@git.zzzzz.arvadosapi.com:"},
-    {cfg: "GitSSH", cfgval: false, refute: /^git@/ },
-    {cfg: "GitHTTP.ExternalURL", cfgval: URI("https://example.com/"), match: %r"^https://example.com/"},
-    {cfg: "GitHTTP.ExternalURL", cfgval: URI(""), match: %r"^https://git.zzzzz.arvadosapi.com/"},
-    {cfg: "GitHTTP", cfgval: false, refute: /^http/ },
-  ].each do |expect|
-    test "set #{expect[:cfg]} to #{expect[:cfgval]}" do
-      ConfigLoader.set_cfg Rails.configuration.Services, expect[:cfg].to_s, expect[:cfgval]
-      authorize_with :active
-      get :index
-      assert_response :success
-      assert_not_empty json_response['items']
-      json_response['items'].each do |r|
-        if expect[:refute]
-          r['clone_urls'].each do |u|
-            refute_match expect[:refute], u
-          end
-        else
-          assert((r['clone_urls'].any? do |u|
-                    expect[:match].match u
-                  end),
-                 "no match for #{expect[:match]} in #{r['clone_urls'].inspect}")
-        end
-      end
-    end
-  end
-
-  test "select push_url in index" do
-    authorize_with :active
-    get(:index, params: {select: ["uuid", "push_url"]})
-    assert_response :success
-    assert_includes(json_response["items"].map { |r| r["push_url"] },
-                    "git@git.zzzzz.arvadosapi.com:active/foo.git")
-  end
-
-  test "select clone_urls in index" do
-    authorize_with :active
-    get(:index, params: {select: ["uuid", "clone_urls"]})
-    assert_response :success
-    assert_includes(json_response["items"].map { |r| r["clone_urls"] }.flatten,
-                    "git@git.zzzzz.arvadosapi.com:active/foo.git")
-  end
-end
index 65a2b64b8a4be509aee4eba716e310477d88a43b..39b0cfe8f99c5c48cf4136452674fd8ccd6bf2f5 100644 (file)
@@ -60,16 +60,16 @@ class Arvados::V1::SchemaControllerTest < ActionController::TestCase
     assert_response :success
     discovery_doc = JSON.parse(@response.body)
     assert_equal('POST',
     assert_response :success
     discovery_doc = JSON.parse(@response.body)
     assert_equal('POST',
-                 discovery_doc['resources']['jobs']['methods']['create']['httpMethod'])
+                 discovery_doc['resources']['collections']['methods']['create']['httpMethod'])
   end
 
   test "non-empty disable_api_methods" do
     Rails.configuration.API.DisabledAPIs = ConfigLoader.to_OrderedOptions(
   end
 
   test "non-empty disable_api_methods" do
     Rails.configuration.API.DisabledAPIs = ConfigLoader.to_OrderedOptions(
-      {'jobs.create'=>{}, 'pipeline_instances.create'=>{}, 'pipeline_templates.create'=>{}})
+      {'collections.create'=>{}, 'workflows.create'=>{}})
     get :index
     assert_response :success
     discovery_doc = JSON.parse(@response.body)
     get :index
     assert_response :success
     discovery_doc = JSON.parse(@response.body)
-    ['jobs', 'pipeline_instances', 'pipeline_templates'].each do |r|
+    ['collections', 'workflows'].each do |r|
       refute_includes(discovery_doc['resources'][r]['methods'].keys(), 'create')
     end
   end
       refute_includes(discovery_doc['resources'][r]['methods'].keys(), 'create')
     end
   end
@@ -97,10 +97,10 @@ class Arvados::V1::SchemaControllerTest < ActionController::TestCase
 
     discovery_doc = JSON.parse(@response.body)
 
 
     discovery_doc = JSON.parse(@response.body)
 
-    specimens_index_params = discovery_doc['resources']['specimens']['methods']['index']['parameters']  # no changes from super
+    workflows_index_params = discovery_doc['resources']['workflows']['methods']['index']['parameters']  # no changes from super
     coll_index_params = discovery_doc['resources']['collections']['methods']['index']['parameters']
 
     coll_index_params = discovery_doc['resources']['collections']['methods']['index']['parameters']
 
-    assert_equal (specimens_index_params.keys + ['include_trash', 'include_old_versions']).sort, coll_index_params.keys.sort
+    assert_equal (workflows_index_params.keys + ['include_trash', 'include_old_versions']).sort, coll_index_params.keys.sort
 
     include_trash_param = coll_index_params['include_trash']
     assert_equal 'boolean', include_trash_param['type']
 
     include_trash_param = coll_index_params['include_trash']
     assert_equal 'boolean', include_trash_param['type']
diff --git a/services/api/test/functional/arvados/v1/specimens_controller_test.rb b/services/api/test/functional/arvados/v1/specimens_controller_test.rb
deleted file mode 100644 (file)
index df681e6..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::SpecimensControllerTest < ActionController::TestCase
-end
diff --git a/services/api/test/functional/arvados/v1/traits_controller_test.rb b/services/api/test/functional/arvados/v1/traits_controller_test.rb
deleted file mode 100644 (file)
index 3c8d097..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class Arvados::V1::TraitsControllerTest < ActionController::TestCase
-end
index cc0b5e1320988b1098f698528fb6e892f4b11ea1..2b643d4b27b6723d599ddf38f66b4e2d453778ab 100644 (file)
@@ -121,12 +121,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_nil updated['username'], 'expected no username'
   end
 
     assert_nil updated['username'], 'expected no username'
   end
 
-  test "create user with user, vm and repo as input" do
+  test "create user with user and vm as input" do
     authorize_with :admin
     authorize_with :admin
-    repo_name = 'usertestrepo'
 
     post :setup, params: {
 
     post :setup, params: {
-      repo_name: repo_name,
       user: {
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
         first_name: "in_create_test_first_name",
       user: {
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
         first_name: "in_create_test_first_name",
@@ -145,11 +143,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # repo link and link add user to 'All users' group
-    verify_links_added 3
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        "foo/#{repo_name}", created['uuid'], 'arvados#repository', true, 'Repository'
+    # added links: vm permission, 'all users' group
+    verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
@@ -165,7 +160,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       uuid: 'bogus_uuid',
 
     post :setup, params: {
       uuid: 'bogus_uuid',
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid
     }
     response_body = JSON.parse(@response.body)
       vm_uuid: @vm_uuid
     }
     response_body = JSON.parse(@response.body)
@@ -179,7 +173,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       user: {uuid: 'bogus_uuid'},
 
     post :setup, params: {
       user: {uuid: 'bogus_uuid'},
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
@@ -193,7 +186,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     authorize_with :admin
 
     post :setup, params: {
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
@@ -208,7 +200,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     post :setup, params: {
       user: {},
 
     post :setup, params: {
       user: {},
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
       vm_uuid: @vm_uuid,
     }
     response_body = JSON.parse(@response.body)
@@ -218,13 +209,12 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         'Expected ArgumentError'
   end
 
         'Expected ArgumentError'
   end
 
-  test "invoke setup with existing uuid, vm and repo and verify links" do
+  test "invoke setup with existing uuid and vm permission, and verify links" do
     authorize_with :admin
     inactive_user = users(:inactive)
 
     post :setup, params: {
       uuid: users(:inactive).uuid,
     authorize_with :admin
     inactive_user = users(:inactive)
 
     post :setup, params: {
       uuid: users(:inactive).uuid,
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid
     }
 
       vm_uuid: @vm_uuid
     }
 
@@ -238,10 +228,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal inactive_user['email'], resp_obj['email'],
         'expecting inactive user email'
 
     assert_equal inactive_user['email'], resp_obj['email'],
         'expecting inactive user email'
 
-    # expect repo and vm links
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'inactiveuser/usertestrepo', resp_obj['uuid'], 'arvados#repository', true, 'Repository'
-
+    # expect vm permission link
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         @vm_uuid, resp_obj['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         @vm_uuid, resp_obj['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
@@ -266,7 +253,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         'expecting inactive user email'
   end
 
         'expecting inactive user email'
   end
 
-  test "setup user with valid email and repo as input" do
+  test "setup user with valid email and repo(ignored) as input" do
     authorize_with :admin
 
     post :setup, params: {
     authorize_with :admin
 
     post :setup, params: {
@@ -280,15 +267,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
-    # three extra links; system_group, group and repo perms
-    verify_links_added 3
+    # added links: system_group, 'all users' group.
+    verify_links_added 2
   end
 
   test "setup user with fake vm and expect error" do
     authorize_with :admin
 
     post :setup, params: {
   end
 
   test "setup user with fake vm and expect error" do
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: 'no_such_vm',
       user: {email: 'foo@example.com'},
     }
       vm_uuid: 'no_such_vm',
       user: {email: 'foo@example.com'},
     }
@@ -300,11 +286,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
           'Expected RuntimeError: No vm found for no_such_vm'
   end
 
           'Expected RuntimeError: No vm found for no_such_vm'
   end
 
-  test "setup user with valid email, repo and real vm as input" do
+  test "setup user with valid email and real vm as input" do
     authorize_with :admin
 
     post :setup, params: {
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {email: 'foo@example.com'}
     }
       vm_uuid: @vm_uuid,
       user: {email: 'foo@example.com'}
     }
@@ -315,8 +300,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
     assert_not_nil response_object['uuid'], 'expected uuid for the new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
-    # four extra links; system_group, group, vm, repo
-    verify_links_added 4
+    # added links; system_group, 'all users' group, vm.
+    verify_links_added 3
   end
 
   test "setup user with valid email, no vm and no repo as input" do
   end
 
   test "setup user with valid email, no vm and no repo as input" do
@@ -332,24 +317,20 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil response_object['uuid'], 'expected uuid for new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
     assert_not_nil response_object['uuid'], 'expected uuid for new user'
     assert_equal response_object['email'], 'foo@example.com', 'expected given email'
 
-    # two extra links; system_group, and group
+    # added links; system_group, 'all users' group.
     verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', response_object['uuid'], 'arvados#group', true, 'Group'
 
     verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', response_object['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', false, 'permission', 'can_manage',
-        'foo/usertestrepo', response_object['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, response_object['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, response_object['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
-  test "setup user with email, first name, repo name and vm uuid" do
+  test "setup user with email, first name, and vm uuid" do
     authorize_with :admin
 
     post :setup, params: {
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       vm_uuid: @vm_uuid,
       user: {
         first_name: 'test_first_name',
       vm_uuid: @vm_uuid,
       user: {
         first_name: 'test_first_name',
@@ -365,8 +346,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_equal 'test_first_name', response_object['first_name'],
         'expecting first name'
 
     assert_equal 'test_first_name', response_object['first_name'],
         'expecting first name'
 
-    # four extra links; system_group, group, repo and vm
-    verify_links_added 4
+    # added links: system_group, 'all users' group, vm.
+    verify_links_added 3
   end
 
   test "setup user with an existing user email and check different object is created" do
   end
 
   test "setup user with an existing user email and check different object is created" do
@@ -374,7 +355,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     inactive_user = users(:inactive)
 
     post :setup, params: {
     inactive_user = users(:inactive)
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       user: {
         email: inactive_user['email']
       }
       user: {
         email: inactive_user['email']
       }
@@ -387,15 +367,14 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_equal response_object['uuid'], inactive_user['uuid'],
         'expected different uuid after create operation'
     assert_equal inactive_user['email'], response_object['email'], 'expected given email'
     assert_not_equal response_object['uuid'], inactive_user['uuid'],
         'expected different uuid after create operation'
     assert_equal inactive_user['email'], response_object['email'], 'expected given email'
-    # system_group, group, and repo. No vm link.
-    verify_links_added 3
+    # added links: system_group, 'all users' group.
+    verify_links_added 2
   end
 
   test "setup user with openid prefix" do
     authorize_with :admin
 
     post :setup, params: {
   end
 
   test "setup user with openid prefix" do
     authorize_with :admin
 
     post :setup, params: {
-      repo_name: 'usertestrepo',
       user: {
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
       user: {
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
@@ -413,12 +392,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # verify links
-    # three new links: system_group, repo, and 'All users' group.
-    verify_links_added 3
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
+    # added links: system_group, 'all users' group.
+    verify_links_added 2
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
@@ -427,7 +402,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
 
-  test "setup user with user, vm and repo and verify links" do
+  test "setup user with user and vm, and verify links" do
     authorize_with :admin
 
     post :setup, params: {
     authorize_with :admin
 
     post :setup, params: {
@@ -437,7 +412,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
         email: "foo@example.com"
       },
       vm_uuid: @vm_uuid,
         email: "foo@example.com"
       },
       vm_uuid: @vm_uuid,
-      repo_name: 'usertestrepo',
     }
 
     assert_response :success
     }
 
     assert_response :success
@@ -450,14 +424,11 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # four new links: system_group, repo, vm and 'All users' group link
-    verify_links_added 4
+    # added links: system_group, 'all users' group, vm
+    verify_links_added 3
 
     # system_group isn't part of the response.  See User#add_system_group_permission_link
 
 
     # system_group isn't part of the response.  See User#add_system_group_permission_link
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
@@ -493,13 +464,11 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
           'Expected Forbidden error'
   end
 
           'Expected Forbidden error'
   end
 
-  test "setup active user with repo and no vm" do
+  test "setup active user with no vm" do
     authorize_with :admin
     active_user = users(:active)
 
     authorize_with :admin
     active_user = users(:active)
 
-    # invoke setup with a repository
     post :setup, params: {
     post :setup, params: {
-      repo_name: 'usertestrepo',
       uuid: active_user['uuid']
     }
 
       uuid: active_user['uuid']
     }
 
@@ -510,13 +479,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
 
     assert_equal active_user[:email], created['email'], 'expected input email'
 
 
     assert_equal active_user[:email], created['email'], 'expected input email'
 
-     # verify links
+    # verify links
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'active/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
@@ -524,13 +490,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
   test "setup active user with vm and no repo" do
     authorize_with :admin
     active_user = users(:active)
   test "setup active user with vm and no repo" do
     authorize_with :admin
     active_user = users(:active)
-    repos_query = Repository.where(owner_uuid: active_user.uuid)
-    repo_link_query = Link.where(tail_uuid: active_user.uuid,
-                                 link_class: "permission", name: "can_manage")
-    repos_count = repos_query.count
-    repo_link_count = repo_link_query.count
 
 
-    # invoke setup with a repository
     post :setup, params: {
       vm_uuid: @vm_uuid,
       uuid: active_user['uuid'],
     post :setup, params: {
       vm_uuid: @vm_uuid,
       uuid: active_user['uuid'],
@@ -548,9 +508,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    assert_equal(repos_count, repos_query.count)
-    assert_equal(repo_link_count, repo_link_query.count)
-
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         @vm_uuid, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         @vm_uuid, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
   end
@@ -655,7 +612,6 @@ The Arvados team.
     authorize_with :admin
     active_user = users(:active)
 
     authorize_with :admin
     active_user = users(:active)
 
-    # invoke setup with a repository
     put :update, params: {
           id: active_user['uuid'],
           user: {
     put :update, params: {
           id: active_user['uuid'],
           user: {
index ef1d0c6d05e6486e40a3e6456eac99f074d7a24f..ea44cbf453f5db44931706f7c30cbd8005a46414 100644 (file)
@@ -40,12 +40,12 @@ class DatabaseControllerTest < ActionController::TestCase
   test "reset succeeds with admin token" do
     new_uuid = nil
     act_as_system_user do
   test "reset succeeds with admin token" do
     new_uuid = nil
     act_as_system_user do
-      new_uuid = Specimen.create.uuid
+      new_uuid = Collection.create.uuid
     end
     end
-    assert_not_empty Specimen.where(uuid: new_uuid)
+    assert_not_empty Collection.where(uuid: new_uuid)
     authorize_with :admin
     post :reset
     assert_response 200
     authorize_with :admin
     post :reset
     assert_response 200
-    assert_empty Specimen.where(uuid: new_uuid)
+    assert_empty Collection.where(uuid: new_uuid)
   end
 end
   end
 end
index e13d702983fe3c372debf3b55bc7b9156e44fe84..ab304c1c7ce94dbd07b3e46981a2957e04093a95 100644 (file)
@@ -96,7 +96,6 @@ class SysControllerTest < ActionController::TestCase
     g_bar = groups(:trashed_subproject)
     g_baz = groups(:trashed_subproject3)
     col = collections(:collection_in_trashed_subproject)
     g_bar = groups(:trashed_subproject)
     g_baz = groups(:trashed_subproject3)
     col = collections(:collection_in_trashed_subproject)
-    job = jobs(:job_in_trashed_project)
     cr = container_requests(:cr_in_trashed_project)
     # Save how many objects were before the sweep
     user_nr_was = User.all.length
     cr = container_requests(:cr_in_trashed_project)
     # Save how many objects were before the sweep
     user_nr_was = User.all.length
@@ -104,12 +103,10 @@ class SysControllerTest < ActionController::TestCase
     group_nr_was = Group.where('group_class<>?', 'project').length
     project_nr_was = Group.where(group_class: 'project').length
     cr_nr_was = ContainerRequest.all.length
     group_nr_was = Group.where('group_class<>?', 'project').length
     project_nr_was = Group.where(group_class: 'project').length
     cr_nr_was = ContainerRequest.all.length
-    job_nr_was = Job.all.length
     assert_not_empty Group.where(uuid: g_foo.uuid)
     assert_not_empty Group.where(uuid: g_bar.uuid)
     assert_not_empty Group.where(uuid: g_baz.uuid)
     assert_not_empty Collection.where(uuid: col.uuid)
     assert_not_empty Group.where(uuid: g_foo.uuid)
     assert_not_empty Group.where(uuid: g_bar.uuid)
     assert_not_empty Group.where(uuid: g_baz.uuid)
     assert_not_empty Collection.where(uuid: col.uuid)
-    assert_not_empty Job.where(uuid: job.uuid)
     assert_not_empty ContainerRequest.where(uuid: cr.uuid)
 
     authorize_with :admin
     assert_not_empty ContainerRequest.where(uuid: cr.uuid)
 
     authorize_with :admin
@@ -120,7 +117,6 @@ class SysControllerTest < ActionController::TestCase
     assert_empty Group.where(uuid: g_bar.uuid)
     assert_empty Group.where(uuid: g_baz.uuid)
     assert_empty Collection.where(uuid: col.uuid)
     assert_empty Group.where(uuid: g_bar.uuid)
     assert_empty Group.where(uuid: g_baz.uuid)
     assert_empty Collection.where(uuid: col.uuid)
-    assert_empty Job.where(uuid: job.uuid)
     assert_empty ContainerRequest.where(uuid: cr.uuid)
     # No unwanted deletions should have happened
     assert_equal user_nr_was, User.all.length
     assert_empty ContainerRequest.where(uuid: cr.uuid)
     # No unwanted deletions should have happened
     assert_equal user_nr_was, User.all.length
@@ -129,7 +125,6 @@ class SysControllerTest < ActionController::TestCase
     assert_equal group_nr_was, Group.where('group_class<>?', 'project').length
     assert_equal project_nr_was-3, Group.where(group_class: 'project').length
     assert_equal cr_nr_was-1, ContainerRequest.all.length
     assert_equal group_nr_was, Group.where('group_class<>?', 'project').length
     assert_equal project_nr_was-3, Group.where(group_class: 'project').length
     assert_equal cr_nr_was-1, ContainerRequest.all.length
-    assert_equal job_nr_was-1, Job.all.length
   end
 
 end
   end
 
 end
diff --git a/services/api/test/helpers/git_test_helper.rb b/services/api/test/helpers/git_test_helper.rb
deleted file mode 100644 (file)
index cb30f68..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'fileutils'
-require 'tmpdir'
-
-# Commit log for "foo" repository in test.git.tar
-# main is the main branch
-# b1 is a branch off of main 
-# tag1 is a tag
-#
-# 1de84a8 * b1
-# 077ba2a * main
-# 4fe459a * tag1
-# 31ce37f * foo
-
-module GitTestHelper
-  def self.included base
-    base.setup do
-      # Extract the test repository data into the default test
-      # environment's Rails.configuration.Git.Repositories. (We
-      # don't use that config setting here, though: it doesn't seem
-      # worth the risk of stepping on a real git repo root.)
-      @tmpdir = Rails.root.join 'tmp', 'git'
-      FileUtils.mkdir_p @tmpdir
-      system("tar", "-xC", @tmpdir.to_s, "-f", "test/test.git.tar")
-      Rails.configuration.Git.Repositories = "#{@tmpdir}/test"
-      Rails.configuration.Containers.JobsAPI.GitInternalDir = "#{@tmpdir}/internal.git"
-    end
-
-    base.teardown do
-      FileUtils.remove_entry CommitsHelper.cache_dir_base, true
-      FileUtils.mkdir_p @tmpdir
-      system("tar", "-xC", @tmpdir.to_s, "-f", "test/test.git.tar")
-    end
-  end
-
-  def internal_tag tag
-    IO.read "|git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape} log --format=format:%H -n1 #{tag.shellescape}"
-  end
-
-  # Intercept fetch_remote_repository and fetch from a specified url
-  # or local fixture instead of the remote url requested. fakeurl can
-  # be a url (probably starting with file:///) or the name of a
-  # fixture (as a symbol)
-  def fetch_remote_from_local_repo url, fakeurl
-    if fakeurl.is_a? Symbol
-      fakeurl = 'file://' + repositories(fakeurl).server_path
-    end
-    CommitsHelper.expects(:fetch_remote_repository).once.with do |gitdir, giturl|
-      if giturl == url
-        CommitsHelper.unstub(:fetch_remote_repository)
-        CommitsHelper.fetch_remote_repository gitdir, fakeurl
-        true
-      end
-    end
-  end
-end
index e106d994cd9c9808e5a73358356e71c59f8855b4..c4dc72d3bac01ae1cda4729540c44847c7936015 100644 (file)
@@ -54,14 +54,10 @@ module UsersTestHelper
     # these don't get added any more!  they shouldn't appear ever.
     assert !oid_login_perms.any?, "expected all oid_login_perms deleted"
 
     # these don't get added any more!  they shouldn't appear ever.
     assert !oid_login_perms.any?, "expected all oid_login_perms deleted"
 
+    # these don't get added any more!  they shouldn't appear ever.
     repo_perms = Link.where(tail_uuid: uuid,
     repo_perms = Link.where(tail_uuid: uuid,
-                            link_class: 'permission',
-                            name: 'can_manage').where("head_uuid like ?", Repository.uuid_like_pattern)
-    if expect_repo_perms
-      assert repo_perms.any?, "expected repo_perms"
-    else
-      assert !repo_perms.any?, "expected all repo_perms deleted"
-    end
+                            link_class: 'permission').where("head_uuid like ?", '_____-s0uqq-_______________')
+    assert !repo_perms.any?, "expected all repo_perms deleted"
 
     vm_login_perms = Link.
       where(tail_uuid: uuid,
 
     vm_login_perms = Link.
       where(tail_uuid: uuid,
index 3b28a3163feef85b026345a39b061db3e9c8372c..83a633764427df0b56840b411bf7b26d49f7430d 100644 (file)
@@ -44,15 +44,15 @@ class ApiTokensScopeTest < ActionDispatch::IntegrationTest
     assert_response 403
    end
 
     assert_response 403
    end
 
-  test "specimens token can see exactly owned specimens" do
-    get_args = {params: {}, headers: auth(:active_specimens)}
-    get(v1_url('specimens'), **get_args)
+  test "collections token can see exactly owned collections" do
+    get_args = {params: {}, headers: auth(:active_all_collections)}
+    get(v1_url('collections'), **get_args)
     assert_response 403
     assert_response 403
-    get(v1_url('specimens', specimens(:owned_by_active_user).uuid), **get_args)
+    get(v1_url('collections', collections(:collection_owned_by_active).uuid), **get_args)
     assert_response :success
     assert_response :success
-    head(v1_url('specimens', specimens(:owned_by_active_user).uuid), **get_args)
+    head(v1_url('collections', collections(:collection_owned_by_active).uuid), **get_args)
     assert_response :success
     assert_response :success
-    get(v1_url('specimens', specimens(:owned_by_spectator).uuid), **get_args)
+    get(v1_url('collections', collections(:collection_owned_by_foo).uuid), **get_args)
     assert_includes(403..404, @response.status)
   end
 
     assert_includes(403..404, @response.status)
   end
 
diff --git a/services/api/test/integration/bundler_version_test.rb b/services/api/test/integration/bundler_version_test.rb
new file mode 100644 (file)
index 0000000..fb1634c
--- /dev/null
@@ -0,0 +1,19 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+require 'test_helper'
+
+class BundlerVersionTest < ActionDispatch::IntegrationTest
+  test "Bundler version matches expectations" do
+    # The expected version range should be the latest that supports all the
+    # versions of Ruby we intend to support. This test checks that a developer
+    # doesn't accidentally update Bundler past that point.
+    expected = Gem::Dependency.new("", "~> 2.4.22")
+    actual = Bundler.gem_version
+    assert(
+      expected.match?("", actual),
+      "Bundler version #{actual} did not match #{expected}",
+    )
+  end
+end
index 7015453a9a62b0825628a19f8883a8b70275d72c..aa778dbf9fdb601641e67bf03c1412711e798bbc 100644 (file)
@@ -31,22 +31,22 @@ class DatabaseResetTest < ActionDispatch::IntegrationTest
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
-    post '/arvados/v1/specimens', params: {specimen: '{}'}, headers: active_auth
+    post '/arvados/v1/collections', params: {collection: '{}'}, headers: active_auth
     assert_response :success
     new_uuid = json_response['uuid']
 
     assert_response :success
     new_uuid = json_response['uuid']
 
-    get '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    get '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response :success
 
     assert_response :success
 
-    put('/arvados/v1/specimens/'+new_uuid,
-      params: {specimen: '{"properties":{}}'},
+    put('/arvados/v1/collections/'+new_uuid,
+      params: {collection: '{"properties":{}}'},
       headers: active_auth)
     assert_response :success
 
       headers: active_auth)
     assert_response :success
 
-    delete '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    delete '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response :success
 
     assert_response :success
 
-    get '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    get '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response 404
   end
 
     assert_response 404
   end
 
@@ -54,14 +54,14 @@ class DatabaseResetTest < ActionDispatch::IntegrationTest
     active_auth = auth(:active)
     admin_auth = auth(:admin)
 
     active_auth = auth(:active)
     admin_auth = auth(:admin)
 
-    old_uuid = specimens(:owned_by_active_user).uuid
+    old_uuid = collections(:collection_owned_by_active).uuid
     authorize_with :admin
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
     authorize_with :admin
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
-    delete '/arvados/v1/specimens/' + old_uuid, params: {}, headers: active_auth
+    delete '/arvados/v1/collections/' + old_uuid, params: {}, headers: active_auth
     assert_response :success
     assert_response :success
-    post '/arvados/v1/specimens', params: {specimen: '{}'}, headers: active_auth
+    post '/arvados/v1/collections', params: {collection: '{}'}, headers: active_auth
     assert_response :success
     new_uuid = json_response['uuid']
 
     assert_response :success
     new_uuid = json_response['uuid']
 
@@ -69,10 +69,10 @@ class DatabaseResetTest < ActionDispatch::IntegrationTest
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
     post '/database/reset', params: {}, headers: admin_auth
     assert_response :success
 
-    # New specimen should disappear. Old specimen should reappear.
-    get '/arvados/v1/specimens/'+new_uuid, params: {}, headers: active_auth
+    # New collection should disappear. Old collection should reappear.
+    get '/arvados/v1/collections/'+new_uuid, params: {}, headers: active_auth
     assert_response 404
     assert_response 404
-    get '/arvados/v1/specimens/'+old_uuid, params: {}, headers: active_auth
+    get '/arvados/v1/collections/'+old_uuid, params: {}, headers: active_auth
     assert_response :success
   end
 end
     assert_response :success
   end
 end
index e76f2b54068ad729fe94f87a3d2150846674db0b..bc5a08c2c8dced5e4f17a621965b1fef75f1a599 100644 (file)
@@ -140,7 +140,7 @@ class GroupsTest < ActionDispatch::IntegrationTest
 
   test 'count none works with offset' do
     first_results = nil
 
   test 'count none works with offset' do
     first_results = nil
-    (0..10).each do |offset|
+    (0..5).each do |offset|
       get "/arvados/v1/groups/contents", params: {
         id: groups(:aproject).uuid,
         offset: offset,
       get "/arvados/v1/groups/contents", params: {
         id: groups(:aproject).uuid,
         offset: offset,
diff --git a/services/api/test/integration/jobs_api_test.rb b/services/api/test/integration/jobs_api_test.rb
deleted file mode 100644 (file)
index 76d4fff..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobsApiTest < ActionDispatch::IntegrationTest
-end
index ba3b2ac6e3198bbc0ad1eee986b71a2eb80db444..7ad95ceebf3d3a8d908adf73459872d472f40a42 100644 (file)
@@ -6,8 +6,8 @@ require 'test_helper'
 
 class LoginWorkflowTest < ActionDispatch::IntegrationTest
   test "default prompt to login is JSON" do
 
 class LoginWorkflowTest < ActionDispatch::IntegrationTest
   test "default prompt to login is JSON" do
-    post('/arvados/v1/specimens',
-      params: {specimen: {}},
+    post('/arvados/v1/collections',
+      params: {collection: {}},
       headers: {'HTTP_ACCEPT' => ''})
     assert_response 401
     json_response['errors'].each do |err|
       headers: {'HTTP_ACCEPT' => ''})
     assert_response 401
     json_response['errors'].each do |err|
@@ -16,8 +16,8 @@ class LoginWorkflowTest < ActionDispatch::IntegrationTest
   end
 
   test "login prompt respects JSON Accept header" do
   end
 
   test "login prompt respects JSON Accept header" do
-    post('/arvados/v1/specimens',
-      params: {specimen: {}},
+    post('/arvados/v1/collections',
+      params: {collection: {}},
       headers: {'HTTP_ACCEPT' => 'application/json'})
     assert_response 401
     json_response['errors'].each do |err|
       headers: {'HTTP_ACCEPT' => 'application/json'})
     assert_response 401
     json_response['errors'].each do |err|
@@ -26,8 +26,8 @@ class LoginWorkflowTest < ActionDispatch::IntegrationTest
   end
 
   test "login prompt respects HTML Accept header" do
   end
 
   test "login prompt respects HTML Accept header" do
-    post('/arvados/v1/specimens',
-      params: {specimen: {}},
+    post('/arvados/v1/collections',
+      params: {collection: {}},
       headers: {'HTTP_ACCEPT' => 'text/html'})
     assert_response 302
     assert_match(%r{http://www.example.com/login$}, @response.headers['Location'],
       headers: {'HTTP_ACCEPT' => 'text/html'})
     assert_response 302
     assert_match(%r{http://www.example.com/login$}, @response.headers['Location'],
index d2dce44f018a0ce2e63232889d9d7cc4fe412aae..9636a82011ffd1e7d0559fa47d2ec5fa41dbc518 100644 (file)
@@ -302,26 +302,29 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     assert_response 404
   end
 
     assert_response 404
   end
 
-  test "RO group-admin finds user's specimens, RW group-admin can update" do
+  test "RO group-admin finds user's collections, RW group-admin can update" do
+    other_user_collection = act_as_user(users(:user_foo_in_sharing_group)) do
+      Collection.create()
+    end
     [[:rominiadmin, false],
      [:miniadmin, true]].each do |which_user, update_should_succeed|
     [[:rominiadmin, false],
      [:miniadmin, true]].each do |which_user, update_should_succeed|
-      get "/arvados/v1/specimens",
+      get "/arvados/v1/collections",
         params: {:format => :json},
         headers: auth(which_user)
       assert_response :success
       resp_uuids = json_response['items'].collect { |i| i['uuid'] }
         params: {:format => :json},
         headers: auth(which_user)
       assert_response :success
       resp_uuids = json_response['items'].collect { |i| i['uuid'] }
-      [[true, specimens(:owned_by_active_user).uuid],
-       [true, specimens(:owned_by_private_group).uuid],
-       [false, specimens(:owned_by_spectator).uuid],
+      [[true, collections(:collection_owned_by_active).uuid],
+       [true, collections(:foo_collection_in_aproject).uuid],
+       [false, other_user_collection.uuid],
       ].each do |should_find, uuid|
         assert_equal(should_find, !resp_uuids.index(uuid).nil?,
       ].each do |should_find, uuid|
         assert_equal(should_find, !resp_uuids.index(uuid).nil?,
-                     "%s should%s see %s in specimen list" %
+                     "%s should%s see %s in collection list" %
                      [which_user.to_s,
                      [which_user.to_s,
-                      should_find ? '' : 'not ',
+                      should_find ? '' : ' not',
                       uuid])
                       uuid])
-        put "/arvados/v1/specimens/#{uuid}",
+        put "/arvados/v1/collections/#{uuid}",
           params: {
           params: {
-            :specimen => {
+            :collection => {
               properties: {
                 miniadmin_was_here: true
               }
               properties: {
                 miniadmin_was_here: true
               }
index e8e8c910c7153a73411cb6602322a6ec64cf8f60..891bffbb1ddf4bd5d734fe6e6f2df310369e9199 100644 (file)
@@ -7,20 +7,20 @@ require 'test_helper'
 class ReaderTokensTest < ActionDispatch::IntegrationTest
   fixtures :all
 
 class ReaderTokensTest < ActionDispatch::IntegrationTest
   fixtures :all
 
-  def spectator_specimen
-    specimens(:owned_by_spectator).uuid
+  def owned_by_foo
+    collections(:collection_owned_by_foo).uuid
   end
 
   end
 
-  def get_specimens(main_auth, read_auth, formatter=:to_a)
+  def get_collections(main_auth, read_auth, formatter=:to_a)
     params = {}
     params[:reader_tokens] = [api_token(read_auth)].send(formatter) if read_auth
     headers = {}
     headers.merge!(auth(main_auth)) if main_auth
     params = {}
     params[:reader_tokens] = [api_token(read_auth)].send(formatter) if read_auth
     headers = {}
     headers.merge!(auth(main_auth)) if main_auth
-    get('/arvados/v1/specimens', params: params, headers: headers)
+    get('/arvados/v1/collections', params: params, headers: headers)
   end
 
   end
 
-  def get_specimen_uuids(main_auth, read_auth, formatter=:to_a)
-    get_specimens(main_auth, read_auth, formatter)
+  def get_collection_uuids(main_auth, read_auth, formatter=:to_a)
+    get_collections(main_auth, read_auth, formatter)
     assert_response :success
     json_response['items'].map { |spec| spec['uuid'] }
   end
     assert_response :success
     json_response['items'].map { |spec| spec['uuid'] }
   end
@@ -33,26 +33,26 @@ class ReaderTokensTest < ActionDispatch::IntegrationTest
       headers = {}
       expected = 401
     end
       headers = {}
       expected = 401
     end
-    post('/arvados/v1/specimens.json',
-      params: {specimen: {}, reader_tokens: [api_token(read_auth)].send(formatter)},
+    post('/arvados/v1/collections.json',
+      params: {collection: {}, reader_tokens: [api_token(read_auth)].send(formatter)},
       headers: headers)
     assert_response expected
   end
 
       headers: headers)
     assert_response expected
   end
 
-  test "active user can't see spectator specimen" do
+  test "active user can't see foo-owned collection" do
     # Other tests in this suite assume that the active user doesn't
     # Other tests in this suite assume that the active user doesn't
-    # have read permission to the owned_by_spectator specimen.
+    # have read permission to the owned_by_foo collection.
     # This test checks that this assumption still holds.
     # This test checks that this assumption still holds.
-    refute_includes(get_specimen_uuids(:active, nil), spectator_specimen,
-                    ["active user can read the owned_by_spectator specimen",
+    refute_includes(get_collection_uuids(:active, nil), owned_by_foo,
+                    ["active user can read the owned_by_foo collection",
                      "other tests will return false positives"].join(" - "))
   end
 
   [nil, :active_noscope].each do |main_auth|
                      "other tests will return false positives"].join(" - "))
   end
 
   [nil, :active_noscope].each do |main_auth|
-    [:spectator, :spectator_specimens].each do |read_auth|
+    [:foo, :foo_collections].each do |read_auth|
       [:to_a, :to_json].each do |formatter|
         test "#{main_auth.inspect} auth with #{formatter} reader token #{read_auth} can#{"'t" if main_auth} read" do
       [:to_a, :to_json].each do |formatter|
         test "#{main_auth.inspect} auth with #{formatter} reader token #{read_auth} can#{"'t" if main_auth} read" do
-          get_specimens(main_auth, read_auth)
+          get_collections(main_auth, read_auth)
           assert_response(if main_auth then 403 else 200 end)
         end
 
           assert_response(if main_auth then 403 else 200 end)
         end
 
@@ -65,18 +65,18 @@ class ReaderTokensTest < ActionDispatch::IntegrationTest
 
   test "scopes are still limited with reader tokens" do
     get('/arvados/v1/collections',
 
   test "scopes are still limited with reader tokens" do
     get('/arvados/v1/collections',
-      params: {reader_tokens: [api_token(:spectator_specimens)]},
+      params: {reader_tokens: [api_token(:foo_collections)]},
       headers: auth(:active_noscope))
     assert_response 403
   end
 
   test "reader tokens grant no permissions when expired" do
       headers: auth(:active_noscope))
     assert_response 403
   end
 
   test "reader tokens grant no permissions when expired" do
-    get_specimens(:active_noscope, :expired)
+    get_collections(:active_noscope, :expired)
     assert_response 403
   end
 
   test "reader tokens grant no permissions outside their scope" do
     assert_response 403
   end
 
   test "reader tokens grant no permissions outside their scope" do
-    refute_includes(get_specimen_uuids(:active, :admin_vm), spectator_specimen,
+    refute_includes(get_collection_uuids(:active, :admin_vm), owned_by_foo,
                     "scoped reader token granted permissions out of scope")
   end
 end
                     "scoped reader token granted permissions out of scope")
   end
 end
index f42fda415077ae4db9ba4496e0e3269df4734453..98250a62424383863de2bd81c39b33e5b204981c 100644 (file)
@@ -75,7 +75,7 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
         res.status = @stub_token_status
         if res.status == 200
           body = {
         res.status = @stub_token_status
         if res.status == 200
           body = {
-            uuid: api_client_authorizations(:active).uuid.sub('zzzzz', clusterid),
+            uuid: @stub_token_uuid || api_client_authorizations(:active).uuid.sub('zzzzz', clusterid),
             owner_uuid: "#{clusterid}-tpzed-00000000000000z",
             scopes: @stub_token_scopes,
           }
             owner_uuid: "#{clusterid}-tpzed-00000000000000z",
             scopes: @stub_token_scopes,
           }
@@ -108,6 +108,7 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
     }
     @stub_token_status = 200
     @stub_token_scopes = ["all"]
     }
     @stub_token_status = 200
     @stub_token_scopes = ["all"]
+    @stub_token_uuid = nil
     ActionMailer::Base.deliveries = []
   end
 
     ActionMailer::Base.deliveries = []
   end
 
@@ -241,6 +242,40 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
     assert_equal 'foo', json_response['username']
   end
 
     assert_equal 'foo', json_response['username']
   end
 
+  test 'authenticate with remote token with secret part identical to previously cached token' do
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+    get '/arvados/v1/api_client_authorizations/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+
+    # Expire the cached token.
+    @cached_token_uuid = json_response['uuid']
+    act_as_system_user do
+      ApiClientAuthorization.where(uuid: @cached_token_uuid).update_all(expires_at: db_current_time() - 1.day)
+    end
+
+    # Now use the same bare token, but set up the remote cluster to
+    # return a different UUID this time.
+    @stub_token_uuid = 'zbbbb-gj3su-123451234512345'
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+
+    # Confirm that we actually retrieved the new UUID from the stub
+    # cluster -- otherwise we didn't really test the conflicting-UUID
+    # case.
+    get '/arvados/v1/api_client_authorizations/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+    assert_equal @stub_token_uuid, json_response['uuid']
+  end
+
   test 'authenticate with remote token from misbehaving remote cluster' do
     get '/arvados/v1/users/current',
       params: {format: 'json'},
   test 'authenticate with remote token from misbehaving remote cluster' do
     get '/arvados/v1/users/current',
       params: {format: 'json'},
@@ -593,15 +628,43 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
     assert_equal 'zzzzz-tpzed-anonymouspublic', json_response['uuid']
   end
 
     assert_equal 'zzzzz-tpzed-anonymouspublic', json_response['uuid']
   end
 
-  [401, 403, 422, 500, 502, 503].each do |status|
-    test "propagate #{status} response from getting remote token" do
+  [400, 401, 403, 422, 500, 502, 503].each do |status|
+    test "handle #{status} response when checking remote-provided v2 token" do
       @stub_token_status = status
       get "/arvados/v1/users/#{@stub_content[:uuid]}",
           params: {format: "json"},
           headers: auth(remote: "zbbbb")
       @stub_token_status = status
       get "/arvados/v1/users/#{@stub_content[:uuid]}",
           params: {format: "json"},
           headers: auth(remote: "zbbbb")
-      assert_response status
+      assert_response(status < 500 ? 401 : status)
+    end
+
+    test "handle #{status} response when checking remote-provided v2 token at anonymously accessible endpoint" do
+      @stub_token_status = status
+      get "/arvados/v1/keep_services/accessible",
+          params: {format: "json"},
+          headers: auth(remote: "zbbbb")
+      assert_response(status < 500 ? :success : status)
+    end
+
+    test "handle #{status} response when checking token issued by login cluster" do
+      @stub_token_status = status
+      Rails.configuration.Login.LoginCluster = "zbbbb"
+      get "/arvados/v1/users/current",
+          params: {format: "json"},
+          headers: {'HTTP_AUTHORIZATION' => "Bearer badtoken"}
+      assert_response(status < 500 ? 401 : status)
     end
 
     end
 
+    test "handle #{status} response when checking token issued by login cluster at anonymously accessible endpoint" do
+      @stub_token_status = status
+      Rails.configuration.Login.LoginCluster = "zbbbb"
+      get "/arvados/v1/keep_services/accessible",
+          params: {format: "json"},
+          headers: {'HTTP_AUTHORIZATION' => "Bearer badtoken"}
+      assert_response(status < 500 ? :success : status)
+    end
+  end
+
+  [401, 403, 422, 500, 502, 503].each do |status|
     test "propagate #{status} response from getting uncached user" do
       @stub_status = status
       get "/arvados/v1/users/#{@stub_content[:uuid]}",
     test "propagate #{status} response from getting uncached user" do
       @stub_status = status
       get "/arvados/v1/users/#{@stub_content[:uuid]}",
index f41c033b3918c2fea8b9dc0e088f534600d38ab7..bf2d0062f222bad9676508238d40fdcadf01bb5d 100644 (file)
@@ -3,26 +3,13 @@
 # SPDX-License-Identifier: AGPL-3.0
 
 require 'test_helper'
 # SPDX-License-Identifier: AGPL-3.0
 
 require 'test_helper'
-require 'helpers/git_test_helper'
 
 class SerializedEncodingTest < ActionDispatch::IntegrationTest
 
 class SerializedEncodingTest < ActionDispatch::IntegrationTest
-  include GitTestHelper
-
   fixtures :all
 
   {
     api_client_authorization: {scopes: []},
   fixtures :all
 
   {
     api_client_authorization: {scopes: []},
-
-    human: {properties: {eye_color: 'gray'}},
-
     link: {link_class: 'test', name: 'test', properties: {foo: :bar}},
     link: {link_class: 'test', name: 'test', properties: {foo: :bar}},
-
-    node: {info: {uptime: 1234}},
-
-    specimen: {properties: {eye_color: 'meringue'}},
-
-    trait: {properties: {eye_color: 'brown'}},
-
     user: {prefs: {cookies: 'thin mint'}},
   }.each_pair do |resource, postdata|
     test "create json-encoded #{resource.to_s}" do
     user: {prefs: {cookies: 'thin mint'}},
   }.each_pair do |resource, postdata|
     test "create json-encoded #{resource.to_s}" do
index eb49cf832e2fe9b2e86a6228a1ea7dc0b7eef59a..2b374542188bfef5ed7196728e4c6a3cad827689 100644 (file)
@@ -87,22 +87,20 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
 
   # Test various combinations of auto_setup configuration and email
   # address provided during a new user's first session setup.
 
   # Test various combinations of auto_setup configuration and email
   # address provided during a new user's first session setup.
-  [{result: :nope, email: nil, cfg: {auto: true, repo: true, vm: true}},
+  [{result: :nope, email: nil, cfg: {auto: true, vm: true}},
    {result: :yup, email: nil, cfg: {auto: true}},
    {result: :yup, email: nil, cfg: {auto: true}},
-   {result: :nope, email: '@example.com', cfg: {auto: true, repo: true, vm: true}},
+   {result: :nope, email: '@example.com', cfg: {auto: true, vm: true}},
    {result: :yup, email: '@example.com', cfg: {auto: true}},
    {result: :yup, email: '@example.com', cfg: {auto: true}},
-   {result: :nope, email: 'root@', cfg: {auto: true, repo: true, vm: true}},
-   {result: :nope, email: 'root@', cfg: {auto: true, repo: true}},
    {result: :nope, email: 'root@', cfg: {auto: true, vm: true}},
    {result: :nope, email: 'root@', cfg: {auto: true, vm: true}},
-   {result: :yup, email: 'root@', cfg: {auto: true}},
-   {result: :nope, email: 'gitolite@', cfg: {auto: true, repo: true}},
+   {result: :nope, email: 'root@', cfg: {auto: true}},
+   {result: :nope, email: 'gitolite@', cfg: {auto: true}},
    {result: :nope, email: '*_*@', cfg: {auto: true, vm: true}},
    {result: :yup, email: 'toor@', cfg: {auto: true, vm: true, repo: true}},
    {result: :yup, email: 'foo@', cfg: {auto: true, vm: true},
      uniqprefix: 'foo'},
    {result: :nope, email: '*_*@', cfg: {auto: true, vm: true}},
    {result: :yup, email: 'toor@', cfg: {auto: true, vm: true, repo: true}},
    {result: :yup, email: 'foo@', cfg: {auto: true, vm: true},
      uniqprefix: 'foo'},
-   {result: :yup, email: 'foo@', cfg: {auto: true, repo: true},
+   {result: :yup, email: 'foo@', cfg: {auto: true},
      uniqprefix: 'foo'},
      uniqprefix: 'foo'},
-   {result: :yup, email: 'auto_setup_vm_login@', cfg: {auto: true, repo: true},
+   {result: :yup, email: 'auto_setup_vm_login@', cfg: {auto: true},
      uniqprefix: 'auto_setup_vm_login'},
    ].each do |testcase|
     test "user auto-activate #{testcase.inspect}" do
      uniqprefix: 'auto_setup_vm_login'},
    ].each do |testcase|
     test "user auto-activate #{testcase.inspect}" do
@@ -111,23 +109,16 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
       Rails.configuration.Users.AutoSetupNewUsers = testcase[:cfg][:auto]
       Rails.configuration.Users.AutoSetupNewUsersWithVmUUID =
         (testcase[:cfg][:vm] ? virtual_machines(:testvm).uuid : "")
       Rails.configuration.Users.AutoSetupNewUsers = testcase[:cfg][:auto]
       Rails.configuration.Users.AutoSetupNewUsersWithVmUUID =
         (testcase[:cfg][:vm] ? virtual_machines(:testvm).uuid : "")
-      Rails.configuration.Users.AutoSetupNewUsersWithRepository =
-        testcase[:cfg][:repo]
 
       mock_auth_with(email: testcase[:email])
       u = assigns(:user)
       vm_links = Link.where('link_class=? and tail_uuid=? and head_uuid like ?',
                             'permission', u.uuid,
                             '%-' + VirtualMachine.uuid_prefix + '-%')
 
       mock_auth_with(email: testcase[:email])
       u = assigns(:user)
       vm_links = Link.where('link_class=? and tail_uuid=? and head_uuid like ?',
                             'permission', u.uuid,
                             '%-' + VirtualMachine.uuid_prefix + '-%')
-      repo_links = Link.where('link_class=? and tail_uuid=? and head_uuid like ?',
-                              'permission', u.uuid,
-                              '%-' + Repository.uuid_prefix + '-%')
-      repos = Repository.where('uuid in (?)', repo_links.collect(&:head_uuid))
       case u[:result]
       when :nope
         assert_equal false, u.is_invited, "should not have been set up"
         assert_empty vm_links, "should not have VM login permission"
       case u[:result]
       when :nope
         assert_equal false, u.is_invited, "should not have been set up"
         assert_empty vm_links, "should not have VM login permission"
-        assert_empty repo_links, "should not have repo permission"
       when :yup
         assert_equal true, u.is_invited
         if testcase[:cfg][:vm]
       when :yup
         assert_equal true, u.is_invited
         if testcase[:cfg][:vm]
@@ -135,21 +126,11 @@ class UserSessionsApiTest < ActionDispatch::IntegrationTest
         else
           assert_empty vm_links, "should not have VM login permission"
         end
         else
           assert_empty vm_links, "should not have VM login permission"
         end
-        if testcase[:cfg][:repo]
-          assert_equal 1, repo_links.count, "wrong number of repo perm links"
-          assert_equal 1, repos.count, "wrong number of repos"
-          assert_equal 'can_manage', repo_links.first.name, "wrong perm type"
-        else
-          assert_empty repo_links, "should not have repo permission"
-        end
       end
       if (prefix = testcase[:uniqprefix])
         # This email address conflicts with a test fixture. Make sure
       end
       if (prefix = testcase[:uniqprefix])
         # This email address conflicts with a test fixture. Make sure
-        # every VM login and repository name got digits added to make
-        # it unique.
-        (repos.collect(&:name) +
-         vm_links.collect { |link| link.properties['username'] }
-         ).each do |name|
+        # every VM login got digits added to make it unique.
+        vm_links.collect { |link| link.properties['username'] }.each do |name|
           r = name.match(/^(.{#{prefix.length}})(\d+)$/)
           assert_not_nil r, "#{name.inspect} does not match {prefix}\\d+"
           assert_equal(prefix, r[1],
           r = name.match(/^(.{#{prefix.length}})(\d+)$/)
           assert_not_nil r, "#{name.inspect} does not match {prefix}\\d+"
           assert_equal(prefix, r[1],
index f8956b21e24a0772899b5c796dd2b2e650fc1e6e..a7d6245443e2c4318f604674adfbc20a8423f65d 100644 (file)
@@ -9,11 +9,8 @@ class UsersTest < ActionDispatch::IntegrationTest
   include UsersTestHelper
 
   test "setup user multiple times" do
   include UsersTestHelper
 
   test "setup user multiple times" do
-    repo_name = 'usertestrepo'
-
     post "/arvados/v1/users/setup",
       params: {
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: repo_name,
         user: {
           uuid: 'zzzzz-tpzed-abcdefghijklmno',
           first_name: "in_create_test_first_name",
         user: {
           uuid: 'zzzzz-tpzed-abcdefghijklmno',
           first_name: "in_create_test_first_name",
@@ -35,10 +32,7 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # repo link and link add user to 'All users' group
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
+    # link to add user to 'All users' group
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
@@ -51,7 +45,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     # invoke setup again with the same data
     post "/arvados/v1/users/setup",
       params: {
     # invoke setup again with the same data
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: repo_name,
         vm_uuid: virtual_machines(:testvm).uuid,
         user: {
           uuid: 'zzzzz-tpzed-abcdefghijklmno',
         vm_uuid: virtual_machines(:testvm).uuid,
         user: {
           uuid: 'zzzzz-tpzed-abcdefghijklmno',
@@ -66,7 +59,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     # invoke setup on the same user
     post "/arvados/v1/users/setup",
       params: {
     # invoke setup on the same user
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: repo_name,
         vm_uuid: virtual_machines(:testvm).uuid,
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
       },
         vm_uuid: virtual_machines(:testvm).uuid,
         uuid: 'zzzzz-tpzed-abcdefghijklmno',
       },
@@ -81,10 +73,7 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
-    # arvados#user, repo link and link add user to 'All users' group
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
+    # arvados#user, and link to add user to 'All users' group
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
@@ -119,31 +108,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
 
     verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
         nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
 
-   # invoke setup with a repository
-    post "/arvados/v1/users/setup",
-      params: {
-        repo_name: 'newusertestrepo',
-        uuid: created['uuid']
-      },
-      headers: auth(:admin)
-
-    assert_response :success
-
-    response_items = json_response['items']
-    created = find_obj_in_resp response_items, 'arvados#user', nil
-
-    assert_equal 'foo@example.com', created['email'], 'expected input email'
-
-     # verify links
-    verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
-        'All users', created['uuid'], 'arvados#group', true, 'Group'
-
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/newusertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
-    verify_link response_items, 'arvados#virtualMachine', false, 'permission', 'can_login',
-        nil, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
-
     # invoke setup with a vm_uuid
     post "/arvados/v1/users/setup",
       params: {
     # invoke setup with a vm_uuid
     post "/arvados/v1/users/setup",
       params: {
@@ -173,7 +137,6 @@ class UsersTest < ActionDispatch::IntegrationTest
   test "setup and unsetup user" do
     post "/arvados/v1/users/setup",
       params: {
   test "setup and unsetup user" do
     post "/arvados/v1/users/setup",
       params: {
-        repo_name: 'newusertestrepo',
         vm_uuid: virtual_machines(:testvm).uuid,
         user: {email: 'foo@example.com'},
       },
         vm_uuid: virtual_machines(:testvm).uuid,
         user: {email: 'foo@example.com'},
       },
@@ -185,14 +148,11 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_not_nil created['uuid'], 'expected uuid for the new user'
     assert_equal created['email'], 'foo@example.com', 'expected given email'
 
     assert_not_nil created['uuid'], 'expected uuid for the new user'
     assert_equal created['email'], 'foo@example.com', 'expected given email'
 
-    # four extra links: system_group, login, group, repo and vm
+    # three extra links: system_group, login, group and vm
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
 
     verify_link response_items, 'arvados#group', true, 'permission', 'can_write',
         'All users', created['uuid'], 'arvados#group', true, 'Group'
 
-    verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
-        'foo/newusertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
-
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         virtual_machines(:testvm).uuid, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
 
     verify_link response_items, 'arvados#virtualMachine', true, 'permission', 'can_login',
         virtual_machines(:testvm).uuid, created['uuid'], 'arvados#virtualMachine', false, 'VirtualMachine'
 
@@ -276,13 +236,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_equal(users(:project_viewer).uuid, json_response['owner_uuid'])
     assert_equal(users(:project_viewer).uuid, json_response['authorized_user_uuid'])
 
     assert_equal(users(:project_viewer).uuid, json_response['owner_uuid'])
     assert_equal(users(:project_viewer).uuid, json_response['authorized_user_uuid'])
 
-    get('/arvados/v1/repositories/' + repositories(:foo).uuid,
-      params: {},
-      headers: auth(:active))
-    assert_response(:success)
-    assert_equal(users(:project_viewer).uuid, json_response['owner_uuid'])
-    assert_equal("#{users(:project_viewer).username}/foo", json_response['name'])
-
     get('/arvados/v1/groups/' + groups(:aproject).uuid,
       params: {},
       headers: auth(:active))
     get('/arvados/v1/groups/' + groups(:aproject).uuid,
       params: {},
       headers: auth(:active))
@@ -317,41 +270,6 @@ class UsersTest < ActionDispatch::IntegrationTest
     assert_equal 'barney', json_response['username']
   end
 
     assert_equal 'barney', json_response['username']
   end
 
-  test 'merge with repository name conflict' do
-    post('/arvados/v1/groups',
-      params: {
-        group: {
-          group_class: 'project',
-          name: "active user's stuff",
-        },
-      },
-      headers: auth(:project_viewer))
-    assert_response(:success)
-    project_uuid = json_response['uuid']
-
-    post('/arvados/v1/repositories/',
-         params: { :repository => { :name => "#{users(:project_viewer).username}/foo", :owner_uuid => users(:project_viewer).uuid } },
-         headers: auth(:project_viewer))
-    assert_response(:success)
-
-    post('/arvados/v1/users/merge',
-      params: {
-        new_user_token: api_client_authorizations(:project_viewer_trustedclient).api_token,
-        new_owner_uuid: project_uuid,
-        redirect_to_new_user: true,
-      },
-      headers: auth(:active_trustedclient))
-    assert_response(:success)
-
-    get('/arvados/v1/repositories/' + repositories(:foo).uuid,
-      params: {},
-      headers: auth(:active))
-    assert_response(:success)
-    assert_equal(users(:project_viewer).uuid, json_response['owner_uuid'])
-    assert_equal("#{users(:project_viewer).username}/migratedfoo", json_response['name'])
-
-  end
-
   test "cannot set is_active to false directly" do
     post('/arvados/v1/users',
       params: {
   test "cannot set is_active to false directly" do
     post('/arvados/v1/users',
       params: {
diff --git a/services/api/test/test.git.tar b/services/api/test/test.git.tar
deleted file mode 100644 (file)
index 7af80b0..0000000
Binary files a/services/api/test/test.git.tar and /dev/null differ
index 69a2710bb954caa04f3c3e86da8c13f3719bb10b..341a8462dded2f467de8a6c639c78dbc3ff9591b 100644 (file)
@@ -8,20 +8,20 @@ class ArvadosModelTest < ActiveSupport::TestCase
   fixtures :all
 
   def create_with_attrs attrs
   fixtures :all
 
   def create_with_attrs attrs
-    a = Specimen.create({material: 'caloric'}.merge(attrs))
+    a = Collection.create({properties: {'foo' => 'bar'}}.merge(attrs))
     a if a.valid?
   end
 
   test 'non-admin cannot assign uuid' do
     set_user_from_auth :active_trustedclient
     a if a.valid?
   end
 
   test 'non-admin cannot assign uuid' do
     set_user_from_auth :active_trustedclient
-    want_uuid = Specimen.generate_uuid
+    want_uuid = Collection.generate_uuid
     a = create_with_attrs(uuid: want_uuid)
     assert_nil a, "Non-admin should not assign uuid."
   end
 
   test 'admin can assign valid uuid' do
     set_user_from_auth :admin_trustedclient
     a = create_with_attrs(uuid: want_uuid)
     assert_nil a, "Non-admin should not assign uuid."
   end
 
   test 'admin can assign valid uuid' do
     set_user_from_auth :admin_trustedclient
-    want_uuid = Specimen.generate_uuid
+    want_uuid = Collection.generate_uuid
     a = create_with_attrs(uuid: want_uuid)
     assert_equal want_uuid, a.uuid, "Admin should assign valid uuid."
     assert a.uuid.length==27, "Auto assigned uuid length is wrong."
     a = create_with_attrs(uuid: want_uuid)
     assert_equal want_uuid, a.uuid, "Admin should assign valid uuid."
     assert a.uuid.length==27, "Auto assigned uuid length is wrong."
@@ -29,7 +29,7 @@ class ArvadosModelTest < ActiveSupport::TestCase
 
   test 'admin cannot assign uuid with wrong object type' do
     set_user_from_auth :admin_trustedclient
 
   test 'admin cannot assign uuid with wrong object type' do
     set_user_from_auth :admin_trustedclient
-    want_uuid = Human.generate_uuid
+    want_uuid = Group.generate_uuid
     a = create_with_attrs(uuid: want_uuid)
     assert_nil a, "Admin should not be able to assign invalid uuid."
   end
     a = create_with_attrs(uuid: want_uuid)
     assert_nil a, "Admin should not be able to assign invalid uuid."
   end
@@ -126,10 +126,23 @@ class ArvadosModelTest < ActiveSupport::TestCase
   end
 
   test "search index exists on models that go into projects" do
   end
 
   test "search index exists on models that go into projects" do
-    all_tables =  ActiveRecord::Base.connection.tables
-    all_tables.delete 'schema_migrations'
-    all_tables.delete 'permission_refresh_lock'
-    all_tables.delete 'ar_internal_metadata'
+    all_tables =  ActiveRecord::Base.connection.tables - [
+      'ar_internal_metadata',
+      'permission_refresh_lock',
+      'schema_migrations',
+      # tables that may still exist in the database even though model
+      # classes have been deleted:
+      'humans',
+      'jobs',
+      'job_tasks',
+      'keep_disks',
+      'nodes',
+      'pipeline_instances',
+      'pipeline_templates',
+      'repositories',
+      'specimens',
+      'traits',
+    ]
 
     all_tables.each do |table|
       table_class = table.classify.constantize
 
     all_tables.each do |table|
       table_class = table.classify.constantize
@@ -159,9 +172,6 @@ class ArvadosModelTest < ActiveSupport::TestCase
     %w[collections collections_trgm_text_search_idx],
     %w[container_requests container_requests_trgm_text_search_idx],
     %w[groups groups_trgm_text_search_idx],
     %w[collections collections_trgm_text_search_idx],
     %w[container_requests container_requests_trgm_text_search_idx],
     %w[groups groups_trgm_text_search_idx],
-    %w[jobs jobs_trgm_text_search_idx],
-    %w[pipeline_instances pipeline_instances_trgm_text_search_idx],
-    %w[pipeline_templates pipeline_templates_trgm_text_search_idx],
     %w[workflows workflows_trgm_text_search_idx]
   ].each do |model|
     table = model[0]
     %w[workflows workflows_trgm_text_search_idx]
   ].each do |model|
     table = model[0]
@@ -180,25 +190,25 @@ class ArvadosModelTest < ActiveSupport::TestCase
   end
 
   test "selectable_attributes includes database attributes" do
   end
 
   test "selectable_attributes includes database attributes" do
-    assert_includes(Job.selectable_attributes, "success")
+    assert_includes(Collection.selectable_attributes, "name")
   end
 
   test "selectable_attributes includes non-database attributes" do
   end
 
   test "selectable_attributes includes non-database attributes" do
-    assert_includes(Job.selectable_attributes, "node_uuids")
+    assert_includes(Collection.selectable_attributes, "unsigned_manifest_text")
   end
 
   test "selectable_attributes includes common attributes in extensions" do
   end
 
   test "selectable_attributes includes common attributes in extensions" do
-    assert_includes(Job.selectable_attributes, "uuid")
+    assert_includes(Collection.selectable_attributes, "uuid")
   end
 
   test "selectable_attributes does not include unexposed attributes" do
   end
 
   test "selectable_attributes does not include unexposed attributes" do
-    refute_includes(Job.selectable_attributes, "nodes")
+    refute_includes(Collection.selectable_attributes, "id")
   end
 
   test "selectable_attributes on a non-default template" do
   end
 
   test "selectable_attributes on a non-default template" do
-    attr_a = Job.selectable_attributes(:common)
+    attr_a = Collection.selectable_attributes(:common)
     assert_includes(attr_a, "uuid")
     assert_includes(attr_a, "uuid")
-    refute_includes(attr_a, "success")
+    refute_includes(attr_a, "name")
   end
 
   test 'create and retrieve using created_at time' do
   end
 
   test 'create and retrieve using created_at time' do
@@ -260,19 +270,21 @@ class ArvadosModelTest < ActiveSupport::TestCase
       else
         Rails.configuration.AuditLogs.MaxAge = 0
       end
       else
         Rails.configuration.AuditLogs.MaxAge = 0
       end
+      tested_serialized = false
       [
         User.find_by_uuid(users(:active).uuid),
         ContainerRequest.find_by_uuid(container_requests(:queued).uuid),
         Container.find_by_uuid(containers(:queued).uuid),
       [
         User.find_by_uuid(users(:active).uuid),
         ContainerRequest.find_by_uuid(container_requests(:queued).uuid),
         Container.find_by_uuid(containers(:queued).uuid),
-        PipelineInstance.find_by_uuid(pipeline_instances(:has_component_with_completed_jobs).uuid),
-        PipelineTemplate.find_by_uuid(pipeline_templates(:two_part).uuid),
-        Job.find_by_uuid(jobs(:running).uuid)
+        Group.find_by_uuid(groups(:afiltergroup).uuid),
+        Collection.find_by_uuid(collections(:collection_with_one_property).uuid),
       ].each do |obj|
       ].each do |obj|
-        assert_not(obj.class.serialized_attributes.empty?,
-          "#{obj.class} model doesn't have serialized attributes")
+        if !obj.class.serialized_attributes.empty?
+          tested_serialized = true
+        end
         # obj shouldn't have changed since it's just retrieved from the database
         assert_not(obj.changed?, "#{obj.class} model's attribute(s) appear as changed: '#{obj.changes.keys.join(',')}' with audit logs #{auditlogs_enabled ? '': 'not '}enabled.")
       end
         # obj shouldn't have changed since it's just retrieved from the database
         assert_not(obj.changed?, "#{obj.class} model's attribute(s) appear as changed: '#{obj.changes.keys.join(',')}' with audit logs #{auditlogs_enabled ? '': 'not '}enabled.")
       end
+      assert(tested_serialized, "did not test any models with serialized attributes")
     end
   end
 end
     end
   end
 end
diff --git a/services/api/test/unit/commit_ancestor_test.rb b/services/api/test/unit/commit_ancestor_test.rb
deleted file mode 100644 (file)
index 4604121..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CommitAncestorTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/commit_test.rb b/services/api/test/unit/commit_test.rb
deleted file mode 100644 (file)
index e83061f..0000000
+++ /dev/null
@@ -1,270 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-# NOTE: calling Commit.find_commit_range(nil, nil, 'rev')
-# produces an error message "fatal: bad object 'rev'" on stderr if
-# 'rev' does not exist in a given repository.  Many of these tests
-# report such errors; their presence does not represent a fatal
-# condition.
-
-class CommitTest < ActiveSupport::TestCase
-  # See git_setup.rb for the commit log for test.git.tar
-  include GitTestHelper
-
-  setup do
-    authorize_with :active
-  end
-
-  test 'find_commit_range does not bypass permissions' do
-    authorize_with :inactive
-    assert_raises ArgumentError do
-      CommitsHelper::find_commit_range 'foo', nil, 'main', []
-    end
-  end
-
-  def must_pipe(cmd)
-    begin
-      return IO.read("|#{cmd}")
-    ensure
-      assert $?.success?
-    end
-  end
-
-  [
-   'https://github.com/arvados/arvados.git',
-   'http://github.com/arvados/arvados.git',
-   'git://github.com/arvados/arvados.git',
-  ].each do |url|
-    test "find_commit_range uses fetch_remote_repository to get #{url}" do
-      fake_gitdir = repositories(:foo).server_path
-      CommitsHelper::expects(:cache_dir_for).once.with(url).returns fake_gitdir
-      CommitsHelper::expects(:fetch_remote_repository).once.with(fake_gitdir, url).returns true
-      c = CommitsHelper::find_commit_range url, nil, 'main', []
-      refute_empty c
-    end
-  end
-
-  [
-   'bogus/repo',
-   '/bogus/repo',
-   '/not/allowed/.git',
-   'file:///not/allowed.git',
-   'git.arvados.org/arvados.git',
-   'github.com/arvados/arvados.git',
-  ].each do |url|
-    test "find_commit_range skips fetch_remote_repository for #{url}" do
-      CommitsHelper::expects(:fetch_remote_repository).never
-      assert_raises ArgumentError do
-        CommitsHelper::find_commit_range url, nil, 'main', []
-      end
-    end
-  end
-
-  test 'fetch_remote_repository does not leak commits across repositories' do
-    url = "http://localhost:1/fake/fake.git"
-    fetch_remote_from_local_repo url, :foo
-    c = CommitsHelper::find_commit_range url, nil, 'main', []
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57'], c
-
-    url = "http://localhost:2/fake/fake.git"
-    fetch_remote_from_local_repo url, 'file://' + File.expand_path('../../.git', Rails.root)
-    c = CommitsHelper::find_commit_range url, nil, '077ba2ad3ea24a929091a9e6ce545c93199b8e57', []
-    assert_equal [], c
-  end
-
-  test 'tag_in_internal_repository creates and updates tags in internal.git' do
-    authorize_with :active
-    gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir}"
-    IO.read("|#{gitint} tag -d testtag 2>/dev/null") # "no such tag", fine
-    assert_match(/^fatal: /, IO.read("|#{gitint} show testtag 2>&1"))
-    refute $?.success?
-    CommitsHelper::tag_in_internal_repository 'active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', 'testtag'
-    assert_match(/^commit 31ce37f/, IO.read("|#{gitint} show testtag"))
-    assert $?.success?
-  end
-
-  def with_foo_repository
-    Dir.chdir("#{Rails.configuration.Git.Repositories}/#{repositories(:foo).uuid}") do
-      must_pipe("git checkout main 2>&1")
-      yield
-    end
-  end
-
-  test 'tag_in_internal_repository, new non-tip sha1 in local repo' do
-    tag = "tag#{rand(10**10)}"
-    sha1 = nil
-    with_foo_repository do
-      must_pipe("git checkout -b branch-#{rand(10**10)} 2>&1")
-      must_pipe("echo -n #{tag.shellescape} >bar")
-      must_pipe("git add bar")
-      must_pipe("git -c user.email=x@x -c user.name=X commit -m -")
-      sha1 = must_pipe("git log -n1 --format=%H").strip
-      must_pipe("git rm bar")
-      must_pipe("git -c user.email=x@x -c user.name=X commit -m -")
-    end
-    CommitsHelper::tag_in_internal_repository 'active/foo', sha1, tag
-    gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape}"
-    assert_match(/^commit /, IO.read("|#{gitint} show #{tag.shellescape}"))
-    assert $?.success?
-  end
-
-  test 'tag_in_internal_repository, new unreferenced sha1 in local repo' do
-    tag = "tag#{rand(10**10)}"
-    sha1 = nil
-    with_foo_repository do
-      must_pipe("echo -n #{tag.shellescape} >bar")
-      must_pipe("git add bar")
-      must_pipe("git -c user.email=x@x -c user.name=X commit -m -")
-      sha1 = must_pipe("git log -n1 --format=%H").strip
-      must_pipe("git reset --hard HEAD^")
-    end
-    CommitsHelper::tag_in_internal_repository 'active/foo', sha1, tag
-    gitint = "git --git-dir #{Rails.configuration.Containers.JobsAPI.GitInternalDir.shellescape}"
-    assert_match(/^commit /, IO.read("|#{gitint} show #{tag.shellescape}"))
-    assert $?.success?
-  end
-
-  # In active/shabranchnames, "7387838c69a21827834586cc42b467ff6c63293b" is
-  # both a commit hash, and the name of a branch that begins from that same
-  # commit.
-  COMMIT_BRANCH_NAME = "7387838c69a21827834586cc42b467ff6c63293b"
-  # A commit that appears in the branch after 7387838c.
-  COMMIT_BRANCH_COMMIT_2 = "abec49829bf1758413509b7ffcab32a771b71e81"
-  # "738783" is another branch that starts from the above commit.
-  SHORT_COMMIT_BRANCH_NAME = COMMIT_BRANCH_NAME[0, 6]
-  # A commit that appears in branch 738783 after 7387838c.
-  SHORT_BRANCH_COMMIT_2 = "77e1a93093663705a63bb4d505698047e109dedd"
-
-  test "find_commit_range min_version prefers commits over branch names" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          COMMIT_BRANCH_NAME, nil, nil))
-  end
-
-  test "find_commit_range max_version prefers commits over branch names" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          nil, COMMIT_BRANCH_NAME, nil))
-  end
-
-  test "find_commit_range min_version with short branch name" do
-    assert_equal([SHORT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          SHORT_COMMIT_BRANCH_NAME, nil, nil))
-  end
-
-  test "find_commit_range max_version with short branch name" do
-    assert_equal([SHORT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          nil, SHORT_COMMIT_BRANCH_NAME, nil))
-  end
-
-  test "find_commit_range min_version with disambiguated branch name" do
-    assert_equal([COMMIT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          "heads/#{COMMIT_BRANCH_NAME}",
-                                          nil, nil))
-  end
-
-  test "find_commit_range max_version with disambiguated branch name" do
-    assert_equal([COMMIT_BRANCH_COMMIT_2],
-                 CommitsHelper::find_commit_range("active/shabranchnames", nil,
-                                          "heads/#{COMMIT_BRANCH_NAME}", nil))
-  end
-
-  test "find_commit_range min_version with unambiguous short name" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames",
-                                          COMMIT_BRANCH_NAME[0..-2], nil, nil))
-  end
-
-  test "find_commit_range max_version with unambiguous short name" do
-    assert_equal([COMMIT_BRANCH_NAME],
-                 CommitsHelper::find_commit_range("active/shabranchnames", nil,
-                                          COMMIT_BRANCH_NAME[0..-2], nil))
-  end
-
-  test "find_commit_range laundry list" do
-    authorize_with :active
-
-    # single
-    a = CommitsHelper::find_commit_range('active/foo', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
-    assert_equal ['31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    #test "test_branch1" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, 'main', nil)
-    assert_includes(a, '077ba2ad3ea24a929091a9e6ce545c93199b8e57')
-
-    #test "test_branch2" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, 'b1', nil)
-    assert_equal ['1de84a854e2b440dc53bf42f8548afa4c17da332'], a
-
-    #test "test_branch3" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, 'HEAD', nil)
-    assert_equal ['1de84a854e2b440dc53bf42f8548afa4c17da332'], a
-
-    #test "test_single_revision_repo" do
-    a = CommitsHelper::find_commit_range('active/foo', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
-    assert_equal ['31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-    a = CommitsHelper::find_commit_range('arvados', nil, '31ce37fe365b3dc204300a3e4c396ad333ed0556', nil)
-    assert_equal [], a
-
-    #test "test_multi_revision" do
-    # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-    a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', nil)
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '4fe459abe02d9b365932b8f5dc419439ab4e2577', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    #test "test_tag" do
-    # complains "fatal: ambiguous argument 'tag1': unknown revision or path
-    # not in the working tree."
-    a = CommitsHelper::find_commit_range('active/foo', 'tag1', 'main', nil)
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '4fe459abe02d9b365932b8f5dc419439ab4e2577'], a
-
-    #test "test_multi_revision_exclude" do
-    a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', ['4fe459abe02d9b365932b8f5dc419439ab4e2577'])
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    #test "test_multi_revision_tagged_exclude" do
-    # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-    a = CommitsHelper::find_commit_range('active/foo', '31ce37fe365b3dc204300a3e4c396ad333ed0556', '077ba2ad3ea24a929091a9e6ce545c93199b8e57', ['tag1'])
-    assert_equal ['077ba2ad3ea24a929091a9e6ce545c93199b8e57', '31ce37fe365b3dc204300a3e4c396ad333ed0556'], a
-
-    Dir.mktmpdir do |touchdir|
-      # invalid input to maximum
-      a = CommitsHelper::find_commit_range('active/foo', nil, "31ce37fe365b3dc204300a3e4c396ad333ed0556 ; touch #{touchdir}/uh_oh", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'maximum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to maximum
-      a = CommitsHelper::find_commit_range('active/foo', nil, "$(uname>#{touchdir}/uh_oh)", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'maximum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to minimum
-      a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556 ; touch #{touchdir}/uh_oh", "31ce37fe365b3dc204300a3e4c396ad333ed0556", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'minimum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to minimum
-      a = CommitsHelper::find_commit_range('active/foo', "$(uname>#{touchdir}/uh_oh)", "31ce37fe365b3dc204300a3e4c396ad333ed0556", nil)
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'minimum' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to 'excludes'
-      # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-      a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556", "077ba2ad3ea24a929091a9e6ce545c93199b8e57", ["4fe459abe02d9b365932b8f5dc419439ab4e2577 ; touch #{touchdir}/uh_oh"])
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'excludes' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-
-      # invalid input to 'excludes'
-      # complains "fatal: bad object 077ba2ad3ea24a929091a9e6ce545c93199b8e57"
-      a = CommitsHelper::find_commit_range('active/foo', "31ce37fe365b3dc204300a3e4c396ad333ed0556", "077ba2ad3ea24a929091a9e6ce545c93199b8e57", ["$(uname>#{touchdir}/uh_oh)"])
-      assert !File.exist?("#{touchdir}/uh_oh"), "#{touchdir}/uh_oh should not exist, 'excludes' parameter of find_commit_range is exploitable"
-      assert_equal [], a
-    end
-  end
-end
index d25c08a579efa650c4f5ec57d2d597b48483a507..fa7910d597de2d0f38e8a24c56683503d37257e4 100644 (file)
@@ -112,11 +112,15 @@ class ContainerRequestTest < ActiveSupport::TestCase
     {"mounts" => {"FOO" => {}}},
     {"mounts" => {"FOO" => {"kind" => "tmp", "capacity" => 42.222}}},
     {"command" => ["echo", 55]},
     {"mounts" => {"FOO" => {}}},
     {"mounts" => {"FOO" => {"kind" => "tmp", "capacity" => 42.222}}},
     {"command" => ["echo", 55]},
-    {"environment" => {"FOO" => 55}}
+    {"environment" => {"FOO" => 55}},
+    {"output_glob" => [false]},
+    {"output_glob" => [["bad"]]},
+    {"output_glob" => "bad"},
+    {"output_glob" => ["nope", -1]},
   ].each do |value|
     test "Create with invalid #{value}" do
       set_user_from_auth :active
   ].each do |value|
     test "Create with invalid #{value}" do
       set_user_from_auth :active
-      assert_raises(ActiveRecord::RecordInvalid) do
+      assert_raises(ActiveRecord::RecordInvalid, Serializer::TypeMismatch) do
         cr = create_minimal_req!({state: "Committed",
                priority: 1}.merge(value))
         cr.save!
         cr = create_minimal_req!({state: "Committed",
                priority: 1}.merge(value))
         cr.save!
@@ -127,7 +131,7 @@ class ContainerRequestTest < ActiveSupport::TestCase
       set_user_from_auth :active
       cr = create_minimal_req!(state: "Uncommitted", priority: 1)
       cr.save!
       set_user_from_auth :active
       cr = create_minimal_req!(state: "Uncommitted", priority: 1)
       cr.save!
-      assert_raises(ActiveRecord::RecordInvalid) do
+      assert_raises(ActiveRecord::RecordInvalid, Serializer::TypeMismatch) do
         cr = ContainerRequest.find_by_uuid cr.uuid
         cr.update!({state: "Committed",
                                priority: 1}.merge(value))
         cr = ContainerRequest.find_by_uuid cr.uuid
         cr.update!({state: "Committed",
                                priority: 1}.merge(value))
index 09b885b391efc4faa83b5f50cb01ac838ada0d8a..88496b793f22ca26d3a3857d2fc9a14a84b6ce1d 100644 (file)
@@ -22,6 +22,7 @@ class ContainerTest < ActiveSupport::TestCase
     cwd: "test",
     command: ["echo", "hello"],
     output_path: "test",
     cwd: "test",
     command: ["echo", "hello"],
     output_path: "test",
+    output_glob: [],
     runtime_constraints: {
       "API" => false,
       "keep_cache_disk" => 0,
     runtime_constraints: {
       "API" => false,
       "keep_cache_disk" => 0,
@@ -48,6 +49,7 @@ class ContainerTest < ActiveSupport::TestCase
     environment: {},
     mounts: {},
     output_path: "test",
     environment: {},
     mounts: {},
     output_path: "test",
+    output_glob: [],
     runtime_auth_scopes: ["all"],
     runtime_constraints: {
       "API" => false,
     runtime_auth_scopes: ["all"],
     runtime_constraints: {
       "API" => false,
@@ -333,7 +335,7 @@ class ContainerTest < ActiveSupport::TestCase
     set_user_from_auth :dispatch1
 
     out1 = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
     set_user_from_auth :dispatch1
 
     out1 = '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
-    log1 = collections(:real_log_collection).portable_data_hash
+    log1 = collections(:log_collection).portable_data_hash
     c_output1.update!({state: Container::Locked})
     c_output1.update!({state: Container::Running})
     c_output1.update!(completed_attrs.merge({log: log1, output: out1}))
     c_output1.update!({state: Container::Locked})
     c_output1.update!({state: Container::Running})
     c_output1.update!(completed_attrs.merge({log: log1, output: out1}))
@@ -845,7 +847,7 @@ class ContainerTest < ActiveSupport::TestCase
     assert c.lock, show_errors(c)
     assert c.update(
              state: Container::Cancelled,
     assert c.lock, show_errors(c)
     assert c.update(
              state: Container::Cancelled,
-             log: collections(:real_log_collection).portable_data_hash,
+             log: collections(:log_collection).portable_data_hash,
            ), show_errors(c)
     check_no_change_from_cancelled c
   end
            ), show_errors(c)
     check_no_change_from_cancelled c
   end
@@ -933,7 +935,7 @@ class ContainerTest < ActiveSupport::TestCase
 
   test "locked_by_uuid can update log when locked/running, and output when running" do
     set_user_from_auth :active
 
   test "locked_by_uuid can update log when locked/running, and output when running" do
     set_user_from_auth :active
-    logcoll = collections(:real_log_collection)
+    logcoll = collections(:container_log_collection)
     c, cr1 = minimal_new
     cr2 = ContainerRequest.new(DEFAULT_ATTRS)
     cr2.state = ContainerRequest::Committed
     c, cr1 = minimal_new
     cr2 = ContainerRequest.new(DEFAULT_ATTRS)
     cr2.state = ContainerRequest::Committed
@@ -975,8 +977,8 @@ class ContainerTest < ActiveSupport::TestCase
     assert_equal cr1log_uuid, cr1.log_uuid
     assert_equal cr2log_uuid, cr2.log_uuid
     assert_equal 1, Collection.where(uuid: [cr1log_uuid, cr2log_uuid]).to_a.collect(&:portable_data_hash).uniq.length
     assert_equal cr1log_uuid, cr1.log_uuid
     assert_equal cr2log_uuid, cr2.log_uuid
     assert_equal 1, Collection.where(uuid: [cr1log_uuid, cr2log_uuid]).to_a.collect(&:portable_data_hash).uniq.length
-    assert_equal ". acbd18db4cc2f85cedef654fccc4a4d8+3 cdd549ae79fe6640fa3d5c6261d8303c+195 0:3:foo.txt 3:195:zzzzz-8i9sb-0vsrcqi7whchuil.log.txt
-./log\\040for\\040container\\040#{cr1.container_uuid} acbd18db4cc2f85cedef654fccc4a4d8+3 cdd549ae79fe6640fa3d5c6261d8303c+195 0:3:foo.txt 3:195:zzzzz-8i9sb-0vsrcqi7whchuil.log.txt
+    assert_equal ". 8c12f5f5297b7337598170c6f531fcee+7882 acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 7882:3:foo.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt
+./log\\040for\\040container\\040#{cr1.container_uuid} 8c12f5f5297b7337598170c6f531fcee+7882 acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:arv-mount.txt 0:1910:container.json 1910:1264:crunch-run.txt 3174:1005:crunchstat.txt 7882:3:foo.txt 4179:659:hoststat.txt 4838:2811:node-info.txt 7649:233:node.json 0:0:stderr.txt
 ", Collection.find_by_uuid(cr1log_uuid).manifest_text
   end
 
 ", Collection.find_by_uuid(cr1log_uuid).manifest_text
   end
 
@@ -1013,7 +1015,7 @@ class ContainerTest < ActiveSupport::TestCase
       assert c.update(runtime_status: {'warning' => 'something happened'})
       assert c.update(progress: 0.5)
       assert c.update(exit_code: 0)
       assert c.update(runtime_status: {'warning' => 'something happened'})
       assert c.update(progress: 0.5)
       assert c.update(exit_code: 0)
-      refute c.update(log: collections(:real_log_collection).portable_data_hash)
+      refute c.update(log: collections(:log_collection).portable_data_hash)
       c.reload
       assert c.update(state: Container::Complete, exit_code: 0)
     end
       c.reload
       assert c.update(state: Container::Complete, exit_code: 0)
     end
index 36f42006ff11511c3549286e31bdb21897cc1fbf..e03ca8da05eebd48d14c318adc55a7b9ab4e9877 100644 (file)
@@ -18,13 +18,13 @@ class GroupTest < ActiveSupport::TestCase
     assert g.save, "active user should be able to modify group #{g.uuid}"
 
     # Use the group as the owner of a new object
     assert g.save, "active user should be able to modify group #{g.uuid}"
 
     # Use the group as the owner of a new object
-    s = Specimen.
+    s = Collection.
       create(owner_uuid: groups(:bad_group_has_ownership_cycle_b).uuid)
     assert s.valid?, "ownership should pass validation #{s.errors.messages}"
     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
 
     # Use the group as the new owner of an existing object
       create(owner_uuid: groups(:bad_group_has_ownership_cycle_b).uuid)
     assert s.valid?, "ownership should pass validation #{s.errors.messages}"
     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
 
     # Use the group as the new owner of an existing object
-    s = specimens(:in_aproject)
+    s = collections(:collection_owned_by_active)
     s.owner_uuid = groups(:bad_group_has_ownership_cycle_b).uuid
     assert s.valid?, "ownership should pass validation"
     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
     s.owner_uuid = groups(:bad_group_has_ownership_cycle_b).uuid
     assert s.valid?, "ownership should pass validation"
     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
diff --git a/services/api/test/unit/helpers/api_client_authorizations_helper_test.rb b/services/api/test/unit/helpers/api_client_authorizations_helper_test.rb
deleted file mode 100644 (file)
index 01ed430..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class ApiClientAuthorizationsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/api_clients_helper_test.rb b/services/api/test/unit/helpers/api_clients_helper_test.rb
deleted file mode 100644 (file)
index 4901fb4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class ApiClientsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/authorized_keys_helper_test.rb b/services/api/test/unit/helpers/authorized_keys_helper_test.rb
deleted file mode 100644 (file)
index 010a0fe..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class AuthorizedKeysHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/collections_helper_test.rb b/services/api/test/unit/helpers/collections_helper_test.rb
deleted file mode 100644 (file)
index dd01ca7..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CollectionsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/commit_ancestors_helper_test.rb b/services/api/test/unit/helpers/commit_ancestors_helper_test.rb
deleted file mode 100644 (file)
index 423dbf6..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CommitAncestorsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/commits_helper_test.rb b/services/api/test/unit/helpers/commits_helper_test.rb
deleted file mode 100644 (file)
index fd960a8..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class CommitsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/groups_helper_test.rb b/services/api/test/unit/helpers/groups_helper_test.rb
deleted file mode 100644 (file)
index ce7a3fa..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class GroupsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/humans_helper_test.rb b/services/api/test/unit/helpers/humans_helper_test.rb
deleted file mode 100644 (file)
index 22f9e81..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class HumansHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/job_tasks_helper_test.rb b/services/api/test/unit/helpers/job_tasks_helper_test.rb
deleted file mode 100644 (file)
index af0302c..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobTasksHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/jobs_helper_test.rb b/services/api/test/unit/helpers/jobs_helper_test.rb
deleted file mode 100644 (file)
index 9d64b7d..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/keep_disks_helper_test.rb b/services/api/test/unit/helpers/keep_disks_helper_test.rb
deleted file mode 100644 (file)
index 9dcc619..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class KeepDisksHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/links_helper_test.rb b/services/api/test/unit/helpers/links_helper_test.rb
deleted file mode 100644 (file)
index 918f145..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class LinksHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/logs_helper_test.rb b/services/api/test/unit/helpers/logs_helper_test.rb
deleted file mode 100644 (file)
index 616f6e6..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class LogsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/nodes_helper_test.rb b/services/api/test/unit/helpers/nodes_helper_test.rb
deleted file mode 100644 (file)
index 8a92eb9..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class NodesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/pipeline_instances_helper_test.rb b/services/api/test/unit/helpers/pipeline_instances_helper_test.rb
deleted file mode 100644 (file)
index 9d3b5c4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelineInstancesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/pipeline_templates_helper_test.rb b/services/api/test/unit/helpers/pipeline_templates_helper_test.rb
deleted file mode 100644 (file)
index 9a9a417..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelinesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/repositories_helper_test.rb b/services/api/test/unit/helpers/repositories_helper_test.rb
deleted file mode 100644 (file)
index 33cb590..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class RepositoriesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/specimens_helper_test.rb b/services/api/test/unit/helpers/specimens_helper_test.rb
deleted file mode 100644 (file)
index 3709198..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class SpecimensHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/traits_helper_test.rb b/services/api/test/unit/helpers/traits_helper_test.rb
deleted file mode 100644 (file)
index 03b6a97..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class TraitsHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/helpers/virtual_machines_helper_test.rb b/services/api/test/unit/helpers/virtual_machines_helper_test.rb
deleted file mode 100644 (file)
index 99fc258..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class VirtualMachinesHelperTest < ActionView::TestCase
-end
diff --git a/services/api/test/unit/human_test.rb b/services/api/test/unit/human_test.rb
deleted file mode 100644 (file)
index 83cc40e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class HumanTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/job_task_test.rb b/services/api/test/unit/job_task_test.rb
deleted file mode 100644 (file)
index 36a0e72..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class JobTaskTest < ActiveSupport::TestCase
-
-end
diff --git a/services/api/test/unit/job_test.rb b/services/api/test/unit/job_test.rb
deleted file mode 100644 (file)
index 815079f..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-require 'helpers/docker_migration_helper'
-
-class JobTest < ActiveSupport::TestCase
-  include DockerMigrationHelper
-  include GitTestHelper
-
-  BAD_COLLECTION = "#{'f' * 32}+0"
-
-  setup do
-    set_user_from_auth :active
-  end
-
-  def job_attrs merge_me={}
-    # Default (valid) set of attributes, with given overrides
-    {
-      script: "hash",
-      script_version: "main",
-      repository: "active/foo",
-    }.merge(merge_me)
-  end
-
-  test "Job without Docker image doesn't get locator" do
-    job = Job.new job_attrs
-    assert job.valid?, job.errors.full_messages.to_s
-    assert_nil job.docker_image_locator
-  end
-
-  { 'name' => [:links, :docker_image_collection_tag, :name],
-    'hash' => [:links, :docker_image_collection_hash, :name],
-    'locator' => [:collections, :docker_image, :portable_data_hash],
-  }.each_pair do |spec_type, (fixture_type, fixture_name, fixture_attr)|
-    test "Job initialized with Docker image #{spec_type} gets locator" do
-      image_spec = send(fixture_type, fixture_name).send(fixture_attr)
-      job = Job.new job_attrs(runtime_constraints:
-                              {'docker_image' => image_spec})
-      assert job.valid?, job.errors.full_messages.to_s
-      assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-    end
-
-    test "Job modified with Docker image #{spec_type} gets locator" do
-      job = Job.new job_attrs
-      assert job.valid?, job.errors.full_messages.to_s
-      assert_nil job.docker_image_locator
-      image_spec = send(fixture_type, fixture_name).send(fixture_attr)
-      job.runtime_constraints['docker_image'] = image_spec
-      assert job.valid?, job.errors.full_messages.to_s
-      assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-    end
-  end
-
-  test "removing a Docker runtime constraint removes the locator" do
-    image_locator = collections(:docker_image).portable_data_hash
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_locator})
-    assert job.valid?, job.errors.full_messages.to_s
-    assert_equal(image_locator, job.docker_image_locator)
-    job.runtime_constraints = {}
-    assert job.valid?, job.errors.full_messages.to_s + "after clearing runtime constraints"
-    assert_nil job.docker_image_locator
-  end
-
-  test "locate a Docker image with a repository + tag" do
-    image_repo, image_tag =
-      links(:docker_image_collection_tag2).name.split(':', 2)
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_repo,
-                              'docker_image_tag' => image_tag})
-    assert job.valid?, job.errors.full_messages.to_s
-    assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-  end
-
-  test "can't locate a Docker image with a nonexistent tag" do
-    image_repo = links(:docker_image_collection_tag).name
-    image_tag = '__nonexistent tag__'
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_repo,
-                              'docker_image_tag' => image_tag})
-    assert(job.invalid?, "Job with bad Docker tag valid")
-  end
-
-  [
-    false,
-    true
-  ].each do |use_config|
-    test "Job with no Docker image uses default docker image when configuration is set #{use_config}" do
-      default_docker_image = collections(:docker_image)[:portable_data_hash]
-      Rails.configuration.Containers.JobsAPI.DefaultDockerImage = default_docker_image if use_config
-
-      job = Job.new job_attrs
-      assert job.valid?, job.errors.full_messages.to_s
-
-      if use_config
-        refute_nil job.docker_image_locator
-        assert_equal default_docker_image, job.docker_image_locator
-      else
-        assert_nil job.docker_image_locator
-      end
-    end
-  end
-
-  test "locate a Docker image with a partial hash" do
-    image_hash = links(:docker_image_collection_hash).name[0..24]
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => image_hash})
-    assert job.valid?, job.errors.full_messages.to_s + " with partial hash #{image_hash}"
-    assert_equal(collections(:docker_image).portable_data_hash, job.docker_image_locator)
-  end
-
-  { 'name' => 'arvados_test_nonexistent',
-    'hash' => 'f' * 64,
-    'locator' => BAD_COLLECTION,
-  }.each_pair do |spec_type, image_spec|
-    test "Job validation fails with nonexistent Docker image #{spec_type}" do
-      Rails.configuration.RemoteClusters = ConfigLoader.to_OrderedOptions({})
-      job = Job.new job_attrs(runtime_constraints:
-                              {'docker_image' => image_spec})
-      assert(job.invalid?, "nonexistent Docker image #{spec_type} #{image_spec} was valid")
-    end
-  end
-
-  test "Job validation fails with non-Docker Collection constraint" do
-    job = Job.new job_attrs(runtime_constraints:
-                            {'docker_image' => collections(:foo_file).uuid})
-    assert(job.invalid?, "non-Docker Collection constraint was valid")
-  end
-
-  test "can create Job with Docker image Collection without Docker links" do
-    image_uuid = collections(:unlinked_docker_image).portable_data_hash
-    job = Job.new job_attrs(runtime_constraints: {"docker_image" => image_uuid})
-    assert(job.valid?, "Job created with unlinked Docker image was invalid")
-    assert_equal(image_uuid, job.docker_image_locator)
-  end
-
-  def check_attrs_unset(job, attrs)
-    assert_empty(attrs.each_key.map { |key| job.send(key) }.compact,
-                 "job has values for #{attrs.keys}")
-  end
-
-  def check_creation_prohibited(attrs)
-    begin
-      job = Job.new(job_attrs(attrs))
-    rescue ActiveModel::MassAssignmentSecurity::Error
-      # Test passes - expected attribute protection
-    else
-      check_attrs_unset(job, attrs)
-    end
-  end
-
-  def check_modification_prohibited(attrs)
-    job = Job.new(job_attrs)
-    attrs.each_pair do |key, value|
-      assert_raises(NoMethodError) { job.send("{key}=".to_sym, value) }
-    end
-    check_attrs_unset(job, attrs)
-  end
-
-  test "can't create Job with Docker image locator" do
-    check_creation_prohibited(docker_image_locator: BAD_COLLECTION)
-  end
-
-  test "can't assign Docker image locator to Job" do
-    check_modification_prohibited(docker_image_locator: BAD_COLLECTION)
-  end
-
-  SDK_MASTER = "ca68b24e51992e790f29df5cc4bc54ce1da4a1c2"
-  SDK_TAGGED = "00634b2b8a492d6f121e3cf1d6587b821136a9a7"
-
-  def sdk_constraint(version)
-    {runtime_constraints: {
-        "arvados_sdk_version" => version,
-        "docker_image" => links(:docker_image_collection_tag).name,
-      }}
-  end
-
-  def check_job_sdk_version(expected)
-    job = yield
-    if expected.nil?
-      refute(job.valid?, "job valid with bad Arvados SDK version")
-    else
-      assert(job.valid?, "job not valid with good Arvados SDK version")
-      assert_equal(expected, job.arvados_sdk_version)
-    end
-  end
-
-  test "can't create job with SDK version assigned directly" do
-    check_creation_prohibited(arvados_sdk_version: SDK_MASTER)
-  end
-
-  test "can't modify job to assign SDK version directly" do
-    check_modification_prohibited(arvados_sdk_version: SDK_MASTER)
-  end
-
-  test 'script_parameters_digest is independent of key order' do
-    j1 = Job.new(job_attrs(script_parameters: {'a' => 'a', 'ddee' => {'d' => 'd', 'e' => 'e'}}))
-    j2 = Job.new(job_attrs(script_parameters: {'ddee' => {'e' => 'e', 'd' => 'd'}, 'a' => 'a'}))
-    assert j1.valid?
-    assert j2.valid?
-    assert_equal(j1.script_parameters_digest, j2.script_parameters_digest)
-  end
-
-  test 'job fixtures have correct script_parameters_digest' do
-    Job.all.each do |j|
-      d = j.script_parameters_digest
-      assert_equal(j.update_script_parameters_digest, d,
-                   "wrong script_parameters_digest for #{j.uuid}")
-    end
-  end
-
-  test 'deep_sort_hash on array of hashes' do
-    a = {'z' => [[{'a' => 'a', 'b' => 'b'}]]}
-    b = {'z' => [[{'b' => 'b', 'a' => 'a'}]]}
-    assert_equal Job.deep_sort_hash(a).to_json, Job.deep_sort_hash(b).to_json
-  end
-
-  def try_find_reusable
-    foobar = jobs(:foobar)
-    example_attrs = {
-      script_version: foobar.script_version,
-      script: foobar.script,
-      script_parameters: foobar.script_parameters,
-      repository: foobar.repository,
-    }
-
-    # Two matching jobs exist with identical outputs. The older one
-    # should be reused.
-    j = Job.find_reusable(example_attrs, {}, [], [users(:active)])
-    assert j
-    assert_equal foobar.uuid, j.uuid
-
-    # Two matching jobs exist with different outputs. Neither should
-    # be reused.
-    Job.where(uuid: jobs(:job_with_latest_version).uuid).
-      update_all(output: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+1')
-    assert_nil Job.find_reusable(example_attrs, {}, [], [users(:active)])
-
-    # ...unless config says to reuse the earlier job in such cases.
-    Rails.configuration.Containers.JobsAPI.ReuseJobIfOutputsDiffer = true
-    j = Job.find_reusable(example_attrs, {}, [], [users(:active)])
-    assert_equal foobar.uuid, j.uuid
-  end
-
-  test 'enable legacy api configuration option = true' do
-    Rails.configuration.Containers.JobsAPI.Enable = "true"
-    check_enable_legacy_jobs_api
-    assert_equal(Disable_update_jobs_api_method_list, Rails.configuration.API.DisabledAPIs)
-  end
-
-  test 'enable legacy api configuration option = false' do
-    Rails.configuration.Containers.JobsAPI.Enable = "false"
-    check_enable_legacy_jobs_api
-    assert_equal Disable_jobs_api_method_list, Rails.configuration.API.DisabledAPIs
-  end
-
-  test 'enable legacy api configuration option = auto, has jobs' do
-    Rails.configuration.Containers.JobsAPI.Enable = "auto"
-    assert Job.count > 0
-    check_enable_legacy_jobs_api
-    assert_equal(Disable_update_jobs_api_method_list, Rails.configuration.API.DisabledAPIs)
-  end
-
-  test 'enable legacy api configuration option = auto, no jobs' do
-    Rails.configuration.Containers.JobsAPI.Enable = "auto"
-    act_as_system_user do
-      Job.destroy_all
-    end
-    assert_equal 0, Job.count
-    assert_equal({}, Rails.configuration.API.DisabledAPIs)
-    check_enable_legacy_jobs_api
-    assert_equal Disable_jobs_api_method_list, Rails.configuration.API.DisabledAPIs
-  end
-end
index b9806486adb381596b024a2ff2258d2d91b164b1..fb0d0283377a6828a972629edc8447f1177a3bc3 100644 (file)
@@ -13,7 +13,7 @@ class LinkTest < ActiveSupport::TestCase
 
   test "cannot delete an object referenced by unwritable links" do
     ob = act_as_user users(:active) do
 
   test "cannot delete an object referenced by unwritable links" do
     ob = act_as_user users(:active) do
-      Specimen.create
+      Collection.create
     end
     link = act_as_user users(:admin) do
       Link.create(tail_uuid: users(:active).uuid,
     end
     link = act_as_user users(:admin) do
       Link.create(tail_uuid: users(:active).uuid,
index d3a1b618d5e8d7ff9ed1c160627a468641997f5e..e69f0517e451577b548400d59d0666a97a7a2211 100644 (file)
@@ -32,6 +32,10 @@ class LogTest < ActiveSupport::TestCase
     Log.where(object_uuid: thing.uuid).order("created_at ASC").all
   end
 
     Log.where(object_uuid: thing.uuid).order("created_at ASC").all
   end
 
+  def clear_logs_about(thing)
+    Log.where(object_uuid: thing.uuid).delete_all
+  end
+
   def assert_logged(thing, event_type)
     logs = get_logs_about(thing)
     assert_equal(@log_count, logs.size, "log count mismatch")
   def assert_logged(thing, event_type)
     logs = get_logs_about(thing)
     assert_equal(@log_count, logs.size, "log count mismatch")
@@ -106,10 +110,11 @@ class LogTest < ActiveSupport::TestCase
 
   test "old_attributes preserves values deep inside a hash" do
     set_user_from_auth :active
 
   test "old_attributes preserves values deep inside a hash" do
     set_user_from_auth :active
-    it = specimens(:owned_by_active_user)
+    it = collections(:collection_owned_by_active)
+    clear_logs_about it
     it.properties = {'foo' => {'bar' => ['baz', 'qux', {'quux' => 'bleat'}]}}
     it.save!
     it.properties = {'foo' => {'bar' => ['baz', 'qux', {'quux' => 'bleat'}]}}
     it.save!
-    @log_count += 1
+    assert_logged it, :update
     it.properties['foo']['bar'][2]['quux'] = 'blert'
     it.save!
     assert_logged it, :update do |props|
     it.properties['foo']['bar'][2]['quux'] = 'blert'
     it.save!
     assert_logged it, :update do |props|
@@ -231,6 +236,7 @@ class LogTest < ActiveSupport::TestCase
   test "don't log changes only to Collection.preserve_version" do
     set_user_from_auth :admin_trustedclient
     col = collections(:collection_owned_by_active)
   test "don't log changes only to Collection.preserve_version" do
     set_user_from_auth :admin_trustedclient
     col = collections(:collection_owned_by_active)
+    clear_logs_about col
     start_log_count = get_logs_about(col).size
     assert_equal false, col.preserve_version
     col.preserve_version = true
     start_log_count = get_logs_about(col).size
     assert_equal false, col.preserve_version
     col.preserve_version = true
@@ -258,27 +264,29 @@ class LogTest < ActiveSupport::TestCase
 
   test "use ownership and permission links to determine which logs a user can see" do
     known_logs = [:noop,
 
   test "use ownership and permission links to determine which logs a user can see" do
     known_logs = [:noop,
-                  :admin_changes_repository2,
-                  :admin_changes_specimen,
+                  :admin_changes_collection_owned_by_active,
+                  :admin_changes_collection_owned_by_foo,
                   :system_adds_foo_file,
                   :system_adds_baz,
                   :log_owned_by_active,
                   :system_adds_foo_file,
                   :system_adds_baz,
                   :log_owned_by_active,
-                  :crunchstat_for_running_job]
+                  :crunchstat_for_running_container]
 
     c = Log.readable_by(users(:admin)).order("id asc").each.to_a
     assert_log_result c, known_logs, known_logs
 
     c = Log.readable_by(users(:active)).order("id asc").each.to_a
 
     c = Log.readable_by(users(:admin)).order("id asc").each.to_a
     assert_log_result c, known_logs, known_logs
 
     c = Log.readable_by(users(:active)).order("id asc").each.to_a
-    assert_log_result c, known_logs, [:admin_changes_repository2, # owned by active
-                                      :system_adds_foo_file,      # readable via link
-                                      :system_adds_baz,           # readable via 'all users' group
-                                      :log_owned_by_active,       # log owned by active
-                                      :crunchstat_for_running_job] # log & job owned by active
+    assert_log_result c, known_logs, [:admin_changes_collection_owned_by_active,
+                                      :system_adds_foo_file,             # readable via link
+                                      :system_adds_baz,                  # readable via 'all users' group
+                                      :log_owned_by_active,              # log owned by active
+                                      :crunchstat_for_running_container] # log & job owned by active
 
     c = Log.readable_by(users(:spectator)).order("id asc").each.to_a
 
     c = Log.readable_by(users(:spectator)).order("id asc").each.to_a
-    assert_log_result c, known_logs, [:noop,                   # object_uuid is spectator
-                                      :admin_changes_specimen, # object_uuid is a specimen owned by spectator
-                                      :system_adds_baz] # readable via 'all users' group
+    assert_log_result c, known_logs, [:noop,                             # object_uuid is spectator
+                                      :system_adds_baz]                  # readable via 'all users' group
+
+    c = Log.readable_by(users(:user_foo_in_sharing_group)).order("id asc").each.to_a
+    assert_log_result c, known_logs, [:admin_changes_collection_owned_by_foo] # collection's parent is readable via role group
   end
 
   def assert_log_result result, known_logs, expected_logs
   end
 
   def assert_log_result result, known_logs, expected_logs
index 1c1bd93b8169484b78ffe4948c69512c1c89bc4b..a96170c7162e137d0817eccfce67f3f5a069db25 100644 (file)
@@ -11,7 +11,7 @@ require 'test_helper'
 # "i" is an item.
 
 class OwnerTest < ActiveSupport::TestCase
 # "i" is an item.
 
 class OwnerTest < ActiveSupport::TestCase
-  fixtures :users, :groups, :specimens
+  fixtures :users, :groups
 
   setup do
     set_user_from_auth :admin_trustedclient
 
   setup do
     set_user_from_auth :admin_trustedclient
@@ -26,22 +26,22 @@ class OwnerTest < ActiveSupport::TestCase
       else
         o = o_class.create!
       end
       else
         o = o_class.create!
       end
-      i = Specimen.create(owner_uuid: o.uuid)
+      i = Collection.create(owner_uuid: o.uuid)
       assert i.valid?, "new item should pass validation"
       assert i.uuid, "new item should have an ID"
       assert i.valid?, "new item should pass validation"
       assert i.uuid, "new item should have an ID"
-      assert Specimen.where(uuid: i.uuid).any?, "new item should really be in DB"
+      assert Collection.where(uuid: i.uuid).any?, "new item should really be in DB"
     end
 
     test "create object with non-existent #{o_class} owner" do
       assert_raises(ActiveRecord::RecordInvalid,
                     "create should fail with random owner_uuid") do
     end
 
     test "create object with non-existent #{o_class} owner" do
       assert_raises(ActiveRecord::RecordInvalid,
                     "create should fail with random owner_uuid") do
-        Specimen.create!(owner_uuid: o_class.generate_uuid)
+        Collection.create!(owner_uuid: o_class.generate_uuid)
       end
 
       end
 
-      i = Specimen.create(owner_uuid: o_class.generate_uuid)
+      i = Collection.create(owner_uuid: o_class.generate_uuid)
       assert !i.valid?, "object with random owner_uuid should not be valid?"
 
       assert !i.valid?, "object with random owner_uuid should not be valid?"
 
-      i = Specimen.new(owner_uuid: o_class.generate_uuid)
+      i = Collection.new(owner_uuid: o_class.generate_uuid)
       assert !i.valid?, "new item should not pass validation"
       assert !i.uuid, "new item should not have an ID"
     end
       assert !i.valid?, "new item should not pass validation"
       assert !i.uuid, "new item should not have an ID"
     end
@@ -53,7 +53,7 @@ class OwnerTest < ActiveSupport::TestCase
             else
               o_class.create!
             end
             else
               o_class.create!
             end
-        i = Specimen.create!(owner_uuid: o.uuid)
+        i = Collection.create!(owner_uuid: o.uuid)
 
         new_o = if new_o_class == Group
               new_o_class.create! group_class: "project"
 
         new_o = if new_o_class == Group
               new_o_class.create! group_class: "project"
@@ -61,7 +61,7 @@ class OwnerTest < ActiveSupport::TestCase
               new_o_class.create!
             end
 
               new_o_class.create!
             end
 
-        assert(Specimen.where(uuid: i.uuid).any?,
+        assert(Collection.where(uuid: i.uuid).any?,
                "new item should really be in DB")
         assert(i.update(owner_uuid: new_o.uuid),
                "should change owner_uuid from #{o.uuid} to #{new_o.uuid}")
                "new item should really be in DB")
         assert(i.update(owner_uuid: new_o.uuid),
                "should change owner_uuid from #{o.uuid} to #{new_o.uuid}")
@@ -102,7 +102,7 @@ class OwnerTest < ActiveSupport::TestCase
   ['users(:active)', 'groups(:aproject)'].each do |ofixt|
     test "delete #{ofixt} that owns other objects" do
       o = eval ofixt
   ['users(:active)', 'groups(:aproject)'].each do |ofixt|
     test "delete #{ofixt} that owns other objects" do
       o = eval ofixt
-      assert_equal(true, Specimen.where(owner_uuid: o.uuid).any?,
+      assert_equal(true, Collection.where(owner_uuid: o.uuid).any?,
                    "need something to be owned by #{o.uuid} for this test")
 
       skip_check_permissions_against_full_refresh do
                    "need something to be owned by #{o.uuid} for this test")
 
       skip_check_permissions_against_full_refresh do
@@ -115,7 +115,7 @@ class OwnerTest < ActiveSupport::TestCase
 
     test "change uuid of #{ofixt} that owns other objects" do
       o = eval ofixt
 
     test "change uuid of #{ofixt} that owns other objects" do
       o = eval ofixt
-      assert_equal(true, Specimen.where(owner_uuid: o.uuid).any?,
+      assert_equal(true, Collection.where(owner_uuid: o.uuid).any?,
                    "need something to be owned by #{o.uuid} for this test")
       new_uuid = o.uuid.sub(/..........$/, rand(2**256).to_s(36)[0..9])
       assert(!o.update(uuid: new_uuid),
                    "need something to be owned by #{o.uuid} for this test")
       new_uuid = o.uuid.sub(/..........$/, rand(2**256).to_s(36)[0..9])
       assert(!o.update(uuid: new_uuid),
index 14c810d81ae363e4b3f45f166f48a8a11a7898fd..305989606198889f9098923c11d4c72dbe5681cd 100644 (file)
@@ -222,7 +222,7 @@ class PermissionTest < ActiveSupport::TestCase
     Rails.configuration.Users.ActivatedUsersAreVisibleToOthers = false
     manager = create :active_user, first_name: "Manage", last_name: "Er"
     minion = create :active_user, first_name: "Min", last_name: "Ion"
     Rails.configuration.Users.ActivatedUsersAreVisibleToOthers = false
     manager = create :active_user, first_name: "Manage", last_name: "Er"
     minion = create :active_user, first_name: "Min", last_name: "Ion"
-    minions_specimen = act_as_user minion do
+    minions_collection = act_as_user minion do
       g = Group.create! name: "minon project", group_class: "project"
       Collection.create! owner_uuid: g.uuid
     end
       g = Group.create! name: "minon project", group_class: "project"
       Collection.create! owner_uuid: g.uuid
     end
@@ -289,11 +289,11 @@ class PermissionTest < ActiveSupport::TestCase
       end
       assert_empty(Collection
                      .readable_by(manager)
       end
       assert_empty(Collection
                      .readable_by(manager)
-                     .where(uuid: minions_specimen.uuid),
+                     .where(uuid: minions_collection.uuid),
                    "manager saw the minion's private stuff")
       assert_raises(ArvadosModel::PermissionDeniedError,
                    "manager could update minion's private stuff") do
                    "manager saw the minion's private stuff")
       assert_raises(ArvadosModel::PermissionDeniedError,
                    "manager could update minion's private stuff") do
-        minions_specimen.update(properties: {'x' => 'y'})
+        minions_collection.update(properties: {'x' => 'y'})
       end
     end
 
       end
     end
 
@@ -307,11 +307,11 @@ class PermissionTest < ActiveSupport::TestCase
       # Now, manager can read and write Minion's stuff.
       assert_not_empty(Collection
                          .readable_by(manager)
       # Now, manager can read and write Minion's stuff.
       assert_not_empty(Collection
                          .readable_by(manager)
-                         .where(uuid: minions_specimen.uuid),
-                       "manager could not find minion's specimen by uuid")
+                         .where(uuid: minions_collection.uuid),
+                       "manager could not find minion's collection by uuid")
       assert_equal(true,
       assert_equal(true,
-                   minions_specimen.update(properties: {'x' => 'y'}),
-                   "manager could not update minion's specimen object")
+                   minions_collection.update(properties: {'x' => 'y'}),
+                   "manager could not update minion's collection object")
     end
   end
 
     end
   end
 
@@ -341,12 +341,12 @@ class PermissionTest < ActiveSupport::TestCase
     assert_not_empty(User.readable_by(a).where(uuid: b.uuid),
                      "#{a.first_name} should be able to see 'b' in the user list")
 
     assert_not_empty(User.readable_by(a).where(uuid: b.uuid),
                      "#{a.first_name} should be able to see 'b' in the user list")
 
-    a_specimen = act_as_user a do
+    a_collection = act_as_user a do
       Collection.create!
     end
       Collection.create!
     end
-    assert_not_empty(Collection.readable_by(a).where(uuid: a_specimen.uuid),
+    assert_not_empty(Collection.readable_by(a).where(uuid: a_collection.uuid),
                      "A cannot read own Collection, following test probably useless.")
                      "A cannot read own Collection, following test probably useless.")
-    assert_empty(Collection.readable_by(b).where(uuid: a_specimen.uuid),
+    assert_empty(Collection.readable_by(b).where(uuid: a_collection.uuid),
                  "B can read A's Collection")
     [a,b].each do |u|
       assert_empty(User.readable_by(u).where(uuid: other.uuid),
                  "B can read A's Collection")
     [a,b].each do |u|
       assert_empty(User.readable_by(u).where(uuid: other.uuid),
diff --git a/services/api/test/unit/pipeline_instance_test.rb b/services/api/test/unit/pipeline_instance_test.rb
deleted file mode 100644 (file)
index 614c169..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelineInstanceTest < ActiveSupport::TestCase
-
-  [:has_component_with_no_script_parameters,
-   :has_component_with_empty_script_parameters].each do |pi_name|
-    test "update pipeline that #{pi_name}" do
-      pi = pipeline_instances pi_name
-
-      Thread.current[:user] = users(:active)
-      assert_equal PipelineInstance::Ready, pi.state
-    end
-  end
-end
diff --git a/services/api/test/unit/pipeline_template_test.rb b/services/api/test/unit/pipeline_template_test.rb
deleted file mode 100644 (file)
index 8ead613..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class PipelineTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/repository_test.rb b/services/api/test/unit/repository_test.rb
deleted file mode 100644 (file)
index 674a34f..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-require 'helpers/git_test_helper'
-
-class RepositoryTest < ActiveSupport::TestCase
-  include GitTestHelper
-
-  def new_repo(owner_key, attrs={})
-    set_user_from_auth owner_key
-    owner = users(owner_key)
-    Repository.new({owner_uuid: owner.uuid}.merge(attrs))
-  end
-
-  def changed_repo(repo_key, changes)
-    repo = repositories(repo_key)
-    changes.each_pair { |attr, value| repo.send("#{attr}=".to_sym, value) }
-    repo
-  end
-
-  def default_git_url(repo_name, user_name=nil)
-    if user_name
-      "git@git.%s.arvadosapi.com:%s/%s.git" %
-        [Rails.configuration.ClusterID, user_name, repo_name]
-    else
-      "git@git.%s.arvadosapi.com:%s.git" %
-        [Rails.configuration.ClusterID, repo_name]
-    end
-  end
-
-  def assert_server_path(path_tail, repo_sym)
-    assert_equal(File.join(Rails.configuration.Git.Repositories, path_tail),
-                 repositories(repo_sym).server_path)
-  end
-
-  ### name validation
-
-  {active: "active/", admin: "admin/", system_user: ""}.
-      each_pair do |user_sym, name_prefix|
-    test "valid names for #{user_sym} repo" do
-      %w(a aa a0 aA Aa AA A0).each do |name|
-        repo = new_repo(user_sym, name: name_prefix + name)
-        assert(repo.valid?)
-      end
-    end
-
-    test "name is required for #{user_sym} repo" do
-      refute(new_repo(user_sym).valid?)
-    end
-
-    test "repo name beginning with numeral is invalid for #{user_sym}" do
-      repo = new_repo(user_sym, name: "#{name_prefix}0a")
-      refute(repo.valid?)
-    end
-
-    test "name containing bad char is invalid for #{user_sym}" do
-      "\\.-_/!@#$%^&*()[]{}".each_char do |bad_char|
-        repo = new_repo(user_sym, name: "#{name_prefix}bad#{bad_char}reponame")
-        refute(repo.valid?)
-      end
-    end
-  end
-
-  test "admin can create valid repo for other user with correct name prefix" do
-    owner = users(:active)
-    repo = new_repo(:admin, name: "#{owner.username}/validnametest",
-                    owner_uuid: owner.uuid)
-    assert(repo.valid?)
-  end
-
-  test "admin can create valid system repo without name prefix" do
-    repo = new_repo(:admin, name: "validnametest",
-                    owner_uuid: users(:system_user).uuid)
-    assert(repo.valid?)
-  end
-
-  test "repo name prefix must match owner_uuid username" do
-    repo = new_repo(:admin, name: "admin/badusernametest",
-                    owner_uuid: users(:active).uuid)
-    refute(repo.valid?)
-  end
-
-  test "repo name prefix must be empty for system repo" do
-    repo = new_repo(:admin, name: "root/badprefixtest",
-                    owner_uuid: users(:system_user).uuid)
-    refute(repo.valid?)
-  end
-
-  ### owner validation
-
-  test "name must be unique per user" do
-    repo = new_repo(:active, name: repositories(:foo).name)
-    refute(repo.valid?)
-  end
-
-  test "name can be duplicated across users" do
-    repo = new_repo(:active, name: "active/#{repositories(:arvados).name}")
-    assert(repo.valid?)
-  end
-
-  test "repository cannot be owned by a group" do
-    set_user_from_auth :active
-    repo = Repository.new(owner_uuid: groups(:all_users).uuid,
-                          name: "ownedbygroup")
-    refute(repo.valid?)
-    refute_empty(repo.errors[:owner_uuid] || [])
-  end
-
-  ### URL generation
-
-  test "fetch_url" do
-    repo = new_repo(:active, name: "active/fetchtest")
-    repo.save
-    assert_equal(default_git_url("fetchtest", "active"), repo.fetch_url)
-  end
-
-  test "fetch_url owned by system user" do
-    set_user_from_auth :admin
-    repo = Repository.new(owner_uuid: users(:system_user).uuid,
-                          name: "fetchtest")
-    repo.save
-    assert_equal(default_git_url("fetchtest"), repo.fetch_url)
-  end
-
-  test "push_url" do
-    repo = new_repo(:active, name: "active/pushtest")
-    repo.save
-    assert_equal(default_git_url("pushtest", "active"), repo.push_url)
-  end
-
-  test "push_url owned by system user" do
-    set_user_from_auth :admin
-    repo = Repository.new(owner_uuid: users(:system_user).uuid,
-                          name: "pushtest")
-    repo.save
-    assert_equal(default_git_url("pushtest"), repo.push_url)
-  end
-
-  ### Path generation
-
-  test "disk path stored by UUID" do
-    assert_server_path("zzzzz-s0uqq-382brsig8rp3666/.git", :foo)
-  end
-
-  test "disk path stored by name" do
-    assert_server_path("arvados/.git", :arvados)
-  end
-
-  test "disk path for repository not on disk" do
-    assert_nil(Repository.new.server_path)
-  end
-
-  ### Repository creation
-
-  test "non-admin can create a repository for themselves" do
-    repo = new_repo(:active, name: "active/newtestrepo")
-    assert(repo.save)
-  end
-
-  test "non-admin can't create a repository for another visible user" do
-    repo = new_repo(:active, name: "repoforanon",
-                    owner_uuid: users(:anonymous).uuid)
-    assert_not_allowed { repo.save }
-  end
-
-  test "admin can create a repository for themselves" do
-    repo = new_repo(:admin, name: "admin/newtestrepo")
-    assert(repo.save)
-  end
-
-  test "admin can create a repository for others" do
-    repo = new_repo(:admin, name: "active/repoforactive",
-                    owner_uuid: users(:active).uuid)
-    assert(repo.save)
-  end
-
-  test "admin can create a system repository" do
-    repo = new_repo(:admin, name: "repoforsystem",
-                    owner_uuid: users(:system_user).uuid)
-    assert(repo.save)
-  end
-
-  ### Repository destruction
-
-  test "non-admin can destroy their own repository" do
-    set_user_from_auth :active
-    assert(repositories(:foo).destroy)
-  end
-
-  test "non-admin can't destroy others' repository" do
-    set_user_from_auth :active
-    assert_not_allowed { repositories(:repository3).destroy }
-  end
-
-  test "non-admin can't destroy system repository" do
-    set_user_from_auth :active
-    assert_not_allowed { repositories(:arvados).destroy }
-  end
-
-  test "admin can destroy their own repository" do
-    set_user_from_auth :admin
-    assert(repositories(:repository3).destroy)
-  end
-
-  test "admin can destroy others' repository" do
-    set_user_from_auth :admin
-    assert(repositories(:foo).destroy)
-  end
-
-  test "admin can destroy system repository" do
-    set_user_from_auth :admin
-    assert(repositories(:arvados).destroy)
-  end
-
-  ### Changing ownership
-
-  test "non-admin can't make their repository a system repository" do
-    set_user_from_auth :active
-    repo = changed_repo(:foo, owner_uuid: users(:system_user).uuid)
-    assert_not_allowed { repo.save }
-  end
-
-  test "admin can give their repository to someone else" do
-    set_user_from_auth :admin
-    repo = changed_repo(:repository3, owner_uuid: users(:active).uuid,
-                        name: "active/foo3")
-    assert(repo.save)
-  end
-
-  test "admin can make their repository a system repository" do
-    set_user_from_auth :admin
-    repo = changed_repo(:repository3, owner_uuid: users(:system_user).uuid,
-                        name: "foo3")
-    assert(repo.save)
-  end
-
-  test 'write permission allows changing modified_at' do
-    act_as_user users(:active) do
-      r = repositories(:foo)
-      modtime_was = r.modified_at
-      r.modified_at = Time.now
-      assert r.save
-      assert_operator modtime_was, :<, r.modified_at
-    end
-  end
-
-  test 'write permission necessary for changing modified_at' do
-    act_as_user users(:spectator) do
-      r = repositories(:foo)
-      modtime_was = r.modified_at
-      r.modified_at = Time.now
-      assert_raises ArvadosModel::PermissionDeniedError do
-        r.save!
-      end
-      r.reload
-      assert_equal modtime_was, r.modified_at
-    end
-  end
-
-  ### Renaming
-
-  test "non-admin can rename own repo" do
-    act_as_user users(:active) do
-      assert repositories(:foo).update(name: 'active/foo12345')
-    end
-  end
-
-  test "top level repo can be touched by non-admin with can_manage" do
-    add_permission_link users(:active), repositories(:arvados), 'can_manage'
-    act_as_user users(:active) do
-      assert changed_repo(:arvados, modified_at: Time.now).save
-    end
-  end
-
-  test "top level repo cannot be renamed by non-admin with can_manage" do
-    add_permission_link users(:active), repositories(:arvados), 'can_manage'
-    act_as_user users(:active) do
-      assert_not_allowed { changed_repo(:arvados, name: 'xarvados').save }
-    end
-  end
-end
diff --git a/services/api/test/unit/specimen_test.rb b/services/api/test/unit/specimen_test.rb
deleted file mode 100644 (file)
index 5b2eda2..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class SpecimenTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
diff --git a/services/api/test/unit/trait_test.rb b/services/api/test/unit/trait_test.rb
deleted file mode 100644 (file)
index fe63f16..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-require 'test_helper'
-
-class TraitTest < ActiveSupport::TestCase
-  # test "the truth" do
-  #   assert true
-  # end
-end
index 810e5b45ecb2e77ad5394d20ffc807fb46d458ea..71b5769be8b87b353b73d8c5851e6ef07d86fd73 100644 (file)
@@ -118,18 +118,7 @@ class UserTest < ActiveSupport::TestCase
     check_new_username_setting("_", nil)
   end
 
     check_new_username_setting("_", nil)
   end
 
-  test "updating username updates repository names" do
-    set_user_from_auth :admin
-    user = users(:active)
-    user.username = "newtestname"
-    assert(user.save, "username update failed")
-    {foo: "newtestname/foo", repository2: "newtestname/foo2"}.
-        each_pair do |repo_sym, expect_name|
-      assert_equal(expect_name, repositories(repo_sym).name)
-    end
-  end
-
-  test "admin can clear username when user owns no repositories" do
+  test "admin can clear username" do
     set_user_from_auth :admin
     user = users(:spectator)
     user.username = nil
     set_user_from_auth :admin
     user = users(:spectator)
     user.username = nil
@@ -137,22 +126,6 @@ class UserTest < ActiveSupport::TestCase
     assert_nil(user.username)
   end
 
     assert_nil(user.username)
   end
 
-  test "admin can't clear username when user owns repositories" do
-    set_user_from_auth :admin
-    user = users(:active)
-    user.username = nil
-    assert_not_allowed { user.save }
-    refute_empty(user.errors[:username])
-  end
-
-  test "failed username update doesn't change repository names" do
-    set_user_from_auth :admin
-    user = users(:active)
-    user.username = users(:fuse).username
-    assert_not_allowed { user.save }
-    assert_equal("active/foo", repositories(:foo).name)
-  end
-
   [[false, 'foo@example.com', true, false],
    [false, 'bar@example.com', false, true],
    [true, 'foo@example.com', true, false],
   [[false, 'foo@example.com', true, false],
    [false, 'bar@example.com', false, true],
    [true, 'foo@example.com', true, false],
@@ -359,37 +332,33 @@ class UserTest < ActiveSupport::TestCase
 
   [
     # Easy inactive user tests.
 
   [
     # Easy inactive user tests.
-    [false, empty_notify_list, empty_notify_list, "inactive-none@example.com", false, false, "inactivenone"],
-    [false, empty_notify_list, empty_notify_list, "inactive-vm@example.com", true, false, "inactivevm"],
-    [false, empty_notify_list, empty_notify_list, "inactive-repo@example.com", false, true, "inactiverepo"],
-    [false, empty_notify_list, empty_notify_list, "inactive-both@example.com", true, true, "inactiveboth"],
+    [false, empty_notify_list, empty_notify_list, "inactive-none@example.com", false, "inactivenone"],
+    [false, empty_notify_list, empty_notify_list, "inactive-vm@example.com", true, "inactivevm"],
 
     # Easy active user tests.
 
     # Easy active user tests.
-    [true, active_notify_list, inactive_notify_list, "active-none@example.com", false, false, "activenone"],
-    [true, active_notify_list, inactive_notify_list, "active-vm@example.com", true, false, "activevm"],
-    [true, active_notify_list, inactive_notify_list, "active-repo@example.com", false, true, "activerepo"],
-    [true, active_notify_list, inactive_notify_list, "active-both@example.com", true, true, "activeboth"],
+    [true, active_notify_list, inactive_notify_list, "active-none@example.com", false, "activenone"],
+    [true, active_notify_list, inactive_notify_list, "active-vm@example.com", true, "activevm"],
 
     # Test users with malformed e-mail addresses.
 
     # Test users with malformed e-mail addresses.
-    [false, empty_notify_list, empty_notify_list, nil, true, true, nil],
-    [false, empty_notify_list, empty_notify_list, "arvados", true, true, nil],
-    [false, empty_notify_list, empty_notify_list, "@example.com", true, true, nil],
-    [true, active_notify_list, inactive_notify_list, "*!*@example.com", true, false, nil],
-    [true, active_notify_list, inactive_notify_list, "*!*@example.com", false, false, nil],
+    [false, empty_notify_list, empty_notify_list, nil, true, nil],
+    [false, empty_notify_list, empty_notify_list, "arvados", true, nil],
+    [false, empty_notify_list, empty_notify_list, "@example.com", true, nil],
+    [true, active_notify_list, inactive_notify_list, "*!*@example.com", true, nil],
+    [true, active_notify_list, inactive_notify_list, "*!*@example.com", false, nil],
 
     # Test users with various username transformations.
 
     # Test users with various username transformations.
-    [false, empty_notify_list, empty_notify_list, "arvados@example.com", false, false, "arvados2"],
-    [true, active_notify_list, inactive_notify_list, "arvados@example.com", false, false, "arvados2"],
-    [true, active_notify_list, inactive_notify_list, "root@example.com", true, false, "root2"],
-    [false, active_notify_list, empty_notify_list, "root@example.com", true, false, "root2"],
-    [true, active_notify_list, inactive_notify_list, "roo_t@example.com", false, true, "root2"],
-    [false, empty_notify_list, empty_notify_list, "^^incorrect_format@example.com", true, true, "incorrectformat"],
-    [true, active_notify_list, inactive_notify_list, "&4a_d9.@example.com", true, true, "ad9"],
-    [true, active_notify_list, inactive_notify_list, "&4a_d9.@example.com", false, false, "ad9"],
-    [false, active_notify_list, empty_notify_list, "&4a_d9.@example.com", true, true, "ad9"],
-    [false, active_notify_list, empty_notify_list, "&4a_d9.@example.com", false, false, "ad9"],
-  ].each do |active, new_user_recipients, inactive_recipients, email, auto_setup_vm, auto_setup_repo, expect_username|
-    test "create new user with auto setup active=#{active} email=#{email} vm=#{auto_setup_vm} repo=#{auto_setup_repo}" do
+    [false, empty_notify_list, empty_notify_list, "arvados@example.com", false, "arvados2"],
+    [true, active_notify_list, inactive_notify_list, "arvados@example.com", false, "arvados2"],
+    [true, active_notify_list, inactive_notify_list, "root@example.com", true, "root2"],
+    [false, active_notify_list, empty_notify_list, "root@example.com", true, "root2"],
+    [true, active_notify_list, inactive_notify_list, "roo_t@example.com", false, "root2"],
+    [false, empty_notify_list, empty_notify_list, "^^incorrect_format@example.com", true, "incorrectformat"],
+    [true, active_notify_list, inactive_notify_list, "&4a_d9.@example.com", true, "ad9"],
+    [true, active_notify_list, inactive_notify_list, "&4a_d9.@example.com", false, "ad9"],
+    [false, active_notify_list, empty_notify_list, "&4a_d9.@example.com", true, "ad9"],
+    [false, active_notify_list, empty_notify_list, "&4a_d9.@example.com", false, "ad9"],
+  ].each do |active, new_user_recipients, inactive_recipients, email, auto_setup_vm, expect_username|
+    test "create new user with auto setup active=#{active} email=#{email} vm=#{auto_setup_vm}" do
       set_user_from_auth :admin
 
       Rails.configuration.Users.AutoSetupNewUsers = true
       set_user_from_auth :admin
 
       Rails.configuration.Users.AutoSetupNewUsers = true
@@ -400,8 +369,6 @@ class UserTest < ActiveSupport::TestCase
         Rails.configuration.Users.AutoSetupNewUsersWithVmUUID = ""
       end
 
         Rails.configuration.Users.AutoSetupNewUsersWithVmUUID = ""
       end
 
-      Rails.configuration.Users.AutoSetupNewUsersWithRepository = auto_setup_repo
-
       create_user_and_verify_setup_and_notifications active, new_user_recipients, inactive_recipients, email, expect_username
     end
   end
       create_user_and_verify_setup_and_notifications active, new_user_recipients, inactive_recipients, email, expect_username
     end
   end
@@ -460,8 +427,7 @@ class UserTest < ActiveSupport::TestCase
 
       vm = VirtualMachine.create
 
 
       vm = VirtualMachine.create
 
-      response = user.setup(repo_name: 'foo/testrepo',
-                            vm_uuid: vm.uuid)
+      response = user.setup(vm_uuid: vm.uuid)
 
       resp_user = find_obj_in_resp response, 'User'
       verify_user resp_user, email
 
       resp_user = find_obj_in_resp response, 'User'
       verify_user resp_user, email
@@ -476,9 +442,6 @@ class UserTest < ActiveSupport::TestCase
         assert_nil group_perm2
       end
 
         assert_nil group_perm2
       end
 
-      repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-      verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
       vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
       verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
       assert_equal("foo", vm_perm.properties["username"])
       vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
       verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
       assert_equal("foo", vm_perm.properties["username"])
@@ -494,8 +457,7 @@ class UserTest < ActiveSupport::TestCase
 
     vm = VirtualMachine.create
 
 
     vm = VirtualMachine.create
 
-    response = user.setup(repo_name: 'foo/testrepo',
-                          vm_uuid: vm.uuid)
+    response = user.setup(vm_uuid: vm.uuid)
 
     resp_user = find_obj_in_resp response, 'User'
     verify_user resp_user, email
 
     resp_user = find_obj_in_resp response, 'User'
     verify_user resp_user, email
@@ -503,9 +465,6 @@ class UserTest < ActiveSupport::TestCase
     group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
     verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
 
     group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
     verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
 
-    repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
     assert_equal("foo", vm_perm.properties["username"])
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
     assert_equal("foo", vm_perm.properties["username"])
@@ -529,23 +488,10 @@ class UserTest < ActiveSupport::TestCase
     group_perm2 = find_obj_in_resp response, 'Link', 'arvados#user'
     verify_link group_perm2, 'permission', 'can_read', groups(:all_users).uuid, nil
 
     group_perm2 = find_obj_in_resp response, 'Link', 'arvados#user'
     verify_link group_perm2, 'permission', 'can_read', groups(:all_users).uuid, nil
 
-    # invoke setup again with repo_name
-    response = user.setup(repo_name: 'foo/testrepo')
-    resp_user = find_obj_in_resp response, 'User', nil
-    verify_user resp_user, email
-    assert_equal user.uuid, resp_user[:uuid], 'expected uuid not found'
-
-    group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
-    verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
-
-    repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
     # invoke setup again with a vm_uuid
     vm = VirtualMachine.create
 
     # invoke setup again with a vm_uuid
     vm = VirtualMachine.create
 
-    response = user.setup(repo_name: 'foo/testrepo',
-                          vm_uuid: vm.uuid)
+    response = user.setup(vm_uuid: vm.uuid)
 
     resp_user = find_obj_in_resp response, 'User', nil
     verify_user resp_user, email
 
     resp_user = find_obj_in_resp response, 'User', nil
     verify_user resp_user, email
@@ -554,9 +500,6 @@ class UserTest < ActiveSupport::TestCase
     group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
     verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
 
     group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
     verify_link group_perm, 'permission', 'can_write', resp_user[:uuid], groups(:all_users).uuid
 
-    repo_perm = find_obj_in_resp response, 'Link', 'arvados#repository'
-    verify_link repo_perm, 'permission', 'can_manage', resp_user[:uuid], nil
-
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
     assert_equal("foo", vm_perm.properties["username"])
     vm_perm = find_obj_in_resp response, 'Link', 'arvados#virtualMachine'
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
     assert_equal("foo", vm_perm.properties["username"])
@@ -614,8 +557,6 @@ class UserTest < ActiveSupport::TestCase
 
     can_setup = (Rails.configuration.Users.AutoSetupNewUsers and
                  (not expect_username.nil?))
 
     can_setup = (Rails.configuration.Users.AutoSetupNewUsers and
                  (not expect_username.nil?))
-    expect_repo_name = "#{expect_username}/#{expect_username}"
-    prior_repo = Repository.where(name: expect_repo_name).first
 
     user = User.new
     user.first_name = "first_name_for_newly_created_user"
 
     user = User.new
     user.first_name = "first_name_for_newly_created_user"
@@ -629,14 +570,6 @@ class UserTest < ActiveSupport::TestCase
                        groups(:all_users).uuid, user.uuid,
                        "permission", "can_write")
 
                        groups(:all_users).uuid, user.uuid,
                        "permission", "can_write")
 
-    # Check for repository.
-    if named_repo = (prior_repo or
-                     Repository.where(name: expect_repo_name).first)
-      verify_link_exists((can_setup and prior_repo.nil? and
-                          Rails.configuration.Users.AutoSetupNewUsersWithRepository),
-                         named_repo.uuid, user.uuid, "permission", "can_manage")
-    end
-
     # Check for VM login.
     if (auto_vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID) != ""
       verify_link_exists(can_setup, auto_vm_uuid, user.uuid,
     # Check for VM login.
     if (auto_vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID) != ""
       verify_link_exists(can_setup, auto_vm_uuid, user.uuid,
index 38e6f564e717d23dc217d66f59465ad584deb4b7..ca20de4c5813d4d0a9bf7dc6a1e74ce7ac68c43c 100644 (file)
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
+#
+# This file runs in one of three modes:
+#
+# 1. If the ARVADOS_BUILDING_VERSION environment variable is set, it writes
+#    _version.py and generates dependencies based on that value.
+# 2. If running from an arvados Git checkout, it writes _version.py
+#    and generates dependencies from Git.
+# 3. Otherwise, we expect this is source previously generated from Git, and
+#    it reads _version.py and generates dependencies from it.
 
 
-import subprocess
-import time
 import os
 import re
 import os
 import re
+import runpy
+import subprocess
 import sys
 
 import sys
 
-SETUP_DIR = os.path.dirname(os.path.abspath(__file__))
-VERSION_PATHS = {
-        SETUP_DIR,
-        os.path.abspath(os.path.join(SETUP_DIR, "../../build/version-at-commit.sh"))
-        }
+from pathlib import Path
+
+# These maps explain the relationships between different Python modules in
+# the arvados repository. We use these to help generate setup.py.
+PACKAGE_DEPENDENCY_MAP = {
+    'arvados-cwl-runner': ['arvados-python-client', 'crunchstat_summary'],
+    'arvados-user-activity': ['arvados-python-client'],
+    'arvados_fuse': ['arvados-python-client'],
+    'crunchstat_summary': ['arvados-python-client'],
+}
+PACKAGE_MODULE_MAP = {
+    'arvados-cwl-runner': 'arvados_cwl',
+    'arvados-docker-cleaner': 'arvados_docker',
+    'arvados-python-client': 'arvados',
+    'arvados-user-activity': 'arvados_user_activity',
+    'arvados_fuse': 'arvados_fuse',
+    'crunchstat_summary': 'crunchstat_summary',
+}
+PACKAGE_SRCPATH_MAP = {
+    'arvados-cwl-runner': Path('sdk', 'cwl'),
+    'arvados-docker-cleaner': Path('services', 'dockercleaner'),
+    'arvados-python-client': Path('sdk', 'python'),
+    'arvados-user-activity': Path('tools', 'user-activity'),
+    'arvados_fuse': Path('services', 'fuse'),
+    'crunchstat_summary': Path('tools', 'crunchstat-summary'),
+}
+
+ENV_VERSION = os.environ.get("ARVADOS_BUILDING_VERSION")
+SETUP_DIR = Path(__file__).absolute().parent
+try:
+    REPO_PATH = Path(subprocess.check_output(
+        ['git', '-C', str(SETUP_DIR), 'rev-parse', '--show-toplevel'],
+        stderr=subprocess.DEVNULL,
+        text=True,
+    ).rstrip('\n'))
+except (subprocess.CalledProcessError, OSError):
+    REPO_PATH = None
+else:
+    # Verify this is the arvados monorepo
+    if all((REPO_PATH / path).exists() for path in PACKAGE_SRCPATH_MAP.values()):
+        PACKAGE_NAME, = (
+            pkg_name for pkg_name, path in PACKAGE_SRCPATH_MAP.items()
+            if (REPO_PATH / path) == SETUP_DIR
+        )
+        MODULE_NAME = PACKAGE_MODULE_MAP[PACKAGE_NAME]
+        VERSION_SCRIPT_PATH = Path(REPO_PATH, 'build', 'version-at-commit.sh')
+    else:
+        REPO_PATH = None
+if REPO_PATH is None:
+    (PACKAGE_NAME, MODULE_NAME), = (
+        (pkg_name, mod_name)
+        for pkg_name, mod_name in PACKAGE_MODULE_MAP.items()
+        if (SETUP_DIR / mod_name).is_dir()
+    )
+
+def git_log_output(path, *args):
+    return subprocess.check_output(
+        ['git', '-C', str(REPO_PATH),
+         'log', '--first-parent', '--max-count=1',
+         *args, str(path)],
+        text=True,
+    ).rstrip('\n')
 
 def choose_version_from():
 
 def choose_version_from():
-    ts = {}
-    for path in VERSION_PATHS:
-        ts[subprocess.check_output(
-            ['git', 'log', '--first-parent', '--max-count=1',
-             '--format=format:%ct', path]).strip()] = path
-
-    sorted_ts = sorted(ts.items())
-    getver = sorted_ts[-1][1]
-    print("Using "+getver+" for version number calculation of "+SETUP_DIR, file=sys.stderr)
+    ver_paths = [SETUP_DIR, VERSION_SCRIPT_PATH, *(
+        PACKAGE_SRCPATH_MAP[pkg]
+        for pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ())
+    )]
+    getver = max(ver_paths, key=lambda path: git_log_output(path, '--format=format:%ct'))
+    print(f"Using {getver} for version number calculation of {SETUP_DIR}", file=sys.stderr)
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
-    myhash = subprocess.check_output(['git', 'log', '-n1', '--first-parent',
-                                       '--format=%H', curdir]).strip()
-    myversion = subprocess.check_output([SETUP_DIR+'/../../build/version-at-commit.sh', myhash]).strip().decode()
-    return myversion
+    myhash = git_log_output(curdir, '--format=%H')
+    return subprocess.check_output(
+        [str(VERSION_SCRIPT_PATH), myhash],
+        text=True,
+    ).rstrip('\n')
 
 def save_version(setup_dir, module, v):
 
 def save_version(setup_dir, module, v):
-    v = v.replace("~dev", ".dev").replace("~rc", "rc")
-    with open(os.path.join(setup_dir, module, "_version.py"), 'wt') as fp:
-        return fp.write("__version__ = '%s'\n" % v)
+    with Path(setup_dir, module, '_version.py').open('w') as fp:
+        print(f"__version__ = {v!r}", file=fp)
 
 def read_version(setup_dir, module):
 
 def read_version(setup_dir, module):
-    with open(os.path.join(setup_dir, module, "_version.py"), 'rt') as fp:
-        return re.match("__version__ = '(.*)'$", fp.read()).groups()[0]
+    file_vars = runpy.run_path(Path(setup_dir, module, '_version.py'))
+    return file_vars['__version__']
 
 
-def get_version(setup_dir, module):
-    env_version = os.environ.get("ARVADOS_BUILDING_VERSION")
-
-    if env_version:
-        save_version(setup_dir, module, env_version)
+def get_version(setup_dir=SETUP_DIR, module=MODULE_NAME):
+    if ENV_VERSION:
+        version = ENV_VERSION
+    elif REPO_PATH is None:
+        return read_version(setup_dir, module)
     else:
     else:
-        try:
-            save_version(setup_dir, module, git_version_at_commit())
-        except (subprocess.CalledProcessError, OSError) as err:
-            print("ERROR: {0}".format(err), file=sys.stderr)
-            pass
+        version = git_version_at_commit()
+    version = version.replace("~dev", ".dev").replace("~rc", "rc")
+    save_version(setup_dir, module, version)
+    return version
+
+def iter_dependencies(version=None):
+    if version is None:
+        version = get_version()
+    # A packaged development release should be installed with other
+    # development packages built from the same source, but those
+    # dependencies may have earlier "dev" versions (read: less recent
+    # Git commit timestamps). This compatible version dependency
+    # expresses that as closely as possible. Allowing versions
+    # compatible with .dev0 allows any development release.
+    # Regular expression borrowed partially from
+    # <https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers-regex>
+    dep_ver, match_count = re.subn(r'\.dev(0|[1-9][0-9]*)$', '.dev0', version, 1)
+    dep_op = '~=' if match_count else '=='
+    for dep_pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ()):
+        yield f'{dep_pkg}{dep_op}{dep_ver}'
 
 
-    return read_version(setup_dir, module)
+# Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
+if __name__ == '__main__':
+    print(get_version())
index b9dcd79500256701a4232d1705a8555f18163594..abc723fcf3ffb4462dcd276e5f09335a232f3545 100755 (executable)
@@ -3,7 +3,5 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import, print_function
-
 from arvados_docker.cleaner import main
 main()
 from arvados_docker.cleaner import main
 main()
diff --git a/services/dockercleaner/pytest.ini b/services/dockercleaner/pytest.ini
new file mode 120000 (symlink)
index 0000000..05a82db
--- /dev/null
@@ -0,0 +1 @@
+../../sdk/python/pytest.ini
\ No newline at end of file
index 2b386c70b47aa2c925b87aedff14838297b88315..cb0fc8d88afae0b7c688aeec4f73a548716b39a1 100644 (file)
@@ -3,23 +3,15 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
-SETUP_DIR = os.path.dirname(__file__) or '.'
-README = os.path.join(SETUP_DIR, 'README.rst')
-
 import arvados_version
 import arvados_version
-version = arvados_version.get_version(SETUP_DIR, "arvados_docker")
-
-short_tests_only = False
-if '--short-tests-only' in sys.argv:
-    short_tests_only = True
-    sys.argv.remove('--short-tests-only')
+version = arvados_version.get_version()
+README = os.path.join(arvados_version.SETUP_DIR, 'README.rst')
 
 setup(name="arvados-docker-cleaner",
       version=version,
 
 setup(name="arvados-docker-cleaner",
       version=version,
@@ -37,6 +29,7 @@ setup(name="arvados-docker-cleaner",
           ('share/doc/arvados-docker-cleaner', ['agpl-3.0.txt', 'arvados-docker-cleaner.service']),
       ],
       install_requires=[
           ('share/doc/arvados-docker-cleaner', ['agpl-3.0.txt', 'arvados-docker-cleaner.service']),
       ],
       install_requires=[
+          *arvados_version.iter_dependencies(version),
           'docker>=6.1.0',
           'setuptools',
       ],
           'docker>=6.1.0',
           'setuptools',
       ],
index 31afcda8d12267970631372014706793ef95c9f3..c29c2430dccec4570532e22950cdb42205b5d192 100644 (file)
@@ -47,19 +47,13 @@ The general FUSE operation flow is as follows:
 The FUSE driver supports the Arvados event bus.  When an event is received for
 an object that is live in the inode cache, that object is immediately updated.
 
 The FUSE driver supports the Arvados event bus.  When an event is received for
 an object that is live in the inode cache, that object is immediately updated.
 
+Implementation note: in the code, the terms 'object', 'entry' and
+'inode' are used somewhat interchangeably, but generally mean an
+arvados_fuse.File or arvados_fuse.Directory object which has numeric
+inode assigned to it and appears in the Inodes._entries dictionary.
+
 """
 
 """
 
-from __future__ import absolute_import
-from __future__ import division
-from future.utils import viewitems
-from future.utils import native
-from future.utils import listvalues
-from future.utils import listitems
-from future import standard_library
-standard_library.install_aliases()
-from builtins import next
-from builtins import str
-from builtins import object
 import os
 import llfuse
 import errno
 import os
 import llfuse
 import errno
@@ -76,22 +70,11 @@ import functools
 import arvados.keep
 from prometheus_client import Summary
 import queue
 import arvados.keep
 from prometheus_client import Summary
 import queue
-
-# Default _notify_queue has a limit of 1000 items, but it really needs to be
-# unlimited to avoid deadlocks, see https://arvados.org/issues/3198#note-43 for
-# details.
-
-if hasattr(llfuse, 'capi'):
-    # llfuse < 0.42
-    llfuse.capi._notify_queue = queue.Queue()
-else:
-    # llfuse >= 0.42
-    llfuse._notify_queue = queue.Queue()
-
-LLFUSE_VERSION_0 = llfuse.__version__.startswith('0')
+from dataclasses import dataclass
+import typing
 
 from .fusedir import Directory, CollectionDirectory, TmpCollectionDirectory, MagicDirectory, TagsDirectory, ProjectDirectory, SharedDirectory, CollectionDirectoryBase
 
 from .fusedir import Directory, CollectionDirectory, TmpCollectionDirectory, MagicDirectory, TagsDirectory, ProjectDirectory, SharedDirectory, CollectionDirectoryBase
-from .fusefile import StringFile, FuseArvadosFile
+from .fusefile import File, StringFile, FuseArvadosFile
 
 _logger = logging.getLogger('arvados.arvados_fuse')
 
 
 _logger = logging.getLogger('arvados.arvados_fuse')
 
@@ -128,28 +111,47 @@ class FileHandle(Handle):
 
 class DirectoryHandle(Handle):
     """Connects a numeric file handle to a Directory object that has
 
 class DirectoryHandle(Handle):
     """Connects a numeric file handle to a Directory object that has
-    been opened by the client."""
+    been opened by the client.
+
+    DirectoryHandle is used by opendir() and readdir() to get
+    directory listings.  Entries returned by readdir() don't increment
+    the lookup count (kernel references), so increment our internal
+    "use count" to avoid having an item being removed mid-read.
+
+    """
 
     def __init__(self, fh, dirobj, entries):
         super(DirectoryHandle, self).__init__(fh, dirobj)
         self.entries = entries
 
 
     def __init__(self, fh, dirobj, entries):
         super(DirectoryHandle, self).__init__(fh, dirobj)
         self.entries = entries
 
+        for ent in self.entries:
+            ent[1].inc_use()
+
+    def release(self):
+        for ent in self.entries:
+            ent[1].dec_use()
+        super(DirectoryHandle, self).release()
+
 
 class InodeCache(object):
     """Records the memory footprint of objects and when they are last used.
 
 
 class InodeCache(object):
     """Records the memory footprint of objects and when they are last used.
 
-    When the cache limit is exceeded, the least recently used objects are
-    cleared.  Clearing the object means discarding its contents to release
-    memory.  The next time the object is accessed, it must be re-fetched from
-    the server.  Note that the inode cache limit is a soft limit; the cache
-    limit may be exceeded if necessary to load very large objects, it may also
-    be exceeded if open file handles prevent objects from being cleared.
+    When the cache limit is exceeded, the least recently used objects
+    are cleared.  Clearing the object means discarding its contents to
+    release memory.  The next time the object is accessed, it must be
+    re-fetched from the server.  Note that the inode cache limit is a
+    soft limit; the cache limit may be exceeded if necessary to load
+    very large projects or collections, it may also be exceeded if an
+    inode can't be safely discarded based on kernel lookups
+    (has_ref()) or internal use count (in_use()).
 
     """
 
     def __init__(self, cap, min_entries=4):
 
     """
 
     def __init__(self, cap, min_entries=4):
-        self._entries = collections.OrderedDict()
-        self._by_uuid = {}
+        # Standard dictionaries are ordered, but OrderedDict is still better here, see
+        # https://docs.python.org/3.11/library/collections.html#ordereddict-objects
+        # specifically we use move_to_end() which standard dicts don't have.
+        self._cache_entries = collections.OrderedDict()
         self.cap = cap
         self._total = 0
         self.min_entries = min_entries
         self.cap = cap
         self._total = 0
         self.min_entries = min_entries
@@ -157,104 +159,148 @@ class InodeCache(object):
     def total(self):
         return self._total
 
     def total(self):
         return self._total
 
-    def _remove(self, obj, clear):
-        if clear:
-            # Kernel behavior seems to be that if a file is
-            # referenced, its parents remain referenced too. This
-            # means has_ref() exits early when a collection is not
-            # candidate for eviction.
-            #
-            # By contrast, in_use() doesn't increment references on
-            # parents, so it requires a full tree walk to determine if
-            # a collection is a candidate for eviction.  This takes
-            # .07s for 240000 files, which becomes a major drag when
-            # cap_cache is being called several times a second and
-            # there are multiple non-evictable collections in the
-            # cache.
-            #
-            # So it is important for performance that we do the
-            # has_ref() check first.
-
-            if obj.has_ref(True):
-                _logger.debug("InodeCache cannot clear inode %i, still referenced", obj.inode)
-                return
+    def evict_candidates(self):
+        """Yield entries that are candidates to be evicted
+        and stop when the cache total has shrunk sufficiently.
 
 
-            if obj.in_use():
-                _logger.debug("InodeCache cannot clear inode %i, in use", obj.inode)
-                return
+        Implements a LRU cache, when an item is added or touch()ed it
+        goes to the back of the OrderedDict, so items in the front are
+        oldest.  The Inodes._remove() function determines if the entry
+        can actually be removed safely.
 
 
-            obj.kernel_invalidate()
-            _logger.debug("InodeCache sent kernel invalidate inode %i", obj.inode)
-            obj.clear()
+        """
 
 
-        # The llfuse lock is released in del_entry(), which is called by
-        # Directory.clear().  While the llfuse lock is released, it can happen
-        # that a reentrant call removes this entry before this call gets to it.
-        # Ensure that the entry is still valid before trying to remove it.
-        if obj.inode not in self._entries:
+        if self._total <= self.cap:
             return
 
             return
 
-        self._total -= obj.cache_size
-        del self._entries[obj.inode]
-        if obj.cache_uuid:
-            self._by_uuid[obj.cache_uuid].remove(obj)
-            if not self._by_uuid[obj.cache_uuid]:
-                del self._by_uuid[obj.cache_uuid]
-            obj.cache_uuid = None
-        if clear:
-            _logger.debug("InodeCache cleared inode %i total now %i", obj.inode, self._total)
+        _logger.debug("InodeCache evict_candidates total %i cap %i entries %i", self._total, self.cap, len(self._cache_entries))
 
 
-    def cap_cache(self):
-        if self._total > self.cap:
-            for ent in listvalues(self._entries):
-                if self._total < self.cap or len(self._entries) < self.min_entries:
-                    break
-                self._remove(ent, True)
-
-    def manage(self, obj):
-        if obj.persisted():
-            obj.cache_size = obj.objsize()
-            self._entries[obj.inode] = obj
-            obj.cache_uuid = obj.uuid()
-            if obj.cache_uuid:
-                if obj.cache_uuid not in self._by_uuid:
-                    self._by_uuid[obj.cache_uuid] = [obj]
-                else:
-                    if obj not in self._by_uuid[obj.cache_uuid]:
-                        self._by_uuid[obj.cache_uuid].append(obj)
-            self._total += obj.objsize()
-            _logger.debug("InodeCache touched inode %i (size %i) (uuid %s) total now %i (%i entries)",
-                          obj.inode, obj.objsize(), obj.cache_uuid, self._total, len(self._entries))
-            self.cap_cache()
+        # Copy this into a deque for two reasons:
+        #
+        # 1. _cache_entries is modified by unmanage() which is called
+        # by _remove
+        #
+        # 2. popping off the front means the reference goes away
+        # immediately intead of sticking around for the lifetime of
+        # "values"
+        values = collections.deque(self._cache_entries.values())
 
 
-    def touch(self, obj):
-        if obj.persisted():
-            if obj.inode in self._entries:
-                self._remove(obj, False)
-            self.manage(obj)
+        while values:
+            if self._total < self.cap or len(self._cache_entries) < self.min_entries:
+                break
+            yield values.popleft()
 
 
-    def unmanage(self, obj):
-        if obj.persisted() and obj.inode in self._entries:
-            self._remove(obj, True)
+    def unmanage(self, entry):
+        """Stop managing an object in the cache.
 
 
-    def find_by_uuid(self, uuid):
-        return self._by_uuid.get(uuid, [])
+        This happens when an object is being removed from the inode
+        entries table.
+
+        """
+
+        if entry.inode not in self._cache_entries:
+            return
+
+        # manage cache size running sum
+        self._total -= entry.cache_size
+        entry.cache_size = 0
+
+        # Now forget about it
+        del self._cache_entries[entry.inode]
+
+    def update_cache_size(self, obj):
+        """Update the cache total in response to the footprint of an
+        object changing (usually because it has been loaded or
+        cleared).
+
+        Adds or removes entries to the cache list based on the object
+        cache size.
+
+        """
+
+        if not obj.persisted():
+            return
+
+        if obj.inode in self._cache_entries:
+            self._total -= obj.cache_size
+
+        obj.cache_size = obj.objsize()
+
+        if obj.cache_size > 0 or obj.parent_inode is None:
+            self._total += obj.cache_size
+            self._cache_entries[obj.inode] = obj
+        elif obj.cache_size == 0 and obj.inode in self._cache_entries:
+            del self._cache_entries[obj.inode]
+
+    def touch(self, obj):
+        """Indicate an object was used recently, making it low
+        priority to be removed from the cache.
+
+        """
+        if obj.inode in self._cache_entries:
+            self._cache_entries.move_to_end(obj.inode)
+            return True
+        return False
 
     def clear(self):
 
     def clear(self):
-        self._entries.clear()
-        self._by_uuid.clear()
+        self._cache_entries.clear()
         self._total = 0
 
         self._total = 0
 
+@dataclass
+class RemoveInode:
+    entry: typing.Union[Directory, File]
+    def inode_op(self, inodes, locked_ops):
+        if locked_ops is None:
+            inodes._remove(self.entry)
+            return True
+        else:
+            locked_ops.append(self)
+            return False
+
+@dataclass
+class InvalidateInode:
+    inode: int
+    def inode_op(self, inodes, locked_ops):
+        llfuse.invalidate_inode(self.inode)
+        return True
+
+@dataclass
+class InvalidateEntry:
+    inode: int
+    name: str
+    def inode_op(self, inodes, locked_ops):
+        llfuse.invalidate_entry(self.inode, self.name)
+        return True
+
+@dataclass
+class EvictCandidates:
+    def inode_op(self, inodes, locked_ops):
+        return True
+
+
 class Inodes(object):
 class Inodes(object):
-    """Manage the set of inodes.  This is the mapping from a numeric id
-    to a concrete File or Directory object"""
+    """Manage the set of inodes.
+
+    This is the mapping from a numeric id to a concrete File or
+    Directory object
 
 
-    def __init__(self, inode_cache, encoding="utf-8"):
+    """
+
+    def __init__(self, inode_cache, encoding="utf-8", fsns=None, shutdown_started=None):
         self._entries = {}
         self._counter = itertools.count(llfuse.ROOT_INODE)
         self.inode_cache = inode_cache
         self.encoding = encoding
         self._entries = {}
         self._counter = itertools.count(llfuse.ROOT_INODE)
         self.inode_cache = inode_cache
         self.encoding = encoding
-        self.deferred_invalidations = []
+        self._fsns = fsns
+        self._shutdown_started = shutdown_started or threading.Event()
+
+        self._inode_remove_queue = queue.Queue()
+        self._inode_remove_thread = threading.Thread(None, self._inode_remove)
+        self._inode_remove_thread.daemon = True
+        self._inode_remove_thread.start()
+
+        self.cap_cache_event = threading.Event()
+        self._by_uuid = collections.defaultdict(list)
 
     def __getitem__(self, item):
         return self._entries[item]
 
     def __getitem__(self, item):
         return self._entries[item]
@@ -266,50 +312,196 @@ class Inodes(object):
         return iter(self._entries.keys())
 
     def items(self):
         return iter(self._entries.keys())
 
     def items(self):
-        return viewitems(self._entries.items())
+        return self._entries.items()
 
     def __contains__(self, k):
         return k in self._entries
 
     def touch(self, entry):
 
     def __contains__(self, k):
         return k in self._entries
 
     def touch(self, entry):
+        """Update the access time, adjust the cache position, and
+        notify the _inode_remove thread to recheck the cache.
+
+        """
+
         entry._atime = time.time()
         entry._atime = time.time()
-        self.inode_cache.touch(entry)
+        if self.inode_cache.touch(entry):
+            self.cap_cache()
+
+    def cap_cache(self):
+        """Notify the _inode_remove thread to recheck the cache."""
+        if not self.cap_cache_event.is_set():
+            self.cap_cache_event.set()
+            self._inode_remove_queue.put(EvictCandidates())
+
+    def update_uuid(self, entry):
+        """Update the Arvados uuid associated with an inode entry.
+
+        This is used to look up inodes that need to be invalidated
+        when a websocket event indicates the object has changed on the
+        API server.
+
+        """
+        if entry.cache_uuid and entry in self._by_uuid[entry.cache_uuid]:
+            self._by_uuid[entry.cache_uuid].remove(entry)
+
+        entry.cache_uuid = entry.uuid()
+        if entry.cache_uuid and entry not in self._by_uuid[entry.cache_uuid]:
+            self._by_uuid[entry.cache_uuid].append(entry)
+
+        if not self._by_uuid[entry.cache_uuid]:
+            del self._by_uuid[entry.cache_uuid]
 
     def add_entry(self, entry):
 
     def add_entry(self, entry):
+        """Assign a numeric inode to a new entry."""
+
         entry.inode = next(self._counter)
         if entry.inode == llfuse.ROOT_INODE:
             entry.inc_ref()
         self._entries[entry.inode] = entry
         entry.inode = next(self._counter)
         if entry.inode == llfuse.ROOT_INODE:
             entry.inc_ref()
         self._entries[entry.inode] = entry
-        self.inode_cache.manage(entry)
+
+        self.update_uuid(entry)
+        self.inode_cache.update_cache_size(entry)
+        self.cap_cache()
         return entry
 
     def del_entry(self, entry):
         return entry
 
     def del_entry(self, entry):
-        if entry.ref_count == 0:
-            self.inode_cache.unmanage(entry)
-            del self._entries[entry.inode]
+        """Remove entry from the inode table.
+
+        Indicate this inode entry is pending deletion by setting
+        parent_inode to None.  Notify the _inode_remove thread to try
+        and remove it.
+
+        """
+
+        entry.parent_inode = None
+        self._inode_remove_queue.put(RemoveInode(entry))
+        _logger.debug("del_entry on inode %i with refcount %i", entry.inode, entry.ref_count)
+
+    def _inode_remove(self):
+        """Background thread to handle tasks related to invalidating
+        inodes in the kernel, and removing objects from the inodes
+        table entirely.
+
+        """
+
+        locked_ops = collections.deque()
+        while True:
+            blocking_get = True
+            while True:
+                try:
+                    qentry = self._inode_remove_queue.get(blocking_get)
+                except queue.Empty:
+                    break
+                blocking_get = False
+                if qentry is None:
+                    return
+
+                if self._shutdown_started.is_set():
+                    continue
+
+                # Process this entry
+                if qentry.inode_op(self, locked_ops):
+                    self._inode_remove_queue.task_done()
+
+                # Give up the reference
+                qentry = None
+
+            with llfuse.lock:
+                while locked_ops:
+                    if locked_ops.popleft().inode_op(self, None):
+                        self._inode_remove_queue.task_done()
+                self.cap_cache_event.clear()
+                for entry in self.inode_cache.evict_candidates():
+                    self._remove(entry)
+
+    def wait_remove_queue_empty(self):
+        # used by tests
+        self._inode_remove_queue.join()
+
+    def _remove(self, entry):
+        """Remove an inode entry if possible.
+
+        If the entry is still referenced or in use, don't do anything.
+        If this is not referenced but the parent is still referenced,
+        clear any data held by the object (which may include directory
+        entries under the object) but don't remove it from the inode
+        table.
+
+        """
+        try:
+            if entry.inode is None:
+                # Removed already
+                return
+
+            if entry.inode == llfuse.ROOT_INODE:
+                return
+
+            if entry.in_use():
+                # referenced internally, stay pinned
+                #_logger.debug("InodeCache cannot clear inode %i, in use", entry.inode)
+                return
+
+            # Tell the kernel it should forget about it
+            entry.kernel_invalidate()
+
+            if entry.has_ref():
+                # has kernel reference, could still be accessed.
+                # when the kernel forgets about it, we can delete it.
+                #_logger.debug("InodeCache cannot clear inode %i, is referenced", entry.inode)
+                return
+
+            # commit any pending changes
             with llfuse.lock_released:
                 entry.finalize()
             with llfuse.lock_released:
                 entry.finalize()
-            entry.inode = None
-        else:
-            entry.dead = True
-            _logger.debug("del_entry on inode %i with refcount %i", entry.inode, entry.ref_count)
+
+            # Clear the contents
+            entry.clear()
+
+            if entry.parent_inode is None:
+                _logger.debug("InodeCache forgetting inode %i, object cache_size %i, cache total %i, forget_inode True, inode entries %i, type %s",
+                              entry.inode, entry.cache_size, self.inode_cache.total(),
+                              len(self._entries), type(entry))
+
+                if entry.cache_uuid:
+                    self._by_uuid[entry.cache_uuid].remove(entry)
+                    if not self._by_uuid[entry.cache_uuid]:
+                        del self._by_uuid[entry.cache_uuid]
+                    entry.cache_uuid = None
+
+                self.inode_cache.unmanage(entry)
+
+                del self._entries[entry.inode]
+                entry.inode = None
+
+        except Exception as e:
+            _logger.exception("failed remove")
 
     def invalidate_inode(self, entry):
 
     def invalidate_inode(self, entry):
-        if entry.has_ref(False):
+        if entry.has_ref():
             # Only necessary if the kernel has previously done a lookup on this
             # inode and hasn't yet forgotten about it.
             # Only necessary if the kernel has previously done a lookup on this
             # inode and hasn't yet forgotten about it.
-            llfuse.invalidate_inode(entry.inode)
+            self._inode_remove_queue.put(InvalidateInode(entry.inode))
 
     def invalidate_entry(self, entry, name):
 
     def invalidate_entry(self, entry, name):
-        if entry.has_ref(False):
+        if entry.has_ref():
             # Only necessary if the kernel has previously done a lookup on this
             # inode and hasn't yet forgotten about it.
             # Only necessary if the kernel has previously done a lookup on this
             # inode and hasn't yet forgotten about it.
-            llfuse.invalidate_entry(entry.inode, native(name.encode(self.encoding)))
+            self._inode_remove_queue.put(InvalidateEntry(entry.inode, name.encode(self.encoding)))
+
+    def begin_shutdown(self):
+        self._inode_remove_queue.put(None)
+        if self._inode_remove_thread is not None:
+            self._inode_remove_thread.join()
+        self._inode_remove_thread = None
 
     def clear(self):
 
     def clear(self):
+        with llfuse.lock_released:
+            self.begin_shutdown()
+
         self.inode_cache.clear()
         self.inode_cache.clear()
+        self._by_uuid.clear()
 
 
-        for k,v in viewitems(self._entries):
+        for k,v in self._entries.items():
             try:
                 v.finalize()
             except Exception as e:
             try:
                 v.finalize()
             except Exception as e:
@@ -317,6 +509,14 @@ class Inodes(object):
 
         self._entries.clear()
 
 
         self._entries.clear()
 
+    def forward_slash_subst(self):
+        return self._fsns
+
+    def find_by_uuid(self, uuid):
+        """Return a list of zero or more inode entries corresponding
+        to this Arvados UUID."""
+        return self._by_uuid.get(uuid, [])
+
 
 def catch_exceptions(orig_func):
     """Catch uncaught exceptions and log them consistently."""
 
 def catch_exceptions(orig_func):
     """Catch uncaught exceptions and log them consistently."""
@@ -377,14 +577,32 @@ class Operations(llfuse.Operations):
     rename_time = fuse_time.labels(op='rename')
     flush_time = fuse_time.labels(op='flush')
 
     rename_time = fuse_time.labels(op='rename')
     flush_time = fuse_time.labels(op='flush')
 
-    def __init__(self, uid, gid, api_client, encoding="utf-8", inode_cache=None, num_retries=4, enable_write=False):
+    def __init__(self, uid, gid, api_client, encoding="utf-8", inode_cache=None, num_retries=4, enable_write=False, fsns=None):
         super(Operations, self).__init__()
 
         self._api_client = api_client
 
         if not inode_cache:
             inode_cache = InodeCache(cap=256*1024*1024)
         super(Operations, self).__init__()
 
         self._api_client = api_client
 
         if not inode_cache:
             inode_cache = InodeCache(cap=256*1024*1024)
-        self.inodes = Inodes(inode_cache, encoding=encoding)
+
+        if fsns is None:
+            try:
+                fsns = self._api_client.config()["Collections"]["ForwardSlashNameSubstitution"]
+            except KeyError:
+                # old API server with no FSNS config
+                fsns = '_'
+            else:
+                if fsns == '' or fsns == '/':
+                    fsns = None
+
+        # If we get overlapping shutdown events (e.g., fusermount -u
+        # -z and operations.destroy()) llfuse calls forget() on inodes
+        # that have already been deleted. To avoid this, we make
+        # forget() a no-op if called after destroy().
+        self._shutdown_started = threading.Event()
+
+        self.inodes = Inodes(inode_cache, encoding=encoding, fsns=fsns,
+                             shutdown_started=self._shutdown_started)
         self.uid = uid
         self.gid = gid
         self.enable_write = enable_write
         self.uid = uid
         self.gid = gid
         self.enable_write = enable_write
@@ -397,12 +615,6 @@ class Operations(llfuse.Operations):
         # is fully initialized should wait() on this event object.
         self.initlock = threading.Event()
 
         # is fully initialized should wait() on this event object.
         self.initlock = threading.Event()
 
-        # If we get overlapping shutdown events (e.g., fusermount -u
-        # -z and operations.destroy()) llfuse calls forget() on inodes
-        # that have already been deleted. To avoid this, we make
-        # forget() a no-op if called after destroy().
-        self._shutdown_started = threading.Event()
-
         self.num_retries = num_retries
 
         self.read_counter = arvados.keep.Counter()
         self.num_retries = num_retries
 
         self.read_counter = arvados.keep.Counter()
@@ -438,23 +650,26 @@ class Operations(llfuse.Operations):
     def metric_count_func(self, opname):
         return lambda: int(self.metric_value(opname, "arvmount_fuse_operations_seconds_count"))
 
     def metric_count_func(self, opname):
         return lambda: int(self.metric_value(opname, "arvmount_fuse_operations_seconds_count"))
 
+    def begin_shutdown(self):
+        self._shutdown_started.set()
+        self.inodes.begin_shutdown()
+
     @destroy_time.time()
     @catch_exceptions
     def destroy(self):
     @destroy_time.time()
     @catch_exceptions
     def destroy(self):
-        self._shutdown_started.set()
+        _logger.debug("arv-mount destroy: start")
+
+        with llfuse.lock_released:
+            self.begin_shutdown()
+
         if self.events:
             self.events.close()
             self.events = None
 
         if self.events:
             self.events.close()
             self.events = None
 
-        # Different versions of llfuse require and forbid us to
-        # acquire the lock here. See #8345#note-37, #10805#note-9.
-        if LLFUSE_VERSION_0 and llfuse.lock.acquire():
-            # llfuse < 0.42
-            self.inodes.clear()
-            llfuse.lock.release()
-        else:
-            # llfuse >= 0.42
-            self.inodes.clear()
+        self.inodes.clear()
+
+        _logger.debug("arv-mount destroy: complete")
+
 
     def access(self, inode, mode, ctx):
         return True
 
     def access(self, inode, mode, ctx):
         return True
@@ -475,28 +690,34 @@ class Operations(llfuse.Operations):
             old_attrs = properties.get("old_attributes") or {}
             new_attrs = properties.get("new_attributes") or {}
 
             old_attrs = properties.get("old_attributes") or {}
             new_attrs = properties.get("new_attributes") or {}
 
-            for item in self.inodes.inode_cache.find_by_uuid(ev["object_uuid"]):
+            for item in self.inodes.find_by_uuid(ev["object_uuid"]):
                 item.invalidate()
 
             oldowner = old_attrs.get("owner_uuid")
             newowner = ev.get("object_owner_uuid")
             for parent in (
                 item.invalidate()
 
             oldowner = old_attrs.get("owner_uuid")
             newowner = ev.get("object_owner_uuid")
             for parent in (
-                    self.inodes.inode_cache.find_by_uuid(oldowner) +
-                    self.inodes.inode_cache.find_by_uuid(newowner)):
+                    self.inodes.find_by_uuid(oldowner) +
+                    self.inodes.find_by_uuid(newowner)):
                 parent.invalidate()
 
     @getattr_time.time()
     @catch_exceptions
     def getattr(self, inode, ctx=None):
         if inode not in self.inodes:
                 parent.invalidate()
 
     @getattr_time.time()
     @catch_exceptions
     def getattr(self, inode, ctx=None):
         if inode not in self.inodes:
+            _logger.debug("arv-mount getattr: inode %i missing", inode)
             raise llfuse.FUSEError(errno.ENOENT)
 
         e = self.inodes[inode]
             raise llfuse.FUSEError(errno.ENOENT)
 
         e = self.inodes[inode]
+        self.inodes.touch(e)
+        parent = None
+        if e.parent_inode:
+            parent = self.inodes[e.parent_inode]
+            self.inodes.touch(parent)
 
         entry = llfuse.EntryAttributes()
         entry.st_ino = inode
         entry.generation = 0
 
         entry = llfuse.EntryAttributes()
         entry.st_ino = inode
         entry.generation = 0
-        entry.entry_timeout = 0
+        entry.entry_timeout = parent.time_to_next_poll() if parent is not None else 0
         entry.attr_timeout = e.time_to_next_poll() if e.allow_attr_cache else 0
 
         entry.st_mode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
         entry.attr_timeout = e.time_to_next_poll() if e.allow_attr_cache else 0
 
         entry.st_mode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
@@ -564,18 +785,23 @@ class Operations(llfuse.Operations):
 
         if name == '.':
             inode = parent_inode
 
         if name == '.':
             inode = parent_inode
-        else:
-            if parent_inode in self.inodes:
-                p = self.inodes[parent_inode]
-                self.inodes.touch(p)
-                if name == '..':
-                    inode = p.parent_inode
-                elif isinstance(p, Directory) and name in p:
-                    inode = p[name].inode
+        elif parent_inode in self.inodes:
+            p = self.inodes[parent_inode]
+            self.inodes.touch(p)
+            if name == '..':
+                inode = p.parent_inode
+            elif isinstance(p, Directory) and name in p:
+                if p[name].inode is None:
+                    _logger.debug("arv-mount lookup: parent_inode %i name '%s' found but inode was None",
+                                  parent_inode, name)
+                    raise llfuse.FUSEError(errno.ENOENT)
+
+                inode = p[name].inode
 
         if inode != None:
             _logger.debug("arv-mount lookup: parent_inode %i name '%s' inode %i",
                       parent_inode, name, inode)
 
         if inode != None:
             _logger.debug("arv-mount lookup: parent_inode %i name '%s' inode %i",
                       parent_inode, name, inode)
+            self.inodes.touch(self.inodes[inode])
             self.inodes[inode].inc_ref()
             return self.getattr(inode)
         else:
             self.inodes[inode].inc_ref()
             return self.getattr(inode)
         else:
@@ -591,7 +817,7 @@ class Operations(llfuse.Operations):
         for inode, nlookup in inodes:
             ent = self.inodes[inode]
             _logger.debug("arv-mount forget: inode %i nlookup %i ref_count %i", inode, nlookup, ent.ref_count)
         for inode, nlookup in inodes:
             ent = self.inodes[inode]
             _logger.debug("arv-mount forget: inode %i nlookup %i ref_count %i", inode, nlookup, ent.ref_count)
-            if ent.dec_ref(nlookup) == 0 and ent.dead:
+            if ent.dec_ref(nlookup) == 0 and ent.parent_inode is None:
                 self.inodes.del_entry(ent)
 
     @open_time.time()
                 self.inodes.del_entry(ent)
 
     @open_time.time()
@@ -600,6 +826,7 @@ class Operations(llfuse.Operations):
         if inode in self.inodes:
             p = self.inodes[inode]
         else:
         if inode in self.inodes:
             p = self.inodes[inode]
         else:
+            _logger.debug("arv-mount open: inode %i missing", inode)
             raise llfuse.FUSEError(errno.ENOENT)
 
         if isinstance(p, Directory):
             raise llfuse.FUSEError(errno.ENOENT)
 
         if isinstance(p, Directory):
@@ -681,7 +908,7 @@ class Operations(llfuse.Operations):
             finally:
                 self._filehandles[fh].release()
                 del self._filehandles[fh]
             finally:
                 self._filehandles[fh].release()
                 del self._filehandles[fh]
-        self.inodes.inode_cache.cap_cache()
+        self.inodes.cap_cache()
 
     def releasedir(self, fh):
         self.release(fh)
 
     def releasedir(self, fh):
         self.release(fh)
@@ -694,6 +921,7 @@ class Operations(llfuse.Operations):
         if inode in self.inodes:
             p = self.inodes[inode]
         else:
         if inode in self.inodes:
             p = self.inodes[inode]
         else:
+            _logger.debug("arv-mount opendir: called with unknown or removed inode %i", inode)
             raise llfuse.FUSEError(errno.ENOENT)
 
         if not isinstance(p, Directory):
             raise llfuse.FUSEError(errno.ENOENT)
 
         if not isinstance(p, Directory):
@@ -703,11 +931,16 @@ class Operations(llfuse.Operations):
         if p.parent_inode in self.inodes:
             parent = self.inodes[p.parent_inode]
         else:
         if p.parent_inode in self.inodes:
             parent = self.inodes[p.parent_inode]
         else:
+            _logger.warning("arv-mount opendir: parent inode %i of %i is missing", p.parent_inode, inode)
             raise llfuse.FUSEError(errno.EIO)
 
             raise llfuse.FUSEError(errno.EIO)
 
+        _logger.debug("arv-mount opendir: inode %i fh %i ", inode, fh)
+
         # update atime
         # update atime
+        p.inc_use()
+        self._filehandles[fh] = DirectoryHandle(fh, p, [('.', p), ('..', parent)] + p.items())
+        p.dec_use()
         self.inodes.touch(p)
         self.inodes.touch(p)
-        self._filehandles[fh] = DirectoryHandle(fh, p, [('.', p), ('..', parent)] + listitems(p))
         return fh
 
     @readdir_time.time()
         return fh
 
     @readdir_time.time()
@@ -722,8 +955,9 @@ class Operations(llfuse.Operations):
 
         e = off
         while e < len(handle.entries):
 
         e = off
         while e < len(handle.entries):
-            if handle.entries[e][1].inode in self.inodes:
-                yield (handle.entries[e][0].encode(self.inodes.encoding), self.getattr(handle.entries[e][1].inode), e+1)
+            ent = handle.entries[e]
+            if ent[1].inode in self.inodes:
+                yield (ent[0].encode(self.inodes.encoding), self.getattr(ent[1].inode), e+1)
             e += 1
 
     @statfs_time.time()
             e += 1
 
     @statfs_time.time()
index 719ec7ee959701fde58bfef0dfb8b3c46dc4b895..8004e8303f624eaa5acd17f616826b3197e92af7 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from future.utils import native_str
-from builtins import range
-from builtins import object
 import argparse
 import arvados
 import daemon
 import argparse
 import arvados
 import daemon
@@ -349,7 +346,15 @@ Filesystem character encoding
             metavar='CLASSES',
             help="Comma-separated list of storage classes to request for new collections",
         )
             metavar='CLASSES',
             help="Comma-separated list of storage classes to request for new collections",
         )
-
+        # This is a hidden argument used by tests.  Normally this
+        # value will be extracted from the cluster config, but mocking
+        # the cluster config under the presence of multiple threads
+        # and processes turned out to be too complicated and brittle.
+        plumbing.add_argument(
+            '--fsns',
+            type=str,
+            default=None,
+            help=argparse.SUPPRESS)
 
 class Mount(object):
     def __init__(self, args, logger=logging.getLogger('arvados.arv-mount')):
 
 class Mount(object):
     def __init__(self, args, logger=logging.getLogger('arvados.arv-mount')):
@@ -402,7 +407,7 @@ class Mount(object):
         if self.args.replace:
             unmount(path=self.args.mountpoint,
                     timeout=self.args.unmount_timeout)
         if self.args.replace:
             unmount(path=self.args.mountpoint,
                     timeout=self.args.unmount_timeout)
-        llfuse.init(self.operations, native_str(self.args.mountpoint), self._fuse_options())
+        llfuse.init(self.operations, str(self.args.mountpoint), self._fuse_options())
         if self.daemon:
             daemon.DaemonContext(
                 working_directory=os.path.dirname(self.args.mountpoint),
         if self.daemon:
             daemon.DaemonContext(
                 working_directory=os.path.dirname(self.args.mountpoint),
@@ -482,13 +487,6 @@ class Mount(object):
                                                       disk_cache=self.args.disk_cache,
                                                       disk_cache_dir=self.args.disk_cache_dir)
 
                                                       disk_cache=self.args.disk_cache,
                                                       disk_cache_dir=self.args.disk_cache_dir)
 
-            # If there's too many prefetch threads and you
-            # max out the CPU, delivering data to the FUSE
-            # layer actually ends up being slower.
-            # Experimentally, capping 7 threads seems to
-            # be a sweet spot.
-            prefetch_threads = min(max((block_cache.cache_max // (64 * 1024 * 1024)) - 1, 1), 7)
-
             self.api = arvados.safeapi.ThreadSafeApiCache(
                 apiconfig=arvados.config.settings(),
                 api_params={
             self.api = arvados.safeapi.ThreadSafeApiCache(
                 apiconfig=arvados.config.settings(),
                 api_params={
@@ -496,7 +494,6 @@ class Mount(object):
                 },
                 keep_params={
                     'block_cache': block_cache,
                 },
                 keep_params={
                     'block_cache': block_cache,
-                    'num_prefetch_threads': prefetch_threads,
                     'num_retries': self.args.retries,
                 },
                 version='v1',
                     'num_retries': self.args.retries,
                 },
                 version='v1',
@@ -514,7 +511,8 @@ class Mount(object):
             api_client=self.api,
             encoding=self.args.encoding,
             inode_cache=InodeCache(cap=self.args.directory_cache),
             api_client=self.api,
             encoding=self.args.encoding,
             inode_cache=InodeCache(cap=self.args.directory_cache),
-            enable_write=self.args.enable_write)
+            enable_write=self.args.enable_write,
+            fsns=self.args.fsns)
 
         if self.args.crunchstat_interval:
             statsthread = threading.Thread(
 
         if self.args.crunchstat_interval:
             statsthread = threading.Thread(
@@ -603,7 +601,6 @@ class Mount(object):
         e = self.operations.inodes.add_entry(Directory(
             llfuse.ROOT_INODE,
             self.operations.inodes,
         e = self.operations.inodes.add_entry(Directory(
             llfuse.ROOT_INODE,
             self.operations.inodes,
-            self.api.config,
             self.args.enable_write,
             self.args.filters,
         ))
             self.args.enable_write,
             self.args.filters,
         ))
@@ -688,8 +685,9 @@ From here, the following directories are available:
 
     def _llfuse_main(self):
         try:
 
     def _llfuse_main(self):
         try:
-            llfuse.main()
+            llfuse.main(workers=10)
         except:
             llfuse.close(unmount=False)
             raise
         except:
             llfuse.close(unmount=False)
             raise
+        self.operations.begin_shutdown()
         llfuse.close()
         llfuse.close()
index 0cb585a6ff6d551a7db86553282d47ae4fecdb60..313c34971fda9b77e3bddca2a5c9078fcb5e5a82 100644 (file)
@@ -2,10 +2,9 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from builtins import str
-from builtins import object
 import sys
 import time
 import sys
 import time
+
 from collections import namedtuple
 
 Stat = namedtuple("Stat", ['name', 'get'])
 from collections import namedtuple
 
 Stat = namedtuple("Stat", ['name', 'get'])
index 53214ee94d70b214f79e3cca5c5193a41ebe2567..ff548f29ee9dfc19df5d84f7ec9d09895af0392b 100644 (file)
@@ -2,11 +2,10 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from builtins import object
-import time
 import ciso8601
 import calendar
 import functools
 import ciso8601
 import calendar
 import functools
+import time
 
 def convertTime(t):
     """Parse Arvados timestamp to unix time."""
 
 def convertTime(t):
     """Parse Arvados timestamp to unix time."""
@@ -62,7 +61,7 @@ class FreshBase(object):
     """
 
     __slots__ = ("_stale", "_poll", "_last_update", "_atime", "_poll_time", "use_count",
     """
 
     __slots__ = ("_stale", "_poll", "_last_update", "_atime", "_poll_time", "use_count",
-                 "ref_count", "dead", "cache_size", "cache_uuid", "allow_attr_cache")
+                 "ref_count", "cache_size", "cache_uuid", "allow_attr_cache")
 
     def __init__(self):
         self._stale = True
 
     def __init__(self):
         self._stale = True
@@ -72,7 +71,6 @@ class FreshBase(object):
         self._poll_time = 60
         self.use_count = 0
         self.ref_count = 0
         self._poll_time = 60
         self.use_count = 0
         self.ref_count = 0
-        self.dead = False
         self.cache_size = 0
         self.cache_uuid = None
 
         self.cache_size = 0
         self.cache_uuid = None
 
@@ -125,17 +123,11 @@ class FreshBase(object):
         self.ref_count -= n
         return self.ref_count
 
         self.ref_count -= n
         return self.ref_count
 
-    def has_ref(self, only_children):
+    def has_ref(self):
         """Determine if there are any kernel references to this
         """Determine if there are any kernel references to this
-        object or its children.
-
-        If only_children is True, ignore refcount of self and only consider
-        children.
+        object.
         """
         """
-        if only_children:
-            return False
-        else:
-            return self.ref_count > 0
+        return self.ref_count > 0
 
     def objsize(self):
         return 0
 
     def objsize(self):
         return 0
index e3b8dd4c2cca29616626dab55f6d440c22b58f51..9c78805107358dadf8b2f87221154753399b2c63 100644 (file)
@@ -36,7 +36,9 @@ class Directory(FreshBase):
     and the value referencing a File or Directory object.
     """
 
     and the value referencing a File or Directory object.
     """
 
-    def __init__(self, parent_inode, inodes, apiconfig, enable_write, filters):
+    __slots__ = ("inode", "parent_inode", "inodes", "_entries", "_mtime", "_enable_write", "_filters")
+
+    def __init__(self, parent_inode, inodes, enable_write, filters):
         """parent_inode is the integer inode number"""
 
         super(Directory, self).__init__()
         """parent_inode is the integer inode number"""
 
         super(Directory, self).__init__()
@@ -46,7 +48,6 @@ class Directory(FreshBase):
             raise Exception("parent_inode should be an int")
         self.parent_inode = parent_inode
         self.inodes = inodes
             raise Exception("parent_inode should be an int")
         self.parent_inode = parent_inode
         self.inodes = inodes
-        self.apiconfig = apiconfig
         self._entries = {}
         self._mtime = time.time()
         self._enable_write = enable_write
         self._entries = {}
         self._mtime = time.time()
         self._enable_write = enable_write
@@ -64,23 +65,9 @@ class Directory(FreshBase):
             else:
                 yield [f_name, *f[1:]]
 
             else:
                 yield [f_name, *f[1:]]
 
-    def forward_slash_subst(self):
-        if not hasattr(self, '_fsns'):
-            self._fsns = None
-            config = self.apiconfig()
-            try:
-                self._fsns = config["Collections"]["ForwardSlashNameSubstitution"]
-            except KeyError:
-                # old API server with no FSNS config
-                self._fsns = '_'
-            else:
-                if self._fsns == '' or self._fsns == '/':
-                    self._fsns = None
-        return self._fsns
-
     def unsanitize_filename(self, incoming):
         """Replace ForwardSlashNameSubstitution value with /"""
     def unsanitize_filename(self, incoming):
         """Replace ForwardSlashNameSubstitution value with /"""
-        fsns = self.forward_slash_subst()
+        fsns = self.inodes.forward_slash_subst()
         if isinstance(fsns, str):
             return incoming.replace(fsns, '/')
         else:
         if isinstance(fsns, str):
             return incoming.replace(fsns, '/')
         else:
@@ -99,7 +86,7 @@ class Directory(FreshBase):
         elif dirty == '..':
             return '__'
         else:
         elif dirty == '..':
             return '__'
         else:
-            fsns = self.forward_slash_subst()
+            fsns = self.inodes.forward_slash_subst()
             if isinstance(fsns, str):
                 dirty = dirty.replace('/', fsns)
             return _disallowed_filename_characters.sub('_', dirty)
             if isinstance(fsns, str):
                 dirty = dirty.replace('/', fsns)
             return _disallowed_filename_characters.sub('_', dirty)
@@ -150,6 +137,10 @@ class Directory(FreshBase):
         self.inodes.touch(self)
         super(Directory, self).fresh()
 
         self.inodes.touch(self)
         super(Directory, self).fresh()
 
+    def objsize(self):
+        # Rough estimate of memory footprint based on using pympler
+        return len(self._entries) * 1024
+
     def merge(self, items, fn, same, new_entry):
         """Helper method for updating the contents of the directory.
 
     def merge(self, items, fn, same, new_entry):
         """Helper method for updating the contents of the directory.
 
@@ -157,16 +148,17 @@ class Directory(FreshBase):
         entries that are the same in both the old and new lists, create new
         entries, and delete old entries missing from the new list.
 
         entries that are the same in both the old and new lists, create new
         entries, and delete old entries missing from the new list.
 
-        :items: iterable with new directory contents
+        Arguments:
+        * items: Iterable --- New directory contents
 
 
-        :fn: function to take an entry in 'items' and return the desired file or
+        * fn: Callable --- Takes an entry in 'items' and return the desired file or
         directory name, or None if this entry should be skipped
 
         directory name, or None if this entry should be skipped
 
-        :same: function to compare an existing entry (a File or Directory
+        * same: Callable --- Compare an existing entry (a File or Directory
         object) with an entry in the items list to determine whether to keep
         the existing entry.
 
         object) with an entry in the items list to determine whether to keep
         the existing entry.
 
-        :new_entry: function to create a new directory entry (File or Directory
+        * new_entry: Callable --- Create a new directory entry (File or Directory
         object) from an entry in the items list.
 
         """
         object) from an entry in the items list.
 
         """
@@ -176,29 +168,43 @@ class Directory(FreshBase):
         changed = False
         for i in items:
             name = self.sanitize_filename(fn(i))
         changed = False
         for i in items:
             name = self.sanitize_filename(fn(i))
-            if name:
-                if name in oldentries and same(oldentries[name], i):
+            if not name:
+                continue
+            if name in oldentries:
+                ent = oldentries[name]
+                if same(ent, i) and ent.parent_inode == self.inode:
                     # move existing directory entry over
                     # move existing directory entry over
-                    self._entries[name] = oldentries[name]
+                    self._entries[name] = ent
                     del oldentries[name]
                     del oldentries[name]
-                else:
-                    _logger.debug("Adding entry '%s' to inode %i", name, self.inode)
-                    # create new directory entry
-                    ent = new_entry(i)
-                    if ent is not None:
-                        self._entries[name] = self.inodes.add_entry(ent)
-                        changed = True
+                    self.inodes.inode_cache.touch(ent)
+
+        for i in items:
+            name = self.sanitize_filename(fn(i))
+            if not name:
+                continue
+            if name not in self._entries:
+                # create new directory entry
+                ent = new_entry(i)
+                if ent is not None:
+                    self._entries[name] = self.inodes.add_entry(ent)
+                    # need to invalidate this just in case there was a
+                    # previous entry that couldn't be moved over or a
+                    # lookup that returned file not found and cached
+                    # a negative result
+                    self.inodes.invalidate_entry(self, name)
+                    changed = True
+                _logger.debug("Added entry '%s' as inode %i to parent inode %i", name, ent.inode, self.inode)
 
         # delete any other directory entries that were not in found in 'items'
 
         # delete any other directory entries that were not in found in 'items'
-        for i in oldentries:
-            _logger.debug("Forgetting about entry '%s' on inode %i", i, self.inode)
-            self.inodes.invalidate_entry(self, i)
-            self.inodes.del_entry(oldentries[i])
+        for name, ent in oldentries.items():
+            _logger.debug("Detaching entry '%s' from parent_inode %i", name, self.inode)
+            self.inodes.invalidate_entry(self, name)
+            self.inodes.del_entry(ent)
             changed = True
 
         if changed:
             changed = True
 
         if changed:
-            self.inodes.invalidate_inode(self)
             self._mtime = time.time()
             self._mtime = time.time()
+            self.inodes.inode_cache.update_cache_size(self)
 
         self.fresh()
 
 
         self.fresh()
 
@@ -210,27 +216,27 @@ class Directory(FreshBase):
                 return True
         return False
 
                 return True
         return False
 
-    def has_ref(self, only_children):
-        if super(Directory, self).has_ref(only_children):
-            return True
-        for v in self._entries.values():
-            if v.has_ref(False):
-                return True
-        return False
-
     def clear(self):
         """Delete all entries"""
     def clear(self):
         """Delete all entries"""
+        if not self._entries:
+            return
         oldentries = self._entries
         self._entries = {}
         oldentries = self._entries
         self._entries = {}
-        for n in oldentries:
-            oldentries[n].clear()
-            self.inodes.del_entry(oldentries[n])
         self.invalidate()
         self.invalidate()
+        for name, ent in oldentries.items():
+            ent.clear()
+            self.inodes.invalidate_entry(self, name)
+            self.inodes.del_entry(ent)
+        self.inodes.inode_cache.update_cache_size(self)
 
     def kernel_invalidate(self):
         # Invalidating the dentry on the parent implies invalidating all paths
         # below it as well.
 
     def kernel_invalidate(self):
         # Invalidating the dentry on the parent implies invalidating all paths
         # below it as well.
-        parent = self.inodes[self.parent_inode]
+        if self.parent_inode in self.inodes:
+            parent = self.inodes[self.parent_inode]
+        else:
+            # parent was removed already.
+            return
 
         # Find self on the parent in order to invalidate this path.
         # Calling the public items() method might trigger a refresh,
 
         # Find self on the parent in order to invalidate this path.
         # Calling the public items() method might trigger a refresh,
@@ -283,9 +289,10 @@ class CollectionDirectoryBase(Directory):
 
     """
 
 
     """
 
-    def __init__(self, parent_inode, inodes, apiconfig, enable_write, filters, collection, collection_root):
-        super(CollectionDirectoryBase, self).__init__(parent_inode, inodes, apiconfig, enable_write, filters)
-        self.apiconfig = apiconfig
+    __slots__ = ("collection", "collection_root", "collection_record_file")
+
+    def __init__(self, parent_inode, inodes, enable_write, filters, collection, collection_root):
+        super(CollectionDirectoryBase, self).__init__(parent_inode, inodes, enable_write, filters)
         self.collection = collection
         self.collection_root = collection_root
         self.collection_record_file = None
         self.collection = collection
         self.collection_root = collection_root
         self.collection_record_file = None
@@ -293,17 +300,16 @@ class CollectionDirectoryBase(Directory):
     def new_entry(self, name, item, mtime):
         name = self.sanitize_filename(name)
         if hasattr(item, "fuse_entry") and item.fuse_entry is not None:
     def new_entry(self, name, item, mtime):
         name = self.sanitize_filename(name)
         if hasattr(item, "fuse_entry") and item.fuse_entry is not None:
-            if item.fuse_entry.dead is not True:
-                raise Exception("Can only reparent dead inode entry")
+            if item.fuse_entry.parent_inode is not None:
+                raise Exception("Can only reparent unparented inode entry")
             if item.fuse_entry.inode is None:
                 raise Exception("Reparented entry must still have valid inode")
             if item.fuse_entry.inode is None:
                 raise Exception("Reparented entry must still have valid inode")
-            item.fuse_entry.dead = False
+            item.fuse_entry.parent_inode = self.inode
             self._entries[name] = item.fuse_entry
         elif isinstance(item, arvados.collection.RichCollectionBase):
             self._entries[name] = self.inodes.add_entry(CollectionDirectoryBase(
                 self.inode,
                 self.inodes,
             self._entries[name] = item.fuse_entry
         elif isinstance(item, arvados.collection.RichCollectionBase):
             self._entries[name] = self.inodes.add_entry(CollectionDirectoryBase(
                 self.inode,
                 self.inodes,
-                self.apiconfig,
                 self._enable_write,
                 self._filters,
                 item,
                 self._enable_write,
                 self._filters,
                 item,
@@ -449,14 +455,23 @@ class CollectionDirectoryBase(Directory):
 
     def clear(self):
         super(CollectionDirectoryBase, self).clear()
 
     def clear(self):
         super(CollectionDirectoryBase, self).clear()
+        if self.collection is not None:
+            self.collection.unsubscribe()
         self.collection = None
 
         self.collection = None
 
+    def objsize(self):
+        # objsize for the whole collection is represented at the root,
+        # don't double-count it
+        return 0
 
 class CollectionDirectory(CollectionDirectoryBase):
     """Represents the root of a directory tree representing a collection."""
 
 
 class CollectionDirectory(CollectionDirectoryBase):
     """Represents the root of a directory tree representing a collection."""
 
+    __slots__ = ("api", "num_retries", "collection_locator",
+                 "_manifest_size", "_writable", "_updating_lock")
+
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters=None, collection_record=None, explicit_collection=None):
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters=None, collection_record=None, explicit_collection=None):
-        super(CollectionDirectory, self).__init__(parent_inode, inodes, api.config, enable_write, filters, None, self)
+        super(CollectionDirectory, self).__init__(parent_inode, inodes, enable_write, filters, None, self)
         self.api = api
         self.num_retries = num_retries
         self._poll = True
         self.api = api
         self.num_retries = num_retries
         self._poll = True
@@ -514,7 +529,10 @@ class CollectionDirectory(CollectionDirectoryBase):
         if self.collection_record_file is not None:
             self.collection_record_file.invalidate()
             self.inodes.invalidate_inode(self.collection_record_file)
         if self.collection_record_file is not None:
             self.collection_record_file.invalidate()
             self.inodes.invalidate_inode(self.collection_record_file)
-            _logger.debug("%s invalidated collection record file", self)
+            _logger.debug("parent_inode %s invalidated collection record file inode %s", self.inode,
+                          self.collection_record_file.inode)
+        self.inodes.update_uuid(self)
+        self.inodes.inode_cache.update_cache_size(self)
         self.fresh()
 
     def uuid(self):
         self.fresh()
 
     def uuid(self):
@@ -592,6 +610,7 @@ class CollectionDirectory(CollectionDirectoryBase):
         return False
 
     @use_counter
         return False
 
     @use_counter
+    @check_update
     def collection_record(self):
         self.flush()
         return self.collection.api_response()
     def collection_record(self):
         self.flush()
         return self.collection.api_response()
@@ -625,22 +644,32 @@ class CollectionDirectory(CollectionDirectoryBase):
         return (self.collection_locator is not None)
 
     def objsize(self):
         return (self.collection_locator is not None)
 
     def objsize(self):
-        # This is an empirically-derived heuristic to estimate the memory used
-        # to store this collection's metadata.  Calculating the memory
-        # footprint directly would be more accurate, but also more complicated.
-        return self._manifest_size * 128
+        # This is a rough guess of the amount of overhead involved for
+        # a collection; the assumptions are that that each file
+        # averages 128 bytes in the manifest, but consume 1024 bytes
+        # of Python data structures, so 1024/128=8 means we estimate
+        # the RAM footprint at 8 times the size of bare manifest text.
+        return self._manifest_size * 8
 
     def finalize(self):
 
     def finalize(self):
-        if self.collection is not None:
-            if self.writable():
+        if self.collection is None:
+            return
+
+        if self.writable():
+            try:
                 self.collection.save()
                 self.collection.save()
-            self.collection.stop_threads()
+            except Exception as e:
+                _logger.exception("Failed to save collection %s", self.collection_locator)
+        self.collection.stop_threads()
 
     def clear(self):
         if self.collection is not None:
             self.collection.stop_threads()
 
     def clear(self):
         if self.collection is not None:
             self.collection.stop_threads()
-        super(CollectionDirectory, self).clear()
         self._manifest_size = 0
         self._manifest_size = 0
+        super(CollectionDirectory, self).clear()
+        if self.collection_record_file is not None:
+            self.inodes.del_entry(self.collection_record_file)
+        self.collection_record_file = None
 
 
 class TmpCollectionDirectory(CollectionDirectoryBase):
 
 
 class TmpCollectionDirectory(CollectionDirectoryBase):
@@ -667,7 +696,7 @@ class TmpCollectionDirectory(CollectionDirectoryBase):
         # This is always enable_write=True because it never tries to
         # save to the backend
         super(TmpCollectionDirectory, self).__init__(
         # This is always enable_write=True because it never tries to
         # save to the backend
         super(TmpCollectionDirectory, self).__init__(
-            parent_inode, inodes, api_client.config, True, filters, collection, self)
+            parent_inode, inodes, True, filters, collection, self)
         self.populate(self.mtime())
 
     def on_event(self, *args, **kwargs):
         self.populate(self.mtime())
 
     def on_event(self, *args, **kwargs):
@@ -689,7 +718,7 @@ class TmpCollectionDirectory(CollectionDirectoryBase):
                 with self.collection.lock:
                     self.collection_record_file.invalidate()
                     self.inodes.invalidate_inode(self.collection_record_file)
                 with self.collection.lock:
                     self.collection_record_file.invalidate()
                     self.inodes.invalidate_inode(self.collection_record_file)
-                    _logger.debug("%s invalidated collection record", self)
+                    _logger.debug("%s invalidated collection record", self.inode)
         finally:
             while lockcount > 0:
                 self.collection.lock.acquire()
         finally:
             while lockcount > 0:
                 self.collection.lock.acquire()
@@ -764,7 +793,7 @@ and the directory will appear if it exists.
 """.lstrip()
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters, pdh_only=False, storage_classes=None):
 """.lstrip()
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters, pdh_only=False, storage_classes=None):
-        super(MagicDirectory, self).__init__(parent_inode, inodes, api.config, enable_write, filters)
+        super(MagicDirectory, self).__init__(parent_inode, inodes, enable_write, filters)
         self.api = api
         self.num_retries = num_retries
         self.pdh_only = pdh_only
         self.api = api
         self.num_retries = num_retries
         self.pdh_only = pdh_only
@@ -863,7 +892,7 @@ class TagsDirectory(Directory):
     """A special directory that contains as subdirectories all tags visible to the user."""
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters, poll_time=60):
     """A special directory that contains as subdirectories all tags visible to the user."""
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters, poll_time=60):
-        super(TagsDirectory, self).__init__(parent_inode, inodes, api.config, enable_write, filters)
+        super(TagsDirectory, self).__init__(parent_inode, inodes, enable_write, filters)
         self.api = api
         self.num_retries = num_retries
         self._poll = True
         self.api = api
         self.num_retries = num_retries
         self._poll = True
@@ -943,7 +972,7 @@ class TagDirectory(Directory):
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters, tag,
                  poll=False, poll_time=60):
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters, tag,
                  poll=False, poll_time=60):
-        super(TagDirectory, self).__init__(parent_inode, inodes, api.config, enable_write, filters)
+        super(TagDirectory, self).__init__(parent_inode, inodes, enable_write, filters)
         self.api = api
         self.num_retries = num_retries
         self.tag = tag
         self.api = api
         self.num_retries = num_retries
         self.tag = tag
@@ -984,9 +1013,13 @@ class TagDirectory(Directory):
 class ProjectDirectory(Directory):
     """A special directory that contains the contents of a project."""
 
 class ProjectDirectory(Directory):
     """A special directory that contains the contents of a project."""
 
+    __slots__ = ("api", "num_retries", "project_object", "project_object_file",
+                 "project_uuid", "_updating_lock",
+                 "_current_user", "_full_listing", "storage_classes", "recursively_contained")
+
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters,
                  project_object, poll=True, poll_time=3, storage_classes=None):
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters,
                  project_object, poll=True, poll_time=3, storage_classes=None):
-        super(ProjectDirectory, self).__init__(parent_inode, inodes, api.config, enable_write, filters)
+        super(ProjectDirectory, self).__init__(parent_inode, inodes, enable_write, filters)
         self.api = api
         self.num_retries = num_retries
         self.project_object = project_object
         self.api = api
         self.num_retries = num_retries
         self.project_object = project_object
@@ -998,6 +1031,19 @@ class ProjectDirectory(Directory):
         self._current_user = None
         self._full_listing = False
         self.storage_classes = storage_classes
         self._current_user = None
         self._full_listing = False
         self.storage_classes = storage_classes
+        self.recursively_contained = False
+
+        # Filter groups can contain themselves, which causes tools
+        # that walk the filesystem to get stuck in an infinite loop,
+        # so suppress returning a listing in that case.
+        if self.project_object.get("group_class") == "filter":
+            iter_parent_inode = parent_inode
+            while iter_parent_inode != llfuse.ROOT_INODE:
+                parent_dir = self.inodes[iter_parent_inode]
+                if isinstance(parent_dir, ProjectDirectory) and parent_dir.project_uuid == self.project_uuid:
+                    self.recursively_contained = True
+                    break
+                iter_parent_inode = parent_dir.parent_inode
 
     def want_event_subscribe(self):
         return True
 
     def want_event_subscribe(self):
         return True
@@ -1048,7 +1094,7 @@ class ProjectDirectory(Directory):
             self.project_object_file = ObjectFile(self.inode, self.project_object)
             self.inodes.add_entry(self.project_object_file)
 
             self.project_object_file = ObjectFile(self.inode, self.project_object)
             self.inodes.add_entry(self.project_object_file)
 
-        if not self._full_listing:
+        if self.recursively_contained or not self._full_listing:
             return True
 
         def samefn(a, i):
             return True
 
         def samefn(a, i):
@@ -1092,7 +1138,6 @@ class ProjectDirectory(Directory):
                         *self._filters_for('collections', qualified=True),
                     ],
                 ) if obj['current_version_uuid'] == obj['uuid'])
                         *self._filters_for('collections', qualified=True),
                     ],
                 ) if obj['current_version_uuid'] == obj['uuid'])
-
             # end with llfuse.lock_released, re-acquire lock
 
             self.merge(contents,
             # end with llfuse.lock_released, re-acquire lock
 
             self.merge(contents,
@@ -1175,6 +1220,12 @@ class ProjectDirectory(Directory):
     def persisted(self):
         return True
 
     def persisted(self):
         return True
 
+    def clear(self):
+        super(ProjectDirectory, self).clear()
+        if self.project_object_file is not None:
+            self.inodes.del_entry(self.project_object_file)
+        self.project_object_file = None
+
     @use_counter
     @check_update
     def mkdir(self, name):
     @use_counter
     @check_update
     def mkdir(self, name):
@@ -1294,7 +1345,7 @@ class SharedDirectory(Directory):
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters,
                  exclude, poll=False, poll_time=60, storage_classes=None):
 
     def __init__(self, parent_inode, inodes, api, num_retries, enable_write, filters,
                  exclude, poll=False, poll_time=60, storage_classes=None):
-        super(SharedDirectory, self).__init__(parent_inode, inodes, api.config, enable_write, filters)
+        super(SharedDirectory, self).__init__(parent_inode, inodes, enable_write, filters)
         self.api = api
         self.num_retries = num_retries
         self.current_user = api.users().current().execute(num_retries=num_retries)
         self.api = api
         self.num_retries = num_retries
         self.current_user = api.users().current().execute(num_retries=num_retries)
index 45d3db16fe00d7edb802f8d279334b312d8fcc48..fce6c9b6146434050288e6be5ef436949b0b9b7d 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
-from builtins import bytes
 import json
 import llfuse
 import logging
 import json
 import llfuse
 import logging
@@ -80,9 +78,17 @@ class FuseArvadosFile(File):
             if self.writable():
                 self.arvfile.parent.root_collection().save()
 
             if self.writable():
                 self.arvfile.parent.root_collection().save()
 
+    def clear(self):
+        if self.parent_inode is None:
+            self.arvfile.fuse_entry = None
+            self.arvfile = None
+
 
 class StringFile(File):
     """Wrap a simple string as a file"""
 
 class StringFile(File):
     """Wrap a simple string as a file"""
+
+    __slots__ = ("contents",)
+
     def __init__(self, parent_inode, contents, _mtime):
         super(StringFile, self).__init__(parent_inode, _mtime)
         self.contents = contents
     def __init__(self, parent_inode, contents, _mtime):
         super(StringFile, self).__init__(parent_inode, _mtime)
         self.contents = contents
@@ -97,6 +103,8 @@ class StringFile(File):
 class ObjectFile(StringFile):
     """Wrap a dict as a serialized json object."""
 
 class ObjectFile(StringFile):
     """Wrap a dict as a serialized json object."""
 
+    __slots__ = ("object_uuid",)
+
     def __init__(self, parent_inode, obj):
         super(ObjectFile, self).__init__(parent_inode, "", 0)
         self.object_uuid = obj['uuid']
     def __init__(self, parent_inode, obj):
         super(ObjectFile, self).__init__(parent_inode, "", 0)
         self.object_uuid = obj['uuid']
@@ -125,6 +133,9 @@ class FuncToJSONFile(StringFile):
     The function is called at the time the file is read. The result is
     cached until invalidate() is called.
     """
     The function is called at the time the file is read. The result is
     cached until invalidate() is called.
     """
+
+    __slots__ = ("func",)
+
     def __init__(self, parent_inode, func):
         super(FuncToJSONFile, self).__init__(parent_inode, "", 0)
         self.func = func
     def __init__(self, parent_inode, func):
         super(FuncToJSONFile, self).__init__(parent_inode, "", 0)
         self.func = func
index d8eec3d9ee98bcdf1bd2ea603d237c5265c1750d..ca20de4c5813d4d0a9bf7dc6a1e74ce7ac68c43c 100644 (file)
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
+#
+# This file runs in one of three modes:
+#
+# 1. If the ARVADOS_BUILDING_VERSION environment variable is set, it writes
+#    _version.py and generates dependencies based on that value.
+# 2. If running from an arvados Git checkout, it writes _version.py
+#    and generates dependencies from Git.
+# 3. Otherwise, we expect this is source previously generated from Git, and
+#    it reads _version.py and generates dependencies from it.
 
 
-import subprocess
-import time
 import os
 import re
 import os
 import re
+import runpy
+import subprocess
 import sys
 
 import sys
 
-SETUP_DIR = os.path.dirname(os.path.abspath(__file__))
-VERSION_PATHS = {
-        SETUP_DIR,
-        os.path.abspath(os.path.join(SETUP_DIR, "../../sdk/python")),
-        os.path.abspath(os.path.join(SETUP_DIR, "../../build/version-at-commit.sh"))
-        }
+from pathlib import Path
+
+# These maps explain the relationships between different Python modules in
+# the arvados repository. We use these to help generate setup.py.
+PACKAGE_DEPENDENCY_MAP = {
+    'arvados-cwl-runner': ['arvados-python-client', 'crunchstat_summary'],
+    'arvados-user-activity': ['arvados-python-client'],
+    'arvados_fuse': ['arvados-python-client'],
+    'crunchstat_summary': ['arvados-python-client'],
+}
+PACKAGE_MODULE_MAP = {
+    'arvados-cwl-runner': 'arvados_cwl',
+    'arvados-docker-cleaner': 'arvados_docker',
+    'arvados-python-client': 'arvados',
+    'arvados-user-activity': 'arvados_user_activity',
+    'arvados_fuse': 'arvados_fuse',
+    'crunchstat_summary': 'crunchstat_summary',
+}
+PACKAGE_SRCPATH_MAP = {
+    'arvados-cwl-runner': Path('sdk', 'cwl'),
+    'arvados-docker-cleaner': Path('services', 'dockercleaner'),
+    'arvados-python-client': Path('sdk', 'python'),
+    'arvados-user-activity': Path('tools', 'user-activity'),
+    'arvados_fuse': Path('services', 'fuse'),
+    'crunchstat_summary': Path('tools', 'crunchstat-summary'),
+}
+
+ENV_VERSION = os.environ.get("ARVADOS_BUILDING_VERSION")
+SETUP_DIR = Path(__file__).absolute().parent
+try:
+    REPO_PATH = Path(subprocess.check_output(
+        ['git', '-C', str(SETUP_DIR), 'rev-parse', '--show-toplevel'],
+        stderr=subprocess.DEVNULL,
+        text=True,
+    ).rstrip('\n'))
+except (subprocess.CalledProcessError, OSError):
+    REPO_PATH = None
+else:
+    # Verify this is the arvados monorepo
+    if all((REPO_PATH / path).exists() for path in PACKAGE_SRCPATH_MAP.values()):
+        PACKAGE_NAME, = (
+            pkg_name for pkg_name, path in PACKAGE_SRCPATH_MAP.items()
+            if (REPO_PATH / path) == SETUP_DIR
+        )
+        MODULE_NAME = PACKAGE_MODULE_MAP[PACKAGE_NAME]
+        VERSION_SCRIPT_PATH = Path(REPO_PATH, 'build', 'version-at-commit.sh')
+    else:
+        REPO_PATH = None
+if REPO_PATH is None:
+    (PACKAGE_NAME, MODULE_NAME), = (
+        (pkg_name, mod_name)
+        for pkg_name, mod_name in PACKAGE_MODULE_MAP.items()
+        if (SETUP_DIR / mod_name).is_dir()
+    )
+
+def git_log_output(path, *args):
+    return subprocess.check_output(
+        ['git', '-C', str(REPO_PATH),
+         'log', '--first-parent', '--max-count=1',
+         *args, str(path)],
+        text=True,
+    ).rstrip('\n')
 
 def choose_version_from():
 
 def choose_version_from():
-    ts = {}
-    for path in VERSION_PATHS:
-        ts[subprocess.check_output(
-            ['git', 'log', '--first-parent', '--max-count=1',
-             '--format=format:%ct', path]).strip()] = path
-
-    sorted_ts = sorted(ts.items())
-    getver = sorted_ts[-1][1]
-    print("Using "+getver+" for version number calculation of "+SETUP_DIR, file=sys.stderr)
+    ver_paths = [SETUP_DIR, VERSION_SCRIPT_PATH, *(
+        PACKAGE_SRCPATH_MAP[pkg]
+        for pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ())
+    )]
+    getver = max(ver_paths, key=lambda path: git_log_output(path, '--format=format:%ct'))
+    print(f"Using {getver} for version number calculation of {SETUP_DIR}", file=sys.stderr)
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
-    myhash = subprocess.check_output(['git', 'log', '-n1', '--first-parent',
-                                       '--format=%H', curdir]).strip()
-    myversion = subprocess.check_output([SETUP_DIR+'/../../build/version-at-commit.sh', myhash]).strip().decode()
-    return myversion
+    myhash = git_log_output(curdir, '--format=%H')
+    return subprocess.check_output(
+        [str(VERSION_SCRIPT_PATH), myhash],
+        text=True,
+    ).rstrip('\n')
 
 def save_version(setup_dir, module, v):
 
 def save_version(setup_dir, module, v):
-    v = v.replace("~dev", ".dev").replace("~rc", "rc")
-    with open(os.path.join(setup_dir, module, "_version.py"), 'wt') as fp:
-        return fp.write("__version__ = '%s'\n" % v)
+    with Path(setup_dir, module, '_version.py').open('w') as fp:
+        print(f"__version__ = {v!r}", file=fp)
 
 def read_version(setup_dir, module):
 
 def read_version(setup_dir, module):
-    with open(os.path.join(setup_dir, module, "_version.py"), 'rt') as fp:
-        return re.match("__version__ = '(.*)'$", fp.read()).groups()[0]
+    file_vars = runpy.run_path(Path(setup_dir, module, '_version.py'))
+    return file_vars['__version__']
 
 
-def get_version(setup_dir, module):
-    env_version = os.environ.get("ARVADOS_BUILDING_VERSION")
-
-    if env_version:
-        save_version(setup_dir, module, env_version)
+def get_version(setup_dir=SETUP_DIR, module=MODULE_NAME):
+    if ENV_VERSION:
+        version = ENV_VERSION
+    elif REPO_PATH is None:
+        return read_version(setup_dir, module)
     else:
     else:
-        try:
-            save_version(setup_dir, module, git_version_at_commit())
-        except (subprocess.CalledProcessError, OSError) as err:
-            print("ERROR: {0}".format(err), file=sys.stderr)
-            pass
+        version = git_version_at_commit()
+    version = version.replace("~dev", ".dev").replace("~rc", "rc")
+    save_version(setup_dir, module, version)
+    return version
+
+def iter_dependencies(version=None):
+    if version is None:
+        version = get_version()
+    # A packaged development release should be installed with other
+    # development packages built from the same source, but those
+    # dependencies may have earlier "dev" versions (read: less recent
+    # Git commit timestamps). This compatible version dependency
+    # expresses that as closely as possible. Allowing versions
+    # compatible with .dev0 allows any development release.
+    # Regular expression borrowed partially from
+    # <https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers-regex>
+    dep_ver, match_count = re.subn(r'\.dev(0|[1-9][0-9]*)$', '.dev0', version, 1)
+    dep_op = '~=' if match_count else '=='
+    for dep_pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ()):
+        yield f'{dep_pkg}{dep_op}{dep_ver}'
 
 
-    return read_version(setup_dir, module)
+# Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
+if __name__ == '__main__':
+    print(get_version())
diff --git a/services/fuse/pytest.ini b/services/fuse/pytest.ini
new file mode 120000 (symlink)
index 0000000..05a82db
--- /dev/null
@@ -0,0 +1 @@
+../../sdk/python/pytest.ini
\ No newline at end of file
index b04829652e948b4de22c3c433620287c4fb51ef1..f9b0fcf91dcb614c9224fefe8b4836cbcffd74cd 100644 (file)
@@ -3,28 +3,15 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
-SETUP_DIR = os.path.dirname(__file__) or '.'
-README = os.path.join(SETUP_DIR, 'README.rst')
-
 import arvados_version
 import arvados_version
-version = arvados_version.get_version(SETUP_DIR, "arvados_fuse")
-if os.environ.get('ARVADOS_BUILDING_VERSION', False):
-    pysdk_dep = "=={}".format(version)
-else:
-    # On dev releases, arvados-python-client may have a different timestamp
-    pysdk_dep = "<={}".format(version)
-
-short_tests_only = False
-if '--short-tests-only' in sys.argv:
-    short_tests_only = True
-    sys.argv.remove('--short-tests-only')
+version = arvados_version.get_version()
+README = os.path.join(arvados_version.SETUP_DIR, 'README.rst')
 
 setup(name='arvados_fuse',
       version=version,
 
 setup(name='arvados_fuse',
       version=version,
@@ -43,9 +30,8 @@ setup(name='arvados_fuse',
           ('share/doc/arvados_fuse', ['agpl-3.0.txt', 'README.rst']),
       ],
       install_requires=[
           ('share/doc/arvados_fuse', ['agpl-3.0.txt', 'README.rst']),
       ],
       install_requires=[
-        'arvados-python-client{}'.format(pysdk_dep),
-        'llfuse >= 1.3.6',
-        'future',
+        *arvados_version.iter_dependencies(version),
+        'arvados-llfuse >= 1.5.1',
         'python-daemon',
         'ciso8601 >= 2.0.0',
         'setuptools',
         'python-daemon',
         'ciso8601 >= 2.0.0',
         'setuptools',
@@ -56,6 +42,6 @@ setup(name='arvados_fuse',
           'Programming Language :: Python :: 3',
       ],
       test_suite='tests',
           'Programming Language :: Python :: 3',
       ],
       test_suite='tests',
-      tests_require=['pbr<1.7.0', 'mock>=1.0', 'PyYAML', 'parameterized',],
+      tests_require=['PyYAML', 'parameterized',],
       zip_safe=False
       )
       zip_safe=False
       )
index 51e3f311ab34756a6e8f39cf3381bcdefaeb8a0c..296f23919c6387f7af4d56a1f9ce77a4ecb275c7 100644 (file)
@@ -2,14 +2,11 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import print_function
-from __future__ import absolute_import
-from builtins import str
-from builtins import range
-from multiprocessing import Process
 import os
 import subprocess
 import sys
 import os
 import subprocess
 import sys
+
+from multiprocessing import Process
 from . import prof
 
 def fn(n):
 from . import prof
 
 def fn(n):
index 89b39dbc87e10677c3024d4566c9325cae756048..24ac7baf046ec526954c35f35b9f390aaf857478 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
 import arvados
 import arvados_fuse
 import arvados_fuse.command
 import arvados
 import arvados_fuse
 import arvados_fuse.command
@@ -12,12 +11,15 @@ import inspect
 import logging
 import multiprocessing
 import os
 import logging
 import multiprocessing
 import os
-from . import run_test_server
 import signal
 import sys
 import tempfile
 import unittest
 
 import signal
 import sys
 import tempfile
 import unittest
 
+import pytest
+
+from . import run_test_server
+
 @atexit.register
 def _pool_cleanup():
     if _pool is None:
 @atexit.register
 def _pool_cleanup():
     if _pool is None:
@@ -86,14 +88,20 @@ class IntegrationTest(unittest.TestCase):
                     with arvados_fuse.command.Mount(
                             arvados_fuse.command.ArgumentParser().parse_args(
                                 argv + ['--foreground',
                     with arvados_fuse.command.Mount(
                             arvados_fuse.command.ArgumentParser().parse_args(
                                 argv + ['--foreground',
-                                        '--unmount-timeout=2',
+                                        '--unmount-timeout=60',
                                         self.mnt])) as self.mount:
                         return func(self, *args, **kwargs)
                 finally:
                     if self.mount and self.mount.llfuse_thread.is_alive():
                                         self.mnt])) as self.mount:
                         return func(self, *args, **kwargs)
                 finally:
                     if self.mount and self.mount.llfuse_thread.is_alive():
-                        logging.warning("IntegrationTest.mount:"
-                                            " llfuse thread still alive after umount"
-                                            " -- killing test suite to avoid deadlock")
-                        os.kill(os.getpid(), signal.SIGKILL)
+                        # pytest uses exit status 2 when test collection failed.
+                        # A UnitTest failing in setup/teardown counts as a
+                        # collection failure, so pytest will exit with status 2
+                        # no matter what status you specify here. run-tests.sh
+                        # looks for this status, so specify 2 just to keep
+                        # everything as consistent as possible.
+                        # TODO: If we refactor these tests so they're not built
+                        # on unittest, consider using a dedicated, non-pytest
+                        # exit code like TEMPFAIL.
+                        pytest.exit("llfuse thread outlived test - aborting test suite to avoid deadlock", 2)
             return wrapper
         return decorator
             return wrapper
         return decorator
index 8a3522e0cb0df7e11aec61279ab530d3d2395e44..9768aeb74da0cef7ac8b3ff423590c455c9faf26 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
 import arvados
 import arvados.keep
 import arvados_fuse as fuse
 import arvados
 import arvados.keep
 import arvados_fuse as fuse
@@ -11,7 +10,6 @@ import llfuse
 import logging
 import multiprocessing
 import os
 import logging
 import multiprocessing
 import os
-from . import run_test_server
 import shutil
 import signal
 import subprocess
 import shutil
 import signal
 import subprocess
@@ -21,10 +19,13 @@ import threading
 import time
 import unittest
 
 import time
 import unittest
 
-logger = logging.getLogger('arvados.arv-mount')
+import pytest
 
 
+from . import run_test_server
 from .integration_test import workerPool
 
 from .integration_test import workerPool
 
+logger = logging.getLogger('arvados.arv-mount')
+
 def make_block_cache(disk_cache):
     if disk_cache:
         disk_cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "arvados", "keep")
 def make_block_cache(disk_cache):
     if disk_cache:
         disk_cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "arvados", "keep")
@@ -102,12 +103,18 @@ class MountTestBase(unittest.TestCase):
                 self.operations.events.close(timeout=10)
             subprocess.call(["fusermount", "-u", "-z", self.mounttmp])
             t0 = time.time()
                 self.operations.events.close(timeout=10)
             subprocess.call(["fusermount", "-u", "-z", self.mounttmp])
             t0 = time.time()
-            self.llfuse_thread.join(timeout=10)
+            self.llfuse_thread.join(timeout=60)
             if self.llfuse_thread.is_alive():
             if self.llfuse_thread.is_alive():
-                logger.warning("MountTestBase.tearDown():"
-                               " llfuse thread still alive 10s after umount"
-                               " -- exiting with SIGKILL")
-                os.kill(os.getpid(), signal.SIGKILL)
+                # pytest uses exit status 2 when test collection failed.
+                # A UnitTest failing in setup/teardown counts as a
+                # collection failure, so pytest will exit with status 2
+                # no matter what status you specify here. run-tests.sh
+                # looks for this status, so specify 2 just to keep
+                # everything as consistent as possible.
+                # TODO: If we refactor these tests so they're not built
+                # on unittest, consider using a dedicated, non-pytest
+                # exit code like TEMPFAIL.
+                pytest.exit("llfuse thread outlived test - aborting test suite to avoid deadlock", 2)
             waited = time.time() - t0
             if waited > 0.1:
                 logger.warning("MountTestBase.tearDown(): waited %f s for llfuse thread to end", waited)
             waited = time.time() - t0
             if waited > 0.1:
                 logger.warning("MountTestBase.tearDown(): waited %f s for llfuse thread to end", waited)
diff --git a/services/fuse/tests/performance/__init__.py b/services/fuse/tests/performance/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/services/fuse/tests/performance/performance_profiler.py b/services/fuse/tests/performance/performance_profiler.py
deleted file mode 120000 (symlink)
index 01a6805..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../../../../sdk/python/tests/performance/performance_profiler.py
\ No newline at end of file
diff --git a/services/fuse/tests/performance/test_collection_performance.py b/services/fuse/tests/performance/test_collection_performance.py
deleted file mode 100644 (file)
index 98bc98a..0000000
+++ /dev/null
@@ -1,491 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-from __future__ import absolute_import
-from future.utils import viewitems
-from builtins import str
-from builtins import range
-import arvados
-import arvados_fuse as fuse
-import llfuse
-import logging
-import os
-import sys
-import unittest
-from .. import run_test_server
-from ..mount_test_base import MountTestBase
-from ..slow_test import slow_test
-
-logger = logging.getLogger('arvados.arv-mount')
-
-from .performance_profiler import profiled
-
-def fuse_createCollectionWithMultipleBlocks(mounttmp, streams=1, files_per_stream=1, data='x'):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.createCollectionWithMultipleBlocks()
-
-        @profiled
-        def createCollectionWithMultipleBlocks(self):
-            for i in range(0, streams):
-                os.mkdir(os.path.join(mounttmp, "./stream" + str(i)))
-
-                # Create files
-                for j in range(0, files_per_stream):
-                    with open(os.path.join(mounttmp, "./stream" + str(i), "file" + str(j) +".txt"), "w") as f:
-                        f.write(data)
-
-    Test().runTest()
-
-def fuse_readContentsFromCollectionWithMultipleBlocks(mounttmp, streams=1, files_per_stream=1, data='x'):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.readContentsFromCollectionWithMultipleBlocks()
-
-        @profiled
-        def readContentsFromCollectionWithMultipleBlocks(self):
-            for i in range(0, streams):
-                d1 = llfuse.listdir(os.path.join(mounttmp, 'stream'+str(i)))
-                for j in range(0, files_per_stream):
-                    with open(os.path.join(mounttmp, 'stream'+str(i), 'file'+str(i)+'.txt')) as f:
-                        self.assertEqual(data, f.read())
-
-    Test().runTest()
-
-def fuse_moveFileFromCollectionWithMultipleBlocks(mounttmp, stream, filename):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.moveFileFromCollectionWithMultipleBlocks()
-
-        @profiled
-        def moveFileFromCollectionWithMultipleBlocks(self):
-            d1 = llfuse.listdir(os.path.join(mounttmp, stream))
-            self.assertIn(filename, d1)
-
-            os.rename(os.path.join(mounttmp, stream, filename), os.path.join(mounttmp, 'moved_from_'+stream+'_'+filename))
-
-            d1 = llfuse.listdir(os.path.join(mounttmp))
-            self.assertIn('moved_from_'+stream+'_'+filename, d1)
-
-            d1 = llfuse.listdir(os.path.join(mounttmp, stream))
-            self.assertNotIn(filename, d1)
-
-    Test().runTest()
-
-def fuse_deleteFileFromCollectionWithMultipleBlocks(mounttmp, stream, filename):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.deleteFileFromCollectionWithMultipleBlocks()
-
-        @profiled
-        def deleteFileFromCollectionWithMultipleBlocks(self):
-            os.remove(os.path.join(mounttmp, stream, filename))
-
-    Test().runTest()
-
-# Create a collection with 2 streams, 3 files_per_stream, 2 blocks_per_file, 2**26 bytes_per_block
-class CreateCollectionWithMultipleBlocksAndMoveAndDeleteFile(MountTestBase):
-    def setUp(self):
-        super(CreateCollectionWithMultipleBlocksAndMoveAndDeleteFile, self).setUp()
-
-    @slow_test
-    def test_CreateCollectionWithManyBlocksAndMoveAndDeleteFile(self):
-        collection = arvados.collection.Collection(api_client=self.api)
-        collection.save_new()
-
-        m = self.make_mount(fuse.CollectionDirectory)
-        with llfuse.lock:
-            m.new_collection(collection.api_response(), collection)
-        self.assertTrue(m.writable())
-
-        streams = 2
-        files_per_stream = 3
-        blocks_per_file = 2
-        bytes_per_block = 2**26
-
-        data = 'x' * blocks_per_file * bytes_per_block
-
-        self.pool.apply(fuse_createCollectionWithMultipleBlocks, (self.mounttmp, streams, files_per_stream, data,))
-
-        collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-
-        for i in range(0, streams):
-            self.assertIn('./stream' + str(i), collection2["manifest_text"])
-
-        for i in range(0, files_per_stream):
-            self.assertIn('file' + str(i) + '.txt', collection2["manifest_text"])
-
-        # Read file contents
-        self.pool.apply(fuse_readContentsFromCollectionWithMultipleBlocks, (self.mounttmp, streams, files_per_stream, data,))
-
-        # Move file0.txt out of the streams into .
-        for i in range(0, streams):
-            self.pool.apply(fuse_moveFileFromCollectionWithMultipleBlocks, (self.mounttmp, 'stream'+str(i), 'file0.txt',))
-
-        collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-
-        manifest_streams = collection2['manifest_text'].split('\n')
-        self.assertEqual(4, len(manifest_streams))
-
-        for i in range(0, streams):
-            self.assertIn('file0.txt', manifest_streams[0])
-
-        for i in range(0, streams):
-            self.assertNotIn('file0.txt', manifest_streams[i+1])
-
-        for i in range(0, streams):
-            for j in range(1, files_per_stream):
-                self.assertIn('file' + str(j) + '.txt', manifest_streams[i+1])
-
-        # Delete 'file1.txt' from all the streams
-        for i in range(0, streams):
-            self.pool.apply(fuse_deleteFileFromCollectionWithMultipleBlocks, (self.mounttmp, 'stream'+str(i), 'file1.txt'))
-
-        collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-
-        manifest_streams = collection2['manifest_text'].split('\n')
-        self.assertEqual(4, len(manifest_streams))
-
-        for i in range(0, streams):
-            self.assertIn('file0.txt', manifest_streams[0])
-
-        self.assertNotIn('file1.txt', collection2['manifest_text'])
-
-        for i in range(0, streams):
-            for j in range(2, files_per_stream):
-                self.assertIn('file' + str(j) + '.txt', manifest_streams[i+1])
-
-
-def fuse_createCollectionWithManyFiles(mounttmp, streams=1, files_per_stream=1, data='x'):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.createCollectionWithManyFiles()
-
-        @profiled
-        def createCollectionWithManyFiles(self):
-            for i in range(0, streams):
-                os.mkdir(os.path.join(mounttmp, "./stream" + str(i)))
-
-                # Create files
-                for j in range(0, files_per_stream):
-                    with open(os.path.join(mounttmp, "./stream" + str(i), "file" + str(j) +".txt"), "w") as f:
-                        f.write(data)
-
-    Test().runTest()
-
-def fuse_readContentsFromCollectionWithManyFiles(mounttmp, streams=1, files_per_stream=1, data='x'):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.readContentsFromCollectionWithManyFiles()
-
-        @profiled
-        def readContentsFromCollectionWithManyFiles(self):
-            for i in range(0, streams):
-                d1 = llfuse.listdir(os.path.join(mounttmp, 'stream'+str(i)))
-                for j in range(0, files_per_stream):
-                    with open(os.path.join(mounttmp, 'stream'+str(i), 'file'+str(i)+'.txt')) as f:
-                        self.assertEqual(data, f.read())
-
-    Test().runTest()
-
-def fuse_moveFileFromCollectionWithManyFiles(mounttmp, stream, filename):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.moveFileFromCollectionWithManyFiles()
-
-        @profiled
-        def moveFileFromCollectionWithManyFiles(self):
-            d1 = llfuse.listdir(os.path.join(mounttmp, stream))
-            self.assertIn(filename, d1)
-
-            os.rename(os.path.join(mounttmp, stream, filename), os.path.join(mounttmp, 'moved_from_'+stream+'_'+filename))
-
-            d1 = llfuse.listdir(os.path.join(mounttmp))
-            self.assertIn('moved_from_'+stream+'_'+filename, d1)
-
-            d1 = llfuse.listdir(os.path.join(mounttmp, stream))
-            self.assertNotIn(filename, d1)
-
-    Test().runTest()
-
-def fuse_deleteFileFromCollectionWithManyFiles(mounttmp, stream, filename):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.deleteFileFromCollectionWithManyFiles()
-
-        @profiled
-        def deleteFileFromCollectionWithManyFiles(self):
-            os.remove(os.path.join(mounttmp, stream, filename))
-
-    Test().runTest()
-
-# Create a collection with two streams, each with 200 files
-class CreateCollectionWithManyFilesAndMoveAndDeleteFile(MountTestBase):
-    def setUp(self):
-        super(CreateCollectionWithManyFilesAndMoveAndDeleteFile, self).setUp()
-
-    @slow_test
-    def test_CreateCollectionWithManyFilesAndMoveAndDeleteFile(self):
-        collection = arvados.collection.Collection(api_client=self.api)
-        collection.save_new()
-
-        m = self.make_mount(fuse.CollectionDirectory)
-        with llfuse.lock:
-            m.new_collection(collection.api_response(), collection)
-        self.assertTrue(m.writable())
-
-        streams = 2
-        files_per_stream = 200
-        data = 'x'
-
-        self.pool.apply(fuse_createCollectionWithManyFiles, (self.mounttmp, streams, files_per_stream, data,))
-
-        collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-
-        for i in range(0, streams):
-            self.assertIn('./stream' + str(i), collection2["manifest_text"])
-
-        for i in range(0, files_per_stream):
-            self.assertIn('file' + str(i) + '.txt', collection2["manifest_text"])
-
-        # Read file contents
-        self.pool.apply(fuse_readContentsFromCollectionWithManyFiles, (self.mounttmp, streams, files_per_stream, data,))
-
-        # Move file0.txt out of the streams into .
-        for i in range(0, streams):
-            self.pool.apply(fuse_moveFileFromCollectionWithManyFiles, (self.mounttmp, 'stream'+str(i), 'file0.txt',))
-
-        collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-
-        manifest_streams = collection2['manifest_text'].split('\n')
-        self.assertEqual(4, len(manifest_streams))
-
-        for i in range(0, streams):
-            self.assertIn('file0.txt', manifest_streams[0])
-
-        for i in range(0, streams):
-            self.assertNotIn('file0.txt', manifest_streams[i+1])
-
-        for i in range(0, streams):
-            for j in range(1, files_per_stream):
-                self.assertIn('file' + str(j) + '.txt', manifest_streams[i+1])
-
-        # Delete 'file1.txt' from all the streams
-        for i in range(0, streams):
-            self.pool.apply(fuse_deleteFileFromCollectionWithManyFiles, (self.mounttmp, 'stream'+str(i), 'file1.txt'))
-
-        collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-
-        manifest_streams = collection2['manifest_text'].split('\n')
-        self.assertEqual(4, len(manifest_streams))
-
-        for i in range(0, streams):
-            self.assertIn('file0.txt', manifest_streams[0])
-
-        self.assertNotIn('file1.txt', collection2['manifest_text'])
-
-        for i in range(0, streams):
-            for j in range(2, files_per_stream):
-                self.assertIn('file' + str(j) + '.txt', manifest_streams[i+1])
-
-
-def magicDirTest_MoveFileFromCollection(mounttmp, collection1, collection2, stream, filename):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.magicDirTest_moveFileFromCollection()
-
-        @profiled
-        def magicDirTest_moveFileFromCollection(self):
-            os.rename(os.path.join(mounttmp, collection1, filename), os.path.join(mounttmp, collection2, filename))
-
-    Test().runTest()
-
-def magicDirTest_RemoveFileFromCollection(mounttmp, collection1, stream, filename):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.magicDirTest_removeFileFromCollection()
-
-        @profiled
-        def magicDirTest_removeFileFromCollection(self):
-            os.remove(os.path.join(mounttmp, collection1, filename))
-
-    Test().runTest()
-
-class UsingMagicDir_CreateCollectionWithManyFilesAndMoveAndDeleteFile(MountTestBase):
-    def setUp(self):
-        super(UsingMagicDir_CreateCollectionWithManyFilesAndMoveAndDeleteFile, self).setUp()
-
-    @profiled
-    def magicDirTest_createCollectionWithManyFiles(self, streams=0, files_per_stream=0, data='x'):
-        # Create collection
-        collection = arvados.collection.Collection(api_client=self.api)
-        for j in range(0, files_per_stream):
-            with collection.open("file"+str(j)+".txt", "w") as f:
-                f.write(data)
-        collection.save_new()
-        return collection
-
-    @profiled
-    def magicDirTest_readCollectionContents(self, collection, streams=1, files_per_stream=1, data='x'):
-        mount_ls = os.listdir(os.path.join(self.mounttmp, collection))
-
-        files = {}
-        for j in range(0, files_per_stream):
-            files[os.path.join(self.mounttmp, collection, 'file'+str(j)+'.txt')] = data
-
-        for k, v in viewItems(files):
-            with open(os.path.join(self.mounttmp, collection, k)) as f:
-                self.assertEqual(v, f.read())
-
-    @slow_test
-    def test_UsingMagicDirCreateCollectionWithManyFilesAndMoveAndDeleteFile(self):
-        streams = 2
-        files_per_stream = 200
-        data = 'x'
-
-        collection1 = self.magicDirTest_createCollectionWithManyFiles()
-        # Create collection with multiple files
-        collection2 = self.magicDirTest_createCollectionWithManyFiles(streams, files_per_stream, data)
-
-        # Mount FuseMagicDir
-        self.make_mount(fuse.MagicDirectory)
-
-        self.magicDirTest_readCollectionContents(collection2.manifest_locator(), streams, files_per_stream, data)
-
-        # Move file0.txt out of the collection2 into collection1
-        self.pool.apply(magicDirTest_MoveFileFromCollection, (self.mounttmp, collection2.manifest_locator(),
-              collection1.manifest_locator(), 'stream0', 'file0.txt',))
-        updated_collection = self.api.collections().get(uuid=collection2.manifest_locator()).execute()
-        self.assertFalse('file0.txt' in updated_collection['manifest_text'])
-        self.assertTrue('file1.txt' in updated_collection['manifest_text'])
-
-        # Delete file1.txt from collection2
-        self.pool.apply(magicDirTest_RemoveFileFromCollection, (self.mounttmp, collection2.manifest_locator(), 'stream0', 'file1.txt',))
-        updated_collection = self.api.collections().get(uuid=collection2.manifest_locator()).execute()
-        self.assertFalse('file1.txt' in updated_collection['manifest_text'])
-        self.assertTrue('file2.txt' in updated_collection['manifest_text'])
-
-
-def magicDirTest_MoveAllFilesFromCollection(mounttmp, from_collection, to_collection, stream, files_per_stream):
-    class Test(unittest.TestCase):
-        def runTest(self):
-            self.magicDirTest_moveAllFilesFromCollection()
-
-        @profiled
-        def magicDirTest_moveAllFilesFromCollection(self):
-            for j in range(0, files_per_stream):
-                os.rename(os.path.join(mounttmp, from_collection, 'file'+str(j)+'.txt'), os.path.join(mounttmp, to_collection, 'file'+str(j)+'.txt'))
-
-    Test().runTest()
-
-class UsingMagicDir_CreateCollectionWithManyFilesAndMoveAllFilesIntoAnother(MountTestBase):
-    def setUp(self):
-        super(UsingMagicDir_CreateCollectionWithManyFilesAndMoveAllFilesIntoAnother, self).setUp()
-
-    @profiled
-    def magicDirTestMoveAllFiles_createCollectionWithManyFiles(self, streams=0, files_per_stream=0,
-            blocks_per_file=0, bytes_per_block=0, data='x'):
-        # Create collection
-        collection = arvados.collection.Collection(api_client=self.api)
-        for j in range(0, files_per_stream):
-            with collection.open("file"+str(j)+".txt", "w") as f:
-                f.write(data)
-        collection.save_new()
-        return collection
-
-    @slow_test
-    def test_UsingMagicDirCreateCollectionWithManyFilesAndMoveAllFilesIntoAnother(self):
-        streams = 2
-        files_per_stream = 200
-        data = 'x'
-
-        collection1 = self.magicDirTestMoveAllFiles_createCollectionWithManyFiles()
-        # Create collection with multiple files
-        collection2 = self.magicDirTestMoveAllFiles_createCollectionWithManyFiles(streams, files_per_stream, data)
-
-        # Mount FuseMagicDir
-        self.make_mount(fuse.MagicDirectory)
-
-        # Move all files from collection2 into collection1
-        self.pool.apply(magicDirTest_MoveAllFilesFromCollection, (self.mounttmp, collection2.manifest_locator(),
-                  collection1.manifest_locator(), 'stream0', files_per_stream,))
-
-        updated_collection = self.api.collections().get(uuid=collection2.manifest_locator()).execute()
-        file_names = ["file%i.txt" % i for i in range(0, files_per_stream)]
-        for name in file_names:
-            self.assertFalse(name in updated_collection['manifest_text'])
-
-        updated_collection = self.api.collections().get(uuid=collection1.manifest_locator()).execute()
-        for name in file_names:
-            self.assertTrue(name in updated_collection['manifest_text'])
-
-
-# Move one file at a time from one collection into another
-class UsingMagicDir_CreateCollectionWithManyFilesAndMoveEachFileIntoAnother(MountTestBase):
-    def setUp(self):
-        super(UsingMagicDir_CreateCollectionWithManyFilesAndMoveEachFileIntoAnother, self).setUp()
-
-    @profiled
-    def magicDirTestMoveFiles_createCollectionWithManyFiles(self, streams=0, files_per_stream=0, data='x'):
-        # Create collection
-        collection = arvados.collection.Collection(api_client=self.api)
-        for j in range(0, files_per_stream):
-            with collection.open("file"+str(j)+".txt", "w") as f:
-                f.write(data)
-        collection.save_new()
-        return collection
-
-    def magicDirTestMoveFiles_oneEachIntoAnother(self, from_collection, to_collection, files_per_stream):
-        for j in range(0, files_per_stream):
-            self.pool.apply(magicDirTest_MoveFileFromCollection, (self.mounttmp, from_collection.manifest_locator(),
-                  to_collection.manifest_locator(), 'stream0', 'file'+str(j)+'.txt',))
-
-    @slow_test
-    def test_UsingMagicDirCreateCollectionWithManyFilesAndMoveEachFileIntoAnother(self):
-        streams = 2
-        files_per_stream = 200
-        data = 'x'
-
-        collection1 = self.magicDirTestMoveFiles_createCollectionWithManyFiles()
-        # Create collection with multiple files
-        collection2 = self.magicDirTestMoveFiles_createCollectionWithManyFiles(streams, files_per_stream, data)
-
-        # Mount FuseMagicDir
-        self.make_mount(fuse.MagicDirectory)
-
-        # Move all files from collection2 into collection1
-        self.magicDirTestMoveFiles_oneEachIntoAnother(collection2, collection1, files_per_stream)
-
-        updated_collection = self.api.collections().get(uuid=collection2.manifest_locator()).execute()
-        file_names = ["file%i.txt" % i for i in range(0, files_per_stream)]
-        for name in file_names:
-            self.assertFalse(name in updated_collection['manifest_text'])
-
-        updated_collection = self.api.collections().get(uuid=collection1.manifest_locator()).execute()
-        for name in file_names:
-            self.assertTrue(name in updated_collection['manifest_text'])
-
-class FuseListLargeProjectContents(MountTestBase):
-    @profiled
-    def getProjectWithManyCollections(self):
-        project_contents = llfuse.listdir(self.mounttmp)
-        self.assertEqual(201, len(project_contents))
-        self.assertIn('Collection_1', project_contents)
-
-    @profiled
-    def listContentsInProjectWithManyCollections(self):
-        project_contents = llfuse.listdir(self.mounttmp)
-        self.assertEqual(201, len(project_contents))
-        self.assertIn('Collection_1', project_contents)
-
-        for collection_name in project_contents:
-            collection_contents = llfuse.listdir(os.path.join(self.mounttmp, collection_name))
-            self.assertIn('baz', collection_contents)
-
-    @slow_test
-    def test_listLargeProjectContents(self):
-        self.make_mount(fuse.ProjectDirectory,
-                        project_object=run_test_server.fixture('groups')['project_with_201_collections'])
-        self.getProjectWithManyCollections()
-        self.listContentsInProjectWithManyCollections()
index f9ce1881de3a70aa578e70f36b3bd5c50c00990d..5bdb1b2e7bfa69a0a809ba145380ef3647399ec8 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import print_function
-from builtins import object
 import time
 
 class CountTime(object):
 import time
 
 class CountTime(object):
diff --git a/services/fuse/tests/slow_test.py b/services/fuse/tests/slow_test.py
deleted file mode 120000 (symlink)
index c7e1f7f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../../../sdk/python/tests/slow_test.py
\ No newline at end of file
index 46ed0be411181a4b8779e6aeb7b6d76e475a6679..3f6b804b92e2f2831f75354f064cec3d0f143211 100644 (file)
@@ -2,7 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from builtins import range
 import arvados
 import arvados.collection
 import arvados_fuse
 import arvados
 import arvados.collection
 import arvados_fuse
index b08ab19335758be4c7ee4f72b91b4f3e26d04cea..a6a387789d65e25f4a4295485af1abd6bcc159ae 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
-from __future__ import print_function
-from six import assertRegex
 import arvados
 import arvados_fuse
 import arvados_fuse.command
 import arvados
 import arvados_fuse
 import arvados_fuse.command
@@ -14,14 +11,16 @@ import io
 import json
 import llfuse
 import logging
 import json
 import llfuse
 import logging
-import mock
 import os
 import os
-from . import run_test_server
 import sys
 import tempfile
 import unittest
 import resource
 
 import sys
 import tempfile
 import unittest
 import resource
 
+from unittest import mock
+
+from . import run_test_server
+
 def noexit(func):
     """If argparse or arvados_fuse tries to exit, fail the test instead"""
     class SystemExitCaught(Exception):
 def noexit(func):
     """If argparse or arvados_fuse tries to exit, fail the test instead"""
     class SystemExitCaught(Exception):
@@ -84,13 +83,13 @@ class MountArgsTest(unittest.TestCase):
 
         e = self.check_ent_type(arvados_fuse.StringFile, 'README')
         readme = e.readfrom(0, -1).decode()
 
         e = self.check_ent_type(arvados_fuse.StringFile, 'README')
         readme = e.readfrom(0, -1).decode()
-        assertRegex(self, readme, r'active-user@arvados\.local')
-        assertRegex(self, readme, r'\n$')
+        self.assertRegex(readme, r'active-user@arvados\.local')
+        self.assertRegex(readme, r'\n$')
 
         e = self.check_ent_type(arvados_fuse.StringFile, 'by_id', 'README')
         txt = e.readfrom(0, -1).decode()
 
         e = self.check_ent_type(arvados_fuse.StringFile, 'by_id', 'README')
         txt = e.readfrom(0, -1).decode()
-        assertRegex(self, txt, r'portable data hash')
-        assertRegex(self, txt, r'\n$')
+        self.assertRegex(txt, r'portable data hash')
+        self.assertRegex(txt, r'\n$')
 
     @noexit
     def test_by_id(self):
 
     @noexit
     def test_by_id(self):
@@ -199,7 +198,7 @@ class MountArgsTest(unittest.TestCase):
 
         with self.assertRaises(SystemExit):
             args = arvados_fuse.command.ArgumentParser().parse_args(['--version'])
 
         with self.assertRaises(SystemExit):
             args = arvados_fuse.command.ArgumentParser().parse_args(['--version'])
-        assertRegex(self, sys.stdout.getvalue(), "[0-9]+\.[0-9]+\.[0-9]+")
+        self.assertRegex(sys.stdout.getvalue(), "[0-9]+\.[0-9]+\.[0-9]+")
         sys.stderr.close()
         sys.stderr = origerr
         sys.stdout = origout
         sys.stderr.close()
         sys.stderr = origerr
         sys.stdout = origout
index 3cf15fe1139893427b0b8c355a17ec27c2f7ed15..32272a83c4311e95ebb059e1dd46d91545817f28 100644 (file)
@@ -2,12 +2,10 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
 import subprocess
 
 from .integration_test import IntegrationTest
 
 import subprocess
 
 from .integration_test import IntegrationTest
 
-
 class CrunchstatTest(IntegrationTest):
     def test_crunchstat(self):
         output = subprocess.check_output(
 class CrunchstatTest(IntegrationTest):
     def test_crunchstat(self):
         output = subprocess.check_output(
index f977990026a99e0cb7856e49be80abc390a24b64..c67cc55f341475a28b6dcd1440bcf5a0bfc44230 100644 (file)
@@ -2,17 +2,15 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
-from six import assertRegex
 import arvados_fuse.command
 import json
 import multiprocessing
 import os
 import arvados_fuse.command
 import json
 import multiprocessing
 import os
-from . import run_test_server
 import shlex
 import tempfile
 import unittest
 
 import shlex
 import tempfile
 import unittest
 
+from . import run_test_server
 from .integration_test import workerPool
 
 def try_exec(mnt, cmd):
 from .integration_test import workerPool
 
 def try_exec(mnt, cmd):
@@ -58,7 +56,4 @@ class ExecMode(unittest.TestCase):
                 shlex.quote(os.path.join(self.okfile)),
             )]))
         with open(self.okfile) as f:
                 shlex.quote(os.path.join(self.okfile)),
             )]))
         with open(self.okfile) as f:
-            assertRegex(
-                self,
-                json.load(f)['manifest_text'],
-                r' 0:3:foo.txt\n')
+            self.assertRegex(json.load(f)['manifest_text'], r' 0:3:foo.txt\n')
index 07e6036d08752ae6993bb5c2e8156aeb47454d65..cc22f521e0b5653d8912b280c29c66c8760a89a7 100644 (file)
@@ -3,15 +3,21 @@
 # SPDX-License-Identifier: AGPL-3.0
 
 import arvados_fuse
 # SPDX-License-Identifier: AGPL-3.0
 
 import arvados_fuse
-import mock
 import unittest
 import llfuse
 import logging
 
 import unittest
 import llfuse
 import logging
 
+from unittest import mock
+
 class InodeTests(unittest.TestCase):
 class InodeTests(unittest.TestCase):
+
+    # The following tests call next(inodes._counter) because inode 1
+    # (the root directory) gets special treatment.
+
     def test_inodes_basic(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
     def test_inodes_basic(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
+        next(inodes._counter)
 
         # Check that ent1 gets added to inodes
         ent1 = mock.MagicMock()
 
         # Check that ent1 gets added to inodes
         ent1 = mock.MagicMock()
@@ -27,6 +33,7 @@ class InodeTests(unittest.TestCase):
     def test_inodes_not_persisted(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
     def test_inodes_not_persisted(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
+        next(inodes._counter)
 
         ent1 = mock.MagicMock()
         ent1.in_use.return_value = False
 
         ent1 = mock.MagicMock()
         ent1.in_use.return_value = False
@@ -48,6 +55,7 @@ class InodeTests(unittest.TestCase):
     def test_inode_cleared(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
     def test_inode_cleared(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
+        next(inodes._counter)
 
         # Check that ent1 gets added to inodes
         ent1 = mock.MagicMock()
 
         # Check that ent1 gets added to inodes
         ent1 = mock.MagicMock()
@@ -68,25 +76,31 @@ class InodeTests(unittest.TestCase):
         inodes.add_entry(ent3)
 
         # Won't clear anything because min_entries = 4
         inodes.add_entry(ent3)
 
         # Won't clear anything because min_entries = 4
-        self.assertEqual(2, len(cache._entries))
+        self.assertEqual(2, len(cache._cache_entries))
         self.assertFalse(ent1.clear.called)
         self.assertEqual(1100, cache.total())
 
         # Change min_entries
         cache.min_entries = 1
         self.assertFalse(ent1.clear.called)
         self.assertEqual(1100, cache.total())
 
         # Change min_entries
         cache.min_entries = 1
-        cache.cap_cache()
+        ent1.parent_inode = None
+        inodes.cap_cache()
+        inodes.wait_remove_queue_empty()
         self.assertEqual(600, cache.total())
         self.assertTrue(ent1.clear.called)
 
         # Touching ent1 should cause ent3 to get cleared
         self.assertEqual(600, cache.total())
         self.assertTrue(ent1.clear.called)
 
         # Touching ent1 should cause ent3 to get cleared
+        ent3.parent_inode = None
         self.assertFalse(ent3.clear.called)
         self.assertFalse(ent3.clear.called)
-        cache.touch(ent1)
+        inodes.inode_cache.update_cache_size(ent1)
+        inodes.touch(ent1)
+        inodes.wait_remove_queue_empty()
         self.assertTrue(ent3.clear.called)
         self.assertEqual(500, cache.total())
 
     def test_clear_in_use(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
         self.assertTrue(ent3.clear.called)
         self.assertEqual(500, cache.total())
 
     def test_clear_in_use(self):
         cache = arvados_fuse.InodeCache(1000, 4)
         inodes = arvados_fuse.Inodes(cache)
+        next(inodes._counter)
 
         ent1 = mock.MagicMock()
         ent1.in_use.return_value = True
 
         ent1 = mock.MagicMock()
         ent1.in_use.return_value = True
@@ -109,10 +123,12 @@ class InodeTests(unittest.TestCase):
         ent3.clear.called = False
         self.assertFalse(ent1.clear.called)
         self.assertFalse(ent3.clear.called)
         ent3.clear.called = False
         self.assertFalse(ent1.clear.called)
         self.assertFalse(ent3.clear.called)
-        cache.touch(ent3)
+        inodes.touch(ent3)
+        inodes.wait_remove_queue_empty()
         self.assertFalse(ent1.clear.called)
         self.assertFalse(ent3.clear.called)
         self.assertFalse(ent1.clear.called)
         self.assertFalse(ent3.clear.called)
-        self.assertFalse(ent3.kernel_invalidate.called)
+        # kernel invalidate gets called anyway
+        self.assertTrue(ent3.kernel_invalidate.called)
         self.assertEqual(1100, cache.total())
 
         # ent1 still in use, ent3 doesn't have ref,
         self.assertEqual(1100, cache.total())
 
         # ent1 still in use, ent3 doesn't have ref,
@@ -120,14 +136,17 @@ class InodeTests(unittest.TestCase):
         ent3.has_ref.return_value = False
         ent1.clear.called = False
         ent3.clear.called = False
         ent3.has_ref.return_value = False
         ent1.clear.called = False
         ent3.clear.called = False
-        cache.touch(ent3)
+        ent3.parent_inode = None
+        inodes.touch(ent3)
+        inodes.wait_remove_queue_empty()
         self.assertFalse(ent1.clear.called)
         self.assertTrue(ent3.clear.called)
         self.assertEqual(500, cache.total())
 
     def test_delete(self):
         self.assertFalse(ent1.clear.called)
         self.assertTrue(ent3.clear.called)
         self.assertEqual(500, cache.total())
 
     def test_delete(self):
-        cache = arvados_fuse.InodeCache(1000, 4)
+        cache = arvados_fuse.InodeCache(1000, 0)
         inodes = arvados_fuse.Inodes(cache)
         inodes = arvados_fuse.Inodes(cache)
+        next(inodes._counter)
 
         ent1 = mock.MagicMock()
         ent1.in_use.return_value = False
 
         ent1 = mock.MagicMock()
         ent1.in_use.return_value = False
@@ -147,6 +166,9 @@ class InodeTests(unittest.TestCase):
         ent1.ref_count = 0
         with llfuse.lock:
             inodes.del_entry(ent1)
         ent1.ref_count = 0
         with llfuse.lock:
             inodes.del_entry(ent1)
+        inodes.wait_remove_queue_empty()
         self.assertEqual(0, cache.total())
         self.assertEqual(0, cache.total())
-        cache.touch(ent3)
+
+        inodes.add_entry(ent3)
+        inodes.wait_remove_queue_empty()
         self.assertEqual(600, cache.total())
         self.assertEqual(600, cache.total())
index ef9c25bcf588f0fa7589ce0f06b4f8e1b9263927..f5f61baeb3761ca8564e1a1d79d6be955c3cd554 100644 (file)
@@ -2,36 +2,32 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
-from future.utils import viewitems
-from builtins import str
-from builtins import object
-from pathlib import Path
-from six import assertRegex
 import errno
 import json
 import llfuse
 import logging
 import errno
 import json
 import llfuse
 import logging
-import mock
 import os
 import subprocess
 import time
 import unittest
 import tempfile
 import os
 import subprocess
 import time
 import unittest
 import tempfile
-import parameterized
+
+from pathlib import Path
+from unittest import mock
 
 import arvados
 import arvados_fuse as fuse
 
 import arvados
 import arvados_fuse as fuse
+import parameterized
+
 from arvados_fuse import fusedir
 from arvados_fuse import fusedir
-from . import run_test_server
 
 
+from . import run_test_server
 from .integration_test import IntegrationTest
 from .mount_test_base import MountTestBase
 from .test_tmp_collection import storage_classes_desired
 
 logger = logging.getLogger('arvados.arv-mount')
 
 from .integration_test import IntegrationTest
 from .mount_test_base import MountTestBase
 from .test_tmp_collection import storage_classes_desired
 
 logger = logging.getLogger('arvados.arv-mount')
 
-
 class AssertWithTimeout(object):
     """Allow some time for an assertion to pass."""
 
 class AssertWithTimeout(object):
     """Allow some time for an assertion to pass."""
 
@@ -124,7 +120,7 @@ class FuseMountTest(MountTestBase):
                  'dir2/dir3/thing7.txt': 'data 7',
                  'dir2/dir3/thing8.txt': 'data 8'}
 
                  'dir2/dir3/thing7.txt': 'data 7',
                  'dir2/dir3/thing8.txt': 'data 8'}
 
-        for k, v in viewitems(files):
+        for k, v in files.items():
             with open(os.path.join(self.mounttmp, k), 'rb') as f:
                 self.assertEqual(v, f.read().decode())
 
             with open(os.path.join(self.mounttmp, k), 'rb') as f:
                 self.assertEqual(v, f.read().decode())
 
@@ -189,7 +185,7 @@ class FuseMagicTest(MountTestBase):
         files = {}
         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
 
         files = {}
         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
 
-        for k, v in viewitems(files):
+        for k, v in files.items():
             with open(os.path.join(self.mounttmp, k), 'rb') as f:
                 self.assertEqual(v, f.read().decode())
 
             with open(os.path.join(self.mounttmp, k), 'rb') as f:
                 self.assertEqual(v, f.read().decode())
 
@@ -312,7 +308,7 @@ class FuseHomeTest(MountTestBase):
             'anonymously_accessible_project']
         found_in = 0
         found_not_in = 0
             'anonymously_accessible_project']
         found_in = 0
         found_not_in = 0
-        for name, item in viewitems(run_test_server.fixture('collections')):
+        for name, item in run_test_server.fixture('collections').items():
             if 'name' not in item:
                 pass
             elif item['owner_uuid'] == public_project['uuid']:
             if 'name' not in item:
                 pass
             elif item['owner_uuid'] == public_project['uuid']:
@@ -451,7 +447,7 @@ class FuseCreateFileTest(MountTestBase):
         self.assertEqual(["file1.txt"], d1)
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
         self.assertEqual(["file1.txt"], d1)
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:file1\.txt$')
 
 
             r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:file1\.txt$')
 
 
@@ -494,7 +490,7 @@ class FuseWriteFileTest(MountTestBase):
         self.assertEqual(12, self.operations.read_counter.get())
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
         self.assertEqual(12, self.operations.read_counter.get())
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
 
             r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
 
@@ -533,7 +529,7 @@ class FuseUpdateFileTest(MountTestBase):
         self.pool.apply(fuseUpdateFileTestHelper, (self.mounttmp,))
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
         self.pool.apply(fuseUpdateFileTestHelper, (self.mounttmp,))
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A\S+ 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:11:file1\.txt 22:1:file1\.txt$')
 
 
             r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A\S+ 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:11:file1\.txt 22:1:file1\.txt$')
 
 
@@ -573,7 +569,7 @@ class FuseMkdirTest(MountTestBase):
         self.pool.apply(fuseMkdirTestHelper, (self.mounttmp,))
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
         self.pool.apply(fuseMkdirTestHelper, (self.mounttmp,))
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
 
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
 
@@ -640,13 +636,13 @@ class FuseRmTest(MountTestBase):
 
         # Starting manifest
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
 
         # Starting manifest
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
         self.pool.apply(fuseRmTestHelperDeleteFile, (self.mounttmp,))
 
         # Empty directories are represented by an empty file named "."
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
         self.pool.apply(fuseRmTestHelperDeleteFile, (self.mounttmp,))
 
         # Empty directories are represented by an empty file named "."
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
                                  r'./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
 
         self.pool.apply(fuseRmTestHelperRmdir, (self.mounttmp,))
                                  r'./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
 
         self.pool.apply(fuseRmTestHelperRmdir, (self.mounttmp,))
@@ -697,13 +693,13 @@ class FuseMvFileTest(MountTestBase):
 
         # Starting manifest
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
 
         # Starting manifest
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
         self.pool.apply(fuseMvFileTestHelperMoveFile, (self.mounttmp,))
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
         self.pool.apply(fuseMvFileTestHelperMoveFile, (self.mounttmp,))
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt\n\./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
 
 
             r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt\n\./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
 
 
@@ -731,7 +727,7 @@ class FuseRenameTest(MountTestBase):
 
         # Starting manifest
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
 
         # Starting manifest
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
         d1 = llfuse.listdir(os.path.join(self.mounttmp))
             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
         d1 = llfuse.listdir(os.path.join(self.mounttmp))
@@ -747,7 +743,7 @@ class FuseRenameTest(MountTestBase):
         self.assertEqual(["file1.txt"], d1)
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
         self.assertEqual(["file1.txt"], d1)
 
         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
-        assertRegex(self, collection2["manifest_text"],
+        self.assertRegex(collection2["manifest_text"],
             r'\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
 
             r'\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
 
 
@@ -818,7 +814,7 @@ def fuseFileConflictTestHelper(mounttmp, uuid, keeptmp, settings):
             with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
                 self.assertEqual(f.read(), "bar")
 
             with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
                 self.assertEqual(f.read(), "bar")
 
-            assertRegex(self, d1[1],
+            self.assertRegex(d1[1],
                 r'file1\.txt~\d\d\d\d\d\d\d\d-\d\d\d\d\d\d~conflict~')
 
             with open(os.path.join(mounttmp, d1[1]), "r") as f:
                 r'file1\.txt~\d\d\d\d\d\d\d\d-\d\d\d\d\d\d~conflict~')
 
             with open(os.path.join(mounttmp, d1[1]), "r") as f:
@@ -923,7 +919,7 @@ class FuseMvFileBetweenCollectionsTest(MountTestBase):
         collection1.update()
         collection2.update()
 
         collection1.update()
         collection2.update()
 
-        assertRegex(self, collection1.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
+        self.assertRegex(collection1.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
         self.assertEqual(collection2.manifest_text(), "")
 
         self.pool.apply(fuseMvFileBetweenCollectionsTest2, (self.mounttmp,
         self.assertEqual(collection2.manifest_text(), "")
 
         self.pool.apply(fuseMvFileBetweenCollectionsTest2, (self.mounttmp,
@@ -934,7 +930,7 @@ class FuseMvFileBetweenCollectionsTest(MountTestBase):
         collection2.update()
 
         self.assertEqual(collection1.manifest_text(), "")
         collection2.update()
 
         self.assertEqual(collection1.manifest_text(), "")
-        assertRegex(self, collection2.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file2\.txt$")
+        self.assertRegex(collection2.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file2\.txt$")
 
         collection1.stop_threads()
         collection2.stop_threads()
 
         collection1.stop_threads()
         collection2.stop_threads()
@@ -994,7 +990,7 @@ class FuseMvDirBetweenCollectionsTest(MountTestBase):
         collection1.update()
         collection2.update()
 
         collection1.update()
         collection2.update()
 
-        assertRegex(self, collection1.manifest_text(), r"\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
+        self.assertRegex(collection1.manifest_text(), r"\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
         self.assertEqual(collection2.manifest_text(), "")
 
         self.pool.apply(fuseMvDirBetweenCollectionsTest2, (self.mounttmp,
         self.assertEqual(collection2.manifest_text(), "")
 
         self.pool.apply(fuseMvDirBetweenCollectionsTest2, (self.mounttmp,
@@ -1005,7 +1001,7 @@ class FuseMvDirBetweenCollectionsTest(MountTestBase):
         collection2.update()
 
         self.assertEqual(collection1.manifest_text(), "")
         collection2.update()
 
         self.assertEqual(collection1.manifest_text(), "")
-        assertRegex(self, collection2.manifest_text(), r"\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
+        self.assertRegex(collection2.manifest_text(), r"\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
 
         collection1.stop_threads()
         collection2.stop_threads()
 
         collection1.stop_threads()
         collection2.stop_threads()
@@ -1127,7 +1123,7 @@ class MagicDirApiError(FuseMagicTest):
 class SanitizeFilenameTest(MountTestBase):
     def test_sanitize_filename(self):
         pdir = fuse.ProjectDirectory(
 class SanitizeFilenameTest(MountTestBase):
     def test_sanitize_filename(self):
         pdir = fuse.ProjectDirectory(
-            1, {}, self.api, 0, False, None,
+            1, fuse.Inodes(None), self.api, 0, False, None,
             project_object=self.api.users().current().execute(),
         )
         acceptable = [
             project_object=self.api.users().current().execute(),
         )
         acceptable = [
@@ -1200,7 +1196,7 @@ class FuseMagicTestPDHOnly(MountTestBase):
         files = {}
         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
 
         files = {}
         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
 
-        for k, v in viewitems(files):
+        for k, v in files.items():
             with open(os.path.join(self.mounttmp, k), 'rb') as f:
                 self.assertEqual(v, f.read().decode())
 
             with open(os.path.join(self.mounttmp, k), 'rb') as f:
                 self.assertEqual(v, f.read().decode())
 
@@ -1227,23 +1223,22 @@ class SlashSubstitutionTest(IntegrationTest):
     mnt_args = [
         '--read-write',
         '--mount-home', 'zzz',
     mnt_args = [
         '--read-write',
         '--mount-home', 'zzz',
+        '--fsns', '[SLASH]'
     ]
 
     def setUp(self):
         super(SlashSubstitutionTest, self).setUp()
     ]
 
     def setUp(self):
         super(SlashSubstitutionTest, self).setUp()
+
         self.api = arvados.safeapi.ThreadSafeApiCache(
             arvados.config.settings(),
         self.api = arvados.safeapi.ThreadSafeApiCache(
             arvados.config.settings(),
-            version='v1',
+            version='v1'
         )
         )
-        self.api.config = lambda: {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
         self.testcoll = self.api.collections().create(body={"name": "foo/bar/baz"}).execute()
         self.testcolleasy = self.api.collections().create(body={"name": "foo-bar-baz"}).execute()
         self.fusename = 'foo[SLASH]bar[SLASH]baz'
 
     @IntegrationTest.mount(argv=mnt_args)
         self.testcoll = self.api.collections().create(body={"name": "foo/bar/baz"}).execute()
         self.testcolleasy = self.api.collections().create(body={"name": "foo-bar-baz"}).execute()
         self.fusename = 'foo[SLASH]bar[SLASH]baz'
 
     @IntegrationTest.mount(argv=mnt_args)
-    @mock.patch('arvados.util.get_config_once')
-    def test_slash_substitution_before_listing(self, get_config_once):
-        get_config_once.return_value = {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
+    def test_slash_substitution_before_listing(self):
         self.pool_test(os.path.join(self.mnt, 'zzz'), self.fusename)
         self.checkContents()
     @staticmethod
         self.pool_test(os.path.join(self.mnt, 'zzz'), self.fusename)
         self.checkContents()
     @staticmethod
index 44ab5cce91a4f9d3a746b7f2f2a21151d83871a4..5bc8a0bd454465e317ae7c38171b350ecd623c26 100644 (file)
@@ -2,22 +2,19 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
-from future import standard_library
-standard_library.install_aliases()
 import arvados
 import arvados_fuse.command
 import json
 import arvados
 import arvados_fuse.command
 import json
-import mock
 import os
 import pycurl
 import queue
 import os
 import pycurl
 import queue
-from . import run_test_server
 import tempfile
 import unittest
 
 import tempfile
 import unittest
 
-from .integration_test import IntegrationTest
+from unittest import mock
 
 
+from . import run_test_server
+from .integration_test import IntegrationTest
 
 class KeepClientRetry(unittest.TestCase):
     origKeepClient = arvados.keep.KeepClient
 
 class KeepClientRetry(unittest.TestCase):
     origKeepClient = arvados.keep.KeepClient
index c59024267a4b628dee6948c8259ac7bcda8257e4..7ab6d52243341139dd69abcc3653c4e3968c22b3 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from builtins import range
-from six import assertRegex
 import arvados
 import arvados_fuse
 import arvados_fuse.command
 import arvados
 import arvados_fuse
 import arvados_fuse.command
@@ -18,7 +16,6 @@ from .mount_test_base import MountTestBase
 
 logger = logging.getLogger('arvados.arv-mount')
 
 
 logger = logging.getLogger('arvados.arv-mount')
 
-
 class TmpCollectionArgsTest(unittest.TestCase):
     def setUp(self):
         self.tmpdir = tempfile.mkdtemp()
 class TmpCollectionArgsTest(unittest.TestCase):
     def setUp(self):
         self.tmpdir = tempfile.mkdtemp()
@@ -107,8 +104,7 @@ class TmpCollectionTest(IntegrationTest):
     def _test_tmp_onefile(self, tmpdir):
         with open(os.path.join(tmpdir, 'foo'), 'w') as f:
             f.write('foo')
     def _test_tmp_onefile(self, tmpdir):
         with open(os.path.join(tmpdir, 'foo'), 'w') as f:
             f.write('foo')
-        assertRegex(
-            self,
+        self.assertRegex(
             current_manifest(tmpdir),
             r'^\. acbd18db4cc2f85cedef654fccc4a4d8\+3(\+\S+)? 0:3:foo\n$')
 
             current_manifest(tmpdir),
             r'^\. acbd18db4cc2f85cedef654fccc4a4d8\+3(\+\S+)? 0:3:foo\n$')
 
@@ -137,7 +133,7 @@ class TmpCollectionTest(IntegrationTest):
                 else:
                     with open(path, 'w') as f:
                         f.write(content)
                 else:
                     with open(path, 'w') as f:
                         f.write(content)
-                assertRegex(self, current_manifest(tmpdir), expect)
+                self.assertRegex(current_manifest(tmpdir), expect)
 
     @IntegrationTest.mount(argv=mnt_args)
     def test_tmp_rewrite(self):
 
     @IntegrationTest.mount(argv=mnt_args)
     def test_tmp_rewrite(self):
@@ -150,4 +146,4 @@ class TmpCollectionTest(IntegrationTest):
             f.write("b2")
         with open(os.path.join(tmpdir, "b1"), 'w') as f:
             f.write("1b")
             f.write("b2")
         with open(os.path.join(tmpdir, "b1"), 'w') as f:
             f.write("1b")
-        assertRegex(self, current_manifest(tmpdir), "^\. ed4f3f67c70b02b29c50ce1ea26666bd\+4(\+\S+)? 0:2:b1 2:2:b2\n$")
+        self.assertRegex(current_manifest(tmpdir), "^\. ed4f3f67c70b02b29c50ce1ea26666bd\+4(\+\S+)? 0:2:b1 2:2:b2\n$")
index 040db2e096252f4f538800e8175f98ed48fcd5cb..89076d72cb834e9507b0d1bff57719140fb26128 100644 (file)
@@ -2,12 +2,10 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from builtins import range
 import apiclient
 import arvados
 import arvados_fuse
 import logging
 import apiclient
 import arvados
 import arvados_fuse
 import logging
-import mock
 import multiprocessing
 import os
 import re
 import multiprocessing
 import os
 import re
@@ -15,6 +13,8 @@ import sys
 import time
 import unittest
 
 import time
 import unittest
 
+from unittest import mock
+
 from .integration_test import IntegrationTest
 
 logger = logging.getLogger('arvados.arv-mount')
 from .integration_test import IntegrationTest
 
 logger = logging.getLogger('arvados.arv-mount')
@@ -65,8 +65,8 @@ class TokenExpiryTest(IntegrationTest):
 
     @staticmethod
     def _test_refresh_old_manifest(self, zzz):
 
     @staticmethod
     def _test_refresh_old_manifest(self, zzz):
-        uuid = 'zzzzz-4zz18-op4e2lbej01tcvu'
-        fnm = 'zzzzz-8i9sb-0vsrcqi7whchuil.log.txt'
+        uuid = 'zzzzz-4zz18-logcollection02'
+        fnm = 'crunch-run.txt'
         os.listdir(os.path.join(zzz, uuid))
         for _ in range(8):
             with open(os.path.join(zzz, uuid, fnm)) as f:
         os.listdir(os.path.join(zzz, uuid))
         for _ in range(8):
             with open(os.path.join(zzz, uuid, fnm)) as f:
index e89571087e5eaf885ce47e41e10603fb805d11de..3949fd5de4d85953ec2ce8b6bf200e6bc8d22427 100644 (file)
@@ -2,8 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
-from builtins import bytes
 import arvados_fuse.unmount
 import os
 import subprocess
 import arvados_fuse.unmount
 import os
 import subprocess
@@ -31,11 +29,11 @@ class UnmountTest(IntegrationTest):
              self.mnt])
         subprocess.check_call(
             ['./bin/arv-mount', '--subtype', 'test', '--replace',
              self.mnt])
         subprocess.check_call(
             ['./bin/arv-mount', '--subtype', 'test', '--replace',
-             '--unmount-timeout', '10',
+             '--unmount-timeout', '60',
              self.mnt])
         subprocess.check_call(
             ['./bin/arv-mount', '--subtype', 'test', '--replace',
              self.mnt])
         subprocess.check_call(
             ['./bin/arv-mount', '--subtype', 'test', '--replace',
-             '--unmount-timeout', '10',
+             '--unmount-timeout', '60',
              self.mnt,
              '--exec', 'true'])
         for m in subprocess.check_output(['mount']).splitlines():
              self.mnt,
              '--exec', 'true'])
         for m in subprocess.check_output(['mount']).splitlines():
diff --git a/services/githttpd/auth_handler.go b/services/githttpd/auth_handler.go
deleted file mode 100644 (file)
index c6b23fd..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "errors"
-       "log"
-       "net/http"
-       "os"
-       "regexp"
-       "strings"
-       "time"
-
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/arvadosclient"
-       "git.arvados.org/arvados.git/sdk/go/auth"
-       "git.arvados.org/arvados.git/sdk/go/httpserver"
-       "github.com/sirupsen/logrus"
-)
-
-type authHandler struct {
-       handler    http.Handler
-       clientPool *arvadosclient.ClientPool
-       cluster    *arvados.Cluster
-}
-
-func (h *authHandler) CheckHealth() error {
-       return nil
-}
-
-func (h *authHandler) Done() <-chan struct{} {
-       return nil
-}
-
-func (h *authHandler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
-       var statusCode int
-       var statusText string
-       var apiToken string
-
-       w := httpserver.WrapResponseWriter(wOrig)
-
-       if r.Method == "OPTIONS" {
-               method := r.Header.Get("Access-Control-Request-Method")
-               if method != "GET" && method != "POST" {
-                       w.WriteHeader(http.StatusMethodNotAllowed)
-                       return
-               }
-               w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
-               w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
-               w.Header().Set("Access-Control-Allow-Origin", "*")
-               w.Header().Set("Access-Control-Max-Age", "86400")
-               w.WriteHeader(http.StatusOK)
-               return
-       }
-
-       if r.Header.Get("Origin") != "" {
-               // Allow simple cross-origin requests without user
-               // credentials ("user credentials" as defined by CORS,
-               // i.e., cookies, HTTP authentication, and client-side
-               // SSL certificates. See
-               // http://www.w3.org/TR/cors/#user-credentials).
-               w.Header().Set("Access-Control-Allow-Origin", "*")
-       }
-
-       defer func() {
-               if w.WroteStatus() == 0 {
-                       // Nobody has called WriteHeader yet: that
-                       // must be our job.
-                       w.WriteHeader(statusCode)
-                       if statusCode >= 400 {
-                               w.Write([]byte(statusText))
-                       }
-               }
-       }()
-
-       creds := auth.CredentialsFromRequest(r)
-       if len(creds.Tokens) == 0 {
-               statusCode, statusText = http.StatusUnauthorized, "no credentials provided"
-               w.Header().Add("WWW-Authenticate", "Basic realm=\"git\"")
-               return
-       }
-       apiToken = creds.Tokens[0]
-
-       // Access to paths "/foo/bar.git/*" and "/foo/bar/.git/*" are
-       // protected by the permissions on the repository named
-       // "foo/bar".
-       pathParts := strings.SplitN(r.URL.Path[1:], ".git/", 2)
-       if len(pathParts) != 2 {
-               statusCode, statusText = http.StatusNotFound, "not found"
-               return
-       }
-       repoName := pathParts[0]
-       repoName = strings.TrimRight(repoName, "/")
-       httpserver.SetResponseLogFields(r.Context(), logrus.Fields{
-               "repoName": repoName,
-       })
-
-       arv := h.clientPool.Get()
-       if arv == nil {
-               statusCode, statusText = http.StatusInternalServerError, "connection pool failed: "+h.clientPool.Err().Error()
-               return
-       }
-       defer h.clientPool.Put(arv)
-
-       // Log the UUID if the supplied token is a v2 token, otherwise
-       // just the last five characters.
-       httpserver.SetResponseLogFields(r.Context(), logrus.Fields{
-               "tokenUUID": func() string {
-                       if strings.HasPrefix(apiToken, "v2/") && strings.IndexRune(apiToken[3:], '/') == 27 {
-                               // UUID part of v2 token
-                               return apiToken[3:30]
-                       } else if len(apiToken) > 5 {
-                               return "[...]" + apiToken[len(apiToken)-5:]
-                       } else {
-                               return apiToken
-                       }
-               }(),
-       })
-
-       // Ask API server whether the repository is readable using
-       // this token (by trying to read it!)
-       arv.ApiToken = apiToken
-       repoUUID, err := h.lookupRepo(arv, repoName)
-       if err != nil {
-               statusCode, statusText = http.StatusInternalServerError, err.Error()
-               return
-       }
-       if repoUUID == "" {
-               statusCode, statusText = http.StatusNotFound, "not found"
-               return
-       }
-
-       isWrite := strings.HasSuffix(r.URL.Path, "/git-receive-pack")
-       if !isWrite {
-               statusText = "read"
-       } else {
-               err := arv.Update("repositories", repoUUID, arvadosclient.Dict{
-                       "repository": arvadosclient.Dict{
-                               "modified_at": time.Now().String(),
-                       },
-               }, &arvadosclient.Dict{})
-               if err != nil {
-                       statusCode, statusText = http.StatusForbidden, err.Error()
-                       return
-               }
-               statusText = "write"
-       }
-
-       // Regardless of whether the client asked for "/foo.git" or
-       // "/foo/.git", we choose whichever variant exists in our repo
-       // root, and we try {uuid}.git and {uuid}/.git first. If none
-       // of these exist, we 404 even though the API told us the repo
-       // _should_ exist (presumably this means the repo was just
-       // created, and gitolite sync hasn't run yet).
-       rewrittenPath := ""
-       tryDirs := []string{
-               "/" + repoUUID + ".git",
-               "/" + repoUUID + "/.git",
-               "/" + repoName + ".git",
-               "/" + repoName + "/.git",
-       }
-       for _, dir := range tryDirs {
-               if fileInfo, err := os.Stat(h.cluster.Git.Repositories + dir); err != nil {
-                       if !os.IsNotExist(err) {
-                               statusCode, statusText = http.StatusInternalServerError, err.Error()
-                               return
-                       }
-               } else if fileInfo.IsDir() {
-                       rewrittenPath = dir + "/" + pathParts[1]
-                       break
-               }
-       }
-       if rewrittenPath == "" {
-               log.Println("WARNING:", repoUUID,
-                       "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"
-               return
-       }
-       r.URL.Path = rewrittenPath
-
-       h.handler.ServeHTTP(w, r)
-}
-
-var uuidRegexp = regexp.MustCompile(`^[0-9a-z]{5}-s0uqq-[0-9a-z]{15}$`)
-
-func (h *authHandler) lookupRepo(arv *arvadosclient.ArvadosClient, repoName string) (string, error) {
-       reposFound := arvadosclient.Dict{}
-       var column string
-       if uuidRegexp.MatchString(repoName) {
-               column = "uuid"
-       } else {
-               column = "name"
-       }
-       err := arv.List("repositories", arvadosclient.Dict{
-               "filters": [][]string{{column, "=", repoName}},
-       }, &reposFound)
-       if err != nil {
-               return "", err
-       } else if avail, ok := reposFound["items_available"].(float64); !ok {
-               return "", errors.New("bad list response from API")
-       } else if avail < 1 {
-               return "", nil
-       } else if avail > 1 {
-               return "", errors.New("name collision")
-       }
-       return reposFound["items"].([]interface{})[0].(map[string]interface{})["uuid"].(string), nil
-}
diff --git a/services/githttpd/auth_handler_test.go b/services/githttpd/auth_handler_test.go
deleted file mode 100644 (file)
index 2d1ec96..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "io"
-       "log"
-       "net/http"
-       "net/http/httptest"
-       "net/url"
-       "path/filepath"
-       "strings"
-
-       "git.arvados.org/arvados.git/lib/config"
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/arvadosclient"
-       "git.arvados.org/arvados.git/sdk/go/arvadostest"
-       "git.arvados.org/arvados.git/sdk/go/ctxlog"
-       check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&AuthHandlerSuite{})
-
-type AuthHandlerSuite struct {
-       cluster *arvados.Cluster
-}
-
-func (s *AuthHandlerSuite) SetUpTest(c *check.C) {
-       arvadostest.ResetEnv()
-       repoRoot, err := filepath.Abs("../api/tmp/git/test")
-       c.Assert(err, check.IsNil)
-
-       cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).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{{Host: "localhost:0"}: {}}
-       s.cluster.TLS.Insecure = true
-       s.cluster.Git.GitCommand = "/usr/bin/git"
-       s.cluster.Git.Repositories = repoRoot
-}
-
-func (s *AuthHandlerSuite) TestPermission(c *check.C) {
-       client, err := arvados.NewClientFromConfig(s.cluster)
-       c.Assert(err, check.IsNil)
-       ac, err := arvadosclient.New(client)
-       c.Assert(err, check.IsNil)
-       h := &authHandler{
-               cluster:    s.cluster,
-               clientPool: &arvadosclient.ClientPool{Prototype: ac},
-               handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-                       log.Printf("%v", r.URL)
-                       io.WriteString(w, r.URL.Path)
-               }),
-       }
-       baseURL, err := url.Parse("http://git.example/")
-       c.Assert(err, check.IsNil)
-       for _, trial := range []struct {
-               label   string
-               token   string
-               pathIn  string
-               pathOut string
-               status  int
-       }{
-               {
-                       label:   "read repo by name",
-                       token:   arvadostest.ActiveToken,
-                       pathIn:  arvadostest.Repository2Name + ".git/git-upload-pack",
-                       pathOut: arvadostest.Repository2UUID + ".git/git-upload-pack",
-               },
-               {
-                       label:   "read repo by uuid",
-                       token:   arvadostest.ActiveToken,
-                       pathIn:  arvadostest.Repository2UUID + ".git/git-upload-pack",
-                       pathOut: arvadostest.Repository2UUID + ".git/git-upload-pack",
-               },
-               {
-                       label:   "write repo by name",
-                       token:   arvadostest.ActiveToken,
-                       pathIn:  arvadostest.Repository2Name + ".git/git-receive-pack",
-                       pathOut: arvadostest.Repository2UUID + ".git/git-receive-pack",
-               },
-               {
-                       label:   "write repo by uuid",
-                       token:   arvadostest.ActiveToken,
-                       pathIn:  arvadostest.Repository2UUID + ".git/git-receive-pack",
-                       pathOut: arvadostest.Repository2UUID + ".git/git-receive-pack",
-               },
-               {
-                       label:  "uuid not found",
-                       token:  arvadostest.ActiveToken,
-                       pathIn: strings.Replace(arvadostest.Repository2UUID, "6", "z", -1) + ".git/git-upload-pack",
-                       status: http.StatusNotFound,
-               },
-               {
-                       label:  "name not found",
-                       token:  arvadostest.ActiveToken,
-                       pathIn: "nonexistent-bogus.git/git-upload-pack",
-                       status: http.StatusNotFound,
-               },
-               {
-                       label:   "read read-only repo",
-                       token:   arvadostest.SpectatorToken,
-                       pathIn:  arvadostest.FooRepoName + ".git/git-upload-pack",
-                       pathOut: arvadostest.FooRepoUUID + "/.git/git-upload-pack",
-               },
-               {
-                       label:  "write read-only repo",
-                       token:  arvadostest.SpectatorToken,
-                       pathIn: arvadostest.FooRepoName + ".git/git-receive-pack",
-                       status: http.StatusForbidden,
-               },
-       } {
-               c.Logf("trial label: %q", trial.label)
-               u, err := baseURL.Parse(trial.pathIn)
-               c.Assert(err, check.IsNil)
-               resp := httptest.NewRecorder()
-               req := &http.Request{
-                       Method: "POST",
-                       URL:    u,
-                       Header: http.Header{
-                               "Authorization": {"Bearer " + trial.token}}}
-               h.ServeHTTP(resp, req)
-               if trial.status == 0 {
-                       trial.status = http.StatusOK
-               }
-               c.Check(resp.Code, check.Equals, trial.status)
-               if trial.status < 400 {
-                       if trial.pathOut != "" && !strings.HasPrefix(trial.pathOut, "/") {
-                               trial.pathOut = "/" + trial.pathOut
-                       }
-                       c.Check(resp.Body.String(), check.Equals, trial.pathOut)
-               }
-       }
-}
-
-func (s *AuthHandlerSuite) TestCORS(c *check.C) {
-       h := &authHandler{cluster: s.cluster}
-
-       // CORS preflight
-       resp := httptest.NewRecorder()
-       req := &http.Request{
-               Method: "OPTIONS",
-               Header: http.Header{
-                       "Origin":                        {"*"},
-                       "Access-Control-Request-Method": {"GET"},
-               },
-       }
-       h.ServeHTTP(resp, req)
-       c.Check(resp.Code, check.Equals, http.StatusOK)
-       c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "GET, POST")
-       c.Check(resp.Header().Get("Access-Control-Allow-Headers"), check.Equals, "Authorization, Content-Type")
-       c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
-       c.Check(resp.Body.String(), check.Equals, "")
-
-       // CORS actual request. Bogus token and path ensure
-       // authHandler responds 4xx without calling our wrapped (nil)
-       // handler.
-       u, err := url.Parse("git.zzzzz.arvadosapi.com/test")
-       c.Assert(err, check.Equals, nil)
-       resp = httptest.NewRecorder()
-       req = &http.Request{
-               Method: "GET",
-               URL:    u,
-               Header: http.Header{
-                       "Origin":        {"*"},
-                       "Authorization": {"OAuth2 foobar"},
-               },
-       }
-       h.ServeHTTP(resp, req)
-       c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
-}
diff --git a/services/githttpd/cmd.go b/services/githttpd/cmd.go
deleted file mode 100644 (file)
index e6ca3c0..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "context"
-
-       "git.arvados.org/arvados.git/lib/service"
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/arvadosclient"
-       "github.com/prometheus/client_golang/prometheus"
-)
-
-var Command = service.Command(arvados.ServiceNameGitHTTP, newHandler)
-
-func newHandler(ctx context.Context, cluster *arvados.Cluster, token string, reg *prometheus.Registry) service.Handler {
-       client, err := arvados.NewClientFromConfig(cluster)
-       if err != nil {
-               return service.ErrorHandler(ctx, cluster, err)
-       }
-       ac, err := arvadosclient.New(client)
-       if err != nil {
-               return service.ErrorHandler(ctx, cluster, err)
-       }
-       return &authHandler{
-               clientPool: &arvadosclient.ClientPool{Prototype: ac},
-               cluster:    cluster,
-               handler:    newGitHandler(ctx, cluster),
-       }
-}
diff --git a/services/githttpd/git_handler.go b/services/githttpd/git_handler.go
deleted file mode 100644 (file)
index 7c94294..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "context"
-       "net"
-       "net/http"
-       "net/http/cgi"
-       "os"
-
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/ctxlog"
-)
-
-// gitHandler is an http.Handler that invokes git-http-backend (or
-// whatever backend is configured) via CGI, with appropriate
-// environment variables in place for git-http-backend or
-// gitolite-shell.
-type gitHandler struct {
-       cgi.Handler
-}
-
-func newGitHandler(ctx context.Context, cluster *arvados.Cluster) http.Handler {
-       const glBypass = "GL_BYPASS_ACCESS_CHECKS"
-       const glHome = "GITOLITE_HTTP_HOME"
-       var env []string
-       path := os.Getenv("PATH")
-       if cluster.Git.GitoliteHome != "" {
-               env = append(env,
-                       glHome+"="+cluster.Git.GitoliteHome,
-                       glBypass+"=1")
-               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)
-               ctxlog.FromContext(ctx).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="+cluster.Git.Repositories,
-               "GIT_HTTP_EXPORT_ALL=",
-               "SERVER_ADDR="+listen.Host,
-               "PATH="+path)
-       return &gitHandler{
-               Handler: cgi.Handler{
-                       Path: cluster.Git.GitCommand,
-                       Dir:  cluster.Git.Repositories,
-                       Env:  env,
-                       Args: []string{"http-backend"},
-               },
-       }
-}
-
-func (h *gitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-       remoteHost, remotePort, err := net.SplitHostPort(r.RemoteAddr)
-       if err != nil {
-               ctxlog.FromContext(r.Context()).Errorf("Internal error: SplitHostPort(r.RemoteAddr==%q): %s", r.RemoteAddr, err)
-               w.WriteHeader(http.StatusInternalServerError)
-               return
-       }
-
-       // Copy the wrapped cgi.Handler, so these request-specific
-       // variables don't leak into the next request.
-       handlerCopy := h.Handler
-       handlerCopy.Env = append(handlerCopy.Env,
-               // In Go1.5 we can skip this, net/http/cgi will do it for us:
-               "REMOTE_HOST="+remoteHost,
-               "REMOTE_ADDR="+remoteHost,
-               "REMOTE_PORT="+remotePort,
-               // Ideally this would be a real username:
-               "REMOTE_USER="+r.RemoteAddr,
-       )
-       handlerCopy.ServeHTTP(w, r)
-}
diff --git a/services/githttpd/git_handler_test.go b/services/githttpd/git_handler_test.go
deleted file mode 100644 (file)
index ef2ee28..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "context"
-       "net/http"
-       "net/http/httptest"
-       "net/url"
-       "regexp"
-
-       "git.arvados.org/arvados.git/lib/config"
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/ctxlog"
-       check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&GitHandlerSuite{})
-
-type GitHandlerSuite struct {
-       cluster *arvados.Cluster
-}
-
-func (s *GitHandlerSuite) SetUpTest(c *check.C) {
-       cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).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{{Host: "localhost:80"}: {}}
-       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()
-       req := &http.Request{
-               Method:     "GET",
-               URL:        u,
-               RemoteAddr: "[::1]:12345",
-       }
-       h := newGitHandler(context.Background(), s.cluster)
-       h.(*gitHandler).Path = "/bin/sh"
-       h.(*gitHandler).Args = []string{"-c", "printf 'Content-Type: text/plain\r\n\r\n'; env"}
-
-       h.ServeHTTP(resp, req)
-
-       c.Check(resp.Code, check.Equals, http.StatusOK)
-       body := resp.Body.String()
-       c.Check(body, check.Matches, `(?ms).*^PATH=.*:/test/ghh/bin$.*`)
-       c.Check(body, check.Matches, `(?ms).*^GITOLITE_HTTP_HOME=/test/ghh$.*`)
-       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("localhost:80")+`$.*`)
-}
-
-func (s *GitHandlerSuite) TestCGIErrorOnSplitHostPortError(c *check.C) {
-       u, err := url.Parse("git.zzzzz.arvadosapi.com/test")
-       c.Check(err, check.Equals, nil)
-       resp := httptest.NewRecorder()
-       req := &http.Request{
-               Method:     "GET",
-               URL:        u,
-               RemoteAddr: "test.bad.address.missing.port",
-       }
-       h := newGitHandler(context.Background(), s.cluster)
-       h.ServeHTTP(resp, req)
-       c.Check(resp.Code, check.Equals, http.StatusInternalServerError)
-       c.Check(resp.Body.String(), check.Equals, "")
-}
diff --git a/services/githttpd/gitolite_test.go b/services/githttpd/gitolite_test.go
deleted file mode 100644 (file)
index d34c413..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "io/ioutil"
-       "os"
-       "os/exec"
-       "strings"
-
-       "git.arvados.org/arvados.git/lib/config"
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/ctxlog"
-       check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&GitoliteSuite{})
-
-// GitoliteSuite tests need an API server, an arvados-git-httpd
-// server, and a repository hosted by gitolite.
-type GitoliteSuite struct {
-       IntegrationSuite
-       gitoliteHome string
-}
-
-func (s *GitoliteSuite) SetUpTest(c *check.C) {
-       var err error
-       s.gitoliteHome, err = ioutil.TempDir("", "githttp")
-       c.Assert(err, check.Equals, nil)
-
-       runGitolite := func(prog string, args ...string) {
-               c.Log(prog, " ", args)
-               cmd := exec.Command(prog, args...)
-               cmd.Dir = s.gitoliteHome
-               cmd.Env = []string{"HOME=" + s.gitoliteHome}
-               for _, e := range os.Environ() {
-                       if !strings.HasPrefix(e, "HOME=") {
-                               cmd.Env = append(cmd.Env, e)
-                       }
-               }
-               diags, err := cmd.CombinedOutput()
-               c.Log(string(diags))
-               c.Assert(err, check.Equals, nil)
-       }
-
-       runGitolite("gitolite", "setup", "--admin", "root")
-
-       s.tmpRepoRoot = s.gitoliteHome + "/repositories"
-
-       cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).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{{Host: "localhost:0"}: {}}
-       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
-       // (*IntegrationTest)SetUpTest() -- see 2.2.4 at
-       // http://gitolite.com/gitolite/gitolite.html
-       runGitolite("gitolite", "setup")
-}
-
-func (s *GitoliteSuite) TearDownTest(c *check.C) {
-       // We really want Unsetenv here, but it's not worth forcing an
-       // upgrade to Go 1.4.
-       os.Setenv("GITOLITE_HTTP_HOME", "")
-       os.Setenv("GL_BYPASS_ACCESS_CHECKS", "")
-       if s.gitoliteHome != "" {
-               err := os.RemoveAll(s.gitoliteHome)
-               c.Check(err, check.Equals, nil)
-       }
-       s.IntegrationSuite.TearDownTest(c)
-}
-
-func (s *GitoliteSuite) TestFetch(c *check.C) {
-       err := s.RunGit(c, activeToken, "fetch", "active/foo.git", "refs/heads/main")
-       c.Check(err, check.Equals, nil)
-}
-
-func (s *GitoliteSuite) TestFetchUnreadable(c *check.C) {
-       err := s.RunGit(c, anonymousToken, "fetch", "active/foo.git")
-       c.Check(err, check.ErrorMatches, `.* not found.*`)
-}
-
-func (s *GitoliteSuite) TestPush(c *check.C) {
-       err := s.RunGit(c, activeToken, "push", "active/foo.git", "main:gitolite-push")
-       c.Check(err, check.Equals, nil)
-
-       // Check that the commit hash appears in the gitolite log, as
-       // assurance that the gitolite hooks really did run.
-
-       sha1, err := exec.Command("git", "--git-dir", s.tmpWorkdir+"/.git",
-               "log", "-n1", "--format=%H").CombinedOutput()
-       c.Logf("git-log in workdir: %q", string(sha1))
-       c.Assert(err, check.Equals, nil)
-       c.Assert(len(sha1), check.Equals, 41)
-
-       gitoliteLog, err := exec.Command("grep", "-r", string(sha1[:40]), s.gitoliteHome+"/.gitolite/logs").CombinedOutput()
-       c.Check(err, check.Equals, nil)
-       c.Logf("gitolite log message: %q", string(gitoliteLog))
-}
-
-func (s *GitoliteSuite) TestPushUnwritable(c *check.C) {
-       err := s.RunGit(c, spectatorToken, "push", "active/foo.git", "main:gitolite-push-fail")
-       c.Check(err, check.ErrorMatches, `.*HTTP (code = )?403.*`)
-}
diff --git a/services/githttpd/integration_test.go b/services/githttpd/integration_test.go
deleted file mode 100644 (file)
index c819272..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "context"
-       "errors"
-       "io/ioutil"
-       "os"
-       "os/exec"
-       "strings"
-       "testing"
-
-       "git.arvados.org/arvados.git/lib/config"
-       "git.arvados.org/arvados.git/sdk/go/arvados"
-       "git.arvados.org/arvados.git/sdk/go/arvadostest"
-       "git.arvados.org/arvados.git/sdk/go/ctxlog"
-       "git.arvados.org/arvados.git/sdk/go/httpserver"
-       check "gopkg.in/check.v1"
-)
-
-// Gocheck boilerplate
-func Test(t *testing.T) {
-       check.TestingT(t)
-}
-
-// IntegrationSuite tests need an API server and an arvados-git-httpd
-// server. See GitSuite and GitoliteSuite.
-type IntegrationSuite struct {
-       tmpRepoRoot string
-       tmpWorkdir  string
-       testServer  *httpserver.Server
-       cluster     *arvados.Cluster
-}
-
-func (s *IntegrationSuite) SetUpTest(c *check.C) {
-       arvadostest.ResetEnv()
-
-       var err error
-       if s.tmpRepoRoot == "" {
-               s.tmpRepoRoot, err = ioutil.TempDir("", "githttp")
-               c.Assert(err, check.Equals, nil)
-       }
-       s.tmpWorkdir, err = ioutil.TempDir("", "githttp")
-       c.Assert(err, check.Equals, nil)
-       _, err = exec.Command("git", "init", "--bare", s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666.git").Output()
-       c.Assert(err, check.Equals, nil)
-       // we need git 2.28 to specify the initial branch with -b; Buster only has 2.20; so we do it in 2 steps
-       _, err = exec.Command("git", "init", s.tmpWorkdir).Output()
-       c.Assert(err, check.Equals, nil)
-       _, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && git checkout -b main").Output()
-       c.Assert(err, check.Equals, nil)
-       _, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && echo initial >initial && git add initial && git -c user.name=Initial -c user.email=Initial commit -am 'foo: initial commit'").CombinedOutput()
-       c.Assert(err, check.Equals, nil)
-       _, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && git push "+s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666.git main:main").CombinedOutput()
-       c.Assert(err, check.Equals, nil)
-       _, 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, ctxlog.TestLogger(c)).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{{Host: "localhost:0"}: {}}
-               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 = &httpserver.Server{}
-       s.testServer.Handler = httpserver.LogRequests(newHandler(context.Background(), s.cluster, "", nil))
-       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",
-               "!cred(){ cat >/dev/null; if [ \"$1\" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred").Output()
-       c.Assert(err, check.Equals, nil)
-       _, err = exec.Command("git", "config",
-               "--file", s.tmpWorkdir+"/.git/config",
-               "credential.http://"+s.testServer.Addr+"/.username",
-               "none").Output()
-       c.Assert(err, check.Equals, nil)
-
-       // Clear ARVADOS_API_* env vars before starting up the server,
-       // to make sure arvados-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")
-}
-
-func (s *IntegrationSuite) TearDownTest(c *check.C) {
-       var err error
-       if s.testServer != nil {
-               err = s.testServer.Close()
-       }
-       c.Check(err, check.Equals, nil)
-       s.testServer = nil
-
-       if s.tmpRepoRoot != "" {
-               err = os.RemoveAll(s.tmpRepoRoot)
-               c.Check(err, check.Equals, nil)
-       }
-       s.tmpRepoRoot = ""
-
-       if s.tmpWorkdir != "" {
-               err = os.RemoveAll(s.tmpWorkdir)
-               c.Check(err, check.Equals, nil)
-       }
-       s.tmpWorkdir = ""
-
-       s.cluster = nil
-}
-
-func (s *IntegrationSuite) RunGit(c *check.C, token, gitCmd, repo string, args ...string) error {
-       cwd, err := os.Getwd()
-       c.Assert(err, check.Equals, nil)
-       defer os.Chdir(cwd)
-       os.Chdir(s.tmpWorkdir)
-
-       gitargs := append([]string{
-               gitCmd, "http://" + s.testServer.Addr + "/" + repo,
-       }, args...)
-       cmd := exec.Command("git", gitargs...)
-       cmd.Env = append(os.Environ(), "ARVADOS_API_TOKEN="+token)
-       w, err := cmd.StdinPipe()
-       c.Assert(err, check.Equals, nil)
-       w.Close()
-       output, err := cmd.CombinedOutput()
-       c.Log("git ", gitargs, " => ", err)
-       c.Log(string(output))
-       if err != nil && len(output) > 0 {
-               // If messages appeared on stderr, they are more
-               // helpful than the err returned by CombinedOutput().
-               //
-               // Easier to match error strings without newlines:
-               err = errors.New(strings.Replace(string(output), "\n", " // ", -1))
-       }
-       return err
-}
diff --git a/services/githttpd/server_test.go b/services/githttpd/server_test.go
deleted file mode 100644 (file)
index 02c13a3..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-package githttpd
-
-import (
-       "os"
-       "os/exec"
-
-       check "gopkg.in/check.v1"
-)
-
-var _ = check.Suite(&GitSuite{})
-
-const (
-       spectatorToken = "zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu"
-       activeToken    = "3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi"
-       anonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
-       expiredToken   = "2ym314ysp27sk7h943q6vtc378srb06se3pq6ghurylyf3pdmx"
-)
-
-type GitSuite struct {
-       IntegrationSuite
-}
-
-func (s *GitSuite) TestPathVariants(c *check.C) {
-       s.makeArvadosRepo(c)
-       for _, repo := range []string{"active/foo.git", "active/foo/.git", "arvados.git", "arvados/.git"} {
-               err := s.RunGit(c, spectatorToken, "fetch", repo, "refs/heads/main")
-               c.Assert(err, check.Equals, nil)
-       }
-}
-
-func (s *GitSuite) TestReadonly(c *check.C) {
-       err := s.RunGit(c, spectatorToken, "fetch", "active/foo.git", "refs/heads/main")
-       c.Assert(err, check.Equals, nil)
-       err = s.RunGit(c, spectatorToken, "push", "active/foo.git", "main:newbranchfail")
-       c.Assert(err, check.ErrorMatches, `.*HTTP (code = )?403.*`)
-       _, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666.git/refs/heads/newbranchfail")
-       c.Assert(err, check.FitsTypeOf, &os.PathError{})
-}
-
-func (s *GitSuite) TestReadwrite(c *check.C) {
-       err := s.RunGit(c, activeToken, "fetch", "active/foo.git", "refs/heads/main")
-       c.Assert(err, check.Equals, nil)
-       err = s.RunGit(c, activeToken, "push", "active/foo.git", "main:newbranch")
-       c.Assert(err, check.Equals, nil)
-       _, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666.git/refs/heads/newbranch")
-       c.Assert(err, check.Equals, nil)
-}
-
-func (s *GitSuite) TestNonexistent(c *check.C) {
-       err := s.RunGit(c, spectatorToken, "fetch", "thisrepodoesnotexist.git", "refs/heads/main")
-       c.Assert(err, check.ErrorMatches, `.* not found.*`)
-}
-
-func (s *GitSuite) TestMissingGitdirReadableRepository(c *check.C) {
-       err := s.RunGit(c, activeToken, "fetch", "active/foo2.git", "refs/heads/main")
-       c.Assert(err, check.ErrorMatches, `.* not found.*`)
-}
-
-func (s *GitSuite) TestNoPermission(c *check.C) {
-       for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-               err := s.RunGit(c, anonymousToken, "fetch", repo, "refs/heads/main")
-               c.Assert(err, check.ErrorMatches, `.* not found.*`)
-       }
-}
-
-func (s *GitSuite) TestExpiredToken(c *check.C) {
-       for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-               err := s.RunGit(c, expiredToken, "fetch", repo, "refs/heads/main")
-               c.Assert(err, check.ErrorMatches, `.* (500 while accessing|requested URL returned error: 500).*`)
-       }
-}
-
-func (s *GitSuite) TestInvalidToken(c *check.C) {
-       for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-               err := s.RunGit(c, "s3cr3tp@ssw0rd", "fetch", repo, "refs/heads/main")
-               c.Assert(err, check.ErrorMatches, `.* requested URL returned error.*`)
-       }
-}
-
-func (s *GitSuite) TestShortToken(c *check.C) {
-       for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
-               err := s.RunGit(c, "s3cr3t", "fetch", repo, "refs/heads/main")
-               c.Assert(err, check.ErrorMatches, `.* (500 while accessing|requested URL returned error: 500).*`)
-       }
-}
-
-func (s *GitSuite) TestShortTokenBadReq(c *check.C) {
-       for _, repo := range []string{"bogus"} {
-               err := s.RunGit(c, "s3cr3t", "fetch", repo, "refs/heads/main")
-               c.Assert(err, check.ErrorMatches, `.*not found.*`)
-       }
-}
-
-// Make a bare arvados repo at {tmpRepoRoot}/arvados.git
-func (s *GitSuite) makeArvadosRepo(c *check.C) {
-       msg, err := exec.Command("git", "init", "--bare", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git").CombinedOutput()
-       c.Log(string(msg))
-       c.Assert(err, check.Equals, nil)
-       msg, err = exec.Command("git", "--git-dir", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git", "fetch", "../../.git", "HEAD:main").CombinedOutput()
-       c.Log(string(msg))
-       c.Assert(err, check.Equals, nil)
-}
index e0da14e774525d9b860e6c92c62a010653e25d06..b9250efec76b8b45f599c30dc8961cbc3e279474 100644 (file)
@@ -178,7 +178,12 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                r.URL.Scheme = xfp
        }
 
                r.URL.Scheme = xfp
        }
 
-       w := httpserver.WrapResponseWriter(wOrig)
+       wbuffer := newWriteBuffer(wOrig, int(h.Cluster.Collections.WebDAVOutputBuffer))
+       defer wbuffer.Close()
+       w := httpserver.WrapResponseWriter(responseWriter{
+               Writer:         wbuffer,
+               ResponseWriter: wOrig,
+       })
 
        if r.Method == "OPTIONS" && ServeCORSPreflight(w, r.Header) {
                return
 
        if r.Method == "OPTIONS" && ServeCORSPreflight(w, r.Header) {
                return
@@ -425,11 +430,26 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                        return
                }
                defer f.Close()
                        return
                }
                defer f.Close()
-               defer sess.Release()
 
                collectionDir, sessionFS, session, tokenUser = f, fs, sess, user
                break
        }
 
                collectionDir, sessionFS, session, tokenUser = f, fs, sess, user
                break
        }
+
+       // releaseSession() is equivalent to session.Release() except
+       // that it's a no-op if (1) session is nil, or (2) it has
+       // already been called.
+       //
+       // This way, we can do a defer call here to ensure it gets
+       // called in all code paths, and also call it inline (see
+       // below) in the cases where we want to release the lock
+       // before returning.
+       releaseSession := func() {}
+       if session != nil {
+               var releaseSessionOnce sync.Once
+               releaseSession = func() { releaseSessionOnce.Do(func() { session.Release() }) }
+       }
+       defer releaseSession()
+
        if forceReload && collectionDir != nil {
                err := collectionDir.Sync()
                if err != nil {
        if forceReload && collectionDir != nil {
                err := collectionDir.Sync()
                if err != nil {
@@ -517,6 +537,7 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodGet || r.Method == http.MethodHead {
                targetfnm := fsprefix + strings.Join(pathParts[stripParts:], "/")
                if fi, err := sessionFS.Stat(targetfnm); err == nil && fi.IsDir() {
        if r.Method == http.MethodGet || r.Method == http.MethodHead {
                targetfnm := fsprefix + strings.Join(pathParts[stripParts:], "/")
                if fi, err := sessionFS.Stat(targetfnm); err == nil && fi.IsDir() {
+                       releaseSession() // because we won't be writing anything
                        if !strings.HasSuffix(r.URL.Path, "/") {
                                h.seeOtherWithCookie(w, r, r.URL.Path+"/", credentialsOK)
                        } else {
                        if !strings.HasSuffix(r.URL.Path, "/") {
                                h.seeOtherWithCookie(w, r, r.URL.Path+"/", credentialsOK)
                        } else {
@@ -586,6 +607,15 @@ func (h *handler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
                                collectionDir.Splice(snap)
                                return nil
                        }}
                                collectionDir.Splice(snap)
                                return nil
                        }}
+       } else {
+               // When writing, we need to block session renewal
+               // until we're finished, in order to guarantee the
+               // effect of the write is visible in future responses.
+               // But if we're not writing, we can release the lock
+               // early.  This enables us to keep renewing sessions
+               // and processing more requests even if a slow client
+               // takes a long time to download a large file.
+               releaseSession()
        }
        if r.Method == http.MethodGet {
                applyContentDispositionHdr(w, r, basename, attachment)
        }
        if r.Method == http.MethodGet {
                applyContentDispositionHdr(w, r, basename, attachment)
index f79df2021213310f72ec3e2da37eada86eaab283..0308f949f4cbd0c4d3b47e6ab6e599100a0f03aa 100644 (file)
@@ -518,7 +518,7 @@ func (s *IntegrationSuite) TestMetrics(c *check.C) {
        allmetrics, err := ioutil.ReadAll(resp.Body)
        c.Check(err, check.IsNil)
 
        allmetrics, err := ioutil.ReadAll(resp.Body)
        c.Check(err, check.IsNil)
 
-       c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_download_apparent_backend_speed_bucket{size_range="0",le="1e\+06"} 4\n.*`)
+       c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_download_apparent_backend_speed_bucket{size_range="0",le="\+Inf"} 4\n.*`)
        c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_download_speed_bucket{size_range="0",le="\+Inf"} 4\n.*`)
        c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_upload_speed_bucket{size_range="0",le="\+Inf"} 2\n.*`)
        c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_upload_sync_delay_seconds_bucket{size_range="0",le="10"} 2\n.*`)
        c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_download_speed_bucket{size_range="0",le="\+Inf"} 4\n.*`)
        c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_upload_speed_bucket{size_range="0",le="\+Inf"} 2\n.*`)
        c.Check(string(allmetrics), check.Matches, `(?ms).*\narvados_keepweb_upload_sync_delay_seconds_bucket{size_range="0",le="10"} 2\n.*`)
diff --git a/services/keep-web/writebuffer.go b/services/keep-web/writebuffer.go
new file mode 100644 (file)
index 0000000..90bdcb4
--- /dev/null
@@ -0,0 +1,161 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package keepweb
+
+import (
+       "errors"
+       "io"
+       "net/http"
+       "sync/atomic"
+)
+
+// writeBuffer uses a ring buffer to implement an asynchronous write
+// buffer.
+//
+// rpos==wpos means the buffer is empty.
+//
+// rpos==(wpos+1)%size means the buffer is full.
+//
+// size<2 means the buffer is always empty and full, so in this case
+// writeBuffer writes through synchronously.
+type writeBuffer struct {
+       out       io.Writer
+       buf       []byte
+       writesize int           // max bytes flush() should write in a single out.Write()
+       wpos      atomic.Int64  // index in buf where writer (Write()) will write to next
+       wsignal   chan struct{} // receives a value after wpos or closed changes
+       rpos      atomic.Int64  // index in buf where reader (flush()) will read from next
+       rsignal   chan struct{} // receives a value after rpos or err changes
+       err       error         // error encountered by flush
+       closed    atomic.Bool
+       flushed   chan struct{} // closes when flush() is finished
+}
+
+func newWriteBuffer(w io.Writer, size int) *writeBuffer {
+       wb := &writeBuffer{
+               out:       w,
+               buf:       make([]byte, size),
+               writesize: (size + 63) / 64,
+               wsignal:   make(chan struct{}, 1),
+               rsignal:   make(chan struct{}, 1),
+               flushed:   make(chan struct{}),
+       }
+       go wb.flush()
+       return wb
+}
+
+func (wb *writeBuffer) Close() error {
+       if wb.closed.Load() {
+               return errors.New("writeBuffer: already closed")
+       }
+       wb.closed.Store(true)
+       // wake up flush()
+       select {
+       case wb.wsignal <- struct{}{}:
+       default:
+       }
+       // wait for flush() to finish
+       <-wb.flushed
+       return wb.err
+}
+
+func (wb *writeBuffer) Write(p []byte) (int, error) {
+       if len(wb.buf) < 2 {
+               // Our buffer logic doesn't work with size<2, and such
+               // a tiny buffer has no purpose anyway, so just write
+               // through unbuffered.
+               return wb.out.Write(p)
+       }
+       todo := p
+       wpos := int(wb.wpos.Load())
+       rpos := int(wb.rpos.Load())
+       for len(todo) > 0 {
+               // wait until the buffer is not full.
+               for rpos == (wpos+1)%len(wb.buf) {
+                       select {
+                       case <-wb.flushed:
+                               if wb.err == nil {
+                                       return 0, errors.New("Write called on closed writeBuffer")
+                               }
+                               return 0, wb.err
+                       case <-wb.rsignal:
+                               rpos = int(wb.rpos.Load())
+                       }
+               }
+               // determine next contiguous portion of buffer that is
+               // available.
+               var avail []byte
+               if rpos == 0 {
+                       avail = wb.buf[wpos : len(wb.buf)-1]
+               } else if wpos >= rpos {
+                       avail = wb.buf[wpos:]
+               } else {
+                       avail = wb.buf[wpos : rpos-1]
+               }
+               n := copy(avail, todo)
+               wpos = (wpos + n) % len(wb.buf)
+               wb.wpos.Store(int64(wpos))
+               // wake up flush()
+               select {
+               case wb.wsignal <- struct{}{}:
+               default:
+               }
+               todo = todo[n:]
+       }
+       return len(p), nil
+}
+
+func (wb *writeBuffer) flush() {
+       defer close(wb.flushed)
+       rpos := 0
+       wpos := 0
+       closed := false
+       for {
+               // wait until buffer is not empty.
+               for rpos == wpos {
+                       if closed {
+                               return
+                       }
+                       <-wb.wsignal
+                       closed = wb.closed.Load()
+                       wpos = int(wb.wpos.Load())
+               }
+               // determine next contiguous portion of buffer that is
+               // ready to write through.
+               var ready []byte
+               if rpos < wpos {
+                       ready = wb.buf[rpos:wpos]
+               } else {
+                       ready = wb.buf[rpos:]
+               }
+               if len(ready) > wb.writesize {
+                       ready = ready[:wb.writesize]
+               }
+               _, wb.err = wb.out.Write(ready)
+               if wb.err != nil {
+                       return
+               }
+               rpos = (rpos + len(ready)) % len(wb.buf)
+               wb.rpos.Store(int64(rpos))
+               select {
+               case wb.rsignal <- struct{}{}:
+               default:
+               }
+       }
+}
+
+// responseWriter enables inserting an io.Writer-wrapper (like
+// *writeBuffer) into an http.ResponseWriter stack.
+//
+// It passes Write() calls to an io.Writer, and all other calls to an
+// http.ResponseWriter.
+type responseWriter struct {
+       io.Writer
+       http.ResponseWriter
+}
+
+func (rwc responseWriter) Write(p []byte) (int, error) {
+       return rwc.Writer.Write(p)
+}
diff --git a/services/keep-web/writebuffer_test.go b/services/keep-web/writebuffer_test.go
new file mode 100644 (file)
index 0000000..589dc24
--- /dev/null
@@ -0,0 +1,98 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package keepweb
+
+import (
+       "bytes"
+       "io"
+       "math/rand"
+       "time"
+
+       . "gopkg.in/check.v1"
+)
+
+var _ = Suite(&writeBufferSuite{})
+
+type writeBufferSuite struct {
+}
+
+// 1000 / 96.3 ns/op = 10.384 GB/s
+func (s *writeBufferSuite) Benchmark_1KBWrites(c *C) {
+       wb := newWriteBuffer(io.Discard, 1<<20)
+       in := make([]byte, 1000)
+       for i := 0; i < c.N; i++ {
+               wb.Write(in)
+       }
+       wb.Close()
+}
+
+func (s *writeBufferSuite) TestRandomizedSpeedsAndSizes(c *C) {
+       for i := 0; i < 20; i++ {
+               insize := rand.Intn(1 << 26)
+               bufsize := rand.Intn(1 << 26)
+               if i < 2 {
+                       // make sure to test edge cases
+                       bufsize = i
+               } else if insize/bufsize > 1000 {
+                       // don't waste too much time testing tiny
+                       // buffer / huge content
+                       insize = bufsize*1000 + 123
+               }
+               c.Logf("%s: insize %d bufsize %d", c.TestName(), insize, bufsize)
+
+               in := make([]byte, insize)
+               b := byte(0)
+               for i := range in {
+                       in[i] = b
+                       b++
+               }
+
+               out := &bytes.Buffer{}
+               done := make(chan struct{})
+               pr, pw := io.Pipe()
+               go func() {
+                       n, err := slowCopy(out, pr, rand.Intn(8192)+1)
+                       c.Check(err, IsNil)
+                       c.Check(n, Equals, int64(insize))
+                       close(done)
+               }()
+               wb := newWriteBuffer(pw, bufsize)
+               n, err := slowCopy(wb, bytes.NewBuffer(in), rand.Intn(8192)+1)
+               c.Check(err, IsNil)
+               c.Check(n, Equals, int64(insize))
+               c.Check(wb.Close(), IsNil)
+               c.Check(pw.Close(), IsNil)
+               <-done
+               c.Check(out.Len(), Equals, insize)
+               for i := 0; i < out.Len() && i < len(in); i++ {
+                       if out.Bytes()[i] != in[i] {
+                               c.Errorf("content mismatch at byte %d", i)
+                               break
+                       }
+               }
+       }
+}
+
+func slowCopy(dst io.Writer, src io.Reader, bufsize int) (int64, error) {
+       wrote := int64(0)
+       buf := make([]byte, bufsize)
+       for {
+               time.Sleep(time.Duration(rand.Intn(100) + 1))
+               n, err := src.Read(buf)
+               if n > 0 {
+                       n, err := dst.Write(buf[:n])
+                       wrote += int64(n)
+                       if err != nil {
+                               return wrote, err
+                       }
+               }
+               if err == io.EOF {
+                       return wrote, nil
+               }
+               if err != nil {
+                       return wrote, err
+               }
+       }
+}
index 39ffd45cbe37b69f663dc6093acd4dfe221c74a1..97a5ad65929094897e4b8fbf9b9c12b3de1633e2 100644 (file)
@@ -23,6 +23,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/health"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
        "git.arvados.org/arvados.git/sdk/go/keepclient"
        "git.arvados.org/arvados.git/sdk/go/health"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
        "git.arvados.org/arvados.git/sdk/go/keepclient"
+       "git.arvados.org/arvados.git/services/keepstore"
        "github.com/gorilla/mux"
        lru "github.com/hashicorp/golang-lru"
        "github.com/prometheus/client_golang/prometheus"
        "github.com/gorilla/mux"
        lru "github.com/hashicorp/golang-lru"
        "github.com/prometheus/client_golang/prometheus"
@@ -271,10 +272,9 @@ func (h *proxyHandler) checkLoop(resp http.ResponseWriter, req *http.Request) er
 }
 
 func setCORSHeaders(resp http.ResponseWriter) {
 }
 
 func setCORSHeaders(resp http.ResponseWriter) {
-       resp.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, OPTIONS")
-       resp.Header().Set("Access-Control-Allow-Origin", "*")
-       resp.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, X-Keep-Desired-Replicas")
-       resp.Header().Set("Access-Control-Max-Age", "86486400")
+       keepstore.SetCORSHeaders(resp)
+       acam := "Access-Control-Allow-Methods"
+       resp.Header().Set(acam, resp.Header().Get(acam)+", POST")
 }
 
 type invalidPathHandler struct{}
 }
 
 type invalidPathHandler struct{}
@@ -419,9 +419,9 @@ func (h *proxyHandler) Put(resp http.ResponseWriter, req *http.Request) {
        locatorIn := mux.Vars(req)["locator"]
 
        // Check if the client specified storage classes
        locatorIn := mux.Vars(req)["locator"]
 
        // Check if the client specified storage classes
-       if req.Header.Get("X-Keep-Storage-Classes") != "" {
+       if req.Header.Get(keepclient.XKeepStorageClasses) != "" {
                var scl []string
                var scl []string
-               for _, sc := range strings.Split(req.Header.Get("X-Keep-Storage-Classes"), ",") {
+               for _, sc := range strings.Split(req.Header.Get(keepclient.XKeepStorageClasses), ",") {
                        scl = append(scl, strings.Trim(sc, " "))
                }
                kc.SetStorageClasses(scl)
                        scl = append(scl, strings.Trim(sc, " "))
                }
                kc.SetStorageClasses(scl)
index 2c73e2d1040d1b37df4e77375b5a859d3187565e..ea8c9ba6ca4cfb6c1ec0b6e5f6734bc0aee7bef4 100644 (file)
@@ -558,14 +558,14 @@ func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
                body, err := ioutil.ReadAll(resp.Body)
                c.Check(err, IsNil)
                c.Check(string(body), Equals, "")
                body, err := ioutil.ReadAll(resp.Body)
                c.Check(err, IsNil)
                c.Check(string(body), Equals, "")
-               c.Check(resp.Header.Get("Access-Control-Allow-Methods"), Equals, "GET, HEAD, POST, PUT, OPTIONS")
+               c.Check(resp.Header.Get("Access-Control-Allow-Methods"), Equals, "GET, HEAD, PUT, OPTIONS, POST")
                c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
        }
 
        {
                resp, err := http.Get(fmt.Sprintf("http://%s/%x+3", srv.Addr, md5.Sum([]byte("foo"))))
                c.Check(err, Equals, nil)
                c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
        }
 
        {
                resp, err := http.Get(fmt.Sprintf("http://%s/%x+3", srv.Addr, md5.Sum([]byte("foo"))))
                c.Check(err, Equals, nil)
-               c.Check(resp.Header.Get("Access-Control-Allow-Headers"), Equals, "Authorization, Content-Length, Content-Type, X-Keep-Desired-Replicas")
+               c.Check(resp.Header.Get("Access-Control-Allow-Headers"), Equals, "Authorization, Content-Length, Content-Type, X-Keep-Desired-Replicas, X-Keep-Signature, X-Keep-Storage-Classes")
                c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
        }
 }
                c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
        }
 }
index 0c8182c6ea31c91a8c20056e8ca886df43d27712..dfb2ace3a74c37a1375e24ec38b0cd9dc05b1905 100644 (file)
@@ -19,6 +19,7 @@ import (
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/auth"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/auth"
        "git.arvados.org/arvados.git/sdk/go/httpserver"
+       "git.arvados.org/arvados.git/sdk/go/keepclient"
        "github.com/gorilla/mux"
 )
 
        "github.com/gorilla/mux"
 )
 
@@ -57,9 +58,11 @@ func newRouter(keepstore *keepstore, puller *puller, trasher *trasher) service.H
        touch.HandleFunc(locatorPath, adminonly(rtr.handleBlockTouch))
        delete := r.Methods(http.MethodDelete).Subrouter()
        delete.HandleFunc(locatorPath, adminonly(rtr.handleBlockTrash))
        touch.HandleFunc(locatorPath, adminonly(rtr.handleBlockTouch))
        delete := r.Methods(http.MethodDelete).Subrouter()
        delete.HandleFunc(locatorPath, adminonly(rtr.handleBlockTrash))
+       options := r.Methods(http.MethodOptions).Subrouter()
+       options.NewRoute().PathPrefix(`/`).HandlerFunc(rtr.handleOptions)
        r.NotFoundHandler = http.HandlerFunc(rtr.handleBadRequest)
        r.MethodNotAllowedHandler = http.HandlerFunc(rtr.handleBadRequest)
        r.NotFoundHandler = http.HandlerFunc(rtr.handleBadRequest)
        r.MethodNotAllowedHandler = http.HandlerFunc(rtr.handleBadRequest)
-       rtr.Handler = auth.LoadToken(r)
+       rtr.Handler = corsHandler(auth.LoadToken(r))
        return rtr
 }
 
        return rtr
 }
 
@@ -75,11 +78,11 @@ func (rtr *router) handleBlockRead(w http.ResponseWriter, req *http.Request) {
        // Intervening proxies must not return a cached GET response
        // to a prior request if a X-Keep-Signature request header has
        // been added or changed.
        // Intervening proxies must not return a cached GET response
        // to a prior request if a X-Keep-Signature request header has
        // been added or changed.
-       w.Header().Add("Vary", "X-Keep-Signature")
+       w.Header().Add("Vary", keepclient.XKeepSignature)
        var localLocator func(string)
        var localLocator func(string)
-       if strings.SplitN(req.Header.Get("X-Keep-Signature"), ",", 2)[0] == "local" {
+       if strings.SplitN(req.Header.Get(keepclient.XKeepSignature), ",", 2)[0] == "local" {
                localLocator = func(locator string) {
                localLocator = func(locator string) {
-                       w.Header().Set("X-Keep-Locator", locator)
+                       w.Header().Set(keepclient.XKeepLocator, locator)
                }
        }
        out := w
                }
        }
        out := w
@@ -107,20 +110,20 @@ func (rtr *router) handleBlockRead(w http.ResponseWriter, req *http.Request) {
 
 func (rtr *router) handleBlockWrite(w http.ResponseWriter, req *http.Request) {
        dataSize, _ := strconv.Atoi(req.Header.Get("Content-Length"))
 
 func (rtr *router) handleBlockWrite(w http.ResponseWriter, req *http.Request) {
        dataSize, _ := strconv.Atoi(req.Header.Get("Content-Length"))
-       replicas, _ := strconv.Atoi(req.Header.Get("X-Arvados-Replicas-Desired"))
+       replicas, _ := strconv.Atoi(req.Header.Get(keepclient.XKeepDesiredReplicas))
        resp, err := rtr.keepstore.BlockWrite(req.Context(), arvados.BlockWriteOptions{
                Hash:           mux.Vars(req)["locator"],
                Reader:         req.Body,
                DataSize:       dataSize,
                RequestID:      req.Header.Get("X-Request-Id"),
        resp, err := rtr.keepstore.BlockWrite(req.Context(), arvados.BlockWriteOptions{
                Hash:           mux.Vars(req)["locator"],
                Reader:         req.Body,
                DataSize:       dataSize,
                RequestID:      req.Header.Get("X-Request-Id"),
-               StorageClasses: trimSplit(req.Header.Get("X-Keep-Storage-Classes"), ","),
+               StorageClasses: trimSplit(req.Header.Get(keepclient.XKeepStorageClasses), ","),
                Replicas:       replicas,
        })
        if err != nil {
                rtr.handleError(w, req, err)
                return
        }
                Replicas:       replicas,
        })
        if err != nil {
                rtr.handleError(w, req, err)
                return
        }
-       w.Header().Set("X-Keep-Replicas-Stored", fmt.Sprintf("%d", resp.Replicas))
+       w.Header().Set(keepclient.XKeepReplicasStored, fmt.Sprintf("%d", resp.Replicas))
        scc := ""
        for k, n := range resp.StorageClasses {
                if n > 0 {
        scc := ""
        for k, n := range resp.StorageClasses {
                if n > 0 {
@@ -130,7 +133,7 @@ func (rtr *router) handleBlockWrite(w http.ResponseWriter, req *http.Request) {
                        scc += fmt.Sprintf("%s=%d", k, n)
                }
        }
                        scc += fmt.Sprintf("%s=%d", k, n)
                }
        }
-       w.Header().Set("X-Keep-Storage-Classes-Confirmed", scc)
+       w.Header().Set(keepclient.XKeepStorageClassesConfirmed, scc)
        w.WriteHeader(http.StatusOK)
        fmt.Fprintln(w, resp.Locator)
 }
        w.WriteHeader(http.StatusOK)
        fmt.Fprintln(w, resp.Locator)
 }
@@ -210,6 +213,9 @@ func (rtr *router) handleBadRequest(w http.ResponseWriter, req *http.Request) {
        http.Error(w, "Bad Request", http.StatusBadRequest)
 }
 
        http.Error(w, "Bad Request", http.StatusBadRequest)
 }
 
+func (rtr *router) handleOptions(w http.ResponseWriter, req *http.Request) {
+}
+
 func (rtr *router) handleError(w http.ResponseWriter, req *http.Request, err error) {
        if req.Context().Err() != nil {
                w.WriteHeader(499)
 func (rtr *router) handleError(w http.ResponseWriter, req *http.Request, err error) {
        if req.Context().Err() != nil {
                w.WriteHeader(499)
@@ -274,3 +280,24 @@ type discardWrite struct {
 func (discardWrite) Write(p []byte) (int, error) {
        return len(p), nil
 }
 func (discardWrite) Write(p []byte) (int, error) {
        return len(p), nil
 }
+
+func corsHandler(h http.Handler) http.Handler {
+       return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+               SetCORSHeaders(w)
+               h.ServeHTTP(w, r)
+       })
+}
+
+var corsHeaders = map[string]string{
+       "Access-Control-Allow-Methods":  "GET, HEAD, PUT, OPTIONS",
+       "Access-Control-Allow-Origin":   "*",
+       "Access-Control-Allow-Headers":  "Authorization, Content-Length, Content-Type, " + keepclient.XKeepDesiredReplicas + ", " + keepclient.XKeepSignature + ", " + keepclient.XKeepStorageClasses,
+       "Access-Control-Expose-Headers": keepclient.XKeepLocator + ", " + keepclient.XKeepReplicasStored + ", " + keepclient.XKeepStorageClassesConfirmed,
+       "Access-Control-Max-Age":        "86486400",
+}
+
+func SetCORSHeaders(w http.ResponseWriter) {
+       for k, v := range corsHeaders {
+               w.Header().Set(k, v)
+       }
+}
index ee7be4768c91499e667c8ba50aa512c7b3a930a3..215033b48ed9f00054b1da713a890b96691f4610 100644 (file)
@@ -78,22 +78,26 @@ func (s *routerSuite) TestBlockRead_Token(c *C) {
        resp := call(router, "GET", "http://example/"+locSigned, "", nil, nil)
        c.Check(resp.Code, Equals, http.StatusUnauthorized)
        c.Check(resp.Body.String(), Matches, "no token provided in Authorization header\n")
        resp := call(router, "GET", "http://example/"+locSigned, "", nil, nil)
        c.Check(resp.Code, Equals, http.StatusUnauthorized)
        c.Check(resp.Body.String(), Matches, "no token provided in Authorization header\n")
+       checkCORSHeaders(c, resp.Header())
 
        // Different token => invalid signature
        resp = call(router, "GET", "http://example/"+locSigned, "badtoken", nil, nil)
        c.Check(resp.Code, Equals, http.StatusBadRequest)
        c.Check(resp.Body.String(), Equals, "invalid signature\n")
 
        // Different token => invalid signature
        resp = call(router, "GET", "http://example/"+locSigned, "badtoken", nil, nil)
        c.Check(resp.Code, Equals, http.StatusBadRequest)
        c.Check(resp.Body.String(), Equals, "invalid signature\n")
+       checkCORSHeaders(c, resp.Header())
 
        // Correct token
        resp = call(router, "GET", "http://example/"+locSigned, arvadostest.ActiveTokenV2, nil, nil)
        c.Check(resp.Code, Equals, http.StatusOK)
        c.Check(resp.Body.String(), Equals, "foo")
 
        // Correct token
        resp = call(router, "GET", "http://example/"+locSigned, arvadostest.ActiveTokenV2, nil, nil)
        c.Check(resp.Code, Equals, http.StatusOK)
        c.Check(resp.Body.String(), Equals, "foo")
+       checkCORSHeaders(c, resp.Header())
 
        // HEAD
        resp = call(router, "HEAD", "http://example/"+locSigned, arvadostest.ActiveTokenV2, nil, nil)
        c.Check(resp.Code, Equals, http.StatusOK)
        c.Check(resp.Result().ContentLength, Equals, int64(3))
        c.Check(resp.Body.String(), Equals, "")
 
        // HEAD
        resp = call(router, "HEAD", "http://example/"+locSigned, arvadostest.ActiveTokenV2, nil, nil)
        c.Check(resp.Code, Equals, http.StatusOK)
        c.Check(resp.Result().ContentLength, Equals, int64(3))
        c.Check(resp.Body.String(), Equals, "")
+       checkCORSHeaders(c, resp.Header())
 }
 
 // As a special case we allow HEAD requests that only provide a hash
 }
 
 // As a special case we allow HEAD requests that only provide a hash
@@ -165,13 +169,16 @@ func (s *routerSuite) TestBlockRead_ChecksumMismatch(c *C) {
                }
                c.Check(resp.Body.Len(), Not(Equals), len(gooddata))
                c.Check(resp.Result().ContentLength, Equals, int64(len(gooddata)))
                }
                c.Check(resp.Body.Len(), Not(Equals), len(gooddata))
                c.Check(resp.Result().ContentLength, Equals, int64(len(gooddata)))
+               checkCORSHeaders(c, resp.Header())
 
                resp = call(router, "HEAD", "http://example/"+locSigned, arvadostest.ActiveTokenV2, nil, nil)
                c.Check(resp.Code, Equals, http.StatusBadGateway)
 
                resp = call(router, "HEAD", "http://example/"+locSigned, arvadostest.ActiveTokenV2, nil, nil)
                c.Check(resp.Code, Equals, http.StatusBadGateway)
+               checkCORSHeaders(c, resp.Header())
 
                hashSigned := router.keepstore.signLocator(arvadostest.ActiveTokenV2, hash)
                resp = call(router, "HEAD", "http://example/"+hashSigned, arvadostest.ActiveTokenV2, nil, nil)
                c.Check(resp.Code, Equals, http.StatusBadGateway)
 
                hashSigned := router.keepstore.signLocator(arvadostest.ActiveTokenV2, hash)
                resp = call(router, "HEAD", "http://example/"+hashSigned, arvadostest.ActiveTokenV2, nil, nil)
                c.Check(resp.Code, Equals, http.StatusBadGateway)
+               checkCORSHeaders(c, resp.Header())
        }
 }
 
        }
 }
 
@@ -181,6 +188,7 @@ func (s *routerSuite) TestBlockWrite(c *C) {
 
        resp := call(router, "PUT", "http://example/"+fooHash, arvadostest.ActiveTokenV2, []byte("foo"), nil)
        c.Check(resp.Code, Equals, http.StatusOK)
 
        resp := call(router, "PUT", "http://example/"+fooHash, arvadostest.ActiveTokenV2, []byte("foo"), nil)
        c.Check(resp.Code, Equals, http.StatusOK)
+       checkCORSHeaders(c, resp.Header())
        locator := strings.TrimSpace(resp.Body.String())
 
        resp = call(router, "GET", "http://example/"+locator, arvadostest.ActiveTokenV2, nil, nil)
        locator := strings.TrimSpace(resp.Body.String())
 
        resp = call(router, "GET", "http://example/"+locator, arvadostest.ActiveTokenV2, nil, nil)
@@ -192,7 +200,7 @@ func (s *routerSuite) TestBlockWrite_Headers(c *C) {
        router, cancel := testRouter(c, s.cluster, nil)
        defer cancel()
 
        router, cancel := testRouter(c, s.cluster, nil)
        defer cancel()
 
-       resp := call(router, "PUT", "http://example/"+fooHash, arvadostest.ActiveTokenV2, []byte("foo"), http.Header{"X-Arvados-Replicas-Desired": []string{"2"}})
+       resp := call(router, "PUT", "http://example/"+fooHash, arvadostest.ActiveTokenV2, []byte("foo"), http.Header{"X-Keep-Desired-Replicas": []string{"2"}})
        c.Check(resp.Code, Equals, http.StatusOK)
        c.Check(resp.Header().Get("X-Keep-Replicas-Stored"), Equals, "1")
        c.Check(sortCommaSeparated(resp.Header().Get("X-Keep-Storage-Classes-Confirmed")), Equals, "testclass1=1")
        c.Check(resp.Code, Equals, http.StatusOK)
        c.Check(resp.Header().Get("X-Keep-Replicas-Stored"), Equals, "1")
        c.Check(sortCommaSeparated(resp.Header().Get("X-Keep-Storage-Classes-Confirmed")), Equals, "testclass1=1")
@@ -373,6 +381,13 @@ func (s *routerSuite) TestVolumeErrorStatusCode(c *C) {
        c.Check(resp.Code, Equals, http.StatusBadGateway)
        c.Check(resp.Body.String(), Equals, "test error\n")
 
        c.Check(resp.Code, Equals, http.StatusBadGateway)
        c.Check(resp.Body.String(), Equals, "test error\n")
 
+       router.keepstore.mountsW[0].volume.(*stubVolume).blockRead = func(_ context.Context, hash string, w io.WriterAt) error {
+               return errors.New("no http status provided")
+       }
+       resp = call(router, "GET", "http://example/"+locSigned, arvadostest.ActiveTokenV2, nil, nil)
+       c.Check(resp.Code, Equals, http.StatusInternalServerError)
+       c.Check(resp.Body.String(), Equals, "no http status provided\n")
+
        c.Assert(router.keepstore.mountsW[1].volume.BlockWrite(context.Background(), barHash, []byte("bar")), IsNil)
 
        // If the requested block is available on the second volume,
        c.Assert(router.keepstore.mountsW[1].volume.BlockWrite(context.Background(), barHash, []byte("bar")), IsNil)
 
        // If the requested block is available on the second volume,
@@ -462,7 +477,6 @@ func (s *routerSuite) TestIndex(c *C) {
                c.Check(resp.Code, Equals, http.StatusOK)
                c.Check(strings.Split(resp.Body.String(), "\n"), HasLen, 5)
        }
                c.Check(resp.Code, Equals, http.StatusOK)
                c.Check(strings.Split(resp.Body.String(), "\n"), HasLen, 5)
        }
-
 }
 
 // Check that the context passed to a volume method gets cancelled
 }
 
 // Check that the context passed to a volume method gets cancelled
@@ -493,6 +507,19 @@ func (s *routerSuite) TestCancelOnDisconnect(c *C) {
        c.Check(resp.Code, Equals, 499)
 }
 
        c.Check(resp.Code, Equals, 499)
 }
 
+func (s *routerSuite) TestCORSPreflight(c *C) {
+       router, cancel := testRouter(c, s.cluster, nil)
+       defer cancel()
+
+       for _, path := range []string{"/", "/whatever", "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa+123"} {
+               c.Logf("=== %s", path)
+               resp := call(router, http.MethodOptions, "http://example"+path, arvadostest.ActiveTokenV2, nil, nil)
+               c.Check(resp.Code, Equals, http.StatusOK)
+               c.Check(resp.Body.String(), Equals, "")
+               checkCORSHeaders(c, resp.Header())
+       }
+}
+
 func call(handler http.Handler, method, path, tok string, body []byte, hdr http.Header) *httptest.ResponseRecorder {
        resp := httptest.NewRecorder()
        req, err := http.NewRequest(method, path, bytes.NewReader(body))
 func call(handler http.Handler, method, path, tok string, body []byte, hdr http.Header) *httptest.ResponseRecorder {
        resp := httptest.NewRecorder()
        req, err := http.NewRequest(method, path, bytes.NewReader(body))
@@ -508,3 +535,10 @@ func call(handler http.Handler, method, path, tok string, body []byte, hdr http.
        handler.ServeHTTP(resp, req)
        return resp
 }
        handler.ServeHTTP(resp, req)
        return resp
 }
+
+func checkCORSHeaders(c *C, h http.Header) {
+       c.Check(h.Get("Access-Control-Allow-Methods"), Equals, "GET, HEAD, PUT, OPTIONS")
+       c.Check(h.Get("Access-Control-Allow-Origin"), Equals, "*")
+       c.Check(h.Get("Access-Control-Allow-Headers"), Equals, "Authorization, Content-Length, Content-Type, X-Keep-Desired-Replicas, X-Keep-Signature, X-Keep-Storage-Classes")
+       c.Check(h.Get("Access-Control-Expose-Headers"), Equals, "X-Keep-Locator, X-Keep-Replicas-Stored, X-Keep-Storage-Classes-Confirmed")
+}
index dc857c32646b2aced992243122b94750607cf4e8..2e2e97a974efa2ddbb7b5e60f67160da85181980 100644 (file)
@@ -217,7 +217,23 @@ func (v *s3Volume) check(ec2metadataHostname string) error {
        creds := aws.NewChainProvider(
                []aws.CredentialsProvider{
                        aws.NewStaticCredentialsProvider(v.AccessKeyID, v.SecretAccessKey, v.AuthToken),
        creds := aws.NewChainProvider(
                []aws.CredentialsProvider{
                        aws.NewStaticCredentialsProvider(v.AccessKeyID, v.SecretAccessKey, v.AuthToken),
-                       ec2rolecreds.New(ec2metadata.New(cfg)),
+                       ec2rolecreds.New(ec2metadata.New(cfg), func(opts *ec2rolecreds.ProviderOptions) {
+                               // (from aws-sdk-go-v2 comments)
+                               // "allow the credentials to trigger
+                               // refreshing prior to the credentials
+                               // actually expiring. This is
+                               // beneficial so race conditions with
+                               // expiring credentials do not cause
+                               // request to fail unexpectedly due to
+                               // ExpiredTokenException exceptions."
+                               //
+                               // (from
+                               // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
+                               // "We make new credentials available
+                               // at least five minutes before the
+                               // expiration of the old credentials."
+                               opts.ExpiryWindow = 5 * time.Minute
+                       }),
                })
 
        cfg.Credentials = creds
                })
 
        cfg.Credentials = creds
index 7358d62706c7ff5723dcea2e551bcec1b781b0b4..9093202ff8ba46d1e6c03b7fc82f604540a94f4d 100644 (file)
@@ -33,6 +33,7 @@ yarn-error.log*
 
 .idea
 .vscode
 
 .idea
 .vscode
+.eslintcache
 /public/config.json
 /public/_health/
 
 /public/config.json
 /public/_health/
 
index 1a68d6fd77c7ca8b52007ce6b0f174ef97947425..72235b96f7eb38ce827b0162a989d01b78043742 100644 (file)
@@ -43,6 +43,12 @@ export WORKSPACE?=$(shell pwd)
 
 ARVADOS_DIRECTORY?=$(shell env -C $(WORKSPACE) git rev-parse --show-toplevel)
 
 
 ARVADOS_DIRECTORY?=$(shell env -C $(WORKSPACE) git rev-parse --show-toplevel)
 
+ifndef ci
+       TI=-ti
+else
+       TI=
+endif
+
 .PHONY: help clean* yarn-install test build packages packages-with-version integration-tests-in-docker
 
 help:
 .PHONY: help clean* yarn-install test build packages packages-with-version integration-tests-in-docker
 
 help:
@@ -92,7 +98,7 @@ else
 endif
 
 integration-tests-in-docker: workbench2-build-image check-arvados-directory
 endif
 
 integration-tests-in-docker: workbench2-build-image check-arvados-directory
-       docker run -ti --rm \
+       docker run $(TI) --rm \
                --env ARVADOS_DIRECTORY=/usr/src/arvados \
                --env GIT_DISCOVERY_ACROSS_FILESYSTEM=1 \
                -v $(WORKSPACE):/usr/src/arvados/services/workbench2 \
                --env ARVADOS_DIRECTORY=/usr/src/arvados \
                --env GIT_DISCOVERY_ACROSS_FILESYSTEM=1 \
                -v $(WORKSPACE):/usr/src/arvados/services/workbench2 \
@@ -102,7 +108,7 @@ integration-tests-in-docker: workbench2-build-image check-arvados-directory
                make arvados-server-install integration-tests SPECFILE=$(SPECFILE)
 
 unit-tests-in-docker: workbench2-build-image check-arvados-directory
                make arvados-server-install integration-tests SPECFILE=$(SPECFILE)
 
 unit-tests-in-docker: workbench2-build-image check-arvados-directory
-       docker run -ti --rm \
+       docker run $(TI) --rm \
                --env ARVADOS_DIRECTORY=/usr/src/arvados \
                --env GIT_DISCOVERY_ACROSS_FILESYSTEM=1 \
                -v $(WORKSPACE):/usr/src/arvados/services/workbench2 \
                --env ARVADOS_DIRECTORY=/usr/src/arvados \
                --env GIT_DISCOVERY_ACROSS_FILESYSTEM=1 \
                -v $(WORKSPACE):/usr/src/arvados/services/workbench2 \
@@ -112,7 +118,7 @@ unit-tests-in-docker: workbench2-build-image check-arvados-directory
                make arvados-server-install unit-tests
 
 tests-in-docker: workbench2-build-image check-arvados-directory
                make arvados-server-install unit-tests
 
 tests-in-docker: workbench2-build-image check-arvados-directory
-       docker run -ti --rm \
+       docker run $(TI) --rm \
                --env ARVADOS_DIRECTORY=/usr/src/arvados \
                --env GIT_DISCOVERY_ACROSS_FILESYSTEM=1 \
                --env ci="${ci}" \
                --env ARVADOS_DIRECTORY=/usr/src/arvados \
                --env GIT_DISCOVERY_ACROSS_FILESYSTEM=1 \
                --env ci="${ci}" \
index f8100961aa26f670f5571298451a8a607eea1c29..ba503934bd055e57dcc9a51c37ff603149be876d 100644 (file)
@@ -80,21 +80,24 @@ describe('Banner / tooltip tests', function () {
         cy.loginAs(adminUser);
         cy.waitForDom();
 
         cy.loginAs(adminUser);
         cy.waitForDom();
 
+        cy.waitForDom().get('[data-cy=confirmation-dialog]', {timeout: 10000}).should('be.visible');
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
-        cy.waitForDom();
-        cy.get('[data-cy=confirmation-dialog]').should('not.exist');
+        cy.waitForDom().get('[data-cy=confirmation-dialog]', {timeout: 10000}).should('not.exist');
 
         cy.get('[title=Notifications]').click();
         cy.get('li').contains('Restore Banner').click();
 
 
         cy.get('[title=Notifications]').click();
         cy.get('li').contains('Restore Banner').click();
 
-        cy.get('[data-cy=confirmation-dialog-ok-btn]').should('be.visible');
+        cy.waitForDom().get('[data-cy=confirmation-dialog-ok-btn]', {timeout: 10000}).should('be.visible');
     });
 
 
     it('should show tooltips and remove tooltips as localStorage key is present', () => {
         cy.loginAs(adminUser);
     });
 
 
     it('should show tooltips and remove tooltips as localStorage key is present', () => {
         cy.loginAs(adminUser);
+        cy.waitForDom();
 
 
+        cy.waitForDom().get('[data-cy=confirmation-dialog]', {timeout: 10000}).should('be.visible');
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
+        cy.waitForDom().get('[data-cy=confirmation-dialog]', {timeout: 10000}).should('not.exist');
 
         cy.contains('This allows you to navigate through the app').should('not.exist'); // This content comes from tooltips.txt
         cy.get('[data-cy=side-panel-tree]').trigger('mouseover');
 
         cy.contains('This allows you to navigate through the app').should('not.exist'); // This content comes from tooltips.txt
         cy.get('[data-cy=side-panel-tree]').trigger('mouseover');
index c5edf0e4f2d64edadbcab474ed747e9d54380b48..20ecf11c09f5a57e27615b7aabfe73105c9a88a4 100644 (file)
@@ -30,17 +30,17 @@ describe("Collection panel tests", function () {
     it('shows the appropriate buttons in the toolbar', () => {
 
         const msButtonTooltips = [
     it('shows the appropriate buttons in the toolbar', () => {
 
         const msButtonTooltips = [
+            'View details',
+            'Open in new tab',
+            'Copy link to clipboard',
+            'Open with 3rd party client',
             'API Details',
             'API Details',
-            'Add to Favorites',
-            'Copy to clipboard',
+            'Share',
             'Edit collection',
             'Edit collection',
-            'Make a copy',
             'Move to',
             'Move to',
+            'Make a copy',
             'Move to trash',
             'Move to trash',
-            'Open in new tab',
-            'Open with 3rd party client',
-            'Share',
-            'View details',
+            'Add to favorites',
         ];
 
         cy.loginAs(activeUser);
         ];
 
         cy.loginAs(activeUser);
@@ -143,7 +143,7 @@ describe("Collection panel tests", function () {
                 cy.get("[data-cy=name-field]").within(() => {
                     cy.get("input").type(" renamed");
                 });
                 cy.get("[data-cy=name-field]").within(() => {
                     cy.get("input").type(" renamed");
                 });
-                cy.get("[data-cy=form-submit-btn]").click();
+                cy.get("[data-cy=form-submit-btn]").click({timeout: 10000});
             });
         cy.get("[data-cy=form-dialog]").should("not.exist");
         // Attempt to rename the collection with the duplicate name
             });
         cy.get("[data-cy=form-dialog]").should("not.exist");
         // Attempt to rename the collection with the duplicate name
@@ -351,7 +351,7 @@ describe("Collection panel tests", function () {
                             cy.get("[data-cy=context-menu]")
                                 .should("contain", "Download")
                                 .and("contain", "Open in new tab")
                             cy.get("[data-cy=context-menu]")
                                 .should("contain", "Download")
                                 .and("contain", "Open in new tab")
-                                .and("contain", "Copy to clipboard")
+                                .and("contain", "Copy link to clipboard")
                                 .and(`${isWritable ? "" : "not."}contain`, "Rename")
                                 .and(`${isWritable ? "" : "not."}contain`, "Remove");
                             cy.get("body").click(); // Collapse the menu
                                 .and(`${isWritable ? "" : "not."}contain`, "Rename")
                                 .and(`${isWritable ? "" : "not."}contain`, "Remove");
                             cy.get("body").click(); // Collapse the menu
@@ -359,7 +359,7 @@ describe("Collection panel tests", function () {
                             cy.get("[data-cy=context-menu]")
                                 .should("not.contain", "Download")
                                 .and("contain", "Open in new tab")
                             cy.get("[data-cy=context-menu]")
                                 .should("not.contain", "Download")
                                 .and("contain", "Open in new tab")
-                                .and("contain", "Copy to clipboard")
+                                .and("contain", "Copy link to clipboard")
                                 .and(`${isWritable ? "" : "not."}contain`, "Rename")
                                 .and(`${isWritable ? "" : "not."}contain`, "Remove");
                             cy.get("body").click(); // Collapse the menu
                                 .and(`${isWritable ? "" : "not."}contain`, "Rename")
                                 .and(`${isWritable ? "" : "not."}contain`, "Remove");
                             cy.get("body").click(); // Collapse the menu
index 2a5a62927fd981f09132d1a9c5eb6a1db9205c2f..6a3a894e8a9556c8edd9198612b134b1fa977a42 100644 (file)
@@ -89,17 +89,15 @@ describe("Process tests", function () {
         it('shows the appropriate buttons in the toolbar', () => {
 
             const msButtonTooltips = [
         it('shows the appropriate buttons in the toolbar', () => {
 
             const msButtonTooltips = [
-                'API Details',
-                'Add to Favorites',
-                'CANCEL',
-                'Copy and re-run process',
-                'Edit process',
-                'Move to',
+                'View details',
                 'Open in new tab',
                 'Outputs',
                 'Open in new tab',
                 'Outputs',
+                'API Details',
+                'Edit process',
+                'Copy and re-run process',
+                'CANCEL',
                 'Remove',
                 'Remove',
-                'Share',
-                'View details',
+                'Add to favorites',
             ];
 
             createContainerRequest(
             ];
 
             createContainerRequest(
@@ -1279,6 +1277,7 @@ describe("Process tests", function () {
                 .contains(name)
                 .parents("tr")
                 .within($mainRow => {
                 .contains(name)
                 .parents("tr")
                 .within($mainRow => {
+                    cy.get($mainRow).scrollIntoView();
                     label && cy.contains(label);
 
                     if (multipleRows) {
                     label && cy.contains(label);
 
                     if (multipleRows) {
@@ -1407,7 +1406,8 @@ describe("Process tests", function () {
                 cy.get("[data-cy=process-io-card] h6")
                     .contains("Input Parameters")
                     .parents("[data-cy=process-io-card]")
                 cy.get("[data-cy=process-io-card] h6")
                     .contains("Input Parameters")
                     .parents("[data-cy=process-io-card]")
-                    .within(() => {
+                    .within((ctx) => {
+                        cy.get(ctx).scrollIntoView();
                         verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
                         verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
                         verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
                         verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
                         verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
                         verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
@@ -1444,11 +1444,11 @@ describe("Process tests", function () {
                     .parents("[data-cy=process-io-card]")
                     .within(ctx => {
                         cy.get(ctx).scrollIntoView();
                     .parents("[data-cy=process-io-card]")
                     .within(ctx => {
                         cy.get(ctx).scrollIntoView();
-                        cy.get('[data-cy="io-preview-image-toggle"]').click({ waitForAnimations: false });
                         const outPdh = testOutputCollection.portable_data_hash;
 
                         verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
                         const outPdh = testOutputCollection.portable_data_hash;
 
                         verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
-                        verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
+                        // Disabled until image preview returns
+                        // verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
                         verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
                         verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
                         verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
                         verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
                         verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
                         verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
@@ -1542,19 +1542,23 @@ describe("Process tests", function () {
                 cy.get("[data-cy=process-io-card] h6")
                     .contains("Input Parameters")
                     .parents("[data-cy=process-io-card]")
                 cy.get("[data-cy=process-io-card] h6")
                     .contains("Input Parameters")
                     .parents("[data-cy=process-io-card]")
-                    .within(() => {
+                    .within((ctx) => {
+                        cy.get(ctx).scrollIntoView();
                         cy.wait(2000);
                         cy.waitForDom();
                         cy.wait(2000);
                         cy.waitForDom();
-                        cy.get("tbody tr").each(item => {
-                            cy.wrap(item).contains("No value");
+
+                        testInputs.map((input) => {
+                            verifyIOParameter(input.definition.id.split('/').slice(-1)[0], null, null, "No value");
                         });
                     });
                 cy.get("[data-cy=process-io-card] h6")
                     .contains("Output Parameters")
                     .parents("[data-cy=process-io-card]")
                         });
                     });
                 cy.get("[data-cy=process-io-card] h6")
                     .contains("Output Parameters")
                     .parents("[data-cy=process-io-card]")
-                    .within(() => {
-                        cy.get("tbody tr").each(item => {
-                            cy.wrap(item).contains("No value");
+                    .within((ctx) => {
+                        cy.get(ctx).scrollIntoView();
+
+                        testOutputs.map((output) => {
+                            verifyIOParameter(output.definition.id.split('/').slice(-1)[0], null, null, "No value");
                         });
                     });
             });
                         });
                     });
             });
index 4aeb59bc75ef4d8cfe79a6e187e3e12411fdcf5e..43215741f531917629c11c3dda43b531a150eda6 100644 (file)
@@ -219,18 +219,18 @@ describe("Project tests", function () {
     it('shows the appropriate buttons in the multiselect toolbar', () => {
 
         const msButtonTooltips = [
     it('shows the appropriate buttons in the multiselect toolbar', () => {
 
         const msButtonTooltips = [
+            'View details',
+            'Open in new tab',
+            'Copy link to clipboard',
+            'Open with 3rd party client',
             'API Details',
             'API Details',
-            'Add to Favorites',
-            'Copy to clipboard',
+            'Share',
+            'New project',
             'Edit project',
             'Edit project',
-            'Freeze Project',
             'Move to',
             'Move to trash',
             'Move to',
             'Move to trash',
-            'New project',
-            'Open in new tab',
-            'Open with 3rd party client',
-            'Share',
-            'View details',
+            'Freeze project',
+            'Add to favorites',
         ];
 
         cy.loginAs(activeUser);
         ];
 
         cy.loginAs(activeUser);
@@ -636,7 +636,7 @@ describe("Project tests", function () {
         cy.get("[data-cy=side-panel-tree]").contains("Projects").click();
         cy.waitForDom();
         cy.get("[data-cy=project-panel]").contains(projectName).should("be.visible").rightclick();
         cy.get("[data-cy=side-panel-tree]").contains("Projects").click();
         cy.waitForDom();
         cy.get("[data-cy=project-panel]").contains(projectName).should("be.visible").rightclick();
-        cy.get("[data-cy=context-menu]").contains("Copy to clipboard").click();
+        cy.get("[data-cy=context-menu]").contains("Copy link to clipboard").click();
         cy.window().then(win =>
             win.navigator.clipboard.readText().then(text => {
                 expect(text).to.match(/https\:\/\/127\.0\.0\.1\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/);
         cy.window().then(win =>
             win.navigator.clipboard.readText().then(text => {
                 expect(text).to.match(/https\:\/\/127\.0\.0\.1\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/);
index ba9077ac2065a78c9a37ecf2a0ed73007112fde9..4e5aa31f4dc2b209bcf1ae35927bf2477ac930d4 100644 (file)
@@ -215,8 +215,6 @@ describe("Search tests", function () {
                         DispatchCloud: { ExternalURL: "" },
                         DispatchLSF: { ExternalURL: "" },
                         DispatchSLURM: { ExternalURL: "" },
                         DispatchCloud: { ExternalURL: "" },
                         DispatchLSF: { ExternalURL: "" },
                         DispatchSLURM: { ExternalURL: "" },
-                        GitHTTP: { ExternalURL: "https://xxxxx.fakecluster.tld:39105/" },
-                        GitSSH: { ExternalURL: "" },
                         Health: { ExternalURL: "https://xxxxx.fakecluster.tld:42915/" },
                         Keepbalance: { ExternalURL: "" },
                         Keepproxy: { ExternalURL: "https://xxxxx.fakecluster.tld:46773/" },
                         Health: { ExternalURL: "https://xxxxx.fakecluster.tld:42915/" },
                         Keepbalance: { ExternalURL: "" },
                         Keepproxy: { ExternalURL: "https://xxxxx.fakecluster.tld:46773/" },
@@ -271,17 +269,17 @@ describe("Search tests", function () {
                 cy.stub(win, "open").as("Open");
             });
 
                 cy.stub(win, "open").as("Open");
             });
 
-            // Check copy to clipboard
+            // Check Copy link to clipboard
             cy.get("[data-cy=search-results]").contains(colName).rightclick();
             cy.get("[data-cy=context-menu]").within(ctx => {
                 // Check that there are 4 items in the menu
                 cy.get(ctx).children().should("have.length", 4);
                 cy.contains("API Details");
             cy.get("[data-cy=search-results]").contains(colName).rightclick();
             cy.get("[data-cy=context-menu]").within(ctx => {
                 // Check that there are 4 items in the menu
                 cy.get(ctx).children().should("have.length", 4);
                 cy.contains("API Details");
-                cy.contains("Copy to clipboard");
+                cy.contains("Copy link to clipboard");
                 cy.contains("Open in new tab");
                 cy.contains("View details");
 
                 cy.contains("Open in new tab");
                 cy.contains("View details");
 
-                cy.contains("Copy to clipboard").click();
+                cy.contains("Copy link to clipboard").click();
                 cy.waitForDom();
                 cy.window().then(win =>
                     win.navigator.clipboard.readText().then(text => {
                 cy.waitForDom();
                 cy.window().then(win =>
                     win.navigator.clipboard.readText().then(text => {
@@ -298,10 +296,10 @@ describe("Search tests", function () {
                 cy.get("@Open").should("have.been.calledOnceWith", `${window.location.origin}/collections/${testCollection.uuid}`);
             });
 
                 cy.get("@Open").should("have.been.calledOnceWith", `${window.location.origin}/collections/${testCollection.uuid}`);
             });
 
-            // Check federated result copy to clipboard
+            // Check federated result Copy link to clipboard
             cy.get("[data-cy=search-results]").contains(federatedColName).rightclick();
             cy.get("[data-cy=context-menu]").within(() => {
             cy.get("[data-cy=search-results]").contains(federatedColName).rightclick();
             cy.get("[data-cy=context-menu]").within(() => {
-                cy.contains("Copy to clipboard").click();
+                cy.contains("Copy link to clipboard").click();
                 cy.waitForDom();
                 cy.window().then(win =>
                     win.navigator.clipboard.readText().then(text => {
                 cy.waitForDom();
                 cy.window().then(win =>
                     win.navigator.clipboard.readText().then(text => {
index 05a7d470bf6e1b1d7ed048dc27c46c60d27cd9aa..4cb7e487853941773cd7494282430777bd4fdc52 100644 (file)
@@ -31,7 +31,7 @@ describe('Sharing tests', function () {
 
             cy.get('main').contains(sharedCollection.name).rightclick();
             cy.get('[data-cy=context-menu]').within(() => {
 
             cy.get('main').contains(sharedCollection.name).rightclick();
             cy.get('[data-cy=context-menu]').within(() => {
-                cy.contains('Share').click();
+                cy.contains('Share').click({ waitForAnimations: false });
             });
             cy.get('.sharing-dialog').within(() => {
                 cy.contains('Sharing URLs').click();
             });
             cy.get('.sharing-dialog').within(() => {
                 cy.contains('Sharing URLs').click();
@@ -63,7 +63,7 @@ describe('Sharing tests', function () {
             cy.contains('Refresh').click();
             cy.get('main').contains(mySharedWritableProject.name).rightclick();
             cy.get('[data-cy=context-menu]').within(() => {
             cy.contains('Refresh').click();
             cy.get('main').contains(mySharedWritableProject.name).rightclick();
             cy.get('[data-cy=context-menu]').within(() => {
-                cy.contains('Share').click();
+                cy.contains('Share').click({ waitForAnimations: false });
             });
             cy.get('[id="select-permissions"]').as('selectPermissions');
             cy.get('@selectPermissions').click();
             });
             cy.get('[id="select-permissions"]').as('selectPermissions');
             cy.get('@selectPermissions').click();
@@ -73,7 +73,7 @@ describe('Sharing tests', function () {
             cy.get('[role=tooltip]').click();
             cy.get('@sharingDialog').within(() => {
                 cy.get('[data-cy=add-invited-people]').click();
             cy.get('[role=tooltip]').click();
             cy.get('@sharingDialog').within(() => {
                 cy.get('[data-cy=add-invited-people]').click();
-                cy.contains('Close').click();
+                cy.contains('Close').click({ waitForAnimations: false });
             });
         });
 
             });
         });
 
@@ -84,14 +84,14 @@ describe('Sharing tests', function () {
             cy.contains('Refresh').click();
             cy.get('main').contains(mySharedReadonlyProject.name).rightclick();
             cy.get('[data-cy=context-menu]').within(() => {
             cy.contains('Refresh').click();
             cy.get('main').contains(mySharedReadonlyProject.name).rightclick();
             cy.get('[data-cy=context-menu]').within(() => {
-                cy.contains('Share').click();
+                cy.contains('Share').click({ waitForAnimations: false });
             });
             cy.get('.sharing-dialog').as('sharingDialog');
             cy.get('[data-cy=invite-people-field]').find('input').type(activeUser.user.email);
             cy.get('[role=tooltip]').click();
             cy.get('@sharingDialog').within(() => {
                 cy.get('[data-cy=add-invited-people]').click();
             });
             cy.get('.sharing-dialog').as('sharingDialog');
             cy.get('[data-cy=invite-people-field]').find('input').type(activeUser.user.email);
             cy.get('[role=tooltip]').click();
             cy.get('@sharingDialog').within(() => {
                 cy.get('[data-cy=add-invited-people]').click();
-                cy.contains('Close').click();
+                cy.contains('Close').click({ waitForAnimations: false });
             });
         });
 
             });
         });
 
@@ -117,7 +117,7 @@ describe('Sharing tests', function () {
                 // Test move to trash
                 cy.get('main').contains(mySharedWritableProject.name).rightclick();
                 cy.get('[data-cy=context-menu]').should('contain', 'Move to trash');
                 // Test move to trash
                 cy.get('main').contains(mySharedWritableProject.name).rightclick();
                 cy.get('[data-cy=context-menu]').should('contain', 'Move to trash');
-                cy.get('[data-cy=context-menu]').contains('Move to trash').click();
+                cy.get('[data-cy=context-menu]').contains('Move to trash').click({ waitForAnimations: false });
 
                 // GUARD: Let's wait for the above removed project to disappear
                 // before continuing, to avoid intermittent failures.
 
                 // GUARD: Let's wait for the above removed project to disappear
                 // before continuing, to avoid intermittent failures.
@@ -161,7 +161,7 @@ describe('Sharing tests', function () {
             .then(function ([]) {
                 cy.loginAs(adminUser);
                 cy.get('[data-cy=project-panel]').contains(collName).rightclick();
             .then(function ([]) {
                 cy.loginAs(adminUser);
                 cy.get('[data-cy=project-panel]').contains(collName).rightclick();
-                cy.get('[data-cy=context-menu]').contains('Share').click();
+                cy.get('[data-cy=context-menu]').contains('Share').click({ waitForAnimations: false });
                 cy.get('button').get('[data-cy=add-invited-people]').should('be.disabled');
                 cy.get('[data-cy=invite-people-field] input').type('Anonymous');
                 cy.get('div[role=tooltip]').contains('anonymous').click();
                 cy.get('button').get('[data-cy=add-invited-people]').should('be.disabled');
                 cy.get('[data-cy=invite-people-field] input').type('Anonymous');
                 cy.get('div[role=tooltip]').contains('anonymous').click();
index 0a06eaf361ba97763ba20d4730001c3b26e93378..3e4cb7fa07b2b2c41b1998a5ac28b7ecedb11be2 100644 (file)
@@ -79,10 +79,10 @@ describe('User profile tests', function() {
             cy.get('[role=button]').contains('API Details');
 
             cy.get('[role=button]').should(account ? 'contain' : 'not.contain', 'Account Settings');
             cy.get('[role=button]').contains('API Details');
 
             cy.get('[role=button]').should(account ? 'contain' : 'not.contain', 'Account Settings');
-            cy.get('[role=button]').should(activate ? 'contain' : 'not.contain', 'Activate User');
-            cy.get('[role=button]').should(deactivate ? 'contain' : 'not.contain', 'Deactivate User');
-            cy.get('[role=button]').should(login ? 'contain' : 'not.contain', 'Login As User');
-            cy.get('[role=button]').should(setup ? 'contain' : 'not.contain', 'Setup User');
+            cy.get('[role=button]').should(activate ? 'contain' : 'not.contain', 'Activate user');
+            cy.get('[role=button]').should(deactivate ? 'contain' : 'not.contain', 'Deactivate user');
+            cy.get('[role=button]').should(login ? 'contain' : 'not.contain', 'Login as user');
+            cy.get('[role=button]').should(setup ? 'contain' : 'not.contain', 'Setup user');
         });
         cy.get('div[role=presentation]').click();
     }
         });
         cy.get('div[role=presentation]').click();
     }
@@ -364,7 +364,7 @@ describe('User profile tests', function() {
 
         // Deactivate user
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
 
         // Deactivate user
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
-        cy.get('[data-cy=context-menu]').contains('Deactivate User').click();
+        cy.get('[data-cy=context-menu]').contains('Deactivate user').click();
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is deactivated
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is deactivated
@@ -382,7 +382,7 @@ describe('User profile tests', function() {
 
         // Setup user
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
 
         // Setup user
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
-        cy.get('[data-cy=context-menu]').contains('Setup User').click();
+        cy.get('[data-cy=context-menu]').contains('Setup user').click();
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is setup
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is setup
@@ -400,7 +400,7 @@ describe('User profile tests', function() {
 
         // Activate user
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
 
         // Activate user
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
-        cy.get('[data-cy=context-menu]').contains('Activate User').click();
+        cy.get('[data-cy=context-menu]').contains('Activate user').click();
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is active
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is active
@@ -418,7 +418,7 @@ describe('User profile tests', function() {
 
         // Deactivate and activate user skipping setup
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
 
         // Deactivate and activate user skipping setup
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
-        cy.get('[data-cy=context-menu]').contains('Deactivate User').click();
+        cy.get('[data-cy=context-menu]').contains('Deactivate user').click();
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
         // Check
         cy.get('[data-cy=account-status]').contains('Inactive');
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
         // Check
         cy.get('[data-cy=account-status]').contains('Inactive');
@@ -434,7 +434,7 @@ describe('User profile tests', function() {
         });
         // reactivate
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
         });
         // reactivate
         cy.get('[data-cy=user-profile-panel-options-btn]').click();
-        cy.get('[data-cy=context-menu]').contains('Activate User').click();
+        cy.get('[data-cy=context-menu]').contains('Activate user').click();
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is active
         cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
 
         // Check that user is active
index c6c49ee34325b294b2f0e17bcfe09058cf562401..b9cf86c55652c003077b25abfa9c0498b0f0370d 100644 (file)
@@ -269,12 +269,12 @@ describe('Registered workflow panel tests', function() {
     it('shows the appropriate buttons in the multiselect toolbar', () => {
 
         const msButtonTooltips = [
     it('shows the appropriate buttons in the multiselect toolbar', () => {
 
         const msButtonTooltips = [
-            'API Details',
-            'Copy to clipboard',
-            'Delete Workflow',
+            'View details',
             'Open in new tab',
             'Open in new tab',
+            'Copy link to clipboard',
+            'API Details',
             'Run Workflow',
             'Run Workflow',
-            'View details',
+            'Delete Workflow',
         ];
 
         cy.createResource(activeUser.token, "workflows", {workflow: {name: "Test wf"}})
         ];
 
         cy.createResource(activeUser.token, "workflows", {workflow: {name: "Test wf"}})
index e02fa6b956df60b7a7a8daeba75fda069c72a90f..8ae2d8a3e962cc3acc27c4fecd2c938324096f02 100644 (file)
@@ -3,6 +3,8 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@babel/core": "^7.0.0",
+    "@babel/runtime-corejs2": "^7.0.0",
     "@coreui/coreui": "^4.3.2",
     "@coreui/react": "^4.11.0",
     "@date-io/date-fns": "1",
     "@coreui/coreui": "^4.3.2",
     "@coreui/react": "^4.11.0",
     "@date-io/date-fns": "1",
     "@types/react-window": "1.8.2",
     "@types/redux-form": "7.4.12",
     "@types/shell-escape": "^0.2.0",
     "@types/react-window": "1.8.2",
     "@types/redux-form": "7.4.12",
     "@types/shell-escape": "^0.2.0",
-    "axios": "^0.21.1",
-    "babel-core": "6.26.3",
-    "babel-runtime": "6.26.0",
+    "axios": "^0.28.1",
     "bootstrap": "^5.3.2",
     "bootstrap": "^5.3.2",
-    "caniuse-lite": "1.0.30001299",
+    "caniuse-lite": "1.0.30001612",
     "classnames": "2.2.6",
     "cwlts": "1.15.29",
     "date-fns": "^2.28.0",
     "classnames": "2.2.6",
     "cwlts": "1.15.29",
     "date-fns": "^2.28.0",
@@ -68,7 +68,7 @@
     "react-router-dom": "4.3.1",
     "react-router-redux": "5.0.0-alpha.9",
     "react-rte": "^0.16.5",
     "react-router-dom": "4.3.1",
     "react-router-redux": "5.0.0-alpha.9",
     "react-rte": "^0.16.5",
-    "react-scripts": "3.4.4",
+    "react-scripts": "4.0.1",
     "react-splitter-layout": "3.0.1",
     "react-transition-group": "2.5.0",
     "react-virtualized-auto-sizer": "1.0.2",
     "react-splitter-layout": "3.0.1",
     "react-transition-group": "2.5.0",
     "react-virtualized-auto-sizer": "1.0.2",
@@ -93,8 +93,8 @@
     "test-local": "react-scripts test",
     "eject": "react-scripts eject",
     "lint": "tslint src/** -t verbose",
     "test-local": "react-scripts test",
     "eject": "react-scripts eject",
     "lint": "tslint src/** -t verbose",
-    "build-css": "node-sass-chokidar src/ -o src/",
-    "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive"
+    "build-css": "node-sass src/ -o src/",
+    "watch-css": "npm run build-css && node-sass src/ -o src/ --watch --recursive"
   },
   "devDependencies": {
     "@sinonjs/fake-timers": "^10.3.0",
   },
   "devDependencies": {
     "@sinonjs/fake-timers": "^10.3.0",
     "enzyme-adapter-react-16": "1.15.6",
     "jest-localstorage-mock": "2.2.0",
     "node-sass": "^9.0.0",
     "enzyme-adapter-react-16": "1.15.6",
     "jest-localstorage-mock": "2.2.0",
     "node-sass": "^9.0.0",
-    "node-sass-chokidar": "^2.0.0",
     "redux-devtools": "3.4.1",
     "redux-mock-store": "1.5.4",
     "ts-mock-imports": "1.3.7",
     "redux-devtools": "3.4.1",
     "redux-mock-store": "1.5.4",
     "ts-mock-imports": "1.3.7",
       "last 1 safari version"
     ]
   },
       "last 1 safari version"
     ]
   },
-  "packageManager": "yarn@3.2.0"
+  "packageManager": "yarn@3.2.0",
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  }
 }
 }
index 47d8fe1bf029bc5915a069c4a2c12dd08c8051b4..3be1e4fc71b4698fefc15ea48062b4b777aba563 100644 (file)
@@ -66,7 +66,7 @@ export const CodeSnippet = withStyles(styles)(connect(mapStateToProps)(
         </Typography>
 ));
 
         </Typography>
 ));
 
-const renderLinks = (auth: FederationConfig, dispatch: Dispatch) => (text: string): JSX.Element => {
+export const renderLinks = (auth: FederationConfig, dispatch: Dispatch) => (text: string): JSX.Element => {
     // Matches UUIDs & PDHs
     const REGEX = /[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}|[0-9a-f]{32}\+\d+/g;
     const links = text.match(REGEX);
     // Matches UUIDs & PDHs
     const REGEX = /[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}|[0-9a-f]{32}\+\d+/g;
     const links = text.match(REGEX);
diff --git a/services/workbench2/src/components/code-snippet/virtual-code-snippet.tsx b/services/workbench2/src/components/code-snippet/virtual-code-snippet.tsx
new file mode 100644 (file)
index 0000000..09db2c0
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { StyleRulesCallback, WithStyles, Typography, withStyles } from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+import classNames from 'classnames';
+import { connect, DispatchProp } from 'react-redux';
+import { RootState } from 'store/store';
+import { FederationConfig } from 'routes/routes';
+import { renderLinks } from './code-snippet';
+import { FixedSizeList } from 'react-window';
+import AutoSizer from "react-virtualized-auto-sizer";
+
+type CssRules = 'root' | 'space' | 'content' ;
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        boxSizing: 'border-box',
+        height: '100%',
+        padding: theme.spacing.unit,
+    },
+    space: {
+        marginLeft: '15px',
+    },
+    content: {
+        maxHeight: '100%',
+        height: '100vh',
+    },
+});
+
+export interface CodeSnippetDataProps {
+    lines: string[];
+    lineFormatter?: (lines: string[], index: number) => string;
+    className?: string;
+    apiResponse?: boolean;
+    linked?: boolean;
+}
+
+interface CodeSnippetAuthProps {
+    auth: FederationConfig;
+}
+
+type CodeSnippetProps = CodeSnippetDataProps & WithStyles<CssRules>;
+
+const mapStateToProps = (state: RootState): CodeSnippetAuthProps => ({
+    auth: state.auth,
+});
+
+export const VirtualCodeSnippet = withStyles(styles)(connect(mapStateToProps)(
+    ({ classes, lines, lineFormatter, linked, className, apiResponse, dispatch, auth }: CodeSnippetProps & CodeSnippetAuthProps & DispatchProp) => {
+        const RenderRow = ({index, style}) => {
+            const lineContents = lineFormatter ? lineFormatter(lines, index) : lines[index];
+            return <span style={style}>{linked ? renderLinks(auth, dispatch)(lineContents) : lineContents}</span>
+        };
+
+        return <Typography
+            component="div"
+            className={classNames([classes.root, className])}>
+            <Typography className={classNames(classes.content, apiResponse ? classes.space : className)} component="pre">
+                <AutoSizer>
+                    {({ height, width }) =>
+                        <FixedSizeList
+                            height={height}
+                            width={width}
+                            itemSize={21}
+                            itemCount={lines.length}
+                        >
+                            {RenderRow}
+                        </FixedSizeList>
+                    }
+                </AutoSizer>
+            </Typography>
+        </Typography>;
+}));
index e58eb8919814c680bf7732c5ef6ee1886008c674..03d4551cabcc7a4beb4f2b33749046209ce89547 100644 (file)
@@ -326,14 +326,16 @@ export const CollectionPanelFiles = withStyles(styles)(
                 setLeftSearch("");
                 setRightSearch("");
             }
                 setLeftSearch("");
                 setRightSearch("");
             }
-        }, [rightKey, rightData]); // eslint-disable-line react-hooks/exhaustive-deps
+            // eslint-disable-next-line react-hooks/exhaustive-deps
+        }, [rightKey, rightData]); 
 
         const currentPDH = (collectionPanel.item || {}).portableDataHash;
         React.useEffect(() => {
             if (currentPDH) {
                 fetchData([leftKey, rightKey], true);
             }
 
         const currentPDH = (collectionPanel.item || {}).portableDataHash;
         React.useEffect(() => {
             if (currentPDH) {
                 fetchData([leftKey, rightKey], true);
             }
-        }, [currentPDH]); // eslint-disable-line react-hooks/exhaustive-deps
+            // eslint-disable-next-line react-hooks/exhaustive-deps
+        }, [currentPDH]); 
 
         React.useEffect(() => {
             if (rightData) {
 
         React.useEffect(() => {
             if (rightData) {
@@ -451,7 +453,8 @@ export const CollectionPanelFiles = withStyles(styles)(
                     onItemMenuOpen(event, item, isWritable);
                 }
             },
                     onItemMenuOpen(event, item, isWritable);
                 }
             },
-            [path, setPath, collectionPanelFiles] // eslint-disable-line react-hooks/exhaustive-deps
+            // eslint-disable-next-line react-hooks/exhaustive-deps
+            [path, setPath, collectionPanelFiles] 
         );
 
         const getItemIcon = React.useCallback(
         );
 
         const getItemIcon = React.useCallback(
@@ -487,7 +490,8 @@ export const CollectionPanelFiles = withStyles(styles)(
             (ev, isWritable) => {
                 props.onOptionsMenuOpen(ev, isWritable);
             },
             (ev, isWritable) => {
                 props.onOptionsMenuOpen(ev, isWritable);
             },
-            [props.onOptionsMenuOpen] // eslint-disable-line react-hooks/exhaustive-deps
+            // eslint-disable-next-line react-hooks/exhaustive-deps
+            [props.onOptionsMenuOpen] 
         );
 
         return (
         );
 
         return (
diff --git a/services/workbench2/src/components/conditional-tabs/conditional-tabs.test.tsx b/services/workbench2/src/components/conditional-tabs/conditional-tabs.test.tsx
new file mode 100644 (file)
index 0000000..db30135
--- /dev/null
@@ -0,0 +1,106 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from "react";
+import { mount, configure } from "enzyme";
+import { ConditionalTabs, TabData } from "./conditional-tabs";
+import Adapter from 'enzyme-adapter-react-16';
+import { Tab } from "@material-ui/core";
+
+configure({ adapter: new Adapter() });
+
+describe("<ConditionalTabs />", () => {
+    let tabs: TabData[] = [];
+
+    beforeEach(() => {
+        tabs = [{
+            show: true,
+            label: "Tab1",
+            content: <div id="content1">Content1</div>,
+        },{
+            show: false,
+            label: "Tab2",
+            content: <div id="content2">Content2</div>,
+        },{
+            show: true,
+            label: "Tab3",
+            content: <div id="content3">Content3</div>,
+        }];
+    });
+
+    it("renders only visible tabs", () => {
+        // given
+        const tabContainer = mount(<ConditionalTabs
+            tabs={tabs}
+        />);
+
+        // expect 2 visible tabs
+        expect(tabContainer.find(Tab)).toHaveLength(2);
+        expect(tabContainer.find(Tab).at(0).text()).toBe("Tab1");
+        expect(tabContainer.find(Tab).at(1).text()).toBe("Tab3");
+        // expect visible content 1 and tab 3 to be hidden but exist
+        // content 2 stays unrendered since the tab is hidden
+        expect(tabContainer.find('div#content1').text()).toBe("Content1");
+        expect(tabContainer.find('div#content1').prop('hidden')).toBeFalsy();
+        expect(tabContainer.find('div#content2').exists()).toBeFalsy();
+        expect(tabContainer.find('div#content3').prop('hidden')).toBeTruthy();
+
+        // Show second tab
+        tabs[1].show = true;
+        tabContainer.setProps({ tabs: tabs });
+        tabContainer.update();
+
+        // Expect 3 visible tabs
+        expect(tabContainer.find(Tab)).toHaveLength(3);
+        expect(tabContainer.find(Tab).at(0).text()).toBe("Tab1");
+        expect(tabContainer.find(Tab).at(1).text()).toBe("Tab2");
+        expect(tabContainer.find(Tab).at(2).text()).toBe("Tab3");
+        // Expect visible content 1 and hidden content 2/3
+        expect(tabContainer.find('div#content1').text()).toBe("Content1");
+        expect(tabContainer.find('div#content1').prop('hidden')).toBeFalsy();
+        expect(tabContainer.find('div#content2').prop('hidden')).toBeTruthy();
+        expect(tabContainer.find('div#content3').prop('hidden')).toBeTruthy();
+
+        // Click on Tab2 (position 1)
+        tabContainer.find(Tab).at(1).simulate('click');
+
+        // Expect 3 visible tabs
+        expect(tabContainer.find(Tab)).toHaveLength(3);
+        expect(tabContainer.find(Tab).at(0).text()).toBe("Tab1");
+        expect(tabContainer.find(Tab).at(1).text()).toBe("Tab2");
+        expect(tabContainer.find(Tab).at(2).text()).toBe("Tab3");
+        // Expect visible content2 and hidden content 1/3
+        expect(tabContainer.find('div#content2').text()).toBe("Content2");
+        expect(tabContainer.find('div#content1').prop('hidden')).toBeTruthy();
+        expect(tabContainer.find('div#content2').prop('hidden')).toBeFalsy();
+        expect(tabContainer.find('div#content3').prop('hidden')).toBeTruthy();
+    });
+
+    it("resets selected tab on tab visibility change", () => {
+        // given
+        const tabContainer = mount(<ConditionalTabs
+            tabs={tabs}
+        />);
+
+        // Expect second tab to be Tab3
+        expect(tabContainer.find(Tab).at(1).text()).toBe("Tab3");
+        // Click on Tab3 (position 2)
+        tabContainer.find(Tab).at(1).simulate('click');
+        expect(tabContainer.find('div#content3').text()).toBe("Content3");
+        expect(tabContainer.find('div#content1').prop('hidden')).toBeTruthy();
+        expect(tabContainer.find('div#content2').exists()).toBeFalsy();
+        expect(tabContainer.find('div#content3').prop('hidden')).toBeFalsy();
+
+        // when Tab2 becomes visible
+        tabs[1].show = true;
+        tabContainer.setProps({ tabs: tabs });
+        tabContainer.update(); // Needed or else tab1 content will still be hidden
+
+        // Selected tab resets to 1, tabs 2/3 are hidden
+        expect(tabContainer.find('div#content1').text()).toBe("Content1");
+        expect(tabContainer.find('div#content1').prop('hidden')).toBeFalsy();
+        expect(tabContainer.find('div#content2').prop('hidden')).toBeTruthy();
+        expect(tabContainer.find('div#content3').prop('hidden')).toBeTruthy();
+    });
+});
diff --git a/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx b/services/workbench2/src/components/conditional-tabs/conditional-tabs.tsx
new file mode 100644 (file)
index 0000000..248c9c0
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React, { ReactElement, useEffect, useState } from "react";
+import { Tabs, Tab } from "@material-ui/core";
+import { TabsProps } from "@material-ui/core/Tabs";
+
+interface ComponentWithHidden {
+    hidden: boolean;
+};
+
+export type TabData = {
+    show: boolean;
+    label: string;
+    content: ReactElement<ComponentWithHidden>;
+};
+
+type ConditionalTabsProps = {
+    tabs: TabData[];
+};
+
+export const ConditionalTabs = (props: Omit<TabsProps, 'value' | 'onChange'> & ConditionalTabsProps) => {
+    const [tabState, setTabState] = useState(0);
+    const visibleTabs = props.tabs.filter(tab => tab.show);
+    const visibleTabNames = visibleTabs.map(tab => tab.label).join();
+
+    const handleTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+        setTabState(value);
+    };
+
+    // Reset tab to 0 when tab visibility changes
+    // (or if tab set change causes visible set to change)
+    useEffect(() => {
+        setTabState(0);
+    }, [visibleTabNames]);
+
+    return <>
+        <Tabs
+            {...props}
+            value={tabState}
+            onChange={handleTabChange} >
+            {visibleTabs.map(tab => <Tab key={tab.label} label={tab.label} />)}
+        </Tabs>
+
+        {visibleTabs.map((tab, i) => (
+            React.cloneElement(tab.content, {key: i, hidden: i !== tabState})
+        ))}
+    </>;
+};
index 3ef483dfe03faf63edd24fa3f02ff322e16110c4..13e653176607bc0b133575bd7aa791bc1a329d8f 100644 (file)
@@ -48,7 +48,7 @@ export const CopyToClipboardSnackbar = connect()(
             render() {
                 const { children, value, classes } = this.props;
                 return (
             render() {
                 const { children, value, classes } = this.props;
                 return (
-                    <Tooltip title='Copy to clipboard' onClick={(ev) => ev.stopPropagation()}>
+                    <Tooltip title='Copy link to clipboard' onClick={(ev) => ev.stopPropagation()}>
                         <span className={classes.copyIcon}>
                             <CopyToClipboard text={value} onCopy={this.onCopy}>
                                 {children || <CopyIcon />}
                         <span className={classes.copyIcon}>
                             <CopyToClipboard text={value} onCopy={this.onCopy}>
                                 {children || <CopyIcon />}
diff --git a/services/workbench2/src/components/copy-to-clipboard/copy-result-to-clipboard.ts b/services/workbench2/src/components/copy-to-clipboard/copy-result-to-clipboard.ts
new file mode 100644 (file)
index 0000000..129002b
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { ReactElementLike } from 'prop-types';
+import copy from 'copy-to-clipboard';
+
+interface CopyToClipboardProps {
+  getText: (() => string);
+  children: ReactElementLike;
+  onCopy?(text: string, result: boolean): void;
+  options?: {
+    debug?: boolean;
+    message?: string;
+    format?: string; // MIME type
+  };
+}
+
+export default class CopyResultToClipboard extends React.PureComponent<CopyToClipboardProps> {
+  static defaultProps = {
+    onCopy: undefined,
+    options: undefined
+  };
+
+  onClick = event => {
+    const {
+      getText,
+      onCopy,
+      children,
+      options
+    } = this.props;
+
+    const elem = React.Children.only(children);
+
+    const text = getText();
+
+    const result = copy(text, options);
+
+    if (onCopy) {
+      onCopy(text, result);
+    }
+
+    // Bypass onClick if it was present
+    if (elem && elem.props && typeof elem.props.onClick === 'function') {
+      elem.props.onClick(event);
+    }
+  };
+
+
+  render() {
+    const {
+      getText: _getText,
+      onCopy: _onCopy,
+      options: _options,
+      children,
+      ...props
+    } = this.props;
+    const elem = React.Children.only(children);
+
+    return React.cloneElement(elem, {...props, onClick: this.onClick});
+  }
+}
index 557abd825a004cf85c0a8fe1486f436940cfc79b..a5ac7421c0eb2b06001642274fdcd43d13d8a9ed 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React, { useEffect } from 'react';
+import React from 'react';
 import {
     WithStyles,
     withStyles,
 import {
     WithStyles,
     withStyles,
@@ -101,6 +101,10 @@ export const DataTableFiltersPopover = withStyles(styles)(
         };
         icon = React.createRef<HTMLElement>();
 
         };
         icon = React.createRef<HTMLElement>();
 
+        componentWillUnmount(): void {
+            this.submit.cancel();
+        }
+
         render() {
             const { name, classes, defaultSelection = SelectionMode.ALL, children } = this.props;
             const isActive = getNodeDescendants('')(this.state.filters).some((f) => (defaultSelection === SelectionMode.ALL ? !f.selected : f.selected));
         render() {
             const { name, classes, defaultSelection = SelectionMode.ALL, children } = this.props;
             const isActive = getNodeDescendants('')(this.state.filters).some((f) => (defaultSelection === SelectionMode.ALL ? !f.selected : f.selected));
@@ -137,7 +141,6 @@ export const DataTableFiltersPopover = withStyles(styles)(
                             </>
                         </Card>
                     </Popover>
                             </>
                         </Card>
                     </Popover>
-                    <this.MountHandler />
                 </>
             );
         }
                 </>
             );
         }
@@ -172,15 +175,6 @@ export const DataTableFiltersPopover = withStyles(styles)(
             }
         }, 1000);
 
             }
         }, 1000);
 
-        MountHandler = () => {
-            useEffect(() => {
-                return () => {
-                    this.submit.cancel();
-                };
-            }, []);
-            return null;
-        };
-
         close = () => {
             this.setState((prev) => ({
                 ...prev,
         close = () => {
             this.setState((prev) => ({
                 ...prev,
diff --git a/services/workbench2/src/components/default-code-snippet/default-virtual-code-snippet.tsx b/services/workbench2/src/components/default-code-snippet/default-virtual-code-snippet.tsx
new file mode 100644 (file)
index 0000000..581f0f4
--- /dev/null
@@ -0,0 +1,31 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
+import { VirtualCodeSnippet, CodeSnippetDataProps } from 'components/code-snippet/virtual-code-snippet';
+import grey from '@material-ui/core/colors/grey';
+import { themeOptions } from 'common/custom-theme';
+
+const theme = createMuiTheme(Object.assign({}, themeOptions, {
+    overrides: {
+        MuiTypography: {
+            body1: {
+                color: grey["900"]
+            },
+            root: {
+                backgroundColor: grey["200"]
+            }
+        }
+    },
+    typography: {
+        fontFamily: 'monospace',
+        useNextVariants: true,
+    }
+}));
+
+export const DefaultVirtualCodeSnippet = (props: CodeSnippetDataProps) =>
+    <MuiThemeProvider theme={theme}>
+        <VirtualCodeSnippet {...props} />
+    </MuiThemeProvider>;
index 5130db56d1f96314b1e9be12d17cb7aa7c883c55..019470c026fb96310bc90266aba4b9fbf9ddbb83 100644 (file)
@@ -96,10 +96,10 @@ export const DetailsAttribute = connect(mapStateToProps)(withStyles(styles)(
                 if (linkUrl[0] === '/') {
                     valueNode = <Link to={linkUrl} className={classes.link}>{uuid}</Link>;
                 } else {
                 if (linkUrl[0] === '/') {
                     valueNode = <Link to={linkUrl} className={classes.link}>{uuid}</Link>;
                 } else {
-                    valueNode = <a href={linkUrl} className={classes.link} target='_blank' rel="noopener">{uuid}</a>;
+                    valueNode = <a href={linkUrl} className={classes.link} target='_blank' rel="noopener noreferrer">{uuid}</a>;
                 }
             } else if (link) {
                 }
             } else if (link) {
-                valueNode = <a href={link} className={classes.link} target='_blank' rel="noopener">{value}</a>;
+                valueNode = <a href={link} className={classes.link} target='_blank' rel="noopener noreferrer">{value}</a>;
             } else {
                 valueNode = value;
             }
             } else {
                 valueNode = value;
             }
@@ -124,7 +124,7 @@ export const DetailsAttributeComponent = withStyles(styles)(
                 className={classnames([props.classes.value, props.classValue, { [props.classes.lowercaseValue]: props.lowercaseValue }])}>
                 {props.value}
                 {props.children}
                 className={classnames([props.classes.value, props.classValue, { [props.classes.lowercaseValue]: props.lowercaseValue }])}>
                 {props.value}
                 {props.children}
-                {(props.linkToUuid || props.copyValue) && props.onCopy && <Tooltip title="Copy to clipboard">
+                {(props.linkToUuid || props.copyValue) && props.onCopy && <Tooltip title="Copy link to clipboard">
                     <span className={props.classes.copyIcon}>
                         <CopyToClipboard text={props.linkToUuid || props.copyValue || ""} onCopy={() => props.onCopy!("Copied")}>
                             <CopyIcon />
                     <span className={props.classes.copyIcon}>
                         <CopyToClipboard text={props.linkToUuid || props.copyValue || ""} onCopy={() => props.onCopy!("Copied")}>
                             <CopyIcon />
index 1ba88d25b221ce4ea5567401ebff3f7e4ed2e6d7..08c2e8f45476874d32f6474d545f6a1a6ee2dfe2 100644 (file)
@@ -179,6 +179,13 @@ export const DoubleRightArrows: IconType = (props: any) => (
     </SvgIcon>
 )
 
     </SvgIcon>
 )
 
+//https://pictogrammers.com/library/memory/icon/box-light-vertical/
+export const VerticalLineDivider: IconType = (props: any) => (
+    <SvgIcon {...props}>
+        <path d="M12 0V22H10V0H12Z" />
+    </SvgIcon>
+)
+
 export type IconType = React.SFC<{ className?: string; style?: object }>;
 
 export const AddIcon: IconType = props => <Add {...props} />;
 export type IconType = React.SFC<{ className?: string; style?: object }>;
 
 export const AddIcon: IconType = props => <Add {...props} />;
index 650059316626632d5ed29b0ef68553eed3433502..194950b134c9c0cf5d7f233909033b70ae16a01e 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React, { useEffect, useState } from "react";
+import React from "react";
 import { connect } from "react-redux";
 import { StyleRulesCallback, withStyles, WithStyles, Toolbar, Tooltip, IconButton } from "@material-ui/core";
 import { ArvadosTheme } from "common/custom-theme";
 import { connect } from "react-redux";
 import { StyleRulesCallback, withStyles, WithStyles, Toolbar, Tooltip, IconButton } from "@material-ui/core";
 import { ArvadosTheme } from "common/custom-theme";
@@ -14,9 +14,8 @@ import { Resource, ResourceKind, extractUuidKind } from "models/resource";
 import { getResource } from "store/resources/resources";
 import { ResourcesState } from "store/resources/resources";
 import { MultiSelectMenuAction, MultiSelectMenuActionSet } from "views-components/multiselect-toolbar/ms-menu-actions";
 import { getResource } from "store/resources/resources";
 import { ResourcesState } from "store/resources/resources";
 import { MultiSelectMenuAction, MultiSelectMenuActionSet } from "views-components/multiselect-toolbar/ms-menu-actions";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
-import { ContextMenuAction } from "views-components/context-menu/context-menu-action-set";
-import { multiselectActionsFilters, TMultiselectActionsFilters, msMenuResourceKind } from "./ms-toolbar-action-filters";
+import { ContextMenuAction, ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
+import { multiselectActionsFilters, TMultiselectActionsFilters } from "./ms-toolbar-action-filters";
 import { kindToActionSet, findActionByName } from "./ms-kind-action-differentiator";
 import { msToggleTrashAction } from "views-components/multiselect-toolbar/ms-project-action-set";
 import { copyToClipboardAction } from "store/open-in-new-tab/open-in-new-tab.actions";
 import { kindToActionSet, findActionByName } from "./ms-kind-action-differentiator";
 import { msToggleTrashAction } from "views-components/multiselect-toolbar/ms-project-action-set";
 import { copyToClipboardAction } from "store/open-in-new-tab/open-in-new-tab.actions";
@@ -35,10 +34,9 @@ import { Process } from "store/processes/process";
 import { PublicFavoritesState } from "store/public-favorites/public-favorites-reducer";
 import { isExactlyOneSelected } from "store/multiselect/multiselect-actions";
 import { IntersectionObserverWrapper } from "./ms-toolbar-overflow-wrapper";
 import { PublicFavoritesState } from "store/public-favorites/public-favorites-reducer";
 import { isExactlyOneSelected } from "store/multiselect/multiselect-actions";
 import { IntersectionObserverWrapper } from "./ms-toolbar-overflow-wrapper";
+import { ContextMenuKind, sortMenuItems, menuDirection } from 'views-components/context-menu/menu-item-sort';
 
 
-const WIDTH_TRANSITION = 150
-
-type CssRules = "root" | "transition" | "button" | "iconContainer" | "icon";
+type CssRules = "root" | "button" | "iconContainer" | "icon" | "divider";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
@@ -48,17 +46,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         height: '2.7rem',
         padding: 0,
         margin: "1rem auto auto 0.3rem",
         height: '2.7rem',
         padding: 0,
         margin: "1rem auto auto 0.3rem",
-        transition: `width ${WIDTH_TRANSITION}ms`,
-        overflow: 'hidden',
-    },
-    transition: {
-        display: "flex",
-        flexDirection: "row",
-        height: '2.7rem',
-        padding: 0,
-        margin: "1rem auto auto 0.3rem",
         overflow: 'hidden',
         overflow: 'hidden',
-        transition: `width ${WIDTH_TRANSITION}ms`,
     },
     button: {
         width: "2.5rem",
     },
     button: {
         width: "2.5rem",
@@ -71,7 +59,11 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     icon: {
         marginLeft: '-0.5rem',
     },
     icon: {
         marginLeft: '-0.5rem',
-    }
+    },
+    divider: {
+        display: "flex",
+        alignItems: "center",
+    },
 });
 
 export type MultiselectToolbarProps = {
 });
 
 export type MultiselectToolbarProps = {
@@ -98,30 +90,24 @@ export const MultiselectToolbar = connect(
         const singleResourceKind = singleSelectedUuid ? [resourceToMsResourceKind(singleSelectedUuid, iconProps.resources, user)] : null
         const currentResourceKinds = singleResourceKind ? singleResourceKind : Array.from(selectedToKindSet(checkedList));
         const currentPathIsTrash = window.location.pathname === "/trash";
         const singleResourceKind = singleSelectedUuid ? [resourceToMsResourceKind(singleSelectedUuid, iconProps.resources, user)] : null
         const currentResourceKinds = singleResourceKind ? singleResourceKind : Array.from(selectedToKindSet(checkedList));
         const currentPathIsTrash = window.location.pathname === "/trash";
-        const [isTransitioning, setIsTransitioning] = useState(false);
-        
-        const handleTransition = () => {
-            setIsTransitioning(true)
-            setTimeout(() => {
-                setIsTransitioning(false)
-            }, WIDTH_TRANSITION);
-        }
-        
-        useEffect(()=>{
-                handleTransition()
-        }, [checkedList])
 
 
-        const actions =
+        const rawActions =
             currentPathIsTrash && selectedToKindSet(checkedList).size
                 ? [msToggleTrashAction]
                 : selectActionsByKind(currentResourceKinds as string[], multiselectActionsFilters).filter((action) =>
                         singleSelectedUuid === null ? action.isForMulti : true
                     );
             currentPathIsTrash && selectedToKindSet(checkedList).size
                 ? [msToggleTrashAction]
                 : selectActionsByKind(currentResourceKinds as string[], multiselectActionsFilters).filter((action) =>
                         singleSelectedUuid === null ? action.isForMulti : true
                     );
+                    
+        const actions: ContextMenuAction[] | MultiSelectMenuAction[] = sortMenuItems(
+            singleResourceKind && singleResourceKind.length ? (singleResourceKind[0] as ContextMenuKind) : ContextMenuKind.MULTI,
+            rawActions,
+            menuDirection.HORIZONTAL
+        ); 
 
         return (
             <React.Fragment>
                 <Toolbar
 
         return (
             <React.Fragment>
                 <Toolbar
-                    className={isTransitioning ? classes.transition: classes.root}
+                    className={classes.root}
                     style={{ width: `${(actions.length * 2.5) + 6}rem`}}
                     data-cy='multiselect-toolbar'
                     >
                     style={{ width: `${(actions.length * 2.5) + 6}rem`}}
                     data-cy='multiselect-toolbar'
                     >
@@ -129,14 +115,24 @@ export const MultiselectToolbar = connect(
                         <IntersectionObserverWrapper menuLength={actions.length}>
                             {actions.map((action, i) =>{
                                 const { hasAlts, useAlts, name, altName, icon, altIcon } = action;
                         <IntersectionObserverWrapper menuLength={actions.length}>
                             {actions.map((action, i) =>{
                                 const { hasAlts, useAlts, name, altName, icon, altIcon } = action;
-                            return hasAlts ? (
+                            return action.name === ContextMenuActionNames.DIVIDER ? (
+                                action.component && (
+                                    <div
+                                        className={classes.divider}
+                                        data-targetid={`${name}${i}`}
+                                        key={i}
+                                    >
+                                        <action.component />
+                                    </div>
+                                )
+                            ) : hasAlts ? (
                                 <Tooltip
                                     className={classes.button}
                                     data-targetid={name}
                                     title={currentPathIsTrash || (useAlts && useAlts(singleSelectedUuid, iconProps)) ? altName : name}
                                     key={i}
                                     disableFocusListener
                                 <Tooltip
                                     className={classes.button}
                                     data-targetid={name}
                                     title={currentPathIsTrash || (useAlts && useAlts(singleSelectedUuid, iconProps)) ? altName : name}
                                     key={i}
                                     disableFocusListener
-                                    >
+                                >
                                     <span className={classes.iconContainer}>
                                         <IconButton
                                             data-cy='multiselect-button'
                                     <span className={classes.iconContainer}>
                                         <IconButton
                                             data-cy='multiselect-button'
@@ -155,7 +151,7 @@ export const MultiselectToolbar = connect(
                                     title={action.name}
                                     key={i}
                                     disableFocusListener
                                     title={action.name}
                                     key={i}
                                     disableFocusListener
-                                    >
+                                >
                                     <span className={classes.iconContainer}>
                                         <IconButton
                                             data-cy='multiselect-button'
                                     <span className={classes.iconContainer}>
                                         <IconButton
                                             data-cy='multiselect-button'
@@ -212,7 +208,7 @@ function filterActions(actionArray: MultiSelectMenuActionSet, filters: Set<strin
     return actionArray[0].filter(action => filters.has(action.name as string));
 }
 
     return actionArray[0].filter(action => filters.has(action.name as string));
 }
 
-const resourceToMsResourceKind = (uuid: string, resources: ResourcesState, user: User | null, readonly = false): (msMenuResourceKind | ResourceKind) | undefined => {
+const resourceToMsResourceKind = (uuid: string, resources: ResourcesState, user: User | null, readonly = false): (ContextMenuKind | ResourceKind) | undefined => {
     if (!user) return;
     const resource = getResourceWithEditableStatus<GroupResource & EditableResource>(uuid, user.uuid)(resources);
     const { isAdmin } = user;
     if (!user) return;
     const resource = getResourceWithEditableStatus<GroupResource & EditableResource>(uuid, user.uuid)(resources);
     const { isAdmin } = user;
@@ -224,18 +220,18 @@ const resourceToMsResourceKind = (uuid: string, resources: ResourcesState, user:
     switch (kind) {
         case ResourceKind.PROJECT:
             if (isFrozen) {
     switch (kind) {
         case ResourceKind.PROJECT:
             if (isFrozen) {
-                return isAdmin ? msMenuResourceKind.FROZEN_PROJECT_ADMIN : msMenuResourceKind.FROZEN_PROJECT;
+                return isAdmin ? ContextMenuKind.FROZEN_PROJECT_ADMIN : ContextMenuKind.FROZEN_PROJECT;
             }
 
             return isAdmin && !readonly
                 ? resource && resource.groupClass !== GroupClass.FILTER
             }
 
             return isAdmin && !readonly
                 ? resource && resource.groupClass !== GroupClass.FILTER
-                    ? msMenuResourceKind.PROJECT_ADMIN
-                    : msMenuResourceKind.FILTER_GROUP_ADMIN
+                    ? ContextMenuKind.PROJECT_ADMIN
+                    : ContextMenuKind.FILTER_GROUP_ADMIN
                 : isEditable
                 ? resource && resource.groupClass !== GroupClass.FILTER
                 : isEditable
                 ? resource && resource.groupClass !== GroupClass.FILTER
-                    ? msMenuResourceKind.PROJECT
-                    : msMenuResourceKind.FILTER_GROUP
-                : msMenuResourceKind.READONLY_PROJECT;
+                    ? ContextMenuKind.PROJECT
+                    : ContextMenuKind.FILTER_GROUP
+                : ContextMenuKind.READONLY_PROJECT;
         case ResourceKind.COLLECTION:
             const c = getResource<CollectionResource>(uuid)(resources);
             if (c === undefined) {
         case ResourceKind.COLLECTION:
             const c = getResource<CollectionResource>(uuid)(resources);
             if (c === undefined) {
@@ -244,36 +240,36 @@ const resourceToMsResourceKind = (uuid: string, resources: ResourcesState, user:
             const isOldVersion = c.uuid !== c.currentVersionUuid;
             const isTrashed = c.isTrashed;
             return isOldVersion
             const isOldVersion = c.uuid !== c.currentVersionUuid;
             const isTrashed = c.isTrashed;
             return isOldVersion
-                ? msMenuResourceKind.OLD_VERSION_COLLECTION
+                ? ContextMenuKind.OLD_VERSION_COLLECTION
                 : isTrashed && isEditable
                 : isTrashed && isEditable
-                ? msMenuResourceKind.TRASHED_COLLECTION
+                ? ContextMenuKind.TRASHED_COLLECTION
                 : isAdmin && isEditable
                 : isAdmin && isEditable
-                ? msMenuResourceKind.COLLECTION_ADMIN
+                ? ContextMenuKind.COLLECTION_ADMIN
                 : isEditable
                 : isEditable
-                ? msMenuResourceKind.COLLECTION
-                : msMenuResourceKind.READONLY_COLLECTION;
+                ? ContextMenuKind.COLLECTION
+                : ContextMenuKind.READONLY_COLLECTION;
         case ResourceKind.PROCESS:
             return isAdmin && isEditable
                 ? resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process)
         case ResourceKind.PROCESS:
             return isAdmin && isEditable
                 ? resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process)
-                    ? msMenuResourceKind.RUNNING_PROCESS_ADMIN
-                    : msMenuResourceKind.PROCESS_ADMIN
+                    ? ContextMenuKind.RUNNING_PROCESS_ADMIN
+                    : ContextMenuKind.PROCESS_ADMIN
                 : readonly
                 : readonly
-                ? msMenuResourceKind.READONLY_PROCESS_RESOURCE
+                ? ContextMenuKind.READONLY_PROCESS_RESOURCE
                 : resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process)
                 : resource && isProcessCancelable(getProcess(resource.uuid)(resources) as Process)
-                ? msMenuResourceKind.RUNNING_PROCESS_RESOURCE
-                : msMenuResourceKind.PROCESS_RESOURCE;
+                ? ContextMenuKind.RUNNING_PROCESS_RESOURCE
+                : ContextMenuKind.PROCESS_RESOURCE;
         case ResourceKind.USER:
         case ResourceKind.USER:
-            return msMenuResourceKind.ROOT_PROJECT;
+            return ContextMenuKind.ROOT_PROJECT;
         case ResourceKind.LINK:
         case ResourceKind.LINK:
-            return msMenuResourceKind.LINK;
+            return ContextMenuKind.LINK;
         case ResourceKind.WORKFLOW:
         case ResourceKind.WORKFLOW:
-            return isEditable ? msMenuResourceKind.WORKFLOW : msMenuResourceKind.READONLY_WORKFLOW;
+            return isEditable ? ContextMenuKind.WORKFLOW : ContextMenuKind.READONLY_WORKFLOW;
         default:
             return;
     }
 }; 
 
         default:
             return;
     }
 }; 
 
-function selectActionsByKind(currentResourceKinds: Array<string>, filterSet: TMultiselectActionsFilters) {
+function selectActionsByKind(currentResourceKinds: Array<string>, filterSet: TMultiselectActionsFilters): MultiSelectMenuAction[] {
     const rawResult: Set<MultiSelectMenuAction> = new Set();
     const resultNames = new Set();
     const allFiltersArray: MultiSelectMenuAction[][] = []
     const rawResult: Set<MultiSelectMenuAction> = new Set();
     const resultNames = new Set();
     const allFiltersArray: MultiSelectMenuAction[][] = []
@@ -303,17 +299,7 @@ function selectActionsByKind(currentResourceKinds: Array<string>, filterSet: TMu
         return true;
     });
 
         return true;
     });
 
-    return filteredResult.sort((a, b) => {
-        const nameA = a.name || "";
-        const nameB = b.name || "";
-        if (nameA < nameB) {
-            return -1;
-        }
-        if (nameA > nameB) {
-            return 1;
-        }
-        return 0;
-    });
+    return filteredResult;
 }
 
 
 }
 
 
@@ -338,13 +324,13 @@ function mapDispatchToProps(dispatch: Dispatch) {
         executeMulti: (selectedAction: ContextMenuAction, checkedList: TCheckedList, resources: ResourcesState): void => {
             const kindGroups = groupByKind(checkedList, resources);
             switch (selectedAction.name) {
         executeMulti: (selectedAction: ContextMenuAction, checkedList: TCheckedList, resources: ResourcesState): void => {
             const kindGroups = groupByKind(checkedList, resources);
             switch (selectedAction.name) {
-                case MultiSelectMenuActionNames.MOVE_TO:
-                case MultiSelectMenuActionNames.REMOVE:
+                case ContextMenuActionNames.MOVE_TO:
+                case ContextMenuActionNames.REMOVE:
                     const firstResource = getResource(selectedToArray(checkedList)[0])(resources) as ContainerRequestResource | Resource;
                     const action = findActionByName(selectedAction.name as string, kindToActionSet[firstResource.kind]);
                     if (action) action.execute(dispatch, kindGroups[firstResource.kind]);
                     break;
                     const firstResource = getResource(selectedToArray(checkedList)[0])(resources) as ContainerRequestResource | Resource;
                     const action = findActionByName(selectedAction.name as string, kindToActionSet[firstResource.kind]);
                     if (action) action.execute(dispatch, kindGroups[firstResource.kind]);
                     break;
-                case MultiSelectMenuActionNames.COPY_TO_CLIPBOARD:
+                case ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD:
                     const selectedResources = selectedToArray(checkedList).map(uuid => getResource(uuid)(resources));
                     dispatch<any>(copyToClipboardAction(selectedResources));
                     break;
                     const selectedResources = selectedToArray(checkedList).map(uuid => getResource(uuid)(resources));
                     dispatch<any>(copyToClipboardAction(selectedResources));
                     break;
index b34cc22cb9ab05934e4ff6568527091ee9dce45a..2b30525e56499c95216b24d9eb0176d560149f6a 100644 (file)
@@ -16,54 +16,7 @@ import {
 import { msProcessActionSet, msCommonProcessActionFilter, msAdminProcessActionFilter, msRunningProcessActionFilter } from 'views-components/multiselect-toolbar/ms-process-action-set';
 import { msWorkflowActionSet, msWorkflowActionFilter, msReadOnlyWorkflowActionFilter } from 'views-components/multiselect-toolbar/ms-workflow-action-set';
 import { ResourceKind } from 'models/resource';
 import { msProcessActionSet, msCommonProcessActionFilter, msAdminProcessActionFilter, msRunningProcessActionFilter } from 'views-components/multiselect-toolbar/ms-process-action-set';
 import { msWorkflowActionSet, msWorkflowActionFilter, msReadOnlyWorkflowActionFilter } from 'views-components/multiselect-toolbar/ms-workflow-action-set';
 import { ResourceKind } from 'models/resource';
-
-export enum msMenuResourceKind {
-    API_CLIENT_AUTHORIZATION = 'ApiClientAuthorization',
-    ROOT_PROJECT = 'RootProject',
-    PROJECT = 'Project',
-    FILTER_GROUP = 'FilterGroup',
-    READONLY_PROJECT = 'ReadOnlyProject',
-    FROZEN_PROJECT = 'FrozenProject',
-    FROZEN_PROJECT_ADMIN = 'FrozenProjectAdmin',
-    PROJECT_ADMIN = 'ProjectAdmin',
-    FILTER_GROUP_ADMIN = 'FilterGroupAdmin',
-    RESOURCE = 'Resource',
-    FAVORITE = 'Favorite',
-    TRASH = 'Trash',
-    COLLECTION_FILES = 'CollectionFiles',
-    COLLECTION_FILES_MULTIPLE = 'CollectionFilesMultiple',
-    READONLY_COLLECTION_FILES = 'ReadOnlyCollectionFiles',
-    READONLY_COLLECTION_FILES_MULTIPLE = 'ReadOnlyCollectionFilesMultiple',
-    COLLECTION_FILES_NOT_SELECTED = 'CollectionFilesNotSelected',
-    COLLECTION_FILE_ITEM = 'CollectionFileItem',
-    COLLECTION_DIRECTORY_ITEM = 'CollectionDirectoryItem',
-    READONLY_COLLECTION_FILE_ITEM = 'ReadOnlyCollectionFileItem',
-    READONLY_COLLECTION_DIRECTORY_ITEM = 'ReadOnlyCollectionDirectoryItem',
-    COLLECTION = 'Collection',
-    COLLECTION_ADMIN = 'CollectionAdmin',
-    READONLY_COLLECTION = 'ReadOnlyCollection',
-    OLD_VERSION_COLLECTION = 'OldVersionCollection',
-    TRASHED_COLLECTION = 'TrashedCollection',
-    PROCESS = 'Process',
-    RUNNING_PROCESS_ADMIN = 'RunningProcessAdmin',
-    PROCESS_ADMIN = 'ProcessAdmin',
-    RUNNING_PROCESS_RESOURCE = 'RunningProcessResource',
-    PROCESS_RESOURCE = 'ProcessResource',
-    READONLY_PROCESS_RESOURCE = 'ReadOnlyProcessResource',
-    PROCESS_LOGS = 'ProcessLogs',
-    REPOSITORY = 'Repository',
-    SSH_KEY = 'SshKey',
-    VIRTUAL_MACHINE = 'VirtualMachine',
-    KEEP_SERVICE = 'KeepService',
-    USER = 'User',
-    GROUPS = 'Group',
-    GROUP_MEMBER = 'GroupMember',
-    PERMISSION_EDIT = 'PermissionEdit',
-    LINK = 'Link',
-    WORKFLOW = 'Workflow',
-    READONLY_WORKFLOW = 'ReadOnlyWorkflow',
-    SEARCH_RESULTS = 'SearchResults',
-}
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 
 const {
     COLLECTION,
 
 const {
     COLLECTION,
@@ -82,7 +35,7 @@ const {
     FILTER_GROUP_ADMIN,
     WORKFLOW,
     READONLY_WORKFLOW,
     FILTER_GROUP_ADMIN,
     WORKFLOW,
     READONLY_WORKFLOW,
-} = msMenuResourceKind;
+} = ContextMenuKind;
 
 export type TMultiselectActionsFilters = Record<string, [MultiSelectMenuActionSet, Set<string>]>;
 
 
 export type TMultiselectActionsFilters = Record<string, [MultiSelectMenuActionSet, Set<string>]>;
 
index 9f8ced940dbaeef8625d370ede55501c8dd0cb62..28b82271590a8ed1642198a2b439fa7155bc8547 100644 (file)
@@ -84,8 +84,7 @@ export const OverflowMenu = withStyles(styles)((props: OverflowMenuProps & WithS
             >
                 {React.Children.map(children, (child: any) => {
                     if (!visibilityMap[child.props['data-targetid']]) {
             >
                 {React.Children.map(children, (child: any) => {
                     if (!visibilityMap[child.props['data-targetid']]) {
-                        return (
-                            <MenuItem
+                        return <MenuItem
                                 key={child}
                                 onClick={handleClose}
                                 className={classes.menuItem}
                                 key={child}
                                 onClick={handleClose}
                                 className={classes.menuItem}
@@ -94,7 +93,6 @@ export const OverflowMenu = withStyles(styles)((props: OverflowMenuProps & WithS
                                     className: classnames(classes.menuElement),
                                 })}
                             </MenuItem>
                                     className: classnames(classes.menuElement),
                                 })}
                             </MenuItem>
-                        );
                     }
                     return null;
                 })}
                     }
                     return null;
                 })}
index 32f977e1a4e772004fb2b2d09dc31a146041fed9..e0f32f1fa6e7a8f8a9b6a3ca45e2661b3a5d1272 100644 (file)
@@ -127,7 +127,7 @@ export const IntersectionObserverWrapper = withStyles(styles)((props: WrapperPro
                     visibilityMap={visibilityMap}
                     className={classes.overflowStyle}
                 >
                     visibilityMap={visibilityMap}
                     className={classes.overflowStyle}
                 >
-                    {children}
+                    {children.filter((child) => !child.props['data-targetid'].includes("Divider"))}
                 </OverflowMenu>
             )}
         </div>
                 </OverflowMenu>
             )}
         </div>
index ba70f752b9d22fe131dd1b40fdbefe6b7dfa5731..213b46404b50cc6baf43c7ea9051b5aa3772c258 100644 (file)
@@ -11,11 +11,10 @@ configure({ adapter: new Adapter() });
 
 describe("<SearchInput />", () => {
 
 
 describe("<SearchInput />", () => {
 
-    jest.useFakeTimers();
-
     let onSearch: () => void;
 
     beforeEach(() => {
     let onSearch: () => void;
 
     beforeEach(() => {
+        jest.useFakeTimers();
         onSearch = jest.fn();
     });
 
         onSearch = jest.fn();
     });
 
index 6d98aed28e3992e334ad08bbc7fc0b0b88bc77c0..4ae3ea9ef6bf302f76a69bcd225d8c178ee603c6 100644 (file)
@@ -46,7 +46,8 @@ export const SearchInput = (props: SearchInputProps) => {
             setValue("");
             clearTimeout(timeout);
         };
             setValue("");
             clearTimeout(timeout);
         };
-    }, [props.value, props.label]); // eslint-disable-line react-hooks/exhaustive-deps
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [props.value, props.label]); 
 
     useEffect(() => {
         if (selfClearProp !== props.selfClearProp) {
 
     useEffect(() => {
         if (selfClearProp !== props.selfClearProp) {
@@ -54,7 +55,8 @@ export const SearchInput = (props: SearchInputProps) => {
             setSelfClearProp(props.selfClearProp);
             handleChange({ target: { value: "" } } as any);
         }
             setSelfClearProp(props.selfClearProp);
             handleChange({ target: { value: "" } } as any);
         }
-    }, [props.selfClearProp]); // eslint-disable-line react-hooks/exhaustive-deps
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [props.selfClearProp]); 
 
     const handleSubmit = (event: React.FormEvent<HTMLElement>) => {
         event.preventDefault();
 
     const handleSubmit = (event: React.FormEvent<HTMLElement>) => {
         event.preventDefault();
index 1de21d9f357949b430b1f3b874087fa2ec52723d..7c90c049de75944d94aaa62effe605b207c77bab 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { CommandInputParameter } from 'models/workflow';
 // SPDX-License-Identifier: AGPL-3.0
 
 import { CommandInputParameter } from 'models/workflow';
-import { require } from 'validators/require';
+import { fieldRequire } from 'validators/require';
 import { CWLType } from '../../models/workflow';
 
 
 import { CWLType } from '../../models/workflow';
 
 
@@ -17,5 +17,5 @@ export const required = ({ type }: CommandInputParameter) => {
             }
         }
     }
             }
         }
     }
-    return require;
+    return fieldRequire;
 };
 };
index ef9ff9c98693576880141b679db8aff6f675d24c..400b975d4d7de73a47cc91f41db43fb367591aee 100644 (file)
@@ -20,7 +20,8 @@ import { MuiThemeProvider } from "@material-ui/core/styles";
 import { CustomTheme } from "common/custom-theme";
 import { fetchConfig } from "common/config";
 import servicesProvider from "common/service-provider";
 import { CustomTheme } from "common/custom-theme";
 import { fetchConfig } from "common/config";
 import servicesProvider from "common/service-provider";
-import { addMenuActionSet, ContextMenuKind } from "views-components/context-menu/context-menu";
+import { addMenuActionSet } from "views-components/context-menu/context-menu";
+import { ContextMenuKind } from "views-components/context-menu/menu-item-sort";
 import { rootProjectActionSet } from "views-components/context-menu/action-sets/root-project-action-set";
 import {
     filterGroupActionSet,
 import { rootProjectActionSet } from "views-components/context-menu/action-sets/root-project-action-set";
 import {
     filterGroupActionSet,
index a043b7c68075e9cae5d0ad5b847e1ec051088f5c..aea95b925fed68cdedba6b53cfc98b761e08d040 100644 (file)
@@ -20,8 +20,7 @@ describe('collection-service-files-response', () => {
             testCases.forEach(([inputURL, inputDisplayName, expectedURL, expectedName]) => {
                 // given
                 const collUUID = 'xxxxx-zzzzz-vvvvvvvvvvvvvvv';
             testCases.forEach(([inputURL, inputDisplayName, expectedURL, expectedName]) => {
                 // given
                 const collUUID = 'xxxxx-zzzzz-vvvvvvvvvvvvvvv';
-                const xmlData = `
-                <?xml version="1.0" encoding="UTF-8"?>
+                const xmlData = `<?xml version="1.0" encoding="UTF-8"?>
                 <D:multistatus xmlns:D="DAV:">
                     <D:response>
                         <D:href>/c=xxxxx-zzzzz-vvvvvvvvvvvvvvv/</D:href>
                 <D:multistatus xmlns:D="DAV:">
                     <D:response>
                         <D:href>/c=xxxxx-zzzzz-vvvvvvvvvvvvvvv/</D:href>
index e50e5ed35026403c6332865d6b897c32a01f5605..12d31d1678b7de140a2f5cb34c4849927e1a1f32 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { CollectionResource, defaultCollectionSelectedFields } from "models/collection";
 // SPDX-License-Identifier: AGPL-3.0
 
 import { CollectionResource, defaultCollectionSelectedFields } from "models/collection";
-import { AxiosInstance } from "axios";
+import { AxiosInstance, AxiosResponse } from "axios";
 import { CollectionFile, CollectionDirectory } from "models/collection-file";
 import { WebDAV } from "common/webdav";
 import { AuthService } from "../auth-service/auth-service";
 import { CollectionFile, CollectionDirectory } from "models/collection-file";
 import { WebDAV } from "common/webdav";
 import { AuthService } from "../auth-service/auth-service";
@@ -20,6 +20,11 @@ type CollectionPartialUpdateOrCreate =
     | (Partial<CollectionResource> & Pick<CollectionResource, "uuid">)
     | (Partial<CollectionResource> & Pick<CollectionResource, "ownerUuid">);
 
     | (Partial<CollectionResource> & Pick<CollectionResource, "uuid">)
     | (Partial<CollectionResource> & Pick<CollectionResource, "ownerUuid">);
 
+type ReplaceFilesPayload = {
+    collection: Partial<CollectionResource>;
+    replace_files: {[key: string]: string};
+}
+
 export const emptyCollectionPdh = "d41d8cd98f00b204e9800998ecf8427e+0";
 export const SOURCE_DESTINATION_EQUAL_ERROR_MESSAGE = "Source and destination cannot be the same";
 
 export const emptyCollectionPdh = "d41d8cd98f00b204e9800998ecf8427e+0";
 export const SOURCE_DESTINATION_EQUAL_ERROR_MESSAGE = "Source and destination cannot be the same";
 
@@ -78,7 +83,7 @@ export class CollectionService extends TrashableResourceService<CollectionResour
     }
 
     private replaceFiles(data: CollectionPartialUpdateOrCreate, fileMap: {}, showErrors?: boolean) {
     }
 
     private replaceFiles(data: CollectionPartialUpdateOrCreate, fileMap: {}, showErrors?: boolean) {
-        const payload = {
+        const payload: ReplaceFilesPayload = {
             collection: {
                 preserve_version: true,
                 ...CommonService.mapKeys(snakeCase)(data),
             collection: {
                 preserve_version: true,
                 ...CommonService.mapKeys(snakeCase)(data),
@@ -89,14 +94,14 @@ export class CollectionService extends TrashableResourceService<CollectionResour
         };
         if (data.uuid) {
             return CommonService.defaultResponse(
         };
         if (data.uuid) {
             return CommonService.defaultResponse(
-                this.serverApi.put<CollectionResource>(`/${this.resourceType}/${data.uuid}`, payload),
+                this.serverApi.put<ReplaceFilesPayload, AxiosResponse<CollectionResource>>(`/${this.resourceType}/${data.uuid}`, payload),
                 this.actions,
                 true, // mapKeys
                 showErrors
             );
         } else {
             return CommonService.defaultResponse(
                 this.actions,
                 true, // mapKeys
                 showErrors
             );
         } else {
             return CommonService.defaultResponse(
-                this.serverApi.post<CollectionResource>(`/${this.resourceType}`, payload),
+                this.serverApi.post<ReplaceFilesPayload, AxiosResponse<CollectionResource>>(`/${this.resourceType}`, payload),
                 this.actions,
                 true, // mapKeys
                 showErrors
                 this.actions,
                 true, // mapKeys
                 showErrors
index 48cd931127d995b094f441250bf8baf14c525ad9..12938e82d6f351cf12d6fd0dd6b70e49c24d7c6c 100644 (file)
@@ -44,7 +44,8 @@ export function setAuthorizationHeader(services: ServiceRepository, token: strin
 }
 
 export function removeAuthorizationHeader(services: ServiceRepository) {
 }
 
 export function removeAuthorizationHeader(services: ServiceRepository) {
-    delete services.apiClient.defaults.headers.common;
+    services.apiClient.defaults.headers.common = {};
+
     services.keepWebdavClient.setAuthorization(undefined);
     services.apiWebdavClient.setAuthorization(undefined);
 }
     services.keepWebdavClient.setAuthorization(undefined);
     services.apiWebdavClient.setAuthorization(undefined);
 }
index 5a0364ebf9000162ef7544dccd0607420ba2b290..259478fdc42c8478d431929d66dd4244ece98c6d 100644 (file)
@@ -33,7 +33,9 @@ describe("AuthMiddleware", () => {
 
     it("handles LOGOUT action", () => {
         localStorage.setItem(API_TOKEN_KEY, 'someToken');
 
     it("handles LOGOUT action", () => {
         localStorage.setItem(API_TOKEN_KEY, 'someToken');
-        window.location.assign = jest.fn();
+        Object.defineProperty(window, 'location', {
+            value: { assign: jest.fn(), href: 'http://localhost', protocol: 'http:', host: 'localhost'}
+          });
         const next = jest.fn();
         const middleware = authMiddleware(services)(store)(next);
         middleware(authActions.LOGOUT({deleteLinkData: false, preservePath: false}));
         const next = jest.fn();
         const middleware = authMiddleware(services)(store)(next);
         middleware(authActions.LOGOUT({deleteLinkData: false, preservePath: false}));
index 808ca822c0af7473b833e9fa0fece0752f0d2a11..6dcd6c3aa52a0bec42e4a592bda778c02a2e055a 100644 (file)
@@ -23,7 +23,9 @@ export const closeBanner = () =>
         dispatch(bannerReducerActions.CLOSE_BANNER());
     };
 
         dispatch(bannerReducerActions.CLOSE_BANNER());
     };
 
-export default {
+const bannerActions = {
     openBanner,
     closeBanner
 };
     openBanner,
     closeBanner
 };
+
+export default bannerActions;
\ No newline at end of file
index 623c45088cc83922f137896effccf4969e19dc0b..a8b8e4089c622ab9f0a8aba7d86fbb072db39b24 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuKind } from 'views-components/context-menu/context-menu';
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { resourceUuidToContextMenuKind } from './context-menu-actions';
 import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
 import { resourceUuidToContextMenuKind } from './context-menu-actions';
 import configureStore from 'redux-mock-store';
 import thunk from 'redux-thunk';
index 464314877ff645328d838f2ddbbb1e4cd2a99ec7..4c31fa4e94b3878631ecadda72b0b832c3e97298 100644 (file)
@@ -4,7 +4,7 @@
 
 import { unionize, ofType, UnionOf } from "common/unionize";
 import { ContextMenuPosition } from "./context-menu-reducer";
 
 import { unionize, ofType, UnionOf } from "common/unionize";
 import { ContextMenuPosition } from "./context-menu-reducer";
-import { ContextMenuKind } from "views-components/context-menu/context-menu";
+import { ContextMenuKind } from "views-components/context-menu/menu-item-sort";
 import { Dispatch } from "redux";
 import { RootState } from "store/store";
 import { getResource, getResourceWithEditableStatus } from "../resources/resources";
 import { Dispatch } from "redux";
 import { RootState } from "store/store";
 import { getResource, getResourceWithEditableStatus } from "../resources/resources";
index da454ed77dbc9561116cf43c6de5fc25ae8edc95..3f4d7a802fbb288ec21f4361cff0b3f267bad596 100644 (file)
@@ -10,7 +10,7 @@ import { checkFavorite } from "./favorites-reducer";
 import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
 import { ServiceRepository } from "services/services";
 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
 import { ServiceRepository } from "services/services";
 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set"; 
 import { addDisabledButton, removeDisabledButton } from "store/multiselect/multiselect-actions";
 import { loadFavoritesTree} from "store/side-panel-tree/side-panel-tree-actions";
 
 import { addDisabledButton, removeDisabledButton } from "store/multiselect/multiselect-actions";
 import { loadFavoritesTree} from "store/side-panel-tree/side-panel-tree-actions";
 
@@ -29,7 +29,7 @@ export const toggleFavorite = (resource: { uuid: string; name: string }) =>
             return Promise.reject("No user");
         }
         dispatch(progressIndicatorActions.START_WORKING("toggleFavorite"));
             return Promise.reject("No user");
         }
         dispatch(progressIndicatorActions.START_WORKING("toggleFavorite"));
-        dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.ADD_TO_FAVORITES))
+        dispatch<any>(addDisabledButton(ContextMenuActionNames.ADD_TO_FAVORITES))
         dispatch(favoritesActions.TOGGLE_FAVORITE({ resourceUuid: resource.uuid }));
         const isFavorite = checkFavorite(resource.uuid, getState().favorites);
         dispatch(snackbarActions.OPEN_SNACKBAR({
         dispatch(favoritesActions.TOGGLE_FAVORITE({ resourceUuid: resource.uuid }));
         const isFavorite = checkFavorite(resource.uuid, getState().favorites);
         dispatch(snackbarActions.OPEN_SNACKBAR({
@@ -54,7 +54,7 @@ export const toggleFavorite = (resource: { uuid: string; name: string }) =>
                     hideDuration: 2000,
                     kind: SnackbarKind.SUCCESS
                 }));
                     hideDuration: 2000,
                     kind: SnackbarKind.SUCCESS
                 }));
-                dispatch<any>(removeDisabledButton(MultiSelectMenuActionNames.ADD_TO_FAVORITES))
+                dispatch<any>(removeDisabledButton(ContextMenuActionNames.ADD_TO_FAVORITES))
                 dispatch(progressIndicatorActions.STOP_WORKING("toggleFavorite"));
                 dispatch<any>(loadFavoritesTree())
             })
                 dispatch(progressIndicatorActions.STOP_WORKING("toggleFavorite"));
                 dispatch<any>(loadFavoritesTree())
             })
index 28da3cf95ab968c62e6d6dd06f956f31e231b97f..2d1ab2e40d450eb49788eb7f79ec0bb3b995cfbc 100644 (file)
@@ -19,7 +19,7 @@ export const openInNewTabAction = (resource: any) => (dispatch: Dispatch, getSta
 };
 
 export const copyToClipboardAction = (resources: Array<any>) => (dispatch: Dispatch, getState: () => RootState) => {
 };
 
 export const copyToClipboardAction = (resources: Array<any>) => (dispatch: Dispatch, getState: () => RootState) => {
-    // Copy to clipboard omits token to avoid accidental sharing
+    // Copy link to clipboard omits token to avoid accidental sharing
 
     let url = getNavUrl(resources[0].uuid, getState().auth, false);
     let wasCopied;
 
     let url = getNavUrl(resources[0].uuid, getState().auth, false);
     let wasCopied;
index 82c267c7a06411c4371586b4075716cd4afa6aad..60a477dd05bc036bb3b7415ad551c36c4c5ef717 100644 (file)
@@ -189,12 +189,13 @@ export const initProcessPanelFilters = processPanelActions.SET_PROCESS_PANEL_FIL
 ]);
 
 export const formatInputData = (inputs: CommandInputParameter[], auth: AuthState): ProcessIOParameter[] => {
 ]);
 
 export const formatInputData = (inputs: CommandInputParameter[], auth: AuthState): ProcessIOParameter[] => {
-    return inputs.map(input => {
-        return {
-            id: getIOParamId(input),
-            label: input.label || "",
-            value: getIOParamDisplayValue(auth, input),
-        };
+    return inputs.flatMap((input): ProcessIOParameter[] => {
+        const processValues = getIOParamDisplayValue(auth, input);
+        return processValues.map((thisValue, i) => ({
+            id: i === 0 ? getIOParamId(input) : "",
+            label: i === 0 ? input.label || "" : "",
+            value: thisValue,
+        }));
     });
 };
 
     });
 };
 
@@ -204,11 +205,12 @@ export const formatOutputData = (
     pdh: string | undefined,
     auth: AuthState
 ): ProcessIOParameter[] => {
     pdh: string | undefined,
     auth: AuthState
 ): ProcessIOParameter[] => {
-    return definitions.map(output => {
-        return {
-            id: getIOParamId(output),
-            label: output.label || "",
-            value: getIOParamDisplayValue(auth, Object.assign(output, { value: values[getIOParamId(output)] || [] }), pdh),
-        };
+    return definitions.flatMap((output): ProcessIOParameter[] => {
+        const processValues = getIOParamDisplayValue(auth, Object.assign(output, { value: values[getIOParamId(output)] || [] }), pdh);
+        return processValues.map((thisValue, i) => ({
+            id: i === 0 ? getIOParamId(output) : "",
+            label: i === 0 ? output.label || "" : "",
+            value: thisValue,
+        }));
     });
 };
     });
 };
index 89f0576d8660a45f80aa197bbcb85adc83da0645..e8d03dfcd76ef6df186a9ab78acc87b692e09178 100644 (file)
@@ -35,7 +35,7 @@ import { updatePublicFavorites } from "store/public-favorites/public-favorites-a
 import { selectedFieldsOfGroup } from "models/group";
 import { defaultCollectionSelectedFields } from "models/collection";
 import { containerRequestFieldsNoMounts } from "models/container-request";
 import { selectedFieldsOfGroup } from "models/group";
 import { defaultCollectionSelectedFields } from "models/collection";
 import { containerRequestFieldsNoMounts } from "models/container-request";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set"; 
 import { removeDisabledButton } from "store/multiselect/multiselect-actions";
 import { dataExplorerActions } from "store/data-explorer/data-explorer-action";
 
 import { removeDisabledButton } from "store/multiselect/multiselect-actions";
 import { dataExplorerActions } from "store/data-explorer/data-explorer-action";
 
@@ -82,7 +82,7 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService
             } finally {
                 if (!background) { 
                     api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId()));
             } finally {
                 if (!background) { 
                     api.dispatch(progressIndicatorActions.PERSIST_STOP_WORKING(this.getId()));
-                    api.dispatch<any>(removeDisabledButton(MultiSelectMenuActionNames.MOVE_TO_TRASH))
+                    api.dispatch<any>(removeDisabledButton(ContextMenuActionNames.MOVE_TO_TRASH))
                 }
             }
         }
                 }
             }
         }
index 28e934d1f85ff988f5d5fc73df6d2faadca3d450..cd72e351964f0d110d95127459e90d8d972a862f 100644 (file)
@@ -7,11 +7,11 @@ import { ServiceRepository } from "services/services";
 import { projectPanelActions } from "store/project-panel/project-panel-action-bind";
 import { loadResource } from "store/resources/resources-actions";
 import { RootState } from "store/store";
 import { projectPanelActions } from "store/project-panel/project-panel-action-bind";
 import { loadResource } from "store/resources/resources-actions";
 import { RootState } from "store/store";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set"; 
 import { addDisabledButton, removeDisabledButton } from "store/multiselect/multiselect-actions";
 
 export const freezeProject = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
 import { addDisabledButton, removeDisabledButton } from "store/multiselect/multiselect-actions";
 
 export const freezeProject = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-    dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.FREEZE_PROJECT))
+    dispatch<any>(addDisabledButton(ContextMenuActionNames.FREEZE_PROJECT))
     const userUUID = getState().auth.user!.uuid;
     
     const updatedProject = await services.projectService.update(uuid, {
     const userUUID = getState().auth.user!.uuid;
     
     const updatedProject = await services.projectService.update(uuid, {
@@ -20,18 +20,18 @@ export const freezeProject = (uuid: string) => async (dispatch: Dispatch, getSta
     
     dispatch(projectPanelActions.REQUEST_ITEMS());
     dispatch<any>(loadResource(uuid, false));
     
     dispatch(projectPanelActions.REQUEST_ITEMS());
     dispatch<any>(loadResource(uuid, false));
-    dispatch<any>(removeDisabledButton(MultiSelectMenuActionNames.FREEZE_PROJECT))
+    dispatch<any>(removeDisabledButton(ContextMenuActionNames.FREEZE_PROJECT))
     return updatedProject;
 };
 
 export const unfreezeProject = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
     return updatedProject;
 };
 
 export const unfreezeProject = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-    dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.FREEZE_PROJECT))
+    dispatch<any>(addDisabledButton(ContextMenuActionNames.FREEZE_PROJECT))
     const updatedProject = await services.projectService.update(uuid, {
         frozenByUuid: null,
     });
 
     dispatch(projectPanelActions.REQUEST_ITEMS());
     dispatch<any>(loadResource(uuid, false));
     const updatedProject = await services.projectService.update(uuid, {
         frozenByUuid: null,
     });
 
     dispatch(projectPanelActions.REQUEST_ITEMS());
     dispatch<any>(loadResource(uuid, false));
-    dispatch<any>(removeDisabledButton(MultiSelectMenuActionNames.FREEZE_PROJECT))
+    dispatch<any>(removeDisabledButton(ContextMenuActionNames.FREEZE_PROJECT))
     return updatedProject;
 };
     return updatedProject;
 };
index 0f8ed6c2611c72e55fc769a2d5f4b7ab3d4c6408..b9915dbdf1357128bce3a3d2759a59cc3aacf4e3 100644 (file)
@@ -10,7 +10,7 @@ import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 import { ServiceRepository } from "services/services";
 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 import { addDisabledButton, removeDisabledButton } from "store/multiselect/multiselect-actions";
 import { ServiceRepository } from "services/services";
 import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
 import { addDisabledButton, removeDisabledButton } from "store/multiselect/multiselect-actions";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { loadPublicFavoritesTree } from "store/side-panel-tree/side-panel-tree-actions";
 
 export const publicFavoritesActions = unionize({
 import { loadPublicFavoritesTree } from "store/side-panel-tree/side-panel-tree-actions";
 
 export const publicFavoritesActions = unionize({
@@ -24,7 +24,7 @@ export type PublicFavoritesAction = UnionOf<typeof publicFavoritesActions>;
 export const togglePublicFavorite = (resource: { uuid: string; name: string }) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
         dispatch(progressIndicatorActions.START_WORKING("togglePublicFavorite"));
 export const togglePublicFavorite = (resource: { uuid: string; name: string }) =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
         dispatch(progressIndicatorActions.START_WORKING("togglePublicFavorite"));
-        dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.ADD_TO_PUBLIC_FAVORITES))
+        dispatch<any>(addDisabledButton(ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES))
         const uuidPrefix = getState().auth.config.uuidPrefix;
         const uuid = `${uuidPrefix}-j7d0g-publicfavorites`;
         dispatch(publicFavoritesActions.TOGGLE_PUBLIC_FAVORITE({ resourceUuid: resource.uuid }));
         const uuidPrefix = getState().auth.config.uuidPrefix;
         const uuid = `${uuidPrefix}-j7d0g-publicfavorites`;
         dispatch(publicFavoritesActions.TOGGLE_PUBLIC_FAVORITE({ resourceUuid: resource.uuid }));
@@ -51,7 +51,7 @@ export const togglePublicFavorite = (resource: { uuid: string; name: string }) =
                     hideDuration: 2000,
                     kind: SnackbarKind.SUCCESS
                 }));
                     hideDuration: 2000,
                     kind: SnackbarKind.SUCCESS
                 }));
-                dispatch<any>(removeDisabledButton(MultiSelectMenuActionNames.ADD_TO_PUBLIC_FAVORITES))
+                dispatch<any>(removeDisabledButton(ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES))
                 dispatch(progressIndicatorActions.STOP_WORKING("togglePublicFavorite"));
                 dispatch<any>(loadPublicFavoritesTree())
             })
                 dispatch(progressIndicatorActions.STOP_WORKING("togglePublicFavorite"));
                 dispatch<any>(loadPublicFavoritesTree())
             })
index c822cece8742856330a7d7734cd655cae923aec7..b0fed19d0d5bf1676e34116de8591a03a3a1007b 100644 (file)
@@ -27,7 +27,7 @@ import { serializeResourceTypeFilters } from 'store//resource-type-filters/resou
 import { getDataExplorerColumnFilters } from 'store/data-explorer/data-explorer-middleware-service';
 import { joinFilters } from 'services/api/filter-builder';
 import { CollectionResource } from "models/collection";
 import { getDataExplorerColumnFilters } from 'store/data-explorer/data-explorer-middleware-service';
 import { joinFilters } from 'services/api/filter-builder';
 import { CollectionResource } from "models/collection";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { removeDisabledButton } from "store/multiselect/multiselect-actions";
 export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
 import { removeDisabledButton } from "store/multiselect/multiselect-actions";
 export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
     constructor(private services: ServiceRepository, id: string) {
@@ -85,7 +85,7 @@ export class TrashPanelMiddlewareService extends DataExplorerMiddlewareService {
             }));
             api.dispatch(couldNotFetchTrashContents());
         }
             }));
             api.dispatch(couldNotFetchTrashContents());
         }
-        api.dispatch<any>(removeDisabledButton(MultiSelectMenuActionNames.MOVE_TO_TRASH))
+        api.dispatch<any>(removeDisabledButton(ContextMenuActionNames.MOVE_TO_TRASH))
     }
 }
 
     }
 }
 
index f4e3d3f0c4de225406cff2a8c4b6e1c9eed61fe9..b6740bf9aef64156c9643eb8f90691236239a8de 100644 (file)
@@ -13,7 +13,7 @@ import { sharedWithMePanelActions } from "store/shared-with-me-panel/shared-with
 import { ResourceKind } from "models/resource";
 import { navigateTo, navigateToTrash } from "store/navigation/navigation-action";
 import { matchCollectionRoute, matchSharedWithMeRoute } from "routes/routes";
 import { ResourceKind } from "models/resource";
 import { navigateTo, navigateToTrash } from "store/navigation/navigation-action";
 import { matchCollectionRoute, matchSharedWithMeRoute } from "routes/routes";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { addDisabledButton } from "store/multiselect/multiselect-actions";
 
 export const toggleProjectTrashed =
 import { addDisabledButton } from "store/multiselect/multiselect-actions";
 
 export const toggleProjectTrashed =
@@ -22,7 +22,7 @@ export const toggleProjectTrashed =
             let errorMessage = "";
             let successMessage = "";
             let untrashedResource;
             let errorMessage = "";
             let successMessage = "";
             let untrashedResource;
-            dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.MOVE_TO_TRASH))
+            dispatch<any>(addDisabledButton(ContextMenuActionNames.MOVE_TO_TRASH))
             try {
                 if (isTrashed) {
                     errorMessage = "Could not restore project from trash";
             try {
                 if (isTrashed) {
                     errorMessage = "Could not restore project from trash";
@@ -77,7 +77,7 @@ export const toggleCollectionTrashed =
         async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
             let errorMessage = "";
             let successMessage = "";
         async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
             let errorMessage = "";
             let successMessage = "";
-            dispatch<any>(addDisabledButton(MultiSelectMenuActionNames.MOVE_TO_TRASH))
+            dispatch<any>(addDisabledButton(ContextMenuActionNames.MOVE_TO_TRASH))
             try {
                 if (isTrashed) {
                     const { location } = getState().router;
             try {
                 if (isTrashed) {
                     const { location } = getState().router;
index fbba02aeb4d8e4127da10f58f12723d7672ef5ae..b594bf0803eaab110c860aa6620b48333f30adfb 100644 (file)
@@ -4,6 +4,6 @@
 
 export const ERROR_MESSAGE = 'This field is required.';
 
 
 export const ERROR_MESSAGE = 'This field is required.';
 
-export const require: any = (value: string) => {
+export const fieldRequire: any = (value: string) => {
     return value && value.length > 0 ? undefined : ERROR_MESSAGE;
 };
     return value && value.length > 0 ? undefined : ERROR_MESSAGE;
 };
index 87a4c1f57e2523fee923b41619b288c929fc99f1..73bc4460dc3653807e881b08b8072cfc643746d0 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { require } from './require';
+import { fieldRequire } from './require';
 import { maxLength } from './max-length';
 import { isRsaKey } from './is-rsa-key';
 import { isRemoteHost } from "./is-remote-host";
 import { maxLength } from './max-length';
 import { isRsaKey } from './is-rsa-key';
 import { isRemoteHost } from "./is-remote-host";
@@ -11,35 +11,35 @@ import { validFilePath, validName, validNameAllowSlash } from "./valid-name";
 export const TAG_KEY_VALIDATION = [maxLength(255)];
 export const TAG_VALUE_VALIDATION = [maxLength(255)];
 
 export const TAG_KEY_VALIDATION = [maxLength(255)];
 export const TAG_VALUE_VALIDATION = [maxLength(255)];
 
-export const PROJECT_NAME_VALIDATION = [require, validName, maxLength(255)];
-export const PROJECT_NAME_VALIDATION_ALLOW_SLASH = [require, validNameAllowSlash, maxLength(255)];
+export const PROJECT_NAME_VALIDATION = [fieldRequire, validName, maxLength(255)];
+export const PROJECT_NAME_VALIDATION_ALLOW_SLASH = [fieldRequire, validNameAllowSlash, maxLength(255)];
 
 
-export const COLLECTION_NAME_VALIDATION = [require, validName, maxLength(255)];
-export const COLLECTION_NAME_VALIDATION_ALLOW_SLASH = [require, validNameAllowSlash, maxLength(255)];
+export const COLLECTION_NAME_VALIDATION = [fieldRequire, validName, maxLength(255)];
+export const COLLECTION_NAME_VALIDATION_ALLOW_SLASH = [fieldRequire, validNameAllowSlash, maxLength(255)];
 export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
 export const COLLECTION_DESCRIPTION_VALIDATION = [maxLength(255)];
-export const COLLECTION_PROJECT_VALIDATION = [require];
+export const COLLECTION_PROJECT_VALIDATION = [fieldRequire];
 
 
-export const COPY_NAME_VALIDATION = [require, maxLength(255)];
-export const COPY_FILE_VALIDATION = [require];
-export const RENAME_FILE_VALIDATION = [require, validFilePath];
+export const COPY_NAME_VALIDATION = [fieldRequire, maxLength(255)];
+export const COPY_FILE_VALIDATION = [fieldRequire];
+export const RENAME_FILE_VALIDATION = [fieldRequire, validFilePath];
 
 
-export const MOVE_TO_VALIDATION = [require];
+export const MOVE_TO_VALIDATION = [fieldRequire];
 
 
-export const PROCESS_NAME_VALIDATION = [require, maxLength(255)];
+export const PROCESS_NAME_VALIDATION = [fieldRequire, maxLength(255)];
 export const PROCESS_DESCRIPTION_VALIDATION = [maxLength(255)];
 
 export const PROCESS_DESCRIPTION_VALIDATION = [maxLength(255)];
 
-export const REPOSITORY_NAME_VALIDATION = [require, maxLength(255)];
+export const REPOSITORY_NAME_VALIDATION = [fieldRequire, maxLength(255)];
 
 
-export const USER_EMAIL_VALIDATION = [require, maxLength(255)];
+export const USER_EMAIL_VALIDATION = [fieldRequire, maxLength(255)];
 export const PROFILE_EMAIL_VALIDATION = [maxLength(255)];
 export const PROFILE_URL_VALIDATION = [maxLength(255)];
 export const USER_LENGTH_VALIDATION = [maxLength(255)];
 
 export const PROFILE_EMAIL_VALIDATION = [maxLength(255)];
 export const PROFILE_URL_VALIDATION = [maxLength(255)];
 export const USER_LENGTH_VALIDATION = [maxLength(255)];
 
-export const SSH_KEY_PUBLIC_VALIDATION = [require, isRsaKey, maxLength(1024)];
-export const SSH_KEY_NAME_VALIDATION = [require, maxLength(255)];
+export const SSH_KEY_PUBLIC_VALIDATION = [fieldRequire, isRsaKey, maxLength(1024)];
+export const SSH_KEY_NAME_VALIDATION = [fieldRequire, maxLength(255)];
 
 
-export const SITE_MANAGER_REMOTE_HOST_VALIDATION = [require, isRemoteHost, maxLength(255)];
+export const SITE_MANAGER_REMOTE_HOST_VALIDATION = [fieldRequire, isRemoteHost, maxLength(255)];
 
 
-export const MY_ACCOUNT_VALIDATION = [require];
+export const MY_ACCOUNT_VALIDATION = [fieldRequire];
 
 
-export const CHOOSE_VM_VALIDATION = [require];
+export const CHOOSE_VM_VALIDATION = [fieldRequire];
index c9b9e2b83fe009f7473a82f63e02c414e46859f2..28084ed1c15e6c2af148ad0052c1cfe2b62ba3c1 100644 (file)
@@ -14,15 +14,12 @@ describe('<AutoLogoutComponent />', () => {
     const sessionIdleTimeout = 300;
     const lastWarningDuration = 60;
     const eventListeners = {};
     const sessionIdleTimeout = 300;
     const lastWarningDuration = 60;
     const eventListeners = {};
-    jest.useFakeTimers();
 
 
-    beforeAll(() => {
+    beforeEach(() => {
+        jest.useFakeTimers();
         window.addEventListener = jest.fn((event, cb) => {
             eventListeners[event] = cb;
         });
         window.addEventListener = jest.fn((event, cb) => {
             eventListeners[event] = cb;
         });
-    });
-
-    beforeEach(() => {
         props = {
             sessionIdleTimeout: sessionIdleTimeout,
             lastWarningDuration: lastWarningDuration,
         props = {
             sessionIdleTimeout: sessionIdleTimeout,
             lastWarningDuration: lastWarningDuration,
index a26b9fe3ee0ad65ab2cc1d6b018de9b5783e90fb..c5bd1d737e8160494b9fe765ba32d79e2a814783 100644 (file)
@@ -10,7 +10,7 @@ import {
 import { RootState } from "store/store";
 import { Dispatch } from "redux";
 import { collectionPanelFilesAction } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 import { RootState } from "store/store";
 import { Dispatch } from "redux";
 import { collectionPanelFilesAction } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
-import { ContextMenuKind } from "../context-menu/context-menu";
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { openContextMenu, openCollectionFilesContextMenu } from 'store/context-menu/context-menu-actions';
 import { openUploadCollectionFilesDialog } from 'store/collections/collection-upload-actions';
 import { ResourceKind } from "models/resource";
 import { openContextMenu, openCollectionFilesContextMenu } from 'store/context-menu/context-menu-actions';
 import { openUploadCollectionFilesDialog } from 'store/collections/collection-upload-actions';
 import { ResourceKind } from "models/resource";
index 8e75d22f6714d97853f2d7061aa9167dc793a072..4a01864f39d706bda248814cde9b563e308bd8b9 100644 (file)
@@ -7,27 +7,27 @@ import {
     openApiClientAuthorizationRemoveDialog,
 } from "store/api-client-authorizations/api-client-authorizations-actions";
 import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
     openApiClientAuthorizationRemoveDialog,
 } from "store/api-client-authorizations/api-client-authorizations-actions";
 import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
-import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
+import { ContextMenuActionSet, ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from "components/icon/icon";
 
 export const apiClientAuthorizationActionSet: ContextMenuActionSet = [
     [
         {
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from "components/icon/icon";
 
 export const apiClientAuthorizationActionSet: ContextMenuActionSet = [
     [
         {
-            name: "Attributes",
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                     dispatch<any>(openApiClientAuthorizationAttributesDialog(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                     dispatch<any>(openApiClientAuthorizationAttributesDialog(resources[0].uuid));
             },
         },
         {
-            name: "API Details",
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                     dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                     dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: "Remove",
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                     dispatch<any>(openApiClientAuthorizationRemoveDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                     dispatch<any>(openApiClientAuthorizationRemoveDialog(resources[0].uuid));
index 95aec9c7c94f476be3de1aa2f595040211b8d6b6..7f8ad9e9e08da20e29be0f98be8d4118919f0e89 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuAction, ContextMenuActionSet } from "../context-menu-action-set";
+import { ContextMenuAction, ContextMenuActionSet, ContextMenuActionNames } from "../context-menu-action-set";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "store/favorites/favorites-actions";
 import {
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "store/favorites/favorites-actions";
 import {
@@ -36,7 +36,7 @@ import { ContextMenuResource } from "store/context-menu/context-menu-actions";
 
 const toggleFavoriteAction: ContextMenuAction = {
     component: ToggleFavoriteAction,
 
 const toggleFavoriteAction: ContextMenuAction = {
     component: ToggleFavoriteAction,
-    name: "ToggleFavoriteAction",
+    name: ContextMenuActionNames.ADD_TO_FAVORITES,
     execute: (dispatch, resources) => {
         for (const resource of [...resources]) {
             dispatch<any>(toggleFavorite(resource)).then(() => {
     execute: (dispatch, resources) => {
         for (const resource of [...resources]) {
             dispatch<any>(toggleFavorite(resource)).then(() => {
@@ -49,21 +49,21 @@ const commonActionSet: ContextMenuActionSet = [
     [
         {
             icon: OpenIcon,
     [
         {
             icon: OpenIcon,
-            name: "Open in new tab",
+            name: ContextMenuActionNames.OPEN_IN_NEW_TAB,
             execute: (dispatch, resources) => {
                 dispatch<any>(openInNewTabAction(resources[0]));
             },
         },
         {
             icon: Link,
             execute: (dispatch, resources) => {
                 dispatch<any>(openInNewTabAction(resources[0]));
             },
         },
         {
             icon: Link,
-            name: "Copy to clipboard",
+            name: ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
             execute: (dispatch, resources) => {
                 dispatch<any>(copyToClipboardAction(resources));
             },
         },
         {
             icon: CopyIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(copyToClipboardAction(resources));
             },
         },
         {
             icon: CopyIcon,
-            name: "Make a copy",
+            name: ContextMenuActionNames.MAKE_A_COPY,
             execute: (dispatch, resources) => {
                 if (resources[0].fromContextMenu || resources.length === 1) dispatch<any>(openCollectionCopyDialog(resources[0]));
                 else dispatch<any>(openMultiCollectionCopyDialog(resources[0]));
             execute: (dispatch, resources) => {
                 if (resources[0].fromContextMenu || resources.length === 1) dispatch<any>(openCollectionCopyDialog(resources[0]));
                 else dispatch<any>(openMultiCollectionCopyDialog(resources[0]));
@@ -71,14 +71,14 @@ const commonActionSet: ContextMenuActionSet = [
         },
         {
             icon: DetailsIcon,
         },
         {
             icon: DetailsIcon,
-            name: "View details",
+            name: ContextMenuActionNames.VIEW_DETAILS,
             execute: dispatch => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
             execute: dispatch => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
-            name: "API Details",
+            name: ContextMenuActionNames.API_DETAILS,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
@@ -92,7 +92,7 @@ export const readOnlyCollectionActionSet: ContextMenuActionSet = [
         toggleFavoriteAction,
         {
             icon: FolderSharedIcon,
         toggleFavoriteAction,
         {
             icon: FolderSharedIcon,
-            name: "Open with 3rd party client",
+            name: ContextMenuActionNames.OPEN_WITH_3RD_PARTY_CLIENT,
             execute: (dispatch, resources) => {
                 dispatch<any>(openWebDavS3InfoDialog(resources[0].uuid));
             },
             execute: (dispatch, resources) => {
                 dispatch<any>(openWebDavS3InfoDialog(resources[0].uuid));
             },
@@ -105,26 +105,26 @@ export const collectionActionSet: ContextMenuActionSet = [
         ...readOnlyCollectionActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             icon: RenameIcon,
         ...readOnlyCollectionActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             icon: RenameIcon,
-            name: "Edit collection",
+            name: ContextMenuActionNames.EDIT_COLLECTION,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionUpdateDialog(resources[0]));
             },
         },
         {
             icon: ShareIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionUpdateDialog(resources[0]));
             },
         },
         {
             icon: ShareIcon,
-            name: "Share",
+            name: ContextMenuActionNames.SHARE,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSharingDialog(resources[0].uuid));
             },
         },
         {
             icon: MoveToIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSharingDialog(resources[0].uuid));
             },
         },
         {
             icon: MoveToIcon,
-            name: "Move to",
+            name: ContextMenuActionNames.MOVE_TO,
             execute: (dispatch, resources) => dispatch<any>(openMoveCollectionDialog(resources[0])),
         },
         {
             component: ToggleTrashAction,
             execute: (dispatch, resources) => dispatch<any>(openMoveCollectionDialog(resources[0])),
         },
         {
             component: ToggleTrashAction,
-            name: "ToggleTrashAction",
+            name: ContextMenuActionNames.MOVE_TO_TRASH,
             execute: (dispatch, resources: ContextMenuResource[]) => {
                 for (const resource of [...resources]) {
                     dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
             execute: (dispatch, resources: ContextMenuResource[]) => {
                 for (const resource of [...resources]) {
                     dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
@@ -139,7 +139,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [
         ...collectionActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             component: TogglePublicFavoriteAction,
         ...collectionActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             component: TogglePublicFavoriteAction,
-            name: "TogglePublicFavoriteAction",
+            name: ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES,
             execute: (dispatch, resources) => {
                 for (const resource of [...resources]) {
                     dispatch<any>(togglePublicFavorite(resource)).then(() => {
             execute: (dispatch, resources) => {
                 for (const resource of [...resources]) {
                     dispatch<any>(togglePublicFavorite(resource)).then(() => {
@@ -156,7 +156,7 @@ export const oldCollectionVersionActionSet: ContextMenuActionSet = [
         ...commonActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             icon: RestoreVersionIcon,
         ...commonActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             icon: RestoreVersionIcon,
-            name: "Restore version",
+            name: ContextMenuActionNames.RESTORE_VERSION,
             execute: (dispatch, resources) => {
                 for (const resource of [...resources]) {
                     dispatch<any>(openRestoreCollectionVersionDialog(resource.uuid));
             execute: (dispatch, resources) => {
                 for (const resource of [...resources]) {
                     dispatch<any>(openRestoreCollectionVersionDialog(resource.uuid));
index 80deb37cade38c6768421f80187ee7ac6f5a0fe7..a117cbc1b07e3ebbb030176aabda84c278a29647 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuAction, ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
+import { ContextMenuAction, ContextMenuActionSet, ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { collectionPanelFilesAction, openMultipleFilesRemoveDialog } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 import {
     openCollectionPartialCopyMultipleToNewCollectionDialog,
 import { collectionPanelFilesAction, openMultipleFilesRemoveDialog } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 import {
     openCollectionPartialCopyMultipleToNewCollectionDialog,
@@ -14,14 +14,14 @@ import { FileCopyIcon, FileMoveIcon, RemoveIcon, SelectAllIcon, SelectNoneIcon }
 
 const copyActions: ContextMenuAction[] = [
     {
 
 const copyActions: ContextMenuAction[] = [
     {
-        name: "Copy selected into new collection",
+        name: ContextMenuActionNames.COPY_SELECTED_INTO_NEW_COLLECTION,
         icon: FileCopyIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialCopyMultipleToNewCollectionDialog());
         }
     },
     {
         icon: FileCopyIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialCopyMultipleToNewCollectionDialog());
         }
     },
     {
-        name: "Copy selected into existing collection",
+        name: ContextMenuActionNames.COPY_SELECTED_INTO_EXISTING_COLLECTION,
         icon: FileCopyIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialCopyMultipleToExistingCollectionDialog());
         icon: FileCopyIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialCopyMultipleToExistingCollectionDialog());
@@ -32,7 +32,7 @@ const copyActions: ContextMenuAction[] = [
 const copyActionsMultiple: ContextMenuAction[] = [
     ...copyActions,
     {
 const copyActionsMultiple: ContextMenuAction[] = [
     ...copyActions,
     {
-        name: "Copy selected into separate collections",
+        name: ContextMenuActionNames.COPY_SELECTED_INTO_SEPARATE_COLLECTIONS,
         icon: FileCopyIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialCopyToSeparateCollectionsDialog());
         icon: FileCopyIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialCopyToSeparateCollectionsDialog());
@@ -42,14 +42,14 @@ const copyActionsMultiple: ContextMenuAction[] = [
 
 const moveActions: ContextMenuAction[] = [
     {
 
 const moveActions: ContextMenuAction[] = [
     {
-        name: "Move selected into new collection",
+        name: ContextMenuActionNames.MOVE_SELECTED_INTO_NEW_COLLECTION,
         icon: FileMoveIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialMoveMultipleToNewCollectionDialog());
         }
     },
     {
         icon: FileMoveIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialMoveMultipleToNewCollectionDialog());
         }
     },
     {
-        name: "Move selected into existing collection",
+        name: ContextMenuActionNames.MOVE_SELECTED_INTO_EXISTING_COLLECTION,
         icon: FileMoveIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialMoveMultipleToExistingCollectionDialog());
         icon: FileMoveIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialMoveMultipleToExistingCollectionDialog());
@@ -60,7 +60,7 @@ const moveActions: ContextMenuAction[] = [
 const moveActionsMultiple: ContextMenuAction[] = [
     ...moveActions,
     {
 const moveActionsMultiple: ContextMenuAction[] = [
     ...moveActions,
     {
-        name: "Move selected into separate collections",
+        name: ContextMenuActionNames.MOVE_SELECTED_INTO_SEPARATE_COLLECTIONS,
         icon: FileMoveIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialMoveToSeparateCollectionsDialog());
         icon: FileMoveIcon,
         execute: dispatch => {
             dispatch<any>(openCollectionPartialMoveToSeparateCollectionsDialog());
@@ -70,14 +70,14 @@ const moveActionsMultiple: ContextMenuAction[] = [
 
 const selectActions: ContextMenuAction[] = [
     {
 
 const selectActions: ContextMenuAction[] = [
     {
-        name: "Select all",
+        name: ContextMenuActionNames.SELECT_ALL,
         icon: SelectAllIcon,
         execute: dispatch => {
             dispatch(collectionPanelFilesAction.SELECT_ALL_COLLECTION_FILES());
         }
     },
     {
         icon: SelectAllIcon,
         execute: dispatch => {
             dispatch(collectionPanelFilesAction.SELECT_ALL_COLLECTION_FILES());
         }
     },
     {
-        name: "Unselect all",
+        name: ContextMenuActionNames.UNSELECT_ALL,
         icon: SelectNoneIcon,
         execute: dispatch => {
             dispatch(collectionPanelFilesAction.UNSELECT_ALL_COLLECTION_FILES());
         icon: SelectNoneIcon,
         execute: dispatch => {
             dispatch(collectionPanelFilesAction.UNSELECT_ALL_COLLECTION_FILES());
@@ -86,7 +86,7 @@ const selectActions: ContextMenuAction[] = [
 ];
 
 const removeAction: ContextMenuAction = {
 ];
 
 const removeAction: ContextMenuAction = {
-    name: "Remove selected",
+    name: ContextMenuActionNames.REMOVE_SELECTED,
     icon: RemoveIcon,
     execute: dispatch => {
         dispatch(openMultipleFilesRemoveDialog());
     icon: RemoveIcon,
     execute: dispatch => {
         dispatch(openMultipleFilesRemoveDialog());
index fb158a826d58904dd055d8b5b1e4b22aa3f2e469..68edb13433f48a0cc8a5383f88f26cf9ae66e464 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from "../context-menu-action-set";
+import { ContextMenuActionSet, ContextMenuActionNames } from "../context-menu-action-set";
 import { FileCopyIcon, FileMoveIcon, RemoveIcon, RenameIcon } from "components/icon/icon";
 import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
 import { openFileRemoveDialog, openRenameFileDialog } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 import { FileCopyIcon, FileMoveIcon, RemoveIcon, RenameIcon } from "components/icon/icon";
 import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
 import { openFileRemoveDialog, openRenameFileDialog } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
@@ -20,14 +20,14 @@ import {
 export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [
     [
         {
 export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [
     [
         {
-            name: "Copy item into new collection",
+            name: ContextMenuActionNames.COPY_ITEM_INTO_NEW_COLLECTION,
             icon: FileCopyIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialCopyToNewCollectionDialog(resources[0]));
             },
         },
         {
             icon: FileCopyIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialCopyToNewCollectionDialog(resources[0]));
             },
         },
         {
-            name: "Copy item into existing collection",
+            name: ContextMenuActionNames.COPY_ITEM_INTO_EXISTING_COLLECTION,
             icon: FileCopyIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialCopyToExistingCollectionDialog(resources[0]));
             icon: FileCopyIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialCopyToExistingCollectionDialog(resources[0]));
@@ -35,12 +35,14 @@ export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [
         },
         {
             component: CollectionFileViewerAction,
         },
         {
             component: CollectionFileViewerAction,
+            name: ContextMenuActionNames.OPEN_IN_NEW_TAB,
             execute: () => {
                 return;
             },
         },
         {
             component: CollectionCopyToClipboardAction,
             execute: () => {
                 return;
             },
         },
         {
             component: CollectionCopyToClipboardAction,
+            name: ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
             execute: () => {
                 return;
             },
             execute: () => {
                 return;
             },
@@ -52,6 +54,7 @@ export const readOnlyCollectionFileItemActionSet: ContextMenuActionSet = [
     [
         {
             component: DownloadCollectionFileAction,
     [
         {
             component: DownloadCollectionFileAction,
+            name: ContextMenuActionNames.DOWNLOAD,
             execute: () => {
                 return;
             },
             execute: () => {
                 return;
             },
@@ -63,21 +66,21 @@ export const readOnlyCollectionFileItemActionSet: ContextMenuActionSet = [
 const writableActionSet: ContextMenuActionSet = [
     [
         {
 const writableActionSet: ContextMenuActionSet = [
     [
         {
-            name: "Move item into new collection",
+            name: ContextMenuActionNames.MOVE_ITEM_INTO_NEW_COLLECTION,
             icon: FileMoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialMoveToNewCollectionDialog(resources[0]));
             },
         },
         {
             icon: FileMoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialMoveToNewCollectionDialog(resources[0]));
             },
         },
         {
-            name: "Move item into existing collection",
+            name: ContextMenuActionNames.MOVE_ITEM_INTO_EXISTING_COLLECTION,
             icon: FileMoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialMoveToExistingCollectionDialog(resources[0]));
             },
         },
         {
             icon: FileMoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCollectionPartialMoveToExistingCollectionDialog(resources[0]));
             },
         },
         {
-            name: "Rename",
+            name: ContextMenuActionNames.RENAME,
             icon: RenameIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(
             icon: RenameIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(
@@ -90,7 +93,7 @@ const writableActionSet: ContextMenuActionSet = [
             },
         },
         {
             },
         },
         {
-            name: "Remove",
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openFileRemoveDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openFileRemoveDialog(resources[0].uuid));
index 1e31d11c800742eac41659e502513aeb39d4d86f..b457efdf9a58faa9838dc7fa8b75255023dec483 100644 (file)
@@ -2,12 +2,12 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
+import { ContextMenuActionSet, ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { collectionPanelFilesAction } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 import { SelectAllIcon } from "components/icon/icon";
 
 export const collectionFilesNotSelectedActionSet: ContextMenuActionSet = [[{
 import { collectionPanelFilesAction } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 import { SelectAllIcon } from "components/icon/icon";
 
 export const collectionFilesNotSelectedActionSet: ContextMenuActionSet = [[{
-    name: "Select all",
+    name: ContextMenuActionNames.SELECT_ALL,
     icon: SelectAllIcon,
     execute: dispatch => {
         dispatch(collectionPanelFilesAction.SELECT_ALL_COLLECTION_FILES());
     icon: SelectAllIcon,
     execute: dispatch => {
         dispatch(collectionPanelFilesAction.SELECT_ALL_COLLECTION_FILES());
index bdc4b07a2452658569830103a99b1a58134ca982..115eec97eb32fe1423ae6122afadb47dab2025fd 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from '../context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from '../context-menu-action-set';
 import { ToggleFavoriteAction } from '../actions/favorite-action';
 import { toggleFavorite } from 'store/favorites/favorites-actions';
 import { favoritePanelActions } from 'store/favorite-panel/favorite-panel-action';
 import { ToggleFavoriteAction } from '../actions/favorite-action';
 import { toggleFavorite } from 'store/favorites/favorites-actions';
 import { favoritePanelActions } from 'store/favorite-panel/favorite-panel-action';
@@ -11,6 +11,7 @@ export const favoriteActionSet: ContextMenuActionSet = [
     [
         {
             component: ToggleFavoriteAction,
     [
         {
             component: ToggleFavoriteAction,
+            name: ContextMenuActionNames.ADD_TO_FAVORITES,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) =>
                     dispatch<any>(toggleFavorite(resource)).then(() => {
             execute: (dispatch, resources) => {
                 resources.forEach((resource) =>
                     dispatch<any>(toggleFavorite(resource)).then(() => {
index 816583faa9f05e3c2d5291362dc12449b503dc92..2c7f164ab1fd672f0fb015b2ca125f985ab1c4aa 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { RenameIcon, AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openGroupAttributes, openRemoveGroupDialog, openGroupUpdateDialog } from 'store/groups-panel/groups-panel-actions';
 import { RenameIcon, AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openGroupAttributes, openRemoveGroupDialog, openGroupUpdateDialog } from 'store/groups-panel/groups-panel-actions';
@@ -10,28 +10,28 @@ import { openGroupAttributes, openRemoveGroupDialog, openGroupUpdateDialog } fro
 export const groupActionSet: ContextMenuActionSet = [
     [
         {
 export const groupActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Rename',
+            name: ContextMenuActionNames.RENAME,
             icon: RenameIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openGroupUpdateDialog(resources[0]))
             },
         },
         {
             icon: RenameIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openGroupUpdateDialog(resources[0]))
             },
         },
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openGroupAttributes(resources[0].uuid))
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openGroupAttributes(resources[0].uuid))
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Remove',
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openRemoveGroupDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openRemoveGroupDialog(resources[0].uuid));
index ad1ce97c2dcb8ba3ee238d7ee10b1a39809f81e7..6b9611caa0c0b72db46cd7ceb01e844c4d4addc7 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openGroupMemberAttributes, openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openGroupMemberAttributes, openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
@@ -10,21 +10,21 @@ import { openGroupMemberAttributes, openRemoveGroupMemberDialog } from 'store/gr
 export const groupMemberActionSet: ContextMenuActionSet = [
     [
         {
 export const groupMemberActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openGroupMemberAttributes(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openGroupMemberAttributes(resources[0].uuid));
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Remove',
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openRemoveGroupMemberDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openRemoveGroupMemberDialog(resources[0].uuid));
index 2957f008cd055f04fea2533e3a5e9e28ee96dfac..67ef034d6bd8c39781497254dbc214ce08581833 100644 (file)
@@ -4,27 +4,27 @@
 
 import { openKeepServiceAttributesDialog, openKeepServiceRemoveDialog } from 'store/keep-services/keep-services-actions';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 
 import { openKeepServiceAttributesDialog, openKeepServiceRemoveDialog } from 'store/keep-services/keep-services-actions';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 
 export const keepServiceActionSet: ContextMenuActionSet = [
     [
         {
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 
 export const keepServiceActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openKeepServiceAttributesDialog(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openKeepServiceAttributesDialog(resources[0].uuid));
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Remove',
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openKeepServiceRemoveDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openKeepServiceRemoveDialog(resources[0].uuid));
index 86458423c2e09e489016d3dd8d226c72dd370b8e..89356c0797f39a301c76eb9e296e4a0d1b7225b6 100644 (file)
@@ -4,27 +4,27 @@
 
 import { openLinkAttributesDialog, openLinkRemoveDialog } from 'store/link-panel/link-panel-actions';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 
 import { openLinkAttributesDialog, openLinkRemoveDialog } from 'store/link-panel/link-panel-actions';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 
 export const linkActionSet: ContextMenuActionSet = [
     [
         {
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 
 export const linkActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openLinkAttributesDialog(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openLinkAttributesDialog(resources[0].uuid));
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Remove',
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openLinkRemoveDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openLinkRemoveDialog(resources[0].uuid));
index 4b6950ee24e17b1019afb72bfdd84fc314ca3176..3ae4513107b76647ae36eacb1dfe3901b4ea7705 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { CanReadIcon, CanManageIcon, CanWriteIcon } from 'components/icon/icon';
 import { editPermissionLevel } from 'store/group-details-panel/group-details-panel-actions';
 import { PermissionLevel } from 'models/permission';
 import { CanReadIcon, CanManageIcon, CanWriteIcon } from 'components/icon/icon';
 import { editPermissionLevel } from 'store/group-details-panel/group-details-panel-actions';
 import { PermissionLevel } from 'models/permission';
@@ -10,21 +10,21 @@ import { PermissionLevel } from 'models/permission';
 export const permissionEditActionSet: ContextMenuActionSet = [
     [
         {
 export const permissionEditActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Read',
+            name: ContextMenuActionNames.READ,
             icon: CanReadIcon,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(editPermissionLevel(resource.uuid, PermissionLevel.CAN_READ)));
             },
         },
         {
             icon: CanReadIcon,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(editPermissionLevel(resource.uuid, PermissionLevel.CAN_READ)));
             },
         },
         {
-            name: 'Write',
+            name: ContextMenuActionNames.WRITE,
             icon: CanWriteIcon,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(editPermissionLevel(resource.uuid, PermissionLevel.CAN_WRITE)));
             },
         },
         {
             icon: CanWriteIcon,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(editPermissionLevel(resource.uuid, PermissionLevel.CAN_WRITE)));
             },
         },
         {
-            name: 'Manage',
+            name: ContextMenuActionNames.MANAGE,
             icon: CanManageIcon,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(editPermissionLevel(resource.uuid, PermissionLevel.CAN_MANAGE)));
             icon: CanManageIcon,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(editPermissionLevel(resource.uuid, PermissionLevel.CAN_MANAGE)));
index 2aa7faa1242369be4ea985bad80805b94529b72f..0203e3fe23c2541f9b78a9ff0dde08d08f0b0fe6 100644 (file)
@@ -2,13 +2,11 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from "../context-menu-action-set";
+import { ContextMenuActionSet, ContextMenuActionNames } from "../context-menu-action-set";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "store/favorites/favorites-actions";
 import {
     RenameIcon,
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "store/favorites/favorites-actions";
 import {
     RenameIcon,
-    ShareIcon,
-    MoveToIcon,
     DetailsIcon,
     RemoveIcon,
     ReRunProcessIcon,
     DetailsIcon,
     RemoveIcon,
     ReRunProcessIcon,
@@ -18,10 +16,8 @@ import {
     StopIcon,
 } from "components/icon/icon";
 import { favoritePanelActions } from "store/favorite-panel/favorite-panel-action";
     StopIcon,
 } from "components/icon/icon";
 import { favoritePanelActions } from "store/favorite-panel/favorite-panel-action";
-import { openMoveProcessDialog } from "store/processes/process-move-actions";
 import { openProcessUpdateDialog } from "store/processes/process-update-actions";
 import { openCopyProcessDialog } from "store/processes/process-copy-actions";
 import { openProcessUpdateDialog } from "store/processes/process-update-actions";
 import { openCopyProcessDialog } from "store/processes/process-copy-actions";
-import { openSharingDialog } from "store/sharing-dialog/sharing-dialog-actions";
 import { openRemoveProcessDialog } from "store/processes/processes-actions";
 import { toggleDetailsPanel } from "store/details-panel/details-panel-action";
 import { navigateToOutput } from "store/process-panel/process-panel-actions";
 import { openRemoveProcessDialog } from "store/processes/processes-actions";
 import { toggleDetailsPanel } from "store/details-panel/details-panel-action";
 import { navigateToOutput } from "store/process-panel/process-panel-actions";
@@ -36,6 +32,7 @@ export const readOnlyProcessResourceActionSet: ContextMenuActionSet = [
     [
         {
             component: ToggleFavoriteAction,
     [
         {
             component: ToggleFavoriteAction,
+            name: ContextMenuActionNames.ADD_TO_FAVORITES,
             execute: (dispatch, resources) => {
                 dispatch<any>(toggleFavorite(resources[0])).then(() => {
                     dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
             execute: (dispatch, resources) => {
                 dispatch<any>(toggleFavorite(resources[0])).then(() => {
                     dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
@@ -44,21 +41,21 @@ export const readOnlyProcessResourceActionSet: ContextMenuActionSet = [
         },
         {
             icon: OpenIcon,
         },
         {
             icon: OpenIcon,
-            name: "Open in new tab",
+            name: ContextMenuActionNames.OPEN_IN_NEW_TAB,
             execute: (dispatch, resources) => {
                 dispatch<any>(openInNewTabAction(resources[0]));
             },
         },
         {
             icon: ReRunProcessIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openInNewTabAction(resources[0]));
             },
         },
         {
             icon: ReRunProcessIcon,
-            name: "Copy and re-run process",
+            name: ContextMenuActionNames.COPY_AND_RERUN_PROCESS,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCopyProcessDialog(resources[0]));
             },
         },
         {
             icon: OutputIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openCopyProcessDialog(resources[0]));
             },
         },
         {
             icon: OutputIcon,
-            name: "Outputs",
+            name: ContextMenuActionNames.OUTPUTS,
             execute: (dispatch, resources) => {
                 if (resources[0]) {
                     dispatch<any>(navigateToOutput(resources[0]));
             execute: (dispatch, resources) => {
                 if (resources[0]) {
                     dispatch<any>(navigateToOutput(resources[0]));
@@ -67,14 +64,14 @@ export const readOnlyProcessResourceActionSet: ContextMenuActionSet = [
         },
         {
             icon: DetailsIcon,
         },
         {
             icon: DetailsIcon,
-            name: "View details",
+            name: ContextMenuActionNames.VIEW_DETAILS,
             execute: dispatch => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
             execute: dispatch => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
-            name: "API Details",
+            name: ContextMenuActionNames.API_DETAILS,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
@@ -87,27 +84,21 @@ export const processResourceActionSet: ContextMenuActionSet = [
         ...readOnlyProcessResourceActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             icon: RenameIcon,
         ...readOnlyProcessResourceActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             icon: RenameIcon,
-            name: "Edit process",
+            name: ContextMenuActionNames.EDIT_PROCESS,
             execute: (dispatch, resources) => {
                 dispatch<any>(openProcessUpdateDialog(resources[0]));
             },
         },
             execute: (dispatch, resources) => {
                 dispatch<any>(openProcessUpdateDialog(resources[0]));
             },
         },
+        // removed until auto-move children is implemented
+        // {
+        //     icon: MoveToIcon,
+        //     name: ContextMenuActionNames.MOVE_TO,
+        //     execute: (dispatch, resources) => {
+        //         dispatch<any>(openMoveProcessDialog(resources[0]));
+        //     },
+        // },
         {
         {
-            icon: ShareIcon,
-            name: "Share",
-            execute: (dispatch, resources) => {
-                dispatch<any>(openSharingDialog(resources[0].uuid));
-            },
-        },
-        {
-            icon: MoveToIcon,
-            name: "Move to",
-            execute: (dispatch, resources) => {
-                dispatch<any>(openMoveProcessDialog(resources[0]));
-            },
-        },
-        {
-            name: "Remove",
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openRemoveProcessDialog(resources[0], resources.length));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openRemoveProcessDialog(resources[0], resources.length));
@@ -119,7 +110,7 @@ export const processResourceActionSet: ContextMenuActionSet = [
 const runningProcessOnlyActionSet: ContextMenuActionSet = [
     [
         {
 const runningProcessOnlyActionSet: ContextMenuActionSet = [
     [
         {
-            name: "CANCEL",
+            name: ContextMenuActionNames.CANCEL,
             icon: StopIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(cancelRunningWorkflow(resources[0].uuid));
             icon: StopIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(cancelRunningWorkflow(resources[0].uuid));
@@ -133,7 +124,7 @@ export const processResourceAdminActionSet: ContextMenuActionSet = [
         ...processResourceActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             component: TogglePublicFavoriteAction,
         ...processResourceActionSet.reduce((prev, next) => prev.concat(next), []),
         {
             component: TogglePublicFavoriteAction,
-            name: "Add to public favorites",
+            name: ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES,
             execute: (dispatch, resources) => {
                 dispatch<any>(togglePublicFavorite(resources[0])).then(() => {
                     dispatch<any>(publicFavoritePanelActions.REQUEST_ITEMS());
             execute: (dispatch, resources) => {
                 dispatch<any>(togglePublicFavorite(resources[0])).then(() => {
                     dispatch<any>(publicFavoritePanelActions.REQUEST_ITEMS());
index c722e61076088a830a91c54d837b589108580b93..8ef968eea9a4025eb10f3f349001898b42be15d8 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from "../context-menu-action-set";
+import { ContextMenuActionSet, ContextMenuActionNames } from "../context-menu-action-set";
 import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link, FolderSharedIcon } from "components/icon/icon";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "store/favorites/favorites-actions";
 import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link, FolderSharedIcon } from "components/icon/icon";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "store/favorites/favorites-actions";
@@ -23,7 +23,7 @@ import { freezeProject, unfreezeProject } from "store/projects/project-lock-acti
 
 export const toggleFavoriteAction = {
     component: ToggleFavoriteAction,
 
 export const toggleFavoriteAction = {
     component: ToggleFavoriteAction,
-    name: "ToggleFavoriteAction",
+    name: ContextMenuActionNames.ADD_TO_FAVORITES,
     execute: (dispatch, resources) => {
         dispatch(toggleFavorite(resources[0])).then(() => {
             dispatch(favoritePanelActions.REQUEST_ITEMS());
     execute: (dispatch, resources) => {
         dispatch(toggleFavorite(resources[0])).then(() => {
             dispatch(favoritePanelActions.REQUEST_ITEMS());
@@ -33,7 +33,7 @@ export const toggleFavoriteAction = {
 
 export const openInNewTabMenuAction = {
     icon: OpenIcon,
 
 export const openInNewTabMenuAction = {
     icon: OpenIcon,
-    name: "Open in new tab",
+    name: ContextMenuActionNames.OPEN_IN_NEW_TAB,
     execute: (dispatch, resources) => {
         dispatch(openInNewTabAction(resources[0]));
     },
     execute: (dispatch, resources) => {
         dispatch(openInNewTabAction(resources[0]));
     },
@@ -41,7 +41,7 @@ export const openInNewTabMenuAction = {
 
 export const copyToClipboardMenuAction = {
     icon: Link,
 
 export const copyToClipboardMenuAction = {
     icon: Link,
-    name: "Copy to clipboard",
+    name: ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
     execute: (dispatch, resources) => {
         dispatch(copyToClipboardAction(resources));
     },
     execute: (dispatch, resources) => {
         dispatch(copyToClipboardAction(resources));
     },
@@ -49,7 +49,7 @@ export const copyToClipboardMenuAction = {
 
 export const viewDetailsAction = {
     icon: DetailsIcon,
 
 export const viewDetailsAction = {
     icon: DetailsIcon,
-    name: "View details",
+    name: ContextMenuActionNames.VIEW_DETAILS,
     execute: dispatch => {
         dispatch(toggleDetailsPanel());
     },
     execute: dispatch => {
         dispatch(toggleDetailsPanel());
     },
@@ -57,7 +57,7 @@ export const viewDetailsAction = {
 
 export const advancedAction = {
     icon: AdvancedIcon,
 
 export const advancedAction = {
     icon: AdvancedIcon,
-    name: "API Details",
+    name: ContextMenuActionNames.API_DETAILS,
     execute: (dispatch, resources) => {
         dispatch(openAdvancedTabDialog(resources[0].uuid));
     },
     execute: (dispatch, resources) => {
         dispatch(openAdvancedTabDialog(resources[0].uuid));
     },
@@ -65,7 +65,7 @@ export const advancedAction = {
 
 export const openWith3rdPartyClientAction = {
     icon: FolderSharedIcon,
 
 export const openWith3rdPartyClientAction = {
     icon: FolderSharedIcon,
-    name: "Open with 3rd party client",
+    name: ContextMenuActionNames.OPEN_WITH_3RD_PARTY_CLIENT,
     execute: (dispatch, resources) => {
         dispatch(openWebDavS3InfoDialog(resources[0].uuid));
     },
     execute: (dispatch, resources) => {
         dispatch(openWebDavS3InfoDialog(resources[0].uuid));
     },
@@ -73,7 +73,7 @@ export const openWith3rdPartyClientAction = {
 
 export const editProjectAction = {
     icon: RenameIcon,
 
 export const editProjectAction = {
     icon: RenameIcon,
-    name: "Edit project",
+    name: ContextMenuActionNames.EDIT_PROJECT,
     execute: (dispatch, resources) => {
         dispatch(openProjectUpdateDialog(resources[0]));
     },
     execute: (dispatch, resources) => {
         dispatch(openProjectUpdateDialog(resources[0]));
     },
@@ -81,7 +81,7 @@ export const editProjectAction = {
 
 export const shareAction = {
     icon: ShareIcon,
 
 export const shareAction = {
     icon: ShareIcon,
-    name: "Share",
+    name: ContextMenuActionNames.SHARE,
     execute: (dispatch, resources) => {
         dispatch(openSharingDialog(resources[0].uuid));
     },
     execute: (dispatch, resources) => {
         dispatch(openSharingDialog(resources[0].uuid));
     },
@@ -89,7 +89,7 @@ export const shareAction = {
 
 export const moveToAction = {
     icon: MoveToIcon,
 
 export const moveToAction = {
     icon: MoveToIcon,
-    name: "Move to",
+    name: ContextMenuActionNames.MOVE_TO,
     execute: (dispatch, resource) => {
         dispatch(openMoveProjectDialog(resource[0]));
     },
     execute: (dispatch, resource) => {
         dispatch(openMoveProjectDialog(resource[0]));
     },
@@ -97,7 +97,7 @@ export const moveToAction = {
 
 export const toggleTrashAction = {
     component: ToggleTrashAction,
 
 export const toggleTrashAction = {
     component: ToggleTrashAction,
-    name: "ToggleTrashAction",
+    name: ContextMenuActionNames.MOVE_TO_TRASH,
     execute: (dispatch, resources) => {
         dispatch(toggleProjectTrashed(resources[0].uuid, resources[0].ownerUuid, resources[0].isTrashed!!, resources.length > 1));
     },
     execute: (dispatch, resources) => {
         dispatch(toggleProjectTrashed(resources[0].uuid, resources[0].ownerUuid, resources[0].isTrashed!!, resources.length > 1));
     },
@@ -105,7 +105,7 @@ export const toggleTrashAction = {
 
 export const freezeProjectAction = {
     component: ToggleLockAction,
 
 export const freezeProjectAction = {
     component: ToggleLockAction,
-    name: "ToggleLockAction",
+    name: ContextMenuActionNames.FREEZE_PROJECT,
     execute: (dispatch, resources) => {
         if (resources[0].isFrozen) {
             dispatch(unfreezeProject(resources[0].uuid));
     execute: (dispatch, resources) => {
         if (resources[0].isFrozen) {
             dispatch(unfreezeProject(resources[0].uuid));
@@ -117,7 +117,7 @@ export const freezeProjectAction = {
 
 export const newProjectAction: any = {
     icon: NewProjectIcon,
 
 export const newProjectAction: any = {
     icon: NewProjectIcon,
-    name: "New project",
+    name: ContextMenuActionNames.NEW_PROJECT,
     execute: (dispatch, resources): void => {
         dispatch(openProjectCreateDialog(resources[0].uuid));
     },
     execute: (dispatch, resources): void => {
         dispatch(openProjectCreateDialog(resources[0].uuid));
     },
index 490bf3e30a9e649f85165a988751aff4357be40f..937b43eb099bf7bd6a0f91f2733d2f5f3baef770 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from "../context-menu-action-set";
+import { ContextMenuActionSet, ContextMenuActionNames } from "../context-menu-action-set";
 import { TogglePublicFavoriteAction } from "views-components/context-menu/actions/public-favorite-action";
 import { togglePublicFavorite } from "store/public-favorites/public-favorites-actions";
 import { publicFavoritePanelActions } from "store/public-favorites-panel/public-favorites-action";
 import { TogglePublicFavoriteAction } from "views-components/context-menu/actions/public-favorite-action";
 import { togglePublicFavorite } from "store/public-favorites/public-favorites-actions";
 import { publicFavoritePanelActions } from "store/public-favorites-panel/public-favorites-action";
@@ -24,7 +24,7 @@ import {
 
 export const togglePublicFavoriteAction = {
     component: TogglePublicFavoriteAction,
 
 export const togglePublicFavoriteAction = {
     component: TogglePublicFavoriteAction,
-    name: "TogglePublicFavoriteAction",
+    name: ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES,
     execute: (dispatch, resources) => {
         dispatch(togglePublicFavorite(resources[0])).then(() => {
             dispatch(publicFavoritePanelActions.REQUEST_ITEMS());
     execute: (dispatch, resources) => {
         dispatch(togglePublicFavorite(resources[0])).then(() => {
             dispatch(publicFavoritePanelActions.REQUEST_ITEMS());
index cbdcd004288780cbdbd3c5cfe2e41449d966fae5..20edbe4a88c1c7f7ea99dd328460630ba9cf0999 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { AdvancedIcon, RemoveIcon, ShareIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openRepositoryAttributes, openRemoveRepositoryDialog } from 'store/repositories/repositories-actions';
 import { AdvancedIcon, RemoveIcon, ShareIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openRepositoryAttributes, openRemoveRepositoryDialog } from 'store/repositories/repositories-actions';
@@ -11,28 +11,28 @@ import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
 export const repositoryActionSet: ContextMenuActionSet = [
     [
         {
 export const repositoryActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openRepositoryAttributes(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openRepositoryAttributes(resources[0].uuid));
             },
         },
         {
-            name: 'Share',
+            name: ContextMenuActionNames.SHARE,
             icon: ShareIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openSharingDialog(resources[0].uuid));
             },
         },
         {
             icon: ShareIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openSharingDialog(resources[0].uuid));
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Remove',
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openRemoveRepositoryDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openRemoveRepositoryDialog(resources[0].uuid));
index 401e9634d93c3db56533f4450298f6e7f9acb380..6909df8427c4adb50f8aa3b097e675338f0797a4 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from '../context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from '../context-menu-action-set';
 import { ToggleFavoriteAction } from '../actions/favorite-action';
 import { toggleFavorite } from 'store/favorites/favorites-actions';
 
 import { ToggleFavoriteAction } from '../actions/favorite-action';
 import { toggleFavorite } from 'store/favorites/favorites-actions';
 
@@ -10,6 +10,7 @@ export const resourceActionSet: ContextMenuActionSet = [
     [
         {
             component: ToggleFavoriteAction,
     [
         {
             component: ToggleFavoriteAction,
+            name: ContextMenuActionNames.ADD_TO_FAVORITES,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(toggleFavorite(resource)));
             },
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(toggleFavorite(resource)));
             },
index a779d1eb2967877c766d7166b63ae00697a4bfb4..8fcdbf0af30014ea8eabfcc3f233569182498058 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from '../context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from '../context-menu-action-set';
 import { openCollectionCreateDialog } from 'store/collections/collection-create-actions';
 import { NewProjectIcon, CollectionIcon } from 'components/icon/icon';
 import { openProjectCreateDialog } from 'store/projects/project-create-actions';
 import { openCollectionCreateDialog } from 'store/collections/collection-create-actions';
 import { NewProjectIcon, CollectionIcon } from 'components/icon/icon';
 import { openProjectCreateDialog } from 'store/projects/project-create-actions';
@@ -11,14 +11,14 @@ export const rootProjectActionSet: ContextMenuActionSet = [
     [
         {
             icon: NewProjectIcon,
     [
         {
             icon: NewProjectIcon,
-            name: 'New project',
+            name: ContextMenuActionNames.NEW_PROJECT,
             execute: (dispatch, resources) => {
                  dispatch<any>(openProjectCreateDialog(resources[0].uuid));
             },
         },
         {
             icon: CollectionIcon,
             execute: (dispatch, resources) => {
                  dispatch<any>(openProjectCreateDialog(resources[0].uuid));
             },
         },
         {
             icon: CollectionIcon,
-            name: 'New Collection',
+            name: ContextMenuActionNames.NEW_COLLECTION,
             execute: (dispatch, resources) => {
                  dispatch<any>(openCollectionCreateDialog(resources[0].uuid));
             },
             execute: (dispatch, resources) => {
                  dispatch<any>(openCollectionCreateDialog(resources[0].uuid));
             },
index dcc9eae20700160c3fdd3bf72224754b9bdded81..debaf2dabfaff3403620abec4313b99d2b25e806 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from '../context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from '../context-menu-action-set';
 import { DetailsIcon, AdvancedIcon, OpenIcon, Link } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
 import { DetailsIcon, AdvancedIcon, OpenIcon, Link } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
@@ -12,28 +12,28 @@ export const searchResultsActionSet: ContextMenuActionSet = [
     [
         {
             icon: OpenIcon,
     [
         {
             icon: OpenIcon,
-            name: 'Open in new tab',
+            name: ContextMenuActionNames.OPEN_IN_NEW_TAB,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(openInNewTabAction(resource)));
             },
         },
         {
             icon: Link,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(openInNewTabAction(resource)));
             },
         },
         {
             icon: Link,
-            name: 'Copy to clipboard',
+            name: ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
             execute: (dispatch, resources) => {
                 dispatch<any>(copyToClipboardAction(resources));
             },
         },
         {
             icon: DetailsIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(copyToClipboardAction(resources));
             },
         },
         {
             icon: DetailsIcon,
-            name: 'View details',
+            name: ContextMenuActionNames.VIEW_DETAILS,
             execute: (dispatch) => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch) => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
index c31e1681a4f88bd861c12ae6158837db982ccdbb..2a64f17cd2b5e3afa1c36524ec86afface69afe4 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openSshKeyRemoveDialog, openSshKeyAttributesDialog } from 'store/auth/auth-action-ssh';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openSshKeyRemoveDialog, openSshKeyAttributesDialog } from 'store/auth/auth-action-ssh';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
@@ -10,21 +10,21 @@ import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 export const sshKeyActionSet: ContextMenuActionSet = [
     [
         {
 export const sshKeyActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSshKeyAttributesDialog(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSshKeyAttributesDialog(resources[0].uuid));
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Remove',
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSshKeyRemoveDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSshKeyRemoveDialog(resources[0].uuid));
index 82e00df6cbbb9fbf26229f012370f8dba568c242..8a2790456938eba6e699cc486c17977e1af7529f 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from '../context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from '../context-menu-action-set';
 import { ToggleTrashAction } from 'views-components/context-menu/actions/trash-action';
 import { toggleTrashed } from 'store/trash/trash-actions';
 
 import { ToggleTrashAction } from 'views-components/context-menu/actions/trash-action';
 import { toggleTrashed } from 'store/trash/trash-actions';
 
@@ -10,6 +10,7 @@ export const trashActionSet: ContextMenuActionSet = [
     [
         {
             component: ToggleTrashAction,
     [
         {
             component: ToggleTrashAction,
+            name: ContextMenuActionNames.MOVE_TO_TRASH,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(toggleTrashed(resource.kind, resource.uuid, resource.ownerUuid, resource.isTrashed!!)));
             },
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(toggleTrashed(resource.kind, resource.uuid, resource.ownerUuid, resource.isTrashed!!)));
             },
index 3e8f0cb647e38e8f73b35e33faf562ccc9caa155..ea66deb683e458739da10b2a7870000ed649a994 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from '../context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from '../context-menu-action-set';
 import { DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RestoreFromTrashIcon } from 'components/icon/icon';
 import { toggleCollectionTrashed } from 'store/trash/trash-actions';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { DetailsIcon, ProvenanceGraphIcon, AdvancedIcon, RestoreFromTrashIcon } from 'components/icon/icon';
 import { toggleCollectionTrashed } from 'store/trash/trash-actions';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
@@ -12,28 +12,28 @@ export const trashedCollectionActionSet: ContextMenuActionSet = [
     [
         {
             icon: DetailsIcon,
     [
         {
             icon: DetailsIcon,
-            name: 'View details',
+            name: ContextMenuActionNames.VIEW_DETAILS,
             execute: (dispatch) => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: ProvenanceGraphIcon,
             execute: (dispatch) => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: ProvenanceGraphIcon,
-            name: 'Provenance graph',
+            name: ContextMenuActionNames.PROVENANCE_GRAPH,
             execute: (dispatch, resource) => {
                 // add code
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resource) => {
                 // add code
             },
         },
         {
             icon: AdvancedIcon,
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: RestoreFromTrashIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: RestoreFromTrashIcon,
-            name: 'Restore',
+            name: ContextMenuActionNames.RESTORE,
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(toggleCollectionTrashed(resource.uuid, true)));
             },
             execute: (dispatch, resources) => {
                 resources.forEach((resource) => dispatch<any>(toggleCollectionTrashed(resource.uuid, true)));
             },
index 0108ff7e50ec1a3cf164ba77019449b1039a0fb2..953ed6e9c98bfce74885aa73e24f85b4b4672eb4 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import {
     AdvancedIcon,
     ProjectIcon,
 import {
     AdvancedIcon,
     ProjectIcon,
@@ -29,28 +29,28 @@ import {
 export const userActionSet: ContextMenuActionSet = [
     [
         {
 export const userActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openUserAttributes(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openUserAttributes(resources[0].uuid));
             },
         },
         {
-            name: 'Project',
+            name: ContextMenuActionNames.HOME_PROJECT,
             icon: ProjectIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openUserProjects(resources[0].uuid));
             },
         },
         {
             icon: ProjectIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openUserProjects(resources[0].uuid));
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Account Settings',
+            name: ContextMenuActionNames.ACCOUNT_SETTINGS,
             icon: UserPanelIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(navigateToUserProfile(resources[0].uuid));
             icon: UserPanelIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(navigateToUserProfile(resources[0].uuid));
@@ -60,7 +60,7 @@ export const userActionSet: ContextMenuActionSet = [
     ],
     [
         {
     ],
     [
         {
-            name: 'Activate User',
+            name: ContextMenuActionNames.ACTIVATE_USER,
             icon: ActiveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openActivateDialog(resources[0].uuid));
             icon: ActiveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openActivateDialog(resources[0].uuid));
@@ -68,7 +68,7 @@ export const userActionSet: ContextMenuActionSet = [
             filters: [isAdmin, canActivateUser],
         },
         {
             filters: [isAdmin, canActivateUser],
         },
         {
-            name: 'Setup User',
+            name: ContextMenuActionNames.SETUP_USER,
             icon: AdminMenuIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSetupDialog(resources[0].uuid));
             icon: AdminMenuIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openSetupDialog(resources[0].uuid));
@@ -76,7 +76,7 @@ export const userActionSet: ContextMenuActionSet = [
             filters: [isAdmin, canSetupUser],
         },
         {
             filters: [isAdmin, canSetupUser],
         },
         {
-            name: 'Deactivate User',
+            name: ContextMenuActionNames.DEACTIVATE_USER,
             icon: DeactivateUserIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openDeactivateDialog(resources[0].uuid));
             icon: DeactivateUserIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openDeactivateDialog(resources[0].uuid));
@@ -84,7 +84,7 @@ export const userActionSet: ContextMenuActionSet = [
             filters: [isAdmin, canDeactivateUser],
         },
         {
             filters: [isAdmin, canDeactivateUser],
         },
         {
-            name: 'Login As User',
+            name: ContextMenuActionNames.LOGIN_AS_USER,
             icon: LoginAsIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(loginAs(resources[0].uuid));
             icon: LoginAsIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(loginAs(resources[0].uuid));
index a26cbe1368d4aa0c8b435af7db26f65b8c138b19..11d94ccc1d8036ea6dedf020f77dcac51e6e943c 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
+import { ContextMenuActionSet, ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openVirtualMachineAttributes, openRemoveVirtualMachineDialog } from 'store/virtual-machines/virtual-machines-actions';
 import { AdvancedIcon, RemoveIcon, AttributesIcon } from 'components/icon/icon';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { openVirtualMachineAttributes, openRemoveVirtualMachineDialog } from 'store/virtual-machines/virtual-machines-actions';
@@ -10,21 +10,21 @@ import { openVirtualMachineAttributes, openRemoveVirtualMachineDialog } from 'st
 export const virtualMachineActionSet: ContextMenuActionSet = [
     [
         {
 export const virtualMachineActionSet: ContextMenuActionSet = [
     [
         {
-            name: 'Attributes',
+            name: ContextMenuActionNames.ATTRIBUTES,
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openVirtualMachineAttributes(resources[0].uuid));
             },
         },
         {
             icon: AttributesIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openVirtualMachineAttributes(resources[0].uuid));
             },
         },
         {
-            name: 'API Details',
+            name: ContextMenuActionNames.API_DETAILS,
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: AdvancedIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
-            name: 'Remove',
+            name: ContextMenuActionNames.REMOVE,
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openRemoveVirtualMachineDialog(resources[0].uuid));
             icon: RemoveIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openRemoveVirtualMachineDialog(resources[0].uuid));
index 4a1460bfc94f81552283595f7d31ffd08d97517a..f03340db4b91758f2c2b7130df92c1cff239e62b 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
+import { ContextMenuActionSet, ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { openRunProcess, deleteWorkflow } from "store/workflow-panel/workflow-panel-actions";
 import { DetailsIcon, AdvancedIcon, OpenIcon, Link, StartIcon, TrashIcon } from "components/icon/icon";
 import { copyToClipboardAction, openInNewTabAction } from "store/open-in-new-tab/open-in-new-tab.actions";
 import { openRunProcess, deleteWorkflow } from "store/workflow-panel/workflow-panel-actions";
 import { DetailsIcon, AdvancedIcon, OpenIcon, Link, StartIcon, TrashIcon } from "components/icon/icon";
 import { copyToClipboardAction, openInNewTabAction } from "store/open-in-new-tab/open-in-new-tab.actions";
@@ -13,35 +13,35 @@ export const readOnlyWorkflowActionSet: ContextMenuActionSet = [
     [
         {
             icon: OpenIcon,
     [
         {
             icon: OpenIcon,
-            name: "Open in new tab",
+            name: ContextMenuActionNames.OPEN_IN_NEW_TAB,
             execute: (dispatch, resources) => {
                 dispatch<any>(openInNewTabAction(resources[0]));
             },
         },
         {
             icon: Link,
             execute: (dispatch, resources) => {
                 dispatch<any>(openInNewTabAction(resources[0]));
             },
         },
         {
             icon: Link,
-            name: "Copy to clipboard",
+            name: ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
             execute: (dispatch, resources) => {
                 dispatch<any>(copyToClipboardAction(resources));
             },
         },
         {
             icon: DetailsIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(copyToClipboardAction(resources));
             },
         },
         {
             icon: DetailsIcon,
-            name: "View details",
+            name: ContextMenuActionNames.VIEW_DETAILS,
             execute: dispatch => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
             execute: dispatch => {
                 dispatch<any>(toggleDetailsPanel());
             },
         },
         {
             icon: AdvancedIcon,
-            name: "API Details",
+            name: ContextMenuActionNames.API_DETAILS,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: StartIcon,
             execute: (dispatch, resources) => {
                 dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
             },
         },
         {
             icon: StartIcon,
-            name: "Run Workflow",
+            name: ContextMenuActionNames.RUN_WORKFLOW,
             execute: (dispatch, resources) => {
                 dispatch<any>(openRunProcess(resources[0].uuid, resources[0].ownerUuid, resources[0].name));
             },
             execute: (dispatch, resources) => {
                 dispatch<any>(openRunProcess(resources[0].uuid, resources[0].ownerUuid, resources[0].name));
             },
@@ -54,7 +54,7 @@ export const workflowActionSet: ContextMenuActionSet = [
         ...readOnlyWorkflowActionSet[0],
         {
             icon: TrashIcon,
         ...readOnlyWorkflowActionSet[0],
         {
             icon: TrashIcon,
-            name: "Delete Workflow",
+            name: ContextMenuActionNames.DELETE_WORKFLOW,
             execute: (dispatch, resources) => {
                 dispatch<any>(deleteWorkflow(resources[0].uuid, resources[0].ownerUuid));
             },
             execute: (dispatch, resources) => {
                 dispatch<any>(deleteWorkflow(resources[0].uuid, resources[0].ownerUuid));
             },
index c92f7bc88b861e28713c7d3ef5c70ad60601e075..dac3858fb31428d5f502e02c9ad07dd4e7445fa1 100644 (file)
@@ -5,7 +5,7 @@
 import { connect } from "react-redux";
 import { RootState } from "../../../store/store";
 import { getNodeValue } from "models/tree";
 import { connect } from "react-redux";
 import { RootState } from "../../../store/store";
 import { getNodeValue } from "models/tree";
-import { ContextMenuKind } from 'views-components/context-menu/context-menu';
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { CopyToClipboardAction } from "./copy-to-clipboard-action";
 
 const mapStateToProps = (state: RootState) => {
 import { CopyToClipboardAction } from "./copy-to-clipboard-action";
 
 const mapStateToProps = (state: RootState) => {
index 8b90f588ffa49ec13e3971f13b6280897fcec6d1..9d8acad20cf3eedde293d5c4d5a0b6aabb377f81 100644 (file)
@@ -8,7 +8,7 @@ import Adapter from 'enzyme-adapter-react-16';
 import configureMockStore from 'redux-mock-store'
 import { Provider } from 'react-redux';
 import { CollectionFileViewerAction } from './collection-file-viewer-action';
 import configureMockStore from 'redux-mock-store'
 import { Provider } from 'react-redux';
 import { CollectionFileViewerAction } from './collection-file-viewer-action';
-import { ContextMenuKind } from 'views-components/context-menu/context-menu';
+import { ContextMenuKind } from '../menu-item-sort';
 import { createTree, initTreeNode, setNode, getNodeValue } from "models/tree";
 import { getInlineFileUrl, sanitizeToken } from "./helpers";
 
 import { createTree, initTreeNode, setNode, getNodeValue } from "models/tree";
 import { getInlineFileUrl, sanitizeToken } from "./helpers";
 
index f736f0bf2705bf8d3481eb974cfdb93a3a1cc23f..06b79bda4d61008340c4c140cd2a78f68e9d2d2c 100644 (file)
@@ -6,7 +6,7 @@ import { connect } from "react-redux";
 import { RootState } from "../../../store/store";
 import { FileViewerAction } from 'views-components/context-menu/actions/file-viewer-action';
 import { getNodeValue } from "models/tree";
 import { RootState } from "../../../store/store";
 import { FileViewerAction } from 'views-components/context-menu/actions/file-viewer-action';
 import { getNodeValue } from "models/tree";
-import { ContextMenuKind } from 'views-components/context-menu/context-menu';
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { getInlineFileUrl, sanitizeToken, isInlineFileUrlSafe } from "./helpers";
 
 const mapStateToProps = (state: RootState) => {
 import { getInlineFileUrl, sanitizeToken, isInlineFileUrlSafe } from "./helpers";
 
 const mapStateToProps = (state: RootState) => {
diff --git a/services/workbench2/src/views-components/context-menu/actions/context-menu-divider.tsx b/services/workbench2/src/views-components/context-menu/actions/context-menu-divider.tsx
new file mode 100644 (file)
index 0000000..77955c2
--- /dev/null
@@ -0,0 +1,45 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { ContextMenuAction } from '../context-menu-action-set';
+import { Divider as DividerComponent, StyleRulesCallback, withStyles } from '@material-ui/core';
+import { WithStyles } from '@material-ui/core/styles';
+import { ArvadosTheme } from 'common/custom-theme';
+import { VerticalLineDivider } from 'components/icon/icon';
+
+type CssRules = 'horizontal' | 'vertical';
+
+const styles:StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+  horizontal: {
+      backgroundColor: 'black',
+  },
+  vertical: {
+    color: theme.palette.grey["400"],
+    margin: 'auto 0',
+    transform: 'scaleY(1.25)',
+  },
+});
+
+export const VerticalLine = withStyles(styles)((props: WithStyles<CssRules>) => {
+  return  <VerticalLineDivider className={props.classes.vertical} />;
+});
+
+export const HorizontalLine = withStyles(styles)((props: WithStyles<CssRules>) => {
+  return  <DividerComponent variant='middle' className={props.classes.horizontal} />;
+});
+
+export const horizontalMenuDivider: ContextMenuAction = {
+  name: 'Divider',
+  icon: () => null,
+  component: VerticalLine,
+  execute: () => null,
+};
+
+export const verticalMenuDivider: ContextMenuAction = {
+  name: 'Divider',
+  icon: () => null,
+  component: HorizontalLine,
+  execute: () => null,
+};
\ No newline at end of file
index 50ed20fd7fb01d2dc0f0cacb8850bbc5bfcec71d..7b6501dc2ec015c69b98bde83d2ef0dcaa980047 100644 (file)
@@ -26,7 +26,7 @@ export const CopyToClipboardAction = (props: { href?: any, download?: any, onCli
                 <Link />
             </ListItemIcon>
             <ListItemText>
                 <Link />
             </ListItemIcon>
             <ListItemText>
-                Copy to clipboard
+                Copy link to clipboard
             </ListItemText>
         </ListItem>
         : null;
             </ListItemText>
         </ListItem>
         : null;
index e3fcfd19e4313b2a709e092fe5ba1c7d272625e4..ed41225a07c1ebd0afbff9cd6aefd97c45667b85 100644 (file)
@@ -23,8 +23,6 @@ const mock = {
     generateAsync: jest.fn().mockImplementation(() => Promise.resolve('test')),
 };
 
     generateAsync: jest.fn().mockImplementation(() => Promise.resolve('test')),
 };
 
-jest.mock('jszip', () => jest.fn().mockImplementation(() => mock));
-
 describe('<DownloadAction />', () => {
     let props;
     let zip;
 describe('<DownloadAction />', () => {
     let props;
     let zip;
index 3b1f770220a02a6f8faab446797ead523a5cfafe..3b04e29e735c1808d1699ca9d3c0f2125dab1966 100644 (file)
@@ -6,7 +6,7 @@ import { connect } from "react-redux";
 import { RootState } from "../../../store/store";
 import { DownloadAction } from "./download-action";
 import { getNodeValue } from "../../../models/tree";
 import { RootState } from "../../../store/store";
 import { DownloadAction } from "./download-action";
 import { getNodeValue } from "../../../models/tree";
-import { ContextMenuKind } from '../context-menu';
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { filterCollectionFilesBySelection } from "store/collection-panel/collection-panel-files/collection-panel-files-state";
 import { sanitizeToken } from "./helpers";
 
 import { filterCollectionFilesBySelection } from "store/collection-panel/collection-panel-files/collection-panel-files-state";
 import { sanitizeToken } from "./helpers";
 
index 0a77876bac7902c8865c2b364231d04e65b16b9e..c4bba3a9320a30c81ef4ebd384ce33707f9b4554 100644 (file)
@@ -12,7 +12,7 @@ export const FileViewerAction = (props: any) => {
             style={{ textDecoration: 'none' }}
             href={props.href}
             target="_blank"
             style={{ textDecoration: 'none' }}
             href={props.href}
             target="_blank"
-            rel="noopener"
+            rel="noopener noreferrer"
             onClick={props.onClick}>
             <ListItem button>
                 <ListItemIcon>
             onClick={props.onClick}>
             <ListItem button>
                 <ListItemIcon>
index a953500b3ae7a49f9216bce9544b24b3771a9982..38de735e29779f4e1f3f54bcdba0cabe1e87df92 100644 (file)
@@ -6,6 +6,61 @@ import { Dispatch } from "redux";
 import { ContextMenuItem } from "components/context-menu/context-menu";
 import { ContextMenuResource } from "store/context-menu/context-menu-actions";
 
 import { ContextMenuItem } from "components/context-menu/context-menu";
 import { ContextMenuResource } from "store/context-menu/context-menu-actions";
 
+export enum ContextMenuActionNames {
+    ACCOUNT_SETTINGS = 'Account settings',
+    ACTIVATE_USER = 'Activate user',
+    ADD_TO_FAVORITES = 'Add to favorites',
+    ADD_TO_PUBLIC_FAVORITES = 'Add to public favorites',
+    ATTRIBUTES = 'Attributes',
+    API_DETAILS = 'API Details',
+    CANCEL = 'CANCEL',
+    COPY_AND_RERUN_PROCESS = 'Copy and re-run process',
+    COPY_ITEM_INTO_EXISTING_COLLECTION = 'Copy item into existing collection',
+    COPY_ITEM_INTO_NEW_COLLECTION = 'Copy item into new collection',
+    COPY_SELECTED_INTO_EXISTING_COLLECTION = 'Copy selected into existing collection',
+    COPY_SELECTED_INTO_SEPARATE_COLLECTIONS = 'Copy selected into separate collections',
+    COPY_SELECTED_INTO_NEW_COLLECTION = 'Copy selected into new collection',
+    COPY_LINK_TO_CLIPBOARD = 'Copy link to clipboard',
+    DEACTIVATE_USER = 'Deactivate user',
+    DELETE_WORKFLOW = 'Delete Workflow',
+    DIVIDER = 'Divider',
+    DOWNLOAD = 'Download',
+    EDIT_COLLECTION = 'Edit collection',
+    EDIT_PROCESS = 'Edit process',
+    EDIT_PROJECT = 'Edit project',
+    FREEZE_PROJECT = 'Freeze project',
+    HOME_PROJECT = 'Home project',
+    LOGIN_AS_USER = 'Login as user',
+    MAKE_A_COPY = 'Make a copy',
+    MANAGE = 'Manage',
+    MOVE_ITEM_INTO_EXISTING_COLLECTION = 'Move item into existing collection',
+    MOVE_ITEM_INTO_NEW_COLLECTION = 'Move item into new collection',
+    MOVE_SELECTED_INTO_EXISTING_COLLECTION = 'Move selected into existing collection',
+    MOVE_SELECTED_INTO_NEW_COLLECTION = 'Move selected into new collection',
+    MOVE_SELECTED_INTO_SEPARATE_COLLECTIONS = 'Move selected into separate collections',
+    MOVE_TO = 'Move to',
+    MOVE_TO_TRASH = 'Move to trash',
+    NEW_COLLECTION = 'New collection',
+    NEW_PROJECT = 'New project',
+    OPEN_IN_NEW_TAB = 'Open in new tab',
+    OPEN_WITH_3RD_PARTY_CLIENT = 'Open with 3rd party client',
+    OUTPUTS = 'Outputs',
+    PROVENANCE_GRAPH = 'Provenance graph',
+    READ = 'Read',
+    REMOVE = 'Remove',
+    REMOVE_SELECTED = 'Remove selected',
+    RENAME = 'Rename',
+    RESTORE = 'Restore',
+    RESTORE_VERSION = 'Restore version',
+    RUN_WORKFLOW = 'Run Workflow',
+    SELECT_ALL = 'Select all',
+    SETUP_USER = 'Setup user',
+    SHARE = 'Share',
+    UNSELECT_ALL = 'Unselect all',
+    VIEW_DETAILS = 'View details',
+    WRITE = 'Write',
+}
+
 export interface ContextMenuAction extends ContextMenuItem {
     execute(dispatch: Dispatch, resources: ContextMenuResource[], state?: any): void;
 }
 export interface ContextMenuAction extends ContextMenuItem {
     execute(dispatch: Dispatch, resources: ContextMenuResource[], state?: any): void;
 }
index aeb69de7624bf3e27a82f7a911f7de1d57e03d4c..a173399b7bae25d7e0e1b609be2de84ae389e373 100644 (file)
@@ -10,7 +10,7 @@ import { createAnchorAt } from "components/popover/helpers";
 import { ContextMenuActionSet, ContextMenuAction } from "./context-menu-action-set";
 import { Dispatch } from "redux";
 import { memoize } from "lodash";
 import { ContextMenuActionSet, ContextMenuAction } from "./context-menu-action-set";
 import { Dispatch } from "redux";
 import { memoize } from "lodash";
-import { sortByProperty } from "common/array-utils";
+import { sortMenuItems, ContextMenuKind, menuDirection } from "./menu-item-sort";
 
 type DataProps = Pick<ContextMenuProps, "anchorEl" | "items" | "open"> & { resource?: ContextMenuResource };
 
 
 type DataProps = Pick<ContextMenuProps, "anchorEl" | "items" | "open"> & { resource?: ContextMenuResource };
 
@@ -65,59 +65,11 @@ export const ContextMenu = connect(mapStateToProps, mapDispatchToProps, mergePro
 
 const menuActionSets = new Map<string, ContextMenuActionSet>();
 
 
 const menuActionSets = new Map<string, ContextMenuActionSet>();
 
-export const addMenuActionSet = (name: string, itemSet: ContextMenuActionSet) => {
-    const sorted = itemSet.map(items => items.sort(sortByProperty("name")));
+export const addMenuActionSet = (name: ContextMenuKind, itemSet: ContextMenuActionSet) => {
+    const sorted = itemSet.map(items => sortMenuItems(name, items, menuDirection.VERTICAL));
     menuActionSets.set(name, sorted);
 };
 
 const emptyActionSet: ContextMenuActionSet = [];
 const getMenuActionSet = (resource?: ContextMenuResource): ContextMenuActionSet =>
     resource ? menuActionSets.get(resource.menuKind) || emptyActionSet : emptyActionSet;
     menuActionSets.set(name, sorted);
 };
 
 const emptyActionSet: ContextMenuActionSet = [];
 const getMenuActionSet = (resource?: ContextMenuResource): ContextMenuActionSet =>
     resource ? menuActionSets.get(resource.menuKind) || emptyActionSet : emptyActionSet;
-
-export enum ContextMenuKind {
-    API_CLIENT_AUTHORIZATION = "ApiClientAuthorization",
-    ROOT_PROJECT = "RootProject",
-    PROJECT = "Project",
-    FILTER_GROUP = "FilterGroup",
-    READONLY_PROJECT = "ReadOnlyProject",
-    FROZEN_PROJECT = "FrozenProject",
-    FROZEN_PROJECT_ADMIN = "FrozenProjectAdmin",
-    PROJECT_ADMIN = "ProjectAdmin",
-    FILTER_GROUP_ADMIN = "FilterGroupAdmin",
-    RESOURCE = "Resource",
-    FAVORITE = "Favorite",
-    TRASH = "Trash",
-    COLLECTION_FILES = "CollectionFiles",
-    COLLECTION_FILES_MULTIPLE = "CollectionFilesMultiple",
-    READONLY_COLLECTION_FILES = "ReadOnlyCollectionFiles",
-    READONLY_COLLECTION_FILES_MULTIPLE = "ReadOnlyCollectionFilesMultiple",
-    COLLECTION_FILES_NOT_SELECTED = "CollectionFilesNotSelected",
-    COLLECTION_FILE_ITEM = "CollectionFileItem",
-    COLLECTION_DIRECTORY_ITEM = "CollectionDirectoryItem",
-    READONLY_COLLECTION_FILE_ITEM = "ReadOnlyCollectionFileItem",
-    READONLY_COLLECTION_DIRECTORY_ITEM = "ReadOnlyCollectionDirectoryItem",
-    COLLECTION = "Collection",
-    COLLECTION_ADMIN = "CollectionAdmin",
-    READONLY_COLLECTION = "ReadOnlyCollection",
-    OLD_VERSION_COLLECTION = "OldVersionCollection",
-    TRASHED_COLLECTION = "TrashedCollection",
-    PROCESS = "Process",
-    RUNNING_PROCESS_ADMIN = "RunningProcessAdmin",
-    PROCESS_ADMIN = "ProcessAdmin",
-    RUNNING_PROCESS_RESOURCE = "RunningProcessResource",
-    PROCESS_RESOURCE = "ProcessResource",
-    READONLY_PROCESS_RESOURCE = "ReadOnlyProcessResource",
-    PROCESS_LOGS = "ProcessLogs",
-    REPOSITORY = "Repository",
-    SSH_KEY = "SshKey",
-    VIRTUAL_MACHINE = "VirtualMachine",
-    KEEP_SERVICE = "KeepService",
-    USER = "User",
-    GROUPS = "Group",
-    GROUP_MEMBER = "GroupMember",
-    PERMISSION_EDIT = "PermissionEdit",
-    LINK = "Link",
-    WORKFLOW = "Workflow",
-    READONLY_WORKFLOW = "ReadOnlyWorkflow",
-    SEARCH_RESULTS = "SearchResults",
-}
diff --git a/services/workbench2/src/views-components/context-menu/menu-item-sort.ts b/services/workbench2/src/views-components/context-menu/menu-item-sort.ts
new file mode 100644 (file)
index 0000000..f331c60
--- /dev/null
@@ -0,0 +1,182 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ContextMenuAction } from './context-menu-action-set';
+import { ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
+import { sortByProperty } from 'common/array-utils';
+import { horizontalMenuDivider, verticalMenuDivider } from './actions/context-menu-divider';
+import { MultiSelectMenuAction } from 'views-components/multiselect-toolbar/ms-menu-actions';
+
+export enum ContextMenuKind {
+    API_CLIENT_AUTHORIZATION = "ApiClientAuthorization",
+    ROOT_PROJECT = "RootProject",
+    PROJECT = "Project",
+    FILTER_GROUP = "FilterGroup",
+    READONLY_PROJECT = "ReadOnlyProject",
+    FROZEN_PROJECT = "FrozenProject",
+    FROZEN_PROJECT_ADMIN = "FrozenProjectAdmin",
+    PROJECT_ADMIN = "ProjectAdmin",
+    FILTER_GROUP_ADMIN = "FilterGroupAdmin",
+    RESOURCE = "Resource",
+    FAVORITE = "Favorite",
+    TRASH = "Trash",
+    COLLECTION_FILES = "CollectionFiles",
+    COLLECTION_FILES_MULTIPLE = "CollectionFilesMultiple",
+    READONLY_COLLECTION_FILES = "ReadOnlyCollectionFiles",
+    READONLY_COLLECTION_FILES_MULTIPLE = "ReadOnlyCollectionFilesMultiple",
+    COLLECTION_FILES_NOT_SELECTED = "CollectionFilesNotSelected",
+    COLLECTION_FILE_ITEM = "CollectionFileItem",
+    COLLECTION_DIRECTORY_ITEM = "CollectionDirectoryItem",
+    READONLY_COLLECTION_FILE_ITEM = "ReadOnlyCollectionFileItem",
+    READONLY_COLLECTION_DIRECTORY_ITEM = "ReadOnlyCollectionDirectoryItem",
+    COLLECTION = "Collection",
+    COLLECTION_ADMIN = "CollectionAdmin",
+    READONLY_COLLECTION = "ReadOnlyCollection",
+    OLD_VERSION_COLLECTION = "OldVersionCollection",
+    TRASHED_COLLECTION = "TrashedCollection",
+    PROCESS = "Process",
+    RUNNING_PROCESS_ADMIN = "RunningProcessAdmin",
+    PROCESS_ADMIN = "ProcessAdmin",
+    RUNNING_PROCESS_RESOURCE = "RunningProcessResource",
+    PROCESS_RESOURCE = "ProcessResource",
+    READONLY_PROCESS_RESOURCE = "ReadOnlyProcessResource",
+    PROCESS_LOGS = "ProcessLogs",
+    REPOSITORY = "Repository",
+    SSH_KEY = "SshKey",
+    VIRTUAL_MACHINE = "VirtualMachine",
+    KEEP_SERVICE = "KeepService",
+    USER = "User",
+    GROUPS = "Group",
+    GROUP_MEMBER = "GroupMember",
+    PERMISSION_EDIT = "PermissionEdit",
+    LINK = "Link",
+    WORKFLOW = "Workflow",
+    READONLY_WORKFLOW = "ReadOnlyWorkflow",
+    SEARCH_RESULTS = "SearchResults",
+    MULTI = "Multi",
+}
+
+
+
+const processOrder = [
+    ContextMenuActionNames.VIEW_DETAILS,
+    ContextMenuActionNames.OPEN_IN_NEW_TAB,
+    ContextMenuActionNames.OUTPUTS,
+    ContextMenuActionNames.API_DETAILS,
+    ContextMenuActionNames.DIVIDER,
+    ContextMenuActionNames.EDIT_PROCESS,
+    ContextMenuActionNames.COPY_AND_RERUN_PROCESS,
+    ContextMenuActionNames.CANCEL,
+    ContextMenuActionNames.REMOVE,
+    ContextMenuActionNames.DIVIDER,
+    ContextMenuActionNames.ADD_TO_FAVORITES,
+    ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES,
+];
+
+const projectOrder = [
+    ContextMenuActionNames.VIEW_DETAILS,
+    ContextMenuActionNames.OPEN_IN_NEW_TAB,
+    ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
+    ContextMenuActionNames.OPEN_WITH_3RD_PARTY_CLIENT,
+    ContextMenuActionNames.API_DETAILS,
+    ContextMenuActionNames.DIVIDER,
+    ContextMenuActionNames.SHARE,
+    ContextMenuActionNames.NEW_PROJECT,
+    ContextMenuActionNames.EDIT_PROJECT,
+    ContextMenuActionNames.MOVE_TO,
+    ContextMenuActionNames.MOVE_TO_TRASH,
+    ContextMenuActionNames.DIVIDER,
+    ContextMenuActionNames.FREEZE_PROJECT,
+    ContextMenuActionNames.ADD_TO_FAVORITES,
+    ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES,
+];
+
+const collectionOrder = [
+    ContextMenuActionNames.VIEW_DETAILS,
+    ContextMenuActionNames.OPEN_IN_NEW_TAB,
+    ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
+    ContextMenuActionNames.OPEN_WITH_3RD_PARTY_CLIENT,
+    ContextMenuActionNames.API_DETAILS,
+    ContextMenuActionNames.DIVIDER,
+    ContextMenuActionNames.SHARE,
+    ContextMenuActionNames.EDIT_COLLECTION,
+    ContextMenuActionNames.MOVE_TO,
+    ContextMenuActionNames.MAKE_A_COPY,
+    ContextMenuActionNames.MOVE_TO_TRASH,
+    ContextMenuActionNames.DIVIDER,
+    ContextMenuActionNames.ADD_TO_FAVORITES,
+    ContextMenuActionNames.ADD_TO_PUBLIC_FAVORITES,
+];
+
+const workflowOrder = [
+    ContextMenuActionNames.VIEW_DETAILS,
+    ContextMenuActionNames.OPEN_IN_NEW_TAB,
+    ContextMenuActionNames.COPY_LINK_TO_CLIPBOARD,
+    ContextMenuActionNames.API_DETAILS,
+    ContextMenuActionNames.DIVIDER,
+    ContextMenuActionNames.RUN_WORKFLOW,
+    ContextMenuActionNames.DELETE_WORKFLOW,
+]
+
+const defaultMultiOrder = [
+    ContextMenuActionNames.MOVE_TO,
+    ContextMenuActionNames.MAKE_A_COPY,
+    ContextMenuActionNames.MOVE_TO_TRASH,
+];
+
+const kindToOrder: Record<string, ContextMenuActionNames[]> = {
+    [ContextMenuKind.MULTI]: defaultMultiOrder,
+
+    [ContextMenuKind.PROCESS]: processOrder,
+    [ContextMenuKind.PROCESS_ADMIN]: processOrder,
+    [ContextMenuKind.PROCESS_RESOURCE]: processOrder,
+    [ContextMenuKind.RUNNING_PROCESS_ADMIN]: processOrder,
+    [ContextMenuKind.RUNNING_PROCESS_RESOURCE]: processOrder,
+
+    [ContextMenuKind.PROJECT]: projectOrder,
+    [ContextMenuKind.PROJECT_ADMIN]: projectOrder,
+    [ContextMenuKind.FROZEN_PROJECT]: projectOrder,
+    [ContextMenuKind.FROZEN_PROJECT_ADMIN]: projectOrder,
+
+    [ContextMenuKind.COLLECTION]: collectionOrder,
+    [ContextMenuKind.COLLECTION_ADMIN]: collectionOrder,
+    [ContextMenuKind.READONLY_COLLECTION]: collectionOrder,
+    [ContextMenuKind.OLD_VERSION_COLLECTION]: collectionOrder,
+
+    [ContextMenuKind.WORKFLOW]: workflowOrder,
+    [ContextMenuKind.READONLY_WORKFLOW]: workflowOrder,
+};
+
+export const menuDirection = {
+    VERTICAL: 'vertical',
+    HORIZONTAL: 'horizontal'
+}
+
+export const sortMenuItems = (menuKind: ContextMenuKind, menuItems: ContextMenuAction[], orthagonality: string): ContextMenuAction[] | MultiSelectMenuAction[] => {
+
+    const preferredOrder = kindToOrder[menuKind];
+    //if no specified order, sort by name
+    if (!preferredOrder) return menuItems.sort(sortByProperty("name"));
+
+    const bucketMap = new Map();
+    const leftovers: ContextMenuAction[] = [];
+
+    // if we have multiple dividers, we need each of them to have a different "name" property
+    let count = 0;
+
+    preferredOrder.forEach((name) => {
+        if (name === ContextMenuActionNames.DIVIDER) {
+            count++;
+            bucketMap.set(`${name}-${count}`, orthagonality === menuDirection.VERTICAL ? verticalMenuDivider : horizontalMenuDivider)
+        } else {
+            bucketMap.set(name, null)
+        }
+    });
+    [...menuItems].forEach((item) => {
+        if (bucketMap.has(item.name)) bucketMap.set(item.name, item);
+        else leftovers.push(item);
+    });
+
+    return Array.from(bucketMap.values()).concat(leftovers).filter((item) => item !== null);
+};
index f65bdabfeb935ef79a93609a5beed7f3988a66d6..c29b627d3646ed1bfa57d0441f1199868367b93c 100644 (file)
@@ -7,7 +7,7 @@ import { InjectedFormProps, Field } from 'redux-form';
 import { WithDialogProps } from 'store/dialog/with-dialog';
 import { CollectionCreateFormDialogData } from 'store/collections/collection-create-actions';
 import { FormDialog } from 'components/form-dialog/form-dialog';
 import { WithDialogProps } from 'store/dialog/with-dialog';
 import { CollectionCreateFormDialogData } from 'store/collections/collection-create-actions';
 import { FormDialog } from 'components/form-dialog/form-dialog';
-import { require } from 'validators/require';
+import { fieldRequire } from 'validators/require';
 import { FileUploaderField } from 'views-components/file-uploader/file-uploader';
 import { WarningCollection } from 'components/warning-collection/warning-collection';
 import { fileUploaderActions } from 'store/file-uploader/file-uploader-actions';
 import { FileUploaderField } from 'views-components/file-uploader/file-uploader';
 import { WarningCollection } from 'components/warning-collection/warning-collection';
 import { fileUploaderActions } from 'store/file-uploader/file-uploader-actions';
@@ -43,6 +43,6 @@ const UploadCollectionFilesFields = () => <>
     <WarningCollection text="Uploading new files will change content address." />
 </>;
 
     <WarningCollection text="Uploading new files will change content address." />
 </>;
 
-const FILES_FIELD_VALIDATION = [require];
+const FILES_FIELD_VALIDATION = [fieldRequire];
 
 
 
 
index 6c5902653bb3cdf21b1416430dfcdd4af6881ad3..7d71078c31d1b69af4e3e636f52e9193acd5d15e 100644 (file)
@@ -13,6 +13,7 @@ import { DispatchProp } from 'react-redux';
 import { saveApiToken } from 'store/auth/auth-action';
 import { navigateToRootProject } from 'store/navigation/navigation-action';
 import { replace } from 'react-router-redux';
 import { saveApiToken } from 'store/auth/auth-action';
 import { navigateToRootProject } from 'store/navigation/navigation-action';
 import { replace } from 'react-router-redux';
+import { PasswordLoginResponse } from 'views/login-panel/login-panel';
 
 type CssRules = 'root' | 'loginBtn' | 'card' | 'wrapper' | 'progress';
 
 
 type CssRules = 'root' | 'loginBtn' | 'card' | 'wrapper' | 'progress';
 
@@ -46,7 +47,7 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
 });
 
 type LoginFormProps = DispatchProp<any> & WithStyles<CssRules> & {
 });
 
 type LoginFormProps = DispatchProp<any> & WithStyles<CssRules> & {
-    handleSubmit: (username: string, password: string) => AxiosPromise;
+    handleSubmit: (username: string, password: string) => AxiosPromise<PasswordLoginResponse>;
     loginLabel?: string,
 };
 
     loginLabel?: string,
 };
 
index 1ce2fa1f0f560fc5518fda8d438b96977b8f526d..af76e4f127daa760b849eb884b26545c5844adf6 100644 (file)
@@ -72,7 +72,7 @@ export const HelpMenu = compose(
                 {
                     links.map(link =>
                         <MenuItem key={link.title}>
                 {
                     links.map(link =>
                         <MenuItem key={link.title}>
-                            <a href={link.link} target="_blank" rel="noopener" className={classes.link}>
+                            <a href={link.link} target="_blank" rel="noopener noreferrer" className={classes.link}>
                                 <ImportContactsIcon className={classes.icon} />
                                 <Typography className={classes.linkTitle}>{link.title}</Typography>
                             </a>
                                 <ImportContactsIcon className={classes.icon} />
                                 <Typography className={classes.linkTitle}>{link.title}</Typography>
                             </a>
index a8a8f45748276dfd6a4578d6c54de7534c0ba13b..19709faec449ef529f716ed7e4005f3f087d76b5 100644 (file)
@@ -2,19 +2,21 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { MoveToIcon, CopyIcon, RenameIcon } from "components/icon/icon";
+import { MoveToIcon, CopyIcon, RenameIcon, ShareIcon } from "components/icon/icon";
 import { openMoveCollectionDialog } from "store/collections/collection-move-actions";
 import { openCollectionCopyDialog, openMultiCollectionCopyDialog } from "store/collections/collection-copy-actions";
 import { toggleCollectionTrashed } from "store/trash/trash-actions";
 import { ContextMenuResource } from "store/context-menu/context-menu-actions";
 import { msCommonActionSet, MultiSelectMenuActionSet, MultiSelectMenuAction } from "./ms-menu-actions";
 import { openMoveCollectionDialog } from "store/collections/collection-move-actions";
 import { openCollectionCopyDialog, openMultiCollectionCopyDialog } from "store/collections/collection-copy-actions";
 import { toggleCollectionTrashed } from "store/trash/trash-actions";
 import { ContextMenuResource } from "store/context-menu/context-menu-actions";
 import { msCommonActionSet, MultiSelectMenuActionSet, MultiSelectMenuAction } from "./ms-menu-actions";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { TrashIcon, Link, FolderSharedIcon } from "components/icon/icon";
 import { openCollectionUpdateDialog } from "store/collections/collection-update-actions";
 import { copyToClipboardAction } from "store/open-in-new-tab/open-in-new-tab.actions";
 import { openWebDavS3InfoDialog } from "store/collections/collection-info-actions";
 import { TrashIcon, Link, FolderSharedIcon } from "components/icon/icon";
 import { openCollectionUpdateDialog } from "store/collections/collection-update-actions";
 import { copyToClipboardAction } from "store/open-in-new-tab/open-in-new-tab.actions";
 import { openWebDavS3InfoDialog } from "store/collections/collection-info-actions";
+import { openSharingDialog } from "store/sharing-dialog/sharing-dialog-actions";
 
 
-const { MAKE_A_COPY, MOVE_TO, MOVE_TO_TRASH, EDIT_COLLECTION, OPEN_IN_NEW_TAB, OPEN_W_3RD_PARTY_CLIENT, COPY_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, ADD_TO_FAVORITES, SHARE} = MultiSelectMenuActionNames;
+
+const { MAKE_A_COPY, MOVE_TO, MOVE_TO_TRASH, EDIT_COLLECTION, OPEN_IN_NEW_TAB, OPEN_WITH_3RD_PARTY_CLIENT, COPY_LINK_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, ADD_TO_FAVORITES, SHARE} = ContextMenuActionNames;
 
 const msCopyCollection: MultiSelectMenuAction = {
     name: MAKE_A_COPY,
 
 const msCopyCollection: MultiSelectMenuAction = {
     name: MAKE_A_COPY,
@@ -48,7 +50,7 @@ const msToggleTrashAction: MultiSelectMenuAction = {
 }
 
 const msEditCollection: MultiSelectMenuAction = {
 }
 
 const msEditCollection: MultiSelectMenuAction = {
-    name: MultiSelectMenuActionNames.EDIT_COLLECTION,
+    name: ContextMenuActionNames.EDIT_COLLECTION,
     icon: RenameIcon,
     hasAlts: false,
     isForMulti: false,
     icon: RenameIcon,
     hasAlts: false,
     isForMulti: false,
@@ -58,7 +60,7 @@ const msEditCollection: MultiSelectMenuAction = {
 }
 
 const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
 }
 
 const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
-    name: COPY_TO_CLIPBOARD,
+    name: COPY_LINK_TO_CLIPBOARD,
     icon: Link,
     hasAlts: false,
     isForMulti: false,
     icon: Link,
     hasAlts: false,
     isForMulti: false,
@@ -68,7 +70,7 @@ const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
 };
 
 const msOpenWith3rdPartyClientAction: MultiSelectMenuAction  = {
 };
 
 const msOpenWith3rdPartyClientAction: MultiSelectMenuAction  = {
-    name: OPEN_W_3RD_PARTY_CLIENT,
+    name: OPEN_WITH_3RD_PARTY_CLIENT,
     icon: FolderSharedIcon,
     hasAlts: false,
     isForMulti: false,
     icon: FolderSharedIcon,
     hasAlts: false,
     isForMulti: false,
@@ -77,6 +79,16 @@ const msOpenWith3rdPartyClientAction: MultiSelectMenuAction  = {
     },
 };
 
     },
 };
 
+const msShareAction: MultiSelectMenuAction  = {
+    name: SHARE,
+    icon: ShareIcon,
+    hasAlts: false,
+    isForMulti: false,
+    execute: (dispatch, resources) => {
+        dispatch<any>(openSharingDialog(resources[0].uuid));
+    },
+};
+
 export const msCollectionActionSet: MultiSelectMenuActionSet = [
     [
         ...msCommonActionSet,
 export const msCollectionActionSet: MultiSelectMenuActionSet = [
     [
         ...msCommonActionSet,
@@ -85,10 +97,11 @@ export const msCollectionActionSet: MultiSelectMenuActionSet = [
         msToggleTrashAction,
         msEditCollection,
         msCopyToClipboardMenuAction,
         msToggleTrashAction,
         msEditCollection,
         msCopyToClipboardMenuAction,
-        msOpenWith3rdPartyClientAction
+        msOpenWith3rdPartyClientAction,
+        msShareAction,
     ],
 ];
 
     ],
 ];
 
-export const msReadOnlyCollectionActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_TO_CLIPBOARD, MAKE_A_COPY, VIEW_DETAILS, API_DETAILS, ADD_TO_FAVORITES, OPEN_W_3RD_PARTY_CLIENT]);
-export const msCommonCollectionActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_TO_CLIPBOARD, MAKE_A_COPY, VIEW_DETAILS, API_DETAILS, OPEN_W_3RD_PARTY_CLIENT, EDIT_COLLECTION, SHARE, MOVE_TO, ADD_TO_FAVORITES, MOVE_TO_TRASH])
-export const msOldCollectionActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_TO_CLIPBOARD, MAKE_A_COPY, VIEW_DETAILS, API_DETAILS, OPEN_W_3RD_PARTY_CLIENT, EDIT_COLLECTION, SHARE, MOVE_TO, ADD_TO_FAVORITES, MOVE_TO_TRASH])
\ No newline at end of file
+export const msReadOnlyCollectionActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_LINK_TO_CLIPBOARD, MAKE_A_COPY, VIEW_DETAILS, API_DETAILS, ADD_TO_FAVORITES, OPEN_WITH_3RD_PARTY_CLIENT]);
+export const msCommonCollectionActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_LINK_TO_CLIPBOARD, MAKE_A_COPY, VIEW_DETAILS, API_DETAILS, OPEN_WITH_3RD_PARTY_CLIENT, EDIT_COLLECTION, SHARE, MOVE_TO, ADD_TO_FAVORITES, MOVE_TO_TRASH])
+export const msOldCollectionActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_LINK_TO_CLIPBOARD, MAKE_A_COPY, VIEW_DETAILS, API_DETAILS, OPEN_WITH_3RD_PARTY_CLIENT, EDIT_COLLECTION, SHARE, MOVE_TO, ADD_TO_FAVORITES, MOVE_TO_TRASH])
\ No newline at end of file
index 91e96d9bfbe002304616782d120f8239850150a4..12840cdea2416daab65cc168a08905e49f2572dc 100644 (file)
@@ -7,42 +7,17 @@ import { IconType } from 'components/icon/icon';
 import { ResourcesState } from 'store/resources/resources';
 import { FavoritesState } from 'store/favorites/favorites-reducer';
 import { ContextMenuResource } from 'store/context-menu/context-menu-actions';
 import { ResourcesState } from 'store/resources/resources';
 import { FavoritesState } from 'store/favorites/favorites-reducer';
 import { ContextMenuResource } from 'store/context-menu/context-menu-actions';
-import { AddFavoriteIcon, AdvancedIcon, DetailsIcon, OpenIcon, PublicFavoriteIcon, RemoveFavoriteIcon, ShareIcon } from 'components/icon/icon';
+import { AddFavoriteIcon, AdvancedIcon, DetailsIcon, OpenIcon, PublicFavoriteIcon, RemoveFavoriteIcon } from 'components/icon/icon';
 import { checkFavorite } from 'store/favorites/favorites-reducer';
 import { toggleFavorite } from 'store/favorites/favorites-actions';
 import { favoritePanelActions } from 'store/favorite-panel/favorite-panel-action';
 import { openInNewTabAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
 import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
 import { checkFavorite } from 'store/favorites/favorites-reducer';
 import { toggleFavorite } from 'store/favorites/favorites-actions';
 import { favoritePanelActions } from 'store/favorite-panel/favorite-panel-action';
 import { openInNewTabAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
 import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
 import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
-import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
 import { togglePublicFavorite } from "store/public-favorites/public-favorites-actions";
 import { publicFavoritePanelActions } from "store/public-favorites-panel/public-favorites-action";
 import { PublicFavoritesState } from 'store/public-favorites/public-favorites-reducer';
 import { togglePublicFavorite } from "store/public-favorites/public-favorites-actions";
 import { publicFavoritePanelActions } from "store/public-favorites-panel/public-favorites-action";
 import { PublicFavoritesState } from 'store/public-favorites/public-favorites-reducer';
-
-export enum MultiSelectMenuActionNames {
-    ADD_TO_FAVORITES = 'Add to Favorites',
-    MOVE_TO_TRASH = 'Move to trash',
-    ADD_TO_PUBLIC_FAVORITES = 'Add to public favorites',
-    API_DETAILS = 'API Details',
-    CANCEL = 'CANCEL',
-    COPY_AND_RERUN_PROCESS = 'Copy and re-run process',
-    COPY_TO_CLIPBOARD = 'Copy to clipboard',
-    DELETE_WORKFLOW = 'Delete Workflow',
-    EDIT_COLLECTION = 'Edit collection',
-    EDIT_PROJECT = 'Edit project',
-    EDIT_PROCESS = 'Edit process',
-    FREEZE_PROJECT = 'Freeze Project',
-    MAKE_A_COPY = 'Make a copy',
-    MOVE_TO = 'Move to',
-    NEW_PROJECT = 'New project',
-    OPEN_IN_NEW_TAB = 'Open in new tab',
-    OPEN_W_3RD_PARTY_CLIENT = 'Open with 3rd party client',
-    OUTPUTS = 'Outputs',
-    REMOVE = 'Remove',
-    RUN_WORKFLOW = 'Run Workflow',
-    SHARE = 'Share',
-    VIEW_DETAILS = 'View details',
-};
+import { ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 
 export type MultiSelectMenuAction = {
     name: string;
 
 export type MultiSelectMenuAction = {
     name: string;
@@ -58,7 +33,7 @@ export type MultiSelectMenuAction = {
 
 export type MultiSelectMenuActionSet = MultiSelectMenuAction[][];
 
 
 export type MultiSelectMenuActionSet = MultiSelectMenuAction[][];
 
-const { ADD_TO_FAVORITES, ADD_TO_PUBLIC_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE } = MultiSelectMenuActionNames;
+const { ADD_TO_FAVORITES, ADD_TO_PUBLIC_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS } = ContextMenuActionNames;
 
 const msToggleFavoriteAction: MultiSelectMenuAction = {
     name: ADD_TO_FAVORITES,
 
 const msToggleFavoriteAction: MultiSelectMenuAction = {
     name: ADD_TO_FAVORITES,
@@ -107,16 +82,6 @@ const msAdvancedAction: MultiSelectMenuAction  = {
     },
 };
 
     },
 };
 
-const msShareAction: MultiSelectMenuAction  = {
-    name: SHARE,
-    icon: ShareIcon,
-    hasAlts: false,
-    isForMulti: false,
-    execute: (dispatch, resources) => {
-        dispatch<any>(openSharingDialog(resources[0].uuid));
-    },
-};
-
 const msTogglePublicFavoriteAction: MultiSelectMenuAction = {
     name: ADD_TO_PUBLIC_FAVORITES,
     icon: PublicFavoriteIcon,
 const msTogglePublicFavoriteAction: MultiSelectMenuAction = {
     name: ADD_TO_PUBLIC_FAVORITES,
     icon: PublicFavoriteIcon,
@@ -139,6 +104,5 @@ export const msCommonActionSet = [
     msOpenInNewTabMenuAction,
     msViewDetailsAction,
     msAdvancedAction,
     msOpenInNewTabMenuAction,
     msViewDetailsAction,
     msAdvancedAction,
-    msShareAction,
     msTogglePublicFavoriteAction
 ];
     msTogglePublicFavoriteAction
 ];
index 7802ad81f12cb303948c8dc7de82655478f21527..73aebe27bcb87ffcf474839923ac94fd8a20beaf 100644 (file)
@@ -2,18 +2,17 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { MoveToIcon, RemoveIcon, ReRunProcessIcon, OutputIcon, RenameIcon, StopIcon } from "components/icon/icon";
-import { openMoveProcessDialog } from "store/processes/process-move-actions";
+import { RemoveIcon, ReRunProcessIcon, OutputIcon, RenameIcon, StopIcon } from "components/icon/icon";
 import { openCopyProcessDialog } from "store/processes/process-copy-actions";
 import { openRemoveProcessDialog } from "store/processes/processes-actions";
 import { MultiSelectMenuAction, MultiSelectMenuActionSet, msCommonActionSet } from "./ms-menu-actions";
 import { openCopyProcessDialog } from "store/processes/process-copy-actions";
 import { openRemoveProcessDialog } from "store/processes/processes-actions";
 import { MultiSelectMenuAction, MultiSelectMenuActionSet, msCommonActionSet } from "./ms-menu-actions";
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { openProcessUpdateDialog } from "store/processes/process-update-actions";
 import { msNavigateToOutput } from "store/multiselect/multiselect-actions";
 import { cancelRunningWorkflow } from "store/processes/processes-actions";
 
 const msCopyAndRerunProcess: MultiSelectMenuAction = {
 import { openProcessUpdateDialog } from "store/processes/process-update-actions";
 import { msNavigateToOutput } from "store/multiselect/multiselect-actions";
 import { cancelRunningWorkflow } from "store/processes/processes-actions";
 
 const msCopyAndRerunProcess: MultiSelectMenuAction = {
-    name: MultiSelectMenuActionNames.COPY_AND_RERUN_PROCESS,
+    name: ContextMenuActionNames.COPY_AND_RERUN_PROCESS,
     icon: ReRunProcessIcon,
     hasAlts: false,
     isForMulti: false,
     icon: ReRunProcessIcon,
     hasAlts: false,
     isForMulti: false,
@@ -25,7 +24,7 @@ const msCopyAndRerunProcess: MultiSelectMenuAction = {
 }
 
 const msRemoveProcess: MultiSelectMenuAction = {
 }
 
 const msRemoveProcess: MultiSelectMenuAction = {
-    name: MultiSelectMenuActionNames.REMOVE,
+    name: ContextMenuActionNames.REMOVE,
     icon: RemoveIcon,
     hasAlts: false,
     isForMulti: true,
     icon: RemoveIcon,
     hasAlts: false,
     isForMulti: true,
@@ -34,18 +33,19 @@ const msRemoveProcess: MultiSelectMenuAction = {
     },
 }
 
     },
 }
 
-const msMoveTo: MultiSelectMenuAction = {
-    name: MultiSelectMenuActionNames.MOVE_TO,
-    icon: MoveToIcon,
-    hasAlts: false,
-    isForMulti: true,
-    execute: (dispatch, resources) => {
-        dispatch<any>(openMoveProcessDialog(resources[0]));
-    },
-}
+// removed until auto-move children is implemented
+// const msMoveTo: MultiSelectMenuAction = {
+//     name: ContextMenuActionNames.MOVE_TO,
+//     icon: MoveToIcon,
+//     hasAlts: false,
+//     isForMulti: true,
+//     execute: (dispatch, resources) => {
+//         dispatch<any>(openMoveProcessDialog(resources[0]));
+//     },
+// }
 
 const msViewOutputs: MultiSelectMenuAction = {
 
 const msViewOutputs: MultiSelectMenuAction = {
-    name: MultiSelectMenuActionNames.OUTPUTS,
+    name: ContextMenuActionNames.OUTPUTS,
     icon: OutputIcon,
     hasAlts: false,
     isForMulti: false,
     icon: OutputIcon,
     hasAlts: false,
     isForMulti: false,
@@ -57,7 +57,7 @@ const msViewOutputs: MultiSelectMenuAction = {
 }
 
 const msEditProcess: MultiSelectMenuAction = {
 }
 
 const msEditProcess: MultiSelectMenuAction = {
-    name: MultiSelectMenuActionNames.EDIT_PROCESS,
+    name: ContextMenuActionNames.EDIT_PROCESS,
     icon: RenameIcon,
     hasAlts: false,
     isForMulti: false,
     icon: RenameIcon,
     hasAlts: false,
     isForMulti: false,
@@ -67,7 +67,7 @@ const msEditProcess: MultiSelectMenuAction = {
 }
 
 const msCancelProcess: MultiSelectMenuAction = {
 }
 
 const msCancelProcess: MultiSelectMenuAction = {
-    name: MultiSelectMenuActionNames.CANCEL,
+    name: ContextMenuActionNames.CANCEL,
     icon: StopIcon,
     hasAlts: false,
     isForMulti: false,
     icon: StopIcon,
     hasAlts: false,
     isForMulti: false,
@@ -81,18 +81,18 @@ export const msProcessActionSet: MultiSelectMenuActionSet = [
         ...msCommonActionSet,
         msCopyAndRerunProcess,
         msRemoveProcess,
         ...msCommonActionSet,
         msCopyAndRerunProcess,
         msRemoveProcess,
-        msMoveTo,
+        // msMoveTo,
         msViewOutputs,
         msEditProcess,
         msCancelProcess
     ]
 ];
 
         msViewOutputs,
         msEditProcess,
         msCancelProcess
     ]
 ];
 
-const { MOVE_TO, REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, ADD_TO_PUBLIC_FAVORITES, OUTPUTS, EDIT_PROCESS, CANCEL } = MultiSelectMenuActionNames
+const {REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, ADD_TO_PUBLIC_FAVORITES, OUTPUTS, EDIT_PROCESS, CANCEL } = ContextMenuActionNames
 
 
-export const msCommonProcessActionFilter = new Set([MOVE_TO, REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, OUTPUTS, EDIT_PROCESS ]);
-export const msRunningProcessActionFilter = new Set([MOVE_TO, REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, OUTPUTS, EDIT_PROCESS, CANCEL ]);
+export const msCommonProcessActionFilter = new Set([REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, OUTPUTS, EDIT_PROCESS ]);
+export const msRunningProcessActionFilter = new Set([REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, OUTPUTS, EDIT_PROCESS, CANCEL ]);
 
 export const msReadOnlyProcessActionFilter = new Set([COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, OUTPUTS ]);
 
 export const msReadOnlyProcessActionFilter = new Set([COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, OUTPUTS ]);
-export const msAdminProcessActionFilter = new Set([MOVE_TO, REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, ADD_TO_PUBLIC_FAVORITES, OUTPUTS, EDIT_PROCESS ]);
+export const msAdminProcessActionFilter = new Set([REMOVE, COPY_AND_RERUN_PROCESS, ADD_TO_FAVORITES, OPEN_IN_NEW_TAB, VIEW_DETAILS, API_DETAILS, SHARE, ADD_TO_PUBLIC_FAVORITES, OUTPUTS, EDIT_PROCESS ]);
 
 
index ee1ea1d1792911ad850791caba0789122441d5fd..0723eaa497b768b7ed15b9dee5a2179644bce204 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { MultiSelectMenuAction, MultiSelectMenuActionSet, msCommonActionSet } from 'views-components/multiselect-toolbar/ms-menu-actions';
 // SPDX-License-Identifier: AGPL-3.0
 
 import { MultiSelectMenuAction, MultiSelectMenuActionSet, msCommonActionSet } from 'views-components/multiselect-toolbar/ms-menu-actions';
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from "views-components/context-menu/context-menu-action-set";
 import { openMoveProjectDialog } from 'store/projects/project-move-actions';
 import { toggleProjectTrashed } from 'store/trash/trash-actions';
 import {
 import { openMoveProjectDialog } from 'store/projects/project-move-actions';
 import { toggleProjectTrashed } from 'store/trash/trash-actions';
 import {
@@ -12,6 +12,7 @@ import {
     NewProjectIcon,
     RenameIcon,
     UnfreezeIcon,
     NewProjectIcon,
     RenameIcon,
     UnfreezeIcon,
+    ShareIcon,
 } from 'components/icon/icon';
 import { RestoreFromTrashIcon, TrashIcon, FolderSharedIcon, Link } from 'components/icon/icon';
 import { getResource } from 'store/resources/resources';
 } from 'components/icon/icon';
 import { RestoreFromTrashIcon, TrashIcon, FolderSharedIcon, Link } from 'components/icon/icon';
 import { getResource } from 'store/resources/resources';
@@ -20,25 +21,26 @@ import { openProjectUpdateDialog } from 'store/projects/project-update-actions';
 import { freezeProject, unfreezeProject } from 'store/projects/project-lock-actions';
 import { openWebDavS3InfoDialog } from 'store/collections/collection-info-actions';
 import { copyToClipboardAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
 import { freezeProject, unfreezeProject } from 'store/projects/project-lock-actions';
 import { openWebDavS3InfoDialog } from 'store/collections/collection-info-actions';
 import { copyToClipboardAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
+import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
 
 const {
     ADD_TO_FAVORITES,
     ADD_TO_PUBLIC_FAVORITES,
     OPEN_IN_NEW_TAB,
 
 const {
     ADD_TO_FAVORITES,
     ADD_TO_PUBLIC_FAVORITES,
     OPEN_IN_NEW_TAB,
-    COPY_TO_CLIPBOARD,
+    COPY_LINK_TO_CLIPBOARD,
     VIEW_DETAILS,
     API_DETAILS,
     VIEW_DETAILS,
     API_DETAILS,
-    OPEN_W_3RD_PARTY_CLIENT,
+    OPEN_WITH_3RD_PARTY_CLIENT,
     EDIT_PROJECT,
     SHARE,
     MOVE_TO,
     MOVE_TO_TRASH,
     FREEZE_PROJECT,
     NEW_PROJECT,
     EDIT_PROJECT,
     SHARE,
     MOVE_TO,
     MOVE_TO_TRASH,
     FREEZE_PROJECT,
     NEW_PROJECT,
-} = MultiSelectMenuActionNames;
+} = ContextMenuActionNames;
 
 const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
 
 const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
-    name: COPY_TO_CLIPBOARD,
+    name: COPY_LINK_TO_CLIPBOARD,
     icon: Link,
     hasAlts: false,
     isForMulti: false,
     icon: Link,
     hasAlts: false,
     isForMulti: false,
@@ -68,7 +70,7 @@ const msMoveToAction: MultiSelectMenuAction = {
 };
 
 const msOpenWith3rdPartyClientAction: MultiSelectMenuAction  = {
 };
 
 const msOpenWith3rdPartyClientAction: MultiSelectMenuAction  = {
-    name: OPEN_W_3RD_PARTY_CLIENT,
+    name: OPEN_WITH_3RD_PARTY_CLIENT,
     icon: FolderSharedIcon,
     hasAlts: false,
     isForMulti: false,
     icon: FolderSharedIcon,
     hasAlts: false,
     isForMulti: false,
@@ -123,6 +125,16 @@ const msNewProjectAction: MultiSelectMenuAction = {
     },
 };
 
     },
 };
 
+const msShareAction: MultiSelectMenuAction  = {
+    name: SHARE,
+    icon: ShareIcon,
+    hasAlts: false,
+    isForMulti: false,
+    execute: (dispatch, resources) => {
+        dispatch<any>(openSharingDialog(resources[0].uuid));
+    },
+};
+
 export const msProjectActionSet: MultiSelectMenuActionSet = [
     [
         ...msCommonActionSet,
 export const msProjectActionSet: MultiSelectMenuActionSet = [
     [
         ...msCommonActionSet,
@@ -132,7 +144,8 @@ export const msProjectActionSet: MultiSelectMenuActionSet = [
         msNewProjectAction,
         msFreezeProjectAction,
         msOpenWith3rdPartyClientAction,
         msNewProjectAction,
         msFreezeProjectAction,
         msOpenWith3rdPartyClientAction,
-        msCopyToClipboardMenuAction
+        msCopyToClipboardMenuAction,
+        msShareAction,
     ],
 ];
 
     ],
 ];
 
@@ -140,19 +153,19 @@ export const msCommonProjectActionFilter = new Set<string>([
     ADD_TO_FAVORITES,
     MOVE_TO_TRASH,
     API_DETAILS,
     ADD_TO_FAVORITES,
     MOVE_TO_TRASH,
     API_DETAILS,
-    COPY_TO_CLIPBOARD,
+    COPY_LINK_TO_CLIPBOARD,
     EDIT_PROJECT,
     FREEZE_PROJECT,
     MOVE_TO,
     NEW_PROJECT,
     OPEN_IN_NEW_TAB,
     EDIT_PROJECT,
     FREEZE_PROJECT,
     MOVE_TO,
     NEW_PROJECT,
     OPEN_IN_NEW_TAB,
-    OPEN_W_3RD_PARTY_CLIENT,
+    OPEN_WITH_3RD_PARTY_CLIENT,
     SHARE,
     VIEW_DETAILS,
 ]);
     SHARE,
     VIEW_DETAILS,
 ]);
-export const msReadOnlyProjectActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_W_3RD_PARTY_CLIENT, VIEW_DETAILS,]);
-export const msFrozenProjectActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_W_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, FREEZE_PROJECT])
-export const msAdminFrozenProjectActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_W_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, FREEZE_PROJECT, ADD_TO_PUBLIC_FAVORITES])
+export const msReadOnlyProjectActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_LINK_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_WITH_3RD_PARTY_CLIENT, VIEW_DETAILS,]);
+export const msFrozenProjectActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_LINK_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_WITH_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, FREEZE_PROJECT])
+export const msAdminFrozenProjectActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_LINK_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_WITH_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, FREEZE_PROJECT, ADD_TO_PUBLIC_FAVORITES])
 
 
-export const msFilterGroupActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_W_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, MOVE_TO_TRASH, EDIT_PROJECT, MOVE_TO])
-export const msAdminFilterGroupActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_W_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, MOVE_TO_TRASH, EDIT_PROJECT, MOVE_TO, ADD_TO_PUBLIC_FAVORITES])
\ No newline at end of file
+export const msFilterGroupActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_LINK_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_WITH_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, MOVE_TO_TRASH, EDIT_PROJECT, MOVE_TO])
+export const msAdminFilterGroupActionFilter = new Set<string>([ADD_TO_FAVORITES, API_DETAILS, COPY_LINK_TO_CLIPBOARD, OPEN_IN_NEW_TAB, OPEN_WITH_3RD_PARTY_CLIENT, VIEW_DETAILS, SHARE, MOVE_TO_TRASH, EDIT_PROJECT, MOVE_TO, ADD_TO_PUBLIC_FAVORITES])
\ No newline at end of file
index ab819df22550b3379743bf599a0c640ecfae9115..9c5cdd79e03cc72e97659b33f1133407b53473db 100644 (file)
@@ -5,10 +5,12 @@
 import { openRunProcess, deleteWorkflow } from 'store/workflow-panel/workflow-panel-actions';
 import { StartIcon, TrashIcon, Link } from 'components/icon/icon';
 import { MultiSelectMenuAction, MultiSelectMenuActionSet, msCommonActionSet } from './ms-menu-actions';
 import { openRunProcess, deleteWorkflow } from 'store/workflow-panel/workflow-panel-actions';
 import { StartIcon, TrashIcon, Link } from 'components/icon/icon';
 import { MultiSelectMenuAction, MultiSelectMenuActionSet, msCommonActionSet } from './ms-menu-actions';
-import { MultiSelectMenuActionNames } from "views-components/multiselect-toolbar/ms-menu-actions";
+import { ContextMenuActionNames } from 'views-components/context-menu/context-menu-action-set';
 import { copyToClipboardAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
 import { copyToClipboardAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
+import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
+import { ShareIcon } from 'components/icon/icon';
 
 
-const { OPEN_IN_NEW_TAB, COPY_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, RUN_WORKFLOW, DELETE_WORKFLOW } = MultiSelectMenuActionNames;
+const { OPEN_IN_NEW_TAB, COPY_LINK_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, RUN_WORKFLOW, DELETE_WORKFLOW, SHARE } = ContextMenuActionNames;
 
 const msRunWorkflow: MultiSelectMenuAction = {
     name: RUN_WORKFLOW,
 
 const msRunWorkflow: MultiSelectMenuAction = {
     name: RUN_WORKFLOW,
@@ -31,7 +33,7 @@ const msDeleteWorkflow: MultiSelectMenuAction = {
 };
 
 const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
 };
 
 const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
-    name: COPY_TO_CLIPBOARD,
+    name: COPY_LINK_TO_CLIPBOARD,
     icon: Link,
     hasAlts: false,
     isForMulti: false,
     icon: Link,
     hasAlts: false,
     isForMulti: false,
@@ -40,7 +42,17 @@ const msCopyToClipboardMenuAction: MultiSelectMenuAction  = {
     },
 };
 
     },
 };
 
-export const msWorkflowActionSet: MultiSelectMenuActionSet = [[...msCommonActionSet, msRunWorkflow, msDeleteWorkflow, msCopyToClipboardMenuAction]];
+const msShareAction: MultiSelectMenuAction  = {
+    name: SHARE,
+    icon: ShareIcon,
+    hasAlts: false,
+    isForMulti: false,
+    execute: (dispatch, resources) => {
+        dispatch<any>(openSharingDialog(resources[0].uuid));
+    },
+};
+
+export const msWorkflowActionSet: MultiSelectMenuActionSet = [[...msCommonActionSet, msRunWorkflow, msDeleteWorkflow, msCopyToClipboardMenuAction, msShareAction]];
 
 
-export const msReadOnlyWorkflowActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, RUN_WORKFLOW ]);
-export const msWorkflowActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, RUN_WORKFLOW, DELETE_WORKFLOW]);
+export const msReadOnlyWorkflowActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_LINK_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, RUN_WORKFLOW ]);
+export const msWorkflowActionFilter = new Set([OPEN_IN_NEW_TAB, COPY_LINK_TO_CLIPBOARD, VIEW_DETAILS, API_DETAILS, RUN_WORKFLOW, DELETE_WORKFLOW]);
index 7df99300f7b3312f9f99a09000d42b7d0ba2f9f4..a76ab0f6d0b996db7b430dd204f8d8ff3e5e365b 100644 (file)
@@ -54,8 +54,8 @@ export const RepositoriesSampleGitDialog = compose(
                         lines={[snippetText(props.data.uuidPrefix)]} />
                     <Typography variant='body1' className={props.classes.spacing}>
                         See also:
                         lines={[snippetText(props.data.uuidPrefix)]} />
                     <Typography variant='body1' className={props.classes.spacing}>
                         See also:
-                        <div><a href="https://doc.arvados.org/user/getting_started/ssh-access-unix.html" className={props.classes.link} target="_blank" rel="noopener">SSH access</a></div>
-                        <div><a href="https://doc.arvados.org/user/tutorials/tutorial-firstscript.html" className={props.classes.link} target="_blank" rel="noopener">Writing a Crunch Script</a></div>
+                        <div><a href="https://doc.arvados.org/user/getting_started/ssh-access-unix.html" className={props.classes.link} target="_blank" rel="noopener noreferrer">SSH access</a></div>
+                        <div><a href="https://doc.arvados.org/user/tutorials/tutorial-firstscript.html" className={props.classes.link} target="_blank" rel="noopener noreferrer">Writing a Crunch Script</a></div>
                     </Typography>
                 </DialogContent>
                 <DialogActions>
                     </Typography>
                 </DialogContent>
                 <DialogActions>
index 3c4471f6489487cfe89ed322189ee5555b6b0a98..5201ba00d5538e7dc1e8a3600ca9a8821a50c10a 100644 (file)
@@ -47,6 +47,7 @@ export const formatPermissionLevel = (value: PermissionLevel) => {
 export const PermissionSelect = (props: SelectProps) =>
     <Select
         {...props}
 export const PermissionSelect = (props: SelectProps) =>
     <Select
         {...props}
+        disableUnderline
         renderValue={renderPermissionItem}>
         <MenuItem value={PermissionSelectValue.READ}>
             {renderPermissionItem(PermissionSelectValue.READ)}
         renderValue={renderPermissionItem}>
         <MenuItem value={PermissionSelectValue.READ}>
             {renderPermissionItem(PermissionSelectValue.READ)}
index 2fc4d01ad6e27b93819f079a4f0373c8ae6332be..86370a1e21d3a0614b09554e0d12aaa6f037192f 100644 (file)
@@ -8,7 +8,8 @@ import Adapter from 'enzyme-adapter-react-16';
 import { Provider } from 'react-redux';
 import { combineReducers, createStore } from 'redux';
 
 import { Provider } from 'react-redux';
 import { combineReducers, createStore } from 'redux';
 
-import SharingDialogComponent, {
+import {
+    SharingDialogComponent,
     SharingDialogComponentProps,
 } from './sharing-dialog-component';
 import {
     SharingDialogComponentProps,
 } from './sharing-dialog-component';
 import {
index f83cec60f24ec2662a73b10fdb3764e7f332c324..919dbe76f1368dfc5c2ee1dc54869e6b492fc15b 100644 (file)
@@ -62,7 +62,7 @@ enum SharingDialogTab {
 }
 export type SharingDialogComponentProps = SharingDialogDataProps & SharingDialogActionProps;
 
 }
 export type SharingDialogComponentProps = SharingDialogDataProps & SharingDialogActionProps;
 
-export default (props: SharingDialogComponentProps) => {
+export const SharingDialogComponent = (props: SharingDialogComponentProps) => {
     const { open, loading, saveEnabled, sharedResourceUuid,
         sharingURLsNr, privateAccess, sharingURLsDisabled,
         onClose, onSave, onCreateSharingToken, refreshPermissions } = props;
     const { open, loading, saveEnabled, sharedResourceUuid,
         sharingURLsNr, privateAccess, sharingURLsDisabled,
         onClose, onSave, onCreateSharingToken, refreshPermissions } = props;
@@ -90,7 +90,7 @@ export default (props: SharingDialogComponentProps) => {
         fullWidth
         maxWidth='sm'
         disableBackdropClick={saveEnabled}
         fullWidth
         maxWidth='sm'
         disableBackdropClick={saveEnabled}
-        disableEscapeKeyDown={saveEnabled}>
+        >
         <DialogTitle>
             Sharing settings
         </DialogTitle>
         <DialogTitle>
             Sharing settings
         </DialogTitle>
@@ -111,7 +111,7 @@ export default (props: SharingDialogComponentProps) => {
             {tabNr === SharingDialogTab.PERMISSIONS &&
                 <Grid container direction='column' spacing={24}>
                     <Grid item>
             {tabNr === SharingDialogTab.PERMISSIONS &&
                 <Grid container direction='column' spacing={24}>
                     <Grid item>
-                        <SharingInvitationForm onSave={onSave} saveEnabled={saveEnabled} />
+                        <SharingInvitationForm onSave={onSave} />
                     </Grid>
                     <Grid item>
                         <SharingManagementForm onSave={onSave} />
                     </Grid>
                     <Grid item>
                         <SharingManagementForm onSave={onSave} />
@@ -182,8 +182,25 @@ export default (props: SharingDialogComponentProps) => {
                     <Button onClick={() => {
                         onClose();
                         setWithExpiration(false);
                     <Button onClick={() => {
                         onClose();
                         setWithExpiration(false);
-                    }}>
-                        Close
+                        }}
+                        disabled={saveEnabled}
+                        color='primary'
+                        size='small'
+                        style={{ marginLeft: '10px' }}
+                        >
+                            Close
+                    </Button>
+                    <Button onClick={() => {
+                            onSave();
+                        }}
+                        data-cy="add-invited-people"
+                        disabled={!saveEnabled}
+                        color='primary'
+                        variant='contained'
+                        size='small'
+                        style={{ marginLeft: '10px' }}
+                        >
+                            Save
                     </Button>
                 </Grid>
             </Grid>
                     </Button>
                 </Grid>
             </Grid>
index 1c9e4d0393fe23d5956542260bf2f4da18d0848a..b5f2bd74cce54c444606cbb7877fea6a9de1106e 100644 (file)
@@ -15,7 +15,8 @@ import {
     initializeManagementForm
 } from 'store/sharing-dialog/sharing-dialog-actions';
 import { WithDialogProps } from 'store/dialog/with-dialog';
     initializeManagementForm
 } from 'store/sharing-dialog/sharing-dialog-actions';
 import { WithDialogProps } from 'store/dialog/with-dialog';
-import SharingDialogComponent, {
+import {
+    SharingDialogComponent,
     SharingDialogDataProps,
     SharingDialogActionProps
 } from './sharing-dialog-component';
     SharingDialogDataProps,
     SharingDialogActionProps
 } from './sharing-dialog-component';
index 871ea503ecee45b0281eeb0bdd81d12138130c9d..cf463fa76a46382573d969b7fb18efa51cc0e66b 100644 (file)
@@ -4,61 +4,33 @@
 
 import React from 'react';
 import { Field, WrappedFieldProps, FieldArray, WrappedFieldArrayProps } from 'redux-form';
 
 import React from 'react';
 import { Field, WrappedFieldProps, FieldArray, WrappedFieldArrayProps } from 'redux-form';
-import { Grid, FormControl, InputLabel, Tooltip, IconButton, StyleRulesCallback } from '@material-ui/core';
+import { Grid, FormControl, InputLabel, StyleRulesCallback } from '@material-ui/core';
 import { PermissionSelect, parsePermissionLevel, formatPermissionLevel } from './permission-select';
 import { ParticipantSelect, Participant } from './participant-select';
 import { PermissionSelect, parsePermissionLevel, formatPermissionLevel } from './permission-select';
 import { ParticipantSelect, Participant } from './participant-select';
-import { AddIcon } from 'components/icon/icon';
 import { WithStyles } from '@material-ui/core/styles';
 import withStyles from '@material-ui/core/styles/withStyles';
 import { ArvadosTheme } from 'common/custom-theme';
 
 import { WithStyles } from '@material-ui/core/styles';
 import withStyles from '@material-ui/core/styles/withStyles';
 import { ArvadosTheme } from 'common/custom-theme';
 
-type SharingStyles = 'root' | 'addButtonRoot' | 'addButtonPrimary' | 'addButtonDisabled';
+type SharingStyles = 'root';
 
 const styles: StyleRulesCallback<SharingStyles> = (theme: ArvadosTheme) => ({
     root: {
         padding: `${theme.spacing.unit}px 0`,
     },
 
 const styles: StyleRulesCallback<SharingStyles> = (theme: ArvadosTheme) => ({
     root: {
         padding: `${theme.spacing.unit}px 0`,
     },
-    addButtonRoot: {
-        height: "36px",
-        width: "36px",
-        marginRight: "6px",
-        marginLeft: "6px",
-        marginTop: "12px",
-    },
-    addButtonPrimary: {
-        color: theme.palette.primary.contrastText,
-        background: theme.palette.primary.main,
-        "&:hover": {
-            background: theme.palette.primary.dark,
-        }
-    },
-    addButtonDisabled: {
-        background: 'none',
-    }
 });
 
 });
 
-const SharingInvitationFormComponent = (props: { onSave: () => void, saveEnabled: boolean }) => <StyledSharingInvitationFormComponent onSave={props.onSave} saveEnabled={props.saveEnabled} />
+const SharingInvitationFormComponent = (props: { onSave: () => void }) => <StyledSharingInvitationFormComponent onSave={props.onSave} />
 
 export default SharingInvitationFormComponent;
 
 const StyledSharingInvitationFormComponent = withStyles(styles)(
 
 export default SharingInvitationFormComponent;
 
 const StyledSharingInvitationFormComponent = withStyles(styles)(
-    ({ onSave, saveEnabled, classes }: { onSave: () => void, saveEnabled: boolean } & WithStyles<SharingStyles>) =>
+    ({ classes }: { onSave: () => void } & WithStyles<SharingStyles>) =>
         <Grid container spacing={8} wrap='nowrap' className={classes.root} >
             <Grid data-cy="invite-people-field" item xs={8}>
                 <InvitedPeopleField />
             </Grid>
             <Grid data-cy="permission-select-field" item xs={4} container wrap='nowrap'>
                 <PermissionSelectField />
         <Grid container spacing={8} wrap='nowrap' className={classes.root} >
             <Grid data-cy="invite-people-field" item xs={8}>
                 <InvitedPeopleField />
             </Grid>
             <Grid data-cy="permission-select-field" item xs={4} container wrap='nowrap'>
                 <PermissionSelectField />
-                <IconButton onClick={onSave} disabled={!saveEnabled} color="primary" classes={{
-                    root: classes.addButtonRoot,
-                    colorPrimary: classes.addButtonPrimary,
-                    disabled: classes.addButtonDisabled
-                }}
-                    data-cy='add-invited-people'>
-                    <Tooltip title="Add authorization">
-                        <AddIcon />
-                    </Tooltip>
-                </IconButton>
             </Grid>
         </Grid >);
 
             </Grid>
         </Grid >);
 
index 33154732256233fa7d81838879567027894f0bab..46f94dd3484876277604fa8372e27179c595a0b6 100644 (file)
@@ -14,7 +14,6 @@ interface InvitationFormData {
 
 interface SaveProps {
     onSave: () => void;
 
 interface SaveProps {
     onSave: () => void;
-    saveEnabled: boolean;
 }
 
 export const SharingInvitationForm =
 }
 
 export const SharingInvitationForm =
index b7ac8ced7612c1a234f7c64f5e61c037bb516660..fa3cc4618924f014768e9999f0469314e310a243 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { Grid, StyleRulesCallback, Divider, IconButton, Typography } from '@material-ui/core';
+import { Grid, StyleRulesCallback, Divider, IconButton, Typography, Tooltip } from '@material-ui/core';
 import {
     Field,
     WrappedFieldProps,
 import {
     Field,
     WrappedFieldProps,
@@ -52,9 +52,16 @@ const PermissionManagementRow = withStyles(permissionManagementRowStyles)(
     ({ field, index, fields, classes, onSave }: { field: string, index: number, fields: FieldArrayFieldsProps<{ email: string }>, onSave: () => void; } & WithStyles<'root'>) =>
         <>
             <Grid container alignItems='center' spacing={8} wrap='nowrap' className={classes.root}>
     ({ field, index, fields, classes, onSave }: { field: string, index: number, fields: FieldArrayFieldsProps<{ email: string }>, onSave: () => void; } & WithStyles<'root'>) =>
         <>
             <Grid container alignItems='center' spacing={8} wrap='nowrap' className={classes.root}>
-                <Grid item xs={8}>
+                <Grid item xs={7}>
                     <Typography noWrap variant='subtitle1'>{fields.get(index).email}</Typography>
                 </Grid>
                     <Typography noWrap variant='subtitle1'>{fields.get(index).email}</Typography>
                 </Grid>
+                <Grid item xs={1} container wrap='nowrap'>
+                    <Tooltip title='Remove access'>
+                        <IconButton onClick={() => { fields.remove(index); onSave(); }}>
+                            <CloseIcon />
+                        </IconButton>
+                    </Tooltip>
+                </Grid>
                 <Grid item xs={4} container wrap='nowrap'>
                     <Field
                         name={`${field}.permissions` as string}
                 <Grid item xs={4} container wrap='nowrap'>
                     <Field
                         name={`${field}.permissions` as string}
@@ -63,9 +70,7 @@ const PermissionManagementRow = withStyles(permissionManagementRowStyles)(
                         parse={parsePermissionLevel}
                         onChange={onSave}
                     />
                         parse={parsePermissionLevel}
                         onChange={onSave}
                     />
-                    <IconButton onClick={() => { fields.remove(index); onSave(); }}>
-                        <CloseIcon />
-                    </IconButton>
+                    
                 </Grid>
             </Grid>
             <Divider />
                 </Grid>
             </Grid>
             <Divider />
index 5fc3f4e38ecce5a0ac7adb24568ea16b5e956e2e..161cff58c7d269f6b0ad14219984228348a459f1 100644 (file)
@@ -29,13 +29,13 @@ const SharingPublicAccessForm = withStyles(sharingPublicAccessStyles)(
     ({ classes, visibility, includePublic, onSave }: WithStyles<'root' | 'heading'> & AccessProps) =>
         <>
             <Typography className={classes.heading}>General access</Typography>
     ({ classes, visibility, includePublic, onSave }: WithStyles<'root' | 'heading'> & AccessProps) =>
         <>
             <Typography className={classes.heading}>General access</Typography>
-            <Grid container alignItems='center' spacing={8} className={classes.root}>
+            <Grid container alignItems='center' className={classes.root}>
                 <Grid item xs={8}>
                     <Typography variant='subtitle1'>
                         {renderVisibilityInfo(visibility)}
                     </Typography>
                 </Grid>
                 <Grid item xs={8}>
                     <Typography variant='subtitle1'>
                         {renderVisibilityInfo(visibility)}
                     </Typography>
                 </Grid>
-                <Grid item xs={4} container wrap='nowrap'>
+                <Grid item xs={4} wrap='nowrap'>
                     <Field<{ includePublic: boolean }> name='visibility' component={VisibilityLevelSelectComponent} includePublic={includePublic} onChange={onSave} />
                 </Grid>
             </Grid>
                     <Field<{ includePublic: boolean }> name='visibility' component={VisibilityLevelSelectComponent} includePublic={includePublic} onChange={onSave} />
                 </Grid>
             </Grid>
index c17fadd5f09a88c16f183642fbc656752162454f..7bb05fa0dcea0887e7fc8a19794d5c580e445455 100644 (file)
@@ -78,7 +78,7 @@ export const SharingURLsComponent = withStyles(styles)((props: SharingURLsCompon
                     </Grid>
                     <Grid item xs />
                     <Grid item>
                     </Grid>
                     <Grid item xs />
                     <Grid item>
-                        <span className={props.classes.sharingUrlButton}><Tooltip title='Copy to clipboard'>
+                        <span className={props.classes.sharingUrlButton}><Tooltip title='Copy link to clipboard'>
                             <CopyToClipboard text={url} onCopy={() => props.onCopy('Sharing URL copied')}>
                                 <CopyIcon />
                             </CopyToClipboard>
                             <CopyToClipboard text={url} onCopy={() => props.onCopy('Sharing URL copied')}>
                                 <CopyIcon />
                             </CopyToClipboard>
index 4f12e3eacd203b6ece64a848abcd0d36a8da6746..b90bc79c9da930c0a9f6fec68248d5dfd434d920 100644 (file)
@@ -17,7 +17,6 @@ type VisibilityLevelSelectClasses = 'root';
 
 const VisibilityLevelSelectStyles: StyleRulesCallback<VisibilityLevelSelectClasses> = theme => ({
     root: {
 
 const VisibilityLevelSelectStyles: StyleRulesCallback<VisibilityLevelSelectClasses> = theme => ({
     root: {
-        marginLeft: theme.spacing.unit,
     }
 });
 export const VisibilityLevelSelect = withStyles(VisibilityLevelSelectStyles)(
     }
 });
 export const VisibilityLevelSelect = withStyles(VisibilityLevelSelectStyles)(
index 400bb1e68724d88d58b0f42819d930755432dc1f..ec68beab9da81f3a63de20a07ca8a53d8603cb5c 100644 (file)
@@ -88,7 +88,7 @@ describe('<CurrentTokenDialog />', () => {
     });
   });
 
     });
   });
 
-  describe('copy to clipboard button', () => {
+  describe('Copy link to clipboard button', () => {
     beforeEach(() => {
       wrapper = mount(
         <Provider store={store}>
     beforeEach(() => {
       wrapper = mount(
         <Provider store={store}>
index e6f3ed582ccd537ef40ba6ed74f548ae3312dc82..ad1093d7e58ecf881eca34c5c7c75097840732a9 100644 (file)
@@ -140,7 +140,7 @@ unset ARVADOS_API_HOST_INSECURE`
                         variant="contained"
                         className={classes.actionButton}
                     >
                         variant="contained"
                         className={classes.actionButton}
                     >
-                        COPY TO CLIPBOARD
+                        Copy link to clipBOARD
                     </Button>
                 </CopyToClipboard>
                 <Typography>
                     </Button>
                 </CopyToClipboard>
                 <Typography>
index 5aab053d8efc1914098a32a822a65c16f27702ba..a32044a711ef36a70820596ceb509d5a976665e9 100644 (file)
@@ -170,7 +170,7 @@ export const WebDavS3InfoDialog = compose(
 
                     <DetailsAttribute
                         label='Internet address'
 
                     <DetailsAttribute
                         label='Internet address'
-                        value={<a href={winDav.toString()} target="_blank" rel="noopener">{winDav.toString()}</a>}
+                        value={<a href={winDav.toString()} target="_blank" rel="noopener noreferrer">{winDav.toString()}</a>}
                         copyValue={winDav.toString()} />
 
                     <DetailsAttribute
                         copyValue={winDav.toString()} />
 
                     <DetailsAttribute
@@ -202,7 +202,7 @@ export const WebDavS3InfoDialog = compose(
                 <TabPanel index={0} value={activeTab}>
                     <DetailsAttribute
                         label='Server'
                 <TabPanel index={0} value={activeTab}>
                     <DetailsAttribute
                         label='Server'
-                        value={<a href={cyberDavStr} target="_blank" rel="noopener">{cyberDavStr}</a>}
+                        value={<a href={cyberDavStr} target="_blank" rel="noopener noreferrer">{cyberDavStr}</a>}
                         copyValue={cyberDavStr} />
 
                     <DetailsAttribute
                         copyValue={cyberDavStr} />
 
                     <DetailsAttribute
index 86c85b5c97f4c909b0c7c8464f1a3dcc0af9180a..e7f682b052fefbb6c5cc7ee3aa4f73679e560c07 100644 (file)
@@ -14,7 +14,7 @@ import { ResourceName } from 'views-components/data-explorer/renderers';
 import { createTree } from 'models/tree';
 import { GROUPS_PANEL_ID, openCreateGroupDialog } from 'store/groups-panel/groups-panel-actions';
 import { noop } from 'lodash/fp';
 import { createTree } from 'models/tree';
 import { GROUPS_PANEL_ID, openCreateGroupDialog } from 'store/groups-panel/groups-panel-actions';
 import { noop } from 'lodash/fp';
-import { ContextMenuKind } from 'views-components/context-menu/context-menu';
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { getResource, ResourcesState } from 'store/resources/resources';
 import { GroupResource } from 'models/group';
 import { RootState } from 'store/store';
 import { getResource, ResourcesState } from 'store/resources/resources';
 import { GroupResource } from 'models/group';
 import { RootState } from 'store/store';
index f834b3b6dfcaf2346890fd9d38da848a20f60ad4..452a66672a327bd9fd67db950efb295136d96efe 100644 (file)
@@ -10,7 +10,7 @@ import { login, authActions } from 'store/auth/auth-action';
 import { ArvadosTheme } from 'common/custom-theme';
 import { RootState } from 'store/store';
 import { LoginForm } from 'views-components/login-form/login-form';
 import { ArvadosTheme } from 'common/custom-theme';
 import { RootState } from 'store/store';
 import { LoginForm } from 'views-components/login-form/login-form';
-import Axios from 'axios';
+import Axios, { AxiosResponse } from 'axios';
 import { Config } from 'common/config';
 import { sanitizeHTML } from 'common/html-sanitize';
 
 import { Config } from 'common/config';
 import { sanitizeHTML } from 'common/html-sanitize';
 
@@ -51,11 +51,17 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     }
 });
 
     }
 });
 
+export type PasswordLoginResponse = {
+    uuid?: string;
+    api_token?: string;
+    message?: string;
+};
+
 const doPasswordLogin = (url: string) => (username: string, password: string) => {
     const formData: string[] = [];
     formData.push('username='+encodeURIComponent(username));
     formData.push('password='+encodeURIComponent(password));
 const doPasswordLogin = (url: string) => (username: string, password: string) => {
     const formData: string[] = [];
     formData.push('username='+encodeURIComponent(username));
     formData.push('password='+encodeURIComponent(password));
-    return Axios.post(`${url}/arvados/v1/users/authenticate`, formData.join('&'), {
+    return Axios.post<string, AxiosResponse<PasswordLoginResponse>>(`${url}/arvados/v1/users/authenticate`, formData.join('&'), {
         headers: {
             'Content-Type': 'application/x-www-form-urlencoded'
         },
         headers: {
             'Content-Type': 'application/x-www-form-urlencoded'
         },
index d8368449cbad9902f818d379c0270d781b19070d..6cef09b4a898c73c5b2fa978de657d4dbc5213c3 100644 (file)
@@ -18,10 +18,10 @@ import {
 import { ArvadosTheme } from 'common/custom-theme';
 import { CloseIcon, CommandIcon, CopyIcon } from 'components/icon/icon';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
 import { ArvadosTheme } from 'common/custom-theme';
 import { CloseIcon, CommandIcon, CopyIcon } from 'components/icon/icon';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
-import { DefaultCodeSnippet } from 'components/default-code-snippet/default-code-snippet';
+import { DefaultVirtualCodeSnippet } from 'components/default-code-snippet/default-virtual-code-snippet';
 import { Process } from 'store/processes/process';
 import shellescape from 'shell-escape';
 import { Process } from 'store/processes/process';
 import shellescape from 'shell-escape';
-import CopyToClipboard from 'react-copy-to-clipboard';
+import CopyResultToClipboard from 'components/copy-to-clipboard/copy-result-to-clipboard';
 
 type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader';
 
 
 type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader';
 
@@ -31,7 +31,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     },
     header: {
         paddingTop: theme.spacing.unit,
     },
     header: {
         paddingTop: theme.spacing.unit,
-        paddingBottom: theme.spacing.unit,
+        paddingBottom: 0,
     },
     iconHeader: {
         fontSize: '1.875rem',
     },
     iconHeader: {
         fontSize: '1.875rem',
@@ -42,8 +42,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         paddingTop: theme.spacing.unit * 0.5
     },
     content: {
         paddingTop: theme.spacing.unit * 0.5
     },
     content: {
+        height: `calc(100% - ${theme.spacing.unit * 6}px)`,
         padding: theme.spacing.unit * 1.0,
         padding: theme.spacing.unit * 1.0,
-        paddingTop: theme.spacing.unit * 0.5,
+        paddingTop: 0,
         '&:last-child': {
             paddingBottom: theme.spacing.unit * 1,
         }
         '&:last-child': {
             paddingBottom: theme.spacing.unit * 1,
         }
@@ -52,7 +53,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         overflow: 'hidden',
         paddingTop: theme.spacing.unit * 0.5,
         color: theme.customs.colors.greyD,
         overflow: 'hidden',
         paddingTop: theme.spacing.unit * 0.5,
         color: theme.customs.colors.greyD,
-        fontSize: '1.875rem'  
+        fontSize: '1.875rem'
     },
 });
 
     },
 });
 
@@ -70,18 +71,23 @@ export const ProcessCmdCard = withStyles(styles)(
     classes,
     doHidePanel,
   }: ProcessCmdCardProps) => {
     classes,
     doHidePanel,
   }: ProcessCmdCardProps) => {
-    const command = process.containerRequest.command.map((v) =>
-      shellescape([v]) // Escape each arg separately
-    );
 
 
-    let formattedCommand = [...command];
-    formattedCommand.forEach((item, i, arr) => {
+    const formatLine = (lines: string[], index: number): string => {
+      // Escape each arg separately
+      let line = shellescape([lines[index]])
       // Indent lines after the first
       // Indent lines after the first
-      const indent = i > 0 ? '  ' : '';
-      // Escape newlines on every non-last arg when there are multiple lines
-      const lineBreak = arr.length > 1 && i < arr.length - 1 ? ' \\' : '';
-      arr[i] = `${indent}${item}${lineBreak}`;
-    });
+      const indent = index > 0 ? '  ' : '';
+      // Add backslash "escaped linebreak"
+      const lineBreak = lines.length > 1 && index < lines.length - 1 ? ' \\' : '';
+
+      return `${indent}${line}${lineBreak}`;
+    };
+
+    const formatClipboardText = (command: string[]) => (): string => (
+      command.map((v) =>
+        shellescape([v]) // Escape each arg separately
+      ).join(' ')
+    );
 
     return (
       <Card className={classes.card}>
 
     return (
       <Card className={classes.card}>
@@ -100,14 +106,14 @@ export const ProcessCmdCard = withStyles(styles)(
           action={
             <Grid container direction="row" alignItems="center">
               <Grid item>
           action={
             <Grid container direction="row" alignItems="center">
               <Grid item>
-                <Tooltip title="Copy to clipboard" disableFocusListener>
+                <Tooltip title="Copy link to clipboard" disableFocusListener>
                   <IconButton>
                   <IconButton>
-                    <CopyToClipboard
-                      text={command.join(" ")}
+                    <CopyResultToClipboard
+                      getText={formatClipboardText(process.containerRequest.command)}
                       onCopy={() => onCopy("Command copied to clipboard")}
                     >
                       <CopyIcon />
                       onCopy={() => onCopy("Command copied to clipboard")}
                     >
                       <CopyIcon />
-                    </CopyToClipboard>
+                    </CopyResultToClipboard>
                   </IconButton>
                 </Tooltip>
               </Grid>
                   </IconButton>
                 </Tooltip>
               </Grid>
@@ -127,7 +133,11 @@ export const ProcessCmdCard = withStyles(styles)(
           }
         />
         <CardContent className={classes.content}>
           }
         />
         <CardContent className={classes.content}>
-          <DefaultCodeSnippet lines={formattedCommand} linked />
+          <DefaultVirtualCodeSnippet
+            lines={process.containerRequest.command}
+            lineFormatter={formatLine}
+            linked
+          />
         </CardContent>
       </Card>
     );
         </CardContent>
       </Card>
     );
index 292f6cccf7d16e5faa42d44b52e36ca4bd494fa1..38061e3f0652a9eb0febd10ca3fa8bfa3a04abaa 100644 (file)
@@ -11,12 +11,15 @@ import Adapter from "enzyme-adapter-react-16";
 import { Provider } from 'react-redux';
 import { ProcessIOCard, ProcessIOCardType } from './process-io-card';
 import { DefaultView } from "components/default-view/default-view";
 import { Provider } from 'react-redux';
 import { ProcessIOCard, ProcessIOCardType } from './process-io-card';
 import { DefaultView } from "components/default-view/default-view";
-import { DefaultCodeSnippet } from "components/default-code-snippet/default-code-snippet";
+import { DefaultVirtualCodeSnippet } from "components/default-code-snippet/default-virtual-code-snippet";
 import { ProcessOutputCollectionFiles } from './process-output-collection-files';
 import { MemoryRouter } from 'react-router-dom';
 
 import { ProcessOutputCollectionFiles } from './process-output-collection-files';
 import { MemoryRouter } from 'react-router-dom';
 
-
+// Mock collection files component since it just needs to exist
 jest.mock('views/process-panel/process-output-collection-files');
 jest.mock('views/process-panel/process-output-collection-files');
+// Mock autosizer for the io panel virtual list
+jest.mock('react-virtualized-auto-sizer', () => ({ children }: any) => children({ height: 600, width: 600 }));
+
 configure({ adapter: new Adapter() });
 
 describe('renderers', () => {
 configure({ adapter: new Adapter() });
 
 describe('renderers', () => {
@@ -108,12 +111,12 @@ describe('renderers', () => {
             // then
             expect(panel.find(CircularProgress).exists()).toBeFalsy();
             expect(panel.find(Tab).length).toBe(1);
             // then
             expect(panel.find(CircularProgress).exists()).toBeFalsy();
             expect(panel.find(Tab).length).toBe(1);
-            expect(panel.find(DefaultCodeSnippet).text()).toContain(JSON.stringify(raw, null, 2));
+            expect(panel.find(DefaultVirtualCodeSnippet).text()).toContain(JSON.stringify(raw, null, 2).replace(/\n/g, ''));
         });
 
         it('shows main process with params', () => {
             // when
         });
 
         it('shows main process with params', () => {
             // when
-            const parameters = [{id: 'someId', label: 'someLabel', value: [{display: 'someValue'}]}];
+            const parameters = [{id: 'someId', label: 'someLabel', value: {display: 'someValue'}}];
             let panel = mount(
                 <Provider store={store}>
                     <MuiThemeProvider theme={CustomTheme}>
             let panel = mount(
                 <Provider store={store}>
                     <MuiThemeProvider theme={CustomTheme}>
@@ -135,6 +138,36 @@ describe('renderers', () => {
             expect(panel.find(TableBody).text()).toContain('someValue');
         });
 
             expect(panel.find(TableBody).text()).toContain('someValue');
         });
 
+        it('shows main process with output collection', () => {
+            // when
+            const outputCollection = '987654321';
+            const parameters = [{id: 'someId', label: 'someLabel', value: {display: 'someValue'}}];
+            let panel = mount(
+                <Provider store={store}>
+                    <MuiThemeProvider theme={CustomTheme}>
+                        <ProcessIOCard
+                            label={ProcessIOCardType.OUTPUT}
+                            process={false} // Treat as a main process, no requestingContainerUuid
+                            outputUuid={outputCollection}
+                            params={parameters}
+                            raw={{}}
+                        />
+                    </MuiThemeProvider>
+                </Provider>
+                );
+
+            // then
+            expect(panel.find(CircularProgress).exists()).toBeFalsy();
+            expect(panel.find(Tab).length).toBe(3); // Empty raw is shown if parameters are present
+            expect(panel.find(TableBody).text()).toContain('someId');
+            expect(panel.find(TableBody).text()).toContain('someLabel');
+            expect(panel.find(TableBody).text()).toContain('someValue');
+
+            // Visit output tab
+            panel.find(Tab).at(2).simulate('click');
+            expect(panel.find(ProcessOutputCollectionFiles).prop('currentItemUuid')).toBe(outputCollection);
+        });
+
         // Subprocess
 
         it('shows subprocess loading', () => {
         // Subprocess
 
         it('shows subprocess loading', () => {
index 5716340edc157342f97fd7534da09757d966faf0..6d60b8cf2219455dc362a91356e271c90b3cc743 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React, { ReactElement, memo, useState } from "react";
+import React, { ReactElement, memo } from "react";
 import { Dispatch } from "redux";
 import {
     StyleRulesCallback,
 import { Dispatch } from "redux";
 import {
     StyleRulesCallback,
@@ -14,8 +14,6 @@ import {
     CardContent,
     Tooltip,
     Typography,
     CardContent,
     Tooltip,
     Typography,
-    Tabs,
-    Tab,
     Table,
     TableHead,
     TableBody,
     Table,
     TableHead,
     TableBody,
@@ -27,7 +25,7 @@ import {
     CircularProgress,
 } from "@material-ui/core";
 import { ArvadosTheme } from "common/custom-theme";
     CircularProgress,
 } from "@material-ui/core";
 import { ArvadosTheme } from "common/custom-theme";
-import { CloseIcon, ImageIcon, InputIcon, ImageOffIcon, OutputIcon, MaximizeIcon, UnMaximizeIcon, InfoIcon } from "components/icon/icon";
+import { CloseIcon, InputIcon, OutputIcon, MaximizeIcon, UnMaximizeIcon, InfoIcon } from "components/icon/icon";
 import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
 import {
     BooleanCommandInputParameter,
 import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
 import {
     BooleanCommandInputParameter,
@@ -65,8 +63,12 @@ import { ProcessOutputCollectionFiles } from "./process-output-collection-files"
 import { Process } from "store/processes/process";
 import { navigateTo } from "store/navigation/navigation-action";
 import classNames from "classnames";
 import { Process } from "store/processes/process";
 import { navigateTo } from "store/navigation/navigation-action";
 import classNames from "classnames";
-import { DefaultCodeSnippet } from "components/default-code-snippet/default-code-snippet";
+import { DefaultVirtualCodeSnippet } from "components/default-code-snippet/default-virtual-code-snippet";
 import { KEEP_URL_REGEX } from "models/resource";
 import { KEEP_URL_REGEX } from "models/resource";
+import { FixedSizeList } from 'react-window';
+import AutoSizer from "react-virtualized-auto-sizer";
+import { LinkProps } from "@material-ui/core/Link";
+import { ConditionalTabs } from "components/conditional-tabs/conditional-tabs";
 
 type CssRules =
     | "card"
 
 type CssRules =
     | "card"
@@ -76,21 +78,17 @@ type CssRules =
     | "avatar"
     | "iconHeader"
     | "tableWrapper"
     | "avatar"
     | "iconHeader"
     | "tableWrapper"
-    | "tableRoot"
-    | "paramValue"
+    | "paramTableRoot"
+    | "paramTableCellText"
+    | "mountsTableRoot"
+    | "jsonWrapper"
     | "keepLink"
     | "collectionLink"
     | "keepLink"
     | "collectionLink"
-    | "imagePreview"
-    | "valArray"
     | "secondaryVal"
     | "secondaryVal"
-    | "secondaryRow"
     | "emptyValue"
     | "noBorderRow"
     | "symmetricTabs"
     | "emptyValue"
     | "noBorderRow"
     | "symmetricTabs"
-    | "imagePlaceholder"
-    | "rowWithPreview"
-    | "labelColumn"
-    | "primaryRow";
+    | "wrapTooltip";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
@@ -108,26 +106,98 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         alignSelf: "flex-start",
         paddingTop: theme.spacing.unit * 0.5,
     },
         alignSelf: "flex-start",
         paddingTop: theme.spacing.unit * 0.5,
     },
+    // Card content
     content: {
     content: {
-        height: `calc(100% - ${theme.spacing.unit * 7}px - ${theme.spacing.unit * 1.5}px)`,
+        height: `calc(100% - ${theme.spacing.unit * 6}px)`,
         padding: theme.spacing.unit * 1.0,
         paddingTop: 0,
         "&:last-child": {
             paddingBottom: theme.spacing.unit * 1,
         },
     },
         padding: theme.spacing.unit * 1.0,
         paddingTop: 0,
         "&:last-child": {
             paddingBottom: theme.spacing.unit * 1,
         },
     },
+    // Card title
     title: {
         overflow: "hidden",
         paddingTop: theme.spacing.unit * 0.5,
         color: theme.customs.colors.greyD,
         fontSize: "1.875rem",
     },
     title: {
         overflow: "hidden",
         paddingTop: theme.spacing.unit * 0.5,
         color: theme.customs.colors.greyD,
         fontSize: "1.875rem",
     },
+    // Applies to table tab and collection table content
     tableWrapper: {
         height: "auto",
     tableWrapper: {
         height: "auto",
-        maxHeight: `calc(100% - ${theme.spacing.unit * 3}px)`,
+        maxHeight: `calc(100% - ${theme.spacing.unit * 6}px)`,
         overflow: "auto",
         overflow: "auto",
+        // Use flexbox to keep scrolling at the virtual list level
+        display: "flex",
+        flexDirection: "column",
+        alignItems: "stretch", // Stretches output collection to full width
+
     },
     },
-    tableRoot: {
+
+    // Param table virtual list styles
+    paramTableRoot: {
+        display: "flex",
+        flexDirection: "column",
+        overflow: "hidden",
+        // Flex header
+        "& thead tr": {
+            alignItems: "end",
+            "& th": {
+                padding: "4px 25px 10px",
+            },
+        },
+        "& tbody": {
+            height: "100vh", // Must be constrained by panel maxHeight
+        },
+        // Flex header/body rows
+        "& thead tr, & > tbody tr": {
+            display: "flex",
+            // Flex header/body cells
+            "& th, & td": {
+                flexGrow: 1,
+                flexShrink: 1,
+                flexBasis: 0,
+                overflow: "hidden",
+            },
+            // Column width overrides
+            "& th:nth-of-type(1), & td:nth-of-type(1)": {
+                flexGrow: 0.7,
+            },
+            "& th:nth-last-of-type(1), & td:nth-last-of-type(1)": {
+                flexGrow: 2,
+            },
+        },
+        // Flex body rows
+        "& tbody tr": {
+            height: "40px",
+            // Flex body cells
+            "& td": {
+                padding: "2px 25px 2px",
+                overflow: "hidden",
+                display: "flex",
+                flexDirection: "row",
+                alignItems: "center",
+                whiteSpace: "nowrap",
+            },
+        },
+    },
+    // Param value cell typography styles
+    paramTableCellText: {
+        overflow: "hidden",
+        display: "flex",
+        // Every cell contents requires a wrapper for the ellipsis
+        // since adding ellipses to an anchor element parent results in misaligned tooltip
+        "& a, & span": {
+            overflow: "hidden",
+            textOverflow: "ellipsis",
+        },
+        '& pre': {
+            margin: 0,
+            overflow: "hidden",
+            textOverflow: "ellipsis",
+        },
+    },
+    mountsTableRoot: {
         width: "100%",
         "& thead th": {
             verticalAlign: "bottom",
         width: "100%",
         "& thead th": {
             verticalAlign: "bottom",
@@ -137,17 +207,18 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
             paddingRight: "25px",
         },
     },
             paddingRight: "25px",
         },
     },
-    paramValue: {
-        display: "flex",
-        alignItems: "flex-start",
-        flexDirection: "column",
+    // JSON tab wrapper
+    jsonWrapper: {
+        height: `calc(100% - ${theme.spacing.unit * 6}px)`,
     },
     keepLink: {
         color: theme.palette.primary.main,
         textDecoration: "none",
     },
     keepLink: {
         color: theme.palette.primary.main,
         textDecoration: "none",
+        // Overflow wrap for mounts table
         overflowWrap: "break-word",
         cursor: "pointer",
     },
         overflowWrap: "break-word",
         cursor: "pointer",
     },
+    // Output collection tab link
     collectionLink: {
         margin: "10px",
         "& a": {
     collectionLink: {
         margin: "10px",
         "& a": {
@@ -157,28 +228,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
             cursor: "pointer",
         },
     },
             cursor: "pointer",
         },
     },
-    imagePreview: {
-        maxHeight: "15em",
-        maxWidth: "15em",
-        marginBottom: theme.spacing.unit,
-    },
-    valArray: {
-        display: "flex",
-        gap: "10px",
-        flexWrap: "wrap",
-        "& span": {
-            display: "inline",
-        },
-    },
     secondaryVal: {
         paddingLeft: "20px",
     },
     secondaryVal: {
         paddingLeft: "20px",
     },
-    secondaryRow: {
-        height: "24px",
-        verticalAlign: "top",
-        position: "relative",
-        top: "-4px",
-    },
     emptyValue: {
         color: theme.customs.colors.grey700,
     },
     emptyValue: {
         color: theme.customs.colors.grey700,
     },
@@ -195,27 +247,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
             flexBasis: "0",
         },
     },
             flexBasis: "0",
         },
     },
-    imagePlaceholder: {
-        width: "60px",
-        height: "60px",
-        display: "flex",
-        alignItems: "center",
-        justifyContent: "center",
-        backgroundColor: "#cecece",
-        borderRadius: "10px",
-    },
-    rowWithPreview: {
-        verticalAlign: "bottom",
-    },
-    labelColumn: {
-        minWidth: "120px",
-    },
-    primaryRow: {
-        height: "24px",
-        "& td": {
-            paddingTop: "2px",
-            paddingBottom: "2px",
-        },
+    wrapTooltip: {
+        maxWidth: "600px",
+        wordWrap: "break-word",
     },
 });
 
     },
 });
 
@@ -233,290 +267,206 @@ export interface ProcessIOCardDataProps {
     forceShowParams?: boolean;
 }
 
     forceShowParams?: boolean;
 }
 
-export interface ProcessIOCardActionProps {
-    navigateTo: (uuid: string) => void;
-}
-
-const mapDispatchToProps = (dispatch: Dispatch): ProcessIOCardActionProps => ({
-    navigateTo: uuid => dispatch<any>(navigateTo(uuid)),
-});
-
-type ProcessIOCardProps = ProcessIOCardDataProps & ProcessIOCardActionProps & WithStyles<CssRules> & MPVPanelProps;
+type ProcessIOCardProps = ProcessIOCardDataProps & WithStyles<CssRules> & MPVPanelProps;
 
 export const ProcessIOCard = withStyles(styles)(
 
 export const ProcessIOCard = withStyles(styles)(
-    connect(
-        null,
-        mapDispatchToProps
-    )(
-        ({
-            classes,
-            label,
-            params,
-            raw,
-            mounts,
-            outputUuid,
-            doHidePanel,
-            doMaximizePanel,
-            doUnMaximizePanel,
-            panelMaximized,
-            panelName,
-            process,
-            navigateTo,
-            forceShowParams,
-        }: ProcessIOCardProps) => {
-            const [mainProcTabState, setMainProcTabState] = useState(0);
-            const [subProcTabState, setSubProcTabState] = useState(0);
-            const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
-                setMainProcTabState(value);
-            };
-            const handleSubProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
-                setSubProcTabState(value);
-            };
-
-            const [showImagePreview, setShowImagePreview] = useState(false);
-
-            const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
-            const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
-            const showParamTable = mainProcess || forceShowParams;
+    ({
+        classes,
+        label,
+        params,
+        raw,
+        mounts,
+        outputUuid,
+        doHidePanel,
+        doMaximizePanel,
+        doUnMaximizePanel,
+        panelMaximized,
+        panelName,
+        process,
+        forceShowParams,
+    }: ProcessIOCardProps) => {
+        const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
+        const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
+        const showParamTable = mainProcess || forceShowParams;
+
+        const loading = raw === null || raw === undefined || params === null;
+
+        const hasRaw = !!(raw && Object.keys(raw).length > 0);
+        const hasParams = !!(params && params.length > 0);
+        // isRawLoaded allows subprocess panel to display raw even if it's {}
+        const isRawLoaded = !!(raw && Object.keys(raw).length >= 0);
+
+        // Subprocess
+        const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
+        const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
+        // Subprocess should not show loading if hasOutputCollection or hasInputMounts
+        const subProcessLoading = loading && !hasOutputCollecton && !hasInputMounts;
 
 
-            const loading = raw === null || raw === undefined || params === null;
-
-            const hasRaw = !!(raw && Object.keys(raw).length > 0);
-            const hasParams = !!(params && params.length > 0);
-            // isRawLoaded allows subprocess panel to display raw even if it's {}
-            const isRawLoaded = !!(raw && Object.keys(raw).length >= 0);
-
-            // Subprocess
-            const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
-            const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
-            // Subprocess should not show loading if hasOutputCollection or hasInputMounts
-            const subProcessLoading = loading && !hasOutputCollecton && !hasInputMounts;
-
-            return (
-                <Card
-                    className={classes.card}
-                    data-cy="process-io-card"
-                >
-                    <CardHeader
-                        className={classes.header}
-                        classes={{
-                            content: classes.title,
-                            avatar: classes.avatar,
-                        }}
-                        avatar={<PanelIcon className={classes.iconHeader} />}
-                        title={
-                            <Typography
-                                noWrap
-                                variant="h6"
-                                color="inherit"
-                            >
-                                {label}
-                            </Typography>
-                        }
-                        action={
-                            <div>
-                                {mainProcess && (
-                                    <Tooltip
-                                        title={"Toggle Image Preview"}
-                                        disableFocusListener
-                                    >
-                                        <IconButton
-                                            data-cy="io-preview-image-toggle"
-                                            onClick={() => {
-                                                setShowImagePreview(!showImagePreview);
-                                            }}
-                                        >
-                                            {showImagePreview ? <ImageIcon /> : <ImageOffIcon />}
-                                        </IconButton>
-                                    </Tooltip>
-                                )}
-                                {doUnMaximizePanel && panelMaximized && (
-                                    <Tooltip
-                                        title={`Unmaximize ${panelName || "panel"}`}
-                                        disableFocusListener
-                                    >
-                                        <IconButton onClick={doUnMaximizePanel}>
-                                            <UnMaximizeIcon />
-                                        </IconButton>
-                                    </Tooltip>
-                                )}
-                                {doMaximizePanel && !panelMaximized && (
-                                    <Tooltip
-                                        title={`Maximize ${panelName || "panel"}`}
-                                        disableFocusListener
-                                    >
-                                        <IconButton onClick={doMaximizePanel}>
-                                            <MaximizeIcon />
-                                        </IconButton>
-                                    </Tooltip>
-                                )}
-                                {doHidePanel && (
-                                    <Tooltip
-                                        title={`Close ${panelName || "panel"}`}
-                                        disableFocusListener
-                                    >
-                                        <IconButton
-                                            disabled={panelMaximized}
-                                            onClick={doHidePanel}
-                                        >
-                                            <CloseIcon />
-                                        </IconButton>
-                                    </Tooltip>
-                                )}
-                            </div>
-                        }
-                    />
-                    <CardContent className={classes.content}>
-                        {showParamTable ? (
-                            <>
-                                {/* raw is undefined until params are loaded */}
-                                {loading && (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
+        return (
+            <Card
+                className={classes.card}
+                data-cy="process-io-card"
+            >
+                <CardHeader
+                    className={classes.header}
+                    classes={{
+                        content: classes.title,
+                        avatar: classes.avatar,
+                    }}
+                    avatar={<PanelIcon className={classes.iconHeader} />}
+                    title={
+                        <Typography
+                            noWrap
+                            variant="h6"
+                            color="inherit"
+                        >
+                            {label}
+                        </Typography>
+                    }
+                    action={
+                        <div>
+                            {doUnMaximizePanel && panelMaximized && (
+                                <Tooltip
+                                    title={`Unmaximize ${panelName || "panel"}`}
+                                    disableFocusListener
+                                >
+                                    <IconButton onClick={doUnMaximizePanel}>
+                                        <UnMaximizeIcon />
+                                    </IconButton>
+                                </Tooltip>
+                            )}
+                            {doMaximizePanel && !panelMaximized && (
+                                <Tooltip
+                                    title={`Maximize ${panelName || "panel"}`}
+                                    disableFocusListener
+                                >
+                                    <IconButton onClick={doMaximizePanel}>
+                                        <MaximizeIcon />
+                                    </IconButton>
+                                </Tooltip>
+                            )}
+                            {doHidePanel && (
+                                <Tooltip
+                                    title={`Close ${panelName || "panel"}`}
+                                    disableFocusListener
+                                >
+                                    <IconButton
+                                        disabled={panelMaximized}
+                                        onClick={doHidePanel}
                                     >
                                     >
-                                        <CircularProgress />
-                                    </Grid>
-                                )}
-                                {/* Once loaded, either raw or params may still be empty
-                                 *   Raw when all params are empty
-                                 *   Params when raw is provided by containerRequest properties but workflow mount is absent for preview
-                                 */}
-                                {!loading && (hasRaw || hasParams) && (
-                                    <>
-                                        <Tabs
-                                            value={mainProcTabState}
-                                            onChange={handleMainProcTabChange}
-                                            variant="fullWidth"
-                                            className={classes.symmetricTabs}
-                                        >
-                                            {/* params will be empty on processes without workflow definitions in mounts, so we only show raw */}
-                                            {hasParams && <Tab label="Parameters" />}
-                                            {!forceShowParams && <Tab label="JSON" />}
-                                            {hasOutputCollecton && <Tab label="Collection" />}
-                                        </Tabs>
-                                        {mainProcTabState === 0 && params && hasParams && (
-                                            <div className={classes.tableWrapper}>
-                                                <ProcessIOPreview
-                                                    data={params}
-                                                    showImagePreview={showImagePreview}
+                                        <CloseIcon />
+                                    </IconButton>
+                                </Tooltip>
+                            )}
+                        </div>
+                    }
+                />
+                <CardContent className={classes.content}>
+                    {showParamTable ? (
+                        <>
+                            {/* raw is undefined until params are loaded */}
+                            {loading && (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <CircularProgress />
+                                </Grid>
+                            )}
+                            {/* Once loaded, either raw or params may still be empty
+                                *   Raw when all params are empty
+                                *   Params when raw is provided by containerRequest properties but workflow mount is absent for preview
+                                */}
+                            {!loading && (hasRaw || hasParams) && (
+                                <ConditionalTabs
+                                    variant="fullWidth"
+                                    className={classes.symmetricTabs}
+                                    tabs={[
+                                        {
+                                            // params will be empty on processes without workflow definitions in mounts, so we only show raw
+                                            show: hasParams,
+                                            label: "Parameters",
+                                            content: <ProcessIOPreview
+                                                    data={params || []}
                                                     valueLabel={forceShowParams ? "Default value" : "Value"}
                                                     valueLabel={forceShowParams ? "Default value" : "Value"}
-                                                />
-                                            </div>
-                                        )}
-                                        {(mainProcTabState === 1 || !hasParams) && (
-                                            <div className={classes.tableWrapper}>
-                                                <ProcessIORaw data={raw} />
-                                            </div>
-                                        )}
-                                        {mainProcTabState === 2 && hasOutputCollecton && (
-                                            <>
-                                                {outputUuid && (
-                                                    <Typography className={classes.collectionLink}>
-                                                        Output Collection:{" "}
-                                                        <MuiLink
-                                                            className={classes.keepLink}
-                                                            onClick={() => {
-                                                                navigateTo(outputUuid || "");
-                                                            }}
-                                                        >
-                                                            {outputUuid}
-                                                        </MuiLink>
-                                                    </Typography>
-                                                )}
-                                                <ProcessOutputCollectionFiles
-                                                    isWritable={false}
-                                                    currentItemUuid={outputUuid}
-                                                />
-                                            </>
-                                        )}
-
-                                    </>
-                                )}
-                                {!loading && !hasRaw && !hasParams && (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
-                                    >
-                                        <DefaultView messages={["No parameters found"]} />
-                                    </Grid>
-                                )}
-                            </>
-                        ) : (
-                            // Subprocess
-                            <>
-                                {subProcessLoading ? (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
-                                    >
-                                        <CircularProgress />
-                                    </Grid>
-                                ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
-                                    <>
-                                        <Tabs
-                                            value={subProcTabState}
-                                            onChange={handleSubProcTabChange}
-                                            variant="fullWidth"
-                                            className={classes.symmetricTabs}
-                                        >
-                                            {hasInputMounts && <Tab label="Collections" />}
-                                            {hasOutputCollecton && <Tab label="Collection" />}
-                                            {isRawLoaded && <Tab label="JSON" />}
-                                        </Tabs>
-                                        <div className={classes.tableWrapper}>
-                                            {subProcTabState === 0 && hasInputMounts && <ProcessInputMounts mounts={mounts || []} />}
-                                            {subProcTabState === 0 && hasOutputCollecton && (
-                                                <>
-                                                    {outputUuid && (
-                                                        <Typography className={classes.collectionLink}>
-                                                            Output Collection:{" "}
-                                                            <MuiLink
-                                                                className={classes.keepLink}
-                                                                onClick={() => {
-                                                                    navigateTo(outputUuid || "");
-                                                                }}
-                                                            >
-                                                                {outputUuid}
-                                                            </MuiLink>
-                                                        </Typography>
-                                                    )}
-                                                    <ProcessOutputCollectionFiles
-                                                        isWritable={false}
-                                                        currentItemUuid={outputUuid}
-                                                    />
-                                                </>
-                                            )}
-                                            {isRawLoaded && (subProcTabState === 1 || (!hasInputMounts && !hasOutputCollecton)) && (
-                                                <div className={classes.tableWrapper}>
-                                                    <ProcessIORaw data={raw} />
-                                                </div>
-                                            )}
-                                        </div>
-                                    </>
-                                ) : (
-                                    <Grid
-                                        container
-                                        item
-                                        alignItems="center"
-                                        justify="center"
-                                    >
-                                        <DefaultView messages={["No data to display"]} />
-                                    </Grid>
-                                )}
-                            </>
-                        )}
-                    </CardContent>
-                </Card>
-            );
-        }
-    )
+                                            />,
+                                        },
+                                        {
+                                            show: !forceShowParams,
+                                            label: "JSON",
+                                            content: <ProcessIORaw data={raw} />,
+                                        },
+                                        {
+                                            show: hasOutputCollecton,
+                                            label: "Collection",
+                                            content: <ProcessOutputCollection outputUuid={outputUuid} />,
+                                        },
+                                    ]}
+                                />
+                            )}
+                            {!loading && !hasRaw && !hasParams && (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <DefaultView messages={["No parameters found"]} />
+                                </Grid>
+                            )}
+                        </>
+                    ) : (
+                        // Subprocess
+                        <>
+                            {subProcessLoading ? (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <CircularProgress />
+                                </Grid>
+                            ) : !subProcessLoading && (hasInputMounts || hasOutputCollecton || isRawLoaded) ? (
+                                <ConditionalTabs
+                                    variant="fullWidth"
+                                    className={classes.symmetricTabs}
+                                    tabs={[
+                                        {
+                                            show: hasInputMounts,
+                                            label: "Collections",
+                                            content: <ProcessInputMounts mounts={mounts || []} />,
+                                        },
+                                        {
+                                            show: hasOutputCollecton,
+                                            label: "Collection",
+                                            content: <ProcessOutputCollection outputUuid={outputUuid} />,
+                                        },
+                                        {
+                                            show: isRawLoaded,
+                                            label: "JSON",
+                                            content: <ProcessIORaw data={raw} />,
+                                        },
+                                    ]}
+                                />
+                            ) : (
+                                <Grid
+                                    container
+                                    item
+                                    alignItems="center"
+                                    justify="center"
+                                >
+                                    <DefaultView messages={["No data to display"]} />
+                                </Grid>
+                            )}
+                        </>
+                    )}
+                </CardContent>
+            </Card>
+        );
+    }
 );
 
 export type ProcessIOValue = {
 );
 
 export type ProcessIOValue = {
@@ -529,132 +479,135 @@ export type ProcessIOValue = {
 export type ProcessIOParameter = {
     id: string;
     label: string;
 export type ProcessIOParameter = {
     id: string;
     label: string;
-    value: ProcessIOValue[];
+    value: ProcessIOValue;
 };
 
 interface ProcessIOPreviewDataProps {
     data: ProcessIOParameter[];
 };
 
 interface ProcessIOPreviewDataProps {
     data: ProcessIOParameter[];
-    showImagePreview: boolean;
     valueLabel: string;
     valueLabel: string;
+    hidden?: boolean;
 }
 
 type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
 
 const ProcessIOPreview = memo(
 }
 
 type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
 
 const ProcessIOPreview = memo(
-    withStyles(styles)(({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
+    withStyles(styles)(({ data, valueLabel, hidden, classes }: ProcessIOPreviewProps) => {
         const showLabel = data.some((param: ProcessIOParameter) => param.label);
         const showLabel = data.some((param: ProcessIOParameter) => param.label);
-        return (
+
+        const hasMoreValues = (index: number) => (
+            data[index+1] && !isMainRow(data[index+1])
+        );
+
+        const isMainRow = (param: ProcessIOParameter) => (
+            param &&
+            ((param.id || param.label) &&
+            !param.value.secondary)
+        );
+
+        const RenderRow = ({index, style}) => {
+            const param = data[index];
+
+            const rowClasses = {
+                [classes.noBorderRow]: hasMoreValues(index),
+            };
+
+            return <TableRow
+                style={style}
+                className={classNames(rowClasses)}
+                data-cy={isMainRow(param) ? "process-io-param" : ""}>
+                <TableCell>
+                    <Tooltip title={param.id}>
+                        <Typography className={classes.paramTableCellText}>
+                            <span>
+                                {param.id}
+                            </span>
+                        </Typography>
+                    </Tooltip>
+                </TableCell>
+                {showLabel && <TableCell>
+                    <Tooltip title={param.label}>
+                        <Typography className={classes.paramTableCellText}>
+                            <span>
+                                {param.label}
+                            </span>
+                        </Typography>
+                    </Tooltip>
+                </TableCell>}
+                <TableCell>
+                    <ProcessValuePreview
+                        value={param.value}
+                    />
+                </TableCell>
+                <TableCell>
+                    <Typography className={classes.paramTableCellText}>
+                        {/** Collection is an anchor so doesn't require wrapper element */}
+                        {param.value.collection}
+                    </Typography>
+                </TableCell>
+            </TableRow>;
+        };
+
+        return <div className={classes.tableWrapper} hidden={hidden}>
             <Table
             <Table
-                className={classes.tableRoot}
+                className={classes.paramTableRoot}
                 aria-label="Process IO Preview"
             >
                 <TableHead>
                     <TableRow>
                         <TableCell>Name</TableCell>
                 aria-label="Process IO Preview"
             >
                 <TableHead>
                     <TableRow>
                         <TableCell>Name</TableCell>
-                        {showLabel && <TableCell className={classes.labelColumn}>Label</TableCell>}
+                        {showLabel && <TableCell>Label</TableCell>}
                         <TableCell>{valueLabel}</TableCell>
                         <TableCell>Collection</TableCell>
                     </TableRow>
                 </TableHead>
                 <TableBody>
                         <TableCell>{valueLabel}</TableCell>
                         <TableCell>Collection</TableCell>
                     </TableRow>
                 </TableHead>
                 <TableBody>
-                    {data.map((param: ProcessIOParameter) => {
-                        const firstVal = param.value.length > 0 ? param.value[0] : undefined;
-                        const rest = param.value.slice(1);
-                        const mainRowClasses = {
-                            [classes.noBorderRow]: rest.length > 0,
-                            [classes.primaryRow]: true
-                        };
-
-                        return (
-                            <React.Fragment key={param.id}>
-                                <TableRow
-                                    className={classNames(mainRowClasses)}
-                                    data-cy="process-io-param"
-                                >
-                                    <TableCell>{param.id}</TableCell>
-                                    {showLabel && <TableCell>{param.label}</TableCell>}
-                                    <TableCell>
-                                        {firstVal && (
-                                            <ProcessValuePreview
-                                                value={firstVal}
-                                                showImagePreview={showImagePreview}
-                                            />
-                                        )}
-                                    </TableCell>
-                                    <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
-                                        <Typography className={classes.paramValue}>{firstVal?.collection}</Typography>
-                                    </TableCell>
-                                </TableRow>
-                                {rest.map((val, i) => {
-                                    const rowClasses = {
-                                        [classes.noBorderRow]: i < rest.length - 1,
-                                        [classes.secondaryRow]: val.secondary,
-                                        [classes.primaryRow]: !val.secondary,
-                                    };
-                                    return (
-                                        <TableRow
-                                            className={classNames(rowClasses)}
-                                            key={i}
-                                        >
-                                            <TableCell />
-                                            {showLabel && <TableCell />}
-                                            <TableCell>
-                                                <ProcessValuePreview
-                                                    value={val}
-                                                    showImagePreview={showImagePreview}
-                                                />
-                                            </TableCell>
-                                            <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
-                                                <Typography className={classes.paramValue}>{val.collection}</Typography>
-                                            </TableCell>
-                                        </TableRow>
-                                    );
-                                })}
-                            </React.Fragment>
-                        );
-                    })}
+                    <AutoSizer>
+                        {({ height, width }) =>
+                            <FixedSizeList
+                                height={height}
+                                itemCount={data.length}
+                                itemSize={40}
+                                width={width}
+                            >
+                                {RenderRow}
+                            </FixedSizeList>
+                        }
+                    </AutoSizer>
                 </TableBody>
             </Table>
                 </TableBody>
             </Table>
-        );
+        </div>;
     })
 );
 
 interface ProcessValuePreviewProps {
     value: ProcessIOValue;
     })
 );
 
 interface ProcessValuePreviewProps {
     value: ProcessIOValue;
-    showImagePreview: boolean;
 }
 
 }
 
-const ProcessValuePreview = withStyles(styles)(({ value, showImagePreview, classes }: ProcessValuePreviewProps & WithStyles<CssRules>) => (
-    <Typography className={classes.paramValue}>
-        {value.imageUrl && showImagePreview ? (
-            <img
-                className={classes.imagePreview}
-                src={value.imageUrl}
-                alt="Inline Preview"
-            />
-        ) : (
-            ""
-        )}
-        {value.imageUrl && !showImagePreview ? <ImagePlaceholder /> : ""}
-        <span className={classNames(classes.valArray, value.secondary && classes.secondaryVal)}>{value.display}</span>
+const ProcessValuePreview = withStyles(styles)(({ value, classes }: ProcessValuePreviewProps & WithStyles<CssRules>) => (
+    <Typography className={classNames(classes.paramTableCellText, value.secondary && classes.secondaryVal)}>
+        {value.display}
     </Typography>
 ));
 
 interface ProcessIORawDataProps {
     data: ProcessIOParameter[];
     </Typography>
 ));
 
 interface ProcessIORawDataProps {
     data: ProcessIOParameter[];
+    hidden?: boolean;
 }
 
 }
 
-const ProcessIORaw = withStyles(styles)(({ data }: ProcessIORawDataProps) => (
-    <Paper elevation={0}>
-        <DefaultCodeSnippet
-            lines={[JSON.stringify(data, null, 2)]}
-            linked
-        />
-    </Paper>
+const ProcessIORaw = withStyles(styles)(({ data, hidden, classes }: ProcessIORawDataProps & WithStyles<CssRules>) => (
+    <div className={classes.jsonWrapper} hidden={hidden}>
+        <Paper elevation={0} style={{minWidth: "100%", height: "100%"}}>
+            <DefaultVirtualCodeSnippet
+                lines={JSON.stringify(data, null, 2).split('\n')}
+                linked
+            />
+        </Paper>
+    </div>
 ));
 
 interface ProcessInputMountsDataProps {
     mounts: InputCollectionMount[];
 ));
 
 interface ProcessInputMountsDataProps {
     mounts: InputCollectionMount[];
+    hidden?: boolean;
 }
 
 type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles<CssRules>;
 }
 
 type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles<CssRules>;
@@ -662,10 +615,11 @@ type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles<CssRules
 const ProcessInputMounts = withStyles(styles)(
     connect((state: RootState) => ({
         auth: state.auth,
 const ProcessInputMounts = withStyles(styles)(
     connect((state: RootState) => ({
         auth: state.auth,
-    }))(({ mounts, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
+    }))(({ mounts, hidden, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
         <Table
         <Table
-            className={classes.tableRoot}
+            className={classes.mountsTableRoot}
             aria-label="Process Input Mounts"
             aria-label="Process Input Mounts"
+            hidden={hidden}
         >
             <TableHead>
                 <TableRow>
         >
             <TableHead>
                 <TableRow>
@@ -694,6 +648,40 @@ const ProcessInputMounts = withStyles(styles)(
     ))
 );
 
     ))
 );
 
+export interface ProcessOutputCollectionActionProps {
+    navigateTo: (uuid: string) => void;
+}
+
+const mapNavigateToProps = (dispatch: Dispatch): ProcessOutputCollectionActionProps => ({
+    navigateTo: uuid => dispatch<any>(navigateTo(uuid)),
+});
+
+type ProcessOutputCollectionProps = {outputUuid: string | undefined, hidden?: boolean} & ProcessOutputCollectionActionProps &  WithStyles<CssRules>;
+
+const ProcessOutputCollection = withStyles(styles)(connect(null, mapNavigateToProps)(({ outputUuid, hidden, navigateTo, classes }: ProcessOutputCollectionProps) => (
+    <div className={classes.tableWrapper} hidden={hidden}>
+        <>
+            {outputUuid && (
+                <Typography className={classes.collectionLink}>
+                    Output Collection:{" "}
+                    <MuiLink
+                        className={classes.keepLink}
+                        onClick={() => {
+                            navigateTo(outputUuid || "");
+                        }}
+                    >
+                        {outputUuid}
+                    </MuiLink>
+                </Typography>
+            )}
+            <ProcessOutputCollectionFiles
+                isWritable={false}
+                currentItemUuid={outputUuid}
+            />
+        </>
+    </div>
+)));
+
 type FileWithSecondaryFiles = {
     secondaryFiles: File[];
 };
 type FileWithSecondaryFiles = {
     secondaryFiles: File[];
 };
@@ -703,7 +691,7 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
         case isPrimitiveOfType(input, CWLType.BOOLEAN):
             const boolValue = (input as BooleanCommandInputParameter).value;
             return boolValue !== undefined && !(Array.isArray(boolValue) && boolValue.length === 0)
         case isPrimitiveOfType(input, CWLType.BOOLEAN):
             const boolValue = (input as BooleanCommandInputParameter).value;
             return boolValue !== undefined && !(Array.isArray(boolValue) && boolValue.length === 0)
-                ? [{ display: renderPrimitiveValue(boolValue, false) }]
+                ? [{ display: <PrimitiveTooltip data={boolValue}>{renderPrimitiveValue(boolValue, false)}</PrimitiveTooltip> }]
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.INT):
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.INT):
@@ -712,20 +700,20 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
             return intValue !== undefined &&
                 // Missing values are empty array
                 !(Array.isArray(intValue) && intValue.length === 0)
             return intValue !== undefined &&
                 // Missing values are empty array
                 !(Array.isArray(intValue) && intValue.length === 0)
-                ? [{ display: renderPrimitiveValue(intValue, false) }]
+                ? [{ display: <PrimitiveTooltip data={intValue}>{renderPrimitiveValue(intValue, false)}</PrimitiveTooltip> }]
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.FLOAT):
         case isPrimitiveOfType(input, CWLType.DOUBLE):
             const floatValue = (input as FloatCommandInputParameter).value;
             return floatValue !== undefined && !(Array.isArray(floatValue) && floatValue.length === 0)
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.FLOAT):
         case isPrimitiveOfType(input, CWLType.DOUBLE):
             const floatValue = (input as FloatCommandInputParameter).value;
             return floatValue !== undefined && !(Array.isArray(floatValue) && floatValue.length === 0)
-                ? [{ display: renderPrimitiveValue(floatValue, false) }]
+                ? [{ display: <PrimitiveTooltip data={floatValue}>{renderPrimitiveValue(floatValue, false)}</PrimitiveTooltip> }]
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.STRING):
             const stringValue = (input as StringCommandInputParameter).value || undefined;
             return stringValue !== undefined && !(Array.isArray(stringValue) && stringValue.length === 0)
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.STRING):
             const stringValue = (input as StringCommandInputParameter).value || undefined;
             return stringValue !== undefined && !(Array.isArray(stringValue) && stringValue.length === 0)
-                ? [{ display: renderPrimitiveValue(stringValue, false) }]
+                ? [{ display: <PrimitiveTooltip data={stringValue}>{renderPrimitiveValue(stringValue, false)}</PrimitiveTooltip> }]
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.FILE):
                 : [{ display: <EmptyValue /> }];
 
         case isPrimitiveOfType(input, CWLType.FILE):
@@ -746,21 +734,21 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
 
         case getEnumType(input) !== null:
             const enumValue = (input as EnumCommandInputParameter).value;
 
         case getEnumType(input) !== null:
             const enumValue = (input as EnumCommandInputParameter).value;
-            return enumValue !== undefined && enumValue ? [{ display: <pre>{enumValue}</pre> }] : [{ display: <EmptyValue /> }];
+            return enumValue !== undefined && enumValue ? [{ display: <PrimitiveTooltip data={enumValue}>{enumValue}</PrimitiveTooltip> }] : [{ display: <EmptyValue /> }];
 
         case isArrayOfType(input, CWLType.STRING):
             const strArray = (input as StringArrayCommandInputParameter).value || [];
 
         case isArrayOfType(input, CWLType.STRING):
             const strArray = (input as StringArrayCommandInputParameter).value || [];
-            return strArray.length ? [{ display: <>{strArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
+            return strArray.length ? [{ display: <PrimitiveArrayTooltip data={strArray}>{strArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
 
         case isArrayOfType(input, CWLType.INT):
         case isArrayOfType(input, CWLType.LONG):
             const intArray = (input as IntArrayCommandInputParameter).value || [];
 
         case isArrayOfType(input, CWLType.INT):
         case isArrayOfType(input, CWLType.LONG):
             const intArray = (input as IntArrayCommandInputParameter).value || [];
-            return intArray.length ? [{ display: <>{intArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
+            return intArray.length ? [{ display: <PrimitiveArrayTooltip data={intArray}>{intArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
 
         case isArrayOfType(input, CWLType.FLOAT):
         case isArrayOfType(input, CWLType.DOUBLE):
             const floatArray = (input as FloatArrayCommandInputParameter).value || [];
 
         case isArrayOfType(input, CWLType.FLOAT):
         case isArrayOfType(input, CWLType.DOUBLE):
             const floatArray = (input as FloatArrayCommandInputParameter).value || [];
-            return floatArray.length ? [{ display: <>{floatArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
+            return floatArray.length ? [{ display: <PrimitiveArrayTooltip data={floatArray}>{floatArray.map(val => renderPrimitiveValue(val, true))}</PrimitiveArrayTooltip> }] : [{ display: <EmptyValue /> }];
 
         case isArrayOfType(input, CWLType.FILE):
             const fileArrayMainFiles = (input as FileArrayCommandInputParameter).value || [];
 
         case isArrayOfType(input, CWLType.FILE):
             const fileArrayMainFiles = (input as FileArrayCommandInputParameter).value || [];
@@ -788,6 +776,27 @@ export const getIOParamDisplayValue = (auth: AuthState, input: CommandInputParam
     }
 };
 
     }
 };
 
+interface PrimitiveTooltipProps {
+    data: boolean | number | string;
+}
+
+const PrimitiveTooltip = (props: React.PropsWithChildren<PrimitiveTooltipProps>) => (
+    <Tooltip title={typeof props.data !== 'object' ? String(props.data) : ""}>
+        <pre>{props.children}</pre>
+    </Tooltip>
+);
+
+interface PrimitiveArrayTooltipProps {
+    data: string[];
+}
+
+const PrimitiveArrayTooltip = (props: React.PropsWithChildren<PrimitiveArrayTooltipProps>) => (
+    <Tooltip title={props.data.join(', ')}>
+        <span>{props.children}</span>
+    </Tooltip>
+);
+
+
 const renderPrimitiveValue = (value: any, asChip: boolean) => {
     const isObject = typeof value === "object";
     if (!isObject) {
 const renderPrimitiveValue = (value: any, asChip: boolean) => {
     const isObject = typeof value === "object";
     if (!isObject) {
@@ -795,9 +804,10 @@ const renderPrimitiveValue = (value: any, asChip: boolean) => {
             <Chip
                 key={value}
                 label={String(value)}
             <Chip
                 key={value}
                 label={String(value)}
+                style={{marginRight: "10px"}}
             />
         ) : (
             />
         ) : (
-            <pre key={value}>{String(value)}</pre>
+            <>{String(value)}</>
         );
     } else {
         return asChip ? <UnsupportedValueChip /> : <UnsupportedValue />;
         );
     } else {
         return asChip ? <UnsupportedValueChip /> : <UnsupportedValue />;
@@ -829,7 +839,7 @@ const KeepUrlBase = withStyles(styles)(({ auth, res, pdh, classes }: KeepUrlProp
     // Passing a pdh always returns a relative wb2 collection url
     const pdhWbPath = getNavUrl(pdhUrl, auth);
     return pdhUrl && pdhWbPath ? (
     // Passing a pdh always returns a relative wb2 collection url
     const pdhWbPath = getNavUrl(pdhUrl, auth);
     return pdhUrl && pdhWbPath ? (
-        <Tooltip title={"View collection in Workbench"}>
+        <Tooltip title={<>View collection in Workbench<br />{pdhUrl}</>}>
             <RouterLink
                 to={pdhWbPath}
                 className={classes.keepLink}
             <RouterLink
                 to={pdhWbPath}
                 className={classes.keepLink}
@@ -849,12 +859,12 @@ const KeepUrlPath = withStyles(styles)(({ auth, res, pdh, classes }: KeepUrlProp
 
     const keepUrlPathNav = getKeepNavUrl(auth, res, pdh);
     return keepUrlPathNav ? (
 
     const keepUrlPathNav = getKeepNavUrl(auth, res, pdh);
     return keepUrlPathNav ? (
-        <Tooltip title={"View in keep-web"}>
+        <Tooltip classes={{tooltip: classes.wrapTooltip}} title={<>View in keep-web<br />{keepUrlPath || "/"}</>}>
             <a
                 className={classes.keepLink}
                 href={keepUrlPathNav}
                 target="_blank"
             <a
                 className={classes.keepLink}
                 href={keepUrlPathNav}
                 target="_blank"
-                rel="noopener"
+                rel="noopener noreferrer"
             >
                 {keepUrlPath || "/"}
             </a>
             >
                 {keepUrlPath || "/"}
             </a>
@@ -923,6 +933,16 @@ const directoryToProcessIOValue = (directory: Directory, auth: AuthState, pdh?:
     };
 };
 
     };
 };
 
+type MuiLinkWithTooltipProps = WithStyles<CssRules> & React.PropsWithChildren<LinkProps>;
+
+const MuiLinkWithTooltip = withStyles(styles)((props: MuiLinkWithTooltipProps) => (
+    <Tooltip title={props.title} classes={{tooltip: props.classes.wrapTooltip}}>
+        <MuiLink {...props}>
+            {props.children}
+        </MuiLink>
+    </Tooltip>
+));
+
 const fileToProcessIOValue = (file: File, secondary: boolean, auth: AuthState, pdh: string | undefined, mainFilePdh: string): ProcessIOValue => {
     if (isExternalValue(file)) {
         return { display: <UnsupportedValue /> };
 const fileToProcessIOValue = (file: File, secondary: boolean, auth: AuthState, pdh: string | undefined, mainFilePdh: string): ProcessIOValue => {
     if (isExternalValue(file)) {
         return { display: <UnsupportedValue /> };
@@ -931,13 +951,14 @@ const fileToProcessIOValue = (file: File, secondary: boolean, auth: AuthState, p
     if (isFileUrl(file.location)) {
         return {
             display: (
     if (isFileUrl(file.location)) {
         return {
             display: (
-                <MuiLink
+                <MuiLinkWithTooltip
                     href={file.location}
                     target="_blank"
                     rel="noopener"
                     href={file.location}
                     target="_blank"
                     rel="noopener"
+                    title={file.location}
                 >
                     {file.location}
                 >
                     {file.location}
-                </MuiLink>
+                </MuiLinkWithTooltip>
             ),
             secondary,
         };
             ),
             secondary,
         };
@@ -979,9 +1000,3 @@ const UnsupportedValueChip = withStyles(styles)(({ classes }: WithStyles<CssRule
         label={"Cannot display value"}
     />
 ));
         label={"Cannot display value"}
     />
 ));
-
-const ImagePlaceholder = withStyles(styles)(({ classes }: WithStyles<CssRules>) => (
-    <span className={classes.imagePlaceholder}>
-        <ImageIcon />
-    </span>
-));
index 4fd8f2343d88b5a8b3356c4b0f53987da3f10cb4..b141abf24c5bf0d4b16359933a1a8a0d38ea460e 100644 (file)
@@ -131,7 +131,7 @@ export const ProcessLogsCard = withStyles(styles)(
                             </Tooltip>
                         </Grid>
                         <Grid item>
                             </Tooltip>
                         </Grid>
                         <Grid item>
-                            <Tooltip title="Copy to clipboard" disableFocusListener>
+                            <Tooltip title="Copy link to clipboard" disableFocusListener>
                                 <IconButton>
                                     <CopyToClipboard text={lines.join()} onCopy={() => onCopy("Log copied to clipboard")}>
                                         <CopyIcon />
                                 <IconButton>
                                     <CopyToClipboard text={lines.join()} onCopy={() => onCopy("Log copied to clipboard")}>
                                         <CopyIcon />
index d0b44cd1e6901f051dce7457c0b8d89264d432dd..c8165564bd1cf7a0b35fc15058c93d3bceb2e653 100644 (file)
@@ -9,7 +9,7 @@ import {
 } from "components/collection-panel-files/collection-panel-files";
 import { Dispatch } from "redux";
 import { collectionPanelFilesAction } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
 } from "components/collection-panel-files/collection-panel-files";
 import { Dispatch } from "redux";
 import { collectionPanelFilesAction } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
-import { ContextMenuKind } from "views-components/context-menu/context-menu";
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { openContextMenu, openCollectionFilesContextMenu } from 'store/context-menu/context-menu-actions';
 import { openUploadCollectionFilesDialog } from 'store/collections/collection-upload-actions';
 import { ResourceKind } from "models/resource";
 import { openContextMenu, openCollectionFilesContextMenu } from 'store/context-menu/context-menu-actions';
 import { openUploadCollectionFilesDialog } from 'store/collections/collection-upload-actions';
 import { ResourceKind } from "models/resource";
index 2a9b3882e86bec1592764ad46f837938c5eb3aa5..c8e93aa3ae655cf53129a9b7d533ade5c7a3f9b7 100644 (file)
@@ -24,7 +24,6 @@ import { ProcessCmdCard } from "./process-cmd-card";
 import { ContainerRequestResource } from "models/container-request";
 import { OutputDetails, NodeInstanceType } from "store/process-panel/process-panel";
 import { NotFoundView } from 'views/not-found-panel/not-found-panel';
 import { ContainerRequestResource } from "models/container-request";
 import { OutputDetails, NodeInstanceType } from "store/process-panel/process-panel";
 import { NotFoundView } from 'views/not-found-panel/not-found-panel';
-import { CollectionFile } from 'models/collection-file';
 
 type CssRules = "root";
 
 
 type CssRules = "root";
 
@@ -198,6 +197,7 @@ export const ProcessPanelRoot = withStyles(styles)(
                 <MPVPanelContent
                     forwardProps
                     xs="auto"
                 <MPVPanelContent
                     forwardProps
                     xs="auto"
+                    maxHeight={"50%"}
                     data-cy="process-cmd">
                     <ProcessCmdCard
                         onCopy={props.onCopyToClipboard}
                     data-cy="process-cmd">
                     <ProcessCmdCard
                         onCopy={props.onCopyToClipboard}
index d1492ddbf5912238e76d1fbc5b8aeeb181d7d1fe..d738ed045be7018b1b10995b4c4250f0dbe5e431 100644 (file)
@@ -15,7 +15,6 @@ import {
     Typography,
     Grid,
     Link,
     Typography,
     Grid,
     Link,
-    Button
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
 import {
 } from '@material-ui/core';
 import { ArvadosTheme } from 'common/custom-theme';
 import {
index 207a30acd0f6f0d28691fcf0d0db74e2e075efdc..e6a6b90cfe4ac1e095d675823297be85aa8e166b 100644 (file)
@@ -5,7 +5,7 @@
 import React from 'react';
 import { Field } from 'redux-form';
 import { memoize } from 'lodash/fp';
 import React from 'react';
 import { Field } from 'redux-form';
 import { memoize } from 'lodash/fp';
-import { require } from 'validators/require';
+import { fieldRequire } from 'validators/require';
 import { Select, MenuItem } from '@material-ui/core';
 import { EnumCommandInputParameter, CommandInputEnumSchema, isRequiredInput, getEnumType } from 'models/workflow';
 import { GenericInputProps, GenericInput } from './generic-input';
 import { Select, MenuItem } from '@material-ui/core';
 import { EnumCommandInputParameter, CommandInputEnumSchema, isRequiredInput, getEnumType } from 'models/workflow';
 import { GenericInputProps, GenericInput } from './generic-input';
@@ -17,7 +17,7 @@ export interface EnumInputProps {
 const getValidation = memoize(
     (input: EnumCommandInputParameter) => ([
         isRequiredInput(input)
 const getValidation = memoize(
     (input: EnumCommandInputParameter) => ([
         isRequiredInput(input)
-            ? require
+            ? fieldRequire
             : () => undefined,
     ]));
 
             : () => undefined,
     ]));
 
index 438bbe8e7e40163b55b8d363a53b82dc5f23ab01..b455135f3a2c12457692e9de89e2691048238b1e 100644 (file)
@@ -21,7 +21,7 @@ import { getUserUuid } from 'common/getuser';
 
 export type ProjectCommandInputParameter = GenericCommandInputParameter<ProjectResource, ProjectResource>;
 
 
 export type ProjectCommandInputParameter = GenericCommandInputParameter<ProjectResource, ProjectResource>;
 
-const require: any = (value?: ProjectResource) => (value === undefined);
+const isUndefined: any = (value?: ProjectResource) => (value === undefined);
 
 export interface ProjectInputProps {
     required: boolean;
 
 export interface ProjectInputProps {
     required: boolean;
@@ -37,7 +37,7 @@ export const ProjectInput = ({ required, input, options }: ProjectInputProps) =>
         commandInput={input}
         component={ProjectInputComponent as any}
         format={format}
         commandInput={input}
         component={ProjectInputComponent as any}
         format={format}
-        validate={required ? require : undefined}
+        validate={required ? isUndefined : undefined}
         {...{
             options,
             required
         {...{
             options,
             required
index 543100db1fe5d9e96fd2d6e18bd28674b15ac91b..e497004801ea0fce01cdcf24edacff0c2226065a 100644 (file)
@@ -6,7 +6,7 @@ import React from 'react';
 import { memoize } from 'lodash/fp';
 import { isRequiredInput, StringCommandInputParameter } from 'models/workflow';
 import { Field } from 'redux-form';
 import { memoize } from 'lodash/fp';
 import { isRequiredInput, StringCommandInputParameter } from 'models/workflow';
 import { Field } from 'redux-form';
-import { require } from 'validators/require';
+import { fieldRequire } from 'validators/require';
 import { GenericInputProps, GenericInput } from 'views/run-process-panel/inputs/generic-input';
 import { Input as MaterialInput } from '@material-ui/core';
 
 import { GenericInputProps, GenericInput } from 'views/run-process-panel/inputs/generic-input';
 import { Input as MaterialInput } from '@material-ui/core';
 
@@ -23,7 +23,7 @@ export const StringInput = ({ input }: StringInputProps) =>
 const getValidation = memoize(
     (input: StringCommandInputParameter) => ([
         isRequiredInput(input)
 const getValidation = memoize(
     (input: StringCommandInputParameter) => ([
         isRequiredInput(input)
-            ? require
+            ? fieldRequire
             : () => undefined,
     ]));
 
             : () => undefined,
     ]));
 
index e9693b50e5917ddfb8542ec26e42fc7cbb004c21..ed113264812504e4301d8f9bc40302b8eeb81bfa 100644 (file)
@@ -149,14 +149,13 @@ export const SearchResultsPanelView = withStyles(styles, { withTheme: true })(
                     setItemPath(tmpPath);
                 }
             })();
                     setItemPath(tmpPath);
                 }
             })();
-
-            // eslint-disable-next-line react-hooks/exhaustive-deps
+        // eslint-disable-next-line react-hooks/exhaustive-deps
         }, [selectedItem]);
 
         const onItemClick = useCallback((uuid) => {
             setSelectedItem(uuid);
             props.onItemClick(uuid);
         }, [selectedItem]);
 
         const onItemClick = useCallback((uuid) => {
             setSelectedItem(uuid);
             props.onItemClick(uuid);
-            // eslint-disable-next-line react-hooks/exhaustive-deps
+        // eslint-disable-next-line react-hooks/exhaustive-deps
         }, [props.onItemClick]);
 
         return <span data-cy='search-results' className={props.classes.searchResults}>
         }, [props.onItemClick]);
 
         return <span data-cy='search-results' className={props.classes.searchResults}>
index ac11bb719535fe42e504a09758ce861f158679ec..31804fbdb8cdb468b758888f1204cb1246b70876 100644 (file)
@@ -28,7 +28,7 @@ import {
 import { navigateTo } from "store/navigation/navigation-action";
 import { loadDetailsPanel } from "store/details-panel/details-panel-action";
 import { toggleTrashed } from "store/trash/trash-actions";
 import { navigateTo } from "store/navigation/navigation-action";
 import { loadDetailsPanel } from "store/details-panel/details-panel-action";
 import { toggleTrashed } from "store/trash/trash-actions";
-import { ContextMenuKind } from "views-components/context-menu/context-menu";
+import { ContextMenuKind } from 'views-components/context-menu/menu-item-sort';
 import { Dispatch } from "redux";
 import { createTree } from 'models/tree';
 import {
 import { Dispatch } from "redux";
 import { createTree } from 'models/tree';
 import {
index 36d432f95a98ed7a2222be97a5f9e445c09ac347..f75b36a60153f94e563970af97939c6dc986e65a 100644 (file)
@@ -174,7 +174,7 @@ const CardContentWithVirtualMachines = (props: VirtualMachineProps) =>
                         {virtualMachineSendRequest(props)}
                     </div>
                     <div className={props.classes.icon}>
                         {virtualMachineSendRequest(props)}
                     </div>
                     <div className={props.classes.icon}>
-                        <a href="https://doc.arvados.org/user/getting_started/vm-login-with-webshell.html" target="_blank" rel="noopener" className={props.classes.linkIcon}>
+                        <a href="https://doc.arvados.org/user/getting_started/vm-login-with-webshell.html" target="_blank" rel="noopener noreferrer" className={props.classes.linkIcon}>
                             <Tooltip title="Access VM using webshell">
                                 <HelpIcon />
                             </Tooltip>
                             <Tooltip title="Access VM using webshell">
                                 <HelpIcon />
                             </Tooltip>
@@ -238,7 +238,7 @@ const virtualMachinesTable = (props: VirtualMachineProps) =>
                             </TableCell>
                             <TableCell>
                                 {command}
                             </TableCell>
                             <TableCell>
                                 {command}
-                                <Tooltip title="Copy to clipboard">
+                                <Tooltip title="Copy link to clipboard">
                                     <span className={props.classes.copyIcon}>
                                         <CopyToClipboard text={command || ""} onCopy={() => props.onCopy!("Copied")}>
                                             <CopyIcon />
                                     <span className={props.classes.copyIcon}>
                                         <CopyToClipboard text={command || ""} onCopy={() => props.onCopy!("Copied")}>
                                             <CopyIcon />
index 3020e0d2987ccbd6a393a0b87100033ec83a9c01..9f3039133fe3b456adb9fffd2b390e90e15e45de 100644 (file)
@@ -322,7 +322,7 @@ const { classes, sidePanelIsCollapsed, isNotLinking, isTransitioning, isUserActi
 
     useEffect(()=>{
         if (isTransitioning) applyCollapsedState(savedWidth);
 
     useEffect(()=>{
         if (isTransitioning) applyCollapsedState(savedWidth);
-        // eslint-disable-next-line react-hooks/exhaustive-deps
+    // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [isTransitioning, savedWidth])
 
     applyCollapsedState(savedWidth);
     }, [isTransitioning, savedWidth])
 
     applyCollapsedState(savedWidth);
index 08f7108e6230629b7bb787ecfda07f41a51390f6..e2ef08c80cf75082374f780ef749d7d836a8d6fc 100644 (file)
@@ -10,7 +10,7 @@
     ],
     "sourceMap": true,
     "allowJs": true,
     ],
     "sourceMap": true,
     "allowJs": true,
-    "jsx": "preserve",
+    "jsx": "react-jsx",
     "moduleResolution": "node",
     "rootDir": "src",
     "baseUrl": "src",
     "moduleResolution": "node",
     "rootDir": "src",
     "baseUrl": "src",
@@ -34,6 +34,7 @@
     "alwaysStrict": false,
     "strictFunctionTypes": false,
     "strictPropertyInitialization": false,
     "alwaysStrict": false,
     "strictFunctionTypes": false,
     "strictPropertyInitialization": false,
+    "noFallthroughCasesInSwitch": false
   },
   "exclude": [
     "node_modules",
   },
   "exclude": [
     "node_modules",
index 7c0e2e6aef70e2c11609be2e81a27143266d808b..c098612c5db8078145aaeac4609fd7f754637049 100644 (file)
@@ -5,16 +5,42 @@ __metadata:
   version: 6
   cacheKey: 8
 
   version: 6
   cacheKey: 8
 
-"@babel/code-frame@npm:7.8.3":
-  version: 7.8.3
-  resolution: "@babel/code-frame@npm:7.8.3"
+"@aashutoshrathi/word-wrap@npm:^1.2.3":
+  version: 1.2.6
+  resolution: "@aashutoshrathi/word-wrap@npm:1.2.6"
+  checksum: ada901b9e7c680d190f1d012c84217ce0063d8f5c5a7725bb91ec3c5ed99bb7572680eb2d2938a531ccbaec39a95422fcd8a6b4a13110c7d98dd75402f66a0cd
+  languageName: node
+  linkType: hard
+
+"@ampproject/remapping@npm:^2.2.0":
+  version: 2.3.0
+  resolution: "@ampproject/remapping@npm:2.3.0"
+  dependencies:
+    "@jridgewell/gen-mapping": ^0.3.5
+    "@jridgewell/trace-mapping": ^0.3.24
+  checksum: d3ad7b89d973df059c4e8e6d7c972cbeb1bb2f18f002a3bd04ae0707da214cb06cc06929b65aa2313b9347463df2914772298bae8b1d7973f246bb3f2ab3e8f0
+  languageName: node
+  linkType: hard
+
+"@babel/code-frame@npm:7.10.4":
+  version: 7.10.4
+  resolution: "@babel/code-frame@npm:7.10.4"
+  dependencies:
+    "@babel/highlight": ^7.10.4
+  checksum: feb4543c8a509fe30f0f6e8d7aa84f82b41148b963b826cd330e34986f649a85cb63b2f13dd4effdf434ac555d16f14940b8ea5f4433297c2f5ff85486ded019
+  languageName: node
+  linkType: hard
+
+"@babel/code-frame@npm:7.12.11":
+  version: 7.12.11
+  resolution: "@babel/code-frame@npm:7.12.11"
   dependencies:
   dependencies:
-    "@babel/highlight": ^7.8.3
-  checksum: 5f3172b0c8d5db625fb88c9f6ab909cb164645152176dfa14c927c19c0774c41fa9ba494cb19cb5d152a414bd6732c41eae708f9f635e02a4ed0889ac239fe4c
+    "@babel/highlight": ^7.10.4
+  checksum: 3963eff3ebfb0e091c7e6f99596ef4b258683e4ba8a134e4e95f77afe85be5c931e184fff6435fb4885d12eba04a5e25532f7fbc292ca13b48e7da943474e2f3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.14.5, @babel/code-frame@npm:^7.8.3":
+"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/code-frame@npm:7.14.5"
   dependencies:
   version: 7.14.5
   resolution: "@babel/code-frame@npm:7.14.5"
   dependencies:
@@ -23,38 +49,78 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/compat-data@npm:^7.13.11, @babel/compat-data@npm:^7.14.5, @babel/compat-data@npm:^7.14.7, @babel/compat-data@npm:^7.9.0":
+"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.24.1, @babel/code-frame@npm:^7.24.2, @babel/code-frame@npm:^7.5.5":
+  version: 7.24.2
+  resolution: "@babel/code-frame@npm:7.24.2"
+  dependencies:
+    "@babel/highlight": ^7.24.2
+    picocolors: ^1.0.0
+  checksum: 70e867340cfe09ca5488b2f36372c45cabf43c79a5b6426e6df5ef0611ff5dfa75a57dda841895693de6008f32c21a7c97027a8c7bcabd63a7d17416cbead6f8
+  languageName: node
+  linkType: hard
+
+"@babel/compat-data@npm:^7.14.5":
   version: 7.14.7
   resolution: "@babel/compat-data@npm:7.14.7"
   checksum: dcf7a72cb650206857a98cce1ab0973e67689f19afc3b30cabff6dbddf563f188d54d3b3f92a70c6bc1feb9049d8b2e601540e1d435b6866c77bffad0a441c9f
   languageName: node
   linkType: hard
 
   version: 7.14.7
   resolution: "@babel/compat-data@npm:7.14.7"
   checksum: dcf7a72cb650206857a98cce1ab0973e67689f19afc3b30cabff6dbddf563f188d54d3b3f92a70c6bc1feb9049d8b2e601540e1d435b6866c77bffad0a441c9f
   languageName: node
   linkType: hard
 
-"@babel/core@npm:7.9.0":
-  version: 7.9.0
-  resolution: "@babel/core@npm:7.9.0"
+"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.5, @babel/compat-data@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/compat-data@npm:7.24.4"
+  checksum: 52ce371658dc7796c9447c9cb3b9c0659370d141b76997f21c5e0028cca4d026ca546b84bc8d157ce7ca30bd353d89f9238504eb8b7aefa9b1f178b4c100c2d4
+  languageName: node
+  linkType: hard
+
+"@babel/core@npm:7.12.3":
+  version: 7.12.3
+  resolution: "@babel/core@npm:7.12.3"
   dependencies:
   dependencies:
-    "@babel/code-frame": ^7.8.3
-    "@babel/generator": ^7.9.0
-    "@babel/helper-module-transforms": ^7.9.0
-    "@babel/helpers": ^7.9.0
-    "@babel/parser": ^7.9.0
-    "@babel/template": ^7.8.6
-    "@babel/traverse": ^7.9.0
-    "@babel/types": ^7.9.0
+    "@babel/code-frame": ^7.10.4
+    "@babel/generator": ^7.12.1
+    "@babel/helper-module-transforms": ^7.12.1
+    "@babel/helpers": ^7.12.1
+    "@babel/parser": ^7.12.3
+    "@babel/template": ^7.10.4
+    "@babel/traverse": ^7.12.1
+    "@babel/types": ^7.12.1
     convert-source-map: ^1.7.0
     debug: ^4.1.0
     gensync: ^1.0.0-beta.1
     json5: ^2.1.2
     convert-source-map: ^1.7.0
     debug: ^4.1.0
     gensync: ^1.0.0-beta.1
     json5: ^2.1.2
-    lodash: ^4.17.13
+    lodash: ^4.17.19
     resolve: ^1.3.2
     semver: ^5.4.1
     source-map: ^0.5.0
     resolve: ^1.3.2
     semver: ^5.4.1
     source-map: ^0.5.0
-  checksum: 0886b35c9cda80628bc61e47172c79d51ab1d1e693f95c037df371bf0a84ca5cd72c0183fb3d01f47c59395d7805d2e79d46660488d73b9966db5fb726ad561c
+  checksum: 29ee14dd7ae66c1af84d1b2864e1e9e1bec23b89f41e414917b10151ae1fcb6d3b6a8a25d028a7e22dba3bb7b69eb1f7f0d844797341357e36fa71ff967fb4a5
+  languageName: node
+  linkType: hard
+
+"@babel/core@npm:^7.0.0, @babel/core@npm:^7.12.3, @babel/core@npm:^7.16.0, @babel/core@npm:^7.7.5, @babel/core@npm:^7.8.4, @babel/core@npm:^7.9.0":
+  version: 7.24.4
+  resolution: "@babel/core@npm:7.24.4"
+  dependencies:
+    "@ampproject/remapping": ^2.2.0
+    "@babel/code-frame": ^7.24.2
+    "@babel/generator": ^7.24.4
+    "@babel/helper-compilation-targets": ^7.23.6
+    "@babel/helper-module-transforms": ^7.23.3
+    "@babel/helpers": ^7.24.4
+    "@babel/parser": ^7.24.4
+    "@babel/template": ^7.24.0
+    "@babel/traverse": ^7.24.1
+    "@babel/types": ^7.24.0
+    convert-source-map: ^2.0.0
+    debug: ^4.1.0
+    gensync: ^1.0.0-beta.2
+    json5: ^2.2.3
+    semver: ^6.3.1
+  checksum: 15ecad7581f3329995956ba461961b1af7bed48901f14fe962ccd3217edca60049e9e6ad4ce48134618397e6c90230168c842e2c28e47ef1f16c97dbbf663c61
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/core@npm:^7.1.0, @babel/core@npm:^7.4.5":
+"@babel/core@npm:^7.1.0":
   version: 7.14.6
   resolution: "@babel/core@npm:7.14.6"
   dependencies:
   version: 7.14.6
   resolution: "@babel/core@npm:7.14.6"
   dependencies:
@@ -77,7 +143,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/generator@npm:^7.14.5, @babel/generator@npm:^7.4.0, @babel/generator@npm:^7.9.0":
+"@babel/generator@npm:^7.12.1, @babel/generator@npm:^7.24.1, @babel/generator@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/generator@npm:7.24.4"
+  dependencies:
+    "@babel/types": ^7.24.0
+    "@jridgewell/gen-mapping": ^0.3.5
+    "@jridgewell/trace-mapping": ^0.3.25
+    jsesc: ^2.5.1
+  checksum: 1b6146c31386c9df3eb594a2c36b5c98da4f67f7c06edb3d68a442b92516b21bb5ba3ad7dbe0058fe76625ed24d66923e15c95b0df75ef1907d4068921a699b8
+  languageName: node
+  linkType: hard
+
+"@babel/generator@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/generator@npm:7.14.5"
   dependencies:
   version: 7.14.5
   resolution: "@babel/generator@npm:7.14.5"
   dependencies:
@@ -88,26 +166,25 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-annotate-as-pure@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-annotate-as-pure@npm:7.14.5"
+"@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.22.5":
+  version: 7.22.5
+  resolution: "@babel/helper-annotate-as-pure@npm:7.22.5"
   dependencies:
   dependencies:
-    "@babel/types": ^7.14.5
-  checksum: 18cefedda60003c2551dabe0e4ad278ef0507682680892c60e9f7cb75ae1dc9a065cddb3ce9964da76f220bf972af5262619eeac4b84c2b8aba1b031961215cc
+    "@babel/types": ^7.22.5
+  checksum: 53da330f1835c46f26b7bf4da31f7a496dee9fd8696cca12366b94ba19d97421ce519a74a837f687749318f94d1a37f8d1abcbf35e8ed22c32d16373b2f6198d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.14.5"
+"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.15":
+  version: 7.22.15
+  resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.15"
   dependencies:
   dependencies:
-    "@babel/helper-explode-assignable-expression": ^7.14.5
-    "@babel/types": ^7.14.5
-  checksum: 0d3571edff0a96d625503a3fd79643f66f8a5204e75c4351276c0d194240e1debe322a70ef9ff47952bd77ac76792f42d732922b00b5bd8b6e2c99909dc4f49b
+    "@babel/types": ^7.22.15
+  checksum: 639c697a1c729f9fafa2dd4c9af2e18568190299b5907bd4c2d0bc818fcbd1e83ffeecc2af24327a7faa7ac4c34edd9d7940510a5e66296c19bad17001cf5c7a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.14.5, @babel/helper-compilation-targets@npm:^7.8.7":
+"@babel/helper-compilation-targets@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-compilation-targets@npm:7.14.5"
   dependencies:
   version: 7.14.5
   resolution: "@babel/helper-compilation-targets@npm:7.14.5"
   dependencies:
@@ -121,100 +198,120 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-create-class-features-plugin@npm:^7.14.5, @babel/helper-create-class-features-plugin@npm:^7.14.6, @babel/helper-create-class-features-plugin@npm:^7.8.3":
-  version: 7.14.6
-  resolution: "@babel/helper-create-class-features-plugin@npm:7.14.6"
+"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.23.6":
+  version: 7.23.6
+  resolution: "@babel/helper-compilation-targets@npm:7.23.6"
   dependencies:
   dependencies:
-    "@babel/helper-annotate-as-pure": ^7.14.5
-    "@babel/helper-function-name": ^7.14.5
-    "@babel/helper-member-expression-to-functions": ^7.14.5
-    "@babel/helper-optimise-call-expression": ^7.14.5
-    "@babel/helper-replace-supers": ^7.14.5
-    "@babel/helper-split-export-declaration": ^7.14.5
+    "@babel/compat-data": ^7.23.5
+    "@babel/helper-validator-option": ^7.23.5
+    browserslist: ^4.22.2
+    lru-cache: ^5.1.1
+    semver: ^6.3.1
+  checksum: c630b98d4527ac8fe2c58d9a06e785dfb2b73ec71b7c4f2ddf90f814b5f75b547f3c015f110a010fd31f76e3864daaf09f3adcd2f6acdbfb18a8de3a48717590
+  languageName: node
+  linkType: hard
+
+"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0, @babel/helper-create-class-features-plugin@npm:^7.24.1, @babel/helper-create-class-features-plugin@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/helper-create-class-features-plugin@npm:7.24.4"
+  dependencies:
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-function-name": ^7.23.0
+    "@babel/helper-member-expression-to-functions": ^7.23.0
+    "@babel/helper-optimise-call-expression": ^7.22.5
+    "@babel/helper-replace-supers": ^7.24.1
+    "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5
+    "@babel/helper-split-export-declaration": ^7.22.6
+    semver: ^6.3.1
   peerDependencies:
     "@babel/core": ^7.0.0
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 9d9c3c6f469bc5da4e5819979d0f70bf8a824967661743800741b5560cfa3cf811d52ab14dc00dd6e839814f8db39cf3118c08d550c487680969c40c9ccf2e2a
+  checksum: 75b0a51ae1f7232932559779b78711c271404d02d069156d1bd9a7982c165c5134058d2ec2d8b5f2e42026ee4f52ba2a30c86a7aa3bce6b5fd0991eb721abc8c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-create-regexp-features-plugin@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-create-regexp-features-plugin@npm:7.14.5"
+"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.22.15, @babel/helper-create-regexp-features-plugin@npm:^7.22.5":
+  version: 7.22.15
+  resolution: "@babel/helper-create-regexp-features-plugin@npm:7.22.15"
   dependencies:
   dependencies:
-    "@babel/helper-annotate-as-pure": ^7.14.5
-    regexpu-core: ^4.7.1
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    regexpu-core: ^5.3.1
+    semver: ^6.3.1
   peerDependencies:
     "@babel/core": ^7.0.0
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: c2636d0a6ea6d57eb3603ba9b223fd6ec273a3d8171eb8d84a357ff028cd747ab383b1d7cef84a4df5f9aebb321d43599895f562f3c8aa96314d4847aa59710e
+  checksum: 0243b8d4854f1dc8861b1029a46d3f6393ad72f366a5a08e36a4648aa682044f06da4c6e87a456260e1e1b33c999f898ba591a0760842c1387bcc93fbf2151a6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-define-polyfill-provider@npm:^0.2.2":
-  version: 0.2.3
-  resolution: "@babel/helper-define-polyfill-provider@npm:0.2.3"
+"@babel/helper-define-polyfill-provider@npm:^0.6.1":
+  version: 0.6.1
+  resolution: "@babel/helper-define-polyfill-provider@npm:0.6.1"
   dependencies:
   dependencies:
-    "@babel/helper-compilation-targets": ^7.13.0
-    "@babel/helper-module-imports": ^7.12.13
-    "@babel/helper-plugin-utils": ^7.13.0
-    "@babel/traverse": ^7.13.0
+    "@babel/helper-compilation-targets": ^7.22.6
+    "@babel/helper-plugin-utils": ^7.22.5
     debug: ^4.1.1
     lodash.debounce: ^4.0.8
     resolve: ^1.14.2
     debug: ^4.1.1
     lodash.debounce: ^4.0.8
     resolve: ^1.14.2
-    semver: ^6.1.2
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.4.0-0
-  checksum: 797699fe870e45bdbc7c4128963427f7d6240609b700b3f2c0a2f2f187e5f848ba704bcfe58d7d91796cabc5001fae01746b3efda113beb5b5b824927cf59fdb
+    "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
+  checksum: b45deb37ce1342d862422e81a3d25ff55f9c7ca52fe303405641e2add8db754091aaaa2119047a0f0b85072221fbddaa92adf53104274661d2795783b56bea2c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-explode-assignable-expression@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-explode-assignable-expression@npm:7.14.5"
+"@babel/helper-environment-visitor@npm:^7.22.20":
+  version: 7.22.20
+  resolution: "@babel/helper-environment-visitor@npm:7.22.20"
+  checksum: d80ee98ff66f41e233f36ca1921774c37e88a803b2f7dca3db7c057a5fea0473804db9fb6729e5dbfd07f4bed722d60f7852035c2c739382e84c335661590b69
+  languageName: node
+  linkType: hard
+
+"@babel/helper-function-name@npm:^7.22.5, @babel/helper-function-name@npm:^7.23.0":
+  version: 7.23.0
+  resolution: "@babel/helper-function-name@npm:7.23.0"
   dependencies:
   dependencies:
-    "@babel/types": ^7.14.5
-  checksum: f3b34c54ad26e48e1409f21aaac8ee5b5fa3bd2917ce4df496f57daec12b6132b2d5c2618da807458e97bc2d7894c5bf505cc96789e0c289dcc9948d7844bb03
+    "@babel/template": ^7.22.15
+    "@babel/types": ^7.23.0
+  checksum: e44542257b2d4634a1f979244eb2a4ad8e6d75eb6761b4cfceb56b562f7db150d134bc538c8e6adca3783e3bc31be949071527aa8e3aab7867d1ad2d84a26e10
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-function-name@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-function-name@npm:7.14.5"
+"@babel/helper-hoist-variables@npm:^7.22.5":
+  version: 7.22.5
+  resolution: "@babel/helper-hoist-variables@npm:7.22.5"
   dependencies:
   dependencies:
-    "@babel/helper-get-function-arity": ^7.14.5
-    "@babel/template": ^7.14.5
-    "@babel/types": ^7.14.5
-  checksum: fd8ffa82f7622b6e9a6294fb3b98b42e743ab2a8e3c329367667a960b5b98b48bc5ebf8be7308981f1985b9f3c69e1a3b4a91c8944ae97c31803240da92fb3c8
+    "@babel/types": ^7.22.5
+  checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-get-function-arity@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-get-function-arity@npm:7.14.5"
+"@babel/helper-member-expression-to-functions@npm:^7.14.5":
+  version: 7.14.7
+  resolution: "@babel/helper-member-expression-to-functions@npm:7.14.7"
   dependencies:
     "@babel/types": ^7.14.5
   dependencies:
     "@babel/types": ^7.14.5
-  checksum: a60779918b677a35e177bb4f46babfd54e9790587b6a4f076092a9eff2a940cbeacdeb10c94331b26abfe838769554d72293d16df897246cfccd1444e5e27cb7
+  checksum: 1768b849224002d7a8553226ad73e1e957fb6184b68234d5df7a45cf8e4453ed1208967c1cace1a4d973b223ddc881d105e372945ec688f09485dff0e8ed6180
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-hoist-variables@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-hoist-variables@npm:7.14.5"
+"@babel/helper-member-expression-to-functions@npm:^7.23.0":
+  version: 7.23.0
+  resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0"
   dependencies:
   dependencies:
-    "@babel/types": ^7.14.5
-  checksum: 35af58eebffca10988de7003e044ce2d27212aea72ac6d2c4604137da7f1e193cc694d8d60805d0d0beaf3d990f6f2dcc2622c52e3d3148e37017a29cacf2e56
+    "@babel/types": ^7.23.0
+  checksum: 494659361370c979ada711ca685e2efe9460683c36db1b283b446122596602c901e291e09f2f980ecedfe6e0f2bd5386cb59768285446530df10c14df1024e75
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-member-expression-to-functions@npm:^7.14.5":
-  version: 7.14.7
-  resolution: "@babel/helper-member-expression-to-functions@npm:7.14.7"
+"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.22.15, @babel/helper-module-imports@npm:^7.24.1, @babel/helper-module-imports@npm:^7.24.3":
+  version: 7.24.3
+  resolution: "@babel/helper-module-imports@npm:7.24.3"
   dependencies:
   dependencies:
-    "@babel/types": ^7.14.5
-  checksum: 1768b849224002d7a8553226ad73e1e957fb6184b68234d5df7a45cf8e4453ed1208967c1cace1a4d973b223ddc881d105e372945ec688f09485dff0e8ed6180
+    "@babel/types": ^7.24.0
+  checksum: c23492189ba97a1ec7d37012336a5661174e8b88194836b6bbf90d13c3b72c1db4626263c654454986f924c6da8be7ba7f9447876d709cd00bd6ffde6ec00796
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.14.5, @babel/helper-module-imports@npm:^7.8.3":
+"@babel/helper-module-imports@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-module-imports@npm:7.14.5"
   dependencies:
   version: 7.14.5
   resolution: "@babel/helper-module-imports@npm:7.14.5"
   dependencies:
@@ -223,7 +320,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-module-transforms@npm:^7.14.5, @babel/helper-module-transforms@npm:^7.9.0":
+"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.23.3":
+  version: 7.23.3
+  resolution: "@babel/helper-module-transforms@npm:7.23.3"
+  dependencies:
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-module-imports": ^7.22.15
+    "@babel/helper-simple-access": ^7.22.5
+    "@babel/helper-split-export-declaration": ^7.22.6
+    "@babel/helper-validator-identifier": ^7.22.20
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  checksum: 5d0895cfba0e16ae16f3aa92fee108517023ad89a855289c4eb1d46f7aef4519adf8e6f971e1d55ac20c5461610e17213f1144097a8f932e768a9132e2278d71
+  languageName: node
+  linkType: hard
+
+"@babel/helper-module-transforms@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-module-transforms@npm:7.14.5"
   dependencies:
   version: 7.14.5
   resolution: "@babel/helper-module-transforms@npm:7.14.5"
   dependencies:
@@ -248,21 +360,39 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
+"@babel/helper-optimise-call-expression@npm:^7.22.5":
+  version: 7.22.5
+  resolution: "@babel/helper-optimise-call-expression@npm:7.22.5"
+  dependencies:
+    "@babel/types": ^7.22.5
+  checksum: c70ef6cc6b6ed32eeeec4482127e8be5451d0e5282d5495d5d569d39eb04d7f1d66ec99b327f45d1d5842a9ad8c22d48567e93fc502003a47de78d122e355f7c
+  languageName: node
+  linkType: hard
+
+"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
   version: 7.14.5
   resolution: "@babel/helper-plugin-utils@npm:7.14.5"
   checksum: fe20e90a24d02770a60ebe80ab9f0dfd7258503cea8006c71709ac9af1aa3e47b0de569499673f11ea6c99597f8c0e4880ae1d505986e61101b69716820972fe
   languageName: node
   linkType: hard
 
   version: 7.14.5
   resolution: "@babel/helper-plugin-utils@npm:7.14.5"
   checksum: fe20e90a24d02770a60ebe80ab9f0dfd7258503cea8006c71709ac9af1aa3e47b0de569499673f11ea6c99597f8c0e4880ae1d505986e61101b69716820972fe
   languageName: node
   linkType: hard
 
-"@babel/helper-remap-async-to-generator@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-remap-async-to-generator@npm:7.14.5"
+"@babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.0":
+  version: 7.24.0
+  resolution: "@babel/helper-plugin-utils@npm:7.24.0"
+  checksum: e2baa0eede34d2fa2265947042aa84d444aa48dc51e9feedea55b67fc1bc3ab051387e18b33ca7748285a6061390831ab82f8a2c767d08470b93500ec727e9b9
+  languageName: node
+  linkType: hard
+
+"@babel/helper-remap-async-to-generator@npm:^7.22.20":
+  version: 7.22.20
+  resolution: "@babel/helper-remap-async-to-generator@npm:7.22.20"
   dependencies:
   dependencies:
-    "@babel/helper-annotate-as-pure": ^7.14.5
-    "@babel/helper-wrap-function": ^7.14.5
-    "@babel/types": ^7.14.5
-  checksum: 022594a15caed0d3bbac52e27eef0f20f9dceb85921b682df55f3bb21dee6fea645b03663e84fdfaadc6b88f4b83b012858520813c15e88728bbc5e16bf3fa29
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-wrap-function": ^7.22.20
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  checksum: 2fe6300a6f1b58211dffa0aed1b45d4958506d096543663dba83bd9251fe8d670fa909143a65b45e72acb49e7e20fbdb73eae315d9ddaced467948c3329986e7
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -278,6 +408,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@babel/helper-replace-supers@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/helper-replace-supers@npm:7.24.1"
+  dependencies:
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-member-expression-to-functions": ^7.23.0
+    "@babel/helper-optimise-call-expression": ^7.22.5
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  checksum: c04182c34a3195c6396de2f2945f86cb60daa94ca7392db09bd8b0d4e7a15b02fbe1947c70f6062c87eadaea6d7135207129efa35cf458ea0987bab8c0f02d5a
+  languageName: node
+  linkType: hard
+
 "@babel/helper-simple-access@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-simple-access@npm:7.14.5"
 "@babel/helper-simple-access@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-simple-access@npm:7.14.5"
@@ -287,12 +430,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-skip-transparent-expression-wrappers@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.14.5"
+"@babel/helper-simple-access@npm:^7.22.5":
+  version: 7.22.5
+  resolution: "@babel/helper-simple-access@npm:7.22.5"
   dependencies:
   dependencies:
-    "@babel/types": ^7.14.5
-  checksum: d16937eb08d57d2577902fa6d05ac4b1695602babd9dff9890fa8e56b593fdc997ad24de13fdaf15617036bfacf3493ea569898a5ac0538c2a831aa163f18985
+    "@babel/types": ^7.22.5
+  checksum: fe9686714caf7d70aedb46c3cce090f8b915b206e09225f1e4dbc416786c2fdbbee40b38b23c268b7ccef749dd2db35f255338fb4f2444429874d900dede5ad2
+  languageName: node
+  linkType: hard
+
+"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0, @babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5":
+  version: 7.22.5
+  resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5"
+  dependencies:
+    "@babel/types": ^7.22.5
+  checksum: 1012ef2295eb12dc073f2b9edf3425661e9b8432a3387e62a8bc27c42963f1f216ab3124228015c748770b2257b4f1fda882ca8fa34c0bf485e929ae5bc45244
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -305,6 +457,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@babel/helper-split-export-declaration@npm:^7.22.6":
+  version: 7.22.6
+  resolution: "@babel/helper-split-export-declaration@npm:7.22.6"
+  dependencies:
+    "@babel/types": ^7.22.5
+  checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921
+  languageName: node
+  linkType: hard
+
+"@babel/helper-string-parser@npm:^7.23.4":
+  version: 7.24.1
+  resolution: "@babel/helper-string-parser@npm:7.24.1"
+  checksum: 8404e865b06013979a12406aab4c0e8d2e377199deec09dfe9f57b833b0c9ce7b6e8c1c553f2da8d0bcd240c5005bd7a269f4fef0d628aeb7d5fe035c436fb67
+  languageName: node
+  linkType: hard
+
 "@babel/helper-validator-identifier@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-validator-identifier@npm:7.14.5"
 "@babel/helper-validator-identifier@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-validator-identifier@npm:7.14.5"
@@ -319,6 +487,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@babel/helper-validator-identifier@npm:^7.22.20":
+  version: 7.22.20
+  resolution: "@babel/helper-validator-identifier@npm:7.22.20"
+  checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc
+  languageName: node
+  linkType: hard
+
 "@babel/helper-validator-option@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-validator-option@npm:7.14.5"
 "@babel/helper-validator-option@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/helper-validator-option@npm:7.14.5"
@@ -326,19 +501,36 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helper-wrap-function@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/helper-wrap-function@npm:7.14.5"
+"@babel/helper-validator-option@npm:^7.23.5":
+  version: 7.23.5
+  resolution: "@babel/helper-validator-option@npm:7.23.5"
+  checksum: 537cde2330a8aede223552510e8a13e9c1c8798afee3757995a7d4acae564124fe2bf7e7c3d90d62d3657434a74340a274b3b3b1c6f17e9a2be1f48af29cb09e
+  languageName: node
+  linkType: hard
+
+"@babel/helper-wrap-function@npm:^7.22.20":
+  version: 7.22.20
+  resolution: "@babel/helper-wrap-function@npm:7.22.20"
   dependencies:
   dependencies:
-    "@babel/helper-function-name": ^7.14.5
-    "@babel/template": ^7.14.5
-    "@babel/traverse": ^7.14.5
-    "@babel/types": ^7.14.5
-  checksum: d5c4bec02396f00d305ae2b60cfa5f3ec27d196a71b88107745b6be4fe257ebe54deedb6ee3997c8c9a2cc5c2571d567c22e9b866109490a2aa7f79a1a2272e2
+    "@babel/helper-function-name": ^7.22.5
+    "@babel/template": ^7.22.15
+    "@babel/types": ^7.22.19
+  checksum: 221ed9b5572612aeb571e4ce6a256f2dee85b3c9536f1dd5e611b0255e5f59a3d0ec392d8d46d4152149156a8109f92f20379b1d6d36abb613176e0e33f05fca
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/helpers@npm:^7.14.6, @babel/helpers@npm:^7.9.0":
+"@babel/helpers@npm:^7.12.1, @babel/helpers@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/helpers@npm:7.24.4"
+  dependencies:
+    "@babel/template": ^7.24.0
+    "@babel/traverse": ^7.24.1
+    "@babel/types": ^7.24.0
+  checksum: ecd2dc0b3b32e24b97fa3bcda432dd3235b77c2be1e16eafc35b8ef8f6c461faa99796a8bc2431a408c98b4aabfd572c160e2b67ecea4c5c9dd3a8314a97994a
+  languageName: node
+  linkType: hard
+
+"@babel/helpers@npm:^7.14.6":
   version: 7.14.6
   resolution: "@babel/helpers@npm:7.14.6"
   dependencies:
   version: 7.14.6
   resolution: "@babel/helpers@npm:7.14.6"
   dependencies:
@@ -349,7 +541,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/highlight@npm:^7.14.5, @babel/highlight@npm:^7.8.3":
+"@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.24.2":
+  version: 7.24.2
+  resolution: "@babel/highlight@npm:7.24.2"
+  dependencies:
+    "@babel/helper-validator-identifier": ^7.22.20
+    chalk: ^2.4.2
+    js-tokens: ^4.0.0
+    picocolors: ^1.0.0
+  checksum: 5f17b131cc3ebf3ab285a62cf98a404aef1bd71a6be045e748f8d5bf66d6a6e1aefd62f5972c84369472e8d9f22a614c58a89cd331eb60b7ba965b31b1bbeaf5
+  languageName: node
+  linkType: hard
+
+"@babel/highlight@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/highlight@npm:7.14.5"
   dependencies:
   version: 7.14.5
   resolution: "@babel/highlight@npm:7.14.5"
   dependencies:
@@ -360,7 +564,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.5, @babel/parser@npm:^7.14.6, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.4.3, @babel/parser@npm:^7.7.0, @babel/parser@npm:^7.9.0":
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.5, @babel/parser@npm:^7.14.6, @babel/parser@npm:^7.7.0":
   version: 7.14.7
   resolution: "@babel/parser@npm:7.14.7"
   bin:
   version: 7.14.7
   resolution: "@babel/parser@npm:7.14.7"
   bin:
@@ -369,280 +573,183 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.14.5"
-  dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-skip-transparent-expression-wrappers": ^7.14.5
-    "@babel/plugin-proposal-optional-chaining": ^7.14.5
-  peerDependencies:
-    "@babel/core": ^7.13.0
-  checksum: 17331fd4c1de860ac78aa3195eb5bd058c4eb24a8f2c6e719f079f9c86cbdb53d9a8affc2f9f78b6fc257afef03811922c2d16addad5d5f6224d2820da1c9f45
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-proposal-async-generator-functions@npm:^7.14.7, @babel/plugin-proposal-async-generator-functions@npm:^7.8.3":
-  version: 7.14.7
-  resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.14.7"
-  dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-remap-async-to-generator": ^7.14.5
-    "@babel/plugin-syntax-async-generators": ^7.8.4
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: 09343a79385615f8d5f95aaef7c44af5e899c82f030f3d73546c2ffffa567c0949f0405052d7e32f643c0eb2a23590a5050f4606855b3506246d3d60e46f32e3
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-proposal-class-properties@npm:7.8.3":
-  version: 7.8.3
-  resolution: "@babel/plugin-proposal-class-properties@npm:7.8.3"
-  dependencies:
-    "@babel/helper-create-class-features-plugin": ^7.8.3
-    "@babel/helper-plugin-utils": ^7.8.3
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: 5ac3435b0393b09162234f8e4c56c6321f038d205af447563f8389e7ed447904c4cdedf958e6a5ed092692fcc2eff980913efff19589c0969dd9846be4710867
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-proposal-class-properties@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-class-properties@npm:7.14.5"
-  dependencies:
-    "@babel/helper-create-class-features-plugin": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: fe2aa0a44f8ea121e10c856d6fb4fca418dc42451258ef6ed29321ca740080fba420ebd3d6700d0456c34c2ab2044f9ce4308498321f52a93184ff5adb015aae
+"@babel/parser@npm:^7.12.3, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.0, @babel/parser@npm:^7.24.1, @babel/parser@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/parser@npm:7.24.4"
+  bin:
+    parser: ./bin/babel-parser.js
+  checksum: 94c9e3e592894cd6fc57c519f4e06b65463df9be5f01739bb0d0bfce7ffcf99b3c2fdadd44dc59cc858ba2739ce6e469813a941c2f2dfacf333a3b2c9c5c8465
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-class-static-block@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-class-static-block@npm:7.14.5"
+"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.4"
   dependencies:
   dependencies:
-    "@babel/helper-create-class-features-plugin": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-class-static-block": ^7.14.5
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.12.0
-  checksum: 0275d0643dacd08638c2d3c129158ad0c2dea6a26e78fa4b2129811a29460ff9a6459d1955a19bfa3b9ed67ba2bb3c88676823ad207b2de4f0c65e0c3751d75c
+    "@babel/core": ^7.0.0
+  checksum: 0be3f41b1b865d7a4ed1a432337be48de67989d0b4e47def34a05097a804b6fc193115f97c954fd757339e0b80030ecf1d0a3d3fd6e7e91718644de0a5aae3d3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-decorators@npm:7.8.3":
-  version: 7.8.3
-  resolution: "@babel/plugin-proposal-decorators@npm:7.8.3"
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-create-class-features-plugin": ^7.8.3
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-syntax-decorators": ^7.8.3
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: f9aa852dd77657d29c19b16bc3a7d8c30e5964a9aa8eb541bf440f34659693631793d4c798c92e344e767c9ccc43baae9e595d86e589bfa4bb157fc07a5e0c05
+    "@babel/core": ^7.0.0
+  checksum: ec5fddc8db6de0e0082a883f21141d6f4f9f9f0bc190d662a732b5e9a506aae5d7d2337049a1bf055d7cb7add6f128036db6d4f47de5e9ac1be29e043c8b7ca8
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-dynamic-import@npm:^7.14.5, @babel/plugin-proposal-dynamic-import@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-dynamic-import@npm:7.14.5"
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-dynamic-import": ^7.8.3
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5
+    "@babel/plugin-transform-optional-chaining": ^7.24.1
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: 47be4b5f8824f8690b47d99a34d52de0e6c19d0b99f26c1f9a2e4cc49e05082bcef7248c610bb3830ae84cec928713c7774f4929fca4fa72df570df7a76a9d2b
+    "@babel/core": ^7.13.0
+  checksum: e18235463e716ac2443938aaec3c18b40c417a1746fba0fa4c26cf4d71326b76ef26c002081ab1b445abfae98e063d561519aa55672dddc1ef80b3940211ffbb
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-export-namespace-from@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.14.5"
+"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-export-namespace-from": ^7.8.3
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: b3f4e0cc196f7ad9132816bb350124e8932bc047ab946e431f85bae9649b0de384c54261a60c050a2b8220703408fc089f90349ad008ed69a70944a6f3048d0e
+    "@babel/core": ^7.0.0
+  checksum: b5e5889ce5ef51e813e3063cd548f55eb3c88e925c3c08913f334e15d62496861e538ae52a3974e0c56a3044ed8fd5033faea67a64814324af56edc9865b7359
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-json-strings@npm:^7.14.5, @babel/plugin-proposal-json-strings@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-json-strings@npm:7.14.5"
+"@babel/plugin-proposal-class-properties@npm:^7.16.0":
+  version: 7.18.6
+  resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-json-strings": ^7.8.3
+    "@babel/helper-create-class-features-plugin": ^7.18.6
+    "@babel/helper-plugin-utils": ^7.18.6
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 51dafe70237860569c9c27dc6a0db83e149bf7babb0fcafa9dbcd55a960b443f7b5bb695956c6e116e46b3dbd2a6777ead62bcad843aff8c1916c1be56e2f504
+  checksum: 49a78a2773ec0db56e915d9797e44fd079ab8a9b2e1716e0df07c92532f2c65d76aeda9543883916b8e0ff13606afeffa67c5b93d05b607bc87653ad18a91422
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-logical-assignment-operators@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.14.5"
+"@babel/plugin-proposal-decorators@npm:^7.16.4":
+  version: 7.24.1
+  resolution: "@babel/plugin-proposal-decorators@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4
+    "@babel/helper-create-class-features-plugin": ^7.24.1
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-decorators": ^7.24.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 08b6dbc991c4824b0d8bfabf46c8254fce02d2df04627b8849cf15a4b6de75629c10c7c83d1e6834cdcebfc98b16264ce2dd32aa9c0fae900ed2af807d5ac42b
+  checksum: b9375c64656bf9ae6d2eeb965c40823e6447f0f4594979d037231884c0f3a92af97172087f35a05e90b8ca0ccb47551b013998e85853c1c634d47b341f4deece
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-nullish-coalescing-operator@npm:7.8.3":
-  version: 7.8.3
-  resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.8.3"
+"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.16.0":
+  version: 7.18.6
+  resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.0
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: 36a87fa8f0ca709f66671ebd1af3f865fd1798e59cbf57f8db71cf69ef15ee8cf21ec54833b34c5e77b8a5a60d5b4e9ae949c00fec8eb320d5bc299bfcc3eae1
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.14.5, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.14.5"
-  dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.18.6
     "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
     "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 033d9483c2feb74928fbb83a73948eb1179c8852d2ae507fbfc37752d2dbf702c9ad0daaf1eaa029f81b12b7e2470061b4f611db88b7293f0e9a71eba288a430
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-proposal-numeric-separator@npm:7.8.3":
-  version: 7.8.3
-  resolution: "@babel/plugin-proposal-numeric-separator@npm:7.8.3"
-  dependencies:
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-syntax-numeric-separator": ^7.8.3
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: fd2d926e5bba27180e79c7511cb62d423a74690419f97ae1804b4a36632017ab2ef763ce2a84810b7c350a5d3e42a8e263780dadefa4ad6288923a2d13950170
+  checksum: 949c9ddcdecdaec766ee610ef98f965f928ccc0361dd87cf9f88cf4896a6ccd62fce063d4494778e50da99dea63d270a1be574a62d6ab81cbe9d85884bf55a7d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-numeric-separator@npm:^7.14.5, @babel/plugin-proposal-numeric-separator@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-numeric-separator@npm:7.14.5"
+"@babel/plugin-proposal-numeric-separator@npm:^7.16.0":
+  version: 7.18.6
+  resolution: "@babel/plugin-proposal-numeric-separator@npm:7.18.6"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.18.6
     "@babel/plugin-syntax-numeric-separator": ^7.10.4
   peerDependencies:
     "@babel/core": ^7.0.0-0
     "@babel/plugin-syntax-numeric-separator": ^7.10.4
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 22093297ec9aed3938b39f4efa1b518252fe7b0835902c3066f0ae6a864ac253b986a4a21a6092aa068d0702d7b09bed74e56cf39f2da8b4f3f43e0747bffb62
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-proposal-object-rest-spread@npm:^7.14.7, @babel/plugin-proposal-object-rest-spread@npm:^7.9.0":
-  version: 7.14.7
-  resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.14.7"
-  dependencies:
-    "@babel/compat-data": ^7.14.7
-    "@babel/helper-compilation-targets": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-object-rest-spread": ^7.8.3
-    "@babel/plugin-transform-parameters": ^7.14.5
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: a35192868166fb5a62003a56ce2c266f74ae680f1d9589652c4495145240dd138a9505301bb5adca069cb874d6f0f733dc2f3d1d05f71a06019735c29c4d1a11
+  checksum: f370ea584c55bf4040e1f78c80b4eeb1ce2e6aaa74f87d1a48266493c33931d0b6222d8cee3a082383d6bb648ab8d6b7147a06f974d3296ef3bc39c7851683ec
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-optional-catch-binding@npm:^7.14.5, @babel/plugin-proposal-optional-catch-binding@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-optional-catch-binding@npm:7.14.5"
+"@babel/plugin-proposal-optional-chaining@npm:^7.16.0":
+  version: 7.21.0
+  resolution: "@babel/plugin-proposal-optional-chaining@npm:7.21.0"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-optional-catch-binding": ^7.8.3
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: f9c1b2b34fef1bde85feeb0b438131f526056161e10b6fb91c74a5828ad39d2a20521b5c3cefc7367a7e5fc792b7c7e607bf278d7999b5d89824c34af3174eae
-  languageName: node
-  linkType: hard
-
-"@babel/plugin-proposal-optional-chaining@npm:7.9.0":
-  version: 7.9.0
-  resolution: "@babel/plugin-proposal-optional-chaining@npm:7.9.0"
-  dependencies:
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-syntax-optional-chaining": ^7.8.0
+    "@babel/helper-plugin-utils": ^7.20.2
+    "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0
+    "@babel/plugin-syntax-optional-chaining": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: da2d4cbf6fe1b3579c83b83cd6b7deb1fa4f907b53eceed8906cf60b0ed02f3dde01bb891040dea67e316865bb8c890ca3272a422d359d2d0a7826c7250572d3
+  checksum: 11c5449e01b18bb8881e8e005a577fa7be2fe5688e2382c8822d51f8f7005342a301a46af7b273b1f5645f9a7b894c428eee8526342038a275ef6ba4c8d8d746
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-optional-chaining@npm:^7.14.5, @babel/plugin-proposal-optional-chaining@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-optional-chaining@npm:7.14.5"
+"@babel/plugin-proposal-private-methods@npm:^7.16.0":
+  version: 7.18.6
+  resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-skip-transparent-expression-wrappers": ^7.14.5
-    "@babel/plugin-syntax-optional-chaining": ^7.8.3
+    "@babel/helper-create-class-features-plugin": ^7.18.6
+    "@babel/helper-plugin-utils": ^7.18.6
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 9e39e20d162bea2241b4c24ea8a339f872a04954a5155c606bf2437edaa1a15b8a517daee4b2b09cfd42d826b93c57f080aa9fbb13c60a8f3a7a72963badf2df
+  checksum: 22d8502ee96bca99ad2c8393e8493e2b8d4507576dd054490fd8201a36824373440106f5b098b6d821b026c7e72b0424ff4aeca69ed5f42e48f029d3a156d5ad
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-private-methods@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-private-methods@npm:7.14.5"
-  dependencies:
-    "@babel/helper-create-class-features-plugin": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+"@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2":
+  version: 7.21.0-placeholder-for-preset-env.2
+  resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0-placeholder-for-preset-env.2"
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: badacc1d68c8cf92a7ba973e3c283bc3aebf586a6573b6d18a96461ce18039d4cdc0135edac1b810df8d92cfca628115d98a0ad83ed8f15bf15eaff21539bf32
+  checksum: d97745d098b835d55033ff3a7fb2b895b9c5295b08a5759e4f20df325aa385a3e0bc9bd5ad8f2ec554a44d4e6525acfc257b8c5848a1345cb40f26a30e277e91
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-private-property-in-object@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.14.5"
+"@babel/plugin-proposal-private-property-in-object@npm:^7.16.0":
+  version: 7.21.11
+  resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.11"
   dependencies:
   dependencies:
-    "@babel/helper-annotate-as-pure": ^7.14.5
-    "@babel/helper-create-class-features-plugin": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-annotate-as-pure": ^7.18.6
+    "@babel/helper-create-class-features-plugin": ^7.21.0
+    "@babel/helper-plugin-utils": ^7.20.2
     "@babel/plugin-syntax-private-property-in-object": ^7.14.5
   peerDependencies:
     "@babel/core": ^7.0.0-0
     "@babel/plugin-syntax-private-property-in-object": ^7.14.5
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: a11da6a52eb13d6dcb6ed36993a81e9746404f6e83d32be16142911b7e5768293d8c4c5373d182ef25cb94d0b18c0c27a07f4553be042ee2dc49f7179f8cbfe2
+  checksum: 1b880543bc5f525b360b53d97dd30807302bb82615cd42bf931968f59003cac75629563d6b104868db50abd22235b3271fdf679fea5db59a267181a99cc0c265
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-proposal-unicode-property-regex@npm:^7.14.5, @babel/plugin-proposal-unicode-property-regex@npm:^7.4.4, @babel/plugin-proposal-unicode-property-regex@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-proposal-unicode-property-regex@npm:7.14.5"
+"@babel/plugin-syntax-async-generators@npm:^7.8.4":
+  version: 7.8.4
+  resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4"
   dependencies:
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.8.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 58bd3277a972a33d101d29ab4f52e964b6e8ec218eb84f764b4ea67bf8ed362909760812d3f7451ee5e54dc273bd81bc5a00cd2c13e8fb64a47ec117cb69d51b
+  checksum: 7ed1c1d9b9e5b64ef028ea5e755c0be2d4e5e4e3d6cf7df757b9a8c4cfa4193d268176d0f1f7fbecdda6fe722885c7fda681f480f3741d8a2d26854736f05367
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-async-generators@npm:^7.8.0, @babel/plugin-syntax-async-generators@npm:^7.8.4":
-  version: 7.8.4
-  resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4"
+"@babel/plugin-syntax-bigint@npm:^7.8.3":
+  version: 7.8.3
+  resolution: "@babel/plugin-syntax-bigint@npm:7.8.3"
   dependencies:
     "@babel/helper-plugin-utils": ^7.8.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   dependencies:
     "@babel/helper-plugin-utils": ^7.8.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 7ed1c1d9b9e5b64ef028ea5e755c0be2d4e5e4e3d6cf7df757b9a8c4cfa4193d268176d0f1f7fbecdda6fe722885c7fda681f480f3741d8a2d26854736f05367
+  checksum: 3a10849d83e47aec50f367a9e56a6b22d662ddce643334b087f9828f4c3dd73bdc5909aaeabe123fed78515767f9ca43498a0e621c438d1cd2802d7fae3c9648
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-class-properties@npm:^7.12.13":
+"@babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3":
   version: 7.12.13
   resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13"
   dependencies:
   version: 7.12.13
   resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13"
   dependencies:
@@ -664,18 +771,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-decorators@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-syntax-decorators@npm:7.14.5"
+"@babel/plugin-syntax-decorators@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-syntax-decorators@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 7e725deeba3848e8e1b57bc8a74c1a852aa253b9ffd293aa0bc043b93e1e7b669414caae3d20c653d2fab907a9388e526f2138e3783b22e49272098566cf9734
+  checksum: 5933fdb1d8d2c0b4b80621ad65dacd4e1ccd836041557c2ddc4cb4c1f46a347fa72977fc519695a801c9cca8b9aaf90d7895ddd52cb4e510fbef5b9f03cb9568
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-dynamic-import@npm:^7.8.0, @babel/plugin-syntax-dynamic-import@npm:^7.8.3":
+"@babel/plugin-syntax-dynamic-import@npm:^7.8.3":
   version: 7.8.3
   resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3"
   dependencies:
   version: 7.8.3
   resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3"
   dependencies:
@@ -697,18 +804,51 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-flow@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-syntax-flow@npm:7.14.5"
+"@babel/plugin-syntax-flow@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-syntax-flow@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: ba6c81325930283bed75c59f92bd7f5873beb006e35fdb092f62498d1f1ecb90f3eaa3d586400ad48dd6d03c63d2bf59a72998e431bab2bd20b3137bd2b10ac0
+  checksum: 87dfe32f3a3ea77941034fb2a39fdfc9ea18a994b8df40c3659a11c8787b2bc5adea029259c4eafc03cd35f11628f6533aa2a06381db7fcbe3b2cc3c2a2bb54f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-json-strings@npm:^7.8.0, @babel/plugin-syntax-json-strings@npm:^7.8.3":
+"@babel/plugin-syntax-import-assertions@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.1"
+  dependencies:
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 2a463928a63b62052e9fb8f8b0018aa11a926e94f32c168260ae012afe864875c6176c6eb361e13f300542c31316dad791b08a5b8ed92436a3095c7a0e4fce65
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-syntax-import-attributes@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-syntax-import-attributes@npm:7.24.1"
+  dependencies:
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 87c8aa4a5ef931313f956871b27f2c051556f627b97ed21e9a5890ca4906b222d89062a956cde459816f5e0dec185ff128d7243d3fdc389504522acb88f0464e
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-syntax-import-meta@npm:^7.10.4, @babel/plugin-syntax-import-meta@npm:^7.8.3":
+  version: 7.10.4
+  resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4"
+  dependencies:
+    "@babel/helper-plugin-utils": ^7.10.4
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 166ac1125d10b9c0c430e4156249a13858c0366d38844883d75d27389621ebe651115cb2ceb6dc011534d5055719fa1727b59f39e1ab3ca97820eef3dcab5b9b
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-syntax-json-strings@npm:^7.8.3":
   version: 7.8.3
   resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3"
   dependencies:
   version: 7.8.3
   resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3"
   dependencies:
@@ -719,18 +859,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-jsx@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-syntax-jsx@npm:7.14.5"
+"@babel/plugin-syntax-jsx@npm:^7.23.3, @babel/plugin-syntax-jsx@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-syntax-jsx@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 3a2ba87534b0f9ee70eba0754d2ae544825c25afd98efb8e42b41399e02de4cc5b1f70fc5ce444fb7a7e5b09972c289eed2f00917be5b88d67407f4cbde8e960
+  checksum: 712f7e7918cb679f106769f57cfab0bc99b311032665c428b98f4c3e2e6d567601d45386a4f246df6a80d741e1f94192b3f008800d66c4f1daae3ad825c243f0
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4":
+"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3":
   version: 7.10.4
   resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4"
   dependencies:
   version: 7.10.4
   resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4"
   dependencies:
@@ -741,7 +881,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.0, @babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3":
+"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3":
   version: 7.8.3
   resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3"
   dependencies:
   version: 7.8.3
   resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3"
   dependencies:
@@ -752,7 +892,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.0, @babel/plugin-syntax-numeric-separator@npm:^7.8.3":
+"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3":
   version: 7.10.4
   resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4"
   dependencies:
   version: 7.10.4
   resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4"
   dependencies:
@@ -763,7 +903,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-object-rest-spread@npm:^7.0.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.0, @babel/plugin-syntax-object-rest-spread@npm:^7.8.3":
+"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3":
   version: 7.8.3
   resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3"
   dependencies:
   version: 7.8.3
   resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3"
   dependencies:
@@ -774,7 +914,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.0, @babel/plugin-syntax-optional-catch-binding@npm:^7.8.3":
+"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3":
   version: 7.8.3
   resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3"
   dependencies:
   version: 7.8.3
   resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3"
   dependencies:
@@ -785,7 +925,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-optional-chaining@npm:^7.8.0, @babel/plugin-syntax-optional-chaining@npm:^7.8.3":
+"@babel/plugin-syntax-optional-chaining@npm:^7.8.3":
   version: 7.8.3
   resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3"
   dependencies:
   version: 7.8.3
   resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3"
   dependencies:
@@ -818,624 +958,731 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-typescript@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-syntax-typescript@npm:7.14.5"
+"@babel/plugin-syntax-typescript@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-syntax-typescript@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 5447d13b31aeeeaa5c2b945e60a598642dedca480f11d3232b0927aeb6a6bb8201a0025f509bc23851da4bf126f69b0522790edbd58f4560f0a4984cabd0d126
+  checksum: bf4bd70788d5456b5f75572e47a2e31435c7c4e43609bd4dffd2cc0c7a6cf90aabcf6cd389e351854de9a64412a07d30effef5373251fe8f6a4c9db0c0163bda
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-arrow-functions@npm:^7.14.5, @babel/plugin-transform-arrow-functions@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-arrow-functions@npm:7.14.5"
+"@babel/plugin-syntax-unicode-sets-regex@npm:^7.18.6":
+  version: 7.18.6
+  resolution: "@babel/plugin-syntax-unicode-sets-regex@npm:7.18.6"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-create-regexp-features-plugin": ^7.18.6
+    "@babel/helper-plugin-utils": ^7.18.6
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  checksum: a651d700fe63ff0ddfd7186f4ebc24447ca734f114433139e3c027bc94a900d013cf1ef2e2db8430425ba542e39ae160c3b05f06b59fd4656273a3df97679e9c
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-arrow-functions@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.1"
+  dependencies:
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 126196ea0107e97f711c0d48d8d1e01a30f5a5e127628f7367658b4c5832182c4e28914294408374690c5bfbb4ad4fe6560068d8bf370cafe8d4fe23599aaa95
+  checksum: 58f9aa9b0de8382f8cfa3f1f1d40b69d98cd2f52340e2391733d0af745fdddda650ba392e509bc056157c880a2f52834a38ab2c5aa5569af8c61bb6ecbf45f34
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-async-to-generator@npm:^7.14.5, @babel/plugin-transform-async-to-generator@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-async-to-generator@npm:7.14.5"
+"@babel/plugin-transform-async-generator-functions@npm:^7.24.3":
+  version: 7.24.3
+  resolution: "@babel/plugin-transform-async-generator-functions@npm:7.24.3"
   dependencies:
   dependencies:
-    "@babel/helper-module-imports": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-remap-async-to-generator": ^7.14.5
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-remap-async-to-generator": ^7.22.20
+    "@babel/plugin-syntax-async-generators": ^7.8.4
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 4c47016c5f65adaa5836054fcc99402f1d295aedd7ebd44e6df128a90977952f2a8abdf3b3d0aa5a9e1186184da538452c4d9a3b1482376759c6962627201da5
+  checksum: 309af02610be65d937664435adb432a32d9b6eb42bb3d3232c377d27fbc57014774d931665a5bfdaff3d1841b72659e0ad7adcef84b709f251cb0b8444f19214
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-block-scoped-functions@npm:^7.14.5, @babel/plugin-transform-block-scoped-functions@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.14.5"
+"@babel/plugin-transform-async-to-generator@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-module-imports": ^7.24.1
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-remap-async-to-generator": ^7.22.20
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 9994d9f107308b21be043de115fe1d06956807d93a3039ddab54333d1fbb39ad50cc5f9eccaedf5317f4699230e923662254974f3a974c4f000e986837bc020a
+  checksum: 429004a6596aa5c9e707b604156f49a146f8d029e31a3152b1649c0b56425264fda5fd38e5db1ddaeb33c3fe45c97dc8078d7abfafe3542a979b49f229801135
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-block-scoping@npm:^7.14.5, @babel/plugin-transform-block-scoping@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-block-scoping@npm:7.14.5"
+"@babel/plugin-transform-block-scoped-functions@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: d317d636d0475317302e9c8b01cf9214fac3ff9353b23d0d16285f196f5c7b95b7864a8e8eaf51a3e1b650649203855f80a58b7a2caef4b0ee9793e7349a0ec5
+  checksum: d8e18bd57b156da1cd4d3c1780ab9ea03afed56c6824ca8e6e74f67959d7989a0e953ec370fe9b417759314f2eef30c8c437395ce63ada2e26c2f469e4704f82
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-classes@npm:^7.14.5, @babel/plugin-transform-classes@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-classes@npm:7.14.5"
+"@babel/plugin-transform-block-scoping@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/plugin-transform-block-scoping@npm:7.24.4"
   dependencies:
   dependencies:
-    "@babel/helper-annotate-as-pure": ^7.14.5
-    "@babel/helper-function-name": ^7.14.5
-    "@babel/helper-optimise-call-expression": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-replace-supers": ^7.14.5
-    "@babel/helper-split-export-declaration": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 5229ffe1c55744b96f791521e2876b01ed05c81df67488a7453ce66c2faceb9d1d653089ce6f0abf512752e15e9acac0e75a797a860f24e05b4d36497c7c3183
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-class-properties@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-class-properties@npm:7.24.1"
+  dependencies:
+    "@babel/helper-create-class-features-plugin": ^7.24.1
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 95779e9eef0c0638b9631c297d48aee53ffdbb2b1b5221bf40d7eccd566a8e34f859ff3571f8f20b9159b67f1bff7d7dc81da191c15d69fbae5a645197eae7e0
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-class-static-block@npm:^7.24.4":
+  version: 7.24.4
+  resolution: "@babel/plugin-transform-class-static-block@npm:7.24.4"
+  dependencies:
+    "@babel/helper-create-class-features-plugin": ^7.24.4
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-class-static-block": ^7.14.5
+  peerDependencies:
+    "@babel/core": ^7.12.0
+  checksum: 3b1db3308b57ba21d47772a9f183804234c23fd64c9ca40915d2d65c5dc7a48b49a6de16b8b90b7a354eacbb51232a862f0fca3dbd23e27d34641f511decddab
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-classes@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-classes@npm:7.24.1"
+  dependencies:
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    "@babel/helper-compilation-targets": ^7.23.6
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-function-name": ^7.23.0
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-replace-supers": ^7.24.1
+    "@babel/helper-split-export-declaration": ^7.22.6
     globals: ^11.1.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
     globals: ^11.1.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 42fc333a0d8a6a90b5c75e90d2ec21494f711ab7c58f2d074d95726cdd38f137e74653602a82d2d1a3e9bc504b5eff62418d70048514b672c9bd108bfb866e25
+  checksum: e5337e707d731c9f4dcc107d09c9a99b90786bc0da6a250165919587ed818818f6cae2bbcceea880abef975c0411715c0c7f3f361ecd1526bf2eaca5ad26bb00
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-computed-properties@npm:^7.14.5, @babel/plugin-transform-computed-properties@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-computed-properties@npm:7.14.5"
+"@babel/plugin-transform-computed-properties@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-computed-properties@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/template": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 87bd4c46255359ab8d53d0e9b5aa5e1ef218c1447874bd8c2eff759d3a2b5fe6b3ec55046babe0087f7e3890f6167524c729737e912080ea1c9758a559765130
+  checksum: f2832bcf100a70f348facbb395873318ef5b9ee4b0fb4104a420d9daaeb6003cc2ecc12fd8083dd2e4a7c2da873272ad73ff94de4497125a0cf473294ef9664e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-destructuring@npm:^7.14.7, @babel/plugin-transform-destructuring@npm:^7.8.3":
-  version: 7.14.7
-  resolution: "@babel/plugin-transform-destructuring@npm:7.14.7"
+"@babel/plugin-transform-destructuring@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-destructuring@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 0b0cf8ed9fb92c53e3888c17402c4f1e8f329f05a759829b559df883b19b442d3950b7f319df419d0cff122ea76fc8b3b55779fdbb9e394e5f058419a8d5ba14
+  checksum: 994fd3c513e40b8f1bdfdd7104ebdcef7c6a11a4e380086074496f586db3ac04cba0ae70babb820df6363b6700747b0556f6860783e046ace7c741a22f49ec5b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-dotall-regex@npm:^7.14.5, @babel/plugin-transform-dotall-regex@npm:^7.4.4, @babel/plugin-transform-dotall-regex@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-dotall-regex@npm:7.14.5"
+"@babel/plugin-transform-dotall-regex@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-create-regexp-features-plugin": ^7.22.15
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 4da3dac9580823c1fe8aaedf6109d3a26d17ad7ef7d1b278ddbcd7c148e02c465cf49250794529a34bac0bda6b53db558ae08d185a96b76efaaa17a5da3911df
+  checksum: 7f623d25b6f213b94ebc1754e9e31c1077c8e288626d8b7bfa76a97b067ce80ddcd0ede402a546706c65002c0ccf45cd5ec621511c2668eed31ebcabe8391d35
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-duplicate-keys@npm:^7.14.5, @babel/plugin-transform-duplicate-keys@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-duplicate-keys@npm:7.14.5"
+"@babel/plugin-transform-duplicate-keys@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: c6c951d2f7ed528a8103d08293d4aaf95efa38c697e7b2b27b7e6c9780280484373e2f7ef8d77daf17dffdc86748fbf75e776e0542b1c7b17e29308bc31ebd8c
+  checksum: a3b07c07cee441e185858a9bb9739bb72643173c18bf5f9f949dd2d4784ca124e56b01d0a270790fb1ff0cf75d436075db0a2b643fb4285ff9a21df9e8dc6284
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-exponentiation-operator@npm:^7.14.5, @babel/plugin-transform-exponentiation-operator@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.14.5"
+"@babel/plugin-transform-dynamic-import@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-dynamic-import": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 7588a582d0bc5c80fda7f1c631354a35a9a7d284dd80ccaf2bbfd086a39a9d6461718dc7dd45a3ca59228593270a7c6a907a9cbe7ddc349d80c7342af0263c5c
+  checksum: 59fc561ee40b1a69f969c12c6c5fac206226d6642213985a569dd0f99f8e41c0f4eaedebd36936c255444a8335079842274c42a975a433beadb436d4c5abb79b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-flow-strip-types@npm:7.9.0":
-  version: 7.9.0
-  resolution: "@babel/plugin-transform-flow-strip-types@npm:7.9.0"
+"@babel/plugin-transform-exponentiation-operator@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-syntax-flow": ^7.8.3
+    "@babel/helper-builder-binary-assignment-operator-visitor": ^7.22.15
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 4bc74b721db01e91c6d591f927b53fa509d69d5c49911f31d9202df984cf2eda8397346826647cb660f72676dd3e5a2498187f587e7e181c0ca66b28b1252591
+  checksum: f90841fe1a1e9f680b4209121d3e2992f923e85efcd322b26e5901c180ef44ff727fb89790803a23fac49af34c1ce2e480018027c22b4573b615512ac5b6fc50
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-for-of@npm:^7.14.5, @babel/plugin-transform-for-of@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-for-of@npm:7.14.5"
+"@babel/plugin-transform-export-namespace-from@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-export-namespace-from": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: aeb76eb11d10b2390996001e2fd529bbaf3695edd306d24e4eba87b8137c10a6afda3896017f88fcf40fd2334cc424c0a111fad34e10c747e81e577e5957e328
+  checksum: bc710ac231919df9555331885748385c11c5e695d7271824fe56fba51dd637d48d3e5cd52e1c69f2b1a384fbbb41552572bc1ca3a2285ee29571f002e9bb2421
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-function-name@npm:^7.14.5, @babel/plugin-transform-function-name@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-function-name@npm:7.14.5"
+"@babel/plugin-transform-flow-strip-types@npm:^7.16.0":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-flow-strip-types@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-function-name": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-flow": ^7.24.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 3db2fa1bcd21b76a91ce78db8ebca047fdadbf198f816e2621e531a751a0d40976cf2a25262dee9352fd0c53bff5b25fddefadebdbb4ba3da6d89b849ab075b6
+  checksum: 83faac90c934e15a8fe813d90cbfdf8aa2cb2cc9108f55e4a1ecda1c3097735af6a0b6623057f059153b572bc1dd088aeb2ff24217e9de82ad2390ab1210d01b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-literals@npm:^7.14.5, @babel/plugin-transform-literals@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-literals@npm:7.14.5"
+"@babel/plugin-transform-for-of@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-for-of@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 2341cfaaf8ac7199c578407ea4de41205d3d74c5a48899aa96c41b08c09d18c46d9018fdc6a2f69f0bccc2662223afc47b60130ae4ff36a79351fface71a61f3
+  checksum: 990adde96ea1766ed6008c006c7040127bef59066533bb2977b246ea4a596fe450a528d1881a0db5f894deaf1b81654dfb494b19ad405b369be942738aa9c364
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-member-expression-literals@npm:^7.14.5, @babel/plugin-transform-member-expression-literals@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-member-expression-literals@npm:7.14.5"
+"@babel/plugin-transform-function-name@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-function-name@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-compilation-targets": ^7.23.6
+    "@babel/helper-function-name": ^7.23.0
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: a94ff910e8d0e28effd58c64f2d15c9772ea4c209644f116fd81dc5c93ce232304f42ef14d5ec2baf095c824786698fcf6c1d4c91952dc3762350f4ec0eb1f17
+  checksum: 31eb3c75297dda7265f78eba627c446f2324e30ec0124a645ccc3e9f341254aaa40d6787bd62b2280d77c0a5c9fbfce1da2c200ef7c7f8e0a1b16a8eb3644c6f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-amd@npm:^7.14.5, @babel/plugin-transform-modules-amd@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-modules-amd@npm:7.14.5"
+"@babel/plugin-transform-json-strings@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-json-strings@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-module-transforms": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    babel-plugin-dynamic-import-node: ^2.3.3
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-json-strings": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 963d9ebb11b282d5c5f462e3e1ad6991e60fb4d190b5a7aa0d9937e0fa83d89cf5f94268f0b0b343576f2cee0cf545bcaf40da40eb8b9dca5c79840fd86a65ed
+  checksum: f42302d42fc81ac00d14e9e5d80405eb80477d7f9039d7208e712d6bcd486a4e3b32fdfa07b5f027d6c773723d8168193ee880f93b0e430c828e45f104fb82a4
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-commonjs@npm:^7.14.5, @babel/plugin-transform-modules-commonjs@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.14.5"
+"@babel/plugin-transform-literals@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-literals@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-module-transforms": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-simple-access": ^7.14.5
-    babel-plugin-dynamic-import-node: ^2.3.3
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 5cc41ee904e421c32f692ce10985190bc8f995df63ee1fd899ea80ce50b4b8408c7f2fddf16e01345244fc5702c8b9c0772afdd934e325c4e468840daa9bee04
+  checksum: 2df94e9478571852483aca7588419e574d76bde97583e78551c286f498e01321e7dbb1d0ef67bee16e8f950688f79688809cfde370c5c4b84c14d841a3ef217a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-systemjs@npm:^7.14.5, @babel/plugin-transform-modules-systemjs@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-modules-systemjs@npm:7.14.5"
+"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-hoist-variables": ^7.14.5
-    "@babel/helper-module-transforms": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-validator-identifier": ^7.14.5
-    babel-plugin-dynamic-import-node: ^2.3.3
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 3ca0bb1c0c22a3d705476186afa9fc86398ae4662afc259ff29c1942e3c8770f4bdadaf67418a21816964d4e1eaf07412eeabccccfaa9d45eac735f971ad148b
+  checksum: 895f2290adf457cbf327428bdb4fb90882a38a22f729bcf0629e8ad66b9b616d2721fbef488ac00411b647489d1dda1d20171bb3772d0796bb7ef5ecf057808a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-umd@npm:^7.14.5, @babel/plugin-transform-modules-umd@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-modules-umd@npm:7.14.5"
+"@babel/plugin-transform-member-expression-literals@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-module-transforms": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 455ff383bed47e104d4b2b32f11bc5a44a25c797fad26b5eab9b8a81856f9945350b45ad28b9b20b0bbf324832c7a826c9c3d6f865e85c26a1771663132e4145
+  checksum: 4ea641cc14a615f9084e45ad2319f95e2fee01c77ec9789685e7e11a6c286238a426a98f9c1ed91568a047d8ac834393e06e8c82d1ff01764b7aa61bee8e9023
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.14.7, @babel/plugin-transform-named-capturing-groups-regex@npm:^7.8.3":
-  version: 7.14.7
-  resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.14.7"
+"@babel/plugin-transform-modules-amd@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-modules-amd@npm:7.24.1"
+  dependencies:
+    "@babel/helper-module-transforms": ^7.23.3
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 3d777c262f257e93f0405b13e178f9c4a0f31855b409f0191a76bb562a28c541326a027bfe6467fcb74752f3488c0333b5ff2de64feec1b3c4c6ace1747afa03
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-modules-commonjs@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.1"
+  dependencies:
+    "@babel/helper-module-transforms": ^7.23.3
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-simple-access": ^7.22.5
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 11402b34c49f76aa921b43c2d76f3f129a32544a1dc4f0d1e48b310f9036ab75269a6d8684ed0198b7a0b07bd7898b12f0cacceb26fbb167999fd2a819aa0802
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-modules-systemjs@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-modules-systemjs@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": ^7.14.5
+    "@babel/helper-hoist-variables": ^7.22.5
+    "@babel/helper-module-transforms": ^7.23.3
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-validator-identifier": ^7.22.20
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 903766f6808f04278e887e4adec9b1efa741726279652dad255eaad0f5701df8f8ff0af25eb8541a00eb3c9eae2dccf337b085cfa011426ca33ed1f95d70bf75
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-modules-umd@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-modules-umd@npm:7.24.1"
+  dependencies:
+    "@babel/helper-module-transforms": ^7.23.3
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 4922f5056d34de6fd59a1ab1c85bc3472afa706c776aceeb886289c9ac9117e6eb8e22d06c537eb5bc0ede6c30f6bd85210bdcc150dc0ae2d2373f8252df9364
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.22.5":
+  version: 7.22.5
+  resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.22.5"
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin": ^7.22.5
+    "@babel/helper-plugin-utils": ^7.22.5
   peerDependencies:
     "@babel/core": ^7.0.0
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 3c68bc77cce387750ecd32d33e9ad0f0968245fbe03b36ec8dddc52bee3ee84757205db3b3b4fc605e055f08769312ef4dbf4a0c8adb8f02eb04b142ffcdf265
+  checksum: 3ee564ddee620c035b928fdc942c5d17e9c4b98329b76f9cefac65c111135d925eb94ed324064cd7556d4f5123beec79abea1d4b97d1c8a2a5c748887a2eb623
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-new-target@npm:^7.14.5, @babel/plugin-transform-new-target@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-new-target@npm:7.14.5"
+"@babel/plugin-transform-new-target@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-new-target@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 5b806c86926cd0b03fa2f22cf21a6d6a86e5831b80e8a1e898877acd3a03fd07078e45da33b671200ec98a5c7ac9be2f3592cd88933e262feffba248ca7ca4e7
+  checksum: f56159ba56e8824840b8073f65073434e4bc4ef20e366bc03aa6cae9a4389365574fa72390e48aed76049edbc6eba1181eb810e58fae22c25946c62f9da13db4
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-object-super@npm:^7.14.5, @babel/plugin-transform-object-super@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-object-super@npm:7.14.5"
+"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-replace-supers": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 88477a8b27e76042ffbff1345088422f5b3135346d69f264e71d90b3749a3d73d5a579c97a33cd11c61c5d499a655911c7cd97dbe68edb36e090dfd5f154d777
+  checksum: 74025e191ceb7cefc619c15d33753aab81300a03d81b96ae249d9b599bc65878f962d608f452462d3aad5d6e334b7ab2b09a6bdcfe8d101fe77ac7aacca4261e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-parameters@npm:^7.14.5, @babel/plugin-transform-parameters@npm:^7.8.7":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-parameters@npm:7.14.5"
+"@babel/plugin-transform-numeric-separator@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-numeric-separator": ^7.10.4
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 932bc616be7b5542ba2371c85cfcc579a8556b9e5a5ea5535b7f0ec5b68284ed2a3724ae181f1a22719b5ea6539c82f5fcee37d9f45f08ed72eb9e43a0940b56
+  checksum: 3247bd7d409574fc06c59e0eb573ae7470d6d61ecf780df40b550102bb4406747d8f39dcbec57eb59406df6c565a86edd3b429e396ad02e4ce201ad92050832e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-property-literals@npm:^7.14.5, @babel/plugin-transform-property-literals@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-property-literals@npm:7.14.5"
+"@babel/plugin-transform-object-rest-spread@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-compilation-targets": ^7.23.6
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-object-rest-spread": ^7.8.3
+    "@babel/plugin-transform-parameters": ^7.24.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 426e7b13a048220314e35bd4e6732640293c616173ef05ceca3a2bfadd043199e35ec693f1604f77178c3a88bea241b6d7ce92d8fc837faeb37117ad7866350f
+  checksum: d5d28b1f33c279a38299d34011421a4915e24b3846aa23a1aba947f1366ce673ddf8df09dd915e0f2c90c5327f798bf126dca013f8adff1fc8f09e18878b675a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-constant-elements@npm:^7.0.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-react-constant-elements@npm:7.14.5"
+"@babel/plugin-transform-object-super@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-object-super@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-replace-supers": ^7.24.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 7e4168535cd3ae1bae5acf8d7cc77a2bd885f8abed46672160631e23ded0c7e0be5152cefb1f87b123c4e3c38a542ca0ce06b3b0d8e7b7694f43687b63c0a9fb
+  checksum: d34d437456a54e2a5dcb26e9cf09ed4c55528f2a327c5edca92c93e9483c37176e228d00d6e0cf767f3d6fdbef45ae3a5d034a7c59337a009e20ae541c8220fa
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-display-name@npm:7.8.3":
-  version: 7.8.3
-  resolution: "@babel/plugin-transform-react-display-name@npm:7.8.3"
+"@babel/plugin-transform-optional-catch-binding@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.8.3
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-optional-catch-binding": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 2712345cdf6aa62ca4da3e749a247c99210658cd3c22ad7579052fb451ca6af482d4dee77ae295c3d12efc603aa0ee4a00399347d8e8ad962aa8715037a17e8e
+  checksum: ff7c02449d32a6de41e003abb38537b4a1ad90b1eaa4c0b578cb1b55548201a677588a8c47f3e161c72738400ae811a6673ea7b8a734344755016ca0ac445dac
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-display-name@npm:^7.14.5, @babel/plugin-transform-react-display-name@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-react-display-name@npm:7.14.5"
+"@babel/plugin-transform-optional-chaining@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5
+    "@babel/plugin-syntax-optional-chaining": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: d7ca35d5e8d7d91ac82b17e1bd68dd4a7dcfae54da95b28d072907799503e2ec234f34dd869c9fee299a29e73e7b5ce3d4c748cf2a29c25d39f9523be130dba3
+  checksum: 0eb5f4abdeb1a101c0f67ef25eba4cce0978a74d8722f6222cdb179a28e60d21ab545eda231855f50169cd63d604ec8268cff44ae9370fd3a499a507c56c2bbd
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx-development@npm:^7.14.5, @babel/plugin-transform-react-jsx-development@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-react-jsx-development@npm:7.14.5"
+"@babel/plugin-transform-parameters@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-parameters@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/plugin-transform-react-jsx": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: b49d6e703aeb4fbaacbb8449418dc3c599bcb3ce608cb900ed21a288c3bce42a33209524693b1978766b645aa2b751c15aa9da5337cc6ac2a79fd9b7c9ae9246
+  checksum: d183008e67b1a13b86c92fb64327a75cd8e13c13eb80d0b6952e15806f1b0c4c456d18360e451c6af73485b2c8f543608b0a29e5126c64eb625a31e970b65f80
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx-self@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-react-jsx-self@npm:7.14.5"
+"@babel/plugin-transform-private-methods@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-private-methods@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-create-class-features-plugin": ^7.24.1
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: b1b19d3aa0d383fd06e085bcb5462a310dd844a073cc608115a3582ed88ca23d1511dc75cfa81369c2a254e14428b0e6482e6c48bdef346764d801882de8012f
+  checksum: 7208c30bb3f3fbc73fb3a88bdcb78cd5cddaf6d523eb9d67c0c04e78f6fc6319ece89f4a5abc41777ceab16df55b3a13a4120e0efc9275ca6d2d89beaba80aa0
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx-source@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-react-jsx-source@npm:7.14.5"
+"@babel/plugin-transform-private-property-in-object@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    "@babel/helper-create-class-features-plugin": ^7.24.1
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-private-property-in-object": ^7.14.5
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: e7e7336bbd07d6c1a281bac1b242e8cb8172f3b1e1d9d214160ab220142fbefc5d79786d57bf197b18f4c694edfc7614dddae2f990adb4b7484146635b58dfe6
+  checksum: 47c123ca9975f7f6b20e6fe8fe89f621cd04b622539faf5ec037e2be7c3d53ce2506f7c785b1930dcdea11994eff79094a02715795218c7d6a0bdc11f2fb3ac2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx@npm:^7.14.5, @babel/plugin-transform-react-jsx@npm:^7.9.1":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-react-jsx@npm:7.14.5"
+"@babel/plugin-transform-property-literals@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-property-literals@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-annotate-as-pure": ^7.14.5
-    "@babel/helper-module-imports": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-jsx": ^7.14.5
-    "@babel/types": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 4be6ba0a0303691ce7e16363da1ae446a5cd6eb63ba5729cd7af21b0e7927c07bb8595482836cbda0f41b39fa979c37f4504ef7c23729085f84fac1659615542
+  checksum: a73646d7ecd95b3931a3ead82c7d5efeb46e68ba362de63eb437d33531f294ec18bd31b6d24238cd3b6a3b919a6310c4a0ba4a2629927721d4d10b0518eb7715
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-pure-annotations@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.14.5"
+"@babel/plugin-transform-react-constant-elements@npm:^7.9.0":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-react-constant-elements@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-annotate-as-pure": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 3b62cc6af2c838eabc28c07473eab1392b41a5db2f0f326b1ba3ec52b95529e1c46098d6a259c7debb6a17489445828b89f7737a6fb85345ea5d27e4819a31fe
+  checksum: 37fd10113b786a2462cf15366aa3a11a2a5bdba9bf8881b2544941f5ad6175ebc31116be5a53549c9fce56a08ded6e0b57adb45d6e42efb55d3bc0ff7afdd433
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-regenerator@npm:^7.14.5, @babel/plugin-transform-regenerator@npm:^7.8.7":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-regenerator@npm:7.14.5"
+"@babel/plugin-transform-react-display-name@npm:^7.16.0, @babel/plugin-transform-react-display-name@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-react-display-name@npm:7.24.1"
   dependencies:
   dependencies:
-    regenerator-transform: ^0.14.2
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: f606bc04da7d0cfd651914cb144e85a0ea6fe20ee453ed21d002747cc47b09c853bc97166c32dc47e959581b772d9883f7d96d1c8e795c81ed21dbbb300e3aa7
+  checksum: d87ac36073f923a25de0ed3cffac067ec5abc4cde63f7f4366881388fbea6dcbced0e4fefd3b7e99edfe58a4ce32ea4d4c523a577d2b9f0515b872ed02b3d8c3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-reserved-words@npm:^7.14.5, @babel/plugin-transform-reserved-words@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-reserved-words@npm:7.14.5"
+"@babel/plugin-transform-react-jsx-development@npm:^7.22.5":
+  version: 7.22.5
+  resolution: "@babel/plugin-transform-react-jsx-development@npm:7.22.5"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/plugin-transform-react-jsx": ^7.22.5
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 8a40d7b48e1b4a549272d603e7b28ead70213e12353d65edd07156b7169d7933cee8b79987b54f374f3c41b835d941aca4b13b8aa23a922c94113af2131ca686
+  checksum: 36bc3ff0b96bb0ef4723070a50cfdf2e72cfd903a59eba448f9fe92fea47574d6f22efd99364413719e1f3fb3c51b6c9b2990b87af088f8486a84b2a5f9e4560
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-runtime@npm:7.9.0":
-  version: 7.9.0
-  resolution: "@babel/plugin-transform-runtime@npm:7.9.0"
+"@babel/plugin-transform-react-jsx@npm:^7.22.5, @babel/plugin-transform-react-jsx@npm:^7.23.4":
+  version: 7.23.4
+  resolution: "@babel/plugin-transform-react-jsx@npm:7.23.4"
   dependencies:
   dependencies:
-    "@babel/helper-module-imports": ^7.8.3
-    "@babel/helper-plugin-utils": ^7.8.3
-    resolve: ^1.8.1
-    semver: ^5.5.1
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    "@babel/helper-module-imports": ^7.22.15
+    "@babel/helper-plugin-utils": ^7.22.5
+    "@babel/plugin-syntax-jsx": ^7.23.3
+    "@babel/types": ^7.23.4
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 48d6a6ada6bb0a08179ab23e7b63b7e5430abb1a6b21f3979524c5574cd0de033abe196c4ec0ff4470a13b87ee367acf4b21ed0ae9294659ee530b89278786fd
+  checksum: d8b8c52e8e22e833bf77c8d1a53b0a57d1fd52ba9596a319d572de79446a8ed9d95521035bc1175c1589d1a6a34600d2e678fa81d81bac8fac121137097f1f0a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-shorthand-properties@npm:^7.14.5, @babel/plugin-transform-shorthand-properties@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-shorthand-properties@npm:7.14.5"
+"@babel/plugin-transform-react-pure-annotations@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 60cdd17e347a6a0973c8ea5c08ae4b3f8e59ce0e188453c4bda045d2a5c34495af8e0e9393631aa9f3fd51282455b9c5d6ba07e262576171dbe2b4094bdaf8ad
+  checksum: 06a6bfe80f1f36408d07dd80c48cf9f61177c8e5d814e80ddbe88cfad81a8b86b3110e1fe9d1ac943db77e74497daa7f874b5490c788707106ad26ecfbe44813
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-spread@npm:^7.14.6, @babel/plugin-transform-spread@npm:^7.8.3":
-  version: 7.14.6
-  resolution: "@babel/plugin-transform-spread@npm:7.14.6"
+"@babel/plugin-transform-regenerator@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-regenerator@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-skip-transparent-expression-wrappers": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    regenerator-transform: ^0.15.2
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 20c11de962dd7ddab110d6c4ab9f3c0bea97393ce09cbe4e46be53182c3df0577eaf0e31aaa2d76344ae21ed3a3b7e779fe814b845d188e11a6031c619648b89
+  checksum: a04319388a0a7931c3f8e15715d01444c32519692178b70deccc86d53304e74c0f589a4268f6c68578d86f75e934dd1fe6e6ed9071f54ee8379f356f88ef6e42
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-sticky-regex@npm:^7.14.5, @babel/plugin-transform-sticky-regex@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-sticky-regex@npm:7.14.5"
+"@babel/plugin-transform-reserved-words@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-reserved-words@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 6d77e0641c4c72203d592d54fdb11770de22a34d659d3335e4c537e95b930d03142b11f1d41d103da3de063c628a0f34bdd4c6534b591bc59d9ce67fafb836dc
+  checksum: 132c6040c65aabae2d98a39289efb5c51a8632546dc50d2ad032c8660aec307fbed74ef499856ea4f881fc8505905f49b48e0270585da2ea3d50b75e962afd89
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-template-literals@npm:^7.14.5, @babel/plugin-transform-template-literals@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-template-literals@npm:7.14.5"
+"@babel/plugin-transform-runtime@npm:^7.16.4":
+  version: 7.24.3
+  resolution: "@babel/plugin-transform-runtime@npm:7.24.3"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-module-imports": ^7.24.3
+    "@babel/helper-plugin-utils": ^7.24.0
+    babel-plugin-polyfill-corejs2: ^0.4.10
+    babel-plugin-polyfill-corejs3: ^0.10.1
+    babel-plugin-polyfill-regenerator: ^0.6.1
+    semver: ^6.3.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 56d273470c16e83bac1bfab5057a64f23191b51460a009b522b3b29806d7a9f64cbd94323836ceb997c4f331b85564f952eb5566c7bd140d0b278f0191a31985
+  checksum: 719112524e6fe3e665385ad4425530dadb2ddee839023381ed9d77edf5ce2748f32cc0e38dacda1990c56a7ae0af4de6cdca2413ffaf307e9f75f8d2200d09a2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-typeof-symbol@npm:^7.14.5, @babel/plugin-transform-typeof-symbol@npm:^7.8.4":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-typeof-symbol@npm:7.14.5"
+"@babel/plugin-transform-shorthand-properties@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 1e71ec00ea8b64522b8677c030f334cc5b3833a5b7269a152a2ba7a6b36f0e0a4333a61072e69113e4062e71554d4751ef2e3ddd5e81994978123323f266981c
+  checksum: 006a2032d1c57dca76579ce6598c679c2f20525afef0a36e9d42affe3c8cf33c1427581ad696b519cc75dfee46c5e8ecdf0c6a29ffb14250caa3e16dd68cb424
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-typescript@npm:^7.9.0":
-  version: 7.14.6
-  resolution: "@babel/plugin-transform-typescript@npm:7.14.6"
+"@babel/plugin-transform-spread@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-spread@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-create-class-features-plugin": ^7.14.6
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/plugin-syntax-typescript": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-skip-transparent-expression-wrappers": ^7.22.5
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: cb3117cfc9c8ebf9612b137eb660448e79a876a189fcad6b79641faa7200073bbfd08bf0e63c7ddb3a35b3d31457d6e90cf63565e64446a73866290dc97353fa
+  checksum: 622ef507e2b5120a9010b25d3df5186c06102ecad8751724a38ec924df8d3527688198fa490c47064eabba14ef2f961b3069855bd22a8c0a1e51a23eed348d02
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-escapes@npm:^7.14.5":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-unicode-escapes@npm:7.14.5"
+"@babel/plugin-transform-sticky-regex@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 2a6979c5b886d9c7d9d3887374d75384542fe05a71eb7738b2cde659386089a930d37d1a34ffb4b87def98fbed3526d78b7cd5dd9bffde4d406b368faba81b7d
+  checksum: e326e96a9eeb6bb01dbc4d3362f989411490671b97f62edf378b8fb102c463a018b777f28da65344d41b22aa6efcdfa01ed43d2b11fdcf202046d3174be137c5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-regex@npm:^7.14.5, @babel/plugin-transform-unicode-regex@npm:^7.8.3":
-  version: 7.14.5
-  resolution: "@babel/plugin-transform-unicode-regex@npm:7.14.5"
+"@babel/plugin-transform-template-literals@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-template-literals@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 1b7a4c0dc6b07390f991e7cac8409f7a1ae74495d94b9e1fb5a716d5362a349a35717cfad883074e3f80e16bb630bbd1986a3436f739f6b01c30a96ef3f9ea9a
+  checksum: 4c9009c72321caf20e3b6328bbe9d7057006c5ae57b794cf247a37ca34d87dfec5e27284169a16df5a6235a083bf0f3ab9e1bfcb005d1c8b75b04aed75652621
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/preset-env@npm:7.9.0":
-  version: 7.9.0
-  resolution: "@babel/preset-env@npm:7.9.0"
+"@babel/plugin-transform-typeof-symbol@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/compat-data": ^7.9.0
-    "@babel/helper-compilation-targets": ^7.8.7
-    "@babel/helper-module-imports": ^7.8.3
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-proposal-async-generator-functions": ^7.8.3
-    "@babel/plugin-proposal-dynamic-import": ^7.8.3
-    "@babel/plugin-proposal-json-strings": ^7.8.3
-    "@babel/plugin-proposal-nullish-coalescing-operator": ^7.8.3
-    "@babel/plugin-proposal-numeric-separator": ^7.8.3
-    "@babel/plugin-proposal-object-rest-spread": ^7.9.0
-    "@babel/plugin-proposal-optional-catch-binding": ^7.8.3
-    "@babel/plugin-proposal-optional-chaining": ^7.9.0
-    "@babel/plugin-proposal-unicode-property-regex": ^7.8.3
-    "@babel/plugin-syntax-async-generators": ^7.8.0
-    "@babel/plugin-syntax-dynamic-import": ^7.8.0
-    "@babel/plugin-syntax-json-strings": ^7.8.0
-    "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.0
-    "@babel/plugin-syntax-numeric-separator": ^7.8.0
-    "@babel/plugin-syntax-object-rest-spread": ^7.8.0
-    "@babel/plugin-syntax-optional-catch-binding": ^7.8.0
-    "@babel/plugin-syntax-optional-chaining": ^7.8.0
-    "@babel/plugin-syntax-top-level-await": ^7.8.3
-    "@babel/plugin-transform-arrow-functions": ^7.8.3
-    "@babel/plugin-transform-async-to-generator": ^7.8.3
-    "@babel/plugin-transform-block-scoped-functions": ^7.8.3
-    "@babel/plugin-transform-block-scoping": ^7.8.3
-    "@babel/plugin-transform-classes": ^7.9.0
-    "@babel/plugin-transform-computed-properties": ^7.8.3
-    "@babel/plugin-transform-destructuring": ^7.8.3
-    "@babel/plugin-transform-dotall-regex": ^7.8.3
-    "@babel/plugin-transform-duplicate-keys": ^7.8.3
-    "@babel/plugin-transform-exponentiation-operator": ^7.8.3
-    "@babel/plugin-transform-for-of": ^7.9.0
-    "@babel/plugin-transform-function-name": ^7.8.3
-    "@babel/plugin-transform-literals": ^7.8.3
-    "@babel/plugin-transform-member-expression-literals": ^7.8.3
-    "@babel/plugin-transform-modules-amd": ^7.9.0
-    "@babel/plugin-transform-modules-commonjs": ^7.9.0
-    "@babel/plugin-transform-modules-systemjs": ^7.9.0
-    "@babel/plugin-transform-modules-umd": ^7.9.0
-    "@babel/plugin-transform-named-capturing-groups-regex": ^7.8.3
-    "@babel/plugin-transform-new-target": ^7.8.3
-    "@babel/plugin-transform-object-super": ^7.8.3
-    "@babel/plugin-transform-parameters": ^7.8.7
-    "@babel/plugin-transform-property-literals": ^7.8.3
-    "@babel/plugin-transform-regenerator": ^7.8.7
-    "@babel/plugin-transform-reserved-words": ^7.8.3
-    "@babel/plugin-transform-shorthand-properties": ^7.8.3
-    "@babel/plugin-transform-spread": ^7.8.3
-    "@babel/plugin-transform-sticky-regex": ^7.8.3
-    "@babel/plugin-transform-template-literals": ^7.8.3
-    "@babel/plugin-transform-typeof-symbol": ^7.8.4
-    "@babel/plugin-transform-unicode-regex": ^7.8.3
-    "@babel/preset-modules": ^0.1.3
-    "@babel/types": ^7.9.0
-    browserslist: ^4.9.1
-    core-js-compat: ^3.6.2
-    invariant: ^2.2.2
-    levenary: ^1.1.1
-    semver: ^5.5.0
+    "@babel/helper-plugin-utils": ^7.24.0
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 5578f6163488ed945c7c318402388d742aef91021ed4c080cba87e22975f0e32f0caadb309fe8687f7a44b2ad7153e4b00f69b7b665f2a2ffd6d43732317f877
+  checksum: 90251c02986aebe50937522a6e404cb83db1b1feda17c0244e97d6429ded1634340c8411536487d14c54495607e1b7c9dc4db4aed969d519f1ff1e363f9c2229
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/preset-env@npm:^7.4.5":
-  version: 7.14.7
-  resolution: "@babel/preset-env@npm:7.14.7"
+"@babel/plugin-transform-typescript@npm:^7.24.1":
+  version: 7.24.4
+  resolution: "@babel/plugin-transform-typescript@npm:7.24.4"
   dependencies:
   dependencies:
-    "@babel/compat-data": ^7.14.7
-    "@babel/helper-compilation-targets": ^7.14.5
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-validator-option": ^7.14.5
-    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.14.5
-    "@babel/plugin-proposal-async-generator-functions": ^7.14.7
-    "@babel/plugin-proposal-class-properties": ^7.14.5
-    "@babel/plugin-proposal-class-static-block": ^7.14.5
-    "@babel/plugin-proposal-dynamic-import": ^7.14.5
-    "@babel/plugin-proposal-export-namespace-from": ^7.14.5
-    "@babel/plugin-proposal-json-strings": ^7.14.5
-    "@babel/plugin-proposal-logical-assignment-operators": ^7.14.5
-    "@babel/plugin-proposal-nullish-coalescing-operator": ^7.14.5
-    "@babel/plugin-proposal-numeric-separator": ^7.14.5
-    "@babel/plugin-proposal-object-rest-spread": ^7.14.7
-    "@babel/plugin-proposal-optional-catch-binding": ^7.14.5
-    "@babel/plugin-proposal-optional-chaining": ^7.14.5
-    "@babel/plugin-proposal-private-methods": ^7.14.5
-    "@babel/plugin-proposal-private-property-in-object": ^7.14.5
-    "@babel/plugin-proposal-unicode-property-regex": ^7.14.5
+    "@babel/helper-annotate-as-pure": ^7.22.5
+    "@babel/helper-create-class-features-plugin": ^7.24.4
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/plugin-syntax-typescript": ^7.24.1
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 57a9a776b1910c706d28972e4b056ced3af8fc59c29b2a6205c2bb2a408141ddb59a8f2f6041f8467a7b260942818767f4ecabb9f63adf7fddf2afa25e774dfc
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-unicode-escapes@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.1"
+  dependencies:
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: d4d7cfea91af7be2768fb6bed902e00d6e3190bda738b5149c3a788d570e6cf48b974ec9548442850308ecd8fc9a67681f4ea8403129e7867bcb85adaf6ec238
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-unicode-property-regex@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.1"
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin": ^7.22.15
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 276099b4483e707f80b054e2d29bc519158bfe52461ef5ff76f70727d592df17e30b1597ef4d8a0f04d810f6cb5a8dd887bdc1d0540af3744751710ef280090f
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-unicode-regex@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.1"
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin": ^7.22.15
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0-0
+  checksum: 400a0927bdb1425b4c0dc68a61b5b2d7d17c7d9f0e07317a1a6a373c080ef94be1dd65fdc4ac9a78fcdb58f89fd128450c7bc0d5b8ca0ae7eca3fbd98e50acba
+  languageName: node
+  linkType: hard
+
+"@babel/plugin-transform-unicode-sets-regex@npm:^7.24.1":
+  version: 7.24.1
+  resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.24.1"
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin": ^7.22.15
+    "@babel/helper-plugin-utils": ^7.24.0
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  checksum: 364342fb8e382dfaa23628b88e6484dc1097e53fb7199f4d338f1e2cd71d839bb0a35a9b1380074f6a10adb2e98b79d53ca3ec78c0b8c557ca895ffff42180df
+  languageName: node
+  linkType: hard
+
+"@babel/preset-env@npm:^7.16.4, @babel/preset-env@npm:^7.8.4, @babel/preset-env@npm:^7.9.5":
+  version: 7.24.4
+  resolution: "@babel/preset-env@npm:7.24.4"
+  dependencies:
+    "@babel/compat-data": ^7.24.4
+    "@babel/helper-compilation-targets": ^7.23.6
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-validator-option": ^7.23.5
+    "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ^7.24.4
+    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.24.1
+    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.24.1
+    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ^7.24.1
+    "@babel/plugin-proposal-private-property-in-object": 7.21.0-placeholder-for-preset-env.2
     "@babel/plugin-syntax-async-generators": ^7.8.4
     "@babel/plugin-syntax-class-properties": ^7.12.13
     "@babel/plugin-syntax-class-static-block": ^7.14.5
     "@babel/plugin-syntax-dynamic-import": ^7.8.3
     "@babel/plugin-syntax-export-namespace-from": ^7.8.3
     "@babel/plugin-syntax-async-generators": ^7.8.4
     "@babel/plugin-syntax-class-properties": ^7.12.13
     "@babel/plugin-syntax-class-static-block": ^7.14.5
     "@babel/plugin-syntax-dynamic-import": ^7.8.3
     "@babel/plugin-syntax-export-namespace-from": ^7.8.3
+    "@babel/plugin-syntax-import-assertions": ^7.24.1
+    "@babel/plugin-syntax-import-attributes": ^7.24.1
+    "@babel/plugin-syntax-import-meta": ^7.10.4
     "@babel/plugin-syntax-json-strings": ^7.8.3
     "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4
     "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3
     "@babel/plugin-syntax-json-strings": ^7.8.3
     "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4
     "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3
@@ -1445,117 +1692,125 @@ __metadata:
     "@babel/plugin-syntax-optional-chaining": ^7.8.3
     "@babel/plugin-syntax-private-property-in-object": ^7.14.5
     "@babel/plugin-syntax-top-level-await": ^7.14.5
     "@babel/plugin-syntax-optional-chaining": ^7.8.3
     "@babel/plugin-syntax-private-property-in-object": ^7.14.5
     "@babel/plugin-syntax-top-level-await": ^7.14.5
-    "@babel/plugin-transform-arrow-functions": ^7.14.5
-    "@babel/plugin-transform-async-to-generator": ^7.14.5
-    "@babel/plugin-transform-block-scoped-functions": ^7.14.5
-    "@babel/plugin-transform-block-scoping": ^7.14.5
-    "@babel/plugin-transform-classes": ^7.14.5
-    "@babel/plugin-transform-computed-properties": ^7.14.5
-    "@babel/plugin-transform-destructuring": ^7.14.7
-    "@babel/plugin-transform-dotall-regex": ^7.14.5
-    "@babel/plugin-transform-duplicate-keys": ^7.14.5
-    "@babel/plugin-transform-exponentiation-operator": ^7.14.5
-    "@babel/plugin-transform-for-of": ^7.14.5
-    "@babel/plugin-transform-function-name": ^7.14.5
-    "@babel/plugin-transform-literals": ^7.14.5
-    "@babel/plugin-transform-member-expression-literals": ^7.14.5
-    "@babel/plugin-transform-modules-amd": ^7.14.5
-    "@babel/plugin-transform-modules-commonjs": ^7.14.5
-    "@babel/plugin-transform-modules-systemjs": ^7.14.5
-    "@babel/plugin-transform-modules-umd": ^7.14.5
-    "@babel/plugin-transform-named-capturing-groups-regex": ^7.14.7
-    "@babel/plugin-transform-new-target": ^7.14.5
-    "@babel/plugin-transform-object-super": ^7.14.5
-    "@babel/plugin-transform-parameters": ^7.14.5
-    "@babel/plugin-transform-property-literals": ^7.14.5
-    "@babel/plugin-transform-regenerator": ^7.14.5
-    "@babel/plugin-transform-reserved-words": ^7.14.5
-    "@babel/plugin-transform-shorthand-properties": ^7.14.5
-    "@babel/plugin-transform-spread": ^7.14.6
-    "@babel/plugin-transform-sticky-regex": ^7.14.5
-    "@babel/plugin-transform-template-literals": ^7.14.5
-    "@babel/plugin-transform-typeof-symbol": ^7.14.5
-    "@babel/plugin-transform-unicode-escapes": ^7.14.5
-    "@babel/plugin-transform-unicode-regex": ^7.14.5
-    "@babel/preset-modules": ^0.1.4
-    "@babel/types": ^7.14.5
-    babel-plugin-polyfill-corejs2: ^0.2.2
-    babel-plugin-polyfill-corejs3: ^0.2.2
-    babel-plugin-polyfill-regenerator: ^0.2.2
-    core-js-compat: ^3.15.0
-    semver: ^6.3.0
+    "@babel/plugin-syntax-unicode-sets-regex": ^7.18.6
+    "@babel/plugin-transform-arrow-functions": ^7.24.1
+    "@babel/plugin-transform-async-generator-functions": ^7.24.3
+    "@babel/plugin-transform-async-to-generator": ^7.24.1
+    "@babel/plugin-transform-block-scoped-functions": ^7.24.1
+    "@babel/plugin-transform-block-scoping": ^7.24.4
+    "@babel/plugin-transform-class-properties": ^7.24.1
+    "@babel/plugin-transform-class-static-block": ^7.24.4
+    "@babel/plugin-transform-classes": ^7.24.1
+    "@babel/plugin-transform-computed-properties": ^7.24.1
+    "@babel/plugin-transform-destructuring": ^7.24.1
+    "@babel/plugin-transform-dotall-regex": ^7.24.1
+    "@babel/plugin-transform-duplicate-keys": ^7.24.1
+    "@babel/plugin-transform-dynamic-import": ^7.24.1
+    "@babel/plugin-transform-exponentiation-operator": ^7.24.1
+    "@babel/plugin-transform-export-namespace-from": ^7.24.1
+    "@babel/plugin-transform-for-of": ^7.24.1
+    "@babel/plugin-transform-function-name": ^7.24.1
+    "@babel/plugin-transform-json-strings": ^7.24.1
+    "@babel/plugin-transform-literals": ^7.24.1
+    "@babel/plugin-transform-logical-assignment-operators": ^7.24.1
+    "@babel/plugin-transform-member-expression-literals": ^7.24.1
+    "@babel/plugin-transform-modules-amd": ^7.24.1
+    "@babel/plugin-transform-modules-commonjs": ^7.24.1
+    "@babel/plugin-transform-modules-systemjs": ^7.24.1
+    "@babel/plugin-transform-modules-umd": ^7.24.1
+    "@babel/plugin-transform-named-capturing-groups-regex": ^7.22.5
+    "@babel/plugin-transform-new-target": ^7.24.1
+    "@babel/plugin-transform-nullish-coalescing-operator": ^7.24.1
+    "@babel/plugin-transform-numeric-separator": ^7.24.1
+    "@babel/plugin-transform-object-rest-spread": ^7.24.1
+    "@babel/plugin-transform-object-super": ^7.24.1
+    "@babel/plugin-transform-optional-catch-binding": ^7.24.1
+    "@babel/plugin-transform-optional-chaining": ^7.24.1
+    "@babel/plugin-transform-parameters": ^7.24.1
+    "@babel/plugin-transform-private-methods": ^7.24.1
+    "@babel/plugin-transform-private-property-in-object": ^7.24.1
+    "@babel/plugin-transform-property-literals": ^7.24.1
+    "@babel/plugin-transform-regenerator": ^7.24.1
+    "@babel/plugin-transform-reserved-words": ^7.24.1
+    "@babel/plugin-transform-shorthand-properties": ^7.24.1
+    "@babel/plugin-transform-spread": ^7.24.1
+    "@babel/plugin-transform-sticky-regex": ^7.24.1
+    "@babel/plugin-transform-template-literals": ^7.24.1
+    "@babel/plugin-transform-typeof-symbol": ^7.24.1
+    "@babel/plugin-transform-unicode-escapes": ^7.24.1
+    "@babel/plugin-transform-unicode-property-regex": ^7.24.1
+    "@babel/plugin-transform-unicode-regex": ^7.24.1
+    "@babel/plugin-transform-unicode-sets-regex": ^7.24.1
+    "@babel/preset-modules": 0.1.6-no-external-plugins
+    babel-plugin-polyfill-corejs2: ^0.4.10
+    babel-plugin-polyfill-corejs3: ^0.10.4
+    babel-plugin-polyfill-regenerator: ^0.6.1
+    core-js-compat: ^3.31.0
+    semver: ^6.3.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: ebebc20ada68c92b67375926021d576af3636a279aee7625c1e234880355c8669188483aecfff2d478c1caa9fcf18b569ea329060b479236b04baed2bdf796d5
+  checksum: 5a057a6463f92b02bfe66257d3f2c76878815bc7847f2a716b0539d9f547eae2a9d1f0fc62a5c0eff6ab0504bb52e815829ef893e4586b641f8dd6a609d114f3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/preset-modules@npm:^0.1.3, @babel/preset-modules@npm:^0.1.4":
-  version: 0.1.4
-  resolution: "@babel/preset-modules@npm:0.1.4"
+"@babel/preset-modules@npm:0.1.6-no-external-plugins":
+  version: 0.1.6-no-external-plugins
+  resolution: "@babel/preset-modules@npm:0.1.6-no-external-plugins"
   dependencies:
     "@babel/helper-plugin-utils": ^7.0.0
   dependencies:
     "@babel/helper-plugin-utils": ^7.0.0
-    "@babel/plugin-proposal-unicode-property-regex": ^7.4.4
-    "@babel/plugin-transform-dotall-regex": ^7.4.4
     "@babel/types": ^7.4.4
     esutils: ^2.0.2
   peerDependencies:
     "@babel/types": ^7.4.4
     esutils: ^2.0.2
   peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: 7c6500be06be9a341e377eb63292a4a22d0da2b4fb8c68714aff703ddb341cbd58e37d4119d64fc3e602f73801103af471fca2c60b4c1e48e08eea3e6b1afc93
+    "@babel/core": ^7.0.0-0 || ^8.0.0-0 <8.0.0
+  checksum: 4855e799bc50f2449fb5210f78ea9e8fd46cf4f242243f1e2ed838e2bd702e25e73e822e7f8447722a5f4baa5e67a8f7a0e403f3e7ce04540ff743a9c411c375
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/preset-react@npm:7.9.1":
-  version: 7.9.1
-  resolution: "@babel/preset-react@npm:7.9.1"
+"@babel/preset-react@npm:^7.16.0, @babel/preset-react@npm:^7.9.4":
+  version: 7.24.1
+  resolution: "@babel/preset-react@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-transform-react-display-name": ^7.8.3
-    "@babel/plugin-transform-react-jsx": ^7.9.1
-    "@babel/plugin-transform-react-jsx-development": ^7.9.0
-    "@babel/plugin-transform-react-jsx-self": ^7.9.0
-    "@babel/plugin-transform-react-jsx-source": ^7.9.0
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-validator-option": ^7.23.5
+    "@babel/plugin-transform-react-display-name": ^7.24.1
+    "@babel/plugin-transform-react-jsx": ^7.23.4
+    "@babel/plugin-transform-react-jsx-development": ^7.22.5
+    "@babel/plugin-transform-react-pure-annotations": ^7.24.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 68efd5246694927d513a27bf4bb4b3894f489b5dc9eea883e7eda82341d152a3f3d6187fb2ed1b080064fa6a111ef53182ec43049ad766fb10af4440ef9bc62b
+  checksum: 70e146a6de480cb4b6c5eb197003960a2d148d513e1f5b5d04ee954f255d68c935c2800da13e550267f47b894bd0214b2548181467b52a4bdc0a85020061b68c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/preset-react@npm:^7.0.0":
-  version: 7.14.5
-  resolution: "@babel/preset-react@npm:7.14.5"
+"@babel/preset-typescript@npm:^7.16.0":
+  version: 7.24.1
+  resolution: "@babel/preset-typescript@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/helper-plugin-utils": ^7.14.5
-    "@babel/helper-validator-option": ^7.14.5
-    "@babel/plugin-transform-react-display-name": ^7.14.5
-    "@babel/plugin-transform-react-jsx": ^7.14.5
-    "@babel/plugin-transform-react-jsx-development": ^7.14.5
-    "@babel/plugin-transform-react-pure-annotations": ^7.14.5
+    "@babel/helper-plugin-utils": ^7.24.0
+    "@babel/helper-validator-option": ^7.23.5
+    "@babel/plugin-syntax-jsx": ^7.24.1
+    "@babel/plugin-transform-modules-commonjs": ^7.24.1
+    "@babel/plugin-transform-typescript": ^7.24.1
   peerDependencies:
     "@babel/core": ^7.0.0-0
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 413c507f853b95c71ecb64f29ea7b0786464a237c54977b03a4410dd837b03bfa55df81d0e337f9792d9abc61f4bf3d616f857d00a36ff4ede79407c143ac865
+  checksum: f3e0ff8c20dd5abc82614df2d7953f1549a98282b60809478f7dfb41c29be63720f2d1d7a51ef1f0d939b65e8666cb7d36e32bc4f8ac2b74c20664efd41e8bdd
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/preset-typescript@npm:7.9.0":
-  version: 7.9.0
-  resolution: "@babel/preset-typescript@npm:7.9.0"
-  dependencies:
-    "@babel/helper-plugin-utils": ^7.8.3
-    "@babel/plugin-transform-typescript": ^7.9.0
-  peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: fbdd8b3d0493df4c4a88a59ca616331658e219b4c294584834a34a5148c991b28173c8f244c3b4fcaee93cd1bad6c99c0a84bd0a47a4b3cc8e759f35a0d6a3eb
+"@babel/regjsgen@npm:^0.8.0":
+  version: 0.8.0
+  resolution: "@babel/regjsgen@npm:0.8.0"
+  checksum: 89c338fee774770e5a487382170711014d49a68eb281e74f2b5eac88f38300a4ad545516a7786a8dd5702e9cf009c94c2f582d200f077ac5decd74c56b973730
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/runtime-corejs3@npm:^7.12.1":
-  version: 7.14.7
-  resolution: "@babel/runtime-corejs3@npm:7.14.7"
+"@babel/runtime-corejs2@npm:^7.0.0":
+  version: 7.24.4
+  resolution: "@babel/runtime-corejs2@npm:7.24.4"
   dependencies:
   dependencies:
-    core-js-pure: ^3.15.0
-    regenerator-runtime: ^0.13.4
-  checksum: 9e49fc27e4de9fd5a97069aeeb0746cf0e42afe2068fa717a8abec740782a58d6bb1dc635c37a7bd47c40f3945fabad6308a44915520e15c7651f34b24b89d6f
+    core-js: ^2.6.12
+    regenerator-runtime: ^0.14.0
+  checksum: f164006b7b63093ff407bc84988427bf56f01b1764a1eca2478139dcff131411e7c2ea10657e116ae47ccc0e96b165c8329a96336d2d421a05bb34d0292d3521
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -1568,25 +1823,36 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/runtime@npm:7.9.0":
-  version: 7.9.0
-  resolution: "@babel/runtime@npm:7.9.0"
+"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2":
+  version: 7.14.6
+  resolution: "@babel/runtime@npm:7.14.6"
   dependencies:
     regenerator-runtime: ^0.13.4
   dependencies:
     regenerator-runtime: ^0.13.4
-  checksum: dc9b50c1893460e71d93c299e659b61b41a37780e78c1e1404b360ca275d44b79107c0d986d74ac8163757f02384cda94d8315bded40deafe72ca07f7761a098
+  checksum: 927ffed7871f2ed29f967a8dad7a72aa10662f93b6735a89d664a161fa4dc2074b8947ca159a8a0a49cec9a71c8de473d7c2b22d3de0ee4d7dd06d24a7f98018
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.3.4, @babel/runtime@npm:^7.4.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2":
-  version: 7.14.6
-  resolution: "@babel/runtime@npm:7.14.6"
+"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.5.5":
+  version: 7.24.4
+  resolution: "@babel/runtime@npm:7.24.4"
   dependencies:
   dependencies:
-    regenerator-runtime: ^0.13.4
-  checksum: 927ffed7871f2ed29f967a8dad7a72aa10662f93b6735a89d664a161fa4dc2074b8947ca159a8a0a49cec9a71c8de473d7c2b22d3de0ee4d7dd06d24a7f98018
+    regenerator-runtime: ^0.14.0
+  checksum: 2f27d4c0ffac7ae7999ac0385e1106f2a06992a8bdcbf3da06adcac7413863cd08c198c2e4e970041bbea849e17f02e1df18875539b6afba76c781b6b59a07c3
+  languageName: node
+  linkType: hard
+
+"@babel/template@npm:^7.10.4, @babel/template@npm:^7.22.15, @babel/template@npm:^7.24.0, @babel/template@npm:^7.3.3":
+  version: 7.24.0
+  resolution: "@babel/template@npm:7.24.0"
+  dependencies:
+    "@babel/code-frame": ^7.23.5
+    "@babel/parser": ^7.24.0
+    "@babel/types": ^7.24.0
+  checksum: f257b003c071a0cecdbfceca74185f18fe62c055469ab5c1d481aab12abeebed328e67e0a19fd978a2a8de97b28953fa4bc3da6d038a7345fdf37923b9fcdec8
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/template@npm:^7.14.5, @babel/template@npm:^7.4.0, @babel/template@npm:^7.8.6":
+"@babel/template@npm:^7.14.5":
   version: 7.14.5
   resolution: "@babel/template@npm:7.14.5"
   dependencies:
   version: 7.14.5
   resolution: "@babel/template@npm:7.14.5"
   dependencies:
@@ -1597,30 +1863,42 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/traverse@npm:^7.1.0, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.14.5, @babel/traverse@npm:^7.4.3, @babel/traverse@npm:^7.7.0, @babel/traverse@npm:^7.9.0":
-  version: 7.14.7
-  resolution: "@babel/traverse@npm:7.14.7"
+"@babel/traverse@npm:^7.1.0, @babel/traverse@npm:^7.12.1, @babel/traverse@npm:^7.14.5, @babel/traverse@npm:^7.24.1, @babel/traverse@npm:^7.7.0":
+  version: 7.24.1
+  resolution: "@babel/traverse@npm:7.24.1"
   dependencies:
   dependencies:
-    "@babel/code-frame": ^7.14.5
-    "@babel/generator": ^7.14.5
-    "@babel/helper-function-name": ^7.14.5
-    "@babel/helper-hoist-variables": ^7.14.5
-    "@babel/helper-split-export-declaration": ^7.14.5
-    "@babel/parser": ^7.14.7
-    "@babel/types": ^7.14.5
-    debug: ^4.1.0
+    "@babel/code-frame": ^7.24.1
+    "@babel/generator": ^7.24.1
+    "@babel/helper-environment-visitor": ^7.22.20
+    "@babel/helper-function-name": ^7.23.0
+    "@babel/helper-hoist-variables": ^7.22.5
+    "@babel/helper-split-export-declaration": ^7.22.6
+    "@babel/parser": ^7.24.1
+    "@babel/types": ^7.24.0
+    debug: ^4.3.1
     globals: ^11.1.0
     globals: ^11.1.0
-  checksum: 11e9162e46bdd6daef8691facbf5c47838f6e312ac775be35c40353c77887338d1b9ce497211d2ae96628a9230551f03eb3df49b4ca53b6f668082f2c157d1a0
+  checksum: 92a5ca906abfba9df17666d2001ab23f18600035f706a687055a0e392a690ae48d6fec67c8bd4ef19ba18699a77a5b7f85727e36b83f7d110141608fe0c24fe9
+  languageName: node
+  linkType: hard
+
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.14.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.4.4, @babel/types@npm:^7.7.0":
+  version: 7.14.5
+  resolution: "@babel/types@npm:7.14.5"
+  dependencies:
+    "@babel/helper-validator-identifier": ^7.14.5
+    to-fast-properties: ^2.0.0
+  checksum: 7c1ab6e8bdf438d44236034cab10f7d0f1971179bc405dca26733a9b89dd87dd692dc49a238a7495075bc41a9a17fb6f08b4d1da45ea6ddcce1e5c8593574aea
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.14.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.4.0, @babel/types@npm:^7.4.4, @babel/types@npm:^7.7.0, @babel/types@npm:^7.9.0":
-  version: 7.14.5
-  resolution: "@babel/types@npm:7.14.5"
+"@babel/types@npm:^7.12.1, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.4, @babel/types@npm:^7.24.0, @babel/types@npm:^7.3.3":
+  version: 7.24.0
+  resolution: "@babel/types@npm:7.24.0"
   dependencies:
   dependencies:
-    "@babel/helper-validator-identifier": ^7.14.5
+    "@babel/helper-string-parser": ^7.23.4
+    "@babel/helper-validator-identifier": ^7.22.20
     to-fast-properties: ^2.0.0
     to-fast-properties: ^2.0.0
-  checksum: 7c1ab6e8bdf438d44236034cab10f7d0f1971179bc405dca26733a9b89dd87dd692dc49a238a7495075bc41a9a17fb6f08b4d1da45ea6ddcce1e5c8593574aea
+  checksum: 4b574a37d490f621470ff36a5afaac6deca5546edcb9b5e316d39acbb20998e9c2be42f3fc0bf2b55906fc49ff2a5a6a097e8f5a726ee3f708a0b0ca93aed807
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -1634,6 +1912,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@bcoe/v8-coverage@npm:^0.2.3":
+  version: 0.2.3
+  resolution: "@bcoe/v8-coverage@npm:0.2.3"
+  checksum: 850f9305536d0f2bd13e9e0881cb5f02e4f93fad1189f7b2d4bebf694e3206924eadee1068130d43c11b750efcc9405f88a8e42ef098b6d75239c0f047de1a27
+  languageName: node
+  linkType: hard
+
 "@cnakazawa/watch@npm:^1.0.3":
   version: 1.0.4
   resolution: "@cnakazawa/watch@npm:1.0.4"
 "@cnakazawa/watch@npm:^1.0.3":
   version: 1.0.4
   resolution: "@cnakazawa/watch@npm:1.0.4"
@@ -1766,6 +2051,23 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@eslint/eslintrc@npm:^0.4.3":
+  version: 0.4.3
+  resolution: "@eslint/eslintrc@npm:0.4.3"
+  dependencies:
+    ajv: ^6.12.4
+    debug: ^4.1.1
+    espree: ^7.3.0
+    globals: ^13.9.0
+    ignore: ^4.0.6
+    import-fresh: ^3.2.1
+    js-yaml: ^3.13.1
+    minimatch: ^3.0.4
+    strip-json-comments: ^3.1.1
+  checksum: 03a7704150b868c318aab6a94d87a33d30dc2ec579d27374575014f06237ba1370ae11178db772f985ef680d469dc237e7b16a1c5d8edaaeb8c3733e7a95a6d3
+  languageName: node
+  linkType: hard
+
 "@fortawesome/fontawesome-common-types@npm:^0.2.28":
   version: 0.2.35
   resolution: "@fortawesome/fontawesome-common-types@npm:0.2.35"
 "@fortawesome/fontawesome-common-types@npm:^0.2.28":
   version: 0.2.35
   resolution: "@fortawesome/fontawesome-common-types@npm:0.2.35"
@@ -1854,7 +2156,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@hapi/joi@npm:^15.0.0":
+"@hapi/joi@npm:^15.1.0":
   version: 15.1.1
   resolution: "@hapi/joi@npm:15.1.1"
   dependencies:
   version: 15.1.1
   resolution: "@hapi/joi@npm:15.1.1"
   dependencies:
@@ -1904,175 +2206,227 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/console@npm:^24.7.1, @jest/console@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/console@npm:24.9.0"
+"@humanwhocodes/config-array@npm:^0.5.0":
+  version: 0.5.0
+  resolution: "@humanwhocodes/config-array@npm:0.5.0"
   dependencies:
   dependencies:
-    "@jest/source-map": ^24.9.0
-    chalk: ^2.0.1
-    slash: ^2.0.0
-  checksum: ee6468c4aeeb8752126e92e20b0ffbf32abda731e9b7865b63b60bd569c3536e9c901efcec4d81c506a7c6fea2a970ace8262190961aba31dedbfeaa3459d78b
+    "@humanwhocodes/object-schema": ^1.2.0
+    debug: ^4.1.1
+    minimatch: ^3.0.4
+  checksum: 44ee6a9f05d93dd9d5935a006b17572328ba9caff8002442f601736cbda79c580cc0f5a49ce9eb88fbacc5c3a6b62098357c2e95326cd17bb9f1a6c61d6e95e7
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/core@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/core@npm:24.9.0"
-  dependencies:
-    "@jest/console": ^24.7.1
-    "@jest/reporters": ^24.9.0
-    "@jest/test-result": ^24.9.0
-    "@jest/transform": ^24.9.0
-    "@jest/types": ^24.9.0
-    ansi-escapes: ^3.0.0
-    chalk: ^2.0.1
+"@humanwhocodes/object-schema@npm:^1.2.0":
+  version: 1.2.1
+  resolution: "@humanwhocodes/object-schema@npm:1.2.1"
+  checksum: a824a1ec31591231e4bad5787641f59e9633827d0a2eaae131a288d33c9ef0290bd16fda8da6f7c0fcb014147865d12118df10db57f27f41e20da92369fcb3f1
+  languageName: node
+  linkType: hard
+
+"@istanbuljs/load-nyc-config@npm:^1.0.0":
+  version: 1.1.0
+  resolution: "@istanbuljs/load-nyc-config@npm:1.1.0"
+  dependencies:
+    camelcase: ^5.3.1
+    find-up: ^4.1.0
+    get-package-type: ^0.1.0
+    js-yaml: ^3.13.1
+    resolve-from: ^5.0.0
+  checksum: d578da5e2e804d5c93228450a1380e1a3c691de4953acc162f387b717258512a3e07b83510a936d9fab03eac90817473917e24f5d16297af3867f59328d58568
+  languageName: node
+  linkType: hard
+
+"@istanbuljs/schema@npm:^0.1.2":
+  version: 0.1.3
+  resolution: "@istanbuljs/schema@npm:0.1.3"
+  checksum: 5282759d961d61350f33d9118d16bcaed914ebf8061a52f4fa474b2cb08720c9c81d165e13b82f2e5a8a212cc5af482f0c6fc1ac27b9e067e5394c9a6ed186c9
+  languageName: node
+  linkType: hard
+
+"@jest/console@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/console@npm:26.6.2"
+  dependencies:
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    chalk: ^4.0.0
+    jest-message-util: ^26.6.2
+    jest-util: ^26.6.2
+    slash: ^3.0.0
+  checksum: 69a9ca6ba357d7634fd537e3b87c64369865ffb59f57fe6661223088bd62273d0c1d660fefce3625a427f42a37d32590f6b291e1295ea6d6b7cb31ddae36a737
+  languageName: node
+  linkType: hard
+
+"@jest/core@npm:^26.6.0, @jest/core@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "@jest/core@npm:26.6.3"
+  dependencies:
+    "@jest/console": ^26.6.2
+    "@jest/reporters": ^26.6.2
+    "@jest/test-result": ^26.6.2
+    "@jest/transform": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    ansi-escapes: ^4.2.1
+    chalk: ^4.0.0
     exit: ^0.1.2
     exit: ^0.1.2
-    graceful-fs: ^4.1.15
-    jest-changed-files: ^24.9.0
-    jest-config: ^24.9.0
-    jest-haste-map: ^24.9.0
-    jest-message-util: ^24.9.0
-    jest-regex-util: ^24.3.0
-    jest-resolve: ^24.9.0
-    jest-resolve-dependencies: ^24.9.0
-    jest-runner: ^24.9.0
-    jest-runtime: ^24.9.0
-    jest-snapshot: ^24.9.0
-    jest-util: ^24.9.0
-    jest-validate: ^24.9.0
-    jest-watcher: ^24.9.0
-    micromatch: ^3.1.10
-    p-each-series: ^1.0.0
-    realpath-native: ^1.1.0
-    rimraf: ^2.5.4
-    slash: ^2.0.0
-    strip-ansi: ^5.0.0
-  checksum: 44d63883bc410ea2448eb359c417b92d9dd5fb9bec51f28bde2bd87ade705c4f0f6698f0c251a679204e860bf865120c58725cf397465862c99a70327bcb99fc
+    graceful-fs: ^4.2.4
+    jest-changed-files: ^26.6.2
+    jest-config: ^26.6.3
+    jest-haste-map: ^26.6.2
+    jest-message-util: ^26.6.2
+    jest-regex-util: ^26.0.0
+    jest-resolve: ^26.6.2
+    jest-resolve-dependencies: ^26.6.3
+    jest-runner: ^26.6.3
+    jest-runtime: ^26.6.3
+    jest-snapshot: ^26.6.2
+    jest-util: ^26.6.2
+    jest-validate: ^26.6.2
+    jest-watcher: ^26.6.2
+    micromatch: ^4.0.2
+    p-each-series: ^2.1.0
+    rimraf: ^3.0.0
+    slash: ^3.0.0
+    strip-ansi: ^6.0.0
+  checksum: f52b26ffe9b923ed67b3ff30e170b3a434d4263990f78d96cd43acbd0aa8ad36aecad2f1822f376da3a80228714fd6b7f7acd51744133cfcd2780ba0e3da537b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/environment@npm:^24.3.0, @jest/environment@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/environment@npm:24.9.0"
+"@jest/environment@npm:^26.6.0, @jest/environment@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/environment@npm:26.6.2"
+  dependencies:
+    "@jest/fake-timers": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    jest-mock: ^26.6.2
+  checksum: 7748081b2a758161785aff161780b05084dccaff908c8ed82c04f7da5d5e5439e77b5eb667306d5c4e1422653c7a67ed2955f26704f48c65c404195e1e21780a
+  languageName: node
+  linkType: hard
+
+"@jest/fake-timers@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/fake-timers@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/fake-timers": ^24.9.0
-    "@jest/transform": ^24.9.0
-    "@jest/types": ^24.9.0
-    jest-mock: ^24.9.0
-  checksum: 6a663c05713ad0cd1dc7c5bf715a3e5e655e73ee02497ab0a9dea4fe0855226504535c504d265c054c8b4bafb1216dff0e7e0e3b4ed064bda4c3d6efe74fe369
+    "@jest/types": ^26.6.2
+    "@sinonjs/fake-timers": ^6.0.1
+    "@types/node": "*"
+    jest-message-util: ^26.6.2
+    jest-mock: ^26.6.2
+    jest-util: ^26.6.2
+  checksum: c732658fac4014a424e6629495296c3b2e8697787518df34c74539ec139625e7141ad792b8a4d3c8392b47954ad01be9846b7c57cc8c631490969e7cafa84e6a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/fake-timers@npm:^24.3.0, @jest/fake-timers@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/fake-timers@npm:24.9.0"
+"@jest/globals@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/globals@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    jest-message-util: ^24.9.0
-    jest-mock: ^24.9.0
-  checksum: d49ab33e28b070d5be75659ed89d4b79e74012c8c28ecf51cf9b89732ba5b2a57129787dd144949c048a0460ed62f1e32079a4b10d896c75bde024699d7a2c5c
+    "@jest/environment": ^26.6.2
+    "@jest/types": ^26.6.2
+    expect: ^26.6.2
+  checksum: 49b28d0cc7e99898eeaf23e6899e3c9ee25a2a4831caa3eb930ec1722de2e92a0e8a6a6f649438fdd20ff0c0d5e522dd78cb719466a57f011a88d60419b903c5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/reporters@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/reporters@npm:24.9.0"
+"@jest/reporters@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/reporters@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/environment": ^24.9.0
-    "@jest/test-result": ^24.9.0
-    "@jest/transform": ^24.9.0
-    "@jest/types": ^24.9.0
-    chalk: ^2.0.1
+    "@bcoe/v8-coverage": ^0.2.3
+    "@jest/console": ^26.6.2
+    "@jest/test-result": ^26.6.2
+    "@jest/transform": ^26.6.2
+    "@jest/types": ^26.6.2
+    chalk: ^4.0.0
+    collect-v8-coverage: ^1.0.0
     exit: ^0.1.2
     glob: ^7.1.2
     exit: ^0.1.2
     glob: ^7.1.2
-    istanbul-lib-coverage: ^2.0.2
-    istanbul-lib-instrument: ^3.0.1
-    istanbul-lib-report: ^2.0.4
-    istanbul-lib-source-maps: ^3.0.1
-    istanbul-reports: ^2.2.6
-    jest-haste-map: ^24.9.0
-    jest-resolve: ^24.9.0
-    jest-runtime: ^24.9.0
-    jest-util: ^24.9.0
-    jest-worker: ^24.6.0
-    node-notifier: ^5.4.2
-    slash: ^2.0.0
+    graceful-fs: ^4.2.4
+    istanbul-lib-coverage: ^3.0.0
+    istanbul-lib-instrument: ^4.0.3
+    istanbul-lib-report: ^3.0.0
+    istanbul-lib-source-maps: ^4.0.0
+    istanbul-reports: ^3.0.2
+    jest-haste-map: ^26.6.2
+    jest-resolve: ^26.6.2
+    jest-util: ^26.6.2
+    jest-worker: ^26.6.2
+    node-notifier: ^8.0.0
+    slash: ^3.0.0
     source-map: ^0.6.0
     source-map: ^0.6.0
-    string-length: ^2.0.0
-  checksum: 588539d0d9a5e483e5e09c1dd7c42b6490199cb0588a9ae8eb1b2c34a74cf7da0bba5dd425c19307a9d95a075bfc4feb0221d3847b9542a3a727342e3f30e5a1
+    string-length: ^4.0.1
+    terminal-link: ^2.0.0
+    v8-to-istanbul: ^7.0.0
+  dependenciesMeta:
+    node-notifier:
+      optional: true
+  checksum: 53c7a697c562becb7682a9a6248ea553013bf7048c08ddce5bf9fb53b975fc9f799ca163f7494e0be6c4d3cf181c8bc392976268da52b7de8ce4470b971ed84e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/source-map@npm:^24.3.0, @jest/source-map@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/source-map@npm:24.9.0"
+"@jest/source-map@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/source-map@npm:26.6.2"
   dependencies:
     callsites: ^3.0.0
   dependencies:
     callsites: ^3.0.0
-    graceful-fs: ^4.1.15
+    graceful-fs: ^4.2.4
     source-map: ^0.6.0
     source-map: ^0.6.0
-  checksum: 00479faf6854d5d183b94465db1a0876980ced72bf26cb6a2fe8c04977dc2692e6529faa6b64269492d1d9cab51feebaac9d453d1e6bb1306fc15777143b72af
+  checksum: b171cef442738887dda85527ab78229996db5946c6435ddb56d442c2851889ba493729a9de73100f1a31b9a31a91207b55bc75656ae7df9843d65078b925385e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/test-result@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/test-result@npm:24.9.0"
+"@jest/test-result@npm:^26.6.0, @jest/test-result@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/test-result@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/console": ^24.9.0
-    "@jest/types": ^24.9.0
+    "@jest/console": ^26.6.2
+    "@jest/types": ^26.6.2
     "@types/istanbul-lib-coverage": ^2.0.0
     "@types/istanbul-lib-coverage": ^2.0.0
-  checksum: 7145c7baa289798881160b3cfa5b2466b2636238a52b77cf46e5468ffe2881fb8fb8d4966155a8d508b26a8d29a302a9eb9037de1a371e5dc9bb6e94837c0ae7
+    collect-v8-coverage: ^1.0.0
+  checksum: dcb6175825231e9377e43546aed4edd6acc22f1788d5f099bbba36bb55b9115a92f760e88426c076bcdeff5a50d8f697327a920db0cd1fb339781fc3713fa8c7
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/test-sequencer@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/test-sequencer@npm:24.9.0"
+"@jest/test-sequencer@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "@jest/test-sequencer@npm:26.6.3"
   dependencies:
   dependencies:
-    "@jest/test-result": ^24.9.0
-    jest-haste-map: ^24.9.0
-    jest-runner: ^24.9.0
-    jest-runtime: ^24.9.0
-  checksum: 049bea54743925b361bf10acce8a1de8e9a2ac9b5158044d484f3fc5748f975d52d8260e9ff2621fc29b5b586a17e54693670c7dfa75b09f5e83e87f2a63aac2
+    "@jest/test-result": ^26.6.2
+    graceful-fs: ^4.2.4
+    jest-haste-map: ^26.6.2
+    jest-runner: ^26.6.3
+    jest-runtime: ^26.6.3
+  checksum: a3450b3d7057f74da1828bb7b3658f228a7c049dc4082c5c49b8bafbd8f69d102a8a99007b7ed5d43464712f7823f53fe3564fda17787f178c219cccf329a461
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/transform@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/transform@npm:24.9.0"
+"@jest/transform@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "@jest/transform@npm:26.6.2"
   dependencies:
     "@babel/core": ^7.1.0
   dependencies:
     "@babel/core": ^7.1.0
-    "@jest/types": ^24.9.0
-    babel-plugin-istanbul: ^5.1.0
-    chalk: ^2.0.1
+    "@jest/types": ^26.6.2
+    babel-plugin-istanbul: ^6.0.0
+    chalk: ^4.0.0
     convert-source-map: ^1.4.0
     fast-json-stable-stringify: ^2.0.0
     convert-source-map: ^1.4.0
     fast-json-stable-stringify: ^2.0.0
-    graceful-fs: ^4.1.15
-    jest-haste-map: ^24.9.0
-    jest-regex-util: ^24.9.0
-    jest-util: ^24.9.0
-    micromatch: ^3.1.10
+    graceful-fs: ^4.2.4
+    jest-haste-map: ^26.6.2
+    jest-regex-util: ^26.0.0
+    jest-util: ^26.6.2
+    micromatch: ^4.0.2
     pirates: ^4.0.1
     pirates: ^4.0.1
-    realpath-native: ^1.1.0
-    slash: ^2.0.0
+    slash: ^3.0.0
     source-map: ^0.6.1
     source-map: ^0.6.1
-    write-file-atomic: 2.4.1
-  checksum: 0153bcd6a9b464c85ee8b67c360f745ab8e41b1b363220f1f12ed644a667dceb6666366017f7f849a8f6cde960020b638b8557eae852af0537520b0903881fbd
-  languageName: node
-  linkType: hard
-
-"@jest/types@npm:^24.3.0, @jest/types@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "@jest/types@npm:24.9.0"
-  dependencies:
-    "@types/istanbul-lib-coverage": ^2.0.0
-    "@types/istanbul-reports": ^1.1.1
-    "@types/yargs": ^13.0.0
-  checksum: 603698f774cf22f9d16a0e0fac9e10e7db21052aebfa33db154c8a5940e0eb1fa9c079a8c91681041ad3aeee2adfa950608dd0c663130316ba034b8bca7b301c
+    write-file-atomic: ^3.0.0
+  checksum: 31667b925a2f3b310d854495da0ab67be8f5da24df76ecfc51162e75f1140aed5d18069ba190cb5e0c7e492b04272c8c79076ddf5bbcff530ee80a16a02c4545
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@jest/types@npm:^26.6.2":
+"@jest/types@npm:^26.6.0, @jest/types@npm:^26.6.2":
   version: 26.6.2
   resolution: "@jest/types@npm:26.6.2"
   dependencies:
   version: 26.6.2
   resolution: "@jest/types@npm:26.6.2"
   dependencies:
@@ -2085,6 +2439,58 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@jridgewell/gen-mapping@npm:^0.3.5":
+  version: 0.3.5
+  resolution: "@jridgewell/gen-mapping@npm:0.3.5"
+  dependencies:
+    "@jridgewell/set-array": ^1.2.1
+    "@jridgewell/sourcemap-codec": ^1.4.10
+    "@jridgewell/trace-mapping": ^0.3.24
+  checksum: ff7a1764ebd76a5e129c8890aa3e2f46045109dabde62b0b6c6a250152227647178ff2069ea234753a690d8f3c4ac8b5e7b267bbee272bffb7f3b0a370ab6e52
+  languageName: node
+  linkType: hard
+
+"@jridgewell/resolve-uri@npm:^3.1.0":
+  version: 3.1.2
+  resolution: "@jridgewell/resolve-uri@npm:3.1.2"
+  checksum: 83b85f72c59d1c080b4cbec0fef84528963a1b5db34e4370fa4bd1e3ff64a0d80e0cee7369d11d73c704e0286fb2865b530acac7a871088fbe92b5edf1000870
+  languageName: node
+  linkType: hard
+
+"@jridgewell/set-array@npm:^1.2.1":
+  version: 1.2.1
+  resolution: "@jridgewell/set-array@npm:1.2.1"
+  checksum: 832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10
+  languageName: node
+  linkType: hard
+
+"@jridgewell/source-map@npm:^0.3.3":
+  version: 0.3.6
+  resolution: "@jridgewell/source-map@npm:0.3.6"
+  dependencies:
+    "@jridgewell/gen-mapping": ^0.3.5
+    "@jridgewell/trace-mapping": ^0.3.25
+  checksum: c9dc7d899397df95e3c9ec287b93c0b56f8e4453cd20743e2b9c8e779b1949bc3cccf6c01bb302779e46560eb45f62ea38d19fedd25370d814734268450a9f30
+  languageName: node
+  linkType: hard
+
+"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14":
+  version: 1.4.15
+  resolution: "@jridgewell/sourcemap-codec@npm:1.4.15"
+  checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8
+  languageName: node
+  linkType: hard
+
+"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25":
+  version: 0.3.25
+  resolution: "@jridgewell/trace-mapping@npm:0.3.25"
+  dependencies:
+    "@jridgewell/resolve-uri": ^3.1.0
+    "@jridgewell/sourcemap-codec": ^1.4.14
+  checksum: 9d3c40d225e139987b50c48988f8717a54a8c994d8a948ee42e1412e08988761d0754d7d10b803061cc3aebf35f92a5dbbab493bd0e1a9ef9e89a2130e83ba34
+  languageName: node
+  linkType: hard
+
 "@material-ui/core@npm:3.9.3":
   version: 3.9.3
   resolution: "@material-ui/core@npm:3.9.3"
 "@material-ui/core@npm:3.9.3":
   version: 3.9.3
   resolution: "@material-ui/core@npm:3.9.3"
@@ -2166,16 +2572,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@mrmlnc/readdir-enhanced@npm:^2.2.1":
-  version: 2.2.1
-  resolution: "@mrmlnc/readdir-enhanced@npm:2.2.1"
-  dependencies:
-    call-me-maybe: ^1.0.1
-    glob-to-regexp: ^0.3.0
-  checksum: d3b82b29368821154ce8e10bef5ccdbfd070d3e9601643c99ea4607e56f3daeaa4e755dd6d2355da20762c695c1b0570543d9f84b48f70c211ec09c4aaada2e1
-  languageName: node
-  linkType: hard
-
 "@nodelib/fs.scandir@npm:2.1.5":
   version: 2.1.5
   resolution: "@nodelib/fs.scandir@npm:2.1.5"
 "@nodelib/fs.scandir@npm:2.1.5":
   version: 2.1.5
   resolution: "@nodelib/fs.scandir@npm:2.1.5"
@@ -2193,20 +2589,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@nodelib/fs.stat@npm:^1.1.2":
-  version: 1.1.3
-  resolution: "@nodelib/fs.stat@npm:1.1.3"
-  checksum: 318deab369b518a34778cdaa0054dd28a4381c0c78e40bbd20252f67d084b1d7bf9295fea4423de2c19ac8e1a34f120add9125f481b2a710f7068bcac7e3e305
-  languageName: node
-  linkType: hard
-
 "@nodelib/fs.walk@npm:^1.2.3":
 "@nodelib/fs.walk@npm:^1.2.3":
-  version: 1.2.7
-  resolution: "@nodelib/fs.walk@npm:1.2.7"
+  version: 1.2.8
+  resolution: "@nodelib/fs.walk@npm:1.2.8"
   dependencies:
     "@nodelib/fs.scandir": 2.1.5
     fastq: ^1.6.0
   dependencies:
     "@nodelib/fs.scandir": 2.1.5
     fastq: ^1.6.0
-  checksum: f5286c39c2f9cc0e89b2cbee6b735c5cf572c37f9c0a47a16ce3c1d9ba5d488f3153976ceb1b984ad09dbd8d1de620fab3e7b0ef2b64a006267d0895a16ce95c
+  checksum: 190c643f156d8f8f277bf2a6078af1ffde1fd43f498f187c2db24d35b4b4b5785c02c7dc52e356497b9a1b65b13edc996de08de0b961c32844364da02986dc53
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -2261,6 +2650,42 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@pmmmwh/react-refresh-webpack-plugin@npm:0.4.2":
+  version: 0.4.2
+  resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.4.2"
+  dependencies:
+    ansi-html: ^0.0.7
+    error-stack-parser: ^2.0.6
+    html-entities: ^1.2.1
+    native-url: ^0.2.6
+    schema-utils: ^2.6.5
+    source-map: ^0.7.3
+  peerDependencies:
+    "@types/webpack": 4.x
+    react-refresh: ^0.8.3
+    sockjs-client: ^1.4.0
+    type-fest: ^0.13.1
+    webpack: ">=4.43.0 <6.0.0"
+    webpack-dev-server: 3.x
+    webpack-hot-middleware: 2.x
+    webpack-plugin-serve: 0.x || 1.x
+  peerDependenciesMeta:
+    "@types/webpack":
+      optional: true
+    sockjs-client:
+      optional: true
+    type-fest:
+      optional: true
+    webpack-dev-server:
+      optional: true
+    webpack-hot-middleware:
+      optional: true
+    webpack-plugin-serve:
+      optional: true
+  checksum: b7f70926f64c996fb9a9dd0993a2b90f516fd01e5dab2841ff4f2a66cabf901cdb08c14698e3e1daf02cbcdd3170ab1736a6ad65fe7c1ffcfab2543b290a201d
+  languageName: node
+  linkType: hard
+
 "@popperjs/core@npm:^2.9.0":
   version: 2.11.6
   resolution: "@popperjs/core@npm:2.11.6"
 "@popperjs/core@npm:^2.9.0":
   version: 2.11.6
   resolution: "@popperjs/core@npm:2.11.6"
@@ -2268,6 +2693,46 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@rollup/plugin-node-resolve@npm:^7.1.1":
+  version: 7.1.3
+  resolution: "@rollup/plugin-node-resolve@npm:7.1.3"
+  dependencies:
+    "@rollup/pluginutils": ^3.0.8
+    "@types/resolve": 0.0.8
+    builtin-modules: ^3.1.0
+    is-module: ^1.0.0
+    resolve: ^1.14.2
+  peerDependencies:
+    rollup: ^1.20.0||^2.0.0
+  checksum: e787c35f123652762d212b63f8cfaf577307434a935466397021c31b71d0d94357c6fa4e326b49bf44b959e22e41bc21f5648470eabec086566e7c36c5d041b1
+  languageName: node
+  linkType: hard
+
+"@rollup/plugin-replace@npm:^2.3.1":
+  version: 2.4.2
+  resolution: "@rollup/plugin-replace@npm:2.4.2"
+  dependencies:
+    "@rollup/pluginutils": ^3.1.0
+    magic-string: ^0.25.7
+  peerDependencies:
+    rollup: ^1.20.0 || ^2.0.0
+  checksum: b2f1618ee5526d288e2f8ae328dcb326e20e8dc8bd1f60d3e14d6708a5832e4aa44811f7d493f4aed2deeadca86e3b6b0503cd39bf50cfb4b595bb9da027fad0
+  languageName: node
+  linkType: hard
+
+"@rollup/pluginutils@npm:^3.0.8, @rollup/pluginutils@npm:^3.1.0":
+  version: 3.1.0
+  resolution: "@rollup/pluginutils@npm:3.1.0"
+  dependencies:
+    "@types/estree": 0.0.39
+    estree-walker: ^1.0.1
+    picomatch: ^2.2.2
+  peerDependencies:
+    rollup: ^1.20.0||^2.0.0
+  checksum: 8be16e27863c219edbb25a4e6ec2fe0e1e451d9e917b6a43cf2ae5bc025a6b8faaa40f82a6e53b66d0de37b58ff472c6c3d57a83037ae635041f8df959d6d9aa
+  languageName: node
+  linkType: hard
+
 "@sinonjs/commons@npm:^1, @sinonjs/commons@npm:^1.3.0, @sinonjs/commons@npm:^1.4.0, @sinonjs/commons@npm:^1.7.0":
   version: 1.8.3
   resolution: "@sinonjs/commons@npm:1.8.3"
 "@sinonjs/commons@npm:^1, @sinonjs/commons@npm:^1.3.0, @sinonjs/commons@npm:^1.4.0, @sinonjs/commons@npm:^1.7.0":
   version: 1.8.3
   resolution: "@sinonjs/commons@npm:1.8.3"
@@ -2295,6 +2760,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@sinonjs/fake-timers@npm:^6.0.1":
+  version: 6.0.1
+  resolution: "@sinonjs/fake-timers@npm:6.0.1"
+  dependencies:
+    "@sinonjs/commons": ^1.7.0
+  checksum: 8e331aa1412d905ecc8efd63550f58a6f77dcb510f878172004e53be63eb82650623618763001a918fc5e21257b86c45041e4e97c454ed6a2d187de084abbd11
+  languageName: node
+  linkType: hard
+
 "@sinonjs/formatio@npm:^3.2.1":
   version: 3.2.2
   resolution: "@sinonjs/formatio@npm:3.2.2"
 "@sinonjs/formatio@npm:^3.2.1":
   version: 3.2.2
   resolution: "@sinonjs/formatio@npm:3.2.2"
@@ -2323,134 +2797,144 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-add-jsx-attribute@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:4.2.0"
-  checksum: 3e67319517c4dbed247ca1c28050028fd8990d0745d28424c70db0999143b8e19f2dba563546e1acb842e89d4732149257462b432d6e8935712eba935c5928c3
+"@surma/rollup-plugin-off-main-thread@npm:^1.1.1":
+  version: 1.4.2
+  resolution: "@surma/rollup-plugin-off-main-thread@npm:1.4.2"
+  dependencies:
+    ejs: ^2.6.1
+    magic-string: ^0.25.0
+  checksum: da721792036a0e1253911f9b5280e6cb236024d7d2255bde3b6e87587c0ea8f46404224c8c032a27ee11ab3244eda752587fb37ec78c2e64eb53e10557373102
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-remove-jsx-attribute@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:4.2.0"
-  checksum: cc831c7b77a333548771190bcb50ad5f121c4cd5f397a7628b6c14df93a69e89a1d4a0b36b0bceda91f46c6fed3074406851252368aa6772248b1023f1b903f0
+"@svgr/babel-plugin-add-jsx-attribute@npm:^5.4.0":
+  version: 5.4.0
+  resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:5.4.0"
+  checksum: 1c538cf312b486598c6aea17f9b72d7fc308eb5dd32effd804630206a185493b8a828ff980ceb29d57d8319c085614c7cea967be709c71ae77702a4c30037011
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:4.2.0"
-  checksum: dedd4c4b9947b44daa02ab7846f8931463f70eca62bc58a16ffec2dd3538ec12e4e654ce7f4fcea79f88f176bba4548352035a5da0e7bc56c6197d44e0e0bd9c
+"@svgr/babel-plugin-remove-jsx-attribute@npm:^5.4.0":
+  version: 5.4.0
+  resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:5.4.0"
+  checksum: ad2231bfcb14daa944201df66236c222cde05a07c4cffaecab1d36d33f606b6caf17bda21844fc435780c1a27195e49beb8397536fe5e7545dfffcfbbcecb7f8
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:4.2.0"
-  checksum: 5c0af9239454ddfa5cadceff6a5292e04601ddb60ccaa78197ff825b9577d868b277d22225be31118926249e79f12f5dc44c03e284838325230e421c98d497ed
+"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^5.0.1":
+  version: 5.0.1
+  resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:5.0.1"
+  checksum: 175c8f13ddcb0744f7c3910ebed3799cfb961a75bff130e1ed2071c87ca8b8df8964825c988e511b2e3c5dbf48ad3d4fbbb6989edc53294253df40cf2a24375e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-svg-dynamic-title@npm:^4.3.3":
-  version: 4.3.3
-  resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:4.3.3"
-  checksum: 401964bb8aa4bcc9fab5f3eca4b73099f6c8a984b791a1b97a5544d6da1108f92ddc32275de8e5a12e330f129532ded6a804efcda20338b2062ce3087309f317
+"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^5.0.1":
+  version: 5.0.1
+  resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:5.0.1"
+  checksum: 68f4e2a5b95eca44e22fce485dc2ddd10adabe2b38f6db3ef9071b35e84bf379685f7acab6c05b7a82f722328c02f6424f8252c6dd5c2c4ed2f00104072b1dfe
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-svg-em-dimensions@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:4.2.0"
-  checksum: f3a86e2e29d1bc67a42bf80240680be5ca59219bc63193e517619385c9888a13eb369cc97bdecbed16b392db7f3faa56cf397f1be215e2ce27316249f9deff97
+"@svgr/babel-plugin-svg-dynamic-title@npm:^5.4.0":
+  version: 5.4.0
+  resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:5.4.0"
+  checksum: c46feb52454acea32031d1d881a81334f2e5f838ed25a2d9014acb5e9541d404405911e86dbee8bee9f1e43c9e07118123a07dc297962dbed0c4c5a86bdc4be9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-transform-react-native-svg@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:4.2.0"
-  checksum: b847d9356fd67844bdb0bf1492f84e3a2adc17ee3e89d3fb26734382155d5b192a5575114d3aafb5bc2e364e4e536ce56c25c92aba1c4c08cef7a5441922cfa4
+"@svgr/babel-plugin-svg-em-dimensions@npm:^5.4.0":
+  version: 5.4.0
+  resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:5.4.0"
+  checksum: 0d19b26147bbba932bd973258dab4a80a7ea6b9d674713186f0e10fa21a9e3aa4327326b2bf1892e8051712bce0ea30561eb187ca27bb241d33c350cea51ac88
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-plugin-transform-svg-component@npm:^4.2.0":
-  version: 4.2.0
-  resolution: "@svgr/babel-plugin-transform-svg-component@npm:4.2.0"
-  checksum: 17084831fb03b78d868155f24e47c6d9a92215a7519d17604cdd000f4a2f873ad16c499a7b421c7e2577e7e5ac3dfe02eb693965881a65fe141ad57600e117e1
+"@svgr/babel-plugin-transform-react-native-svg@npm:^5.4.0":
+  version: 5.4.0
+  resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:5.4.0"
+  checksum: 8ac5dc9fb2dee24addc74dbcb169860c95a69247606f986eabb0618fb300dd08e8f220891b758e62c051428ba04d8dd50f2c2bf877e15fa190e6d384d1ccd2ad
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/babel-preset@npm:^4.3.3":
-  version: 4.3.3
-  resolution: "@svgr/babel-preset@npm:4.3.3"
+"@svgr/babel-plugin-transform-svg-component@npm:^5.5.0":
+  version: 5.5.0
+  resolution: "@svgr/babel-plugin-transform-svg-component@npm:5.5.0"
+  checksum: 94c3fed490deb8544af4ea32a5d78a840334cdcc8a5a33fe8ea9f1c220a4d714d57c9e10934492de99b7e1acc17963b1749a49927e27b1e839a4dc3c893605c7
+  languageName: node
+  linkType: hard
+
+"@svgr/babel-preset@npm:^5.5.0":
+  version: 5.5.0
+  resolution: "@svgr/babel-preset@npm:5.5.0"
   dependencies:
   dependencies:
-    "@svgr/babel-plugin-add-jsx-attribute": ^4.2.0
-    "@svgr/babel-plugin-remove-jsx-attribute": ^4.2.0
-    "@svgr/babel-plugin-remove-jsx-empty-expression": ^4.2.0
-    "@svgr/babel-plugin-replace-jsx-attribute-value": ^4.2.0
-    "@svgr/babel-plugin-svg-dynamic-title": ^4.3.3
-    "@svgr/babel-plugin-svg-em-dimensions": ^4.2.0
-    "@svgr/babel-plugin-transform-react-native-svg": ^4.2.0
-    "@svgr/babel-plugin-transform-svg-component": ^4.2.0
-  checksum: a1ccdd34ac96ecb73f8ebb02a111602935b59cfa824fa9b9c20c38bc88bb9f176caab602f81c1dc3b00b0d56795ebc3d10153e97466291345cfc8eaf92e13d5f
+    "@svgr/babel-plugin-add-jsx-attribute": ^5.4.0
+    "@svgr/babel-plugin-remove-jsx-attribute": ^5.4.0
+    "@svgr/babel-plugin-remove-jsx-empty-expression": ^5.0.1
+    "@svgr/babel-plugin-replace-jsx-attribute-value": ^5.0.1
+    "@svgr/babel-plugin-svg-dynamic-title": ^5.4.0
+    "@svgr/babel-plugin-svg-em-dimensions": ^5.4.0
+    "@svgr/babel-plugin-transform-react-native-svg": ^5.4.0
+    "@svgr/babel-plugin-transform-svg-component": ^5.5.0
+  checksum: 5d396c4499c9ff2df9db6d08a160d10386b9f459cb9c2bb5ee183ab03b2f46c8ef3c9a070f1eee93f4e4433a5f00704e7632b1386078eb697ad8a2b38edb8522
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/core@npm:^4.3.3":
-  version: 4.3.3
-  resolution: "@svgr/core@npm:4.3.3"
+"@svgr/core@npm:^5.4.0":
+  version: 5.5.0
+  resolution: "@svgr/core@npm:5.5.0"
   dependencies:
   dependencies:
-    "@svgr/plugin-jsx": ^4.3.3
-    camelcase: ^5.3.1
-    cosmiconfig: ^5.2.1
-  checksum: 014c7dae4e1523ffdb6662a7396975476b802614d5477780b71e292c2fe789faa3e0572ce845b3dbd45098b0e3affdfc63dea742e316c5d1bac2d2c77afd8838
+    "@svgr/plugin-jsx": ^5.5.0
+    camelcase: ^6.2.0
+    cosmiconfig: ^7.0.0
+  checksum: 39b230151e30b9ca8551d10674e50efb821d1a49ce10969b09587af130780eba581baa1e321b0922f48331943096f05590aa6ae92d88d011d58093a89dd34158
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/hast-util-to-babel-ast@npm:^4.3.2":
-  version: 4.3.2
-  resolution: "@svgr/hast-util-to-babel-ast@npm:4.3.2"
+"@svgr/hast-util-to-babel-ast@npm:^5.5.0":
+  version: 5.5.0
+  resolution: "@svgr/hast-util-to-babel-ast@npm:5.5.0"
   dependencies:
   dependencies:
-    "@babel/types": ^7.4.4
-  checksum: 0d68084731afd1818ddbaecfc9201a24e10370f88c894eaaf48da9c4db93cd4bf5b7a8e03d1fcd4446165718e5ee124450ecab9f9a22208f87b2fa223ea6d3ca
+    "@babel/types": ^7.12.6
+  checksum: a03c1c7ab92b1a6dbd7671b0b78df4c07e8d808ff092671554a78752ec0c0425c03b6c82569a5f33903d191c73379eedf631f23aeb30b7a70185f5f2fc67fae6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/plugin-jsx@npm:^4.3.3":
-  version: 4.3.3
-  resolution: "@svgr/plugin-jsx@npm:4.3.3"
+"@svgr/plugin-jsx@npm:^5.4.0, @svgr/plugin-jsx@npm:^5.5.0":
+  version: 5.5.0
+  resolution: "@svgr/plugin-jsx@npm:5.5.0"
   dependencies:
   dependencies:
-    "@babel/core": ^7.4.5
-    "@svgr/babel-preset": ^4.3.3
-    "@svgr/hast-util-to-babel-ast": ^4.3.2
-    svg-parser: ^2.0.0
-  checksum: 880ae8c6b841c84a71ef3b1b6954089925f4b5f4a1f31767b2ce9004d7f72bfff7d66af05099a3e48612f10b242206d97a0991d366f3648c3e8df5c63cf665f5
+    "@babel/core": ^7.12.3
+    "@svgr/babel-preset": ^5.5.0
+    "@svgr/hast-util-to-babel-ast": ^5.5.0
+    svg-parser: ^2.0.2
+  checksum: e053f8dd6bfcd72377b432dd5b1db3c89d503d29839639a87f85b597a680d0b69e33a4db376f5a1074a89615f7157cd36f63f94bdb4083a0fd5bbe918c7fcb9b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/plugin-svgo@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@svgr/plugin-svgo@npm:4.3.1"
+"@svgr/plugin-svgo@npm:^5.4.0":
+  version: 5.5.0
+  resolution: "@svgr/plugin-svgo@npm:5.5.0"
   dependencies:
   dependencies:
-    cosmiconfig: ^5.2.1
-    merge-deep: ^3.0.2
+    cosmiconfig: ^7.0.0
+    deepmerge: ^4.2.2
     svgo: ^1.2.2
     svgo: ^1.2.2
-  checksum: 8d68f29d9c7d9c00fc079de25b58e0f83365cddc0e079e792ec9d76c7a676269029d22466aa9ab8493f0794130711fb6f20e9779cfa197f84da20285c37f2a3c
+  checksum: bef5d09581349afdf654209f82199670649cc749b81ff5f310ce4a3bbad749cde877c9b1a711dd9ced51224e2b5b5a720d242bdf183fa0f83e08e8d5e069b0b6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@svgr/webpack@npm:4.3.3":
-  version: 4.3.3
-  resolution: "@svgr/webpack@npm:4.3.3"
-  dependencies:
-    "@babel/core": ^7.4.5
-    "@babel/plugin-transform-react-constant-elements": ^7.0.0
-    "@babel/preset-env": ^7.4.5
-    "@babel/preset-react": ^7.0.0
-    "@svgr/core": ^4.3.3
-    "@svgr/plugin-jsx": ^4.3.3
-    "@svgr/plugin-svgo": ^4.3.1
-    loader-utils: ^1.2.3
-  checksum: e5ec59ee492c73c26cd22220ac1038fb61681cb22048485aa68edf850328be6effe93900fbb60dab735fad7e6939a598c5c9fe46768c1cb74c1b3a3330555e43
+"@svgr/webpack@npm:5.4.0":
+  version: 5.4.0
+  resolution: "@svgr/webpack@npm:5.4.0"
+  dependencies:
+    "@babel/core": ^7.9.0
+    "@babel/plugin-transform-react-constant-elements": ^7.9.0
+    "@babel/preset-env": ^7.9.5
+    "@babel/preset-react": ^7.9.4
+    "@svgr/core": ^5.4.0
+    "@svgr/plugin-jsx": ^5.4.0
+    "@svgr/plugin-svgo": ^5.4.0
+    loader-utils: ^2.0.0
+  checksum: f814b0eb4106ce7e9f0df3ed07969f11d435e82a331d76a1bfde6de7614b78591d2e9dce4683e5c7a121d427c2ce9bade542d2256aee33a62aa14581e243f556
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -2468,16 +2952,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@types/babel__core@npm:^7.1.0":
-  version: 7.1.14
-  resolution: "@types/babel__core@npm:7.1.14"
+"@types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.1.7":
+  version: 7.20.5
+  resolution: "@types/babel__core@npm:7.20.5"
   dependencies:
   dependencies:
-    "@babel/parser": ^7.1.0
-    "@babel/types": ^7.0.0
+    "@babel/parser": ^7.20.7
+    "@babel/types": ^7.20.7
     "@types/babel__generator": "*"
     "@types/babel__template": "*"
     "@types/babel__traverse": "*"
     "@types/babel__generator": "*"
     "@types/babel__template": "*"
     "@types/babel__traverse": "*"
-  checksum: de4a1a4905e4fb66e9b5ea185704b209892fa104b6aec8705021a3ddf0ff017234c41a1b0bffb0acf2c361afd5352c2d216e3548c8a702ba2558ab63f0bf2200
+  checksum: a3226f7930b635ee7a5e72c8d51a357e799d19cbf9d445710fa39ab13804f79ab1a54b72ea7d8e504659c7dfc50675db974b526142c754398d7413aa4bc30845
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -2509,6 +2993,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/babel__traverse@npm:^7.0.4":
+  version: 7.20.5
+  resolution: "@types/babel__traverse@npm:7.20.5"
+  dependencies:
+    "@babel/types": ^7.20.7
+  checksum: 608e0ab4fc31cd47011d98942e6241b34d461608c0c0e153377c5fd822c436c475f1ded76a56bfa76a1adf8d9266b727bbf9bfac90c4cb152c97f30dadc5b7e8
+  languageName: node
+  linkType: hard
+
 "@types/cheerio@npm:*":
   version: 0.22.29
   resolution: "@types/cheerio@npm:0.22.29"
 "@types/cheerio@npm:*":
   version: 0.22.29
   resolution: "@types/cheerio@npm:0.22.29"
@@ -2570,6 +3063,30 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/eslint@npm:^7.29.0":
+  version: 7.29.0
+  resolution: "@types/eslint@npm:7.29.0"
+  dependencies:
+    "@types/estree": "*"
+    "@types/json-schema": "*"
+  checksum: df13991c554954353ce8f3bb03e19da6cc71916889443d68d178d4f858b561ba4cc4a4f291c6eb9eebb7f864b12b9b9313051b3a8dfea3e513dadf3188a77bdf
+  languageName: node
+  linkType: hard
+
+"@types/estree@npm:*":
+  version: 1.0.5
+  resolution: "@types/estree@npm:1.0.5"
+  checksum: dd8b5bed28e6213b7acd0fb665a84e693554d850b0df423ac8076cc3ad5823a6bc26b0251d080bdc545af83179ede51dd3f6fa78cad2c46ed1f29624ddf3e41a
+  languageName: node
+  linkType: hard
+
+"@types/estree@npm:0.0.39":
+  version: 0.0.39
+  resolution: "@types/estree@npm:0.0.39"
+  checksum: 412fb5b9868f2c418126451821833414189b75cc6bf84361156feed733e3d92ec220b9d74a89e52722e03d5e241b2932732711b7497374a404fad49087adc248
+  languageName: node
+  linkType: hard
+
 "@types/file-saver@npm:2.0.0":
   version: 2.0.0
   resolution: "@types/file-saver@npm:2.0.0"
 "@types/file-saver@npm:2.0.0":
   version: 2.0.0
   resolution: "@types/file-saver@npm:2.0.0"
@@ -2587,6 +3104,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/graceful-fs@npm:^4.1.2":
+  version: 4.1.9
+  resolution: "@types/graceful-fs@npm:4.1.9"
+  dependencies:
+    "@types/node": "*"
+  checksum: 79d746a8f053954bba36bd3d94a90c78de995d126289d656fb3271dd9f1229d33f678da04d10bce6be440494a5a73438e2e363e92802d16b8315b051036c5256
+  languageName: node
+  linkType: hard
+
 "@types/history@npm:*":
   version: 4.7.8
   resolution: "@types/history@npm:4.7.8"
 "@types/history@npm:*":
   version: 4.7.8
   resolution: "@types/history@npm:4.7.8"
@@ -2594,6 +3120,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/html-minifier-terser@npm:^5.0.0":
+  version: 5.1.2
+  resolution: "@types/html-minifier-terser@npm:5.1.2"
+  checksum: 4bca779c44d2aebe4cc4036c5db370abe7466249038e9c5996cb3c192debeff1c75b7a2ab78e5fd2a014ad24ebf0f357f9a174a4298540dc1e1317d43aa69cfa
+  languageName: node
+  linkType: hard
+
 "@types/is-image@npm:3.0.0":
   version: 3.0.0
   resolution: "@types/is-image@npm:3.0.0"
 "@types/is-image@npm:3.0.0":
   version: 3.0.0
   resolution: "@types/is-image@npm:3.0.0"
@@ -2610,6 +3143,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/istanbul-lib-coverage@npm:^2.0.1":
+  version: 2.0.6
+  resolution: "@types/istanbul-lib-coverage@npm:2.0.6"
+  checksum: 3feac423fd3e5449485afac999dcfcb3d44a37c830af898b689fadc65d26526460bedb889db278e0d4d815a670331796494d073a10ee6e3a6526301fe7415778
+  languageName: node
+  linkType: hard
+
 "@types/istanbul-lib-report@npm:*":
   version: 3.0.0
   resolution: "@types/istanbul-lib-report@npm:3.0.0"
 "@types/istanbul-lib-report@npm:*":
   version: 3.0.0
   resolution: "@types/istanbul-lib-report@npm:3.0.0"
@@ -2619,16 +3159,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@types/istanbul-reports@npm:^1.1.1":
-  version: 1.1.2
-  resolution: "@types/istanbul-reports@npm:1.1.2"
-  dependencies:
-    "@types/istanbul-lib-coverage": "*"
-    "@types/istanbul-lib-report": "*"
-  checksum: 00866e815d1e68d0a590d691506937b79d8d65ad8eab5ed34dbfee66136c7c0f4ea65327d32046d5fe469f22abea2b294987591dc66365ebc3991f7e413b2d78
-  languageName: node
-  linkType: hard
-
 "@types/istanbul-reports@npm:^3.0.0":
   version: 3.0.1
   resolution: "@types/istanbul-reports@npm:3.0.1"
 "@types/istanbul-reports@npm:^3.0.0":
   version: 3.0.1
   resolution: "@types/istanbul-reports@npm:3.0.1"
@@ -2655,13 +3185,27 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.7":
+"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.3, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8":
+  version: 7.0.15
+  resolution: "@types/json-schema@npm:7.0.15"
+  checksum: 97ed0cb44d4070aecea772b7b2e2ed971e10c81ec87dd4ecc160322ffa55ff330dace1793489540e3e318d90942064bb697cc0f8989391797792d919737b3b98
+  languageName: node
+  linkType: hard
+
+"@types/json-schema@npm:^7.0.5":
   version: 7.0.7
   resolution: "@types/json-schema@npm:7.0.7"
   checksum: ea3b409235862d28122751158f4054e729e31ad844bd7b8b23868f38c518047b1c0e8e4e7cc293e02c31a2fb8cfc8a4506c2de2a745cf78b218e064fb8898cd4
   languageName: node
   linkType: hard
 
   version: 7.0.7
   resolution: "@types/json-schema@npm:7.0.7"
   checksum: ea3b409235862d28122751158f4054e729e31ad844bd7b8b23868f38c518047b1c0e8e4e7cc293e02c31a2fb8cfc8a4506c2de2a745cf78b218e064fb8898cd4
   languageName: node
   linkType: hard
 
+"@types/json5@npm:^0.0.29":
+  version: 0.0.29
+  resolution: "@types/json5@npm:0.0.29"
+  checksum: e60b153664572116dfea673c5bda7778dbff150498f44f998e34b5886d8afc47f16799280e4b6e241c0472aef1bc36add771c569c68fc5125fc2ae519a3eb9ac
+  languageName: node
+  linkType: hard
+
 "@types/jss@npm:^9.5.6":
   version: 9.5.8
   resolution: "@types/jss@npm:9.5.8"
 "@types/jss@npm:^9.5.6":
   version: 9.5.8
   resolution: "@types/jss@npm:9.5.8"
@@ -2730,6 +3274,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/prettier@npm:^2.0.0":
+  version: 2.7.3
+  resolution: "@types/prettier@npm:2.7.3"
+  checksum: 705384209cea6d1433ff6c187c80dcc0b95d99d5c5ce21a46a9a58060c527973506822e428789d842761e0280d25e3359300f017fbe77b9755bc772ab3dc2f83
+  languageName: node
+  linkType: hard
+
 "@types/prop-types@npm:*":
   version: 15.7.3
   resolution: "@types/prop-types@npm:15.7.3"
 "@types/prop-types@npm:*":
   version: 15.7.3
   resolution: "@types/prop-types@npm:15.7.3"
@@ -2909,6 +3460,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/resolve@npm:0.0.8":
+  version: 0.0.8
+  resolution: "@types/resolve@npm:0.0.8"
+  dependencies:
+    "@types/node": "*"
+  checksum: f241bb773ab14b14500623ac3b57c52006ce32b20426b6d8bf2fe5fdc0344f42c77ac0f94ff57b443ae1d320a1a86c62b4e47239f0321699404402fbeb24bad6
+  languageName: node
+  linkType: hard
+
 "@types/scheduler@npm:*":
   version: 0.16.1
   resolution: "@types/scheduler@npm:0.16.1"
 "@types/scheduler@npm:*":
   version: 0.16.1
   resolution: "@types/scheduler@npm:0.16.1"
@@ -2944,10 +3504,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@types/stack-utils@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "@types/stack-utils@npm:1.0.1"
-  checksum: 9dc052b575acfeca3f165fb19d87b7b2989d54ed7d64a7eeb0b7587bc5795ef1f2c2b1511a44dcf0831ef35b8ce3486f97fcbfdd50c01f68aa297de31502c9d9
+"@types/source-list-map@npm:*":
+  version: 0.1.6
+  resolution: "@types/source-list-map@npm:0.1.6"
+  checksum: 9cd294c121f1562062de5d241fe4d10780b1131b01c57434845fe50968e9dcf67ede444591c2b1ad6d3f9b6bc646ac02cc8f51a3577c795f9c64cf4573dcc6b1
+  languageName: node
+  linkType: hard
+
+"@types/stack-utils@npm:^2.0.0":
+  version: 2.0.3
+  resolution: "@types/stack-utils@npm:2.0.3"
+  checksum: 72576cc1522090fe497337c2b99d9838e320659ac57fa5560fcbdcbafcf5d0216c6b3a0a8a4ee4fdb3b1f5e3420aa4f6223ab57b82fef3578bec3206425c6cf5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -2958,6 +3525,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/tapable@npm:^1, @types/tapable@npm:^1.0.5":
+  version: 1.0.12
+  resolution: "@types/tapable@npm:1.0.12"
+  checksum: 5312fbc01e0135bd11b44cfea2bf29943807cd9675c10bbed13873ad0e73f656993fb88bb6ceaf05b12a55c570e6acc0267faf59e9c4d2f032fc833bafcf0597
+  languageName: node
+  linkType: hard
+
 "@types/trusted-types@npm:*":
   version: 2.0.4
   resolution: "@types/trusted-types@npm:2.0.4"
 "@types/trusted-types@npm:*":
   version: 2.0.4
   resolution: "@types/trusted-types@npm:2.0.4"
@@ -2965,6 +3539,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"@types/uglify-js@npm:*":
+  version: 3.17.5
+  resolution: "@types/uglify-js@npm:3.17.5"
+  dependencies:
+    source-map: ^0.6.1
+  checksum: ffed5d63637c6ea5c155469121ee40d9b652e677e6d9eb07b72ff72bb4029ffad19049a0af6e91a5021bad6c481cff2572fbf6367e319c6885cf1537c20d861d
+  languageName: node
+  linkType: hard
+
 "@types/uuid@npm:3.4.4":
   version: 3.4.4
   resolution: "@types/uuid@npm:3.4.4"
 "@types/uuid@npm:3.4.4":
   version: 3.4.4
   resolution: "@types/uuid@npm:3.4.4"
@@ -2974,19 +3557,35 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@types/yargs-parser@npm:*":
-  version: 20.2.0
-  resolution: "@types/yargs-parser@npm:20.2.0"
-  checksum: 54cf3f8d2c7db47e200e8c96e05b3b33ee554e78d29f3db55f04ca4b86dc6b8ff6b1349f5772268ce2d365cde0a0f4fdd92bf5933c2be2c1ea3f19f0b4599e1f
+"@types/webpack-sources@npm:*":
+  version: 3.2.3
+  resolution: "@types/webpack-sources@npm:3.2.3"
+  dependencies:
+    "@types/node": "*"
+    "@types/source-list-map": "*"
+    source-map: ^0.7.3
+  checksum: 7b557f242efaa10e4e3e18cc4171a0c98e22898570caefdd4f7b076fe8534b5abfac92c953c6604658dcb7218507f970230352511840fe9fdea31a9af3b9a906
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@types/yargs@npm:^13.0.0":
-  version: 13.0.11
-  resolution: "@types/yargs@npm:13.0.11"
+"@types/webpack@npm:^4.41.8":
+  version: 4.41.38
+  resolution: "@types/webpack@npm:4.41.38"
   dependencies:
   dependencies:
-    "@types/yargs-parser": "*"
-  checksum: efcbcccd20eab773970c2f103efaf69901924ab3bfc69cc5603ece0be7626937242b2f952b7ebc3708c121f8507e1d0633eb4cc04843433bf3d8b133b83bb811
+    "@types/node": "*"
+    "@types/tapable": ^1
+    "@types/uglify-js": "*"
+    "@types/webpack-sources": "*"
+    anymatch: ^3.0.0
+    source-map: ^0.6.0
+  checksum: d3de65993ef3a7621f75548c2f6f509e8f87f586032238e999743d6067030655c67e38ec5f8b32e04fa5276c83bdfb7a761773bce0e6f28605da87e3fc388e3e
+  languageName: node
+  linkType: hard
+
+"@types/yargs-parser@npm:*":
+  version: 20.2.0
+  resolution: "@types/yargs-parser@npm:20.2.0"
+  checksum: 54cf3f8d2c7db47e200e8c96e05b3b33ee554e78d29f3db55f04ca4b86dc6b8ff6b1349f5772268ce2d365cde0a0f4fdd92bf5933c2be2c1ea3f19f0b4599e1f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -3017,14 +3616,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@typescript-eslint/eslint-plugin@npm:^2.10.0":
-  version: 4.28.0
-  resolution: "@typescript-eslint/eslint-plugin@npm:4.28.0"
+"@typescript-eslint/eslint-plugin@npm:^4.5.0":
+  version: 4.33.0
+  resolution: "@typescript-eslint/eslint-plugin@npm:4.33.0"
   dependencies:
   dependencies:
-    "@typescript-eslint/experimental-utils": 4.28.0
-    "@typescript-eslint/scope-manager": 4.28.0
+    "@typescript-eslint/experimental-utils": 4.33.0
+    "@typescript-eslint/scope-manager": 4.33.0
     debug: ^4.3.1
     functional-red-black-tree: ^1.0.1
     debug: ^4.3.1
     functional-red-black-tree: ^1.0.1
+    ignore: ^5.1.8
     regexpp: ^3.1.0
     semver: ^7.3.5
     tsutils: ^3.21.0
     regexpp: ^3.1.0
     semver: ^7.3.5
     tsutils: ^3.21.0
@@ -3034,66 +3634,107 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: fb52430e3a219e45c014c8a8407ccd2830da45e6fc134d0138b99faf45e580d3ad9a3405cad98726183779d94640366c532e4519783a2a99fe072dc73705b8ad
+  checksum: d74855d0a5ffe0b2f362ec02fcd9301d39a53fb4155b9bd0cb15a0a31d065143129ebf98df9d86af4b6f74de1d423a4c0d8c0095520844068117453afda5bc4f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@typescript-eslint/experimental-utils@npm:4.28.0":
-  version: 4.28.0
-  resolution: "@typescript-eslint/experimental-utils@npm:4.28.0"
+"@typescript-eslint/experimental-utils@npm:4.33.0, @typescript-eslint/experimental-utils@npm:^4.0.1":
+  version: 4.33.0
+  resolution: "@typescript-eslint/experimental-utils@npm:4.33.0"
   dependencies:
     "@types/json-schema": ^7.0.7
   dependencies:
     "@types/json-schema": ^7.0.7
-    "@typescript-eslint/scope-manager": 4.28.0
-    "@typescript-eslint/types": 4.28.0
-    "@typescript-eslint/typescript-estree": 4.28.0
+    "@typescript-eslint/scope-manager": 4.33.0
+    "@typescript-eslint/types": 4.33.0
+    "@typescript-eslint/typescript-estree": 4.33.0
     eslint-scope: ^5.1.1
     eslint-utils: ^3.0.0
   peerDependencies:
     eslint: "*"
     eslint-scope: ^5.1.1
     eslint-utils: ^3.0.0
   peerDependencies:
     eslint: "*"
-  checksum: 29bcee0d581ad20043532b6b1fa0c2e844ab35a99aa67478fbb68b7be5889dc8aee1f52b72c3a51d8f4cf57e1f0ac664d92738b3eb5aea9aa8a8c72aa30a74b7
+  checksum: f859800ada0884f92db6856f24efcb1d073ac9883ddc2b1aa9339f392215487895bed8447ebce3741e8141bb32e545244abef62b73193ba9a8a0527c523aabae
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/experimental-utils@npm:^3.10.1":
+  version: 3.10.1
+  resolution: "@typescript-eslint/experimental-utils@npm:3.10.1"
+  dependencies:
+    "@types/json-schema": ^7.0.3
+    "@typescript-eslint/types": 3.10.1
+    "@typescript-eslint/typescript-estree": 3.10.1
+    eslint-scope: ^5.0.0
+    eslint-utils: ^2.0.0
+  peerDependencies:
+    eslint: "*"
+  checksum: 635cc1afe466088b04901c2bce0e4c3e48bb74668e61e39aa74a485f856c6f9683482350d4b16b3f4c0112ce40cad2c2c427d4fe5e11a3329b3bb93286d4ab26
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@typescript-eslint/parser@npm:^2.10.0":
-  version: 4.28.0
-  resolution: "@typescript-eslint/parser@npm:4.28.0"
+"@typescript-eslint/parser@npm:^4.5.0":
+  version: 4.33.0
+  resolution: "@typescript-eslint/parser@npm:4.33.0"
   dependencies:
   dependencies:
-    "@typescript-eslint/scope-manager": 4.28.0
-    "@typescript-eslint/types": 4.28.0
-    "@typescript-eslint/typescript-estree": 4.28.0
+    "@typescript-eslint/scope-manager": 4.33.0
+    "@typescript-eslint/types": 4.33.0
+    "@typescript-eslint/typescript-estree": 4.33.0
     debug: ^4.3.1
   peerDependencies:
     eslint: ^5.0.0 || ^6.0.0 || ^7.0.0
   peerDependenciesMeta:
     typescript:
       optional: true
     debug: ^4.3.1
   peerDependencies:
     eslint: ^5.0.0 || ^6.0.0 || ^7.0.0
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 08c167db5c36776a638afe52130e0bf20bfc94598da3f178171cfafb72703fc508b17667b4584cd6bea4c8c6d6922af8f7a45e9a7d6419f83fe2f6c893845d96
+  checksum: 102457eae1acd516211098fea081c8a2ed728522bbda7f5a557b6ef23d88970514f9a0f6285d53fca134d3d4d7d17822b5d5e12438d5918df4d1f89cc9e67d57
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@typescript-eslint/scope-manager@npm:4.28.0":
-  version: 4.28.0
-  resolution: "@typescript-eslint/scope-manager@npm:4.28.0"
+"@typescript-eslint/scope-manager@npm:4.33.0":
+  version: 4.33.0
+  resolution: "@typescript-eslint/scope-manager@npm:4.33.0"
   dependencies:
   dependencies:
-    "@typescript-eslint/types": 4.28.0
-    "@typescript-eslint/visitor-keys": 4.28.0
-  checksum: cdab7ef35e18cf2c9a25a4e588cb464bd7e6a3e3f9a7dceaa567fa04202b68901b5c9dc108c3ff7b92215c373274c9d105b8061ae97d2600039345c53d33a4ae
+    "@typescript-eslint/types": 4.33.0
+    "@typescript-eslint/visitor-keys": 4.33.0
+  checksum: 9a25fb7ba7c725ea7227a24d315b0f6aacbad002e2549a049edf723c1d3615c22f5c301f0d7d615b377f2cdf2f3519d97e79af0c459de6ef8d2aaf0906dff13e
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/types@npm:3.10.1":
+  version: 3.10.1
+  resolution: "@typescript-eslint/types@npm:3.10.1"
+  checksum: 3ea820d37c2595d457acd6091ffda8b531e5d916e1cce708336bf958aa8869126f95cca3268a724f453ce13be11c5388a0a4143bf09bca51be1020ec46635d92
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/types@npm:4.33.0":
+  version: 4.33.0
+  resolution: "@typescript-eslint/types@npm:4.33.0"
+  checksum: 3baae1ca35872421b4eb60f5d3f3f32dc1d513f2ae0a67dee28c7d159fd7a43ed0d11a8a5a0f0c2d38507ffa036fc7c511cb0f18a5e8ac524b3ebde77390ec53
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@typescript-eslint/types@npm:4.28.0":
-  version: 4.28.0
-  resolution: "@typescript-eslint/types@npm:4.28.0"
-  checksum: 5d167f32e93f5854e78315a3a59beaf45bf9fdc5dc6a01d02c22152c08c8871022b5c41281c5355f44be1f9d36f9d24a67087a5c0dcfeea25477eb5918b5c410
+"@typescript-eslint/typescript-estree@npm:3.10.1":
+  version: 3.10.1
+  resolution: "@typescript-eslint/typescript-estree@npm:3.10.1"
+  dependencies:
+    "@typescript-eslint/types": 3.10.1
+    "@typescript-eslint/visitor-keys": 3.10.1
+    debug: ^4.1.1
+    glob: ^7.1.6
+    is-glob: ^4.0.1
+    lodash: ^4.17.15
+    semver: ^7.3.2
+    tsutils: ^3.17.1
+  peerDependenciesMeta:
+    typescript:
+      optional: true
+  checksum: 911680da9d26220944f4f8f26f88349917609844fafcff566147cecae37ff0211d66c626eb62a2b24d17fd50d10715f5b0f32b2e7f5d9a88efc46709266d5053
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@typescript-eslint/typescript-estree@npm:4.28.0":
-  version: 4.28.0
-  resolution: "@typescript-eslint/typescript-estree@npm:4.28.0"
+"@typescript-eslint/typescript-estree@npm:4.33.0":
+  version: 4.33.0
+  resolution: "@typescript-eslint/typescript-estree@npm:4.33.0"
   dependencies:
   dependencies:
-    "@typescript-eslint/types": 4.28.0
-    "@typescript-eslint/visitor-keys": 4.28.0
+    "@typescript-eslint/types": 4.33.0
+    "@typescript-eslint/visitor-keys": 4.33.0
     debug: ^4.3.1
     globby: ^11.0.3
     is-glob: ^4.0.1
     debug: ^4.3.1
     globby: ^11.0.3
     is-glob: ^4.0.1
@@ -3102,199 +3743,207 @@ __metadata:
   peerDependenciesMeta:
     typescript:
       optional: true
   peerDependenciesMeta:
     typescript:
       optional: true
-  checksum: 680ec9a48cc702eba81a1a9101d6635fac10977c930cdb79a94c975b611c0276cc30e13f41630faeb28446cdcc0afb50bd389042629b35a6dd51b6d6774e0973
+  checksum: 2566984390c76bd95f43240057215c068c69769e406e27aba41e9f21fd300074d6772e4983fa58fe61e80eb5550af1548d2e31e80550d92ba1d051bb00fe6f5c
+  languageName: node
+  linkType: hard
+
+"@typescript-eslint/visitor-keys@npm:3.10.1":
+  version: 3.10.1
+  resolution: "@typescript-eslint/visitor-keys@npm:3.10.1"
+  dependencies:
+    eslint-visitor-keys: ^1.1.0
+  checksum: 0c4825b9829b1c11258a73aaee70d64834ba6d9b24157e7624e80f27f6537f468861d4dd33ad233c13ad2c6520afb9008c0675da6d792f26e82d75d6bfe9b0c6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@typescript-eslint/visitor-keys@npm:4.28.0":
-  version: 4.28.0
-  resolution: "@typescript-eslint/visitor-keys@npm:4.28.0"
+"@typescript-eslint/visitor-keys@npm:4.33.0":
+  version: 4.33.0
+  resolution: "@typescript-eslint/visitor-keys@npm:4.33.0"
   dependencies:
   dependencies:
-    "@typescript-eslint/types": 4.28.0
+    "@typescript-eslint/types": 4.33.0
     eslint-visitor-keys: ^2.0.0
     eslint-visitor-keys: ^2.0.0
-  checksum: a2f5cec756946d924e3a4f5e89d600da82d500690575620513eb700e9ab65226f62910a16ed3a2e1e9c46d7171ee6c5f23ff82e222dcd65e4b6e14f712534d71
+  checksum: 59953e474ad4610c1aa23b2b1a964445e2c6201521da6367752f37939d854352bbfced5c04ea539274065e012b1337ba3ffa49c2647a240a4e87155378ba9873
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/ast@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/ast@npm:1.8.5"
+"@webassemblyjs/ast@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/ast@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/helper-module-context": 1.8.5
-    "@webassemblyjs/helper-wasm-bytecode": 1.8.5
-    "@webassemblyjs/wast-parser": 1.8.5
-  checksum: eee2593fd09ea888ed5ed8919e681a479f9d997061f97264020c8d4b0be11ab3c4e8646463afd021c8c40f1d92023cad1a09559ccdef6e24fdb4290e021a368b
+    "@webassemblyjs/helper-module-context": 1.9.0
+    "@webassemblyjs/helper-wasm-bytecode": 1.9.0
+    "@webassemblyjs/wast-parser": 1.9.0
+  checksum: 8a9838dc7fdac358aee8daa75eefa35934ab18dafb594092ff7be79c467ebe9dabb2543e58313c905fd802bdcc3cb8320e4e19af7444e49853a7a24e25138f75
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/floating-point-hex-parser@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.8.5"
-  checksum: 68a1ff458355fb6b1553c8f7e2df6d76623bf5ef895f3fc30de620b88d1e68224643c8daf517d19b75d4e10a7f663c038b9912970edcae6f5a4fdb85b630bfc3
+"@webassemblyjs/floating-point-hex-parser@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.9.0"
+  checksum: d3aeb19bc30da26f639698daa28e44e0c18d5aa135359ef3c54148e194eec46451a912d0506099d479a71a94bc3eef6ef52d6ec234799528a25a9744789852de
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/helper-api-error@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/helper-api-error@npm:1.8.5"
-  checksum: 83e3c62a67f94cc36b2b8c4785aa92c15fc5d95a0e22c4b6c39dace583dd9c47c88bc4dea032a959b511d33db26eeb8de9b7be6f5d343f89ebdbd17c11520827
+"@webassemblyjs/helper-api-error@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/helper-api-error@npm:1.9.0"
+  checksum: 9179d3148639cc202e89a118145b485cf834613260679a99af6ec487bbc15f238566ca713207394b336160a41bf8c1b75cf2e853b3e96f0cc73c1e5c735b3f64
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/helper-buffer@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/helper-buffer@npm:1.8.5"
-  checksum: 5eeb48b135d5ca013c8876228a3a2ccfba98d87dfe12fcf6921e0acf7a272070f369e4e4e8a7f34f2cf22e8faaade24a39a9bcfba76498f103f051384b0f55b3
+"@webassemblyjs/helper-buffer@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/helper-buffer@npm:1.9.0"
+  checksum: dcb85f630f8a2e22b7346ad4dd58c3237a2cad1457699423e8fd19592a0bd3eacbc2639178a1b9a873c3ac217bfc7a23a134ff440a099496b590e82c7a4968d5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/helper-code-frame@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/helper-code-frame@npm:1.8.5"
+"@webassemblyjs/helper-code-frame@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/helper-code-frame@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/wast-printer": 1.8.5
-  checksum: 80ca0fdc18c39ba1fe3f139f657f62159b7269ca153f10c5b984f5140a83e3fb8c18565f4443afbce144622b9eb8d16034beb52efc91b69e1e107e15b9f5a6c6
+    "@webassemblyjs/wast-printer": 1.9.0
+  checksum: a28fa057f7beff0fd14bff716561520f8edb8c9c56c7a5559451e6765acfb70aaeb8af718ea2bd2262e7baeba597545af407e28eb2eff8329235afe8605f20d1
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/helper-fsm@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/helper-fsm@npm:1.8.5"
-  checksum: 5026861c39518cf7f8fa6a88ccad8e251d906130a9ccfe2a49da5eb5321bfdf0861f31e5269f76687259f96cc8143f09a9df73a3836c44cb5445bdc01f77fd91
+"@webassemblyjs/helper-fsm@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/helper-fsm@npm:1.9.0"
+  checksum: 374cc510c8f5a7a07d4fe9eb7036cc475a96a670b5d25c31f16757ac8295be8d03a2f29657ff53eaefa9e8315670a48824d430ed910e7c1835788ac79f93124e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/helper-module-context@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/helper-module-context@npm:1.8.5"
+"@webassemblyjs/helper-module-context@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/helper-module-context@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    mamacro: ^0.0.3
-  checksum: 519ff898993e9331b21bf22dbb85c91378f5c227e7075a4cd580c8e51dfd1372847c722b2e49564d2609b54e10283d68cad6d243b8a95d7833f60c6eb33a5259
+    "@webassemblyjs/ast": 1.9.0
+  checksum: 55e8f89c7ea1beaa78fad88403f3753b8413b0f3b6bb32d898ce95078b3e1d1b48ade0919c00b82fc2e3813c0ab6901e415f7a4d4fa9be50944e2431adde75a5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/helper-wasm-bytecode@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.8.5"
-  checksum: ac560cafe93e5ef07d892cea8ed5f1cb2b7cb8777a335fa92d99068eef650fbc37077e2ac8861bcaed337ac613db477741603554d9784373d41acaeffefd2c01
+"@webassemblyjs/helper-wasm-bytecode@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.9.0"
+  checksum: 280da4df3c556f73a1a02053277f8a4be481de32df4aa21050b015c8f4d27c46af89f0417eb88e486df117e5df4bccffae593f78cb1e79f212d3b3d4f3ed0f04
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/helper-wasm-section@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/helper-wasm-section@npm:1.8.5"
+"@webassemblyjs/helper-wasm-section@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/helper-wasm-section@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/helper-buffer": 1.8.5
-    "@webassemblyjs/helper-wasm-bytecode": 1.8.5
-    "@webassemblyjs/wasm-gen": 1.8.5
-  checksum: f8af22bf904d43d2700708bcb61ebfe1241cb57a4ef3e1400327c072d43b34bf5a04c39493a5d7691cca0590cec0cb0935cad7111764593cdb0840e637edac9d
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/helper-buffer": 1.9.0
+    "@webassemblyjs/helper-wasm-bytecode": 1.9.0
+    "@webassemblyjs/wasm-gen": 1.9.0
+  checksum: b8f7bb45d4194074c82210211a5d3e402a5b5fa63ecae26d2c356ae3978af5a530e91192fb260f32f9d561b18e2828b3da2e2f41c59efadb5f3c6d72446807f0
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/ieee754@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/ieee754@npm:1.8.5"
+"@webassemblyjs/ieee754@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/ieee754@npm:1.9.0"
   dependencies:
     "@xtuc/ieee754": ^1.2.0
   dependencies:
     "@xtuc/ieee754": ^1.2.0
-  checksum: 20230eb79e4bdf812f49ae73ca145a0a8d0fa1ec8a6353b5a36e57c1955ecc7245f277bfb1bf839e041fff7f300948d938b0672bae9d5764519ed0b6a6aa1bdb
+  checksum: 7fe4a217ba0f7051e2cfef92919d4a64fac1a63c65411763779bd50907820f33f440255231a474fe3ba03bd1d9ee0328662d1eae3fce4c59b91549d6b62b839b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/leb128@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/leb128@npm:1.8.5"
+"@webassemblyjs/leb128@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/leb128@npm:1.9.0"
   dependencies:
     "@xtuc/long": 4.2.2
   dependencies:
     "@xtuc/long": 4.2.2
-  checksum: c41603eba2d4052bf14e9213bfa80534dcbafacc782bd8faef80394cf2a93a4aece465416a5132aff2ec8339381003689850b72899828c236313e3653fb47214
+  checksum: 4ca7cbb869530d78d42a414f34ae53249364cb1ecebbfb6ed5d562c2f209fce857502f088822ee82a23876f653a262ddc34ab64e45a7962510a263d39bb3f51a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/utf8@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/utf8@npm:1.8.5"
-  checksum: 6aac4440996a160f268762a3dad1ef4a02f4d06fe3a7a0189556adbbbc34ed9ec54a2eadc2adb0aea2ba3430e9dbe20ab461df4f224eed73c9066904b17013e4
+"@webassemblyjs/utf8@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/utf8@npm:1.9.0"
+  checksum: e328a30ac8a503bbd015d32e75176e0dedcb45a21d4be051c25dfe89a00035ca7a6dbd8937b442dd5b4b334de3959d4f5fe0b330037bd226a28b9814cd49e84f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wasm-edit@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/wasm-edit@npm:1.8.5"
+"@webassemblyjs/wasm-edit@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/wasm-edit@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/helper-buffer": 1.8.5
-    "@webassemblyjs/helper-wasm-bytecode": 1.8.5
-    "@webassemblyjs/helper-wasm-section": 1.8.5
-    "@webassemblyjs/wasm-gen": 1.8.5
-    "@webassemblyjs/wasm-opt": 1.8.5
-    "@webassemblyjs/wasm-parser": 1.8.5
-    "@webassemblyjs/wast-printer": 1.8.5
-  checksum: 7298a60bd4914a7d13bceebce4a130f412056eb40a9d9a27d102bf173a0b369cb0d4be3303abcd08c9482695afe79080e896ace4f2ecabbb0247e2f1829fd4ca
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/helper-buffer": 1.9.0
+    "@webassemblyjs/helper-wasm-bytecode": 1.9.0
+    "@webassemblyjs/helper-wasm-section": 1.9.0
+    "@webassemblyjs/wasm-gen": 1.9.0
+    "@webassemblyjs/wasm-opt": 1.9.0
+    "@webassemblyjs/wasm-parser": 1.9.0
+    "@webassemblyjs/wast-printer": 1.9.0
+  checksum: 1997e0c2f4051c33239587fb143242919320bc861a0af03a873c7150a27d6404bd2e063c658193288b0aa88c35aadbe0c4fde601fe642bae0743a8c8eda52717
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wasm-gen@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/wasm-gen@npm:1.8.5"
+"@webassemblyjs/wasm-gen@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/wasm-gen@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/helper-wasm-bytecode": 1.8.5
-    "@webassemblyjs/ieee754": 1.8.5
-    "@webassemblyjs/leb128": 1.8.5
-    "@webassemblyjs/utf8": 1.8.5
-  checksum: d861e0233aff09e4841624f6554e32fc3056c232a2b3a9cf92bfcc3f9f34f850e240b6dec70977ae55afd5e5cea3e8d260292cccb1803dc26da4fdcee72b4637
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/helper-wasm-bytecode": 1.9.0
+    "@webassemblyjs/ieee754": 1.9.0
+    "@webassemblyjs/leb128": 1.9.0
+    "@webassemblyjs/utf8": 1.9.0
+  checksum: 2456e84e8e6bedb7ab47f6333a0ee170f7ef62842c90862ca787c08528ca8041061f3f8bc257fc2a01bf6e8d1a76fddaddd43418c738f681066e5b50f88fe7df
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wasm-opt@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/wasm-opt@npm:1.8.5"
+"@webassemblyjs/wasm-opt@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/wasm-opt@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/helper-buffer": 1.8.5
-    "@webassemblyjs/wasm-gen": 1.8.5
-    "@webassemblyjs/wasm-parser": 1.8.5
-  checksum: 44b18c328b919ba4510d58b4dfe6244edac8c21cd2b6cf7167ad58feb0ddc61217c98521b2f0ffc0e388a0b5469b060a6908e8cc7753ab72945204b4a87dd31b
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/helper-buffer": 1.9.0
+    "@webassemblyjs/wasm-gen": 1.9.0
+    "@webassemblyjs/wasm-parser": 1.9.0
+  checksum: 91242205bdbd1aa8045364a5338bfb34880cb2c65f56db8dd19382894209673699fb31a0e5279f25c7e5bcd8f3097d6c9ca84d8969d9613ef2cf166450cc3515
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wasm-parser@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/wasm-parser@npm:1.8.5"
+"@webassemblyjs/wasm-parser@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/wasm-parser@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/helper-api-error": 1.8.5
-    "@webassemblyjs/helper-wasm-bytecode": 1.8.5
-    "@webassemblyjs/ieee754": 1.8.5
-    "@webassemblyjs/leb128": 1.8.5
-    "@webassemblyjs/utf8": 1.8.5
-  checksum: ea80e9ba6d8f8ba7c3177eda500a41226b5a0373b92a32e8ce81b4562fd4bec37a67ff1a833a378a811307cf1ec4f54f44207c6bbc82fb45709a6cb60d86458f
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/helper-api-error": 1.9.0
+    "@webassemblyjs/helper-wasm-bytecode": 1.9.0
+    "@webassemblyjs/ieee754": 1.9.0
+    "@webassemblyjs/leb128": 1.9.0
+    "@webassemblyjs/utf8": 1.9.0
+  checksum: 493f6cfc63a5e16073056c81ff0526a9936f461327379ef3c83cc841000e03623b6352704f6bf9f7cb5b3610f0032020a61f9cca78c91b15b8e995854b29c098
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wast-parser@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/wast-parser@npm:1.8.5"
+"@webassemblyjs/wast-parser@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/wast-parser@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/floating-point-hex-parser": 1.8.5
-    "@webassemblyjs/helper-api-error": 1.8.5
-    "@webassemblyjs/helper-code-frame": 1.8.5
-    "@webassemblyjs/helper-fsm": 1.8.5
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/floating-point-hex-parser": 1.9.0
+    "@webassemblyjs/helper-api-error": 1.9.0
+    "@webassemblyjs/helper-code-frame": 1.9.0
+    "@webassemblyjs/helper-fsm": 1.9.0
     "@xtuc/long": 4.2.2
     "@xtuc/long": 4.2.2
-  checksum: ec0b28f0c558950024521808c1e12b4597023b0ef914aed0eb9078bcb4daaa34555a46efe35406b8edb899b008782b1a43d96c6485c45e98ce9745fe17196817
+  checksum: 705dd48fbbceec7f6bed299b8813631b242fd9312f9594dbb2985dda86c9688048692357d684f6080fc2c5666287cefaa26b263d01abadb6a9049d4c8978b9db
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wast-printer@npm:1.8.5":
-  version: 1.8.5
-  resolution: "@webassemblyjs/wast-printer@npm:1.8.5"
+"@webassemblyjs/wast-printer@npm:1.9.0":
+  version: 1.9.0
+  resolution: "@webassemblyjs/wast-printer@npm:1.9.0"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/wast-parser": 1.8.5
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/wast-parser": 1.9.0
     "@xtuc/long": 4.2.2
     "@xtuc/long": 4.2.2
-  checksum: 7c53f5f694b9820cef5e58653a85f5e9b0eba4e59013a2e0fcf3562d7e70501b0202d73ebadbd14b5845ecf958e3639bdde5a197a4245dded722f2015ec45e2a
+  checksum: 3d1e1b2e84745a963f69acd1c02425b321dd2e608e11dabc467cae0c9a808962bc769ec9afc46fbcea7188cc1e47d72370da762d258f716fb367cb1a7865c54b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -3312,10 +3961,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"abab@npm:^2.0.0":
-  version: 2.0.5
-  resolution: "abab@npm:2.0.5"
-  checksum: 0ec951b46d5418c2c2f923021ec193eaebdb4e802ffd5506286781b454be722a13a8430f98085cd3e204918401d9130ec6cc8f5ae19be315b3a0e857d83196e1
+"abab@npm:^2.0.3, abab@npm:^2.0.5":
+  version: 2.0.6
+  resolution: "abab@npm:2.0.6"
+  checksum: 6ffc1af4ff315066c62600123990d87551ceb0aafa01e6539da77b0f5987ac7019466780bf480f1787576d4385e3690c81ccc37cfda12819bf510b8ab47e5a3e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -3326,7 +3975,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.7":
+"accepts@npm:~1.3.4, accepts@npm:~1.3.5":
   version: 1.3.7
   resolution: "accepts@npm:1.3.7"
   dependencies:
   version: 1.3.7
   resolution: "accepts@npm:1.3.7"
   dependencies:
@@ -3336,42 +3985,43 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"acorn-globals@npm:^4.1.0, acorn-globals@npm:^4.3.0":
-  version: 4.3.4
-  resolution: "acorn-globals@npm:4.3.4"
+"accepts@npm:~1.3.8":
+  version: 1.3.8
+  resolution: "accepts@npm:1.3.8"
   dependencies:
   dependencies:
-    acorn: ^6.0.1
-    acorn-walk: ^6.0.1
-  checksum: c31bfde102d8a104835e9591c31dd037ec771449f9c86a6b1d2ac3c7c336694f828cfabba7687525b094f896a854affbf1afe6e1b12c0d998be6bab5d49c9663
+    mime-types: ~2.1.34
+    negotiator: 0.6.3
+  checksum: 50c43d32e7b50285ebe84b613ee4a3aa426715a7d131b65b786e2ead0fd76b6b60091b9916d3478a75f11f162628a2139991b6c03ab3f1d9ab7c86075dc8eab4
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"acorn-jsx@npm:^5.2.0":
-  version: 5.3.1
-  resolution: "acorn-jsx@npm:5.3.1"
-  peerDependencies:
-    acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
-  checksum: daf441a9d7b59c0ea1f7fe2934c48aca604a007455129ce35fa62ec3d4c8363e2efc2d4da636d18ce0049979260ba07d8b42bc002ae95182916d2c90901529c2
+"acorn-globals@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "acorn-globals@npm:6.0.0"
+  dependencies:
+    acorn: ^7.1.1
+    acorn-walk: ^7.1.1
+  checksum: 72d95e5b5e585f9acd019b993ab8bbba68bb3cbc9d9b5c1ebb3c2f1fe5981f11deababfb4949f48e6262f9c57878837f5958c0cca396f81023814680ca878042
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"acorn-walk@npm:^6.0.1":
-  version: 6.2.0
-  resolution: "acorn-walk@npm:6.2.0"
-  checksum: ea241a5d96338f1e8030aafae72a91ff0ec4360e2775e44a2fdb2eb618b07fc309e000a5126056631ac7f00fe8bd9bbd23fcb6d018eee4ba11086eb36c1b2e61
+"acorn-jsx@npm:^5.3.1":
+  version: 5.3.2
+  resolution: "acorn-jsx@npm:5.3.2"
+  peerDependencies:
+    acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+  checksum: c3d3b2a89c9a056b205b69530a37b972b404ee46ec8e5b341666f9513d3163e2a4f214a71f4dfc7370f5a9c07472d2fd1c11c91c3f03d093e37637d95da98950
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"acorn@npm:^5.5.3":
-  version: 5.7.4
-  resolution: "acorn@npm:5.7.4"
-  bin:
-    acorn: bin/acorn
-  checksum: f51392a4d25c7705fadb890f784c59cde4ac1c5452ccd569fa59bd2191b7951b4a6398348ab7ea08a54f0bc0a56c13776710f4e1bae9de441e4d33e2015ad1e0
+"acorn-walk@npm:^7.1.1":
+  version: 7.2.0
+  resolution: "acorn-walk@npm:7.2.0"
+  checksum: 9252158a79b9d92f1bc0dd6acc0fcfb87a67339e84bcc301bb33d6078936d27e35d606b4d35626d2962cd43c256d6f27717e70cbe15c04fff999ab0b2260b21f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"acorn@npm:^6.0.1, acorn@npm:^6.0.4, acorn@npm:^6.2.1":
+"acorn@npm:^6.4.1":
   version: 6.4.2
   resolution: "acorn@npm:6.4.2"
   bin:
   version: 6.4.2
   resolution: "acorn@npm:6.4.2"
   bin:
@@ -3380,7 +4030,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"acorn@npm:^7.1.1":
+"acorn@npm:^7.1.0, acorn@npm:^7.1.1, acorn@npm:^7.4.0":
   version: 7.4.1
   resolution: "acorn@npm:7.4.1"
   bin:
   version: 7.4.1
   resolution: "acorn@npm:7.4.1"
   bin:
@@ -3389,6 +4039,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"acorn@npm:^8.2.4, acorn@npm:^8.8.2":
+  version: 8.11.3
+  resolution: "acorn@npm:8.11.3"
+  bin:
+    acorn: bin/acorn
+  checksum: 76d8e7d559512566b43ab4aadc374f11f563f0a9e21626dd59cb2888444e9445923ae9f3699972767f18af61df89cd89f5eaaf772d1327b055b45cb829b4a88c
+  languageName: node
+  linkType: hard
+
 "acorn@npm:^8.5.0":
   version: 8.7.0
   resolution: "acorn@npm:8.7.0"
 "acorn@npm:^8.5.0":
   version: 8.7.0
   resolution: "acorn@npm:8.7.0"
@@ -3491,7 +4150,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ajv@npm:^6.1.0, ajv@npm:^6.1.1, ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4":
+"ajv@npm:^6.1.0, ajv@npm:^6.1.1, ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5":
   version: 6.12.6
   resolution: "ajv@npm:6.12.6"
   dependencies:
   version: 6.12.6
   resolution: "ajv@npm:6.12.6"
   dependencies:
@@ -3503,6 +4162,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"ajv@npm:^8.0.1":
+  version: 8.12.0
+  resolution: "ajv@npm:8.12.0"
+  dependencies:
+    fast-deep-equal: ^3.1.1
+    json-schema-traverse: ^1.0.0
+    require-from-string: ^2.0.2
+    uri-js: ^4.2.2
+  checksum: 4dc13714e316e67537c8b31bc063f99a1d9d9a497eb4bbd55191ac0dcd5e4985bbb71570352ad6f1e76684fb6d790928f96ba3b2d4fd6e10024be9612fe3f001
+  languageName: node
+  linkType: hard
+
 "alphanum-sort@npm:^1.0.0":
   version: 1.0.2
   resolution: "alphanum-sort@npm:1.0.2"
 "alphanum-sort@npm:^1.0.0":
   version: 1.0.2
   resolution: "alphanum-sort@npm:1.0.2"
@@ -3510,13 +4181,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"amdefine@npm:>=0.0.4":
-  version: 1.0.1
-  resolution: "amdefine@npm:1.0.1"
-  checksum: 9d4e15b94641643a9385b2841b4cb2bcf4e8e2f741ea4bd475c93ad7bab261ad4ed827a32e9c549b38b98759c4526c173ae4e6dde8caeb75ee5cebedc9863762
-  languageName: node
-  linkType: hard
-
 "ansi-colors@npm:^3.0.0":
   version: 3.2.4
   resolution: "ansi-colors@npm:3.2.4"
 "ansi-colors@npm:^3.0.0":
   version: 3.2.4
   resolution: "ansi-colors@npm:3.2.4"
@@ -3531,14 +4195,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ansi-escapes@npm:^3.0.0":
-  version: 3.2.0
-  resolution: "ansi-escapes@npm:3.2.0"
-  checksum: 0f94695b677ea742f7f1eed961f7fd8d05670f744c6ad1f8f635362f6681dcfbc1575cb05b43abc7bb6d67e25a75fb8c7ea8f2a57330eb2c76b33f18cb2cef0a
-  languageName: node
-  linkType: hard
-
-"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0":
+"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.1":
   version: 4.3.2
   resolution: "ansi-escapes@npm:4.3.2"
   dependencies:
   version: 4.3.2
   resolution: "ansi-escapes@npm:4.3.2"
   dependencies:
@@ -3547,7 +4204,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ansi-html@npm:0.0.7":
+"ansi-html@npm:0.0.7, ansi-html@npm:^0.0.7":
   version: 0.0.7
   resolution: "ansi-html@npm:0.0.7"
   bin:
   version: 0.0.7
   resolution: "ansi-html@npm:0.0.7"
   bin:
@@ -3563,14 +4220,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ansi-regex@npm:^3.0.0":
-  version: 3.0.1
-  resolution: "ansi-regex@npm:3.0.1"
-  checksum: 09daf180c5f59af9850c7ac1bd7fda85ba596cc8cbeb210826e90755f06c818af86d9fa1e6e8322fab2c3b9e9b03f56c537b42241139f824dd75066a1e7257cc
-  languageName: node
-  linkType: hard
-
-"ansi-regex@npm:^4.0.0, ansi-regex@npm:^4.1.0":
+"ansi-regex@npm:^4.1.0":
   version: 4.1.1
   resolution: "ansi-regex@npm:4.1.1"
   checksum: b1a6ee44cb6ecdabaa770b2ed500542714d4395d71c7e5c25baa631f680fb2ad322eb9ba697548d498a6fd366949fc8b5bfcf48d49a32803611f648005b01888
   version: 4.1.1
   resolution: "ansi-regex@npm:4.1.1"
   checksum: b1a6ee44cb6ecdabaa770b2ed500542714d4395d71c7e5c25baa631f680fb2ad322eb9ba697548d498a6fd366949fc8b5bfcf48d49a32803611f648005b01888
@@ -3584,13 +4234,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ansi-styles@npm:^2.2.1":
-  version: 2.2.1
-  resolution: "ansi-styles@npm:2.2.1"
-  checksum: ebc0e00381f2a29000d1dac8466a640ce11943cef3bda3cd0020dc042e31e1058ab59bf6169cd794a54c3a7338a61ebc404b7c91e004092dd20e028c432c9c2c
-  languageName: node
-  linkType: hard
-
 "ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1":
   version: 3.2.1
   resolution: "ansi-styles@npm:3.2.1"
 "ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1":
   version: 3.2.1
   resolution: "ansi-styles@npm:3.2.1"
@@ -3619,6 +4262,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"anymatch@npm:^3.0.0, anymatch@npm:^3.0.3":
+  version: 3.1.3
+  resolution: "anymatch@npm:3.1.3"
+  dependencies:
+    normalize-path: ^3.0.0
+    picomatch: ^2.0.4
+  checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2
+  languageName: node
+  linkType: hard
+
 "anymatch@npm:~3.1.2":
   version: 3.1.2
   resolution: "anymatch@npm:3.1.2"
 "anymatch@npm:~3.1.2":
   version: 3.1.2
   resolution: "anymatch@npm:3.1.2"
@@ -3650,16 +4303,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"are-we-there-yet@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "are-we-there-yet@npm:2.0.0"
-  dependencies:
-    delegates: ^1.0.0
-    readable-stream: ^3.6.0
-  checksum: 6c80b4fd04ecee6ba6e737e0b72a4b41bdc64b7d279edfc998678567ff583c8df27e27523bc789f2c99be603ffa9eaa612803da1d886962d2086e7ff6fa90c7c
-  languageName: node
-  linkType: hard
-
 "are-we-there-yet@npm:^3.0.0":
   version: 3.0.0
   resolution: "are-we-there-yet@npm:3.0.0"
 "are-we-there-yet@npm:^3.0.0":
   version: 3.0.0
   resolution: "are-we-there-yet@npm:3.0.0"
@@ -3679,13 +4322,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"aria-query@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "aria-query@npm:3.0.0"
+"aria-query@npm:^5.3.0":
+  version: 5.3.0
+  resolution: "aria-query@npm:5.3.0"
   dependencies:
   dependencies:
-    ast-types-flow: 0.0.7
-    commander: ^2.11.0
-  checksum: 52861d7d31321a23f27e5f95a437ddafd20e5eee03ff6e4319eeb1e98dce103f03ccaea34acb5bf2810580f71a9ac1658200fa3d49435279e99df2908f213f1b
+    dequal: ^2.0.3
+  checksum: 305bd73c76756117b59aba121d08f413c7ff5e80fa1b98e217a3443fcddb9a232ee790e24e432b59ae7625aebcf4c47cb01c2cac872994f0b426f5bdfcd96ba9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -3717,17 +4359,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"array-equal@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "array-equal@npm:1.0.0"
-  checksum: 3f68045806357db9b2fa1ad583e42a659de030633118a0cd35ee4975cb20db3b9a3d36bbec9b5afe70011cf989eefd215c12fe0ce08c498f770859ca6e70688a
-  languageName: node
-  linkType: hard
-
-"array-find-index@npm:^1.0.1":
-  version: 1.0.2
-  resolution: "array-find-index@npm:1.0.2"
-  checksum: aac128bf369e1ac6c06ff0bb330788371c0e256f71279fb92d745e26fb4b9db8920e485b4ec25e841c93146bf71a34dcdbcefa115e7e0f96927a214d237b7081
+"array-buffer-byte-length@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "array-buffer-byte-length@npm:1.0.1"
+  dependencies:
+    call-bind: ^1.0.5
+    is-array-buffer: ^3.0.4
+  checksum: 53524e08f40867f6a9f35318fafe467c32e45e9c682ba67b11943e167344d2febc0f6977a17e699b05699e805c3e8f073d876f8bbf1b559ed494ad2cd0fae09e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -3752,16 +4390,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"array-includes@npm:^3.0.3, array-includes@npm:^3.1.1":
-  version: 3.1.3
-  resolution: "array-includes@npm:3.1.3"
+"array-includes@npm:^3.1.6, array-includes@npm:^3.1.7":
+  version: 3.1.8
+  resolution: "array-includes@npm:3.1.8"
   dependencies:
   dependencies:
-    call-bind: ^1.0.2
-    define-properties: ^1.1.3
-    es-abstract: ^1.18.0-next.2
-    get-intrinsic: ^1.1.1
-    is-string: ^1.0.5
-  checksum: eaab8812412b5ec921c8fe678a9d61f501b12f6c72e271e0e8652fe7f4145276cc7ad79ff303ac4ed69cbf5135155bfb092b1b6d552e423e75106d1c887da150
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.2
+    es-object-atoms: ^1.0.0
+    get-intrinsic: ^1.2.4
+    is-string: ^1.0.7
+  checksum: eb39ba5530f64e4d8acab39297c11c1c5be2a4ea188ab2b34aba5fb7224d918f77717a9d57a3e2900caaa8440e59431bdaf5c974d5212ef65d97f132e38e2d91
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -3818,7 +4457,35 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"array.prototype.flat@npm:^1.2.1, array.prototype.flat@npm:^1.2.3":
+"array.prototype.findlast@npm:^1.2.4":
+  version: 1.2.5
+  resolution: "array.prototype.findlast@npm:1.2.5"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.2
+    es-errors: ^1.3.0
+    es-object-atoms: ^1.0.0
+    es-shim-unscopables: ^1.0.2
+  checksum: 83ce4ad95bae07f136d316f5a7c3a5b911ac3296c3476abe60225bc4a17938bf37541972fcc37dd5adbc99cbb9c928c70bbbfc1c1ce549d41a415144030bb446
+  languageName: node
+  linkType: hard
+
+"array.prototype.findlastindex@npm:^1.2.3":
+  version: 1.2.5
+  resolution: "array.prototype.findlastindex@npm:1.2.5"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.2
+    es-errors: ^1.3.0
+    es-object-atoms: ^1.0.0
+    es-shim-unscopables: ^1.0.2
+  checksum: 2c81cff2a75deb95bf1ed89b6f5f2bfbfb882211e3b7cc59c3d6b87df774cd9d6b36949a8ae39ac476e092c1d4a4905f5ee11a86a456abb10f35f8211ae4e710
+  languageName: node
+  linkType: hard
+
+"array.prototype.flat@npm:^1.2.3":
   version: 1.2.4
   resolution: "array.prototype.flat@npm:1.2.4"
   dependencies:
   version: 1.2.4
   resolution: "array.prototype.flat@npm:1.2.4"
   dependencies:
@@ -3829,6 +4496,71 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2":
+  version: 1.3.2
+  resolution: "array.prototype.flat@npm:1.3.2"
+  dependencies:
+    call-bind: ^1.0.2
+    define-properties: ^1.2.0
+    es-abstract: ^1.22.1
+    es-shim-unscopables: ^1.0.0
+  checksum: 5d6b4bf102065fb3f43764bfff6feb3295d372ce89591e6005df3d0ce388527a9f03c909af6f2a973969a4d178ab232ffc9236654149173e0e187ec3a1a6b87b
+  languageName: node
+  linkType: hard
+
+"array.prototype.flatmap@npm:^1.3.2":
+  version: 1.3.2
+  resolution: "array.prototype.flatmap@npm:1.3.2"
+  dependencies:
+    call-bind: ^1.0.2
+    define-properties: ^1.2.0
+    es-abstract: ^1.22.1
+    es-shim-unscopables: ^1.0.0
+  checksum: ce09fe21dc0bcd4f30271f8144083aa8c13d4639074d6c8dc82054b847c7fc9a0c97f857491f4da19d4003e507172a78f4bcd12903098adac8b9cd374f734be3
+  languageName: node
+  linkType: hard
+
+"array.prototype.toreversed@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "array.prototype.toreversed@npm:1.1.2"
+  dependencies:
+    call-bind: ^1.0.2
+    define-properties: ^1.2.0
+    es-abstract: ^1.22.1
+    es-shim-unscopables: ^1.0.0
+  checksum: 58598193426282155297bedf950dc8d464624a0d81659822fb73124286688644cb7e0e4927a07f3ab2daaeb6617b647736cc3a5e6ca7ade5bb8e573b284e6240
+  languageName: node
+  linkType: hard
+
+"array.prototype.tosorted@npm:^1.1.3":
+  version: 1.1.3
+  resolution: "array.prototype.tosorted@npm:1.1.3"
+  dependencies:
+    call-bind: ^1.0.5
+    define-properties: ^1.2.1
+    es-abstract: ^1.22.3
+    es-errors: ^1.1.0
+    es-shim-unscopables: ^1.0.2
+  checksum: 555e8808086bbde9e634c5dc5a8c0a2f1773075447b43b2fa76ab4f94f4e90f416d2a4f881024e1ce1a2931614caf76cd6b408af901c9d7cd13061d0d268f5af
+  languageName: node
+  linkType: hard
+
+"arraybuffer.prototype.slice@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "arraybuffer.prototype.slice@npm:1.0.3"
+  dependencies:
+    array-buffer-byte-length: ^1.0.1
+    call-bind: ^1.0.5
+    define-properties: ^1.2.1
+    es-abstract: ^1.22.3
+    es-errors: ^1.2.1
+    get-intrinsic: ^1.2.3
+    is-array-buffer: ^3.0.4
+    is-shared-array-buffer: ^1.0.2
+  checksum: 352259cba534dcdd969c92ab002efd2ba5025b2e3b9bead3973150edbdf0696c629d7f4b3f061c5931511e8207bdc2306da614703c820b45dabce39e3daf7e3e
+  languageName: node
+  linkType: hard
+
 "arrify@npm:^1.0.1":
   version: 1.0.1
   resolution: "arrify@npm:1.0.1"
 "arrify@npm:^1.0.1":
   version: 1.0.1
   resolution: "arrify@npm:1.0.1"
@@ -3836,10 +4568,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"arrify@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "arrify@npm:2.0.1"
+  checksum: 067c4c1afd182806a82e4c1cb8acee16ab8b5284fbca1ce29408e6e91281c36bb5b612f6ddfbd40a0f7a7e0c75bf2696eb94c027f6e328d6e9c52465c98e4209
+  languageName: node
+  linkType: hard
+
 "arvados-workbench-2@workspace:.":
   version: 0.0.0-use.local
   resolution: "arvados-workbench-2@workspace:."
   dependencies:
 "arvados-workbench-2@workspace:.":
   version: 0.0.0-use.local
   resolution: "arvados-workbench-2@workspace:."
   dependencies:
+    "@babel/core": ^7.0.0
+    "@babel/runtime-corejs2": ^7.0.0
     "@coreui/coreui": ^4.3.2
     "@coreui/react": ^4.11.0
     "@date-io/date-fns": 1
     "@coreui/coreui": ^4.3.2
     "@coreui/react": ^4.11.0
     "@date-io/date-fns": 1
@@ -3879,12 +4620,10 @@ __metadata:
     "@types/shell-escape": ^0.2.0
     "@types/sinon": 7.5
     "@types/uuid": 3.4.4
     "@types/shell-escape": ^0.2.0
     "@types/sinon": 7.5
     "@types/uuid": 3.4.4
-    axios: ^0.21.1
+    axios: ^0.28.1
     axios-mock-adapter: 1.17.0
     axios-mock-adapter: 1.17.0
-    babel-core: 6.26.3
-    babel-runtime: 6.26.0
     bootstrap: ^5.3.2
     bootstrap: ^5.3.2
-    caniuse-lite: 1.0.30001299
+    caniuse-lite: 1.0.30001612
     classnames: 2.2.6
     cwlts: 1.15.29
     cypress: ^13.6.6
     classnames: 2.2.6
     cwlts: 1.15.29
     cypress: ^13.6.6
@@ -3911,7 +4650,6 @@ __metadata:
     mime: ^3.0.0
     moment: ^2.29.4
     node-sass: ^9.0.0
     mime: ^3.0.0
     moment: ^2.29.4
     node-sass: ^9.0.0
-    node-sass-chokidar: ^2.0.0
     parse-duration: 0.4.4
     prop-types: 15.7.2
     query-string: 6.9.0
     parse-duration: 0.4.4
     prop-types: 15.7.2
     query-string: 6.9.0
@@ -3929,7 +4667,7 @@ __metadata:
     react-router-dom: 4.3.1
     react-router-redux: 5.0.0-alpha.9
     react-rte: ^0.16.5
     react-router-dom: 4.3.1
     react-router-redux: 5.0.0-alpha.9
     react-rte: ^0.16.5
-    react-scripts: 3.4.4
+    react-scripts: 4.0.1
     react-splitter-layout: 3.0.1
     react-transition-group: 2.5.0
     react-virtualized-auto-sizer: 1.0.2
     react-splitter-layout: 3.0.1
     react-transition-group: 2.5.0
     react-virtualized-auto-sizer: 1.0.2
@@ -3963,6 +4701,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"asn1.js@npm:^4.10.1":
+  version: 4.10.1
+  resolution: "asn1.js@npm:4.10.1"
+  dependencies:
+    bn.js: ^4.0.0
+    inherits: ^2.0.1
+    minimalistic-assert: ^1.0.0
+  checksum: 9289a1a55401238755e3142511d7b8f6fc32f08c86ff68bd7100da8b6c186179dd6b14234fba2f7f6099afcd6758a816708485efe44bc5b2a6ec87d9ceeddbb5
+  languageName: node
+  linkType: hard
+
 "asn1.js@npm:^5.2.0":
   version: 5.4.1
   resolution: "asn1.js@npm:5.4.1"
 "asn1.js@npm:^5.2.0":
   version: 5.4.1
   resolution: "asn1.js@npm:5.4.1"
@@ -4008,17 +4757,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ast-types-flow@npm:0.0.7, ast-types-flow@npm:^0.0.7":
-  version: 0.0.7
-  resolution: "ast-types-flow@npm:0.0.7"
-  checksum: a26dcc2182ffee111cad7c471759b0bda22d3b7ebacf27c348b22c55f16896b18ab0a4d03b85b4020dce7f3e634b8f00b593888f622915096ea1927fa51866c4
-  languageName: node
-  linkType: hard
-
-"astral-regex@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "astral-regex@npm:1.0.0"
-  checksum: 93417fc0879531cd95ace2560a54df865c9461a3ac0714c60cbbaa5f1f85d2bee85489e78d82f70b911b71ac25c5f05fc5a36017f44c9bb33c701bee229ff848
+"ast-types-flow@npm:^0.0.8":
+  version: 0.0.8
+  resolution: "ast-types-flow@npm:0.0.8"
+  checksum: 0a64706609a179233aac23817837abab614f3548c252a2d3d79ea1e10c74aa28a0846e11f466cf72771b6ed8713abc094dcf8c40c3ec4207da163efa525a94a8
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -4122,6 +4864,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"available-typed-arrays@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "available-typed-arrays@npm:1.0.7"
+  dependencies:
+    possible-typed-array-names: ^1.0.0
+  checksum: 1aa3ffbfe6578276996de660848b6e95669d9a95ad149e3dd0c0cda77db6ee1dbd9d1dd723b65b6d277b882dd0c4b91a654ae9d3cf9e1254b7e93e4908d78fd3
+  languageName: node
+  linkType: hard
+
 "aws-sign2@npm:~0.7.0":
   version: 0.7.0
   resolution: "aws-sign2@npm:0.7.0"
 "aws-sign2@npm:~0.7.0":
   version: 0.7.0
   resolution: "aws-sign2@npm:0.7.0"
@@ -4136,72 +4887,45 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"axios-mock-adapter@npm:1.17.0":
-  version: 1.17.0
-  resolution: "axios-mock-adapter@npm:1.17.0"
-  dependencies:
-    deep-equal: ^1.0.1
-  peerDependencies:
-    axios: ">= 0.9.0"
-  checksum: 2f462212f030925ba3d11968ebc6947c9169e590cf05a74d18c927ca4727b49ae69dbc8835a19f518697da991e46f7b7baf28b62765dea8945bfcaaa6941426c
-  languageName: node
-  linkType: hard
-
-"axios@npm:^0.21.1":
-  version: 0.21.4
-  resolution: "axios@npm:0.21.4"
-  dependencies:
-    follow-redirects: ^1.14.0
-  checksum: 44245f24ac971e7458f3120c92f9d66d1fc695e8b97019139de5b0cc65d9b8104647db01e5f46917728edfc0cfd88eb30fc4c55e6053eef4ace76768ce95ff3c
+"axe-core@npm:=4.7.0":
+  version: 4.7.0
+  resolution: "axe-core@npm:4.7.0"
+  checksum: f086bcab42be1761ba2b0b127dec350087f4c3a853bba8dd58f69d898cefaac31a1561da23146f6f3c07954c76171d1f2ce460e555e052d2b02cd79af628fa4a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"axobject-query@npm:^2.0.2":
-  version: 2.2.0
-  resolution: "axobject-query@npm:2.2.0"
-  checksum: 96b8c7d807ca525f41ad9b286186e2089b561ba63a6d36c3e7d73dc08150714660995c7ad19cda05784458446a0793b45246db45894631e13853f48c1aa3117f
+"axios-mock-adapter@npm:1.17.0":
+  version: 1.17.0
+  resolution: "axios-mock-adapter@npm:1.17.0"
+  dependencies:
+    deep-equal: ^1.0.1
+  peerDependencies:
+    axios: ">= 0.9.0"
+  checksum: 2f462212f030925ba3d11968ebc6947c9169e590cf05a74d18c927ca4727b49ae69dbc8835a19f518697da991e46f7b7baf28b62765dea8945bfcaaa6941426c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-code-frame@npm:^6.22.0, babel-code-frame@npm:^6.26.0":
-  version: 6.26.0
-  resolution: "babel-code-frame@npm:6.26.0"
+"axios@npm:^0.28.1":
+  version: 0.28.1
+  resolution: "axios@npm:0.28.1"
   dependencies:
   dependencies:
-    chalk: ^1.1.3
-    esutils: ^2.0.2
-    js-tokens: ^3.0.2
-  checksum: 9410c3d5a921eb02fa409675d1a758e493323a49e7b9dddb7a2a24d47e61d39ab1129dd29f9175836eac9ce8b1d4c0a0718fcdc57ce0b865b529fd250dbab313
+    follow-redirects: ^1.15.0
+    form-data: ^4.0.0
+    proxy-from-env: ^1.1.0
+  checksum: 5115a38d79064d07437c5a28f15841e3607634040e3120ec06a2c4367a7d07cf213b16496eab53b6f58ebc5fb377a440ba9ed4782529b14449a1e285734bfb54
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-core@npm:6.26.3, babel-core@npm:^6.26.0":
-  version: 6.26.3
-  resolution: "babel-core@npm:6.26.3"
+"axobject-query@npm:^3.2.1":
+  version: 3.2.1
+  resolution: "axobject-query@npm:3.2.1"
   dependencies:
   dependencies:
-    babel-code-frame: ^6.26.0
-    babel-generator: ^6.26.0
-    babel-helpers: ^6.24.1
-    babel-messages: ^6.23.0
-    babel-register: ^6.26.0
-    babel-runtime: ^6.26.0
-    babel-template: ^6.26.0
-    babel-traverse: ^6.26.0
-    babel-types: ^6.26.0
-    babylon: ^6.18.0
-    convert-source-map: ^1.5.1
-    debug: ^2.6.9
-    json5: ^0.5.1
-    lodash: ^4.17.4
-    minimatch: ^3.0.4
-    path-is-absolute: ^1.0.1
-    private: ^0.1.8
-    slash: ^1.0.0
-    source-map: ^0.5.7
-  checksum: 3d6a37e5c69ea7f7d66c2a261cbd7219197f2f938700e6ebbabb6d84a03f2bf86691ffa066866dcb49ba6c4bd702d347c9e0e147660847d709705cf43c964752
+    dequal: ^2.0.3
+  checksum: a94047e702b57c91680e6a952ec4a1aaa2cfd0d80ead76bc8c954202980d8c51968a6ea18b4d8010e8e2cf95676533d8022a8ebba9abc1dfe25686721df26fd2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-eslint@npm:10.1.0":
+"babel-eslint@npm:^10.1.0":
   version: 10.1.0
   resolution: "babel-eslint@npm:10.1.0"
   dependencies:
   version: 10.1.0
   resolution: "babel-eslint@npm:10.1.0"
   dependencies:
@@ -4226,46 +4950,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-generator@npm:^6.26.0":
-  version: 6.26.1
-  resolution: "babel-generator@npm:6.26.1"
-  dependencies:
-    babel-messages: ^6.23.0
-    babel-runtime: ^6.26.0
-    babel-types: ^6.26.0
-    detect-indent: ^4.0.0
-    jsesc: ^1.3.0
-    lodash: ^4.17.4
-    source-map: ^0.5.7
-    trim-right: ^1.0.1
-  checksum: 5397f4d4d1243e7157e3336be96c10fcb1f29f73bf2d9842229c71764d9a6431397d249483a38c4d8b1581459e67be4df6f32d26b1666f02d0f5bfc2c2f25193
-  languageName: node
-  linkType: hard
-
-"babel-helpers@npm:^6.24.1":
-  version: 6.24.1
-  resolution: "babel-helpers@npm:6.24.1"
+"babel-jest@npm:^26.6.0, babel-jest@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "babel-jest@npm:26.6.3"
   dependencies:
   dependencies:
-    babel-runtime: ^6.22.0
-    babel-template: ^6.24.1
-  checksum: 751c6010e18648eebae422adfea5f3b5eff70d592d693bfe0f53346227d74b38e6cd2553c4c18de1e64faac585de490eccbd3ab86ba0885bdac42ed4478bc6b0
-  languageName: node
-  linkType: hard
-
-"babel-jest@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "babel-jest@npm:24.9.0"
-  dependencies:
-    "@jest/transform": ^24.9.0
-    "@jest/types": ^24.9.0
-    "@types/babel__core": ^7.1.0
-    babel-plugin-istanbul: ^5.1.0
-    babel-preset-jest: ^24.9.0
-    chalk: ^2.4.2
-    slash: ^2.0.0
+    "@jest/transform": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/babel__core": ^7.1.7
+    babel-plugin-istanbul: ^6.0.0
+    babel-preset-jest: ^26.6.2
+    chalk: ^4.0.0
+    graceful-fs: ^4.2.4
+    slash: ^3.0.0
   peerDependencies:
     "@babel/core": ^7.0.0
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 205f0d701a202edb483a1f8cc79557f777d20df42656f1a1c2e7ef368f8f53f9d4c4af08ea812d98b61ab12cc5f146db4573a301880770d1dc5748624cc51711
+  checksum: 5917233f0d381e719e195b69b81e46da90293432d10288d79f8f59b8f3f9ac030e14701f3d9f90893fb739481df1d132446f1b983d841e65e2623775db100897
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -4285,98 +4984,84 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-messages@npm:^6.23.0":
-  version: 6.23.0
-  resolution: "babel-messages@npm:6.23.0"
-  dependencies:
-    babel-runtime: ^6.22.0
-  checksum: c8075c17587a33869e1a5bd0a5b73bbe395b68188362dacd5418debbc7c8fd784bcd3295e81ee7e410dc2c2655755add6af03698c522209f6a68334c15e6d6ca
-  languageName: node
-  linkType: hard
-
-"babel-plugin-dynamic-import-node@npm:^2.3.3":
-  version: 2.3.3
-  resolution: "babel-plugin-dynamic-import-node@npm:2.3.3"
-  dependencies:
-    object.assign: ^4.1.0
-  checksum: c9d24415bcc608d0db7d4c8540d8002ac2f94e2573d2eadced137a29d9eab7e25d2cbb4bc6b9db65cf6ee7430f7dd011d19c911a9a778f0533b4a05ce8292c9b
-  languageName: node
-  linkType: hard
-
-"babel-plugin-istanbul@npm:^5.1.0":
-  version: 5.2.0
-  resolution: "babel-plugin-istanbul@npm:5.2.0"
+"babel-plugin-istanbul@npm:^6.0.0":
+  version: 6.1.1
+  resolution: "babel-plugin-istanbul@npm:6.1.1"
   dependencies:
     "@babel/helper-plugin-utils": ^7.0.0
   dependencies:
     "@babel/helper-plugin-utils": ^7.0.0
-    find-up: ^3.0.0
-    istanbul-lib-instrument: ^3.3.0
-    test-exclude: ^5.2.3
-  checksum: 46e31a53d1c08a4b738c988871e94dd83e534b3d49248c45c9e63d04d221aa787d8c4f32576e1fade26dbab7cabeae665cbf5eb067aaef74500048dfef365c80
+    "@istanbuljs/load-nyc-config": ^1.0.0
+    "@istanbuljs/schema": ^0.1.2
+    istanbul-lib-instrument: ^5.0.4
+    test-exclude: ^6.0.0
+  checksum: cb4fd95738219f232f0aece1116628cccff16db891713c4ccb501cddbbf9272951a5df81f2f2658dfdf4b3e7b236a9d5cbcf04d5d8c07dd5077297339598061a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-plugin-jest-hoist@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "babel-plugin-jest-hoist@npm:24.9.0"
+"babel-plugin-jest-hoist@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "babel-plugin-jest-hoist@npm:26.6.2"
   dependencies:
   dependencies:
+    "@babel/template": ^7.3.3
+    "@babel/types": ^7.3.3
+    "@types/babel__core": ^7.0.0
     "@types/babel__traverse": ^7.0.6
     "@types/babel__traverse": ^7.0.6
-  checksum: 9f0d23fcf94448e302e201665d7232303a548107adf545590b09f22a747755387cb9dc676d22884a298b17d11ede5401436e1b70fa574eee3efa61ad1230c8e6
+  checksum: abe3732fdf20f96e91cbf788a54d776b30bd7a6054cb002a744d7071c656813e26e77a780dc2a6f6b197472897e220836cd907bda3fadb9d0481126bfd6c3783
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-plugin-macros@npm:2.8.0":
-  version: 2.8.0
-  resolution: "babel-plugin-macros@npm:2.8.0"
+"babel-plugin-macros@npm:^3.1.0":
+  version: 3.1.0
+  resolution: "babel-plugin-macros@npm:3.1.0"
   dependencies:
   dependencies:
-    "@babel/runtime": ^7.7.2
-    cosmiconfig: ^6.0.0
-    resolve: ^1.12.0
-  checksum: 59b09a21cf3ae1e14186c1b021917d004b49b953824b24953a54c6502da79e8051d4ac31cfd4a0ae7f6ea5ddf1f7edd93df4895dd3c3982a5b2431859c2889ac
+    "@babel/runtime": ^7.12.5
+    cosmiconfig: ^7.0.0
+    resolve: ^1.19.0
+  checksum: 765de4abebd3e4688ebdfbff8571ddc8cd8061f839bb6c3e550b0344a4027b04c60491f843296ce3f3379fb356cc873d57a9ee6694262547eb822c14a25be9a6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-plugin-named-asset-import@npm:^0.3.6":
-  version: 0.3.7
-  resolution: "babel-plugin-named-asset-import@npm:0.3.7"
+"babel-plugin-named-asset-import@npm:^0.3.7":
+  version: 0.3.8
+  resolution: "babel-plugin-named-asset-import@npm:0.3.8"
   peerDependencies:
     "@babel/core": ^7.1.0
   peerDependencies:
     "@babel/core": ^7.1.0
-  checksum: 4c9a42a2762f3d596a09105d05991525a0553d095030459d0f71449b023801ccc43e90fa20b618c52283dc61ca528a4a59df244e5b1dd583867786088eb473b7
+  checksum: d1e58df8cb75d91d070feea31087bc989906d3465144bde7e9f3c3690b514a90a55d3aebf3e65e76c5d4c743ecedde5f640f09f43a21fa60f1a5d413cb3f7a67
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-plugin-polyfill-corejs2@npm:^0.2.2":
-  version: 0.2.2
-  resolution: "babel-plugin-polyfill-corejs2@npm:0.2.2"
+"babel-plugin-polyfill-corejs2@npm:^0.4.10":
+  version: 0.4.10
+  resolution: "babel-plugin-polyfill-corejs2@npm:0.4.10"
   dependencies:
   dependencies:
-    "@babel/compat-data": ^7.13.11
-    "@babel/helper-define-polyfill-provider": ^0.2.2
-    semver: ^6.1.1
+    "@babel/compat-data": ^7.22.6
+    "@babel/helper-define-polyfill-provider": ^0.6.1
+    semver: ^6.3.1
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: eee45ecce743e06840d29936a7f4a9f9eca19552ba010e9f3676c6a2697ab815230f39953296b72f09665de0e8fffe260e52b348011a9ddba36cfa7eec6f8c51
+    "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
+  checksum: 2c0e4868789152f50db306f4957fa7934876cefb51d5d86436595f0b091539e45ce0e9c0125b5db2d71f913b29cd48ae76b8e942ba28fcf2273e084f54664a1c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-plugin-polyfill-corejs3@npm:^0.2.2":
-  version: 0.2.3
-  resolution: "babel-plugin-polyfill-corejs3@npm:0.2.3"
+"babel-plugin-polyfill-corejs3@npm:^0.10.1, babel-plugin-polyfill-corejs3@npm:^0.10.4":
+  version: 0.10.4
+  resolution: "babel-plugin-polyfill-corejs3@npm:0.10.4"
   dependencies:
   dependencies:
-    "@babel/helper-define-polyfill-provider": ^0.2.2
-    core-js-compat: ^3.14.0
+    "@babel/helper-define-polyfill-provider": ^0.6.1
+    core-js-compat: ^3.36.1
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: e390c5317b35808633d32db2c1718aef6af788df148adc6fa54e56d2266896ad2da2d200163f392e06ae1ebd1a0feaeaf18d7a337dea70387429618898b90a68
+    "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
+  checksum: b96a54495f7cc8b3797251c8c15f5ed015edddc3110fc122f6b32c94bec33af1e8bc56fa99091808f500bde0cccaaa266889cdc5935d9e6e9cf09898214f02dd
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-plugin-polyfill-regenerator@npm:^0.2.2":
-  version: 0.2.2
-  resolution: "babel-plugin-polyfill-regenerator@npm:0.2.2"
+"babel-plugin-polyfill-regenerator@npm:^0.6.1":
+  version: 0.6.1
+  resolution: "babel-plugin-polyfill-regenerator@npm:0.6.1"
   dependencies:
   dependencies:
-    "@babel/helper-define-polyfill-provider": ^0.2.2
+    "@babel/helper-define-polyfill-provider": ^0.6.1
   peerDependencies:
   peerDependencies:
-    "@babel/core": ^7.0.0-0
-  checksum: 3e32e318fd91d65c3af2bb363189f00d3839f07a73a08813b553553e07da205162091b428dd5b6ffb6ea4caf531ff43ebc54197b0a5a9dc2fc5c7e9a650e946d
+    "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
+  checksum: 9df4a8e9939dd419fed3d9ea26594b4479f2968f37c225e1b2aa463001d7721f5537740e6622909d2a570b61cec23256924a1701404fc9d6fd4474d3e845cedb
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -4397,64 +5082,72 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-plugin-transform-react-remove-prop-types@npm:0.4.24":
+"babel-plugin-transform-react-remove-prop-types@npm:^0.4.24":
   version: 0.4.24
   resolution: "babel-plugin-transform-react-remove-prop-types@npm:0.4.24"
   checksum: 54afe56d67f0d118c9da23996f39948e502a152b3f582eb6e8f163fcb76c2c1ea4e0cdd4f9fac5c0ef050eab4fe0a950b0b74aae6237bcc0d31d8fc4cc808d1a
   languageName: node
   linkType: hard
 
   version: 0.4.24
   resolution: "babel-plugin-transform-react-remove-prop-types@npm:0.4.24"
   checksum: 54afe56d67f0d118c9da23996f39948e502a152b3f582eb6e8f163fcb76c2c1ea4e0cdd4f9fac5c0ef050eab4fe0a950b0b74aae6237bcc0d31d8fc4cc808d1a
   languageName: node
   linkType: hard
 
-"babel-preset-jest@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "babel-preset-jest@npm:24.9.0"
+"babel-preset-current-node-syntax@npm:^1.0.0":
+  version: 1.0.1
+  resolution: "babel-preset-current-node-syntax@npm:1.0.1"
   dependencies:
   dependencies:
-    "@babel/plugin-syntax-object-rest-spread": ^7.0.0
-    babel-plugin-jest-hoist: ^24.9.0
+    "@babel/plugin-syntax-async-generators": ^7.8.4
+    "@babel/plugin-syntax-bigint": ^7.8.3
+    "@babel/plugin-syntax-class-properties": ^7.8.3
+    "@babel/plugin-syntax-import-meta": ^7.8.3
+    "@babel/plugin-syntax-json-strings": ^7.8.3
+    "@babel/plugin-syntax-logical-assignment-operators": ^7.8.3
+    "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3
+    "@babel/plugin-syntax-numeric-separator": ^7.8.3
+    "@babel/plugin-syntax-object-rest-spread": ^7.8.3
+    "@babel/plugin-syntax-optional-catch-binding": ^7.8.3
+    "@babel/plugin-syntax-optional-chaining": ^7.8.3
+    "@babel/plugin-syntax-top-level-await": ^7.8.3
   peerDependencies:
     "@babel/core": ^7.0.0
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: d32ab6255e36ed06ef1cc53089b261a74c171d17758792979c2992d4fcb97982f67f837156bbef38042eb11751496a783dee61aafcbf2d7449ed94d52483bee2
+  checksum: d118c2742498c5492c095bc8541f4076b253e705b5f1ad9a2e7d302d81a84866f0070346662355c8e25fc02caa28dc2da8d69bcd67794a0d60c4d6fab6913cc8
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-preset-react-app@npm:^9.1.2":
-  version: 9.1.2
-  resolution: "babel-preset-react-app@npm:9.1.2"
+"babel-preset-jest@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "babel-preset-jest@npm:26.6.2"
   dependencies:
   dependencies:
-    "@babel/core": 7.9.0
-    "@babel/plugin-proposal-class-properties": 7.8.3
-    "@babel/plugin-proposal-decorators": 7.8.3
-    "@babel/plugin-proposal-nullish-coalescing-operator": 7.8.3
-    "@babel/plugin-proposal-numeric-separator": 7.8.3
-    "@babel/plugin-proposal-optional-chaining": 7.9.0
-    "@babel/plugin-transform-flow-strip-types": 7.9.0
-    "@babel/plugin-transform-react-display-name": 7.8.3
-    "@babel/plugin-transform-runtime": 7.9.0
-    "@babel/preset-env": 7.9.0
-    "@babel/preset-react": 7.9.1
-    "@babel/preset-typescript": 7.9.0
-    "@babel/runtime": 7.9.0
-    babel-plugin-macros: 2.8.0
-    babel-plugin-transform-react-remove-prop-types: 0.4.24
-  checksum: ebdf90c922394ba3c72a326e14c5deff45292fdb46e114d5d83e9a1cf9cb433262254def4347767f5c7aa0924f0795dadae5c82bbc3acd77111c0b1df9316cd9
+    babel-plugin-jest-hoist: ^26.6.2
+    babel-preset-current-node-syntax: ^1.0.0
+  peerDependencies:
+    "@babel/core": ^7.0.0
+  checksum: 1d9bef3a7ac6751a09d29ceb84be8b1998abd210fafa12223689c744db4f2a63ab90cba7986a71f3154d9aceda9dbeca563178731d21cbaf793b4096ed3a4d01
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-register@npm:^6.26.0":
-  version: 6.26.0
-  resolution: "babel-register@npm:6.26.0"
+"babel-preset-react-app@npm:^10.0.0":
+  version: 10.0.1
+  resolution: "babel-preset-react-app@npm:10.0.1"
   dependencies:
   dependencies:
-    babel-core: ^6.26.0
-    babel-runtime: ^6.26.0
-    core-js: ^2.5.0
-    home-or-tmp: ^2.0.0
-    lodash: ^4.17.4
-    mkdirp: ^0.5.1
-    source-map-support: ^0.4.15
-  checksum: 75d5fe060e4850dbdbd5f56db2928cd0b6b6c93a65ba5f2a991465af4dc3f4adf46d575138f228b2169b1e25e3b4a7cdd16515a355fea41b873321bf56467583
+    "@babel/core": ^7.16.0
+    "@babel/plugin-proposal-class-properties": ^7.16.0
+    "@babel/plugin-proposal-decorators": ^7.16.4
+    "@babel/plugin-proposal-nullish-coalescing-operator": ^7.16.0
+    "@babel/plugin-proposal-numeric-separator": ^7.16.0
+    "@babel/plugin-proposal-optional-chaining": ^7.16.0
+    "@babel/plugin-proposal-private-methods": ^7.16.0
+    "@babel/plugin-transform-flow-strip-types": ^7.16.0
+    "@babel/plugin-transform-react-display-name": ^7.16.0
+    "@babel/plugin-transform-runtime": ^7.16.4
+    "@babel/preset-env": ^7.16.4
+    "@babel/preset-react": ^7.16.0
+    "@babel/preset-typescript": ^7.16.0
+    "@babel/runtime": ^7.16.3
+    babel-plugin-macros: ^3.1.0
+    babel-plugin-transform-react-remove-prop-types: ^0.4.24
+  checksum: ee66043484e67b8aef2541976388299691478ea00834f3bb14b6b3d5edcd316a5ac95351f6ec084b41ee555cad820d4194280ad38ce51884fedc7e8946a57b74
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-runtime@npm:6.26.0, babel-runtime@npm:^6.22.0, babel-runtime@npm:^6.23.0, babel-runtime@npm:^6.26.0":
+"babel-runtime@npm:^6.23.0, babel-runtime@npm:^6.26.0":
   version: 6.26.0
   resolution: "babel-runtime@npm:6.26.0"
   dependencies:
   version: 6.26.0
   resolution: "babel-runtime@npm:6.26.0"
   dependencies:
@@ -4464,48 +5157,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"babel-template@npm:^6.24.1, babel-template@npm:^6.26.0":
-  version: 6.26.0
-  resolution: "babel-template@npm:6.26.0"
-  dependencies:
-    babel-runtime: ^6.26.0
-    babel-traverse: ^6.26.0
-    babel-types: ^6.26.0
-    babylon: ^6.18.0
-    lodash: ^4.17.4
-  checksum: 028dd57380f09b5641b74874a19073c53c4fb3f1696e849575aae18f8c80eaf21db75209057db862f3b893ce2cd9b795d539efa591b58f4a0fb011df0a56fbed
-  languageName: node
-  linkType: hard
-
-"babel-traverse@npm:^6.26.0":
-  version: 6.26.0
-  resolution: "babel-traverse@npm:6.26.0"
-  dependencies:
-    babel-code-frame: ^6.26.0
-    babel-messages: ^6.23.0
-    babel-runtime: ^6.26.0
-    babel-types: ^6.26.0
-    babylon: ^6.18.0
-    debug: ^2.6.8
-    globals: ^9.18.0
-    invariant: ^2.2.2
-    lodash: ^4.17.4
-  checksum: fca037588d2791ae0409f1b7aa56075b798699cccc53ea04d82dd1c0f97b9e7ab17065f7dd3ecd69101d7874c9c8fd5e0f88fa53abbae1fe94e37e6b81ebcb8d
-  languageName: node
-  linkType: hard
-
-"babel-types@npm:^6.26.0":
-  version: 6.26.0
-  resolution: "babel-types@npm:6.26.0"
-  dependencies:
-    babel-runtime: ^6.26.0
-    esutils: ^2.0.2
-    lodash: ^4.17.4
-    to-fast-properties: ^1.0.3
-  checksum: d16b0fa86e9b0e4c2623be81d0a35679faff24dd2e43cde4ca58baf49f3e39415a011a889e6c2259ff09e1228e4c3a3db6449a62de59e80152fe1ce7398fde76
-  languageName: node
-  linkType: hard
-
 "babylon@npm:^6.18.0":
   version: 6.18.0
   resolution: "babylon@npm:6.18.0"
 "babylon@npm:^6.18.0":
   version: 6.18.0
   resolution: "babylon@npm:6.18.0"
@@ -4560,6 +5211,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"bfj@npm:^7.0.2":
+  version: 7.1.0
+  resolution: "bfj@npm:7.1.0"
+  dependencies:
+    bluebird: ^3.7.2
+    check-types: ^11.2.3
+    hoopy: ^0.1.4
+    jsonpath: ^1.1.1
+    tryer: ^1.0.1
+  checksum: 36da9ed36c60f377a3f43bb0433092af7dc40442914b8155a1330ae86b1905640baf57e9c195ab83b36d6518b27cf8ed880adff663aa444c193be149e027d722
+  languageName: node
+  linkType: hard
+
 "big.js@npm:^5.2.2":
   version: 5.2.2
   resolution: "big.js@npm:5.2.2"
 "big.js@npm:^5.2.2":
   version: 5.2.2
   resolution: "big.js@npm:5.2.2"
@@ -4611,28 +5275,37 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1":
+"bn.js@npm:^5.0.0":
   version: 5.2.0
   resolution: "bn.js@npm:5.2.0"
   checksum: 6117170393200f68b35a061ecbf55d01dd989302e7b3c798a3012354fa638d124f0b2f79e63f77be5556be80322a09c40339eda6413ba7468524c0b6d4b4cb7a
   languageName: node
   linkType: hard
 
   version: 5.2.0
   resolution: "bn.js@npm:5.2.0"
   checksum: 6117170393200f68b35a061ecbf55d01dd989302e7b3c798a3012354fa638d124f0b2f79e63f77be5556be80322a09c40339eda6413ba7468524c0b6d4b4cb7a
   languageName: node
   linkType: hard
 
-"body-parser@npm:1.19.0":
-  version: 1.19.0
-  resolution: "body-parser@npm:1.19.0"
+"bn.js@npm:^5.2.1":
+  version: 5.2.1
+  resolution: "bn.js@npm:5.2.1"
+  checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3
+  languageName: node
+  linkType: hard
+
+"body-parser@npm:1.20.2":
+  version: 1.20.2
+  resolution: "body-parser@npm:1.20.2"
   dependencies:
   dependencies:
-    bytes: 3.1.0
-    content-type: ~1.0.4
+    bytes: 3.1.2
+    content-type: ~1.0.5
     debug: 2.6.9
     debug: 2.6.9
-    depd: ~1.1.2
-    http-errors: 1.7.2
+    depd: 2.0.0
+    destroy: 1.2.0
+    http-errors: 2.0.0
     iconv-lite: 0.4.24
     iconv-lite: 0.4.24
-    on-finished: ~2.3.0
-    qs: 6.7.0
-    raw-body: 2.4.0
-    type-is: ~1.6.17
-  checksum: 490231b4c89bbd43112762f7ba8e5342c174a6c9f64284a3b0fcabf63277e332f8316765596f1e5b15e4f3a6cf0422e005f4bb3149ed3a224bb025b7a36b9ac1
+    on-finished: 2.4.1
+    qs: 6.11.0
+    raw-body: 2.5.2
+    type-is: ~1.6.18
+    unpipe: 1.0.0
+  checksum: 14d37ec638ab5c93f6099ecaed7f28f890d222c650c69306872e00b9efa081ff6c596cd9afb9930656aae4d6c4e1c17537bea12bb73c87a217cb3cfea8896737
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -4733,16 +5406,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"browser-resolve@npm:^1.11.3":
-  version: 1.11.3
-  resolution: "browser-resolve@npm:1.11.3"
-  dependencies:
-    resolve: 1.1.7
-  checksum: 431bfc1a17406362a3010a2c35503eb7d1253dbcb8081c1ce236ddb0b954a33d52dcaf0b07f64c0f20394d6eeec1be4f6551da3734ce9ed5dcc38e876c96d5d5
-  languageName: node
-  linkType: hard
-
-"browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4":
+"browserify-aes@npm:^1.0.0, browserify-aes@npm:^1.0.4, browserify-aes@npm:^1.2.0":
   version: 1.2.0
   resolution: "browserify-aes@npm:1.2.0"
   dependencies:
   version: 1.2.0
   resolution: "browserify-aes@npm:1.2.0"
   dependencies:
@@ -4779,7 +5443,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.0.1":
+"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.1.0":
   version: 4.1.0
   resolution: "browserify-rsa@npm:4.1.0"
   dependencies:
   version: 4.1.0
   resolution: "browserify-rsa@npm:4.1.0"
   dependencies:
@@ -4790,19 +5454,20 @@ __metadata:
   linkType: hard
 
 "browserify-sign@npm:^4.0.0":
   linkType: hard
 
 "browserify-sign@npm:^4.0.0":
-  version: 4.2.1
-  resolution: "browserify-sign@npm:4.2.1"
+  version: 4.2.3
+  resolution: "browserify-sign@npm:4.2.3"
   dependencies:
   dependencies:
-    bn.js: ^5.1.1
-    browserify-rsa: ^4.0.1
+    bn.js: ^5.2.1
+    browserify-rsa: ^4.1.0
     create-hash: ^1.2.0
     create-hmac: ^1.1.7
     create-hash: ^1.2.0
     create-hmac: ^1.1.7
-    elliptic: ^6.5.3
+    elliptic: ^6.5.5
+    hash-base: ~3.0
     inherits: ^2.0.4
     inherits: ^2.0.4
-    parse-asn1: ^5.1.5
-    readable-stream: ^3.6.0
-    safe-buffer: ^5.2.0
-  checksum: 0221f190e3f5b2d40183fa51621be7e838d9caa329fe1ba773406b7637855f37b30f5d83e52ff8f244ed12ffe6278dd9983638609ed88c841ce547e603855707
+    parse-asn1: ^5.1.7
+    readable-stream: ^2.3.8
+    safe-buffer: ^5.2.1
+  checksum: 403a8061d229ae31266670345b4a7c00051266761d2c9bbeb68b1a9bcb05f68143b16110cf23a171a5d6716396a1f41296282b3e73eeec0a1871c77f0ff4ee6b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -4815,21 +5480,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"browserslist@npm:4.10.0":
-  version: 4.10.0
-  resolution: "browserslist@npm:4.10.0"
+"browserslist@npm:4.14.2":
+  version: 4.14.2
+  resolution: "browserslist@npm:4.14.2"
   dependencies:
   dependencies:
-    caniuse-lite: ^1.0.30001035
-    electron-to-chromium: ^1.3.378
-    node-releases: ^1.1.52
-    pkg-up: ^3.1.0
+    caniuse-lite: ^1.0.30001125
+    electron-to-chromium: ^1.3.564
+    escalade: ^3.0.2
+    node-releases: ^1.1.61
   bin:
     browserslist: cli.js
   bin:
     browserslist: cli.js
-  checksum: 35fdd9653656008a4f7a42026faa3e5ff3c5da83a39b7163675ae96985cbf8607beba55a877f2cf68f34cba7c8bb95418683664b663a051f094eb6d73dd4baf5
+  checksum: 44b5d7a444b867e1f027923f37a8ed537b4403f8a85a35869904e7d3e4071b37459df08d41ab4d425f5191f3125f1c5a191cbff9070f81f4d311803dc0a2fb0f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.16.6, browserslist@npm:^4.6.2, browserslist@npm:^4.6.4, browserslist@npm:^4.9.1":
+"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.16.6, browserslist@npm:^4.6.2, browserslist@npm:^4.6.4":
   version: 4.22.1
   resolution: "browserslist@npm:4.22.1"
   dependencies:
   version: 4.22.1
   resolution: "browserslist@npm:4.22.1"
   dependencies:
@@ -4843,6 +5508,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"browserslist@npm:^4.22.2, browserslist@npm:^4.23.0":
+  version: 4.23.0
+  resolution: "browserslist@npm:4.23.0"
+  dependencies:
+    caniuse-lite: ^1.0.30001587
+    electron-to-chromium: ^1.4.668
+    node-releases: ^2.0.14
+    update-browserslist-db: ^1.0.13
+  bin:
+    browserslist: cli.js
+  checksum: 436f49e796782ca751ebab7edc010cfc9c29f68536f387666cd70ea22f7105563f04dd62c6ff89cb24cc3254d17cba385f979eeeb3484d43e012412ff7e75def
+  languageName: node
+  linkType: hard
+
 "bser@npm:2.1.1":
   version: 2.1.1
   resolution: "bser@npm:2.1.1"
 "bser@npm:2.1.1":
   version: 2.1.1
   resolution: "bser@npm:2.1.1"
@@ -4908,6 +5587,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"builtin-modules@npm:^3.1.0":
+  version: 3.3.0
+  resolution: "builtin-modules@npm:3.3.0"
+  checksum: db021755d7ed8be048f25668fe2117620861ef6703ea2c65ed2779c9e3636d5c3b82325bd912244293959ff3ae303afa3471f6a15bf5060c103e4cc3a839749d
+  languageName: node
+  linkType: hard
+
 "builtin-status-codes@npm:^3.0.0":
   version: 3.0.0
   resolution: "builtin-status-codes@npm:3.0.0"
 "builtin-status-codes@npm:^3.0.0":
   version: 3.0.0
   resolution: "builtin-status-codes@npm:3.0.0"
@@ -4922,10 +5608,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"bytes@npm:3.1.0":
-  version: 3.1.0
-  resolution: "bytes@npm:3.1.0"
-  checksum: 7c3b21c5d9d44ed455460d5d36a31abc6fa2ce3807964ba60a4b03fd44454c8cf07bb0585af83bfde1c5cc2ea4bbe5897bc3d18cd15e0acf25a3615a35aba2df
+"bytes@npm:3.1.2":
+  version: 3.1.2
+  resolution: "bytes@npm:3.1.2"
+  checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -4952,33 +5638,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cacache@npm:^13.0.1":
-  version: 13.0.1
-  resolution: "cacache@npm:13.0.1"
-  dependencies:
-    chownr: ^1.1.2
-    figgy-pudding: ^3.5.1
-    fs-minipass: ^2.0.0
-    glob: ^7.1.4
-    graceful-fs: ^4.2.2
-    infer-owner: ^1.0.4
-    lru-cache: ^5.1.1
-    minipass: ^3.0.0
-    minipass-collect: ^1.0.2
-    minipass-flush: ^1.0.5
-    minipass-pipeline: ^1.2.2
-    mkdirp: ^0.5.1
-    move-concurrently: ^1.0.1
-    p-map: ^3.0.0
-    promise-inflight: ^1.0.1
-    rimraf: ^2.7.1
-    ssri: ^7.0.0
-    unique-filename: ^1.1.1
-  checksum: 733e65de5a0db3f1c181aa780f60ff121b5efd9b7c0851e1e1f213df768a790882d4d5af987fb0cfa70c5c6c4834e0474a291ac8872d227056f7ea12c1447092
-  languageName: node
-  linkType: hard
-
-"cacache@npm:^15.2.0, cacache@npm:^15.3.0":
+"cacache@npm:^15.0.5, cacache@npm:^15.2.0, cacache@npm:^15.3.0":
   version: 15.3.0
   resolution: "cacache@npm:15.3.0"
   dependencies:
   version: 15.3.0
   resolution: "cacache@npm:15.3.0"
   dependencies:
@@ -5064,10 +5724,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"call-me-maybe@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "call-me-maybe@npm:1.0.1"
-  checksum: d19e9d6ac2c6a83fb1215718b64c5e233f688ebebb603bdfe4af59cde952df1f2b648530fab555bf290ea910d69d7d9665ebc916e871e0e194f47c2e48e4886b
+"call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "call-bind@npm:1.0.7"
+  dependencies:
+    es-define-property: ^1.0.0
+    es-errors: ^1.3.0
+    function-bind: ^1.1.2
+    get-intrinsic: ^1.2.4
+    set-function-length: ^1.2.1
+  checksum: 295c0c62b90dd6522e6db3b0ab1ce26bdf9e7404215bda13cfee25b626b5ff1a7761324d58d38b1ef1607fc65aca2d06e44d2e18d0dfc6c14b465b00d8660029
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5113,16 +5779,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"camelcase-keys@npm:^2.0.0":
-  version: 2.1.0
-  resolution: "camelcase-keys@npm:2.1.0"
-  dependencies:
-    camelcase: ^2.0.0
-    map-obj: ^1.0.0
-  checksum: 97d2993da5db44d45e285910c70a54ce7f83a2be05afceaafd9831f7aeaf38a48dcdede5ca3aae2b2694852281d38dc459706e346942c5df0bf755f4133f5c39
-  languageName: node
-  linkType: hard
-
 "camelcase-keys@npm:^6.2.2":
   version: 6.2.2
   resolution: "camelcase-keys@npm:6.2.2"
 "camelcase-keys@npm:^6.2.2":
   version: 6.2.2
   resolution: "camelcase-keys@npm:6.2.2"
@@ -5141,17 +5797,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"camelcase@npm:^2.0.0":
-  version: 2.1.1
-  resolution: "camelcase@npm:2.1.1"
-  checksum: 20a3ef08f348de832631d605362ffe447d883ada89617144a82649363ed5860923b021f8e09681624ef774afb93ff3597cfbcf8aaf0574f65af7648f1aea5e50
-  languageName: node
-  linkType: hard
-
-"camelcase@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "camelcase@npm:3.0.0"
-  checksum: ae4fe1c17c8442a3a345a6b7d2393f028ab7a7601af0c352ad15d1ab97ca75112e19e29c942b2a214898e160194829b68923bce30e018d62149c6d84187f1673
+"camelcase@npm:^6.0.0, camelcase@npm:^6.1.0, camelcase@npm:^6.2.0":
+  version: 6.3.0
+  resolution: "camelcase@npm:6.3.0"
+  checksum: 8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5174,17 +5823,24 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"caniuse-lite@npm:1.0.30001299":
-  version: 1.0.30001299
-  resolution: "caniuse-lite@npm:1.0.30001299"
-  checksum: c770f60ebf3e0cc8043ba4db0ebec12d7a595a6b50cb4437c3c5c55b04de9d2413f711f2828be761e8c37bb46b927a8abe6b199b8f0ffc1a34af0ebdee84be27
+"caniuse-lite@npm:1.0.30001612":
+  version: 1.0.30001612
+  resolution: "caniuse-lite@npm:1.0.30001612"
+  checksum: 2b6ab6a19c72bdf8dccac824944e828a2a1fae52c6dfeb2d64ccecfd60d0466d2e5a392e996da2150d92850188a5034666dceed34a38d978177f6934e0bf106d
+  languageName: node
+  linkType: hard
+
+"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30000981, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001541, caniuse-lite@npm:^1.0.30001587":
+  version: 1.0.30001614
+  resolution: "caniuse-lite@npm:1.0.30001614"
+  checksum: 1b695625f9a1b08584c3c229d4b8deaebb89e7901a2a2ffe599a6250c0a79fc61afc49c374c32a76dbf593a5dedac3229bb0140bbacd438276211bdd1d7c4958
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30000981, caniuse-lite@npm:^1.0.30001035, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001541":
-  version: 1.0.30001593
-  resolution: "caniuse-lite@npm:1.0.30001593"
-  checksum: 3e2b19075563c3222101c8d5e6ab2f6e1ba99c3ad03b8d2449f9ee7ed03e9d3dac0b1fb24c129e9a5d89fdde4abb97392280c0abb113c0c60250a2b49f378c60
+"caniuse-lite@npm:^1.0.30001125":
+  version: 1.0.30001610
+  resolution: "caniuse-lite@npm:1.0.30001610"
+  checksum: 580c7367aafd7e524f4e3f0e8b22ac08d081a4d44ceece211f1758e214df9a87961750fb1e1ee28a2cd2830f0daf3edafe5e1d87bf1eefbbe7c6cf3d00e2979d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5211,7 +5867,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"chalk@npm:2.4.2, chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.1.0, chalk@npm:^2.3.0, chalk@npm:^2.4.1, chalk@npm:^2.4.2":
+"chalk@npm:2.4.2, chalk@npm:^2.0.0, chalk@npm:^2.3.0, chalk@npm:^2.4.1, chalk@npm:^2.4.2":
   version: 2.4.2
   resolution: "chalk@npm:2.4.2"
   dependencies:
   version: 2.4.2
   resolution: "chalk@npm:2.4.2"
   dependencies:
@@ -5222,19 +5878,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"chalk@npm:^1.1.3":
-  version: 1.1.3
-  resolution: "chalk@npm:1.1.3"
-  dependencies:
-    ansi-styles: ^2.2.1
-    escape-string-regexp: ^1.0.2
-    has-ansi: ^2.0.0
-    strip-ansi: ^3.0.0
-    supports-color: ^2.0.0
-  checksum: 9d2ea6b98fc2b7878829eec223abcf404622db6c48396a9b9257f6d0ead2acf18231ae368d6a664a83f272b0679158da12e97b5229f794939e555cc574478acd
-  languageName: node
-  linkType: hard
-
 "chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2":
   version: 4.1.2
   resolution: "chalk@npm:4.1.2"
 "chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2":
   version: 4.1.2
   resolution: "chalk@npm:4.1.2"
@@ -5252,10 +5895,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"chardet@npm:^0.7.0":
-  version: 0.7.0
-  resolution: "chardet@npm:0.7.0"
-  checksum: 6fd5da1f5d18ff5712c1e0aed41da200d7c51c28f11b36ee3c7b483f3696dabc08927fc6b227735eb8f0e1215c9a8abd8154637f3eff8cada5959df7f58b024d
+"char-regex@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "char-regex@npm:1.0.2"
+  checksum: b563e4b6039b15213114626621e7a3d12f31008bdce20f9c741d69987f62aeaace7ec30f6018890ad77b2e9b4d95324c9f5acfca58a9441e3b1dcdd1e2525d17
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5266,6 +5909,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"check-types@npm:^11.2.3":
+  version: 11.2.3
+  resolution: "check-types@npm:11.2.3"
+  checksum: f99ff09ae65e63cfcfa40a1275c0a70d8c43ffbf9ac35095f3bf030cc70361c92e075a9975a1144329e50b4fe4620be6bedb4568c18abc96071a3e23aed3ed8e
+  languageName: node
+  linkType: hard
+
 "cheerio-select@npm:^1.5.0":
   version: 1.5.0
   resolution: "cheerio-select@npm:1.5.0"
 "cheerio-select@npm:^1.5.0":
   version: 1.5.0
   resolution: "cheerio-select@npm:1.5.0"
@@ -5317,7 +5967,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"chokidar@npm:^3.3.0, chokidar@npm:^3.4.0, chokidar@npm:^3.4.1":
+"chokidar@npm:^3.4.1":
   version: 3.5.3
   resolution: "chokidar@npm:3.5.3"
   dependencies:
   version: 3.5.3
   resolution: "chokidar@npm:3.5.3"
   dependencies:
@@ -5336,7 +5986,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"chownr@npm:^1.1.1, chownr@npm:^1.1.2":
+"chownr@npm:^1.1.1":
   version: 1.1.4
   resolution: "chownr@npm:1.1.4"
   checksum: 115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d
   version: 1.1.4
   resolution: "chownr@npm:1.1.4"
   checksum: 115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d
@@ -5381,6 +6031,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"cjs-module-lexer@npm:^0.6.0":
+  version: 0.6.0
+  resolution: "cjs-module-lexer@npm:0.6.0"
+  checksum: 445b039607efd74561d7db8d0867031c8b6a69f25e83fdd861b0fa1fbc11f12de057ba1db80637f3c9016774354092af5325eebb90505d65ccc5389cae09d1fd
+  languageName: node
+  linkType: hard
+
 "class-autobind@npm:^0.1.4":
   version: 0.1.4
   resolution: "class-autobind@npm:0.1.4"
 "class-autobind@npm:^0.1.4":
   version: 0.1.4
   resolution: "class-autobind@npm:0.1.4"
@@ -5462,31 +6119,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cli-width@npm:^2.0.0":
-  version: 2.2.1
-  resolution: "cli-width@npm:2.2.1"
-  checksum: 3c21b897a2ff551ae5b3c3ab32c866ed2965dcf7fb442f81adf0e27f4a397925c8f84619af7bcc6354821303f6ee9b2aa31d248306174f32c287986158cf4eed
-  languageName: node
-  linkType: hard
-
-"cli-width@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "cli-width@npm:3.0.0"
-  checksum: 4c94af3769367a70e11ed69aa6095f1c600c0ff510f3921ab4045af961820d57c0233acfa8b6396037391f31b4c397e1f614d234294f979ff61430a6c166c3f6
-  languageName: node
-  linkType: hard
-
-"cliui@npm:^3.2.0":
-  version: 3.2.0
-  resolution: "cliui@npm:3.2.0"
-  dependencies:
-    string-width: ^1.0.1
-    strip-ansi: ^3.0.1
-    wrap-ansi: ^2.0.0
-  checksum: c68d1dbc3e347bfe79ed19cc7f48007d5edd6cd8438342e32073e0b4e311e3c44e1f4f19221462bc6590de56c2df520e427533a9dde95dee25710bec322746ad
-  languageName: node
-  linkType: hard
-
 "cliui@npm:^5.0.0":
   version: 5.0.0
   resolution: "cliui@npm:5.0.0"
 "cliui@npm:^5.0.0":
   version: 5.0.0
   resolution: "cliui@npm:5.0.0"
@@ -5498,6 +6130,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"cliui@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "cliui@npm:6.0.0"
+  dependencies:
+    string-width: ^4.2.0
+    strip-ansi: ^6.0.0
+    wrap-ansi: ^6.2.0
+  checksum: 4fcfd26d292c9f00238117f39fc797608292ae36bac2168cfee4c85923817d0607fe21b3329a8621e01aedf512c99b7eaa60e363a671ffd378df6649fb48ae42
+  languageName: node
+  linkType: hard
+
 "cliui@npm:^7.0.2":
   version: 7.0.4
   resolution: "cliui@npm:7.0.4"
 "cliui@npm:^7.0.2":
   version: 7.0.4
   resolution: "cliui@npm:7.0.4"
@@ -5520,19 +6163,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"clone-deep@npm:^0.2.4":
-  version: 0.2.4
-  resolution: "clone-deep@npm:0.2.4"
-  dependencies:
-    for-own: ^0.1.3
-    is-plain-object: ^2.0.1
-    kind-of: ^3.0.2
-    lazy-cache: ^1.0.3
-    shallow-clone: ^0.1.2
-  checksum: bcf9752052130c270c47d3e1c357497354b91d682f507e0079bec5950975b3293b619d9e100d70874606d716f2376e84956b045759a09af703e1038ecad6c438
-  languageName: node
-  linkType: hard
-
 "clone-deep@npm:^4.0.1":
   version: 4.0.1
   resolution: "clone-deep@npm:4.0.1"
 "clone-deep@npm:^4.0.1":
   version: 4.0.1
   resolution: "clone-deep@npm:4.0.1"
@@ -5569,10 +6199,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"code-point-at@npm:^1.0.0":
-  version: 1.1.0
-  resolution: "code-point-at@npm:1.1.0"
-  checksum: 17d5666611f9b16d64fdf48176d9b7fb1c7d1c1607a189f7e600040a11a6616982876af148230336adb7d8fe728a559f743a4e29db3747e3b1a32fa7f4529681
+"collect-v8-coverage@npm:^1.0.0":
+  version: 1.0.2
+  resolution: "collect-v8-coverage@npm:1.0.2"
+  checksum: c10f41c39ab84629d16f9f6137bc8a63d332244383fc368caf2d2052b5e04c20cd1fd70f66fcf4e2422b84c8226598b776d39d5f2d2a51867cc1ed5d1982b4da
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5628,7 +6258,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"color-support@npm:^1.1.2, color-support@npm:^1.1.3":
+"color-support@npm:^1.1.3":
   version: 1.1.3
   resolution: "color-support@npm:1.1.3"
   bin:
   version: 1.1.3
   resolution: "color-support@npm:1.1.3"
   bin:
@@ -5661,7 +6291,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"combined-stream@npm:^1.0.6, combined-stream@npm:~1.0.6":
+"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6":
   version: 1.0.8
   resolution: "combined-stream@npm:1.0.8"
   dependencies:
   version: 1.0.8
   resolution: "combined-stream@npm:1.0.8"
   dependencies:
@@ -5670,7 +6300,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"commander@npm:^2.11.0, commander@npm:^2.12.1, commander@npm:^2.19.0, commander@npm:^2.20.0":
+"commander@npm:^2.12.1, commander@npm:^2.19.0, commander@npm:^2.20.0":
   version: 2.20.3
   resolution: "commander@npm:2.20.3"
   checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e
   version: 2.20.3
   resolution: "commander@npm:2.20.3"
   checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e
@@ -5764,10 +6394,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"confusing-browser-globals@npm:^1.0.9":
-  version: 1.0.10
-  resolution: "confusing-browser-globals@npm:1.0.10"
-  checksum: 7ccdc44c2ca419cf6576c3e4336106e18d1c5337f547e461342f51aec4a10f96fdfe45414b522be3c7d24ea0b62bf4372cd37768022e4d6161707ffb2c0987e6
+"confusing-browser-globals@npm:^1.0.10":
+  version: 1.0.11
+  resolution: "confusing-browser-globals@npm:1.0.11"
+  checksum: 3afc635abd37e566477f610e7978b15753f0e84025c25d49236f1f14d480117185516bdd40d2a2167e6bed8048641a9854964b9c067e3dcdfa6b5d0ad3c3a5ef
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5785,7 +6415,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0":
+"console-control-strings@npm:^1.1.0":
   version: 1.1.0
   resolution: "console-control-strings@npm:1.1.0"
   checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed
   version: 1.1.0
   resolution: "console-control-strings@npm:1.1.0"
   checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed
@@ -5799,19 +6429,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"contains-path@npm:^0.1.0":
-  version: 0.1.0
-  resolution: "contains-path@npm:0.1.0"
-  checksum: 94ecfd944e0bc51be8d3fc596dcd17d705bd4c8a1a627952a3a8c5924bac01c7ea19034cf40b4b4f89e576cdead130a7e5fd38f5f7f07ef67b4b261d875871e3
-  languageName: node
-  linkType: hard
-
-"content-disposition@npm:0.5.3":
-  version: 0.5.3
-  resolution: "content-disposition@npm:0.5.3"
+"content-disposition@npm:0.5.4":
+  version: 0.5.4
+  resolution: "content-disposition@npm:0.5.4"
   dependencies:
   dependencies:
-    safe-buffer: 5.1.2
-  checksum: 95bf164c0b0b8199d3f44b7631e51b37f683c6a90b9baa4315bd3d405a6d1bc81b7346f0981046aa004331fb3d7a28b629514d01fc209a5251573fc7e4d33380
+    safe-buffer: 5.2.1
+  checksum: afb9d545e296a5171d7574fcad634b2fdf698875f4006a9dd04a3e1333880c5c0c98d47b560d01216fb6505a54a2ba6a843ee3a02ec86d7e911e8315255f56c3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5822,6 +6445,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"content-type@npm:~1.0.5":
+  version: 1.0.5
+  resolution: "content-type@npm:1.0.5"
+  checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766
+  languageName: node
+  linkType: hard
+
 "convert-source-map@npm:1.7.0":
   version: 1.7.0
   resolution: "convert-source-map@npm:1.7.0"
 "convert-source-map@npm:1.7.0":
   version: 1.7.0
   resolution: "convert-source-map@npm:1.7.0"
@@ -5838,7 +6468,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.5.1, convert-source-map@npm:^1.7.0":
+"convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.7.0":
   version: 1.8.0
   resolution: "convert-source-map@npm:1.8.0"
   dependencies:
   version: 1.8.0
   resolution: "convert-source-map@npm:1.8.0"
   dependencies:
@@ -5847,6 +6477,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"convert-source-map@npm:^1.6.0":
+  version: 1.9.0
+  resolution: "convert-source-map@npm:1.9.0"
+  checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8
+  languageName: node
+  linkType: hard
+
+"convert-source-map@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "convert-source-map@npm:2.0.0"
+  checksum: 63ae9933be5a2b8d4509daca5124e20c14d023c820258e484e32dc324d34c2754e71297c94a05784064ad27615037ef677e3f0c00469fb55f409d2bb21261035
+  languageName: node
+  linkType: hard
+
 "cookie-signature@npm:1.0.6":
   version: 1.0.6
   resolution: "cookie-signature@npm:1.0.6"
 "cookie-signature@npm:1.0.6":
   version: 1.0.6
   resolution: "cookie-signature@npm:1.0.6"
@@ -5854,10 +6498,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cookie@npm:0.4.0":
-  version: 0.4.0
-  resolution: "cookie@npm:0.4.0"
-  checksum: 760384ba0aef329c52523747e36a452b5e51bc49b34160363a6934e7b7df3f93fcc88b35e33450361535d40a92a96412da870e1816aba9aa6cc556a9fedd8492
+"cookie@npm:0.6.0":
+  version: 0.6.0
+  resolution: "cookie@npm:0.6.0"
+  checksum: f56a7d32a07db5458e79c726b77e3c2eff655c36792f2b6c58d351fb5f61531e5b1ab7f46987150136e366c65213cbe31729e02a3eaed630c3bf7334635fb410
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5891,20 +6535,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"core-js-compat@npm:^3.14.0, core-js-compat@npm:^3.15.0, core-js-compat@npm:^3.6.2":
-  version: 3.15.1
-  resolution: "core-js-compat@npm:3.15.1"
+"core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.36.1":
+  version: 3.36.1
+  resolution: "core-js-compat@npm:3.36.1"
   dependencies:
   dependencies:
-    browserslist: ^4.16.6
-    semver: 7.0.0
-  checksum: cf2fb3406c7fd82edee3ccf9e55e538cf75da79845d5dbffaf979cb9e73e26943ee6e7d07c5cbc50c5909fba1c5a4ca499d0f249fdb491da45b40f8584a4c761
-  languageName: node
-  linkType: hard
-
-"core-js-pure@npm:^3.15.0":
-  version: 3.15.1
-  resolution: "core-js-pure@npm:3.15.1"
-  checksum: e4053f6f3ab4268f991d76f4c3f918cfa5a95182d0c5ddcc32d381bc208318b5817db8fb01d531363a7d110b46ea1c6ffb14e832f661bfea3e213d52d9b92658
+    browserslist: ^4.23.0
+  checksum: c9109bd599a97b5d20f25fc8b8339b8c7f3fca5f9a1bebd397805383ff7699e117786c7ffe0f7a95058a6fa5e0e1435d4c10e5cda6ad86ce1957986bb6580562
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -5915,20 +6551,27 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"core-js@npm:^2.4.0, core-js@npm:^2.5.0":
+"core-js@npm:^2.4.0, core-js@npm:^2.5.0, core-js@npm:^2.6.12":
   version: 2.6.12
   resolution: "core-js@npm:2.6.12"
   checksum: 44fa9934a85f8c78d61e0c8b7b22436330471ffe59ec5076fe7f324d6e8cf7f824b14b1c81ca73608b13bdb0fef035bd820989bf059767ad6fa13123bb8bd016
   languageName: node
   linkType: hard
 
   version: 2.6.12
   resolution: "core-js@npm:2.6.12"
   checksum: 44fa9934a85f8c78d61e0c8b7b22436330471ffe59ec5076fe7f324d6e8cf7f824b14b1c81ca73608b13bdb0fef035bd820989bf059767ad6fa13123bb8bd016
   languageName: node
   linkType: hard
 
-"core-js@npm:^3.5.0, core-js@npm:^3.6.4":
+"core-js@npm:^3.6.4":
   version: 3.15.1
   resolution: "core-js@npm:3.15.1"
   checksum: d44c1099b4028bee17990473df0b508ad0f6701aba9e13055183fe4a8bd1459e9e22f22b8e6c0b0a6ac0974b404672df47d52be3341a776a227fc368f2aa1fbe
   languageName: node
   linkType: hard
 
   version: 3.15.1
   resolution: "core-js@npm:3.15.1"
   checksum: d44c1099b4028bee17990473df0b508ad0f6701aba9e13055183fe4a8bd1459e9e22f22b8e6c0b0a6ac0974b404672df47d52be3341a776a227fc368f2aa1fbe
   languageName: node
   linkType: hard
 
+"core-js@npm:^3.6.5":
+  version: 3.36.1
+  resolution: "core-js@npm:3.36.1"
+  checksum: 6f6c152179bd0673da34e67a82c6a5c37f31f9fbe908e9caf93749dc62a25b6e07fbff2411de3b74bb2d0661b7f9fb247115ba8efabf9904f5fef26edead515e
+  languageName: node
+  linkType: hard
+
 "core-util-is@npm:1.0.2, core-util-is@npm:~1.0.0":
   version: 1.0.2
   resolution: "core-util-is@npm:1.0.2"
 "core-util-is@npm:1.0.2, core-util-is@npm:~1.0.0":
   version: 1.0.2
   resolution: "core-util-is@npm:1.0.2"
@@ -5936,7 +6579,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cosmiconfig@npm:^5.0.0, cosmiconfig@npm:^5.2.1":
+"cosmiconfig@npm:^5.0.0":
   version: 5.2.1
   resolution: "cosmiconfig@npm:5.2.1"
   dependencies:
   version: 5.2.1
   resolution: "cosmiconfig@npm:5.2.1"
   dependencies:
@@ -5948,16 +6591,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cosmiconfig@npm:^6.0.0":
-  version: 6.0.0
-  resolution: "cosmiconfig@npm:6.0.0"
+"cosmiconfig@npm:^7.0.0":
+  version: 7.1.0
+  resolution: "cosmiconfig@npm:7.1.0"
   dependencies:
     "@types/parse-json": ^4.0.0
   dependencies:
     "@types/parse-json": ^4.0.0
-    import-fresh: ^3.1.0
+    import-fresh: ^3.2.1
     parse-json: ^5.0.0
     path-type: ^4.0.0
     parse-json: ^5.0.0
     path-type: ^4.0.0
-    yaml: ^1.7.2
-  checksum: 8eed7c854b91643ecb820767d0deb038b50780ecc3d53b0b19e03ed8aabed4ae77271198d1ae3d49c3b110867edf679f5faad924820a8d1774144a87cb6f98fc
+    yaml: ^1.10.0
+  checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -6007,38 +6650,27 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cross-spawn@npm:7.0.1":
-  version: 7.0.1
-  resolution: "cross-spawn@npm:7.0.1"
+"cross-spawn@npm:7.0.3, cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
+  version: 7.0.3
+  resolution: "cross-spawn@npm:7.0.3"
   dependencies:
     path-key: ^3.1.0
     shebang-command: ^2.0.0
     which: ^2.0.1
   dependencies:
     path-key: ^3.1.0
     shebang-command: ^2.0.0
     which: ^2.0.1
-  checksum: 5c1c52be2d24f0ada793920bf0beca61ea9cc03bb5c400617ddfd2c03f10ed86a0c39fb67bcf2cee91ec4dd7e9f1595ed9c40f84352d2881937bf861281f651a
+  checksum: 671cc7c7288c3a8406f3c69a3ae2fc85555c04169e9d611def9a675635472614f1c0ed0ef80955d5b6d4e724f6ced67f0ad1bb006c2ea643488fcfef994d7f52
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cross-spawn@npm:^6.0.0, cross-spawn@npm:^6.0.5":
+"cross-spawn@npm:^6.0.0":
   version: 6.0.5
   resolution: "cross-spawn@npm:6.0.5"
   dependencies:
     nice-try: ^1.0.4
   version: 6.0.5
   resolution: "cross-spawn@npm:6.0.5"
   dependencies:
     nice-try: ^1.0.4
-    path-key: ^2.0.1
-    semver: ^5.5.0
-    shebang-command: ^1.2.0
-    which: ^1.2.9
-  checksum: f893bb0d96cd3d5751d04e67145bdddf25f99449531a72e82dcbbd42796bbc8268c1076c6b3ea51d4d455839902804b94bc45dfb37ecbb32ea8e54a6741c3ab9
-  languageName: node
-  linkType: hard
-
-"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3":
-  version: 7.0.3
-  resolution: "cross-spawn@npm:7.0.3"
-  dependencies:
-    path-key: ^3.1.0
-    shebang-command: ^2.0.0
-    which: ^2.0.1
-  checksum: 671cc7c7288c3a8406f3c69a3ae2fc85555c04169e9d611def9a675635472614f1c0ed0ef80955d5b6d4e724f6ced67f0ad1bb006c2ea643488fcfef994d7f52
+    path-key: ^2.0.1
+    semver: ^5.5.0
+    shebang-command: ^1.2.0
+    which: ^1.2.9
+  checksum: f893bb0d96cd3d5751d04e67145bdddf25f99449531a72e82dcbbd42796bbc8268c1076c6b3ea51d4d455839902804b94bc45dfb37ecbb32ea8e54a6741c3ab9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -6061,6 +6693,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"crypto-random-string@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "crypto-random-string@npm:1.0.0"
+  checksum: 6fc61a46c18547b49a93da24f4559c4a1c859f4ee730ecc9533c1ba89fa2a9e9d81f390c2789467afbbd0d1c55a6e96a71e4716b6cd3e77736ed5fced7a2df9a
+  languageName: node
+  linkType: hard
+
 "css-blank-pseudo@npm:^0.1.4":
   version: 0.1.4
   resolution: "css-blank-pseudo@npm:0.1.4"
 "css-blank-pseudo@npm:^0.1.4":
   version: 0.1.4
   resolution: "css-blank-pseudo@npm:0.1.4"
@@ -6108,25 +6747,25 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"css-loader@npm:3.4.2":
-  version: 3.4.2
-  resolution: "css-loader@npm:3.4.2"
+"css-loader@npm:4.3.0":
+  version: 4.3.0
+  resolution: "css-loader@npm:4.3.0"
   dependencies:
   dependencies:
-    camelcase: ^5.3.1
+    camelcase: ^6.0.0
     cssesc: ^3.0.0
     icss-utils: ^4.1.1
     cssesc: ^3.0.0
     icss-utils: ^4.1.1
-    loader-utils: ^1.2.3
-    normalize-path: ^3.0.0
-    postcss: ^7.0.23
+    loader-utils: ^2.0.0
+    postcss: ^7.0.32
     postcss-modules-extract-imports: ^2.0.0
     postcss-modules-extract-imports: ^2.0.0
-    postcss-modules-local-by-default: ^3.0.2
-    postcss-modules-scope: ^2.1.1
+    postcss-modules-local-by-default: ^3.0.3
+    postcss-modules-scope: ^2.2.0
     postcss-modules-values: ^3.0.0
     postcss-modules-values: ^3.0.0
-    postcss-value-parser: ^4.0.2
-    schema-utils: ^2.6.0
+    postcss-value-parser: ^4.1.0
+    schema-utils: ^2.7.1
+    semver: ^7.3.2
   peerDependencies:
   peerDependencies:
-    webpack: ^4.0.0 || ^5.0.0
-  checksum: dbd80f052b41ea7c33d96a2fbeabca82773f7e3567300c636ffb079ffcf8ba111b02f315346942334ed27ffc137323c9a4ac1e446eaed5837abbdd3fdd371a0c
+    webpack: ^4.27.0 || ^5.0.0
+  checksum: 697a8838f0975f86c634e7a920572604879a9738128fcc01e5393fae5ac9a7a1a925c0d14ebb6ed67fa7e14bd17849eec152a99e3299cc92f422f6b0cd4eff73
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -6353,19 +6992,26 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"cssom@npm:0.3.x, cssom@npm:>= 0.3.2 < 0.4.0, cssom@npm:^0.3.4":
+"cssom@npm:^0.4.4":
+  version: 0.4.4
+  resolution: "cssom@npm:0.4.4"
+  checksum: e3bc1076e7ee4213d4fef05e7ae03bfa83dc05f32611d8edc341f4ecc3d9647b89c8245474c7dd2cdcdb797a27c462e99da7ad00a34399694559f763478ff53f
+  languageName: node
+  linkType: hard
+
+"cssom@npm:~0.3.6":
   version: 0.3.8
   resolution: "cssom@npm:0.3.8"
   checksum: 24beb3087c76c0d52dd458be9ee1fbc80ac771478a9baef35dd258cdeb527c68eb43204dd439692bb2b1ae5272fa5f2946d10946edab0d04f1078f85e06bc7f6
   languageName: node
   linkType: hard
 
   version: 0.3.8
   resolution: "cssom@npm:0.3.8"
   checksum: 24beb3087c76c0d52dd458be9ee1fbc80ac771478a9baef35dd258cdeb527c68eb43204dd439692bb2b1ae5272fa5f2946d10946edab0d04f1078f85e06bc7f6
   languageName: node
   linkType: hard
 
-"cssstyle@npm:^1.0.0, cssstyle@npm:^1.1.1":
-  version: 1.4.0
-  resolution: "cssstyle@npm:1.4.0"
+"cssstyle@npm:^2.3.0":
+  version: 2.3.0
+  resolution: "cssstyle@npm:2.3.0"
   dependencies:
   dependencies:
-    cssom: 0.3.x
-  checksum: 7efb9731d68dd042f32e0e3bbc7c1096653ba521f21ab1c5b158862321e4fcbfb51070641b834fadc8dd070a634dd43f328177e00d1b8481b5143a3e09f3d3f6
+    cssom: ~0.3.6
+  checksum: 5f05e6fd2e3df0b44695c2f08b9ef38b011862b274e320665176467c0725e44a53e341bc4959a41176e83b66064ab786262e7380fd1cabeae6efee0d255bb4e3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -6390,15 +7036,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"currently-unhandled@npm:^0.4.1":
-  version: 0.4.1
-  resolution: "currently-unhandled@npm:0.4.1"
-  dependencies:
-    array-find-index: ^1.0.1
-  checksum: 1f59fe10b5339b54b1a1eee110022f663f3495cf7cf2f480686e89edc7fa8bfe42dbab4b54f85034bc8b092a76cc7becbc2dad4f9adad332ab5831bec39ad540
-  languageName: node
-  linkType: hard
-
 "cwlts@npm:1.15.29":
   version: 1.15.29
   resolution: "cwlts@npm:1.15.29"
 "cwlts@npm:1.15.29":
   version: 1.15.29
   resolution: "cwlts@npm:1.15.29"
@@ -6485,10 +7122,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"damerau-levenshtein@npm:^1.0.4":
-  version: 1.0.7
-  resolution: "damerau-levenshtein@npm:1.0.7"
-  checksum: ec8161cb381523e0db9b5c9b64863736da3197808b6fdc4a3a2ca764c0b4357e9232a4c5592220fb18755a91240b8fee7b13ab1b269fbbdc5f68c36f0053aceb
+"d@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "d@npm:1.0.2"
+  dependencies:
+    es5-ext: ^0.10.64
+    type: ^2.7.2
+  checksum: 775db1e8ced6707cddf64a5840522fcf5475d38ef49a5d615be0ac47f86ef64d15f5a73de1522b09327cc466d4dc35ea83dbfeed456f7a0fdcab138deb800355
+  languageName: node
+  linkType: hard
+
+"damerau-levenshtein@npm:^1.0.8":
+  version: 1.0.8
+  resolution: "damerau-levenshtein@npm:1.0.8"
+  checksum: d240b7757544460ae0586a341a53110ab0a61126570ef2d8c731e3eab3f0cb6e488e2609e6a69b46727635de49be20b071688698744417ff1b6c1d7ccd03e0de
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -6501,14 +7148,47 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"data-urls@npm:^1.0.0, data-urls@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "data-urls@npm:1.1.0"
+"data-urls@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "data-urls@npm:2.0.0"
+  dependencies:
+    abab: ^2.0.3
+    whatwg-mimetype: ^2.3.0
+    whatwg-url: ^8.0.0
+  checksum: 97caf828aac25e25e04ba6869db0f99c75e6859bb5b424ada28d3e7841941ebf08ddff3c1b1bb4585986bd507a5d54c2a716853ea6cb98af877400e637393e71
+  languageName: node
+  linkType: hard
+
+"data-view-buffer@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "data-view-buffer@npm:1.0.1"
+  dependencies:
+    call-bind: ^1.0.6
+    es-errors: ^1.3.0
+    is-data-view: ^1.0.1
+  checksum: ce24348f3c6231223b216da92e7e6a57a12b4af81a23f27eff8feabdf06acfb16c00639c8b705ca4d167f761cfc756e27e5f065d0a1f840c10b907fdaf8b988c
+  languageName: node
+  linkType: hard
+
+"data-view-byte-length@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "data-view-byte-length@npm:1.0.1"
+  dependencies:
+    call-bind: ^1.0.7
+    es-errors: ^1.3.0
+    is-data-view: ^1.0.1
+  checksum: dbb3200edcb7c1ef0d68979834f81d64fd8cab2f7691b3a4c6b97e67f22182f3ec2c8602efd7b76997b55af6ff8bce485829c1feda4fa2165a6b71fb7baa4269
+  languageName: node
+  linkType: hard
+
+"data-view-byte-offset@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "data-view-byte-offset@npm:1.0.0"
   dependencies:
   dependencies:
-    abab: ^2.0.0
-    whatwg-mimetype: ^2.2.0
-    whatwg-url: ^7.0.0
-  checksum: dc4bd9621df0dff336d7c4c0517c792488ef3cf11cd37e72ab80f3a7f0a0aa14bad677ac97cf22c87c6eb9518e58b98590e1c8c756b56240940f0e470c81612e
+    call-bind: ^1.0.6
+    es-errors: ^1.3.0
+    is-data-view: ^1.0.1
+  checksum: 7f0bf8720b7414ca719eedf1846aeec392f2054d7af707c5dc9a753cc77eb8625f067fa901e0b5127e831f9da9056138d894b9c2be79c27a21f6db5824f009c2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -6540,7 +7220,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.6.0, debug@npm:^2.6.8, debug@npm:^2.6.9":
+"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.3.3, debug@npm:^2.6.0":
   version: 2.6.9
   resolution: "debug@npm:2.6.9"
   dependencies:
   version: 2.6.9
   resolution: "debug@npm:2.6.9"
   dependencies:
@@ -6604,13 +7284,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"decamelize@npm:^1.1.0, decamelize@npm:^1.1.1, decamelize@npm:^1.1.2, decamelize@npm:^1.2.0":
+"decamelize@npm:^1.1.0, decamelize@npm:^1.2.0":
   version: 1.2.0
   resolution: "decamelize@npm:1.2.0"
   checksum: ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa
   languageName: node
   linkType: hard
 
   version: 1.2.0
   resolution: "decamelize@npm:1.2.0"
   checksum: ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa
   languageName: node
   linkType: hard
 
+"decimal.js@npm:^10.2.1":
+  version: 10.4.3
+  resolution: "decimal.js@npm:10.4.3"
+  checksum: 796404dcfa9d1dbfdc48870229d57f788b48c21c603c3f6554a1c17c10195fc1024de338b0cf9e1efe0c7c167eeb18f04548979bcc5fdfabebb7cc0ae3287bae
+  languageName: node
+  linkType: hard
+
 "decode-uri-component@npm:^0.2.0":
   version: 0.2.2
   resolution: "decode-uri-component@npm:0.2.2"
 "decode-uri-component@npm:^0.2.0":
   version: 0.2.2
   resolution: "decode-uri-component@npm:0.2.2"
@@ -6618,6 +7305,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"dedent@npm:^0.7.0":
+  version: 0.7.0
+  resolution: "dedent@npm:0.7.0"
+  checksum: 87de191050d9a40dd70cad01159a0bcf05ecb59750951242070b6abf9569088684880d00ba92a955b4058804f16eeaf91d604f283929b4f614d181cd7ae633d2
+  languageName: node
+  linkType: hard
+
 "deep-equal@npm:^1.0.1":
   version: 1.1.1
   resolution: "deep-equal@npm:1.1.1"
 "deep-equal@npm:^1.0.1":
   version: 1.1.1
   resolution: "deep-equal@npm:1.1.1"
@@ -6632,6 +7326,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"deep-is@npm:^0.1.3":
+  version: 0.1.4
+  resolution: "deep-is@npm:0.1.4"
+  checksum: edb65dd0d7d1b9c40b2f50219aef30e116cedd6fc79290e740972c132c09106d2e80aa0bc8826673dd5a00222d4179c84b36a790eef63a4c4bca75a37ef90804
+  languageName: node
+  linkType: hard
+
 "deep-is@npm:~0.1.3":
   version: 0.1.3
   resolution: "deep-is@npm:0.1.3"
 "deep-is@npm:~0.1.3":
   version: 0.1.3
   resolution: "deep-is@npm:0.1.3"
@@ -6646,6 +7347,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"deepmerge@npm:^4.2.2":
+  version: 4.3.1
+  resolution: "deepmerge@npm:4.3.1"
+  checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052
+  languageName: node
+  linkType: hard
+
 "default-gateway@npm:^4.2.0":
   version: 4.2.0
   resolution: "default-gateway@npm:4.2.0"
 "default-gateway@npm:^4.2.0":
   version: 4.2.0
   resolution: "default-gateway@npm:4.2.0"
@@ -6656,6 +7364,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4":
+  version: 1.1.4
+  resolution: "define-data-property@npm:1.1.4"
+  dependencies:
+    es-define-property: ^1.0.0
+    es-errors: ^1.3.0
+    gopd: ^1.0.1
+  checksum: 8068ee6cab694d409ac25936eb861eea704b7763f7f342adbdfe337fc27c78d7ae0eff2364b2917b58c508d723c7a074326d068eef2e45c4edcd85cf94d0313b
+  languageName: node
+  linkType: hard
+
 "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3":
   version: 1.1.3
   resolution: "define-properties@npm:1.1.3"
 "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3":
   version: 1.1.3
   resolution: "define-properties@npm:1.1.3"
@@ -6665,6 +7384,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"define-properties@npm:^1.2.0, define-properties@npm:^1.2.1":
+  version: 1.2.1
+  resolution: "define-properties@npm:1.2.1"
+  dependencies:
+    define-data-property: ^1.0.1
+    has-property-descriptors: ^1.0.0
+    object-keys: ^1.1.1
+  checksum: b4ccd00597dd46cb2d4a379398f5b19fca84a16f3374e2249201992f36b30f6835949a9429669ee6b41b6e837205a163eadd745e472069e70dfc10f03e5fcc12
+  languageName: node
+  linkType: hard
+
 "define-property@npm:^0.2.5":
   version: 0.2.5
   resolution: "define-property@npm:0.2.5"
 "define-property@npm:^0.2.5":
   version: 0.2.5
   resolution: "define-property@npm:0.2.5"
@@ -6722,6 +7452,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"depd@npm:2.0.0":
+  version: 2.0.0
+  resolution: "depd@npm:2.0.0"
+  checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a
+  languageName: node
+  linkType: hard
+
 "depd@npm:^1.1.2, depd@npm:~1.1.2":
   version: 1.1.2
   resolution: "depd@npm:1.1.2"
 "depd@npm:^1.1.2, depd@npm:~1.1.2":
   version: 1.1.2
   resolution: "depd@npm:1.1.2"
@@ -6729,6 +7466,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"dequal@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "dequal@npm:2.0.3"
+  checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90
+  languageName: node
+  linkType: hard
+
 "des.js@npm:^1.0.0":
   version: 1.0.1
   resolution: "des.js@npm:1.0.1"
 "des.js@npm:^1.0.0":
   version: 1.0.1
   resolution: "des.js@npm:1.0.1"
@@ -6739,26 +7483,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"destroy@npm:~1.0.4":
-  version: 1.0.4
-  resolution: "destroy@npm:1.0.4"
-  checksum: da9ab4961dc61677c709da0c25ef01733042614453924d65636a7db37308fef8a24cd1e07172e61173d471ca175371295fbc984b0af5b2b4ff47cd57bd784c03
-  languageName: node
-  linkType: hard
-
-"detect-indent@npm:^4.0.0":
-  version: 4.0.0
-  resolution: "detect-indent@npm:4.0.0"
-  dependencies:
-    repeating: ^2.0.0
-  checksum: 328f273915c1610899bc7d4784ce874413d0a698346364cd3ee5d79afba1c5cf4dbc97b85a801e20f4d903c0598bd5096af32b800dfb8696b81464ccb3dfda2c
+"destroy@npm:1.2.0":
+  version: 1.2.0
+  resolution: "destroy@npm:1.2.0"
+  checksum: 0acb300b7478a08b92d810ab229d5afe0d2f4399272045ab22affa0d99dbaf12637659411530a6fcd597a9bdac718fc94373a61a95b4651bbc7b83684a565e38
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"detect-newline@npm:^2.1.0":
-  version: 2.1.0
-  resolution: "detect-newline@npm:2.1.0"
-  checksum: c55146fd5b97a9ce914f17f85a01466c9e8679289e2d390588b027a58f2e090dbc38457923072369c603b8904f982f87b78fee17e48d5706f35571642f4599f8
+"detect-newline@npm:^3.0.0":
+  version: 3.1.0
+  resolution: "detect-newline@npm:3.1.0"
+  checksum: ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -6782,13 +7517,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"diff-sequences@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "diff-sequences@npm:24.9.0"
-  checksum: b81f906ff1737e0a65e8f7ee3ad1d27b426dcc25498731365aeaccc32333da3bf3a7100c963c7104f12c8e64e545114d4fe4c0b90daf2565b0b00b79f0df45c4
-  languageName: node
-  linkType: hard
-
 "diff-sequences@npm:^26.6.2":
   version: 26.6.2
   resolution: "diff-sequences@npm:26.6.2"
 "diff-sequences@npm:^26.6.2":
   version: 26.6.2
   resolution: "diff-sequences@npm:26.6.2"
@@ -6821,16 +7549,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"dir-glob@npm:2.0.0":
-  version: 2.0.0
-  resolution: "dir-glob@npm:2.0.0"
-  dependencies:
-    arrify: ^1.0.1
-    path-type: ^3.0.0
-  checksum: adc4dc5dd9d2cc0a9ce864e52f9ac1c93e34487720fbed68bdf94cef7a9d88be430cc565300750571589dd35e168d0b286120317c0797f83a7cd8e6d9c69fcb7
-  languageName: node
-  linkType: hard
-
 "dir-glob@npm:^3.0.1":
   version: 3.0.1
   resolution: "dir-glob@npm:3.0.1"
 "dir-glob@npm:^3.0.1":
   version: 3.0.1
   resolution: "dir-glob@npm:3.0.1"
@@ -6885,16 +7603,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"doctrine@npm:1.5.0":
-  version: 1.5.0
-  resolution: "doctrine@npm:1.5.0"
-  dependencies:
-    esutils: ^2.0.2
-    isarray: ^1.0.0
-  checksum: 7ce8102a05cbb9d942d49db5461d2f3dd1208ebfed929bf1c04770a1ef6ef540b792e63c45eae4c51f8b16075e0af4a73581a06bad31c37ceb0988f2e398509b
-  languageName: node
-  linkType: hard
-
 "doctrine@npm:^2.1.0":
   version: 2.1.0
   resolution: "doctrine@npm:2.1.0"
 "doctrine@npm:^2.1.0":
   version: 2.1.0
   resolution: "doctrine@npm:2.1.0"
@@ -6973,12 +7681,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"domexception@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "domexception@npm:1.0.1"
+"domexception@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "domexception@npm:2.0.1"
   dependencies:
   dependencies:
-    webidl-conversions: ^4.0.2
-  checksum: f564a9c0915dcb83ceefea49df14aaed106b1468fbe505119e8bcb0b77e242534f3aba861978537c0fc9dc6f35b176d0ffc77b3e342820fb27a8f215e7ae4d52
+    webidl-conversions: ^5.0.0
+  checksum: d638e9cb05c52999f1b2eb87c374b03311ea5b1d69c2f875bc92da73e17db60c12142b45c950228642ff7f845c536b65305483350d080df59003a653da80b691
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -7194,10 +7902,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"electron-to-chromium@npm:^1.3.378":
-  version: 1.3.758
-  resolution: "electron-to-chromium@npm:1.3.758"
-  checksum: 2fec13dcdd1b24a2314d309566bd08c7f0ce383787e64ea43c14a7fc2a11c8a76fdb9a56ce7a1da6137e1ef46365f999d10c656f2fb6b9ff792ea3ae808ebb86
+"ejs@npm:^2.6.1":
+  version: 2.7.4
+  resolution: "ejs@npm:2.7.4"
+  checksum: a1d2bfc7d1f0b39e99ae19b20c9469a25aeddba1ffc225db098110b18d566f73772fcdcc740b108cfda7452276f67d7b64eb359f90285414c942f4ae70713371
+  languageName: node
+  linkType: hard
+
+"electron-to-chromium@npm:^1.3.564":
+  version: 1.4.736
+  resolution: "electron-to-chromium@npm:1.4.736"
+  checksum: dcba6d43ffbc40e5d3decb3a0de67b9721a257fefde4eceac0d75202029c62ace7b377d217f49d1ba9cfbad61f89a14514e85a4de77b7205cee336f2e1f0baee
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -7208,6 +7923,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"electron-to-chromium@npm:^1.4.668":
+  version: 1.4.729
+  resolution: "electron-to-chromium@npm:1.4.729"
+  checksum: fc7d28957d2aa72c57220e8b60e86f523d782a413440d2a8f38563844343b62e6caee9bf866019ba0839eb6e0c247297c6057d86152fa45855f32da88c44bd90
+  languageName: node
+  linkType: hard
+
 "elliptic@npm:6.5.4, elliptic@npm:^6.5.3":
   version: 6.5.4
   resolution: "elliptic@npm:6.5.4"
 "elliptic@npm:6.5.4, elliptic@npm:^6.5.3":
   version: 6.5.4
   resolution: "elliptic@npm:6.5.4"
@@ -7223,7 +7945,29 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"emoji-regex@npm:^7.0.1, emoji-regex@npm:^7.0.2":
+"elliptic@npm:^6.5.5":
+  version: 6.5.5
+  resolution: "elliptic@npm:6.5.5"
+  dependencies:
+    bn.js: ^4.11.9
+    brorand: ^1.1.0
+    hash.js: ^1.0.0
+    hmac-drbg: ^1.0.1
+    inherits: ^2.0.4
+    minimalistic-assert: ^1.0.1
+    minimalistic-crypto-utils: ^1.0.1
+  checksum: ec9105e4469eb3b32b0ee2579756c888ddf3f99d259aa0d65fccb906ee877768aaf8880caae73e3e669c9a4adeb3eb1945703aa974ec5000d2d33a239f4567eb
+  languageName: node
+  linkType: hard
+
+"emittery@npm:^0.7.1":
+  version: 0.7.2
+  resolution: "emittery@npm:0.7.2"
+  checksum: 908cd933d48a9bcb58ddf39e9a7d4ba1e049de392ccbef010102539a636e03cea2b28218331b7ede41de8165d9ed7f148851c5112ebd2e943117c0f61eff5f10
+  languageName: node
+  linkType: hard
+
+"emoji-regex@npm:^7.0.1":
   version: 7.0.3
   resolution: "emoji-regex@npm:7.0.3"
   checksum: 9159b2228b1511f2870ac5920f394c7e041715429a68459ebe531601555f11ea782a8e1718f969df2711d38c66268174407cbca57ce36485544f695c2dfdc96e
   version: 7.0.3
   resolution: "emoji-regex@npm:7.0.3"
   checksum: 9159b2228b1511f2870ac5920f394c7e041715429a68459ebe531601555f11ea782a8e1718f969df2711d38c66268174407cbca57ce36485544f695c2dfdc96e
@@ -7237,10 +7981,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"emojis-list@npm:^2.0.0":
-  version: 2.1.0
-  resolution: "emojis-list@npm:2.1.0"
-  checksum: fb61fa6356dfcc9fbe6db8e334c29da365a34d3d82a915cb59621883d3023d804fd5edad5acd42b8eec016936e81d3b38e2faf921b32e073758374253afe1272
+"emoji-regex@npm:^9.2.2":
+  version: 9.2.2
+  resolution: "emoji-regex@npm:9.2.2"
+  checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -7276,7 +8020,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"enhanced-resolve@npm:^4.1.0":
+"enhanced-resolve@npm:^4.3.0":
   version: 4.5.0
   resolution: "enhanced-resolve@npm:4.5.0"
   dependencies:
   version: 4.5.0
   resolution: "enhanced-resolve@npm:4.5.0"
   dependencies:
@@ -7287,7 +8031,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"enquirer@npm:^2.3.6":
+"enquirer@npm:^2.3.5, enquirer@npm:^2.3.6":
   version: 2.4.1
   resolution: "enquirer@npm:2.4.1"
   dependencies:
   version: 2.4.1
   resolution: "enquirer@npm:2.4.1"
   dependencies:
@@ -7407,7 +8151,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"error-ex@npm:^1.2.0, error-ex@npm:^1.3.1":
+"error-ex@npm:^1.3.1":
   version: 1.3.2
   resolution: "error-ex@npm:1.3.2"
   dependencies:
   version: 1.3.2
   resolution: "error-ex@npm:1.3.2"
   dependencies:
@@ -7416,6 +8160,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"error-stack-parser@npm:^2.0.6":
+  version: 2.1.4
+  resolution: "error-stack-parser@npm:2.1.4"
+  dependencies:
+    stackframe: ^1.3.4
+  checksum: 3b916d2d14c6682f287c8bfa28e14672f47eafe832701080e420e7cdbaebb2c50293868256a95706ac2330fe078cf5664713158b49bc30d7a5f2ac229ded0e18
+  languageName: node
+  linkType: hard
+
 "es-abstract@npm:^1.17.2, es-abstract@npm:^1.17.4, es-abstract@npm:^1.18.0, es-abstract@npm:^1.18.0-next.1, es-abstract@npm:^1.18.0-next.2, es-abstract@npm:^1.18.2":
   version: 1.18.3
   resolution: "es-abstract@npm:1.18.3"
 "es-abstract@npm:^1.17.2, es-abstract@npm:^1.17.4, es-abstract@npm:^1.18.0, es-abstract@npm:^1.18.0-next.1, es-abstract@npm:^1.18.0-next.2, es-abstract@npm:^1.18.2":
   version: 1.18.3
   resolution: "es-abstract@npm:1.18.3"
@@ -7440,6 +8193,60 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.1, es-abstract@npm:^1.23.2":
+  version: 1.23.3
+  resolution: "es-abstract@npm:1.23.3"
+  dependencies:
+    array-buffer-byte-length: ^1.0.1
+    arraybuffer.prototype.slice: ^1.0.3
+    available-typed-arrays: ^1.0.7
+    call-bind: ^1.0.7
+    data-view-buffer: ^1.0.1
+    data-view-byte-length: ^1.0.1
+    data-view-byte-offset: ^1.0.0
+    es-define-property: ^1.0.0
+    es-errors: ^1.3.0
+    es-object-atoms: ^1.0.0
+    es-set-tostringtag: ^2.0.3
+    es-to-primitive: ^1.2.1
+    function.prototype.name: ^1.1.6
+    get-intrinsic: ^1.2.4
+    get-symbol-description: ^1.0.2
+    globalthis: ^1.0.3
+    gopd: ^1.0.1
+    has-property-descriptors: ^1.0.2
+    has-proto: ^1.0.3
+    has-symbols: ^1.0.3
+    hasown: ^2.0.2
+    internal-slot: ^1.0.7
+    is-array-buffer: ^3.0.4
+    is-callable: ^1.2.7
+    is-data-view: ^1.0.1
+    is-negative-zero: ^2.0.3
+    is-regex: ^1.1.4
+    is-shared-array-buffer: ^1.0.3
+    is-string: ^1.0.7
+    is-typed-array: ^1.1.13
+    is-weakref: ^1.0.2
+    object-inspect: ^1.13.1
+    object-keys: ^1.1.1
+    object.assign: ^4.1.5
+    regexp.prototype.flags: ^1.5.2
+    safe-array-concat: ^1.1.2
+    safe-regex-test: ^1.0.3
+    string.prototype.trim: ^1.2.9
+    string.prototype.trimend: ^1.0.8
+    string.prototype.trimstart: ^1.0.8
+    typed-array-buffer: ^1.0.2
+    typed-array-byte-length: ^1.0.1
+    typed-array-byte-offset: ^1.0.2
+    typed-array-length: ^1.0.6
+    unbox-primitive: ^1.0.2
+    which-typed-array: ^1.1.15
+  checksum: f840cf161224252512f9527306b57117192696571e07920f777cb893454e32999206198b4f075516112af6459daca282826d1735c450528470356d09eff3a9ae
+  languageName: node
+  linkType: hard
+
 "es-array-method-boxes-properly@npm:^1.0.0":
   version: 1.0.0
   resolution: "es-array-method-boxes-properly@npm:1.0.0"
 "es-array-method-boxes-properly@npm:^1.0.0":
   version: 1.0.0
   resolution: "es-array-method-boxes-properly@npm:1.0.0"
@@ -7447,6 +8254,73 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"es-define-property@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "es-define-property@npm:1.0.0"
+  dependencies:
+    get-intrinsic: ^1.2.4
+  checksum: f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6
+  languageName: node
+  linkType: hard
+
+"es-errors@npm:^1.1.0, es-errors@npm:^1.2.1, es-errors@npm:^1.3.0":
+  version: 1.3.0
+  resolution: "es-errors@npm:1.3.0"
+  checksum: ec1414527a0ccacd7f15f4a3bc66e215f04f595ba23ca75cdae0927af099b5ec865f9f4d33e9d7e86f512f252876ac77d4281a7871531a50678132429b1271b5
+  languageName: node
+  linkType: hard
+
+"es-iterator-helpers@npm:^1.0.15, es-iterator-helpers@npm:^1.0.17":
+  version: 1.0.18
+  resolution: "es-iterator-helpers@npm:1.0.18"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.0
+    es-errors: ^1.3.0
+    es-set-tostringtag: ^2.0.3
+    function-bind: ^1.1.2
+    get-intrinsic: ^1.2.4
+    globalthis: ^1.0.3
+    has-property-descriptors: ^1.0.2
+    has-proto: ^1.0.3
+    has-symbols: ^1.0.3
+    internal-slot: ^1.0.7
+    iterator.prototype: ^1.1.2
+    safe-array-concat: ^1.1.2
+  checksum: 1594324ff3ca8890fe30c98b2419d3007d2b14b35f9773f188114408ff973e13c526f6045d88209e932f58dc0c55fc9a4ae1554636f8938ed7d926ffc27d3e1a
+  languageName: node
+  linkType: hard
+
+"es-object-atoms@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "es-object-atoms@npm:1.0.0"
+  dependencies:
+    es-errors: ^1.3.0
+  checksum: 26f0ff78ab93b63394e8403c353842b2272836968de4eafe97656adfb8a7c84b9099bf0fe96ed58f4a4cddc860f6e34c77f91649a58a5daa4a9c40b902744e3c
+  languageName: node
+  linkType: hard
+
+"es-set-tostringtag@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "es-set-tostringtag@npm:2.0.3"
+  dependencies:
+    get-intrinsic: ^1.2.4
+    has-tostringtag: ^1.0.2
+    hasown: ^2.0.1
+  checksum: 7227fa48a41c0ce83e0377b11130d324ac797390688135b8da5c28994c0165be8b252e15cd1de41e1325e5a5412511586960213e88f9ab4a5e7d028895db5129
+  languageName: node
+  linkType: hard
+
+"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "es-shim-unscopables@npm:1.0.2"
+  dependencies:
+    hasown: ^2.0.0
+  checksum: 432bd527c62065da09ed1d37a3f8e623c423683285e6188108286f4a1e8e164a5bcbfbc0051557c7d14633cd2a41ce24c7048e6bbb66a985413fd32f1be72626
+  languageName: node
+  linkType: hard
+
 "es-to-primitive@npm:^1.2.1":
   version: 1.2.1
   resolution: "es-to-primitive@npm:1.2.1"
 "es-to-primitive@npm:^1.2.1":
   version: 1.2.1
   resolution: "es-to-primitive@npm:1.2.1"
@@ -7458,14 +8332,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.50":
-  version: 0.10.53
-  resolution: "es5-ext@npm:0.10.53"
+"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.50, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14":
+  version: 0.10.64
+  resolution: "es5-ext@npm:0.10.64"
   dependencies:
   dependencies:
-    es6-iterator: ~2.0.3
-    es6-symbol: ~3.1.3
-    next-tick: ~1.0.0
-  checksum: 24ec22369260cf98605cb2f51eae9d7df5dc621bc5d3b311f6f5c3d0fcdb7bafae888270f3083ee6e9af27350a5ea49f1fe2dd6406a9017247ca40f091f529b2
+    es6-iterator: ^2.0.3
+    es6-symbol: ^3.1.3
+    esniff: ^2.0.1
+    next-tick: ^1.1.0
+  checksum: 01179fab0769fdbef213062222f99d0346724dbaccf04b87c0e6ee7f0c97edabf14be647ca1321f0497425ea7145de0fd278d1b3f3478864b8933e7136a5c645
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -7476,7 +8351,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"es6-iterator@npm:2.0.3, es6-iterator@npm:~2.0.3":
+"es6-iterator@npm:2.0.3, es6-iterator@npm:^2.0.3":
   version: 2.0.3
   resolution: "es6-iterator@npm:2.0.3"
   dependencies:
   version: 2.0.3
   resolution: "es6-iterator@npm:2.0.3"
   dependencies:
@@ -7487,7 +8362,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"es6-symbol@npm:^3.1.1, es6-symbol@npm:~3.1.3":
+"es6-symbol@npm:^3.1.1":
   version: 3.1.3
   resolution: "es6-symbol@npm:3.1.3"
   dependencies:
   version: 3.1.3
   resolution: "es6-symbol@npm:3.1.3"
   dependencies:
@@ -7497,6 +8372,23 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"es6-symbol@npm:^3.1.3":
+  version: 3.1.4
+  resolution: "es6-symbol@npm:3.1.4"
+  dependencies:
+    d: ^1.0.2
+    ext: ^1.7.0
+  checksum: 52125ec4b5d1b6b93b8d3d42830bb19f8da21080ffcf45253b614bc6ff3e31349be202fb745d4d1af6778cdf5e38fea30e0c7e7dc37e2aecd44acc43502055f9
+  languageName: node
+  linkType: hard
+
+"escalade@npm:^3.0.2":
+  version: 3.1.2
+  resolution: "escalade@npm:3.1.2"
+  checksum: 1ec0977aa2772075493002bdbd549d595ff6e9393b1cb0d7d6fcaf78c750da0c158f180938365486f75cb69fba20294351caddfce1b46552a7b6c3cde52eaa02
+  languageName: node
+  linkType: hard
+
 "escalade@npm:^3.1.1":
   version: 3.1.1
   resolution: "escalade@npm:3.1.1"
 "escalade@npm:^3.1.1":
   version: 3.1.1
   resolution: "escalade@npm:3.1.1"
@@ -7518,14 +8410,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5":
+"escape-string-regexp@npm:^1.0.5":
   version: 1.0.5
   resolution: "escape-string-regexp@npm:1.0.5"
   checksum: 6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410
   languageName: node
   linkType: hard
 
   version: 1.0.5
   resolution: "escape-string-regexp@npm:1.0.5"
   checksum: 6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410
   languageName: node
   linkType: hard
 
-"escodegen@npm:^1.11.0, escodegen@npm:^1.9.1":
+"escape-string-regexp@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "escape-string-regexp@npm:4.0.0"
+  checksum: 98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5
+  languageName: node
+  linkType: hard
+
+"escodegen@npm:^1.8.1":
   version: 1.14.3
   resolution: "escodegen@npm:1.14.3"
   dependencies:
   version: 1.14.3
   resolution: "escodegen@npm:1.14.3"
   dependencies:
@@ -7544,141 +8443,198 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-config-react-app@npm:^5.2.1":
-  version: 5.2.1
-  resolution: "eslint-config-react-app@npm:5.2.1"
+"escodegen@npm:^2.0.0":
+  version: 2.1.0
+  resolution: "escodegen@npm:2.1.0"
   dependencies:
   dependencies:
-    confusing-browser-globals: ^1.0.9
-  peerDependencies:
-    "@typescript-eslint/eslint-plugin": 2.x
-    "@typescript-eslint/parser": 2.x
-    babel-eslint: 10.x
-    eslint: 6.x
-    eslint-plugin-flowtype: 3.x || 4.x
-    eslint-plugin-import: 2.x
-    eslint-plugin-jsx-a11y: 6.x
-    eslint-plugin-react: 7.x
-    eslint-plugin-react-hooks: 1.x || 2.x
-  checksum: 8af6801f29d7314611e111a1593e91d412d41cde6719303ee6db7de65d78ed4b53e9197497765bb2deed65e6bfd73bf7e74da58cab3f66838c2927880b21eeba
+    esprima: ^4.0.1
+    estraverse: ^5.2.0
+    esutils: ^2.0.2
+    source-map: ~0.6.1
+  dependenciesMeta:
+    source-map:
+      optional: true
+  bin:
+    escodegen: bin/escodegen.js
+    esgenerate: bin/esgenerate.js
+  checksum: 096696407e161305cd05aebb95134ad176708bc5cb13d0dcc89a5fcbb959b8ed757e7f2591a5f8036f8f4952d4a724de0df14cd419e29212729fa6df5ce16bf6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-import-resolver-node@npm:^0.3.2":
-  version: 0.3.4
-  resolution: "eslint-import-resolver-node@npm:0.3.4"
+"eslint-config-react-app@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "eslint-config-react-app@npm:6.0.0"
   dependencies:
   dependencies:
-    debug: ^2.6.9
-    resolve: ^1.13.1
-  checksum: a0db55ec26c5bb385c8681af6b8d6dee16768d5f27dff72c3113407d0f028f28e56dcb1cc3a4689c79396a5f6a9c24bd0cac9a2c9c588c7d7357d24a42bec876
+    confusing-browser-globals: ^1.0.10
+  peerDependencies:
+    "@typescript-eslint/eslint-plugin": ^4.0.0
+    "@typescript-eslint/parser": ^4.0.0
+    babel-eslint: ^10.0.0
+    eslint: ^7.5.0
+    eslint-plugin-flowtype: ^5.2.0
+    eslint-plugin-import: ^2.22.0
+    eslint-plugin-jest: ^24.0.0
+    eslint-plugin-jsx-a11y: ^6.3.1
+    eslint-plugin-react: ^7.20.3
+    eslint-plugin-react-hooks: ^4.0.8
+    eslint-plugin-testing-library: ^3.9.0
+  peerDependenciesMeta:
+    eslint-plugin-jest:
+      optional: true
+    eslint-plugin-testing-library:
+      optional: true
+  checksum: b265852455b1c10e9c5f0cebe199306fffc7f8e1b6548fcb0bccdc4415c288dfee8ab10717122a32275b91130dfb482dcbbc87d2fb79d8728d4c2bfa889f0915
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-loader@npm:3.0.3":
-  version: 3.0.3
-  resolution: "eslint-loader@npm:3.0.3"
+"eslint-import-resolver-node@npm:^0.3.9":
+  version: 0.3.9
+  resolution: "eslint-import-resolver-node@npm:0.3.9"
   dependencies:
   dependencies:
-    fs-extra: ^8.1.0
-    loader-fs-cache: ^1.0.2
-    loader-utils: ^1.2.3
-    object-hash: ^2.0.1
-    schema-utils: ^2.6.1
-  peerDependencies:
-    eslint: ^5.0.0 || ^6.0.0
-    webpack: ^4.0.0 || ^5.0.0
-  checksum: 5151ec134e26fb7caa20c4e7cf443e4400a48092008f8adebb7fef8ad4a6301d8f19a9f9c5aa78fec65b2d2594037edaef4ceae4769230cd7912566cd4ec7970
+    debug: ^3.2.7
+    is-core-module: ^2.13.0
+    resolve: ^1.22.4
+  checksum: 439b91271236b452d478d0522a44482e8c8540bf9df9bd744062ebb89ab45727a3acd03366a6ba2bdbcde8f9f718bab7fe8db64688aca75acf37e04eafd25e22
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-module-utils@npm:^2.4.1":
-  version: 2.6.1
-  resolution: "eslint-module-utils@npm:2.6.1"
+"eslint-module-utils@npm:^2.8.0":
+  version: 2.8.1
+  resolution: "eslint-module-utils@npm:2.8.1"
   dependencies:
     debug: ^3.2.7
   dependencies:
     debug: ^3.2.7
-    pkg-dir: ^2.0.0
-  checksum: 3cc43a36a0075d300db6a3946203ec92249b6da1539694ef205a43b4ccfbc2eaf4961475d4b89c24b12c187d6bfd882c7c7d0b2ce02adb40c2dedb7fd022a7e2
+  peerDependenciesMeta:
+    eslint:
+      optional: true
+  checksum: 3cecd99b6baf45ffc269167da0f95dcb75e5aa67b93d73a3bab63e2a7eedd9cdd6f188eed048e2f57c1b77db82c9cbf2adac20b512fa70e597d863dd3720170d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-plugin-flowtype@npm:4.6.0":
-  version: 4.6.0
-  resolution: "eslint-plugin-flowtype@npm:4.6.0"
+"eslint-plugin-flowtype@npm:^5.2.0":
+  version: 5.10.0
+  resolution: "eslint-plugin-flowtype@npm:5.10.0"
   dependencies:
     lodash: ^4.17.15
   dependencies:
     lodash: ^4.17.15
+    string-natural-compare: ^3.0.1
   peerDependencies:
   peerDependencies:
-    eslint: ">=6.1.0"
-  checksum: 2256be93a2b49b9440defea2823349b0d3c428437c1e8868fb02d7f21a5d7ab48e53d8afb05fea713da2c3ee8c0d3dbe6e8c9efc798a86aebdb0cffc0f9dfc7a
+    eslint: ^7.1.0
+  checksum: 791cd53c886bf819d52d6353cdfb4d49276dcd8a14f564a85d275d5017d81c7b1cc1921013ac9749f69c3f1bc4d23f36182137aab42bc059c2ae3f9773dd7740
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-plugin-import@npm:2.20.1":
-  version: 2.20.1
-  resolution: "eslint-plugin-import@npm:2.20.1"
-  dependencies:
-    array-includes: ^3.0.3
-    array.prototype.flat: ^1.2.1
-    contains-path: ^0.1.0
-    debug: ^2.6.9
-    doctrine: 1.5.0
-    eslint-import-resolver-node: ^0.3.2
-    eslint-module-utils: ^2.4.1
-    has: ^1.0.3
-    minimatch: ^3.0.4
-    object.values: ^1.1.0
-    read-pkg-up: ^2.0.0
-    resolve: ^1.12.0
+"eslint-plugin-import@npm:^2.22.1":
+  version: 2.29.1
+  resolution: "eslint-plugin-import@npm:2.29.1"
+  dependencies:
+    array-includes: ^3.1.7
+    array.prototype.findlastindex: ^1.2.3
+    array.prototype.flat: ^1.3.2
+    array.prototype.flatmap: ^1.3.2
+    debug: ^3.2.7
+    doctrine: ^2.1.0
+    eslint-import-resolver-node: ^0.3.9
+    eslint-module-utils: ^2.8.0
+    hasown: ^2.0.0
+    is-core-module: ^2.13.1
+    is-glob: ^4.0.3
+    minimatch: ^3.1.2
+    object.fromentries: ^2.0.7
+    object.groupby: ^1.0.1
+    object.values: ^1.1.7
+    semver: ^6.3.1
+    tsconfig-paths: ^3.15.0
   peerDependencies:
   peerDependencies:
-    eslint: 2.x - 6.x
-  checksum: 9c2f06fb5907afa12023df8b7b881ee345b11ba0930891966de5d009932bce4af7d1a8749037e15435d189796b26ba7190456d90a4ee7a8ce661e021a8b833d4
+    eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+  checksum: e65159aef808136d26d029b71c8c6e4cb5c628e65e5de77f1eb4c13a379315ae55c9c3afa847f43f4ff9df7e54515c77ffc6489c6a6f81f7dd7359267577468c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-plugin-jsx-a11y@npm:6.2.3":
-  version: 6.2.3
-  resolution: "eslint-plugin-jsx-a11y@npm:6.2.3"
-  dependencies:
-    "@babel/runtime": ^7.4.5
-    aria-query: ^3.0.0
-    array-includes: ^3.0.3
-    ast-types-flow: ^0.0.7
-    axobject-query: ^2.0.2
-    damerau-levenshtein: ^1.0.4
-    emoji-regex: ^7.0.2
-    has: ^1.0.3
-    jsx-ast-utils: ^2.2.1
+"eslint-plugin-jest@npm:^24.1.0":
+  version: 24.7.0
+  resolution: "eslint-plugin-jest@npm:24.7.0"
+  dependencies:
+    "@typescript-eslint/experimental-utils": ^4.0.1
   peerDependencies:
   peerDependencies:
-    eslint: ^3 || ^4 || ^5 || ^6
-  checksum: 2e9f0ff28567e141479968a860f5670009a403250054970c714bf723e1f8c9ae7cddeb2bf13ee9f6882af333588645a06c10a417aa2733084813d162dec6c235
+    "@typescript-eslint/eslint-plugin": ">= 4"
+    eslint: ">=5"
+  peerDependenciesMeta:
+    "@typescript-eslint/eslint-plugin":
+      optional: true
+  checksum: a4056582825ab3359d2e0e3aae50518f6f867d1cfb3240496605247d3ff9c84b4164f1a7e1f7087d5a2eae1343d738ada1ba74c422b13ad20b737601dc47ae08
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-plugin-react-hooks@npm:^1.6.1":
-  version: 1.7.0
-  resolution: "eslint-plugin-react-hooks@npm:1.7.0"
+"eslint-plugin-jsx-a11y@npm:^6.3.1":
+  version: 6.8.0
+  resolution: "eslint-plugin-jsx-a11y@npm:6.8.0"
+  dependencies:
+    "@babel/runtime": ^7.23.2
+    aria-query: ^5.3.0
+    array-includes: ^3.1.7
+    array.prototype.flatmap: ^1.3.2
+    ast-types-flow: ^0.0.8
+    axe-core: =4.7.0
+    axobject-query: ^3.2.1
+    damerau-levenshtein: ^1.0.8
+    emoji-regex: ^9.2.2
+    es-iterator-helpers: ^1.0.15
+    hasown: ^2.0.0
+    jsx-ast-utils: ^3.3.5
+    language-tags: ^1.0.9
+    minimatch: ^3.1.2
+    object.entries: ^1.1.7
+    object.fromentries: ^2.0.7
+  peerDependencies:
+    eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+  checksum: 3dec00e2a3089c4c61ac062e4196a70985fb7eda1fd67fe035363d92578debde92fdb8ed2e472321fc0d71e75f4a1e8888c6a3218c14dd93c8e8d19eb6f51554
+  languageName: node
+  linkType: hard
+
+"eslint-plugin-react-hooks@npm:^4.2.0":
+  version: 4.6.0
+  resolution: "eslint-plugin-react-hooks@npm:4.6.0"
   peerDependencies:
   peerDependencies:
-    eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
-  checksum: ea16d0cf4aaa0a30db05860bd892f3432a4cc299983a5adece092c021c4ee359e7e7277ff1e7207d1f550fae5f08a8b3aa2698f36e82cdebb756a8a3e1c842eb
+    eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+  checksum: 23001801f14c1d16bf0a837ca7970d9dd94e7b560384b41db378b49b6e32dc43d6e2790de1bd737a652a86f81a08d6a91f402525061b47719328f586a57e86c3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-plugin-react@npm:7.19.0":
-  version: 7.19.0
-  resolution: "eslint-plugin-react@npm:7.19.0"
+"eslint-plugin-react@npm:^7.21.5":
+  version: 7.34.1
+  resolution: "eslint-plugin-react@npm:7.34.1"
   dependencies:
   dependencies:
-    array-includes: ^3.1.1
+    array-includes: ^3.1.7
+    array.prototype.findlast: ^1.2.4
+    array.prototype.flatmap: ^1.3.2
+    array.prototype.toreversed: ^1.1.2
+    array.prototype.tosorted: ^1.1.3
     doctrine: ^2.1.0
     doctrine: ^2.1.0
-    has: ^1.0.3
-    jsx-ast-utils: ^2.2.3
-    object.entries: ^1.1.1
-    object.fromentries: ^2.0.2
-    object.values: ^1.1.1
-    prop-types: ^15.7.2
-    resolve: ^1.15.1
-    semver: ^6.3.0
-    string.prototype.matchall: ^4.0.2
-    xregexp: ^4.3.0
+    es-iterator-helpers: ^1.0.17
+    estraverse: ^5.3.0
+    jsx-ast-utils: ^2.4.1 || ^3.0.0
+    minimatch: ^3.1.2
+    object.entries: ^1.1.7
+    object.fromentries: ^2.0.7
+    object.hasown: ^1.1.3
+    object.values: ^1.1.7
+    prop-types: ^15.8.1
+    resolve: ^2.0.0-next.5
+    semver: ^6.3.1
+    string.prototype.matchall: ^4.0.10
+  peerDependencies:
+    eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8
+  checksum: 82f391c5a093235c3bc2f664c54e009c49460778ee7d1b86c1536df9ac4d2a80d1dedc9241ac797df4a9dced936e955d9c89042fb3ac8d017b5359d1320d3c0f
+  languageName: node
+  linkType: hard
+
+"eslint-plugin-testing-library@npm:^3.9.2":
+  version: 3.10.2
+  resolution: "eslint-plugin-testing-library@npm:3.10.2"
+  dependencies:
+    "@typescript-eslint/experimental-utils": ^3.10.1
   peerDependencies:
   peerDependencies:
-    eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
-  checksum: c15bc7aa27670ceb1410e9c703072c13d8cd6117af92fcdffcc531bbaf4e4ae4266f52583779178a71c7f917860ac7cce4677e4dd428d3d1f9883fe3a819e81e
+    eslint: ^5 || ^6 || ^7
+  checksum: 3859d4a4816b130cfefc3b45bc7d303aff19b8d4e83a5e35ca3d634de9f3c4aa1b4340cb4f41e2d1bfe70b173562b9882c58ac48be4e07ddf6a1f88659e2604d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -7702,12 +8658,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-utils@npm:^1.4.3":
-  version: 1.4.3
-  resolution: "eslint-utils@npm:1.4.3"
+"eslint-utils@npm:^2.0.0, eslint-utils@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "eslint-utils@npm:2.1.0"
   dependencies:
     eslint-visitor-keys: ^1.1.0
   dependencies:
     eslint-visitor-keys: ^1.1.0
-  checksum: a20630e686034107138272f245c460f6d77705d1f4bb0628c1a1faf59fc800f441188916b3ec3b957394dc405aa200a3017dfa2b0fff0976e307a4e645a18d1e
+  checksum: 27500938f348da42100d9e6ad03ae29b3de19ba757ae1a7f4a087bdcf83ac60949bbb54286492ca61fac1f5f3ac8692dd21537ce6214240bf95ad0122f24d71d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -7722,7 +8678,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint-visitor-keys@npm:^1.0.0, eslint-visitor-keys@npm:^1.1.0":
+"eslint-visitor-keys@npm:^1.0.0, eslint-visitor-keys@npm:^1.1.0, eslint-visitor-keys@npm:^1.3.0":
   version: 1.3.0
   resolution: "eslint-visitor-keys@npm:1.3.0"
   checksum: 37a19b712f42f4c9027e8ba98c2b06031c17e0c0a4c696cd429bd9ee04eb43889c446f2cd545e1ff51bef9593fcec94ecd2c2ef89129fcbbf3adadbef520376a
   version: 1.3.0
   resolution: "eslint-visitor-keys@npm:1.3.0"
   checksum: 37a19b712f42f4c9027e8ba98c2b06031c17e0c0a4c696cd429bd9ee04eb43889c446f2cd545e1ff51bef9593fcec94ecd2c2ef89129fcbbf3adadbef520376a
@@ -7736,61 +8692,103 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"eslint@npm:^6.6.0":
-  version: 6.8.0
-  resolution: "eslint@npm:6.8.0"
+"eslint-webpack-plugin@npm:^2.1.0":
+  version: 2.7.0
+  resolution: "eslint-webpack-plugin@npm:2.7.0"
+  dependencies:
+    "@types/eslint": ^7.29.0
+    arrify: ^2.0.1
+    jest-worker: ^27.5.1
+    micromatch: ^4.0.5
+    normalize-path: ^3.0.0
+    schema-utils: ^3.1.1
+  peerDependencies:
+    eslint: ^7.0.0 || ^8.0.0
+    webpack: ^4.0.0 || ^5.0.0
+  checksum: b6fd7cf4c49078b345a908b82b0bee06bc82ab0cec214ddd5fe5bb18b065765d52a07ad4077f6bba5830ba2f55f37d8f2208a52d11f34ee29df81153e3124d9c
+  languageName: node
+  linkType: hard
+
+"eslint@npm:^7.11.0":
+  version: 7.32.0
+  resolution: "eslint@npm:7.32.0"
   dependencies:
   dependencies:
-    "@babel/code-frame": ^7.0.0
+    "@babel/code-frame": 7.12.11
+    "@eslint/eslintrc": ^0.4.3
+    "@humanwhocodes/config-array": ^0.5.0
     ajv: ^6.10.0
     ajv: ^6.10.0
-    chalk: ^2.1.0
-    cross-spawn: ^6.0.5
+    chalk: ^4.0.0
+    cross-spawn: ^7.0.2
     debug: ^4.0.1
     doctrine: ^3.0.0
     debug: ^4.0.1
     doctrine: ^3.0.0
-    eslint-scope: ^5.0.0
-    eslint-utils: ^1.4.3
-    eslint-visitor-keys: ^1.1.0
-    espree: ^6.1.2
-    esquery: ^1.0.1
+    enquirer: ^2.3.5
+    escape-string-regexp: ^4.0.0
+    eslint-scope: ^5.1.1
+    eslint-utils: ^2.1.0
+    eslint-visitor-keys: ^2.0.0
+    espree: ^7.3.1
+    esquery: ^1.4.0
     esutils: ^2.0.2
     esutils: ^2.0.2
-    file-entry-cache: ^5.0.1
+    fast-deep-equal: ^3.1.3
+    file-entry-cache: ^6.0.1
     functional-red-black-tree: ^1.0.1
     functional-red-black-tree: ^1.0.1
-    glob-parent: ^5.0.0
-    globals: ^12.1.0
+    glob-parent: ^5.1.2
+    globals: ^13.6.0
     ignore: ^4.0.6
     import-fresh: ^3.0.0
     imurmurhash: ^0.1.4
     ignore: ^4.0.6
     import-fresh: ^3.0.0
     imurmurhash: ^0.1.4
-    inquirer: ^7.0.0
     is-glob: ^4.0.0
     js-yaml: ^3.13.1
     json-stable-stringify-without-jsonify: ^1.0.1
     is-glob: ^4.0.0
     js-yaml: ^3.13.1
     json-stable-stringify-without-jsonify: ^1.0.1
-    levn: ^0.3.0
-    lodash: ^4.17.14
+    levn: ^0.4.1
+    lodash.merge: ^4.6.2
     minimatch: ^3.0.4
     minimatch: ^3.0.4
-    mkdirp: ^0.5.1
     natural-compare: ^1.4.0
     natural-compare: ^1.4.0
-    optionator: ^0.8.3
+    optionator: ^0.9.1
     progress: ^2.0.0
     progress: ^2.0.0
-    regexpp: ^2.0.1
-    semver: ^6.1.2
-    strip-ansi: ^5.2.0
-    strip-json-comments: ^3.0.1
-    table: ^5.2.3
+    regexpp: ^3.1.0
+    semver: ^7.2.1
+    strip-ansi: ^6.0.0
+    strip-json-comments: ^3.1.0
+    table: ^6.0.9
     text-table: ^0.2.0
     v8-compile-cache: ^2.0.3
   bin:
     text-table: ^0.2.0
     v8-compile-cache: ^2.0.3
   bin:
-    eslint: ./bin/eslint.js
-  checksum: d4edbe69589ef194e7d3470a18632560c5399a5f685295bd59a11cddba4c6f7e03a137a15a21389f8f85712ebd82d0a628ee4e9cd4391113556029c486616e25
+    eslint: bin/eslint.js
+  checksum: cc85af9985a3a11085c011f3d27abe8111006d34cc274291b3c4d7bea51a4e2ff6135780249becd919ba7f6d6d1ecc38a6b73dacb6a7be08d38453b344dc8d37
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"espree@npm:^6.1.2":
-  version: 6.2.1
-  resolution: "espree@npm:6.2.1"
+"esniff@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "esniff@npm:2.0.1"
   dependencies:
   dependencies:
-    acorn: ^7.1.1
-    acorn-jsx: ^5.2.0
-    eslint-visitor-keys: ^1.1.0
-  checksum: 99c508950b5b9f53d008d781d2abb7a4ef3496ea699306fb6eb737c7e513aa594644314364c50ec27abb220124c6851fff64a6b62c358479534369904849360b
+    d: ^1.0.1
+    es5-ext: ^0.10.62
+    event-emitter: ^0.3.5
+    type: ^2.7.2
+  checksum: d814c0e5c39bce9925b2e65b6d8767af72c9b54f35a65f9f3d6e8c606dce9aebe35a9599d30f15b0807743f88689f445163cfb577a425de4fb8c3c5bc16710cc
+  languageName: node
+  linkType: hard
+
+"espree@npm:^7.3.0, espree@npm:^7.3.1":
+  version: 7.3.1
+  resolution: "espree@npm:7.3.1"
+  dependencies:
+    acorn: ^7.4.0
+    acorn-jsx: ^5.3.1
+    eslint-visitor-keys: ^1.3.0
+  checksum: aa9b50dcce883449af2e23bc2b8d9abb77118f96f4cb313935d6b220f77137eaef7724a83c3f6243b96bc0e4ab14766198e60818caad99f9519ae5a336a39b45
+  languageName: node
+  linkType: hard
+
+"esprima@npm:1.2.2":
+  version: 1.2.2
+  resolution: "esprima@npm:1.2.2"
+  bin:
+    esparse: ./bin/esparse.js
+    esvalidate: ./bin/esvalidate.js
+  checksum: 4f10006f0e315f2f7d8cf6630e465f183512f1ab2e862b11785a133ce37ed1696573deefb5256e510eaa4368342b13b393334477f6ccdcdb8f10e782b0f5e6dc
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -7813,6 +8811,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"esquery@npm:^1.4.0":
+  version: 1.5.0
+  resolution: "esquery@npm:1.5.0"
+  dependencies:
+    estraverse: ^5.1.0
+  checksum: aefb0d2596c230118656cd4ec7532d447333a410a48834d80ea648b1e7b5c9bc9ed8b5e33a89cb04e487b60d622f44cf5713bf4abed7c97343edefdc84a35900
+  languageName: node
+  linkType: hard
+
 "esrecurse@npm:^4.1.0, esrecurse@npm:^4.3.0":
   version: 4.3.0
   resolution: "esrecurse@npm:4.3.0"
 "esrecurse@npm:^4.1.0, esrecurse@npm:^4.3.0":
   version: 4.3.0
   resolution: "esrecurse@npm:4.3.0"
@@ -7836,6 +8843,27 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"estraverse@npm:^5.3.0":
+  version: 5.3.0
+  resolution: "estraverse@npm:5.3.0"
+  checksum: 072780882dc8416ad144f8fe199628d2b3e7bbc9989d9ed43795d2c90309a2047e6bc5979d7e2322a341163d22cfad9e21f4110597fe487519697389497e4e2b
+  languageName: node
+  linkType: hard
+
+"estree-walker@npm:^0.6.1":
+  version: 0.6.1
+  resolution: "estree-walker@npm:0.6.1"
+  checksum: 9d6f82a4921f11eec18f8089fb3cce6e53bcf45a8e545c42a2674d02d055fb30f25f90495f8be60803df6c39680c80dcee7f944526867eb7aa1fc9254883b23d
+  languageName: node
+  linkType: hard
+
+"estree-walker@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "estree-walker@npm:1.0.1"
+  checksum: 7e70da539691f6db03a08e7ce94f394ce2eef4180e136d251af299d41f92fb2d28ebcd9a6e393e3728d7970aeb5358705ddf7209d52fbcb2dd4693f95dcf925f
+  languageName: node
+  linkType: hard
+
 "esutils@npm:^2.0.2":
   version: 2.0.3
   resolution: "esutils@npm:2.0.3"
 "esutils@npm:^2.0.2":
   version: 2.0.3
   resolution: "esutils@npm:2.0.3"
@@ -7850,6 +8878,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"event-emitter@npm:^0.3.5":
+  version: 0.3.5
+  resolution: "event-emitter@npm:0.3.5"
+  dependencies:
+    d: 1
+    es5-ext: ~0.10.14
+  checksum: 27c1399557d9cd7e0aa0b366c37c38a4c17293e3a10258e8b692a847dd5ba9fb90429c3a5a1eeff96f31f6fa03ccbd31d8ad15e00540b22b22f01557be706030
+  languageName: node
+  linkType: hard
+
 "eventemitter2@npm:6.4.7":
   version: 6.4.7
   resolution: "eventemitter2@npm:6.4.7"
 "eventemitter2@npm:6.4.7":
   version: 6.4.7
   resolution: "eventemitter2@npm:6.4.7"
@@ -7896,7 +8934,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"execa@npm:4.1.0":
+"execa@npm:4.1.0, execa@npm:^4.0.0":
   version: 4.1.0
   resolution: "execa@npm:4.1.0"
   dependencies:
   version: 4.1.0
   resolution: "execa@npm:4.1.0"
   dependencies:
@@ -7959,55 +8997,56 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"expect@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "expect@npm:24.9.0"
+"expect@npm:^26.6.0, expect@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "expect@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    ansi-styles: ^3.2.0
-    jest-get-type: ^24.9.0
-    jest-matcher-utils: ^24.9.0
-    jest-message-util: ^24.9.0
-    jest-regex-util: ^24.9.0
-  checksum: bfce2243543dd10e3c2047bbe6fc99b7b150cea71b198ddd8feb2e7ebfef1a3dd46ec7519e05d23a20b30c242b13dad97551368a690731d9a591f6f863528cee
+    "@jest/types": ^26.6.2
+    ansi-styles: ^4.0.0
+    jest-get-type: ^26.3.0
+    jest-matcher-utils: ^26.6.2
+    jest-message-util: ^26.6.2
+    jest-regex-util: ^26.0.0
+  checksum: 79a9b888c5c6d37d11f2cb76def6cf1dc8ff098d38662ee20c9f2ee0da67e9a93435f2327854b2e7554732153870621843e7f83e8cefb1250447ee2bc39883a4
   languageName: node
   linkType: hard
 
 "express@npm:^4.17.1":
   languageName: node
   linkType: hard
 
 "express@npm:^4.17.1":
-  version: 4.17.1
-  resolution: "express@npm:4.17.1"
+  version: 4.19.2
+  resolution: "express@npm:4.19.2"
   dependencies:
   dependencies:
-    accepts: ~1.3.7
+    accepts: ~1.3.8
     array-flatten: 1.1.1
     array-flatten: 1.1.1
-    body-parser: 1.19.0
-    content-disposition: 0.5.3
+    body-parser: 1.20.2
+    content-disposition: 0.5.4
     content-type: ~1.0.4
     content-type: ~1.0.4
-    cookie: 0.4.0
+    cookie: 0.6.0
     cookie-signature: 1.0.6
     debug: 2.6.9
     cookie-signature: 1.0.6
     debug: 2.6.9
-    depd: ~1.1.2
+    depd: 2.0.0
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
     etag: ~1.8.1
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
     etag: ~1.8.1
-    finalhandler: ~1.1.2
+    finalhandler: 1.2.0
     fresh: 0.5.2
     fresh: 0.5.2
+    http-errors: 2.0.0
     merge-descriptors: 1.0.1
     methods: ~1.1.2
     merge-descriptors: 1.0.1
     methods: ~1.1.2
-    on-finished: ~2.3.0
+    on-finished: 2.4.1
     parseurl: ~1.3.3
     path-to-regexp: 0.1.7
     parseurl: ~1.3.3
     path-to-regexp: 0.1.7
-    proxy-addr: ~2.0.5
-    qs: 6.7.0
+    proxy-addr: ~2.0.7
+    qs: 6.11.0
     range-parser: ~1.2.1
     range-parser: ~1.2.1
-    safe-buffer: 5.1.2
-    send: 0.17.1
-    serve-static: 1.14.1
-    setprototypeof: 1.1.1
-    statuses: ~1.5.0
+    safe-buffer: 5.2.1
+    send: 0.18.0
+    serve-static: 1.15.0
+    setprototypeof: 1.2.0
+    statuses: 2.0.1
     type-is: ~1.6.18
     utils-merge: 1.0.1
     vary: ~1.1.2
     type-is: ~1.6.18
     utils-merge: 1.0.1
     vary: ~1.1.2
-  checksum: d964e9e17af331ea6fa2f84999b063bc47189dd71b4a735df83f9126d3bb2b92e830f1cb1d7c2742530eb625e2689d7a9a9c71f0c3cc4dd6015c3cd32a01abd5
+  checksum: 212dbd6c2c222a96a61bc927639c95970a53b06257080bb9e2838adb3bffdb966856551fdad1ab5dd654a217c35db94f987d0aa88d48fb04d306340f5f34dca5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8020,6 +9059,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"ext@npm:^1.7.0":
+  version: 1.7.0
+  resolution: "ext@npm:1.7.0"
+  dependencies:
+    type: ^2.7.2
+  checksum: ef481f9ef45434d8c867cfd09d0393b60945b7c8a1798bedc4514cb35aac342ccb8d8ecb66a513e6a2b4ec1e294a338e3124c49b29736f8e7c735721af352c31
+  languageName: node
+  linkType: hard
+
 "extend-shallow@npm:^2.0.1":
   version: 2.0.1
   resolution: "extend-shallow@npm:2.0.1"
 "extend-shallow@npm:^2.0.1":
   version: 2.0.1
   resolution: "extend-shallow@npm:2.0.1"
@@ -8046,17 +9094,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"external-editor@npm:^3.0.3":
-  version: 3.1.0
-  resolution: "external-editor@npm:3.1.0"
-  dependencies:
-    chardet: ^0.7.0
-    iconv-lite: ^0.4.24
-    tmp: ^0.0.33
-  checksum: 1c2a616a73f1b3435ce04030261bed0e22d4737e14b090bb48e58865da92529c9f2b05b893de650738d55e692d071819b45e1669259b2b354bc3154d27a698c7
-  languageName: node
-  linkType: hard
-
 "extglob@npm:^2.0.4":
   version: 2.0.4
   resolution: "extglob@npm:2.0.4"
 "extglob@npm:^2.0.4":
   version: 2.0.4
   resolution: "extglob@npm:2.0.4"
@@ -8104,48 +9141,34 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"fast-deep-equal@npm:^3.1.1":
+"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
   version: 3.1.3
   resolution: "fast-deep-equal@npm:3.1.3"
   checksum: e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d
   languageName: node
   linkType: hard
 
   version: 3.1.3
   resolution: "fast-deep-equal@npm:3.1.3"
   checksum: e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d
   languageName: node
   linkType: hard
 
-"fast-glob@npm:^2.0.2":
-  version: 2.2.7
-  resolution: "fast-glob@npm:2.2.7"
-  dependencies:
-    "@mrmlnc/readdir-enhanced": ^2.2.1
-    "@nodelib/fs.stat": ^1.1.2
-    glob-parent: ^3.1.0
-    is-glob: ^4.0.0
-    merge2: ^1.2.3
-    micromatch: ^3.1.10
-  checksum: 304ccff1d437fcc44ae0168b0c3899054b92e0fd6af6ad7c3ccc82ab4ddd210b99c7c739d60ee3686da2aa165cd1a31810b31fd91f7c2a575d297342a9fc0534
-  languageName: node
-  linkType: hard
-
-"fast-glob@npm:^3.2.9":
-  version: 3.3.1
-  resolution: "fast-glob@npm:3.3.1"
+"fast-glob@npm:^3.1.1, fast-glob@npm:^3.2.9":
+  version: 3.3.2
+  resolution: "fast-glob@npm:3.3.2"
   dependencies:
     "@nodelib/fs.stat": ^2.0.2
     "@nodelib/fs.walk": ^1.2.3
     glob-parent: ^5.1.2
     merge2: ^1.3.0
     micromatch: ^4.0.4
   dependencies:
     "@nodelib/fs.stat": ^2.0.2
     "@nodelib/fs.walk": ^1.2.3
     glob-parent: ^5.1.2
     merge2: ^1.3.0
     micromatch: ^4.0.4
-  checksum: b6f3add6403e02cf3a798bfbb1183d0f6da2afd368f27456010c0bc1f9640aea308243d4cb2c0ab142f618276e65ecb8be1661d7c62a7b4e5ba774b9ce5432e5
+  checksum: 900e4979f4dbc3313840078419245621259f349950411ca2fa445a2f9a1a6d98c3b5e7e0660c5ccd563aa61abe133a21765c6c0dec8e57da1ba71d8000b05ec1
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"fast-json-stable-stringify@npm:^2.0.0":
+"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0":
   version: 2.1.0
   resolution: "fast-json-stable-stringify@npm:2.1.0"
   checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb
   languageName: node
   linkType: hard
 
   version: 2.1.0
   resolution: "fast-json-stable-stringify@npm:2.1.0"
   checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb
   languageName: node
   linkType: hard
 
-"fast-levenshtein@npm:~2.0.6":
+"fast-levenshtein@npm:^2.0.6, fast-levenshtein@npm:~2.0.6":
   version: 2.0.6
   resolution: "fast-levenshtein@npm:2.0.6"
   checksum: 92cfec0a8dfafd9c7a15fba8f2cc29cd0b62b85f056d99ce448bbcd9f708e18ab2764bda4dd5158364f4145a7c72788538994f0d1787b956ef0d1062b0f7c24c
   version: 2.0.6
   resolution: "fast-levenshtein@npm:2.0.6"
   checksum: 92cfec0a8dfafd9c7a15fba8f2cc29cd0b62b85f056d99ce448bbcd9f708e18ab2764bda4dd5158364f4145a7c72788538994f0d1787b956ef0d1062b0f7c24c
@@ -8153,11 +9176,11 @@ __metadata:
   linkType: hard
 
 "fastq@npm:^1.6.0":
   linkType: hard
 
 "fastq@npm:^1.6.0":
-  version: 1.11.0
-  resolution: "fastq@npm:1.11.0"
+  version: 1.17.1
+  resolution: "fastq@npm:1.17.1"
   dependencies:
     reusify: ^1.0.4
   dependencies:
     reusify: ^1.0.4
-  checksum: 9db0ceea9280c5f207da40c562a4e574913c18933cd74b880b01bf8e81a9a6e368ec71e89c9c1b9f4066d0275cc22600efd6dde87f713217acbf67076481734b
+  checksum: a8c5b26788d5a1763f88bae56a8ddeee579f935a831c5fe7a8268cea5b0a91fbfe705f612209e02d639b881d7b48e461a50da4a10cfaa40da5ca7cc9da098d88
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8242,7 +9265,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"figures@npm:^3.0.0, figures@npm:^3.2.0":
+"figures@npm:^3.2.0":
   version: 3.2.0
   resolution: "figures@npm:3.2.0"
   dependencies:
   version: 3.2.0
   resolution: "figures@npm:3.2.0"
   dependencies:
@@ -8251,24 +9274,24 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"file-entry-cache@npm:^5.0.1":
-  version: 5.0.1
-  resolution: "file-entry-cache@npm:5.0.1"
+"file-entry-cache@npm:^6.0.1":
+  version: 6.0.1
+  resolution: "file-entry-cache@npm:6.0.1"
   dependencies:
   dependencies:
-    flat-cache: ^2.0.1
-  checksum: 9014b17766815d59b8b789633aed005242ef857348c09be558bd85b4a24e16b0ad1e0e5229ccea7a2109f74ef1b3db1a559b58afe12b884f09019308711376fd
+    flat-cache: ^3.0.4
+  checksum: f49701feaa6314c8127c3c2f6173cfefff17612f5ed2daaafc6da13b5c91fd43e3b2a58fd0d63f9f94478a501b167615931e7200e31485e320f74a33885a9c74
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"file-loader@npm:4.3.0":
-  version: 4.3.0
-  resolution: "file-loader@npm:4.3.0"
+"file-loader@npm:6.1.1":
+  version: 6.1.1
+  resolution: "file-loader@npm:6.1.1"
   dependencies:
   dependencies:
-    loader-utils: ^1.2.3
-    schema-utils: ^2.5.0
+    loader-utils: ^2.0.0
+    schema-utils: ^3.0.0
   peerDependencies:
   peerDependencies:
-    webpack: ^4.0.0
-  checksum: a005ac5599e96631e8ead32db874283ef821c121e93997b0d6f853db1284bcd7832e1ac59d39a21c201de22b6e33146996c28bd8c486893a5191c334a00f61b2
+    webpack: ^4.0.0 || ^5.0.0
+  checksum: 6369da5af456b640599d7ede7a3a9a55e485138a7829c583313d5165d0984c3d337de3aebee32fdfa3295facb4a44b74a9c3c956b1e0e30e8c96152106ff4b23
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8286,10 +9309,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"filesize@npm:6.0.1":
-  version: 6.0.1
-  resolution: "filesize@npm:6.0.1"
-  checksum: 2e3f9b09a32086e068162a1caf4b6b438aba23389086bf77cf67c410698ed12baf01c928507777b8b2d3e1e2578f2e74f219608a0f7ea210a74e33082c0caab1
+"filesize@npm:6.1.0":
+  version: 6.1.0
+  resolution: "filesize@npm:6.1.0"
+  checksum: c46d644cb562fba7b7e837d5cd339394492abaa06722018b91a97d2a63b6c753ef30653de5c03bf178c631185bf55c3561c28fa9ccc4e9755f42d853c6ed4d09
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8314,29 +9337,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"finalhandler@npm:~1.1.2":
-  version: 1.1.2
-  resolution: "finalhandler@npm:1.1.2"
+"finalhandler@npm:1.2.0":
+  version: 1.2.0
+  resolution: "finalhandler@npm:1.2.0"
   dependencies:
     debug: 2.6.9
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
   dependencies:
     debug: 2.6.9
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
-    on-finished: ~2.3.0
+    on-finished: 2.4.1
     parseurl: ~1.3.3
     parseurl: ~1.3.3
-    statuses: ~1.5.0
+    statuses: 2.0.1
     unpipe: ~1.0.0
     unpipe: ~1.0.0
-  checksum: 617880460c5138dd7ccfd555cb5dde4d8f170f4b31b8bd51e4b646bb2946c30f7db716428a1f2882d730d2b72afb47d1f67cc487b874cb15426f95753a88965e
-  languageName: node
-  linkType: hard
-
-"find-cache-dir@npm:^0.1.1":
-  version: 0.1.1
-  resolution: "find-cache-dir@npm:0.1.1"
-  dependencies:
-    commondir: ^1.0.1
-    mkdirp: ^0.5.1
-    pkg-dir: ^1.0.0
-  checksum: b5d9d68c1ff8c222124bb19089a405be9a3d0333e713ae989d980342c35690dfddd05f0fb456ec11846579e30e0f0e18293d20632662506cd2fa2c7237783479
+  checksum: 92effbfd32e22a7dff2994acedbd9bcc3aa646a3e919ea6a53238090e87097f8ef07cced90aa2cc421abdf993aefbdd5b00104d55c7c5479a8d00ed105b45716
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8372,25 +9384,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"find-up@npm:^1.0.0":
-  version: 1.1.2
-  resolution: "find-up@npm:1.1.2"
-  dependencies:
-    path-exists: ^2.0.0
-    pinkie-promise: ^2.0.0
-  checksum: a2cb9f4c9f06ee3a1e92ed71d5aed41ac8ae30aefa568132f6c556fac7678a5035126153b59eaec68da78ac409eef02503b2b059706bdbf232668d7245e3240a
-  languageName: node
-  linkType: hard
-
-"find-up@npm:^2.0.0, find-up@npm:^2.1.0":
-  version: 2.1.0
-  resolution: "find-up@npm:2.1.0"
-  dependencies:
-    locate-path: ^2.0.0
-  checksum: 43284fe4da09f89011f08e3c32cd38401e786b19226ea440b75386c1b12a4cb738c94969808d53a84f564ede22f732c8409e3cfc3f7fb5b5c32378ad0bbf28bd
-  languageName: node
-  linkType: hard
-
 "find-up@npm:^3.0.0":
   version: 3.0.0
   resolution: "find-up@npm:3.0.0"
 "find-up@npm:^3.0.0":
   version: 3.0.0
   resolution: "find-up@npm:3.0.0"
@@ -8400,21 +9393,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"flat-cache@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "flat-cache@npm:2.0.1"
+"flat-cache@npm:^3.0.4":
+  version: 3.2.0
+  resolution: "flat-cache@npm:3.2.0"
   dependencies:
   dependencies:
-    flatted: ^2.0.0
-    rimraf: 2.6.3
-    write: 1.0.3
-  checksum: 0f5e66467658039e6fcaaccb363b28f43906ba72fab7ff2a4f6fcd5b4899679e13ca46d9fc6cc48b68ac925ae93137106d4aaeb79874c13f21f87a361705f1b1
+    flatted: ^3.2.9
+    keyv: ^4.5.3
+    rimraf: ^3.0.2
+  checksum: e7e0f59801e288b54bee5cb9681e9ee21ee28ef309f886b312c9d08415b79fc0f24ac842f84356ce80f47d6a53de62197ce0e6e148dc42d5db005992e2a756ec
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"flatted@npm:^2.0.0":
-  version: 2.0.2
-  resolution: "flatted@npm:2.0.2"
-  checksum: 473c754db7a529e125a22057098f1a4c905ba17b8cc269c3acf77352f0ffa6304c851eb75f6a1845f74461f560e635129ca6b0b8a78fb253c65cea4de3d776f2
+"flatted@npm:^3.2.9":
+  version: 3.3.1
+  resolution: "flatted@npm:3.3.1"
+  checksum: 85ae7181650bb728c221e7644cbc9f4bf28bc556f2fc89bb21266962bdf0ce1029cc7acc44bb646cd469d9baac7c317f64e841c4c4c00516afa97320cdac7f94
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8435,13 +9428,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0":
-  version: 1.15.3
-  resolution: "follow-redirects@npm:1.15.3"
+"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.15.0":
+  version: 1.15.6
+  resolution: "follow-redirects@npm:1.15.6"
   peerDependenciesMeta:
     debug:
       optional: true
   peerDependenciesMeta:
     debug:
       optional: true
-  checksum: 584da22ec5420c837bd096559ebfb8fe69d82512d5585004e36a3b4a6ef6d5905780e0c74508c7b72f907d1fa2b7bd339e613859e9c304d0dc96af2027fd0231
+  checksum: a62c378dfc8c00f60b9c80cab158ba54e99ba0239a5dd7c81245e5a5b39d10f0c35e249c3379eae719ff0285fff88c365dd446fab19dee771f1d76252df1bbf5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8454,29 +9447,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"for-in@npm:^0.1.3":
-  version: 0.1.8
-  resolution: "for-in@npm:0.1.8"
-  checksum: f5bdad7811700ee6a0f96b33d72a1db966aea75a1f03c7245d147f8369305e709f53a55ee7ae8eaddcfa85c7c89bca78472be8f1bc605475ce5bb2c70f77f8da
-  languageName: node
-  linkType: hard
-
-"for-in@npm:^1.0.1, for-in@npm:^1.0.2":
+"for-in@npm:^1.0.2":
   version: 1.0.2
   resolution: "for-in@npm:1.0.2"
   checksum: 09f4ae93ce785d253ac963d94c7f3432d89398bf25ac7a24ed034ca393bf74380bdeccc40e0f2d721a895e54211b07c8fad7132e8157827f6f7f059b70b4043d
   languageName: node
   linkType: hard
 
   version: 1.0.2
   resolution: "for-in@npm:1.0.2"
   checksum: 09f4ae93ce785d253ac963d94c7f3432d89398bf25ac7a24ed034ca393bf74380bdeccc40e0f2d721a895e54211b07c8fad7132e8157827f6f7f059b70b4043d
   languageName: node
   linkType: hard
 
-"for-own@npm:^0.1.3":
-  version: 0.1.5
-  resolution: "for-own@npm:0.1.5"
-  dependencies:
-    for-in: ^1.0.1
-  checksum: 07eb0a2e98eb55ce13b56dd11ef4fb5e619ba7380aaec388b9eec1946153d74fa734ce409e8434020557e9489a50c34bc004d55754f5863bf7d77b441d8dee8c
-  languageName: node
-  linkType: hard
-
 "forever-agent@npm:~0.6.1":
   version: 0.6.1
   resolution: "forever-agent@npm:0.6.1"
 "forever-agent@npm:~0.6.1":
   version: 0.6.1
   resolution: "forever-agent@npm:0.6.1"
@@ -8484,19 +9461,40 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"fork-ts-checker-webpack-plugin@npm:3.1.1":
-  version: 3.1.1
-  resolution: "fork-ts-checker-webpack-plugin@npm:3.1.1"
+"fork-ts-checker-webpack-plugin@npm:4.1.6":
+  version: 4.1.6
+  resolution: "fork-ts-checker-webpack-plugin@npm:4.1.6"
   dependencies:
   dependencies:
-    babel-code-frame: ^6.22.0
+    "@babel/code-frame": ^7.5.5
     chalk: ^2.4.1
     chalk: ^2.4.1
-    chokidar: ^3.3.0
     micromatch: ^3.1.10
     minimatch: ^3.0.4
     semver: ^5.6.0
     tapable: ^1.0.0
     worker-rpc: ^0.1.0
     micromatch: ^3.1.10
     minimatch: ^3.0.4
     semver: ^5.6.0
     tapable: ^1.0.0
     worker-rpc: ^0.1.0
-  checksum: c7b278e569ec7d5a1c73808b80870dda87f6171dc33c7cf06090c441c54753e1a5886b8256269fd2e100cda980b3f12db02d02ae2008fc067c81e948a173f35f
+  checksum: 4cc4fa7919dd9a0d765514d064c86e3a6f9cea8e700996b3e775cfcc0280f606a2dd16203d9b7e294b64e900795b0d80eb41fc8c192857d3350e407f14ef3eed
+  languageName: node
+  linkType: hard
+
+"form-data@npm:^3.0.0":
+  version: 3.0.1
+  resolution: "form-data@npm:3.0.1"
+  dependencies:
+    asynckit: ^0.4.0
+    combined-stream: ^1.0.8
+    mime-types: ^2.1.12
+  checksum: b019e8d35c8afc14a2bd8a7a92fa4f525a4726b6d5a9740e8d2623c30e308fbb58dc8469f90415a856698933c8479b01646a9dff33c87cc4e76d72aedbbf860d
+  languageName: node
+  linkType: hard
+
+"form-data@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "form-data@npm:4.0.0"
+  dependencies:
+    asynckit: ^0.4.0
+    combined-stream: ^1.0.8
+    mime-types: ^2.1.12
+  checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8544,17 +9542,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"fs-extra@npm:^4.0.2":
-  version: 4.0.3
-  resolution: "fs-extra@npm:4.0.3"
-  dependencies:
-    graceful-fs: ^4.1.2
-    jsonfile: ^4.0.0
-    universalify: ^0.1.0
-  checksum: c5ae3c7043ad7187128e619c0371da01b58694c1ffa02c36fb3f5b459925d9c27c3cb1e095d9df0a34a85ca993d8b8ff6f6ecef868fd5ebb243548afa7fc0936
-  languageName: node
-  linkType: hard
-
 "fs-extra@npm:^7.0.0":
   version: 7.0.1
   resolution: "fs-extra@npm:7.0.1"
 "fs-extra@npm:^7.0.0":
   version: 7.0.1
   resolution: "fs-extra@npm:7.0.1"
@@ -8577,7 +9564,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"fs-extra@npm:^9.1.0":
+"fs-extra@npm:^9.0.1, fs-extra@npm:^9.1.0":
   version: 9.1.0
   resolution: "fs-extra@npm:9.1.0"
   dependencies:
   version: 9.1.0
   resolution: "fs-extra@npm:9.1.0"
   dependencies:
@@ -8617,16 +9604,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"fsevents@npm:2.1.2":
-  version: 2.1.2
-  resolution: "fsevents@npm:2.1.2"
-  dependencies:
-    node-gyp: latest
-  checksum: 63fe1ba77b63d5da5dde6112c5f0eb161b9d18a61427a8a49d661eeed080189d99e8f9da11bb6b75ecd5129a69edc5757d60a4eb0bbada6de68d5156c382c5e1
-  conditions: os=darwin
-  languageName: node
-  linkType: hard
-
 "fsevents@npm:^1.2.7":
   version: 1.2.13
   resolution: "fsevents@npm:1.2.13"
 "fsevents@npm:^1.2.7":
   version: 1.2.13
   resolution: "fsevents@npm:1.2.13"
@@ -8638,21 +9615,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"fsevents@npm:~2.3.2":
-  version: 2.3.2
-  resolution: "fsevents@npm:2.3.2"
+"fsevents@npm:^2.1.2, fsevents@npm:^2.1.3":
+  version: 2.3.3
+  resolution: "fsevents@npm:2.3.3"
   dependencies:
     node-gyp: latest
   dependencies:
     node-gyp: latest
-  checksum: 97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f
+  checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317
   conditions: os=darwin
   languageName: node
   linkType: hard
 
   conditions: os=darwin
   languageName: node
   linkType: hard
 
-"fsevents@patch:fsevents@2.1.2#~builtin<compat/fsevents>":
-  version: 2.1.2
-  resolution: "fsevents@patch:fsevents@npm%3A2.1.2#~builtin<compat/fsevents>::version=2.1.2&hash=18f3a7"
+"fsevents@npm:~2.3.2":
+  version: 2.3.2
+  resolution: "fsevents@npm:2.3.2"
   dependencies:
     node-gyp: latest
   dependencies:
     node-gyp: latest
+  checksum: 97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f
   conditions: os=darwin
   languageName: node
   linkType: hard
   conditions: os=darwin
   languageName: node
   linkType: hard
@@ -8667,6 +9645,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"fsevents@patch:fsevents@^2.1.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@^2.1.3#~builtin<compat/fsevents>":
+  version: 2.3.3
+  resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin<compat/fsevents>::version=2.3.3&hash=18f3a7"
+  dependencies:
+    node-gyp: latest
+  conditions: os=darwin
+  languageName: node
+  linkType: hard
+
 "fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>":
   version: 2.3.2
   resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=18f3a7"
 "fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>":
   version: 2.3.2
   resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=18f3a7"
@@ -8695,6 +9682,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"function-bind@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "function-bind@npm:1.1.2"
+  checksum: 2b0ff4ce708d99715ad14a6d1f894e2a83242e4a52ccfcefaee5e40050562e5f6dafc1adbb4ce2d4ab47279a45dc736ab91ea5042d843c3c092820dfe032efb1
+  languageName: node
+  linkType: hard
+
 "function.prototype.name@npm:^1.1.2, function.prototype.name@npm:^1.1.3":
   version: 1.1.4
   resolution: "function.prototype.name@npm:1.1.4"
 "function.prototype.name@npm:^1.1.2, function.prototype.name@npm:^1.1.3":
   version: 1.1.4
   resolution: "function.prototype.name@npm:1.1.4"
@@ -8707,6 +9701,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"function.prototype.name@npm:^1.1.5, function.prototype.name@npm:^1.1.6":
+  version: 1.1.6
+  resolution: "function.prototype.name@npm:1.1.6"
+  dependencies:
+    call-bind: ^1.0.2
+    define-properties: ^1.2.0
+    es-abstract: ^1.22.1
+    functions-have-names: ^1.2.3
+  checksum: 7a3f9bd98adab09a07f6e1f03da03d3f7c26abbdeaeee15223f6c04a9fb5674792bdf5e689dac19b97ac71de6aad2027ba3048a9b883aa1b3173eed6ab07f479
+  languageName: node
+  linkType: hard
+
 "functional-red-black-tree@npm:^1.0.1":
   version: 1.0.1
   resolution: "functional-red-black-tree@npm:1.0.1"
 "functional-red-black-tree@npm:^1.0.1":
   version: 1.0.1
   resolution: "functional-red-black-tree@npm:1.0.1"
@@ -8721,20 +9727,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"gauge@npm:^3.0.0":
-  version: 3.0.2
-  resolution: "gauge@npm:3.0.2"
-  dependencies:
-    aproba: ^1.0.3 || ^2.0.0
-    color-support: ^1.1.2
-    console-control-strings: ^1.0.0
-    has-unicode: ^2.0.1
-    object-assign: ^4.1.1
-    signal-exit: ^3.0.0
-    string-width: ^4.2.3
-    strip-ansi: ^6.0.1
-    wide-align: ^1.1.2
-  checksum: 81296c00c7410cdd48f997800155fbead4f32e4f82109be0719c63edc8560e6579946cc8abd04205297640691ec26d21b578837fd13a4e96288ab4b40b1dc3e9
+"functions-have-names@npm:^1.2.3":
+  version: 1.2.3
+  resolution: "functions-have-names@npm:1.2.3"
+  checksum: c3f1f5ba20f4e962efb71344ce0a40722163e85bee2101ce25f88214e78182d2d2476aa85ef37950c579eb6cf6ee811c17b3101bb84004bb75655f3e33f3fdb5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -8771,13 +9767,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"get-caller-file@npm:^1.0.1":
-  version: 1.0.3
-  resolution: "get-caller-file@npm:1.0.3"
-  checksum: 2b90a7f848896abcebcdc0acc627a435bcf05b9cd280599bc980ebfcdc222416c3df12c24c4845f69adc4346728e8966f70b758f9369f3534182791dfbc25c05
-  languageName: node
-  linkType: hard
-
 "get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5":
   version: 2.0.5
   resolution: "get-caller-file@npm:2.0.5"
 "get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5":
   version: 2.0.5
   resolution: "get-caller-file@npm:2.0.5"
@@ -8785,7 +9774,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
+"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1":
   version: 1.1.1
   resolution: "get-intrinsic@npm:1.1.1"
   dependencies:
   version: 1.1.1
   resolution: "get-intrinsic@npm:1.1.1"
   dependencies:
@@ -8796,6 +9785,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.3, get-intrinsic@npm:^1.2.4":
+  version: 1.2.4
+  resolution: "get-intrinsic@npm:1.2.4"
+  dependencies:
+    es-errors: ^1.3.0
+    function-bind: ^1.1.2
+    has-proto: ^1.0.1
+    has-symbols: ^1.0.3
+    hasown: ^2.0.0
+  checksum: 414e3cdf2c203d1b9d7d33111df746a4512a1aa622770b361dadddf8ed0b5aeb26c560f49ca077e24bfafb0acb55ca908d1f709216ccba33ffc548ec8a79a951
+  languageName: node
+  linkType: hard
+
 "get-own-enumerable-property-symbols@npm:^3.0.0":
   version: 3.0.2
   resolution: "get-own-enumerable-property-symbols@npm:3.0.2"
 "get-own-enumerable-property-symbols@npm:^3.0.0":
   version: 3.0.2
   resolution: "get-own-enumerable-property-symbols@npm:3.0.2"
@@ -8803,6 +9805,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"get-package-type@npm:^0.1.0":
+  version: 0.1.0
+  resolution: "get-package-type@npm:0.1.0"
+  checksum: bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148
+  languageName: node
+  linkType: hard
+
 "get-stdin@npm:^4.0.1":
   version: 4.0.1
   resolution: "get-stdin@npm:4.0.1"
 "get-stdin@npm:^4.0.1":
   version: 4.0.1
   resolution: "get-stdin@npm:4.0.1"
@@ -8828,6 +9837,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"get-symbol-description@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "get-symbol-description@npm:1.0.2"
+  dependencies:
+    call-bind: ^1.0.5
+    es-errors: ^1.3.0
+    get-intrinsic: ^1.2.4
+  checksum: e1cb53bc211f9dbe9691a4f97a46837a553c4e7caadd0488dc24ac694db8a390b93edd412b48dcdd0b4bbb4c595de1709effc75fc87c0839deedc6968f5bd973
+  languageName: node
+  linkType: hard
+
 "get-value@npm:^2.0.3, get-value@npm:^2.0.6":
   version: 2.0.6
   resolution: "get-value@npm:2.0.6"
 "get-value@npm:^2.0.3, get-value@npm:^2.0.6":
   version: 2.0.6
   resolution: "get-value@npm:2.0.6"
@@ -8863,7 +9883,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"glob-parent@npm:^5.0.0, glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2":
+"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2":
   version: 5.1.2
   resolution: "glob-parent@npm:5.1.2"
   dependencies:
   version: 5.1.2
   resolution: "glob-parent@npm:5.1.2"
   dependencies:
@@ -8872,13 +9892,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"glob-to-regexp@npm:^0.3.0":
-  version: 0.3.0
-  resolution: "glob-to-regexp@npm:0.3.0"
-  checksum: d34b3219d860042d508c4893b67617cd16e2668827e445ff39cff9f72ef70361d3dc24f429e003cdfb6607c75c9664b8eadc41d2eeb95690af0b0d3113c1b23b
-  languageName: node
-  linkType: hard
-
 "glob@npm:^7.0.0, glob@npm:^7.0.3, glob@npm:^7.0.5, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:~7.1.1":
   version: 7.1.7
   resolution: "glob@npm:7.1.7"
 "glob@npm:^7.0.0, glob@npm:^7.0.3, glob@npm:^7.0.5, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:~7.1.1":
   version: 7.1.7
   resolution: "glob@npm:7.1.7"
@@ -8893,6 +9906,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"glob@npm:^7.1.6":
+  version: 7.2.3
+  resolution: "glob@npm:7.2.3"
+  dependencies:
+    fs.realpath: ^1.0.0
+    inflight: ^1.0.4
+    inherits: 2
+    minimatch: ^3.1.1
+    once: ^1.3.0
+    path-is-absolute: ^1.0.0
+  checksum: 29452e97b38fa704dabb1d1045350fb2467cf0277e155aa9ff7077e90ad81d1ea9d53d3ee63bd37c05b09a065e90f16aec4a65f5b8de401d1dac40bc5605d133
+  languageName: node
+  linkType: hard
+
 "glob@npm:^8.0.1":
   version: 8.1.0
   resolution: "glob@npm:8.1.0"
 "glob@npm:^8.0.1":
   version: 8.1.0
   resolution: "glob@npm:8.1.0"
@@ -8942,34 +9969,35 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"globals@npm:^12.1.0":
-  version: 12.4.0
-  resolution: "globals@npm:12.4.0"
+"globals@npm:^13.6.0, globals@npm:^13.9.0":
+  version: 13.24.0
+  resolution: "globals@npm:13.24.0"
   dependencies:
   dependencies:
-    type-fest: ^0.8.1
-  checksum: 7ae5ee16a96f1e8d71065405f57da0e33267f6b070cd36a5444c7780dd28639b48b92413698ac64f04bf31594f9108878bd8cb158ecdf759c39e05634fefcca6
+    type-fest: ^0.20.2
+  checksum: 56066ef058f6867c04ff203b8a44c15b038346a62efbc3060052a1016be9f56f4cf0b2cd45b74b22b81e521a889fc7786c73691b0549c2f3a6e825b3d394f43c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"globals@npm:^9.18.0":
-  version: 9.18.0
-  resolution: "globals@npm:9.18.0"
-  checksum: e9c066aecfdc5ea6f727344a4246ecc243aaf66ede3bffee10ddc0c73351794c25e727dd046090dcecd821199a63b9de6af299a6e3ba292c8b22f0a80ea32073
+"globalthis@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "globalthis@npm:1.0.3"
+  dependencies:
+    define-properties: ^1.1.3
+  checksum: fbd7d760dc464c886d0196166d92e5ffb4c84d0730846d6621a39fbbc068aeeb9c8d1421ad330e94b7bca4bb4ea092f5f21f3d36077812af5d098b4dc006c998
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"globby@npm:8.0.2":
-  version: 8.0.2
-  resolution: "globby@npm:8.0.2"
+"globby@npm:11.0.1":
+  version: 11.0.1
+  resolution: "globby@npm:11.0.1"
   dependencies:
   dependencies:
-    array-union: ^1.0.1
-    dir-glob: 2.0.0
-    fast-glob: ^2.0.2
-    glob: ^7.1.2
-    ignore: ^3.3.5
-    pify: ^3.0.0
-    slash: ^1.0.0
-  checksum: 87dc31e0b812d3a6beee200555c252591d23ef12f8347bce3b61fa185a99fbe7ae1694ed30cc01a353e27369d6a8e1e50a97f1c5e2547fa7b1d87d8392ff9264
+    array-union: ^2.1.0
+    dir-glob: ^3.0.1
+    fast-glob: ^3.1.1
+    ignore: ^5.1.4
+    merge2: ^1.3.0
+    slash: ^3.0.0
+  checksum: b0b26e580666ef8caf0b0facd585c1da46eb971207ee9f8c7b690c1372d77602dd072f047f26c3ae1c293807fdf8fb6890d9291d37bc6d2602b1f07386f983e5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -9011,13 +10039,29 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2":
+"gopd@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "gopd@npm:1.0.1"
+  dependencies:
+    get-intrinsic: ^1.1.3
+  checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6
+  languageName: node
+  linkType: hard
+
+"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0":
   version: 4.2.6
   resolution: "graceful-fs@npm:4.2.6"
   checksum: 792e64aafda05a151289f83eaa16aff34ef259658cefd65393883d959409f5a2389b0ec9ebf28f3d21f1b0ddc8f594a1162ae9b18e2b507a6799a70706ec573d
   languageName: node
   linkType: hard
 
   version: 4.2.6
   resolution: "graceful-fs@npm:4.2.6"
   checksum: 792e64aafda05a151289f83eaa16aff34ef259658cefd65393883d959409f5a2389b0ec9ebf28f3d21f1b0ddc8f594a1162ae9b18e2b507a6799a70706ec573d
   languageName: node
   linkType: hard
 
+"graceful-fs@npm:^4.2.4":
+  version: 4.2.11
+  resolution: "graceful-fs@npm:4.2.11"
+  checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7
+  languageName: node
+  linkType: hard
+
 "graceful-fs@npm:^4.2.6":
   version: 4.2.9
   resolution: "graceful-fs@npm:4.2.9"
 "graceful-fs@npm:^4.2.6":
   version: 4.2.9
   resolution: "graceful-fs@npm:4.2.9"
@@ -9080,15 +10124,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"has-ansi@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "has-ansi@npm:2.0.0"
-  dependencies:
-    ansi-regex: ^2.0.0
-  checksum: 1b51daa0214440db171ff359d0a2d17bc20061164c57e76234f614c91dbd2a79ddd68dfc8ee73629366f7be45a6df5f2ea9de83f52e1ca24433f2cc78c35d8ec
-  languageName: node
-  linkType: hard
-
 "has-bigints@npm:^1.0.1":
   version: 1.0.1
   resolution: "has-bigints@npm:1.0.1"
 "has-bigints@npm:^1.0.1":
   version: 1.0.1
   resolution: "has-bigints@npm:1.0.1"
@@ -9096,6 +10131,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"has-bigints@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "has-bigints@npm:1.0.2"
+  checksum: 390e31e7be7e5c6fe68b81babb73dfc35d413604d7ee5f56da101417027a4b4ce6a27e46eff97ad040c835b5d228676eae99a9b5c3bc0e23c8e81a49241ff45b
+  languageName: node
+  linkType: hard
+
 "has-flag@npm:^3.0.0":
   version: 3.0.0
   resolution: "has-flag@npm:3.0.0"
 "has-flag@npm:^3.0.0":
   version: 3.0.0
   resolution: "has-flag@npm:3.0.0"
@@ -9110,6 +10152,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "has-property-descriptors@npm:1.0.2"
+  dependencies:
+    es-define-property: ^1.0.0
+  checksum: fcbb246ea2838058be39887935231c6d5788babed499d0e9d0cc5737494c48aba4fe17ba1449e0d0fbbb1e36175442faa37f9c427ae357d6ccb1d895fbcd3de3
+  languageName: node
+  linkType: hard
+
+"has-proto@npm:^1.0.1, has-proto@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "has-proto@npm:1.0.3"
+  checksum: fe7c3d50b33f50f3933a04413ed1f69441d21d2d2944f81036276d30635cad9279f6b43bc8f32036c31ebdfcf6e731150f46c1907ad90c669ffe9b066c3ba5c4
+  languageName: node
+  linkType: hard
+
 "has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2":
   version: 1.0.2
   resolution: "has-symbols@npm:1.0.2"
 "has-symbols@npm:^1.0.1, has-symbols@npm:^1.0.2":
   version: 1.0.2
   resolution: "has-symbols@npm:1.0.2"
@@ -9117,6 +10175,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"has-symbols@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "has-symbols@npm:1.0.3"
+  checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410
+  languageName: node
+  linkType: hard
+
+"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "has-tostringtag@npm:1.0.2"
+  dependencies:
+    has-symbols: ^1.0.3
+  checksum: 999d60bb753ad714356b2c6c87b7fb74f32463b8426e159397da4bde5bca7e598ab1073f4d8d4deafac297f2eb311484cd177af242776bf05f0d11565680468d
+  languageName: node
+  linkType: hard
+
 "has-unicode@npm:^2.0.1":
   version: 2.0.1
   resolution: "has-unicode@npm:2.0.1"
 "has-unicode@npm:^2.0.1":
   version: 2.0.1
   resolution: "has-unicode@npm:2.0.1"
@@ -9183,6 +10257,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"hash-base@npm:~3.0":
+  version: 3.0.4
+  resolution: "hash-base@npm:3.0.4"
+  dependencies:
+    inherits: ^2.0.1
+    safe-buffer: ^5.0.1
+  checksum: 878465a0dfcc33cce195c2804135352c590d6d10980adc91a9005fd377e77f2011256c2b7cfce472e3f2e92d561d1bf3228d2da06348a9017ce9a258b3b49764
+  languageName: node
+  linkType: hard
+
 "hash.js@npm:^1.0.0, hash.js@npm:^1.0.3":
   version: 1.1.7
   resolution: "hash.js@npm:1.1.7"
 "hash.js@npm:^1.0.0, hash.js@npm:^1.0.3":
   version: 1.1.7
   resolution: "hash.js@npm:1.1.7"
@@ -9193,6 +10277,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"hasown@npm:^2.0.0, hasown@npm:^2.0.1, hasown@npm:^2.0.2":
+  version: 2.0.2
+  resolution: "hasown@npm:2.0.2"
+  dependencies:
+    function-bind: ^1.1.2
+  checksum: e8516f776a15149ca6c6ed2ae3110c417a00b62260e222590e54aa367cbcd6ed99122020b37b7fbdf05748df57b265e70095d7bf35a47660587619b15ffb93db
+  languageName: node
+  linkType: hard
+
 "he@npm:^1.2.0":
   version: 1.2.0
   resolution: "he@npm:1.2.0"
 "he@npm:^1.2.0":
   version: 1.2.0
   resolution: "he@npm:1.2.0"
@@ -9257,13 +10350,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"home-or-tmp@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "home-or-tmp@npm:2.0.0"
-  dependencies:
-    os-homedir: ^1.0.0
-    os-tmpdir: ^1.0.1
-  checksum: b783c6ffd22f716d82f53e8c781cbe49bc9f4109a89ea86a27951e54c0bd335caf06bd828be2958cd9f4681986df1739558ae786abda6298cdd6d3edc2c362f1
+"hoopy@npm:^0.1.4":
+  version: 0.1.4
+  resolution: "hoopy@npm:0.1.4"
+  checksum: cfa60c7684c5e1ee4efe26e167bc54b73f839ffb59d1d44a5c4bf891e26b4f5bcc666555219a98fec95508fea4eda3a79540c53c05cc79afc1f66f9a238f4d9e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -9319,16 +10409,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"html-encoding-sniffer@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "html-encoding-sniffer@npm:1.0.2"
+"html-encoding-sniffer@npm:^2.0.1":
+  version: 2.0.1
+  resolution: "html-encoding-sniffer@npm:2.0.1"
   dependencies:
   dependencies:
-    whatwg-encoding: ^1.0.1
-  checksum: b874df6750451b7642fbe8e998c6bdd2911b0f42ad2927814b717bf1f4b082b0904b6178a1bfbc40117bf5799777993b0825e7713ca0fca49844e5aec03aa0e2
+    whatwg-encoding: ^1.0.5
+  checksum: bf30cce461015ed7e365736fcd6a3063c7bc016a91f74398ef6158886970a96333938f7c02417ab3c12aa82e3e53b40822145facccb9ddfbcdc15a879ae4d7ba
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"html-entities@npm:^1.3.1":
+"html-entities@npm:^1.2.1, html-entities@npm:^1.3.1":
   version: 1.4.0
   resolution: "html-entities@npm:1.4.0"
   checksum: 4b73ffb9eead200f99146e4fbe70acb0af2fea136901a131fc3a782e9ef876a7cbb07dec303ca1f8804232b812249dbf3643a270c9c524852065d9224a8dcdd0
   version: 1.4.0
   resolution: "html-entities@npm:1.4.0"
   checksum: 4b73ffb9eead200f99146e4fbe70acb0af2fea136901a131fc3a782e9ef876a7cbb07dec303ca1f8804232b812249dbf3643a270c9c524852065d9224a8dcdd0
@@ -9359,10 +10449,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"html-webpack-plugin@npm:4.0.0-beta.11":
-  version: 4.0.0-beta.11
-  resolution: "html-webpack-plugin@npm:4.0.0-beta.11"
+"html-webpack-plugin@npm:4.5.0":
+  version: 4.5.0
+  resolution: "html-webpack-plugin@npm:4.5.0"
   dependencies:
   dependencies:
+    "@types/html-minifier-terser": ^5.0.0
+    "@types/tapable": ^1.0.5
+    "@types/webpack": ^4.41.8
     html-minifier-terser: ^5.0.1
     loader-utils: ^1.2.3
     lodash: ^4.17.15
     html-minifier-terser: ^5.0.1
     loader-utils: ^1.2.3
     lodash: ^4.17.15
@@ -9370,8 +10463,8 @@ __metadata:
     tapable: ^1.1.3
     util.promisify: 1.0.0
   peerDependencies:
     tapable: ^1.1.3
     util.promisify: 1.0.0
   peerDependencies:
-    webpack: ^4.0.0
-  checksum: ea34c7a12d20f938c59e6b5f404aaddac4689ec622995b748ce13e0016e52a199ff25a837b905dd756bebcfb35465435d4c455ed36e16bae3d3dc5e0706d0030
+    webpack: ^4.0.0 || ^5.0.0
+  checksum: d197db16a160ab9136a544e297c3c75d34b769d3cee12a82b9e7af7ee38ff07f4a27f2235581a9624f03996cd24997613df807341799140b4427c12bc4f496f9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -9401,16 +10494,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"http-errors@npm:1.7.2":
-  version: 1.7.2
-  resolution: "http-errors@npm:1.7.2"
+"http-errors@npm:2.0.0":
+  version: 2.0.0
+  resolution: "http-errors@npm:2.0.0"
   dependencies:
   dependencies:
-    depd: ~1.1.2
-    inherits: 2.0.3
-    setprototypeof: 1.1.1
-    statuses: ">= 1.5.0 < 2"
-    toidentifier: 1.0.0
-  checksum: 5534b0ae08e77f5a45a2380f500e781f6580c4ff75b816cb1f09f99a290b57e78a518be6d866db1b48cca6b052c09da2c75fc91fb16a2fe3da3c44d9acbb9972
+    depd: 2.0.0
+    inherits: 2.0.4
+    setprototypeof: 1.2.0
+    statuses: 2.0.1
+    toidentifier: 1.0.1
+  checksum: 9b0a3782665c52ce9dc658a0d1560bcb0214ba5699e4ea15aefb2a496e2ca83db03ebc42e1cce4ac1f413e4e0d2d736a3fd755772c556a9a06853ba2a0b7d920
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -9426,19 +10519,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"http-errors@npm:~1.7.2":
-  version: 1.7.3
-  resolution: "http-errors@npm:1.7.3"
-  dependencies:
-    depd: ~1.1.2
-    inherits: 2.0.4
-    setprototypeof: 1.1.1
-    statuses: ">= 1.5.0 < 2"
-    toidentifier: 1.0.0
-  checksum: a59f359473f4b3ea78305beee90d186268d6075432622a46fb7483059068a2dd4c854a20ac8cd438883127e06afb78c1309168bde6cdfeed1e3700eb42487d99
-  languageName: node
-  linkType: hard
-
 "http-parser-js@npm:>=0.5.1":
   version: 0.5.3
   resolution: "http-parser-js@npm:0.5.3"
 "http-parser-js@npm:>=0.5.1":
   version: 0.5.3
   resolution: "http-parser-js@npm:0.5.3"
@@ -9553,7 +10633,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
+"iconv-lite@npm:0.4.24":
   version: 0.4.24
   resolution: "iconv-lite@npm:0.4.24"
   dependencies:
   version: 0.4.24
   resolution: "iconv-lite@npm:0.4.24"
   dependencies:
@@ -9603,13 +10683,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ignore@npm:^3.3.5":
-  version: 3.3.10
-  resolution: "ignore@npm:3.3.10"
-  checksum: 23e8cc776e367b56615ab21b78decf973a35dfca5522b39d9b47643d8168473b0d1f18dd1321a1bab466a12ea11a2411903f3b21644f4d5461ee0711ec8678bd
-  languageName: node
-  linkType: hard
-
 "ignore@npm:^4.0.6":
   version: 4.0.6
   resolution: "ignore@npm:4.0.6"
 "ignore@npm:^4.0.6":
   version: 4.0.6
   resolution: "ignore@npm:4.0.6"
@@ -9617,10 +10690,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ignore@npm:^5.2.0":
-  version: 5.2.4
-  resolution: "ignore@npm:5.2.4"
-  checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef
+"ignore@npm:^5.1.4, ignore@npm:^5.1.8, ignore@npm:^5.2.0":
+  version: 5.3.1
+  resolution: "ignore@npm:5.3.1"
+  checksum: 71d7bb4c1dbe020f915fd881108cbe85a0db3d636a0ea3ba911393c53946711d13a9b1143c7e70db06d571a5822c0a324a6bcde5c9904e7ca5047f01f1bf8cd3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -9638,10 +10711,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"immer@npm:1.10.0":
-  version: 1.10.0
-  resolution: "immer@npm:1.10.0"
-  checksum: 8bdce9ebd81861dcef21725bc0f9cc456c2051188b7c001bcd9b9dffb9519cd897ab207f475b5425b83767a4b1fba76b4665e3f3c41171e24ea938c3cd02d035
+"immer@npm:8.0.1":
+  version: 8.0.1
+  resolution: "immer@npm:8.0.1"
+  checksum: 63d875c04882b862481a0a692816e5982975a47a57619698bc1bb52963ad3b624911991708b2b81a7af6fdac15083d408e43932d83a6a61216e5a4503efd4095
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -9678,7 +10751,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"import-fresh@npm:^3.0.0, import-fresh@npm:^3.1.0":
+"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1":
   version: 3.3.0
   resolution: "import-fresh@npm:3.3.0"
   dependencies:
   version: 3.3.0
   resolution: "import-fresh@npm:3.3.0"
   dependencies:
@@ -9709,6 +10782,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"import-local@npm:^3.0.2":
+  version: 3.1.0
+  resolution: "import-local@npm:3.1.0"
+  dependencies:
+    pkg-dir: ^4.2.0
+    resolve-cwd: ^3.0.0
+  bin:
+    import-local-fixture: fixtures/cli.js
+  checksum: bfcdb63b5e3c0e245e347f3107564035b128a414c4da1172a20dc67db2504e05ede4ac2eee1252359f78b0bfd7b19ef180aec427c2fce6493ae782d73a04cddd
+  languageName: node
+  linkType: hard
+
 "imurmurhash@npm:^0.1.4":
   version: 0.1.4
   resolution: "imurmurhash@npm:0.1.4"
 "imurmurhash@npm:^0.1.4":
   version: 0.1.4
   resolution: "imurmurhash@npm:0.1.4"
@@ -9725,15 +10810,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"indent-string@npm:^2.1.0":
-  version: 2.1.0
-  resolution: "indent-string@npm:2.1.0"
-  dependencies:
-    repeating: ^2.0.0
-  checksum: 2fe7124311435f4d7a98f0a314d8259a4ec47ecb221110a58e2e2073e5f75c8d2b4f775f2ed199598fbe20638917e57423096539455ca8bff8eab113c9bee12c
-  languageName: node
-  linkType: hard
-
 "indent-string@npm:^4.0.0":
   version: 4.0.0
   resolution: "indent-string@npm:4.0.0"
 "indent-string@npm:^4.0.0":
   version: 4.0.0
   resolution: "indent-string@npm:4.0.0"
@@ -9800,48 +10876,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"inquirer@npm:7.0.4":
-  version: 7.0.4
-  resolution: "inquirer@npm:7.0.4"
-  dependencies:
-    ansi-escapes: ^4.2.1
-    chalk: ^2.4.2
-    cli-cursor: ^3.1.0
-    cli-width: ^2.0.0
-    external-editor: ^3.0.3
-    figures: ^3.0.0
-    lodash: ^4.17.15
-    mute-stream: 0.0.8
-    run-async: ^2.2.0
-    rxjs: ^6.5.3
-    string-width: ^4.1.0
-    strip-ansi: ^5.1.0
-    through: ^2.3.6
-  checksum: 01a87cdbe74e7eb5ca770580f0d6bcad0269e6b0da97107aa9e2b37446a795aac63a63865d33410e964441499f9ac34a84c2e97c40d1abe2e57efc7f0d5b416d
-  languageName: node
-  linkType: hard
-
-"inquirer@npm:^7.0.0":
-  version: 7.3.3
-  resolution: "inquirer@npm:7.3.3"
-  dependencies:
-    ansi-escapes: ^4.2.1
-    chalk: ^4.1.0
-    cli-cursor: ^3.1.0
-    cli-width: ^3.0.0
-    external-editor: ^3.0.3
-    figures: ^3.0.0
-    lodash: ^4.17.19
-    mute-stream: 0.0.8
-    run-async: ^2.4.0
-    rxjs: ^6.6.0
-    string-width: ^4.1.0
-    strip-ansi: ^6.0.0
-    through: ^2.3.6
-  checksum: 4d387fc1eb6126acbd58cbdb9ad99d2887d181df86ab0c2b9abdf734e751093e2d5882c2b6dc7144d9ab16b7ab30a78a1d7f01fb6a2850a44aeb175d1e3f8778
-  languageName: node
-  linkType: hard
-
 "internal-ip@npm:^4.3.0":
   version: 4.3.0
   resolution: "internal-ip@npm:4.3.0"
 "internal-ip@npm:^4.3.0":
   version: 4.3.0
   resolution: "internal-ip@npm:4.3.0"
@@ -9852,18 +10886,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"internal-slot@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "internal-slot@npm:1.0.3"
+"internal-slot@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "internal-slot@npm:1.0.7"
   dependencies:
   dependencies:
-    get-intrinsic: ^1.1.0
-    has: ^1.0.3
+    es-errors: ^1.3.0
+    hasown: ^2.0.0
     side-channel: ^1.0.4
     side-channel: ^1.0.4
-  checksum: 1944f92e981e47aebc98a88ff0db579fd90543d937806104d0b96557b10c1f170c51fb777b97740a8b6ddeec585fca8c39ae99fd08a8e058dfc8ab70937238bf
+  checksum: cadc5eea5d7d9bc2342e93aae9f31f04c196afebb11bde97448327049f492cd7081e18623ae71388aac9cd237b692ca3a105be9c68ac39c1dec679d7409e33eb
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"invariant@npm:^2.0.0, invariant@npm:^2.1.0, invariant@npm:^2.2.2, invariant@npm:^2.2.4":
+"invariant@npm:^2.0.0, invariant@npm:^2.1.0, invariant@npm:^2.2.4":
   version: 2.2.4
   resolution: "invariant@npm:2.2.4"
   dependencies:
   version: 2.2.4
   resolution: "invariant@npm:2.2.4"
   dependencies:
@@ -9872,13 +10906,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"invert-kv@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "invert-kv@npm:1.0.0"
-  checksum: aebeee31dda3b3d25ffd242e9a050926e7fe5df642d60953ab183aca1a7d1ffb39922eb2618affb0e850cf2923116f0da1345367759d88d097df5da1f1e1590e
-  languageName: node
-  linkType: hard
-
 "ip-regex@npm:^2.1.0":
   version: 2.1.0
   resolution: "ip-regex@npm:2.1.0"
 "ip-regex@npm:^2.1.0":
   version: 2.1.0
   resolution: "ip-regex@npm:2.1.0"
@@ -9887,16 +10914,16 @@ __metadata:
   linkType: hard
 
 "ip@npm:^1.1.0, ip@npm:^1.1.5":
   linkType: hard
 
 "ip@npm:^1.1.0, ip@npm:^1.1.5":
-  version: 1.1.5
-  resolution: "ip@npm:1.1.5"
-  checksum: 30133981f082a060a32644f6a7746e9ba7ac9e2bc07ecc8bbdda3ee8ca9bec1190724c390e45a1ee7695e7edfd2a8f7dda2c104ec5f7ac5068c00648504c7e5a
+  version: 1.1.9
+  resolution: "ip@npm:1.1.9"
+  checksum: b6d91fd45a856e3bd6d4f601ea0619d90f3517638f6918ebd079f959a8a6308568d8db5ef4fdf037e0d9cfdcf264f46833dfeea81ca31309cf0a7eb4b1307b84
   languageName: node
   linkType: hard
 
 "ip@npm:^2.0.0":
   languageName: node
   linkType: hard
 
 "ip@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "ip@npm:2.0.0"
-  checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349
+  version: 2.0.1
+  resolution: "ip@npm:2.0.1"
+  checksum: d765c9fd212b8a99023a4cde6a558a054c298d640fec1020567494d257afd78ca77e37126b1a3ef0e053646ced79a816bf50621d38d5e768cdde0431fa3b0d35
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -9948,6 +10975,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-array-buffer@npm:^3.0.4":
+  version: 3.0.4
+  resolution: "is-array-buffer@npm:3.0.4"
+  dependencies:
+    call-bind: ^1.0.2
+    get-intrinsic: ^1.2.1
+  checksum: e4e3e6ef0ff2239e75371d221f74bc3c26a03564a22efb39f6bb02609b598917ddeecef4e8c877df2a25888f247a98198959842a5e73236bc7f22cabdf6351a7
+  languageName: node
+  linkType: hard
+
 "is-arrayish@npm:^0.2.1":
   version: 0.2.1
   resolution: "is-arrayish@npm:0.2.1"
 "is-arrayish@npm:^0.2.1":
   version: 0.2.1
   resolution: "is-arrayish@npm:0.2.1"
@@ -9962,6 +10999,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-async-function@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "is-async-function@npm:2.0.0"
+  dependencies:
+    has-tostringtag: ^1.0.0
+  checksum: e3471d95e6c014bf37cad8a93f2f4b6aac962178e0a5041e8903147166964fdc1c5c1d2ef87e86d77322c370ca18f2ea004fa7420581fa747bcaf7c223069dbd
+  languageName: node
+  linkType: hard
+
 "is-bigint@npm:^1.0.1":
   version: 1.0.2
   resolution: "is-bigint@npm:1.0.2"
 "is-bigint@npm:^1.0.1":
   version: 1.0.2
   resolution: "is-bigint@npm:1.0.2"
@@ -9996,7 +11042,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"is-buffer@npm:^1.0.2, is-buffer@npm:^1.1.5":
+"is-buffer@npm:^1.1.5":
   version: 1.1.6
   resolution: "is-buffer@npm:1.1.6"
   checksum: 4a186d995d8bbf9153b4bd9ff9fd04ae75068fe695d29025d25e592d9488911eeece84eefbd8fa41b8ddcc0711058a71d4c466dcf6f1f6e1d83830052d8ca707
   version: 1.1.6
   resolution: "is-buffer@npm:1.1.6"
   checksum: 4a186d995d8bbf9153b4bd9ff9fd04ae75068fe695d29025d25e592d9488911eeece84eefbd8fa41b8ddcc0711058a71d4c466dcf6f1f6e1d83830052d8ca707
@@ -10010,6 +11056,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-callable@npm:^1.2.7":
+  version: 1.2.7
+  resolution: "is-callable@npm:1.2.7"
+  checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac
+  languageName: node
+  linkType: hard
+
 "is-ci@npm:^2.0.0":
   version: 2.0.0
   resolution: "is-ci@npm:2.0.0"
 "is-ci@npm:^2.0.0":
   version: 2.0.0
   resolution: "is-ci@npm:2.0.0"
@@ -10046,6 +11099,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-core-module@npm:^2.0.0, is-core-module@npm:^2.13.0, is-core-module@npm:^2.13.1":
+  version: 2.13.1
+  resolution: "is-core-module@npm:2.13.1"
+  dependencies:
+    hasown: ^2.0.0
+  checksum: 256559ee8a9488af90e4bad16f5583c6d59e92f0742e9e8bb4331e758521ee86b810b93bae44f390766ffbc518a0488b18d9dab7da9a5ff997d499efc9403f7c
+  languageName: node
+  linkType: hard
+
 "is-core-module@npm:^2.2.0":
   version: 2.4.0
   resolution: "is-core-module@npm:2.4.0"
 "is-core-module@npm:^2.2.0":
   version: 2.4.0
   resolution: "is-core-module@npm:2.4.0"
@@ -10082,6 +11144,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-data-view@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "is-data-view@npm:1.0.1"
+  dependencies:
+    is-typed-array: ^1.1.13
+  checksum: 4ba4562ac2b2ec005fefe48269d6bd0152785458cd253c746154ffb8a8ab506a29d0cfb3b74af87513843776a88e4981ae25c89457bf640a33748eab1a7216b5
+  languageName: node
+  linkType: hard
+
 "is-date-object@npm:^1.0.1":
   version: 1.0.4
   resolution: "is-date-object@npm:1.0.4"
 "is-date-object@npm:^1.0.1":
   version: 1.0.4
   resolution: "is-date-object@npm:1.0.4"
@@ -10089,6 +11160,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-date-object@npm:^1.0.5":
+  version: 1.0.5
+  resolution: "is-date-object@npm:1.0.5"
+  dependencies:
+    has-tostringtag: ^1.0.0
+  checksum: baa9077cdf15eb7b58c79398604ca57379b2fc4cf9aa7a9b9e295278648f628c9b201400c01c5e0f7afae56507d741185730307cbe7cad3b9f90a77e5ee342fc
+  languageName: node
+  linkType: hard
+
 "is-descriptor@npm:^0.1.0":
   version: 0.1.6
   resolution: "is-descriptor@npm:0.1.6"
 "is-descriptor@npm:^0.1.0":
   version: 0.1.6
   resolution: "is-descriptor@npm:0.1.6"
@@ -10150,19 +11230,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"is-finite@npm:^1.0.0":
-  version: 1.1.0
-  resolution: "is-finite@npm:1.1.0"
-  checksum: 532b97ed3d03e04c6bd203984d9e4ba3c0c390efee492bad5d1d1cd1802a68ab27adbd3ef6382f6312bed6c8bb1bd3e325ea79a8dc8fe080ed7a06f5f97b93e7
-  languageName: node
-  linkType: hard
-
-"is-fullwidth-code-point@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "is-fullwidth-code-point@npm:1.0.0"
+"is-finalizationregistry@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "is-finalizationregistry@npm:1.0.2"
   dependencies:
   dependencies:
-    number-is-nan: ^1.0.0
-  checksum: 4d46a7465a66a8aebcc5340d3b63a56602133874af576a9ca42c6f0f4bd787a743605771c5f246db77da96605fefeffb65fc1dbe862dcc7328f4b4d03edf5a57
+    call-bind: ^1.0.2
+  checksum: 4f243a8e06228cd45bdab8608d2cb7abfc20f6f0189c8ac21ea8d603f1f196eabd531ce0bb8e08cbab047e9845ef2c191a3761c9a17ad5cabf8b35499c4ad35d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -10187,6 +11260,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-generator-function@npm:^1.0.10":
+  version: 1.0.10
+  resolution: "is-generator-function@npm:1.0.10"
+  dependencies:
+    has-tostringtag: ^1.0.0
+  checksum: d54644e7dbaccef15ceb1e5d91d680eb5068c9ee9f9eb0a9e04173eb5542c9b51b5ab52c5537f5703e48d5fddfd376817c1ca07a84a407b7115b769d4bdde72b
+  languageName: node
+  linkType: hard
+
 "is-glob@npm:^3.1.0":
   version: 3.1.0
   resolution: "is-glob@npm:3.1.0"
 "is-glob@npm:^3.1.0":
   version: 3.1.0
   resolution: "is-glob@npm:3.1.0"
@@ -10205,6 +11287,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-glob@npm:^4.0.3":
+  version: 4.0.3
+  resolution: "is-glob@npm:4.0.3"
+  dependencies:
+    is-extglob: ^2.1.1
+  checksum: d381c1319fcb69d341cc6e6c7cd588e17cd94722d9a32dbd60660b993c4fb7d0f19438674e68dfec686d09b7c73139c9166b47597f846af387450224a8101ab4
+  languageName: node
+  linkType: hard
+
 "is-image@npm:*, is-image@npm:3.0.0":
   version: 3.0.0
   resolution: "is-image@npm:3.0.0"
 "is-image@npm:*, is-image@npm:3.0.0":
   version: 3.0.0
   resolution: "is-image@npm:3.0.0"
@@ -10238,6 +11329,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-map@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "is-map@npm:2.0.3"
+  checksum: e6ce5f6380f32b141b3153e6ba9074892bbbbd655e92e7ba5ff195239777e767a976dcd4e22f864accaf30e53ebf961ab1995424aef91af68788f0591b7396cc
+  languageName: node
+  linkType: hard
+
+"is-module@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "is-module@npm:1.0.0"
+  checksum: 8cd5390730c7976fb4e8546dd0b38865ee6f7bacfa08dfbb2cc07219606755f0b01709d9361e01f13009bbbd8099fa2927a8ed665118a6105d66e40f1b838c3f
+  languageName: node
+  linkType: hard
+
 "is-negative-zero@npm:^2.0.1":
   version: 2.0.1
   resolution: "is-negative-zero@npm:2.0.1"
 "is-negative-zero@npm:^2.0.1":
   version: 2.0.1
   resolution: "is-negative-zero@npm:2.0.1"
@@ -10245,6 +11350,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-negative-zero@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "is-negative-zero@npm:2.0.3"
+  checksum: c1e6b23d2070c0539d7b36022d5a94407132411d01aba39ec549af824231f3804b1aea90b5e4e58e807a65d23ceb538ed6e355ce76b267bdd86edb757ffcbdcd
+  languageName: node
+  linkType: hard
+
 "is-number-object@npm:^1.0.4":
   version: 1.0.5
   resolution: "is-number-object@npm:1.0.5"
 "is-number-object@npm:^1.0.4":
   version: 1.0.5
   resolution: "is-number-object@npm:1.0.5"
@@ -10321,7 +11433,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"is-plain-object@npm:^2.0.1, is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4":
+"is-plain-object@npm:^2.0.3, is-plain-object@npm:^2.0.4":
   version: 2.0.4
   resolution: "is-plain-object@npm:2.0.4"
   dependencies:
   version: 2.0.4
   resolution: "is-plain-object@npm:2.0.4"
   dependencies:
@@ -10330,6 +11442,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-potential-custom-element-name@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "is-potential-custom-element-name@npm:1.0.1"
+  checksum: ced7bbbb6433a5b684af581872afe0e1767e2d1146b2207ca0068a648fb5cab9d898495d1ac0583524faaf24ca98176a7d9876363097c2d14fee6dd324f3a1ab
+  languageName: node
+  linkType: hard
+
 "is-promise@npm:^2.1.0":
   version: 2.2.2
   resolution: "is-promise@npm:2.2.2"
 "is-promise@npm:^2.1.0":
   version: 2.2.2
   resolution: "is-promise@npm:2.2.2"
@@ -10347,6 +11466,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-regex@npm:^1.1.4":
+  version: 1.1.4
+  resolution: "is-regex@npm:1.1.4"
+  dependencies:
+    call-bind: ^1.0.2
+    has-tostringtag: ^1.0.0
+  checksum: 362399b33535bc8f386d96c45c9feb04cf7f8b41c182f54174c1a45c9abbbe5e31290bbad09a458583ff6bf3b2048672cdb1881b13289569a7c548370856a652
+  languageName: node
+  linkType: hard
+
 "is-regexp@npm:^1.0.0":
   version: 1.0.0
   resolution: "is-regexp@npm:1.0.0"
 "is-regexp@npm:^1.0.0":
   version: 1.0.0
   resolution: "is-regexp@npm:1.0.0"
@@ -10368,6 +11497,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-set@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "is-set@npm:2.0.3"
+  checksum: 36e3f8c44bdbe9496c9689762cc4110f6a6a12b767c5d74c0398176aa2678d4467e3bf07595556f2dba897751bde1422480212b97d973c7b08a343100b0c0dfe
+  languageName: node
+  linkType: hard
+
+"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "is-shared-array-buffer@npm:1.0.3"
+  dependencies:
+    call-bind: ^1.0.7
+  checksum: a4fff602c309e64ccaa83b859255a43bb011145a42d3f56f67d9268b55bc7e6d98a5981a1d834186ad3105d6739d21547083fe7259c76c0468483fc538e716d8
+  languageName: node
+  linkType: hard
+
 "is-stream@npm:^1.0.1, is-stream@npm:^1.1.0":
   version: 1.1.0
   resolution: "is-stream@npm:1.1.0"
 "is-stream@npm:^1.0.1, is-stream@npm:^1.1.0":
   version: 1.1.0
   resolution: "is-stream@npm:1.1.0"
@@ -10389,6 +11534,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"is-string@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "is-string@npm:1.0.7"
+  dependencies:
+    has-tostringtag: ^1.0.0
+  checksum: 323b3d04622f78d45077cf89aab783b2f49d24dc641aa89b5ad1a72114cfeff2585efc8c12ef42466dff32bde93d839ad321b26884cf75e5a7892a938b089989
+  languageName: node
+  linkType: hard
+
 "is-subset@npm:^0.1.1":
   version: 0.1.1
   resolution: "is-subset@npm:0.1.1"
 "is-subset@npm:^0.1.1":
   version: 0.1.1
   resolution: "is-subset@npm:0.1.1"
@@ -10405,7 +11559,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"is-typedarray@npm:~1.0.0":
+"is-typed-array@npm:^1.1.13":
+  version: 1.1.13
+  resolution: "is-typed-array@npm:1.1.13"
+  dependencies:
+    which-typed-array: ^1.1.14
+  checksum: 150f9ada183a61554c91e1c4290086d2c100b0dff45f60b028519be72a8db964da403c48760723bf5253979b8dffe7b544246e0e5351dcd05c5fdb1dcc1dc0f0
+  languageName: node
+  linkType: hard
+
+"is-typedarray@npm:^1.0.0, is-typedarray@npm:~1.0.0":
   version: 1.0.0
   resolution: "is-typedarray@npm:1.0.0"
   checksum: 3508c6cd0a9ee2e0df2fa2e9baabcdc89e911c7bd5cf64604586697212feec525aa21050e48affb5ffc3df20f0f5d2e2cf79b08caa64e1ccc9578e251763aef7
   version: 1.0.0
   resolution: "is-typedarray@npm:1.0.0"
   checksum: 3508c6cd0a9ee2e0df2fa2e9baabcdc89e911c7bd5cf64604586697212feec525aa21050e48affb5ffc3df20f0f5d2e2cf79b08caa64e1ccc9578e251763aef7
@@ -10419,10 +11582,29 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"is-utf8@npm:^0.2.0":
-  version: 0.2.1
-  resolution: "is-utf8@npm:0.2.1"
-  checksum: 167ccd2be869fc228cc62c1a28df4b78c6b5485d15a29027d3b5dceb09b383e86a3522008b56dcac14b592b22f0a224388718c2505027a994fd8471465de54b3
+"is-weakmap@npm:^2.0.2":
+  version: 2.0.2
+  resolution: "is-weakmap@npm:2.0.2"
+  checksum: f36aef758b46990e0d3c37269619c0a08c5b29428c0bb11ecba7f75203442d6c7801239c2f31314bc79199217ef08263787f3837d9e22610ad1da62970d6616d
+  languageName: node
+  linkType: hard
+
+"is-weakref@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "is-weakref@npm:1.0.2"
+  dependencies:
+    call-bind: ^1.0.2
+  checksum: 95bd9a57cdcb58c63b1c401c60a474b0f45b94719c30f548c891860f051bc2231575c290a6b420c6bc6e7ed99459d424c652bd5bf9a1d5259505dc35b4bf83de
+  languageName: node
+  linkType: hard
+
+"is-weakset@npm:^2.0.3":
+  version: 2.0.3
+  resolution: "is-weakset@npm:2.0.3"
+  dependencies:
+    call-bind: ^1.0.7
+    get-intrinsic: ^1.2.4
+  checksum: 8b6a20ee9f844613ff8f10962cfee49d981d584525f2357fee0a04dfbcde9fd607ed60cb6dab626dbcc470018ae6392e1ff74c0c1aced2d487271411ad9d85ae
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -10440,7 +11622,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"is-wsl@npm:^2.1.1":
+"is-wsl@npm:^2.1.1, is-wsl@npm:^2.2.0":
   version: 2.2.0
   resolution: "is-wsl@npm:2.2.0"
   dependencies:
   version: 2.2.0
   resolution: "is-wsl@npm:2.2.0"
   dependencies:
@@ -10463,6 +11645,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"isarray@npm:^2.0.5":
+  version: 2.0.5
+  resolution: "isarray@npm:2.0.5"
+  checksum: bd5bbe4104438c4196ba58a54650116007fa0262eccef13a4c55b2e09a5b36b59f1e75b9fcc49883dd9d4953892e6fc007eef9e9155648ceea036e184b0f930a
+  languageName: node
+  linkType: hard
+
 "isexe@npm:^2.0.0":
   version: 2.0.0
   resolution: "isexe@npm:2.0.0"
 "isexe@npm:^2.0.0":
   version: 2.0.0
   resolution: "isexe@npm:2.0.0"
@@ -10503,133 +11692,178 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"istanbul-lib-coverage@npm:^2.0.2, istanbul-lib-coverage@npm:^2.0.5":
-  version: 2.0.5
-  resolution: "istanbul-lib-coverage@npm:2.0.5"
-  checksum: c83bf39dc722d2a3e7c98b16643f2fef719fd59adf23441ad8a1e6422bb1f3367ac7d4c42ac45d0d87413476891947b6ffbdecf2184047436336aa0c28bbfc15
+"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0":
+  version: 3.2.2
+  resolution: "istanbul-lib-coverage@npm:3.2.2"
+  checksum: 2367407a8d13982d8f7a859a35e7f8dd5d8f75aae4bb5484ede3a9ea1b426dc245aff28b976a2af48ee759fdd9be374ce2bd2669b644f31e76c5f46a2e29a831
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"istanbul-lib-instrument@npm:^3.0.1, istanbul-lib-instrument@npm:^3.3.0":
-  version: 3.3.0
-  resolution: "istanbul-lib-instrument@npm:3.3.0"
-  dependencies:
-    "@babel/generator": ^7.4.0
-    "@babel/parser": ^7.4.3
-    "@babel/template": ^7.4.0
-    "@babel/traverse": ^7.4.3
-    "@babel/types": ^7.4.0
-    istanbul-lib-coverage: ^2.0.5
-    semver: ^6.0.0
-  checksum: 5ff86440c2f4afe83603f899721e43f9bbc0049ebf4e7fd696ea361d0c9ae5c831c656eec07c13f42ba934fc808c78f42a7884f1a08349802bc9bfa5af760571
+"istanbul-lib-instrument@npm:^4.0.3":
+  version: 4.0.3
+  resolution: "istanbul-lib-instrument@npm:4.0.3"
+  dependencies:
+    "@babel/core": ^7.7.5
+    "@istanbuljs/schema": ^0.1.2
+    istanbul-lib-coverage: ^3.0.0
+    semver: ^6.3.0
+  checksum: fa1171d3022b1bb8f6a734042620ac5d9ee7dc80f3065a0bb12863e9f0494d0eefa3d86608fcc0254ab2765d29d7dad8bdc42e5f8df2f9a1fbe85ccc59d76cb9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"istanbul-lib-report@npm:^2.0.4":
-  version: 2.0.8
-  resolution: "istanbul-lib-report@npm:2.0.8"
+"istanbul-lib-instrument@npm:^5.0.4":
+  version: 5.2.1
+  resolution: "istanbul-lib-instrument@npm:5.2.1"
   dependencies:
   dependencies:
-    istanbul-lib-coverage: ^2.0.5
-    make-dir: ^2.1.0
-    supports-color: ^6.1.0
-  checksum: eef53d35ea750fd971bc7abf2cf1350615804e4dee5a7ee6e13cff45ff36b518970baaeef4bf019d46149581f9d10c3f3675083cf6625da6cc3d4d4b4c670374
+    "@babel/core": ^7.12.3
+    "@babel/parser": ^7.14.7
+    "@istanbuljs/schema": ^0.1.2
+    istanbul-lib-coverage: ^3.2.0
+    semver: ^6.3.0
+  checksum: bf16f1803ba5e51b28bbd49ed955a736488381e09375d830e42ddeb403855b2006f850711d95ad726f2ba3f1ae8e7366de7e51d2b9ac67dc4d80191ef7ddf272
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"istanbul-lib-source-maps@npm:^3.0.1":
-  version: 3.0.6
-  resolution: "istanbul-lib-source-maps@npm:3.0.6"
+"istanbul-lib-report@npm:^3.0.0":
+  version: 3.0.1
+  resolution: "istanbul-lib-report@npm:3.0.1"
+  dependencies:
+    istanbul-lib-coverage: ^3.0.0
+    make-dir: ^4.0.0
+    supports-color: ^7.1.0
+  checksum: fd17a1b879e7faf9bb1dc8f80b2a16e9f5b7b8498fe6ed580a618c34df0bfe53d2abd35bf8a0a00e628fb7405462576427c7df20bbe4148d19c14b431c974b21
+  languageName: node
+  linkType: hard
+
+"istanbul-lib-source-maps@npm:^4.0.0":
+  version: 4.0.1
+  resolution: "istanbul-lib-source-maps@npm:4.0.1"
   dependencies:
     debug: ^4.1.1
   dependencies:
     debug: ^4.1.1
-    istanbul-lib-coverage: ^2.0.5
-    make-dir: ^2.1.0
-    rimraf: ^2.6.3
+    istanbul-lib-coverage: ^3.0.0
     source-map: ^0.6.1
     source-map: ^0.6.1
-  checksum: 1c6ebc81331ab4d831910db3e98da1ee4e3e96f64c2fb533e1b73516305f020b44765fa2937f24eee4adb11be22a1fa42c04786e0d697d4893987a1a5180a541
+  checksum: 21ad3df45db4b81852b662b8d4161f6446cd250c1ddc70ef96a585e2e85c26ed7cd9c2a396a71533cfb981d1a645508bc9618cae431e55d01a0628e7dec62ef2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"istanbul-reports@npm:^2.2.6":
-  version: 2.2.7
-  resolution: "istanbul-reports@npm:2.2.7"
+"istanbul-reports@npm:^3.0.2":
+  version: 3.1.7
+  resolution: "istanbul-reports@npm:3.1.7"
   dependencies:
     html-escaper: ^2.0.0
   dependencies:
     html-escaper: ^2.0.0
-  checksum: 138604c86fe4a386c4ba23c103aa64f3d867548cb1ac9961cafe912004bde601180d7ece918a76e2e0078b94e503b77aa696d6e6f68a0d8698abbf0923d78285
+    istanbul-lib-report: ^3.0.0
+  checksum: 2072db6e07bfbb4d0eb30e2700250636182398c1af811aea5032acb219d2080f7586923c09fa194029efd6b92361afb3dcbe1ebcc3ee6651d13340f7c6c4ed95
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-changed-files@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-changed-files@npm:24.9.0"
+"iterator.prototype@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "iterator.prototype@npm:1.1.2"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    execa: ^1.0.0
-    throat: ^4.0.0
-  checksum: f40e901e6ac2e6f47730b610c3dbef44a9235d556ba53b23926d45e6334c1c5989fd255140753d3270d5e63371ae69084e0867c11b8322030edab51e1ff1b8b7
+    define-properties: ^1.2.1
+    get-intrinsic: ^1.2.1
+    has-symbols: ^1.0.3
+    reflect.getprototypeof: ^1.0.4
+    set-function-name: ^2.0.1
+  checksum: d8a507e2ccdc2ce762e8a1d3f4438c5669160ac72b88b648e59a688eec6bc4e64b22338e74000518418d9e693faf2a092d2af21b9ec7dbf7763b037a54701168
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-cli@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-cli@npm:24.9.0"
+"jest-changed-files@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-changed-files@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/core": ^24.9.0
-    "@jest/test-result": ^24.9.0
-    "@jest/types": ^24.9.0
-    chalk: ^2.0.1
+    "@jest/types": ^26.6.2
+    execa: ^4.0.0
+    throat: ^5.0.0
+  checksum: 8c405f5ff905ee69ace9fd39355233206e3e233badf6a3f3b27e45bbf0a46d86943430be2e080d25b1e085f4231b9b3b27c94317aa04116efb40b592184066f4
+  languageName: node
+  linkType: hard
+
+"jest-circus@npm:26.6.0":
+  version: 26.6.0
+  resolution: "jest-circus@npm:26.6.0"
+  dependencies:
+    "@babel/traverse": ^7.1.0
+    "@jest/environment": ^26.6.0
+    "@jest/test-result": ^26.6.0
+    "@jest/types": ^26.6.0
+    "@types/babel__traverse": ^7.0.4
+    "@types/node": "*"
+    chalk: ^4.0.0
+    co: ^4.6.0
+    dedent: ^0.7.0
+    expect: ^26.6.0
+    is-generator-fn: ^2.0.0
+    jest-each: ^26.6.0
+    jest-matcher-utils: ^26.6.0
+    jest-message-util: ^26.6.0
+    jest-runner: ^26.6.0
+    jest-runtime: ^26.6.0
+    jest-snapshot: ^26.6.0
+    jest-util: ^26.6.0
+    pretty-format: ^26.6.0
+    stack-utils: ^2.0.2
+    throat: ^5.0.0
+  checksum: acc354223964bafd40fd1caae4099b58ccb1551bc93a394398b441274c225552f1941ce9903d126fb0adc3952a108e2994270c6a50a3e7e5af931b65b8c170f0
+  languageName: node
+  linkType: hard
+
+"jest-cli@npm:^26.6.0":
+  version: 26.6.3
+  resolution: "jest-cli@npm:26.6.3"
+  dependencies:
+    "@jest/core": ^26.6.3
+    "@jest/test-result": ^26.6.2
+    "@jest/types": ^26.6.2
+    chalk: ^4.0.0
     exit: ^0.1.2
     exit: ^0.1.2
-    import-local: ^2.0.0
+    graceful-fs: ^4.2.4
+    import-local: ^3.0.2
     is-ci: ^2.0.0
     is-ci: ^2.0.0
-    jest-config: ^24.9.0
-    jest-util: ^24.9.0
-    jest-validate: ^24.9.0
+    jest-config: ^26.6.3
+    jest-util: ^26.6.2
+    jest-validate: ^26.6.2
     prompts: ^2.0.1
     prompts: ^2.0.1
-    realpath-native: ^1.1.0
-    yargs: ^13.3.0
+    yargs: ^15.4.1
   bin:
   bin:
-    jest: ./bin/jest.js
-  checksum: 8fc975da02e6793352a3508fae1523c094ed44633dc5e651aa1f01e49b9d4be8353422fd5dc7f01e464f6aafee13b3210daf3d11ce466c8959071251bdb0dc09
+    jest: bin/jest.js
+  checksum: c8554147be756f09f5566974f0026485f78742e8642d2723f8fbee5746f50f44fb72b17aad181226655a8446d3ecc8ad8ed0a11a8a55686fa2b9c10d85700121
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-config@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-config@npm:24.9.0"
+"jest-config@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "jest-config@npm:26.6.3"
   dependencies:
     "@babel/core": ^7.1.0
   dependencies:
     "@babel/core": ^7.1.0
-    "@jest/test-sequencer": ^24.9.0
-    "@jest/types": ^24.9.0
-    babel-jest: ^24.9.0
-    chalk: ^2.0.1
+    "@jest/test-sequencer": ^26.6.3
+    "@jest/types": ^26.6.2
+    babel-jest: ^26.6.3
+    chalk: ^4.0.0
+    deepmerge: ^4.2.2
     glob: ^7.1.1
     glob: ^7.1.1
-    jest-environment-jsdom: ^24.9.0
-    jest-environment-node: ^24.9.0
-    jest-get-type: ^24.9.0
-    jest-jasmine2: ^24.9.0
-    jest-regex-util: ^24.3.0
-    jest-resolve: ^24.9.0
-    jest-util: ^24.9.0
-    jest-validate: ^24.9.0
-    micromatch: ^3.1.10
-    pretty-format: ^24.9.0
-    realpath-native: ^1.1.0
-  checksum: 87268fcab5322775601181f4ee17d51102ba153b1e0dc68a55075e44109b372f4925fe9c361cca6a72d5934f806b16f8331f0efad8b6b296a6f7bffcb7a34cb9
-  languageName: node
-  linkType: hard
-
-"jest-diff@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-diff@npm:24.9.0"
-  dependencies:
-    chalk: ^2.0.1
-    diff-sequences: ^24.9.0
-    jest-get-type: ^24.9.0
-    pretty-format: ^24.9.0
-  checksum: 462ccb128cb1b64eb285d28245d0c5bfc230cb063624bd117550d6dbc94332f606828a5de86938611d1e6a78489e576c496737ae139084f6049a56b768ad6402
+    graceful-fs: ^4.2.4
+    jest-environment-jsdom: ^26.6.2
+    jest-environment-node: ^26.6.2
+    jest-get-type: ^26.3.0
+    jest-jasmine2: ^26.6.3
+    jest-regex-util: ^26.0.0
+    jest-resolve: ^26.6.2
+    jest-util: ^26.6.2
+    jest-validate: ^26.6.2
+    micromatch: ^4.0.2
+    pretty-format: ^26.6.2
+  peerDependencies:
+    ts-node: ">=9.0.0"
+  peerDependenciesMeta:
+    ts-node:
+      optional: true
+  checksum: 303c798582d3c5d4b4e6ab8a4d91a83ded28e4ebbc0bcfc1ad271f9864437ef5409b7c7773010143811bc8176b0695c096717b91419c6484b56dcc032560a74b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-diff@npm:^26.0.0":
+"jest-diff@npm:^26.0.0, jest-diff@npm:^26.6.2":
   version: 26.6.2
   resolution: "jest-diff@npm:26.6.2"
   dependencies:
   version: 26.6.2
   resolution: "jest-diff@npm:26.6.2"
   dependencies:
@@ -10641,73 +11875,54 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-docblock@npm:^24.3.0":
-  version: 24.9.0
-  resolution: "jest-docblock@npm:24.9.0"
-  dependencies:
-    detect-newline: ^2.1.0
-  checksum: 0b2321a4ac5b2b59f9183f805d4c50223635e53ce76080c406da3d499916972b70ce8809fda6d0616b2ce606dd201be36be6b4c8c62ae2c0e62f14cfa3bfcbdb
-  languageName: node
-  linkType: hard
-
-"jest-each@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-each@npm:24.9.0"
+"jest-docblock@npm:^26.0.0":
+  version: 26.0.0
+  resolution: "jest-docblock@npm:26.0.0"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    chalk: ^2.0.1
-    jest-get-type: ^24.9.0
-    jest-util: ^24.9.0
-    pretty-format: ^24.9.0
-  checksum: 93dc198e1dbea985816e3739b8a6e8622f1ee7b3f8b97d074aa8d512b4f81b8b70b30dcdcb5f735b3407bbd0fe5a9ac06e38cbf6499f7ab302daff2832c49683
+    detect-newline: ^3.0.0
+  checksum: e03ef104ee8c571335e6fa394b8fc8d2bd87eec9fe8b3d7d9aac056ada7de288f37ee8ac4922bb3a4222ac304db975d8832d5abc85486092866c534a16847cd5
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-environment-jsdom-fourteen@npm:1.0.1":
-  version: 1.0.1
-  resolution: "jest-environment-jsdom-fourteen@npm:1.0.1"
+"jest-each@npm:^26.6.0, jest-each@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-each@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/environment": ^24.3.0
-    "@jest/fake-timers": ^24.3.0
-    "@jest/types": ^24.3.0
-    jest-mock: ^24.0.0
-    jest-util: ^24.0.0
-    jsdom: ^14.1.0
-  checksum: 39b34962c44260b69a58bab74ba36c6746db70947e6a44695ea26776bda2a9d9fd66edd1f6c36e9f456e5e0993633339f0db86fc452e0f1dfcaa9336a0656a35
+    "@jest/types": ^26.6.2
+    chalk: ^4.0.0
+    jest-get-type: ^26.3.0
+    jest-util: ^26.6.2
+    pretty-format: ^26.6.2
+  checksum: 4e00ea4667e4fe015b894dc698cce0ae695cf458e021e5da62d4a5b052cd2c0a878da93f8c97cbdde60bcecf70982e8d3a7a5d63e1588f59531cc797a18c39ef
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-environment-jsdom@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-environment-jsdom@npm:24.9.0"
+"jest-environment-jsdom@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-environment-jsdom@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/environment": ^24.9.0
-    "@jest/fake-timers": ^24.9.0
-    "@jest/types": ^24.9.0
-    jest-mock: ^24.9.0
-    jest-util: ^24.9.0
-    jsdom: ^11.5.1
-  checksum: 093e7f25735e52a1ff01673f0e3921e3e8228d2e902762bf102f1c34cd206e9b73aa83dcd0598e101c6cf4c23e99e5c84df84084258268a696c3007d6990f701
+    "@jest/environment": ^26.6.2
+    "@jest/fake-timers": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    jest-mock: ^26.6.2
+    jest-util: ^26.6.2
+    jsdom: ^16.4.0
+  checksum: 8af9ffdf1b147362a19032bfe9ed51b709d43c74dc4b1c45e56d721808bf6cabdca8c226855b55a985ea196ce51cdb171bfe420ceec3daa2d13818d5c1915890
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-environment-node@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-environment-node@npm:24.9.0"
+"jest-environment-node@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-environment-node@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/environment": ^24.9.0
-    "@jest/fake-timers": ^24.9.0
-    "@jest/types": ^24.9.0
-    jest-mock: ^24.9.0
-    jest-util: ^24.9.0
-  checksum: 61a446f7cbab96b1777f53bcbb45ecda139a2473c7a093a9420f0018824ec307b93f920f9e188b5f11b605d0ed14798396c97113aedb66c2801b29367a5dc8d2
-  languageName: node
-  linkType: hard
-
-"jest-get-type@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-get-type@npm:24.9.0"
-  checksum: 821e6cd46434c917370cd362fbc4ce564c6e22780351f3ca468b230fbbc657ae19905ed5cdcc5e112d81a2c79cbd3fbcbe0dd44dc62860414b60ea223009958c
+    "@jest/environment": ^26.6.2
+    "@jest/fake-timers": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    jest-mock: ^26.6.2
+    jest-util: ^26.6.2
+  checksum: 0b69b481e6d6f2350ed241c2dabc70b0b1f3a00f9a410b7dad97c8ab38e88026acf7445ca663eb314f46ff50acee0133100b1006bf4ebda5298ffb02763a6861
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -10718,60 +11933,64 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-haste-map@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-haste-map@npm:24.9.0"
+"jest-haste-map@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-haste-map@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    anymatch: ^2.0.0
+    "@jest/types": ^26.6.2
+    "@types/graceful-fs": ^4.1.2
+    "@types/node": "*"
+    anymatch: ^3.0.3
     fb-watchman: ^2.0.0
     fb-watchman: ^2.0.0
-    fsevents: ^1.2.7
-    graceful-fs: ^4.1.15
-    invariant: ^2.2.4
-    jest-serializer: ^24.9.0
-    jest-util: ^24.9.0
-    jest-worker: ^24.9.0
-    micromatch: ^3.1.10
+    fsevents: ^2.1.2
+    graceful-fs: ^4.2.4
+    jest-regex-util: ^26.0.0
+    jest-serializer: ^26.6.2
+    jest-util: ^26.6.2
+    jest-worker: ^26.6.2
+    micromatch: ^4.0.2
     sane: ^4.0.3
     walker: ^1.0.7
   dependenciesMeta:
     fsevents:
       optional: true
     sane: ^4.0.3
     walker: ^1.0.7
   dependenciesMeta:
     fsevents:
       optional: true
-  checksum: 3ec2d60863c315d52a32b2d1df3cc8bb5403f7d8bf159e556c878db09dedc4d1fb4e4d5f56cb67c92663b334d49ef8b768375b0d153adebf4d48a7b6959e71b3
+  checksum: 8ad5236d5646d2388d2bd58a57ea53698923434f43d59ea9ebdc58bce4d0b8544c8de2f7acaa9a6d73171f04460388b2b6d7d6b6c256aea4ebb8780140781596
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-jasmine2@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-jasmine2@npm:24.9.0"
+"jest-jasmine2@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "jest-jasmine2@npm:26.6.3"
   dependencies:
     "@babel/traverse": ^7.1.0
   dependencies:
     "@babel/traverse": ^7.1.0
-    "@jest/environment": ^24.9.0
-    "@jest/test-result": ^24.9.0
-    "@jest/types": ^24.9.0
-    chalk: ^2.0.1
+    "@jest/environment": ^26.6.2
+    "@jest/source-map": ^26.6.2
+    "@jest/test-result": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    chalk: ^4.0.0
     co: ^4.6.0
     co: ^4.6.0
-    expect: ^24.9.0
+    expect: ^26.6.2
     is-generator-fn: ^2.0.0
     is-generator-fn: ^2.0.0
-    jest-each: ^24.9.0
-    jest-matcher-utils: ^24.9.0
-    jest-message-util: ^24.9.0
-    jest-runtime: ^24.9.0
-    jest-snapshot: ^24.9.0
-    jest-util: ^24.9.0
-    pretty-format: ^24.9.0
-    throat: ^4.0.0
-  checksum: 0ce903a12f5c237565e033d6e97bbb22d3131f918d4f715f6908950d820424c780b2f7020b9771001cede4e0a76bd06592fff99924b84cafbc8353feb38667aa
+    jest-each: ^26.6.2
+    jest-matcher-utils: ^26.6.2
+    jest-message-util: ^26.6.2
+    jest-runtime: ^26.6.3
+    jest-snapshot: ^26.6.2
+    jest-util: ^26.6.2
+    pretty-format: ^26.6.2
+    throat: ^5.0.0
+  checksum: 41df0b993ae0cdeb2660fb3d8e88e2dcc83aec6b5c27d85eb233c2d507b546f8dce45fc54898ffbefa48ccc4633f225d0e023fd0979b8f7f2f1626074a69a9a3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-leak-detector@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-leak-detector@npm:24.9.0"
+"jest-leak-detector@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-leak-detector@npm:26.6.2"
   dependencies:
   dependencies:
-    jest-get-type: ^24.9.0
-    pretty-format: ^24.9.0
-  checksum: ab54f8ca8f9abf76db9f681b8add50a17767e7b15459710ece030bd034e1fad47c67da73562408779839138dc7423a08f387f5930efdd800eac67d5653badce8
+    jest-get-type: ^26.3.0
+    pretty-format: ^26.6.2
+  checksum: 364dd4d021347e26c66ba9c09da8a30477f14a3a8a208d2d7d64e4c396db81b85d8cb6b6834bcfc47a61b5938e274553957d11a7de2255f058c9d55d7f8fdfe7
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -10782,239 +12001,267 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-matcher-utils@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-matcher-utils@npm:24.9.0"
+"jest-matcher-utils@npm:^26.6.0, jest-matcher-utils@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-matcher-utils@npm:26.6.2"
   dependencies:
   dependencies:
-    chalk: ^2.0.1
-    jest-diff: ^24.9.0
-    jest-get-type: ^24.9.0
-    pretty-format: ^24.9.0
-  checksum: e9dcd4c7a0bf52dccb4890de7ac2da3e857af067e71633b730fdc865dd271b8a2c3d68a2761d5ca6060ea4a455be42176f58462006468b8eb7c216921251e2ee
+    chalk: ^4.0.0
+    jest-diff: ^26.6.2
+    jest-get-type: ^26.3.0
+    pretty-format: ^26.6.2
+  checksum: 74d2165c1ac7fe98fe27cd2b5407499478e6b2fe99dd54e26d8ee5c9f5f913bdd7bdc07c7221b9b04df0c15e9be0e866ff3455b03e38cc66c480d9996d6d5405
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-message-util@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-message-util@npm:24.9.0"
+"jest-message-util@npm:^26.6.0, jest-message-util@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-message-util@npm:26.6.2"
   dependencies:
     "@babel/code-frame": ^7.0.0
   dependencies:
     "@babel/code-frame": ^7.0.0
-    "@jest/test-result": ^24.9.0
-    "@jest/types": ^24.9.0
-    "@types/stack-utils": ^1.0.1
-    chalk: ^2.0.1
-    micromatch: ^3.1.10
-    slash: ^2.0.0
-    stack-utils: ^1.0.1
-  checksum: c173117b245090967db4853c28c3452ad2987a10caf28161abbfeb8d96be13f0d9e25422df10162bcc5e46860887e35ec4b4963f85392c4a625e4c37ad242f0b
+    "@jest/types": ^26.6.2
+    "@types/stack-utils": ^2.0.0
+    chalk: ^4.0.0
+    graceful-fs: ^4.2.4
+    micromatch: ^4.0.2
+    pretty-format: ^26.6.2
+    slash: ^3.0.0
+    stack-utils: ^2.0.2
+  checksum: ffe5a715591c41240b9ed4092faf10f3eaf9ddfdf25d257a0c9f903aaa8d9eed5baa7e38016d2ec4f610fd29225e0f5231a91153e087a043e62824972c83d015
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-mock@npm:^24.0.0, jest-mock@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-mock@npm:24.9.0"
+"jest-mock@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-mock@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-  checksum: 823feac37b003543fe81e05d5d8a1ec69cdf9ae5b797582a3e90424ec476120ce42a11e6b1d8231958e01232d4e40e57207cf2c56197d63d309bdeaf63fcf804
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+  checksum: 6c0fe028ff0cdc87b5d63b9ca749af04cae6c5577aaab234f602e546cae3f4b932adac9d77e6de2abb24955ee00978e1e5d5a861725654e2f9a42317d91fbc1f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-pnp-resolver@npm:^1.2.1":
-  version: 1.2.2
-  resolution: "jest-pnp-resolver@npm:1.2.2"
+"jest-pnp-resolver@npm:^1.2.2":
+  version: 1.2.3
+  resolution: "jest-pnp-resolver@npm:1.2.3"
   peerDependencies:
     jest-resolve: "*"
   peerDependenciesMeta:
     jest-resolve:
       optional: true
   peerDependencies:
     jest-resolve: "*"
   peerDependenciesMeta:
     jest-resolve:
       optional: true
-  checksum: bd85dcc0e76e0eb0c3d56382ec140f08d25ff4068cda9d0e360bb78fb176cb726d0beab82dc0e8694cafd09f55fee7622b8bcb240afa5fad301f4ed3eebb4f47
+  checksum: db1a8ab2cb97ca19c01b1cfa9a9c8c69a143fde833c14df1fab0766f411b1148ff0df878adea09007ac6a2085ec116ba9a996a6ad104b1e58c20adbf88eed9b2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-regex-util@npm:^24.3.0, jest-regex-util@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-regex-util@npm:24.9.0"
-  checksum: 94299972501ae5dfc3932673b263fd15dba5e28698571687a28cc59b5a173edcbf52b992f4d5a6eded9da5b7e1468d263ef96a1564267832799b41c2986fc423
+"jest-regex-util@npm:^26.0.0":
+  version: 26.0.0
+  resolution: "jest-regex-util@npm:26.0.0"
+  checksum: 930a00665e8dfbedc29140678b4a54f021b41b895cf35050f76f557c1da3ac48ff42dd7b18ba2ccba6f4e518c6445d6753730d03ec7049901b93992db1ef0483
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-resolve-dependencies@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-resolve-dependencies@npm:24.9.0"
+"jest-resolve-dependencies@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "jest-resolve-dependencies@npm:26.6.3"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    jest-regex-util: ^24.3.0
-    jest-snapshot: ^24.9.0
-  checksum: 126627777e7382b7ecc5b342f5f7b0e247a99e35895ee59282e7066c611d58ff2bd6a7332628e44e221a52361b8ecd1d9de41ba20d240f9b621ee80b6aebf820
+    "@jest/types": ^26.6.2
+    jest-regex-util: ^26.0.0
+    jest-snapshot: ^26.6.2
+  checksum: 533ea1e271426006ff02c03c9802b108fcd68f2144615b6110ae59f3a0a2cc4a7abb3f44c3c65299c76b3a725d5d8220aaed9c58b79c8c8c508c18699a96e3f7
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-resolve@npm:24.9.0, jest-resolve@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-resolve@npm:24.9.0"
+"jest-resolve@npm:26.6.0":
+  version: 26.6.0
+  resolution: "jest-resolve@npm:26.6.0"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    browser-resolve: ^1.11.3
-    chalk: ^2.0.1
-    jest-pnp-resolver: ^1.2.1
-    realpath-native: ^1.1.0
-  checksum: 60a84cbd75d5cdab1ad29c8ed62e43fbc374c906e5a0f166fae5170f91c863ee9372aaab7dbdb3a06a38b0362131fa7c907c114be76a8bc1aeac47013ec308e4
+    "@jest/types": ^26.6.0
+    chalk: ^4.0.0
+    graceful-fs: ^4.2.4
+    jest-pnp-resolver: ^1.2.2
+    jest-util: ^26.6.0
+    read-pkg-up: ^7.0.1
+    resolve: ^1.17.0
+    slash: ^3.0.0
+  checksum: c5d0277d4aa22f9f38693ba3e5d6176edf2e367af2f0c38e16c88e9b80b2292ee4d9df9b3675607f5d0c0b2652b4e3f69d8155f9fedd83ddd0ef937cfb6230c0
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-runner@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-runner@npm:24.9.0"
+"jest-resolve@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-resolve@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/console": ^24.7.1
-    "@jest/environment": ^24.9.0
-    "@jest/test-result": ^24.9.0
-    "@jest/types": ^24.9.0
-    chalk: ^2.4.2
+    "@jest/types": ^26.6.2
+    chalk: ^4.0.0
+    graceful-fs: ^4.2.4
+    jest-pnp-resolver: ^1.2.2
+    jest-util: ^26.6.2
+    read-pkg-up: ^7.0.1
+    resolve: ^1.18.1
+    slash: ^3.0.0
+  checksum: d6264d3f39b098753802a237c8c54f3109f5f3b3b7fa6f8d7aec7dca01b357ddf518ce1c33a68454357c15f48fb3c6026a92b9c4f5d72f07e24e80f04bcc8d58
+  languageName: node
+  linkType: hard
+
+"jest-runner@npm:^26.6.0, jest-runner@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "jest-runner@npm:26.6.3"
+  dependencies:
+    "@jest/console": ^26.6.2
+    "@jest/environment": ^26.6.2
+    "@jest/test-result": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    chalk: ^4.0.0
+    emittery: ^0.7.1
     exit: ^0.1.2
     exit: ^0.1.2
-    graceful-fs: ^4.1.15
-    jest-config: ^24.9.0
-    jest-docblock: ^24.3.0
-    jest-haste-map: ^24.9.0
-    jest-jasmine2: ^24.9.0
-    jest-leak-detector: ^24.9.0
-    jest-message-util: ^24.9.0
-    jest-resolve: ^24.9.0
-    jest-runtime: ^24.9.0
-    jest-util: ^24.9.0
-    jest-worker: ^24.6.0
+    graceful-fs: ^4.2.4
+    jest-config: ^26.6.3
+    jest-docblock: ^26.0.0
+    jest-haste-map: ^26.6.2
+    jest-leak-detector: ^26.6.2
+    jest-message-util: ^26.6.2
+    jest-resolve: ^26.6.2
+    jest-runtime: ^26.6.3
+    jest-util: ^26.6.2
+    jest-worker: ^26.6.2
     source-map-support: ^0.5.6
     source-map-support: ^0.5.6
-    throat: ^4.0.0
-  checksum: cb5c9fe598ca4ce8d13c2cf8b1649573e1bc73a50eb9438719b33970fed35ee75f731d64090d3392990f077ac1974119d094e311f503884eab42fa10081bd8a3
+    throat: ^5.0.0
+  checksum: ccd69918baa49a5efa45985cf60cfa1fbb1686b32d7a86296b7b55f89684e36d1f08e62598c4b7be7e81f2cf2e245d1a65146ea7bdcaedfa6ed176d3e645d7e2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-runtime@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-runtime@npm:24.9.0"
-  dependencies:
-    "@jest/console": ^24.7.1
-    "@jest/environment": ^24.9.0
-    "@jest/source-map": ^24.3.0
-    "@jest/transform": ^24.9.0
-    "@jest/types": ^24.9.0
-    "@types/yargs": ^13.0.0
-    chalk: ^2.0.1
+"jest-runtime@npm:^26.6.0, jest-runtime@npm:^26.6.3":
+  version: 26.6.3
+  resolution: "jest-runtime@npm:26.6.3"
+  dependencies:
+    "@jest/console": ^26.6.2
+    "@jest/environment": ^26.6.2
+    "@jest/fake-timers": ^26.6.2
+    "@jest/globals": ^26.6.2
+    "@jest/source-map": ^26.6.2
+    "@jest/test-result": ^26.6.2
+    "@jest/transform": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/yargs": ^15.0.0
+    chalk: ^4.0.0
+    cjs-module-lexer: ^0.6.0
+    collect-v8-coverage: ^1.0.0
     exit: ^0.1.2
     glob: ^7.1.3
     exit: ^0.1.2
     glob: ^7.1.3
-    graceful-fs: ^4.1.15
-    jest-config: ^24.9.0
-    jest-haste-map: ^24.9.0
-    jest-message-util: ^24.9.0
-    jest-mock: ^24.9.0
-    jest-regex-util: ^24.3.0
-    jest-resolve: ^24.9.0
-    jest-snapshot: ^24.9.0
-    jest-util: ^24.9.0
-    jest-validate: ^24.9.0
-    realpath-native: ^1.1.0
-    slash: ^2.0.0
-    strip-bom: ^3.0.0
-    yargs: ^13.3.0
+    graceful-fs: ^4.2.4
+    jest-config: ^26.6.3
+    jest-haste-map: ^26.6.2
+    jest-message-util: ^26.6.2
+    jest-mock: ^26.6.2
+    jest-regex-util: ^26.0.0
+    jest-resolve: ^26.6.2
+    jest-snapshot: ^26.6.2
+    jest-util: ^26.6.2
+    jest-validate: ^26.6.2
+    slash: ^3.0.0
+    strip-bom: ^4.0.0
+    yargs: ^15.4.1
   bin:
   bin:
-    jest-runtime: ./bin/jest-runtime.js
-  checksum: 924afebac3f1aaf8d9d6dec1b949d1c082b59a26c1b8917a7c47bf9bd27ad05544d534748119616b7f4e99ff50f546f25ca8b3f9bf32a34504355b8059bd0d45
+    jest-runtime: bin/jest-runtime.js
+  checksum: 867922b49f9ab4cf2f5f1356ac3d9962c4477c7a2ff696cc841ea4c600ea389e7d6dfcbf945fec6849e606f81980addf31e4f34d63eaa3d3415f4901de2f605a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-serializer@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-serializer@npm:24.9.0"
-  checksum: 56d70bd50ebd71de7a38e1f94ef2fdf1293c3810ef6d372b69238263625d3df1e6749417872bc6be0515e39832f4c40df03c74d20d8f0f43efd14ea21e22178d
+"jest-serializer@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-serializer@npm:26.6.2"
+  dependencies:
+    "@types/node": "*"
+    graceful-fs: ^4.2.4
+  checksum: dbecfb0d01462fe486a0932cf1680cf6abb204c059db2a8f72c6c2a7c9842a82f6d256874112774cea700764ed8f38fc9e3db982456c138d87353e3390e746fe
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-snapshot@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-snapshot@npm:24.9.0"
+"jest-snapshot@npm:^26.6.0, jest-snapshot@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-snapshot@npm:26.6.2"
   dependencies:
     "@babel/types": ^7.0.0
   dependencies:
     "@babel/types": ^7.0.0
-    "@jest/types": ^24.9.0
-    chalk: ^2.0.1
-    expect: ^24.9.0
-    jest-diff: ^24.9.0
-    jest-get-type: ^24.9.0
-    jest-matcher-utils: ^24.9.0
-    jest-message-util: ^24.9.0
-    jest-resolve: ^24.9.0
-    mkdirp: ^0.5.1
+    "@jest/types": ^26.6.2
+    "@types/babel__traverse": ^7.0.4
+    "@types/prettier": ^2.0.0
+    chalk: ^4.0.0
+    expect: ^26.6.2
+    graceful-fs: ^4.2.4
+    jest-diff: ^26.6.2
+    jest-get-type: ^26.3.0
+    jest-haste-map: ^26.6.2
+    jest-matcher-utils: ^26.6.2
+    jest-message-util: ^26.6.2
+    jest-resolve: ^26.6.2
     natural-compare: ^1.4.0
     natural-compare: ^1.4.0
-    pretty-format: ^24.9.0
-    semver: ^6.2.0
-  checksum: 474dc05ededdb8b39fb79801498fcd16c1a13a01b4701a27172be0ee3ebc5640e2bfb2780a9afa49bd825b19fc2be1e2ec5fc3d501afa76a5f7bc40f0120aaf3
+    pretty-format: ^26.6.2
+    semver: ^7.3.2
+  checksum: 53f1de055b1d3840bc6e851fd674d5991b844d4695dadbd07354c93bf191048d8767b8606999847e97c4214a485b9afb45c1d2411772befa1870414ac973b3e2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-util@npm:^24.0.0, jest-util@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-util@npm:24.9.0"
+"jest-util@npm:^26.6.0, jest-util@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-util@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/console": ^24.9.0
-    "@jest/fake-timers": ^24.9.0
-    "@jest/source-map": ^24.9.0
-    "@jest/test-result": ^24.9.0
-    "@jest/types": ^24.9.0
-    callsites: ^3.0.0
-    chalk: ^2.0.1
-    graceful-fs: ^4.1.15
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    chalk: ^4.0.0
+    graceful-fs: ^4.2.4
     is-ci: ^2.0.0
     is-ci: ^2.0.0
-    mkdirp: ^0.5.1
-    slash: ^2.0.0
-    source-map: ^0.6.0
-  checksum: ee84238bfb8c4aa60830b546e0e5dbdff53bbe55a1462f023182130ee7f1f3aac2dce0ab8395ab72b93e5a889fa12a55cebeeab04352a623d00d29c262dfbeb0
+    micromatch: ^4.0.2
+  checksum: 3c6a5fba05c4c6892cd3a9f66196ea8867087b77a5aa1a3f6cd349c785c3f1ca24abfd454664983aed1a165cab7846688e44fe8630652d666ba326b08625bc3d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-validate@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-validate@npm:24.9.0"
+"jest-validate@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-validate@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/types": ^24.9.0
-    camelcase: ^5.3.1
-    chalk: ^2.0.1
-    jest-get-type: ^24.9.0
+    "@jest/types": ^26.6.2
+    camelcase: ^6.0.0
+    chalk: ^4.0.0
+    jest-get-type: ^26.3.0
     leven: ^3.1.0
     leven: ^3.1.0
-    pretty-format: ^24.9.0
-  checksum: 8e9abc2b605a10e9872bd7cc9cd676641b781b16f22028b7ed59cb3243e942065229e804bf5aa3c9e2d62a1444dd492193155bb7e02d9e6e330faa0afbb6dd9f
+    pretty-format: ^26.6.2
+  checksum: bac11d6586d9b8885328a4a66eec45b692e45ac23034a5c09eb0ee32de324f2d3d52b073e0c34e9c222b3642b083d1152a736cf24c52109e4957537d731ca62b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-watch-typeahead@npm:0.4.2":
-  version: 0.4.2
-  resolution: "jest-watch-typeahead@npm:0.4.2"
+"jest-watch-typeahead@npm:0.6.1":
+  version: 0.6.1
+  resolution: "jest-watch-typeahead@npm:0.6.1"
   dependencies:
   dependencies:
-    ansi-escapes: ^4.2.1
-    chalk: ^2.4.1
-    jest-regex-util: ^24.9.0
-    jest-watcher: ^24.3.0
+    ansi-escapes: ^4.3.1
+    chalk: ^4.0.0
+    jest-regex-util: ^26.0.0
+    jest-watcher: ^26.3.0
     slash: ^3.0.0
     slash: ^3.0.0
-    string-length: ^3.1.0
-    strip-ansi: ^5.0.0
-  checksum: d65675b8a374307199852693feecf76c16e455910478eb1495d51ec5be66d08b6601e17274249ecce42454452bb202c7fea95262a3cfb5b16c8d50833e46f0db
+    string-length: ^4.0.1
+    strip-ansi: ^6.0.0
+  peerDependencies:
+    jest: ^26.0.0
+  checksum: a65dfd080e68b79ce7c861ec07791a0768820049a1d6a471d01f3fc41ee88723db29b434e19c917421e7f34ec567bcade368f3671e234c557288e206f7fd4257
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-watcher@npm:^24.3.0, jest-watcher@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "jest-watcher@npm:24.9.0"
+"jest-watcher@npm:^26.3.0, jest-watcher@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-watcher@npm:26.6.2"
   dependencies:
   dependencies:
-    "@jest/test-result": ^24.9.0
-    "@jest/types": ^24.9.0
-    "@types/yargs": ^13.0.0
-    ansi-escapes: ^3.0.0
-    chalk: ^2.0.1
-    jest-util: ^24.9.0
-    string-length: ^2.0.0
-  checksum: c0ceec6e854ee73a196064e51471fe01ff743ca78df8f4ef1c78194a0fd4f43ece26d2c55d011e258ac7ae0f37eaecbe3cc100defb604124d90cd9473538a97b
+    "@jest/test-result": ^26.6.2
+    "@jest/types": ^26.6.2
+    "@types/node": "*"
+    ansi-escapes: ^4.2.1
+    chalk: ^4.0.0
+    jest-util: ^26.6.2
+    string-length: ^4.0.1
+  checksum: 401137f1a73bf23cdf390019ebffb3f6f89c53ca49d48252d1dd6daf17a68787fef75cc55a623de28b63d87d0e8f13d8972d7dd06740f2f64f7b2a0409d119d2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-worker@npm:^24.6.0, jest-worker@npm:^24.9.0":
+"jest-worker@npm:^24.9.0":
   version: 24.9.0
   resolution: "jest-worker@npm:24.9.0"
   dependencies:
   version: 24.9.0
   resolution: "jest-worker@npm:24.9.0"
   dependencies:
@@ -11024,29 +12271,42 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest-worker@npm:^25.4.0":
-  version: 25.5.0
-  resolution: "jest-worker@npm:25.5.0"
+"jest-worker@npm:^26.5.0, jest-worker@npm:^26.6.2":
+  version: 26.6.2
+  resolution: "jest-worker@npm:26.6.2"
   dependencies:
   dependencies:
+    "@types/node": "*"
     merge-stream: ^2.0.0
     supports-color: ^7.0.0
     merge-stream: ^2.0.0
     supports-color: ^7.0.0
-  checksum: 773ad5c680f7c47c023e90a63faffe041dc297c19df90d31768598d700517ef31ad5e3289e68bdf85ab7eca91efde8134f8646472747f47ae3f60c96a37d1c4b
+  checksum: f9afa3b88e3f12027901e4964ba3ff048285b5783b5225cab28fac25b4058cea8ad54001e9a1577ee2bed125fac3ccf5c80dc507b120300cc1bbcb368796533e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jest@npm:24.9.0":
-  version: 24.9.0
-  resolution: "jest@npm:24.9.0"
+"jest-worker@npm:^27.5.1":
+  version: 27.5.1
+  resolution: "jest-worker@npm:27.5.1"
   dependencies:
   dependencies:
-    import-local: ^2.0.0
-    jest-cli: ^24.9.0
+    "@types/node": "*"
+    merge-stream: ^2.0.0
+    supports-color: ^8.0.0
+  checksum: 98cd68b696781caed61c983a3ee30bf880b5bd021c01d98f47b143d4362b85d0737f8523761e2713d45e18b4f9a2b98af1eaee77afade4111bb65c77d6f7c980
+  languageName: node
+  linkType: hard
+
+"jest@npm:26.6.0":
+  version: 26.6.0
+  resolution: "jest@npm:26.6.0"
+  dependencies:
+    "@jest/core": ^26.6.0
+    import-local: ^3.0.2
+    jest-cli: ^26.6.0
   bin:
   bin:
-    jest: ./bin/jest.js
-  checksum: 7bc61d47f94b18d52f354d785a9743883045222d0f1309a1131f0843479bdf8d98de1d62b9f519a562e99f883c51bd8af6a52f9e5a19596dae97d835abbc2cff
+    jest: bin/jest.js
+  checksum: e0d3efff0dc2a31c453a3f7d87586e5d6c0f008c9b827bb9204edde09288f922ddfb3a8917480bf68f4ac0298be28637daef98ebaaac65ea23d3cb754a6620c4
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"js-base64@npm:^2.1.8, js-base64@npm:^2.4.9":
+"js-base64@npm:^2.4.9":
   version: 2.6.4
   resolution: "js-base64@npm:2.6.4"
   checksum: 5f4084078d6c46f8529741d110df84b14fac3276b903760c21fa8cc8521370d607325dfe1c1a9fbbeaae1ff8e602665aaeef1362427d8fef704f9e3659472ce8
   version: 2.6.4
   resolution: "js-base64@npm:2.6.4"
   checksum: 5f4084078d6c46f8529741d110df84b14fac3276b903760c21fa8cc8521370d607325dfe1c1a9fbbeaae1ff8e602665aaeef1362427d8fef704f9e3659472ce8
@@ -11060,13 +12320,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"js-tokens@npm:^3.0.2":
-  version: 3.0.2
-  resolution: "js-tokens@npm:3.0.2"
-  checksum: ff24cf90e6e4ac446eba56e604781c1aaf3bdaf9b13a00596a0ebd972fa3b25dc83c0f0f67289c33252abb4111e0d14e952a5d9ffb61f5c22532d555ebd8d8a9
-  languageName: node
-  linkType: hard
-
 "js-yaml@npm:3.13.1":
   version: 3.13.1
   resolution: "js-yaml@npm:3.13.1"
 "js-yaml@npm:3.13.1":
   version: 3.13.1
   resolution: "js-yaml@npm:3.13.1"
@@ -11098,80 +12351,43 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jsdom@npm:^11.5.1":
-  version: 11.12.0
-  resolution: "jsdom@npm:11.12.0"
-  dependencies:
-    abab: ^2.0.0
-    acorn: ^5.5.3
-    acorn-globals: ^4.1.0
-    array-equal: ^1.0.0
-    cssom: ">= 0.3.2 < 0.4.0"
-    cssstyle: ^1.0.0
-    data-urls: ^1.0.0
-    domexception: ^1.0.1
-    escodegen: ^1.9.1
-    html-encoding-sniffer: ^1.0.2
-    left-pad: ^1.3.0
-    nwsapi: ^2.0.7
-    parse5: 4.0.0
-    pn: ^1.1.0
-    request: ^2.87.0
-    request-promise-native: ^1.0.5
-    sax: ^1.2.4
-    symbol-tree: ^3.2.2
-    tough-cookie: ^2.3.4
-    w3c-hr-time: ^1.0.1
-    webidl-conversions: ^4.0.2
-    whatwg-encoding: ^1.0.3
-    whatwg-mimetype: ^2.1.0
-    whatwg-url: ^6.4.1
-    ws: ^5.2.0
-    xml-name-validator: ^3.0.0
-  checksum: 1dab757e92ce857df648ebec3dbe487954f886652faf9d97953c3b502958b1e4487e147baef5494718294e8625ae238e68354db710456fa73c394fb93dbfc68b
-  languageName: node
-  linkType: hard
-
-"jsdom@npm:^14.1.0":
-  version: 14.1.0
-  resolution: "jsdom@npm:14.1.0"
-  dependencies:
-    abab: ^2.0.0
-    acorn: ^6.0.4
-    acorn-globals: ^4.3.0
-    array-equal: ^1.0.0
-    cssom: ^0.3.4
-    cssstyle: ^1.1.1
-    data-urls: ^1.1.0
-    domexception: ^1.0.1
-    escodegen: ^1.11.0
-    html-encoding-sniffer: ^1.0.2
-    nwsapi: ^2.1.3
-    parse5: 5.1.0
-    pn: ^1.1.0
-    request: ^2.88.0
-    request-promise-native: ^1.0.5
-    saxes: ^3.1.9
-    symbol-tree: ^3.2.2
-    tough-cookie: ^2.5.0
-    w3c-hr-time: ^1.0.1
-    w3c-xmlserializer: ^1.1.2
-    webidl-conversions: ^4.0.2
+"jsdom@npm:^16.4.0":
+  version: 16.7.0
+  resolution: "jsdom@npm:16.7.0"
+  dependencies:
+    abab: ^2.0.5
+    acorn: ^8.2.4
+    acorn-globals: ^6.0.0
+    cssom: ^0.4.4
+    cssstyle: ^2.3.0
+    data-urls: ^2.0.0
+    decimal.js: ^10.2.1
+    domexception: ^2.0.1
+    escodegen: ^2.0.0
+    form-data: ^3.0.0
+    html-encoding-sniffer: ^2.0.1
+    http-proxy-agent: ^4.0.1
+    https-proxy-agent: ^5.0.0
+    is-potential-custom-element-name: ^1.0.1
+    nwsapi: ^2.2.0
+    parse5: 6.0.1
+    saxes: ^5.0.1
+    symbol-tree: ^3.2.4
+    tough-cookie: ^4.0.0
+    w3c-hr-time: ^1.0.2
+    w3c-xmlserializer: ^2.0.0
+    webidl-conversions: ^6.1.0
     whatwg-encoding: ^1.0.5
     whatwg-mimetype: ^2.3.0
     whatwg-encoding: ^1.0.5
     whatwg-mimetype: ^2.3.0
-    whatwg-url: ^7.0.0
-    ws: ^6.1.2
+    whatwg-url: ^8.5.0
+    ws: ^7.4.6
     xml-name-validator: ^3.0.0
     xml-name-validator: ^3.0.0
-  checksum: c8ece2c4324be30536411a5ef9e52ebccefeb1605bd1ba31d14e40ab576a40a0e7d009bd89edd0e422654e4518383bb1f4ab6f574ccecaf98e5839c200fd7772
-  languageName: node
-  linkType: hard
-
-"jsesc@npm:^1.3.0":
-  version: 1.3.0
-  resolution: "jsesc@npm:1.3.0"
-  bin:
-    jsesc: bin/jsesc
-  checksum: 9384cc72bf8ef7f2eb75fea64176b8b0c1c5e77604854c72cb4670b7072e112e3baaa69ef134be98cb078834a7812b0bfe676ad441ccd749a59427f5ed2127f1
+  peerDependencies:
+    canvas: ^2.5.0
+  peerDependenciesMeta:
+    canvas:
+      optional: true
+  checksum: 454b83371857000763ed31130a049acd1b113e3b927e6dcd75c67ddc30cdd242d7ebcac5c2294b7a1a6428155cb1398709c573b3c6d809218692ea68edd93370
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -11193,6 +12409,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"json-buffer@npm:3.0.1":
+  version: 3.0.1
+  resolution: "json-buffer@npm:3.0.1"
+  checksum: 9026b03edc2847eefa2e37646c579300a1f3a4586cfb62bf857832b60c852042d0d6ae55d1afb8926163fa54c2b01d83ae24705f34990348bdac6273a29d4581
+  languageName: node
+  linkType: hard
+
 "json-parse-better-errors@npm:^1.0.1, json-parse-better-errors@npm:^1.0.2":
   version: 1.0.2
   resolution: "json-parse-better-errors@npm:1.0.2"
 "json-parse-better-errors@npm:^1.0.1, json-parse-better-errors@npm:^1.0.2":
   version: 1.0.2
   resolution: "json-parse-better-errors@npm:1.0.2"
@@ -11214,6 +12437,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"json-schema-traverse@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "json-schema-traverse@npm:1.0.0"
+  checksum: 02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad
+  languageName: node
+  linkType: hard
+
 "json-schema@npm:0.4.0":
   version: 0.4.0
   resolution: "json-schema@npm:0.4.0"
 "json-schema@npm:0.4.0":
   version: 0.4.0
   resolution: "json-schema@npm:0.4.0"
@@ -11228,15 +12458,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"json-stable-stringify@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "json-stable-stringify@npm:1.0.1"
-  dependencies:
-    jsonify: ~0.0.0
-  checksum: 65d6cbf0fca72a4136999f65f4401cf39a129f7aeff0fdd987ac3d3423a2113659294045fb8377e6e20d865cac32b1b8d70f3d87346c9786adcee60661d96ca5
-  languageName: node
-  linkType: hard
-
 "json-stringify-safe@npm:~5.0.1":
   version: 5.0.1
   resolution: "json-stringify-safe@npm:5.0.1"
 "json-stringify-safe@npm:~5.0.1":
   version: 5.0.1
   resolution: "json-stringify-safe@npm:5.0.1"
@@ -11251,16 +12472,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"json5@npm:^0.5.1":
-  version: 0.5.1
-  resolution: "json5@npm:0.5.1"
-  bin:
-    json5: lib/cli.js
-  checksum: 9b85bf06955b23eaa4b7328aa8892e3887e81ca731dd27af04a5f5f1458fbc5e1de57a24442e3272f8a888dd1abe1cb68eb693324035f6b3aeba4fcab7667d62
-  languageName: node
-  linkType: hard
-
-"json5@npm:^1.0.1":
+"json5@npm:^1.0.1, json5@npm:^1.0.2":
   version: 1.0.2
   resolution: "json5@npm:1.0.2"
   dependencies:
   version: 1.0.2
   resolution: "json5@npm:1.0.2"
   dependencies:
@@ -11271,7 +12483,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"json5@npm:^2.1.2":
+"json5@npm:^2.1.2, json5@npm:^2.2.3":
   version: 2.2.3
   resolution: "json5@npm:2.2.3"
   bin:
   version: 2.2.3
   resolution: "json5@npm:2.2.3"
   bin:
@@ -11305,10 +12517,14 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jsonify@npm:~0.0.0":
-  version: 0.0.0
-  resolution: "jsonify@npm:0.0.0"
-  checksum: d8d4ed476c116e6987a460dcb82f22284686caae9f498ac87b0502c1765ac1522f4f450a4cad4cc368d202fd3b27a3860735140a82867fc6d558f5f199c38bce
+"jsonpath@npm:^1.1.1":
+  version: 1.1.1
+  resolution: "jsonpath@npm:1.1.1"
+  dependencies:
+    esprima: 1.2.2
+    static-eval: 2.0.2
+    underscore: 1.12.1
+  checksum: 5480d8e9e424fe2ed4ade6860b6e2cefddb21adb3a99abe0254cd9428e8ef9b0c9fb5729d6a5a514e90df50d645ccea9f3be48d627570e6222dd5dadc28eba7b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -11414,13 +12630,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"jsx-ast-utils@npm:^2.2.1, jsx-ast-utils@npm:^2.2.3":
-  version: 2.4.1
-  resolution: "jsx-ast-utils@npm:2.4.1"
+"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5":
+  version: 3.3.5
+  resolution: "jsx-ast-utils@npm:3.3.5"
   dependencies:
   dependencies:
-    array-includes: ^3.1.1
-    object.assign: ^4.1.0
-  checksum: 833477231266631e0def7ab5fa5da386790130ce5f9ab5db22fb3a8e67ee0adba9082ff27687e5c64c893af00beeb2285a7309cbc40c5edbcafdaf4e9de069a1
+    array-includes: ^3.1.6
+    array.prototype.flat: ^1.3.1
+    object.assign: ^4.1.4
+    object.values: ^1.1.6
+  checksum: f4b05fa4d7b5234230c905cfa88d36dc8a58a6666975a3891429b1a8cdc8a140bca76c297225cb7a499fad25a2c052ac93934449a2c31a44fc9edd06c773780a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -11443,6 +12661,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"keyv@npm:^4.5.3":
+  version: 4.5.4
+  resolution: "keyv@npm:4.5.4"
+  dependencies:
+    json-buffer: 3.0.1
+  checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72
+  languageName: node
+  linkType: hard
+
 "killable@npm:^1.0.1":
   version: 1.0.1
   resolution: "killable@npm:1.0.1"
 "killable@npm:^1.0.1":
   version: 1.0.1
   resolution: "killable@npm:1.0.1"
@@ -11450,15 +12677,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"kind-of@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "kind-of@npm:2.0.1"
-  dependencies:
-    is-buffer: ^1.0.2
-  checksum: 043df2943e113bca612d26224947395e9673bb3808d94aed30e47fbf0bafd618e2a29ff0ca2d5498f64332c320fff07f0aa9d6edfc20906a93c1b8792f11759c
-  languageName: node
-  linkType: hard
-
 "kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0":
   version: 3.2.2
   resolution: "kind-of@npm:3.2.2"
 "kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0":
   version: 3.2.2
   resolution: "kind-of@npm:3.2.2"
@@ -11498,6 +12716,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"language-subtag-registry@npm:^0.3.20":
+  version: 0.3.22
+  resolution: "language-subtag-registry@npm:0.3.22"
+  checksum: 8ab70a7e0e055fe977ac16ea4c261faec7205ac43db5e806f72e5b59606939a3b972c4bd1e10e323b35d6ffa97c3e1c4c99f6553069dad2dfdd22020fa3eb56a
+  languageName: node
+  linkType: hard
+
+"language-tags@npm:^1.0.9":
+  version: 1.0.9
+  resolution: "language-tags@npm:1.0.9"
+  dependencies:
+    language-subtag-registry: ^0.3.20
+  checksum: 57c530796dc7179914dee71bc94f3747fd694612480241d0453a063777265dfe3a951037f7acb48f456bf167d6eb419d4c00263745326b3ba1cdcf4657070e78
+  languageName: node
+  linkType: hard
+
 "last-call-webpack-plugin@npm:^3.0.0":
   version: 3.0.0
   resolution: "last-call-webpack-plugin@npm:3.0.0"
 "last-call-webpack-plugin@npm:^3.0.0":
   version: 3.0.0
   resolution: "last-call-webpack-plugin@npm:3.0.0"
@@ -11515,36 +12749,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"lazy-cache@npm:^0.2.3":
-  version: 0.2.7
-  resolution: "lazy-cache@npm:0.2.7"
-  checksum: b4538aff20db586c354f31de3ed59ea2c8d5dc4f01141bf49f07601e7ca0d7ed43a3f49362ade49b1e18ab1f3d121df0f2c9ea9b599b44dd54fb0c0db253c8b9
-  languageName: node
-  linkType: hard
-
-"lazy-cache@npm:^1.0.3":
-  version: 1.0.4
-  resolution: "lazy-cache@npm:1.0.4"
-  checksum: e6650c22e5de1cc3f4a0c25d2b35fe9cd400473c1b3562be9fceadf8f368d708b54d24f5aa51b321b090da65b36426823a8f706b8dbdd68270db0daba812c5d3
-  languageName: node
-  linkType: hard
-
-"lcid@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "lcid@npm:1.0.0"
-  dependencies:
-    invert-kv: ^1.0.0
-  checksum: e8c7a4db07663068c5c44b650938a2bc41aa992037eebb69376214320f202c1250e70b50c32f939e28345fd30c2d35b8e8cd9a19d5932c398246a864ce54843d
-  languageName: node
-  linkType: hard
-
-"left-pad@npm:^1.3.0":
-  version: 1.3.0
-  resolution: "left-pad@npm:1.3.0"
-  checksum: 13fa96e17b70a54836490de22d4bab706e2ed508338bbabecfac72ecce445a74139c5b009a8112252cab8fc4ab7ac4ebd870e5b35bd236b443b12be96f8745ac
-  languageName: node
-  linkType: hard
-
 "leven@npm:^3.1.0":
   version: 3.1.0
   resolution: "leven@npm:3.1.0"
 "leven@npm:^3.1.0":
   version: 3.1.0
   resolution: "leven@npm:3.1.0"
@@ -11552,16 +12756,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"levenary@npm:^1.1.1":
-  version: 1.1.1
-  resolution: "levenary@npm:1.1.1"
+"levn@npm:^0.4.1":
+  version: 0.4.1
+  resolution: "levn@npm:0.4.1"
   dependencies:
   dependencies:
-    leven: ^3.1.0
-  checksum: d292b002e278c2b7e33fe0856920363a6abe61373c04c702bce3dfc324069a52b52ceb8c87d6b6032a074020425e56f2fd0c0a99f577511fabd1674a12df3282
+    prelude-ls: ^1.2.1
+    type-check: ~0.4.0
+  checksum: 12c5021c859bd0f5248561bf139121f0358285ec545ebf48bb3d346820d5c61a4309535c7f387ed7d84361cf821e124ce346c6b7cef8ee09a67c1473b46d0fc4
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"levn@npm:^0.3.0, levn@npm:~0.3.0":
+"levn@npm:~0.3.0":
   version: 0.3.0
   resolution: "levn@npm:0.3.0"
   dependencies:
   version: 0.3.0
   resolution: "levn@npm:0.3.0"
   dependencies:
@@ -11608,53 +12813,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"load-json-file@npm:^1.0.0":
-  version: 1.1.0
-  resolution: "load-json-file@npm:1.1.0"
-  dependencies:
-    graceful-fs: ^4.1.2
-    parse-json: ^2.2.0
-    pify: ^2.0.0
-    pinkie-promise: ^2.0.0
-    strip-bom: ^2.0.0
-  checksum: 0e4e4f380d897e13aa236246a917527ea5a14e4fc34d49e01ce4e7e2a1e08e2740ee463a03fb021c04f594f29a178f4adb994087549d7c1c5315fcd29bf9934b
-  languageName: node
-  linkType: hard
-
-"load-json-file@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "load-json-file@npm:2.0.0"
-  dependencies:
-    graceful-fs: ^4.1.2
-    parse-json: ^2.2.0
-    pify: ^2.0.0
-    strip-bom: ^3.0.0
-  checksum: 7f212bbf08a8c9aab087ead07aa220d1f43d83ec1c4e475a00a8d9bf3014eb29ebe901db8554627dcfb70184c274d05b7379f1e9678fe8297ae74dc495212049
-  languageName: node
-  linkType: hard
-
-"load-json-file@npm:^4.0.0":
-  version: 4.0.0
-  resolution: "load-json-file@npm:4.0.0"
-  dependencies:
-    graceful-fs: ^4.1.2
-    parse-json: ^4.0.0
-    pify: ^3.0.0
-    strip-bom: ^3.0.0
-  checksum: 8f5d6d93ba64a9620445ee9bde4d98b1eac32cf6c8c2d20d44abfa41a6945e7969456ab5f1ca2fb06ee32e206c9769a20eec7002fe290de462e8c884b6b8b356
-  languageName: node
-  linkType: hard
-
-"loader-fs-cache@npm:^1.0.2":
-  version: 1.0.3
-  resolution: "loader-fs-cache@npm:1.0.3"
-  dependencies:
-    find-cache-dir: ^0.1.1
-    mkdirp: ^0.5.1
-  checksum: 39781412e10bb0d6b5ca1afa9a4bd65e1827c5c51ef9ff746ae3fe8ce0e2cfa3fb96492d6619d8ab305407d20be82a9b244c439df0207f6ced4b98f2861bd372
-  languageName: node
-  linkType: hard
-
 "loader-runner@npm:^2.4.0":
   version: 2.4.0
   resolution: "loader-runner@npm:2.4.0"
 "loader-runner@npm:^2.4.0":
   version: 2.4.0
   resolution: "loader-runner@npm:2.4.0"
@@ -11662,14 +12820,14 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"loader-utils@npm:1.2.3":
-  version: 1.2.3
-  resolution: "loader-utils@npm:1.2.3"
+"loader-utils@npm:2.0.0":
+  version: 2.0.0
+  resolution: "loader-utils@npm:2.0.0"
   dependencies:
     big.js: ^5.2.2
   dependencies:
     big.js: ^5.2.2
-    emojis-list: ^2.0.0
-    json5: ^1.0.1
-  checksum: 385407fc2683b6d664276fd41df962296de4a15030bb24389de77b175570c3b56bd896869376ba14cf8b33a9e257e17a91d395739ba7e23b5b68a8749a41df7e
+    emojis-list: ^3.0.0
+    json5: ^2.1.2
+  checksum: 6856423131b50b6f5f259da36f498cfd7fc3c3f8bb17777cf87fdd9159e797d4ba4288d9a96415fd8da62c2906960e88f74711dee72d03a9003bddcd0d364a51
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -11695,16 +12853,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"locate-path@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "locate-path@npm:2.0.0"
-  dependencies:
-    p-locate: ^2.0.0
-    path-exists: ^3.0.0
-  checksum: 02d581edbbbb0fa292e28d96b7de36b5b62c2fa8b5a7e82638ebb33afa74284acf022d3b1e9ae10e3ffb7658fbc49163fcd5e76e7d1baaa7801c3e05a81da755
-  languageName: node
-  linkType: hard
-
 "locate-path@npm:^3.0.0":
   version: 3.0.0
   resolution: "locate-path@npm:3.0.0"
 "locate-path@npm:^3.0.0":
   version: 3.0.0
   resolution: "locate-path@npm:3.0.0"
@@ -11780,6 +12928,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"lodash.merge@npm:^4.6.2":
+  version: 4.6.2
+  resolution: "lodash.merge@npm:4.6.2"
+  checksum: ad580b4bdbb7ca1f7abf7e1bce63a9a0b98e370cf40194b03380a46b4ed799c9573029599caebc1b14e3f24b111aef72b96674a56cfa105e0f5ac70546cdc005
+  languageName: node
+  linkType: hard
+
 "lodash.mergewith@npm:4.6.2":
   version: 4.6.2
   resolution: "lodash.mergewith@npm:4.6.2"
 "lodash.mergewith@npm:4.6.2":
   version: 4.6.2
   resolution: "lodash.mergewith@npm:4.6.2"
@@ -11794,14 +12949,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"lodash.sortby@npm:^4.7.0":
-  version: 4.7.0
-  resolution: "lodash.sortby@npm:4.7.0"
-  checksum: db170c9396d29d11fe9a9f25668c4993e0c1331bcb941ddbd48fb76f492e732add7f2a47cfdf8e9d740fa59ac41bbfaf931d268bc72aab3ab49e9f89354d718c
-  languageName: node
-  linkType: hard
-
-"lodash.template@npm:4.5.0, lodash.template@npm:^4.4.0":
+"lodash.template@npm:4.5.0, lodash.template@npm:^4.5.0":
   version: 4.5.0
   resolution: "lodash.template@npm:4.5.0"
   dependencies:
   version: 4.5.0
   resolution: "lodash.template@npm:4.5.0"
   dependencies:
@@ -11820,6 +12968,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"lodash.truncate@npm:^4.4.2":
+  version: 4.4.2
+  resolution: "lodash.truncate@npm:4.4.2"
+  checksum: b463d8a382cfb5f0e71c504dcb6f807a7bd379ff1ea216669aa42c52fc28c54e404bfbd96791aa09e6df0de2c1d7b8f1b7f4b1a61f324d38fe98bc535aeee4f5
+  languageName: node
+  linkType: hard
+
 "lodash.uniq@npm:^4.5.0":
   version: 4.5.0
   resolution: "lodash.uniq@npm:4.5.0"
 "lodash.uniq@npm:^4.5.0":
   version: 4.5.0
   resolution: "lodash.uniq@npm:4.5.0"
@@ -11827,7 +12982,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"lodash@npm:>=3.5 <5, lodash@npm:^4.0.0, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.17.5, lodash@npm:^4.2.0, lodash@npm:^4.2.1, lodash@npm:~4.17.10":
+"lodash@npm:>=3.5 <5, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.5, lodash@npm:^4.2.0, lodash@npm:^4.2.1, lodash@npm:^4.7.0, lodash@npm:~4.17.10":
   version: 4.17.21
   resolution: "lodash@npm:4.17.21"
   checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7
   version: 4.17.21
   resolution: "lodash@npm:4.17.21"
   checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7
@@ -11890,16 +13045,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"loud-rejection@npm:^1.0.0":
-  version: 1.6.0
-  resolution: "loud-rejection@npm:1.6.0"
-  dependencies:
-    currently-unhandled: ^0.4.1
-    signal-exit: ^3.0.0
-  checksum: 750e12defde34e8cbf263c2bff16f028a89b56e022ad6b368aa7c39495b5ac33f2349a8d00665a9b6d25c030b376396524d8a31eb0dde98aaa97956d7324f927
-  languageName: node
-  linkType: hard
-
 "lower-case@npm:^2.0.2":
   version: 2.0.2
   resolution: "lower-case@npm:2.0.2"
 "lower-case@npm:^2.0.2":
   version: 2.0.2
   resolution: "lower-case@npm:2.0.2"
@@ -11941,7 +13086,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0":
+"magic-string@npm:^0.25.0, magic-string@npm:^0.25.7":
+  version: 0.25.9
+  resolution: "magic-string@npm:0.25.9"
+  dependencies:
+    sourcemap-codec: ^1.4.8
+  checksum: 9a0e55a15c7303fc360f9572a71cffba1f61451bc92c5602b1206c9d17f492403bf96f946dfce7483e66822d6b74607262e24392e87b0ac27b786e69a40e9b1a
+  languageName: node
+  linkType: hard
+
+"make-dir@npm:^2.0.0":
   version: 2.1.0
   resolution: "make-dir@npm:2.1.0"
   dependencies:
   version: 2.1.0
   resolution: "make-dir@npm:2.1.0"
   dependencies:
@@ -11960,6 +13114,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"make-dir@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "make-dir@npm:4.0.0"
+  dependencies:
+    semver: ^7.5.3
+  checksum: bf0731a2dd3aab4db6f3de1585cea0b746bb73eb5a02e3d8d72757e376e64e6ada190b1eddcde5b2f24a81b688a9897efd5018737d05e02e2a671dda9cff8a8a
+  languageName: node
+  linkType: hard
+
 "make-fetch-happen@npm:^10.0.3":
   version: 10.0.5
   resolution: "make-fetch-happen@npm:10.0.5"
 "make-fetch-happen@npm:^10.0.3":
   version: 10.0.5
   resolution: "make-fetch-happen@npm:10.0.5"
@@ -12041,13 +13204,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"mamacro@npm:^0.0.3":
-  version: 0.0.3
-  resolution: "mamacro@npm:0.0.3"
-  checksum: ed3f970007248e377cd3a141866e2d6ba0ef09344b4ed1d80dcce6b5d6cdec6a50675894cc5249efdefeace60dd430afcf4af6cbd6bf975d79feb9c4b703fbc2
-  languageName: node
-  linkType: hard
-
 "map-age-cleaner@npm:^0.1.1":
   version: 0.1.3
   resolution: "map-age-cleaner@npm:0.1.3"
 "map-age-cleaner@npm:^0.1.1":
   version: 0.1.3
   resolution: "map-age-cleaner@npm:0.1.3"
@@ -12064,7 +13220,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"map-obj@npm:^1.0.0, map-obj@npm:^1.0.1":
+"map-obj@npm:^1.0.0":
   version: 1.0.1
   resolution: "map-obj@npm:1.0.1"
   checksum: 9949e7baec2a336e63b8d4dc71018c117c3ce6e39d2451ccbfd3b8350c547c4f6af331a4cbe1c83193d7c6b786082b6256bde843db90cb7da2a21e8fcc28afed
   version: 1.0.1
   resolution: "map-obj@npm:1.0.1"
   checksum: 9949e7baec2a336e63b8d4dc71018c117c3ce6e39d2451ccbfd3b8350c547c4f6af331a4cbe1c83193d7c6b786082b6256bde843db90cb7da2a21e8fcc28afed
@@ -12183,24 +13339,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"meow@npm:^3.7.0":
-  version: 3.7.0
-  resolution: "meow@npm:3.7.0"
-  dependencies:
-    camelcase-keys: ^2.0.0
-    decamelize: ^1.1.2
-    loud-rejection: ^1.0.0
-    map-obj: ^1.0.1
-    minimist: ^1.1.3
-    normalize-package-data: ^2.3.4
-    object-assign: ^4.0.1
-    read-pkg-up: ^1.0.1
-    redent: ^1.0.0
-    trim-newlines: ^1.0.0
-  checksum: 65a412e5d0d643615508007a9292799bb3e4e690597d54c9e98eb0ca3adb7b8ca8899f41ea7cb7d8277129cdcd9a1a60202b31f88e0034e6aaae02894d80999a
-  languageName: node
-  linkType: hard
-
 "meow@npm:^9.0.0":
   version: 9.0.0
   resolution: "meow@npm:9.0.0"
 "meow@npm:^9.0.0":
   version: 9.0.0
   resolution: "meow@npm:9.0.0"
@@ -12221,17 +13359,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"merge-deep@npm:^3.0.2":
-  version: 3.0.3
-  resolution: "merge-deep@npm:3.0.3"
-  dependencies:
-    arr-union: ^3.1.0
-    clone-deep: ^0.2.4
-    kind-of: ^3.0.2
-  checksum: d2eb367b8300327c66a3e1e01eb06251f51b440bf5bfa5f0f8065ae95bf3af620d21fcd0ab2eb50e74f5119aac40ffd26c85e3bf82f79082e8757675f5885d3d
-  languageName: node
-  linkType: hard
-
 "merge-descriptors@npm:1.0.1":
   version: 1.0.1
   resolution: "merge-descriptors@npm:1.0.1"
 "merge-descriptors@npm:1.0.1":
   version: 1.0.1
   resolution: "merge-descriptors@npm:1.0.1"
@@ -12246,7 +13373,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"merge2@npm:^1.2.3, merge2@npm:^1.3.0, merge2@npm:^1.4.1":
+"merge2@npm:^1.3.0, merge2@npm:^1.4.1":
   version: 1.4.1
   resolution: "merge2@npm:1.4.1"
   checksum: 7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2
   version: 1.4.1
   resolution: "merge2@npm:1.4.1"
   checksum: 7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2
@@ -12288,7 +13415,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"micromatch@npm:^4.0.4":
+"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5":
   version: 4.0.5
   resolution: "micromatch@npm:4.0.5"
   dependencies:
   version: 4.0.5
   resolution: "micromatch@npm:4.0.5"
   dependencies:
@@ -12317,6 +13444,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"mime-db@npm:1.52.0":
+  version: 1.52.0
+  resolution: "mime-db@npm:1.52.0"
+  checksum: 0d99a03585f8b39d68182803b12ac601d9c01abfa28ec56204fa330bc9f3d1c5e14beb049bafadb3dbdf646dfb94b87e24d4ec7b31b7279ef906a8ea9b6a513f
+  languageName: node
+  linkType: hard
+
 "mime-types@npm:^2.1.12, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24":
   version: 2.1.31
   resolution: "mime-types@npm:2.1.31"
 "mime-types@npm:^2.1.12, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24":
   version: 2.1.31
   resolution: "mime-types@npm:2.1.31"
@@ -12326,6 +13460,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"mime-types@npm:^2.1.27, mime-types@npm:~2.1.34":
+  version: 2.1.35
+  resolution: "mime-types@npm:2.1.35"
+  dependencies:
+    mime-db: 1.52.0
+  checksum: 89a5b7f1def9f3af5dad6496c5ed50191ae4331cc5389d7c521c8ad28d5fdad2d06fd81baf38fed813dc4e46bb55c8145bb0ff406330818c9cf712fb2e9b3836
+  languageName: node
+  linkType: hard
+
 "mime@npm:1.6.0":
   version: 1.6.0
   resolution: "mime@npm:1.6.0"
 "mime@npm:1.6.0":
   version: 1.6.0
   resolution: "mime@npm:1.6.0"
@@ -12374,17 +13517,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"mini-css-extract-plugin@npm:0.9.0":
-  version: 0.9.0
-  resolution: "mini-css-extract-plugin@npm:0.9.0"
+"mini-css-extract-plugin@npm:0.11.3":
+  version: 0.11.3
+  resolution: "mini-css-extract-plugin@npm:0.11.3"
   dependencies:
     loader-utils: ^1.1.0
     normalize-url: 1.9.1
     schema-utils: ^1.0.0
     webpack-sources: ^1.1.0
   peerDependencies:
   dependencies:
     loader-utils: ^1.1.0
     normalize-url: 1.9.1
     schema-utils: ^1.0.0
     webpack-sources: ^1.1.0
   peerDependencies:
-    webpack: ^4.4.0
-  checksum: e5cf437c15e4adf119d3a5af1bb604c880bc90a637aaf0535c8db68219efec42dcace1c54789422dec05d76ced98c44ef89ae44a3c556e34936fdbdd4743a210
+    webpack: ^4.4.0 || ^5.0.0
+  checksum: 14fbdf1338fe0264a2f7f87b3fc640809b7443f6434c6532bdbec1c5ab113502325fec958e9cf0667c3790087dc1e83c02e1f4d7463c10c956b0d6ebe56ea99e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -12411,7 +13554,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"minimatch@npm:^3.0.4":
+"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
   version: 3.1.2
   resolution: "minimatch@npm:3.1.2"
   dependencies:
   version: 3.1.2
   resolution: "minimatch@npm:3.1.2"
   dependencies:
@@ -12449,7 +13592,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"minimist@npm:^1.1.1, minimist@npm:^1.1.3, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.8":
+"minimist@npm:^1.1.1, minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8":
   version: 1.2.8
   resolution: "minimist@npm:1.2.8"
   checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0
   version: 1.2.8
   resolution: "minimist@npm:1.2.8"
   checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0
@@ -12609,16 +13752,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"mixin-object@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "mixin-object@npm:2.0.1"
-  dependencies:
-    for-in: ^0.1.3
-    is-extendable: ^0.1.1
-  checksum: 7d0eb7c2f06435fcc01d132824b4c973a0df689a117d8199d79911b506363b6f4f86a84458a63f3acfa7388f3052612cfe27105400b4932678452925a9739a4c
-  languageName: node
-  linkType: hard
-
 "mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1":
   version: 0.5.5
   resolution: "mkdirp@npm:0.5.5"
 "mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3, mkdirp@npm:^0.5.5, mkdirp@npm:~0.5.1":
   version: 0.5.5
   resolution: "mkdirp@npm:0.5.5"
@@ -12674,13 +13807,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ms@npm:2.1.1":
-  version: 2.1.1
-  resolution: "ms@npm:2.1.1"
-  checksum: 0078a23cd916a9a7435c413caa14c57d4b4f6e2470e0ab554b6964163c8a4436448ac7ae020e883685475da6b6796cc396b670f579cb275db288a21e3e57721e
-  languageName: node
-  linkType: hard
-
 "ms@npm:2.1.2":
   version: 2.1.2
   resolution: "ms@npm:2.1.2"
 "ms@npm:2.1.2":
   version: 2.1.2
   resolution: "ms@npm:2.1.2"
@@ -12688,7 +13814,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ms@npm:^2.0.0, ms@npm:^2.1.1":
+"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1":
   version: 2.1.3
   resolution: "ms@npm:2.1.3"
   checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
   version: 2.1.3
   resolution: "ms@npm:2.1.3"
   checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
@@ -12714,14 +13840,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"mute-stream@npm:0.0.8":
-  version: 0.0.8
-  resolution: "mute-stream@npm:0.0.8"
-  checksum: ff48d251fc3f827e5b1206cda0ffdaec885e56057ee86a3155e1951bc940fd5f33531774b1cc8414d7668c10a8907f863f6561875ee6e8768931a62121a531a1
-  languageName: node
-  linkType: hard
-
-"nan@npm:^2.12.1, nan@npm:^2.13.2":
+"nan@npm:^2.12.1":
   version: 2.14.2
   resolution: "nan@npm:2.14.2"
   dependencies:
   version: 2.14.2
   resolution: "nan@npm:2.14.2"
   dependencies:
@@ -12739,7 +13858,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"nanoid@npm:^3.3.6":
+"nanoid@npm:^3.3.6, nanoid@npm:^3.3.7":
   version: 3.3.7
   resolution: "nanoid@npm:3.3.7"
   bin:
   version: 3.3.7
   resolution: "nanoid@npm:3.3.7"
   bin:
@@ -12767,6 +13886,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"native-url@npm:^0.2.6":
+  version: 0.2.6
+  resolution: "native-url@npm:0.2.6"
+  dependencies:
+    querystring: ^0.2.0
+  checksum: d56a67b32e635c4944985f551a9976dfe609a3947810791c50f5c37cff1d9dd5fe040184989d104be8752582b79dc4e726f2a9c075d691ecce86b31ae9387f1b
+  languageName: node
+  linkType: hard
+
 "natural-compare@npm:^1.4.0":
   version: 1.4.0
   resolution: "natural-compare@npm:1.4.0"
 "natural-compare@npm:^1.4.0":
   version: 1.4.0
   resolution: "natural-compare@npm:1.4.0"
@@ -12798,7 +13926,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"negotiator@npm:^0.6.2, negotiator@npm:^0.6.3":
+"negotiator@npm:0.6.3, negotiator@npm:^0.6.2, negotiator@npm:^0.6.3":
   version: 0.6.3
   resolution: "negotiator@npm:0.6.3"
   checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9
   version: 0.6.3
   resolution: "negotiator@npm:0.6.3"
   checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9
@@ -12812,10 +13940,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"next-tick@npm:~1.0.0":
-  version: 1.0.0
-  resolution: "next-tick@npm:1.0.0"
-  checksum: 83fcb3d4f8d9380210b1c2b8a610463602d80283f0c0c8571c1688e1ad6cbf3a16b345f5bb7212617d4898bedcfa10dff327dc09ec20a112a5bf43a0271375fb
+"next-tick@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "next-tick@npm:1.1.0"
+  checksum: 83b5cf36027a53ee6d8b7f9c0782f2ba87f4858d977342bfc3c20c21629290a2111f8374d13a81221179603ffc4364f38374b5655d17b6a8f8a8c77bdea4fe8b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -12965,23 +14093,24 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"node-notifier@npm:^5.4.2":
-  version: 5.4.5
-  resolution: "node-notifier@npm:5.4.5"
+"node-notifier@npm:^8.0.0":
+  version: 8.0.2
+  resolution: "node-notifier@npm:8.0.2"
   dependencies:
     growly: ^1.3.0
   dependencies:
     growly: ^1.3.0
-    is-wsl: ^1.1.0
-    semver: ^5.5.0
+    is-wsl: ^2.2.0
+    semver: ^7.3.2
     shellwords: ^0.1.1
     shellwords: ^0.1.1
-    which: ^1.3.0
-  checksum: 8de174eb055a2ec55c1b0beede9328e8f9d4e32e7eacb7e3e2fddff48534105d0e2e10b4947dd422cc0602c65141317499c6fb1dc3b8ba03c775fb159e360bef
+    uuid: ^8.3.0
+    which: ^2.0.2
+  checksum: 7db1683003f6aaa4324959dfa663cd56e301ccc0165977a9e7737989ffe3b4763297f9fc85f44d0662b63a4fd85516eda43411b492a4d2fae207afb23773f912
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"node-releases@npm:^1.1.52":
-  version: 1.1.73
-  resolution: "node-releases@npm:1.1.73"
-  checksum: 44a6caec3330538a669c156fa84833725ae92b317585b106e08ab292c14da09f30cb913c10f1a7402180a51b10074832d4e045b6c3512d74c37d86b41a69e63b
+"node-releases@npm:^1.1.61":
+  version: 1.1.77
+  resolution: "node-releases@npm:1.1.77"
+  checksum: eb2fcb45310e7d77f82bfdadeca546a698d258e011f15d88ad9a452a5e838a672ec532906581096ca19c66284a788330c3b09227ffc540e67228730f41b9c2e2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -12992,46 +14121,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"node-sass-chokidar@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "node-sass-chokidar@npm:2.0.0"
-  dependencies:
-    async-foreach: ^0.1.3
-    chokidar: ^3.4.0
-    get-stdin: ^4.0.1
-    glob: ^7.0.3
-    meow: ^3.7.0
-    node-sass: ^7.0.1
-    sass-graph: ^2.2.4
-    stdout-stream: ^1.4.0
-  bin:
-    node-sass-chokidar: bin/node-sass-chokidar
-  checksum: 5aeffc93cddf5cc32d0e86de4999e56e3cdccb1d86b5ed211e2d661f4e579bac19c078ca791662e2aaff9752ba2e18ce87324c07de5b3222064a4c9703856d9c
-  languageName: node
-  linkType: hard
-
-"node-sass@npm:^7.0.1":
-  version: 7.0.3
-  resolution: "node-sass@npm:7.0.3"
-  dependencies:
-    async-foreach: ^0.1.3
-    chalk: ^4.1.2
-    cross-spawn: ^7.0.3
-    gaze: ^1.0.0
-    get-stdin: ^4.0.1
-    glob: ^7.0.3
-    lodash: ^4.17.15
-    meow: ^9.0.0
-    nan: ^2.13.2
-    node-gyp: ^8.4.1
-    npmlog: ^5.0.0
-    request: ^2.88.0
-    sass-graph: ^4.0.1
-    stdout-stream: ^1.4.0
-    true-case-path: ^1.0.2
-  bin:
-    node-sass: bin/node-sass
-  checksum: 7d577d0fb68948959f367341e6cfc2858aa37abc5fadbd9e6b477ed0d192bebf7f8516d0b53c27be30ab05d5cd62d8a9bab08cc4442ef901b02cb51d864b4419
+"node-releases@npm:^2.0.14":
+  version: 2.0.14
+  resolution: "node-releases@npm:2.0.14"
+  checksum: 59443a2f77acac854c42d321bf1b43dea0aef55cd544c6a686e9816a697300458d4e82239e2d794ea05f7bbbc8a94500332e2d3ac3f11f52e4b16cbe638b3c41
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -13070,7 +14163,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"normalize-package-data@npm:^2.3.2, normalize-package-data@npm:^2.3.4, normalize-package-data@npm:^2.5.0":
+"normalize-package-data@npm:^2.5.0":
   version: 2.5.0
   resolution: "normalize-package-data@npm:2.5.0"
   dependencies:
   version: 2.5.0
   resolution: "normalize-package-data@npm:2.5.0"
   dependencies:
@@ -13161,18 +14254,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"npmlog@npm:^5.0.0":
-  version: 5.0.1
-  resolution: "npmlog@npm:5.0.1"
-  dependencies:
-    are-we-there-yet: ^2.0.0
-    console-control-strings: ^1.1.0
-    gauge: ^3.0.0
-    set-blocking: ^2.0.0
-  checksum: 516b2663028761f062d13e8beb3f00069c5664925871a9b57989642ebe09f23ab02145bf3ab88da7866c4e112cafff72401f61a672c7c8a20edc585a7016ef5f
-  languageName: node
-  linkType: hard
-
 "npmlog@npm:^6.0.0":
   version: 6.0.1
   resolution: "npmlog@npm:6.0.1"
 "npmlog@npm:^6.0.0":
   version: 6.0.1
   resolution: "npmlog@npm:6.0.1"
@@ -13210,17 +14291,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"number-is-nan@npm:^1.0.0":
-  version: 1.0.1
-  resolution: "number-is-nan@npm:1.0.1"
-  checksum: 13656bc9aa771b96cef209ffca31c31a03b507ca6862ba7c3f638a283560620d723d52e626d57892c7fff475f4c36ac07f0600f14544692ff595abff214b9ffb
-  languageName: node
-  linkType: hard
-
-"nwsapi@npm:^2.0.7, nwsapi@npm:^2.1.3":
-  version: 2.2.0
-  resolution: "nwsapi@npm:2.2.0"
-  checksum: 5ef4a9bc0c1a5b7f2e014aa6a4b359a257503b796618ed1ef0eb852098f77e772305bb0e92856e4bbfa3e6c75da48c0113505c76f144555ff38867229c2400a7
+"nwsapi@npm:^2.2.0":
+  version: 2.2.7
+  resolution: "nwsapi@npm:2.2.7"
+  checksum: cab25f7983acec7e23490fec3ef7be608041b460504229770e3bfcf9977c41d6fe58f518994d3bd9aa3a101f501089a3d4a63536f4ff8ae4b8c4ca23bdbfda4e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -13249,13 +14323,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"object-hash@npm:^2.0.1":
-  version: 2.2.0
-  resolution: "object-hash@npm:2.2.0"
-  checksum: 55ba841e3adce9c4f1b9b46b41983eda40f854e0d01af2802d3ae18a7085a17168d6b81731d43fdf1d6bcbb3c9f9c56d22c8fea992203ad90a38d7d919bc28f1
-  languageName: node
-  linkType: hard
-
 "object-inspect@npm:^1.10.3, object-inspect@npm:^1.7.0, object-inspect@npm:^1.9.0":
   version: 1.10.3
   resolution: "object-inspect@npm:1.10.3"
 "object-inspect@npm:^1.10.3, object-inspect@npm:^1.7.0, object-inspect@npm:^1.9.0":
   version: 1.10.3
   resolution: "object-inspect@npm:1.10.3"
@@ -13263,6 +14330,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"object-inspect@npm:^1.13.1":
+  version: 1.13.1
+  resolution: "object-inspect@npm:1.13.1"
+  checksum: 7d9fa9221de3311dcb5c7c307ee5dc011cdd31dc43624b7c184b3840514e118e05ef0002be5388304c416c0eb592feb46e983db12577fc47e47d5752fbbfb61f
+  languageName: node
+  linkType: hard
+
 "object-is@npm:^1.0.1, object-is@npm:^1.0.2, object-is@npm:^1.1.2":
   version: 1.1.5
   resolution: "object-is@npm:1.1.5"
 "object-is@npm:^1.0.1, object-is@npm:^1.0.2, object-is@npm:^1.1.2":
   version: 1.1.5
   resolution: "object-is@npm:1.1.5"
@@ -13301,6 +14375,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"object.assign@npm:^4.1.4, object.assign@npm:^4.1.5":
+  version: 4.1.5
+  resolution: "object.assign@npm:4.1.5"
+  dependencies:
+    call-bind: ^1.0.5
+    define-properties: ^1.2.1
+    has-symbols: ^1.0.3
+    object-keys: ^1.1.1
+  checksum: f9aeac0541661370a1fc86e6a8065eb1668d3e771f7dbb33ee54578201336c057b21ee61207a186dd42db0c62201d91aac703d20d12a79fc79c353eed44d4e25
+  languageName: node
+  linkType: hard
+
 "object.entries@npm:^1.1.0, object.entries@npm:^1.1.1, object.entries@npm:^1.1.2":
   version: 1.1.4
   resolution: "object.entries@npm:1.1.4"
 "object.entries@npm:^1.1.0, object.entries@npm:^1.1.1, object.entries@npm:^1.1.2":
   version: 1.1.4
   resolution: "object.entries@npm:1.1.4"
@@ -13312,7 +14398,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"object.fromentries@npm:^2.0.2, object.fromentries@npm:^2.0.3":
+"object.entries@npm:^1.1.7":
+  version: 1.1.8
+  resolution: "object.entries@npm:1.1.8"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-object-atoms: ^1.0.0
+  checksum: 5314877cb637ef3437a30bba61d9bacdb3ce74bf73ac101518be0633c37840c8cc67407edb341f766e8093b3d7516d5c3358f25adfee4a2c697c0ec4c8491907
+  languageName: node
+  linkType: hard
+
+"object.fromentries@npm:^2.0.3":
   version: 2.0.4
   resolution: "object.fromentries@npm:2.0.4"
   dependencies:
   version: 2.0.4
   resolution: "object.fromentries@npm:2.0.4"
   dependencies:
@@ -13324,7 +14421,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"object.getownpropertydescriptors@npm:^2.0.3, object.getownpropertydescriptors@npm:^2.1.0, object.getownpropertydescriptors@npm:^2.1.1":
+"object.fromentries@npm:^2.0.7":
+  version: 2.0.8
+  resolution: "object.fromentries@npm:2.0.8"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.2
+    es-object-atoms: ^1.0.0
+  checksum: 29b2207a2db2782d7ced83f93b3ff5d425f901945f3665ffda1821e30a7253cd1fd6b891a64279976098137ddfa883d748787a6fea53ecdb51f8df8b8cec0ae1
+  languageName: node
+  linkType: hard
+
+"object.getownpropertydescriptors@npm:^2.0.3, object.getownpropertydescriptors@npm:^2.1.0":
   version: 2.1.2
   resolution: "object.getownpropertydescriptors@npm:2.1.2"
   dependencies:
   version: 2.1.2
   resolution: "object.getownpropertydescriptors@npm:2.1.2"
   dependencies:
@@ -13335,6 +14444,28 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"object.groupby@npm:^1.0.1":
+  version: 1.0.3
+  resolution: "object.groupby@npm:1.0.3"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.2
+  checksum: 0d30693ca3ace29720bffd20b3130451dca7a56c612e1926c0a1a15e4306061d84410bdb1456be2656c5aca53c81b7a3661eceaa362db1bba6669c2c9b6d1982
+  languageName: node
+  linkType: hard
+
+"object.hasown@npm:^1.1.3":
+  version: 1.1.4
+  resolution: "object.hasown@npm:1.1.4"
+  dependencies:
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.2
+    es-object-atoms: ^1.0.0
+  checksum: bc46eb5ca22106fcd07aab1411508c2c68b7565fe8fb272f166fb9bf203972e8b5c86a5a4b2c86204beead0626a7a4119d32cefbaf7c5dd57b400bf9e6363cb6
+  languageName: node
+  linkType: hard
+
 "object.pick@npm:^1.3.0":
   version: 1.3.0
   resolution: "object.pick@npm:1.3.0"
 "object.pick@npm:^1.3.0":
   version: 1.3.0
   resolution: "object.pick@npm:1.3.0"
@@ -13355,6 +14486,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"object.values@npm:^1.1.6, object.values@npm:^1.1.7":
+  version: 1.2.0
+  resolution: "object.values@npm:1.2.0"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-object-atoms: ^1.0.0
+  checksum: 51fef456c2a544275cb1766897f34ded968b22adfc13ba13b5e4815fdaf4304a90d42a3aee114b1f1ede048a4890381d47a5594d84296f2767c6a0364b9da8fa
+  languageName: node
+  linkType: hard
+
 "obuf@npm:^1.0.0, obuf@npm:^1.1.2":
   version: 1.1.2
   resolution: "obuf@npm:1.1.2"
 "obuf@npm:^1.0.0, obuf@npm:^1.1.2":
   version: 1.1.2
   resolution: "obuf@npm:1.1.2"
@@ -13362,12 +14504,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"on-finished@npm:~2.3.0":
-  version: 2.3.0
-  resolution: "on-finished@npm:2.3.0"
+"on-finished@npm:2.4.1":
+  version: 2.4.1
+  resolution: "on-finished@npm:2.4.1"
   dependencies:
     ee-first: 1.1.1
   dependencies:
     ee-first: 1.1.1
-  checksum: 1db595bd963b0124d6fa261d18320422407b8f01dc65863840f3ddaaf7bcad5b28ff6847286703ca53f4ec19595bd67a2f1253db79fc4094911ec6aa8df1671b
+  checksum: d20929a25e7f0bb62f937a425b5edeb4e4cde0540d77ba146ec9357f00b0d497cdb3b9b05b9c8e46222407d1548d08166bff69cc56dfa55ba0e4469228920ff0
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -13415,19 +14557,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"optimize-css-assets-webpack-plugin@npm:5.0.3":
-  version: 5.0.3
-  resolution: "optimize-css-assets-webpack-plugin@npm:5.0.3"
+"optimize-css-assets-webpack-plugin@npm:5.0.4":
+  version: 5.0.4
+  resolution: "optimize-css-assets-webpack-plugin@npm:5.0.4"
   dependencies:
     cssnano: ^4.1.10
     last-call-webpack-plugin: ^3.0.0
   peerDependencies:
     webpack: ^4.0.0
   dependencies:
     cssnano: ^4.1.10
     last-call-webpack-plugin: ^3.0.0
   peerDependencies:
     webpack: ^4.0.0
-  checksum: 334eb9cb83643bba259946034d15ab123fd503d646f07edd1731efe57cf1c086c4fe28f804da8171316bbfa175c5f24913ae4337059045785cf7dacac303228d
+  checksum: bcd509eaab2a6f0ed8396fe847f4f0da73655a54f4c418fa30dc1fc4a0b1b620f38e2fcd6bcb369e2a6cf4530995b371e9d12011566ac7ffe6ac6aec2ab0a4fb
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"optionator@npm:^0.8.1, optionator@npm:^0.8.3":
+"optionator@npm:^0.8.1":
   version: 0.8.3
   resolution: "optionator@npm:0.8.3"
   dependencies:
   version: 0.8.3
   resolution: "optionator@npm:0.8.3"
   dependencies:
@@ -13441,33 +14583,24 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"os-browserify@npm:^0.3.0":
-  version: 0.3.0
-  resolution: "os-browserify@npm:0.3.0"
-  checksum: 16e37ba3c0e6a4c63443c7b55799ce4066d59104143cb637ecb9fce586d5da319cdca786ba1c867abbe3890d2cbf37953f2d51eea85e20dd6c4570d6c54bfebf
-  languageName: node
-  linkType: hard
-
-"os-homedir@npm:^1.0.0":
-  version: 1.0.2
-  resolution: "os-homedir@npm:1.0.2"
-  checksum: af609f5a7ab72de2f6ca9be6d6b91a599777afc122ac5cad47e126c1f67c176fe9b52516b9eeca1ff6ca0ab8587fe66208bc85e40a3940125f03cdb91408e9d2
-  languageName: node
-  linkType: hard
-
-"os-locale@npm:^1.4.0":
-  version: 1.4.0
-  resolution: "os-locale@npm:1.4.0"
+"optionator@npm:^0.9.1":
+  version: 0.9.3
+  resolution: "optionator@npm:0.9.3"
   dependencies:
   dependencies:
-    lcid: ^1.0.0
-  checksum: 0161a1b6b5a8492f99f4b47fe465df9fc521c55ba5414fce6444c45e2500487b8ed5b40a47a98a2363fe83ff04ab033785300ed8df717255ec4c3b625e55b1fb
+    "@aashutoshrathi/word-wrap": ^1.2.3
+    deep-is: ^0.1.3
+    fast-levenshtein: ^2.0.6
+    levn: ^0.4.1
+    prelude-ls: ^1.2.1
+    type-check: ^0.4.0
+  checksum: 09281999441f2fe9c33a5eeab76700795365a061563d66b098923eb719251a42bdbe432790d35064d0816ead9296dbeb1ad51a733edf4167c96bd5d0882e428a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"os-tmpdir@npm:^1.0.1, os-tmpdir@npm:~1.0.2":
-  version: 1.0.2
-  resolution: "os-tmpdir@npm:1.0.2"
-  checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d
+"os-browserify@npm:^0.3.0":
+  version: 0.3.0
+  resolution: "os-browserify@npm:0.3.0"
+  checksum: 16e37ba3c0e6a4c63443c7b55799ce4066d59104143cb637ecb9fce586d5da319cdca786ba1c867abbe3890d2cbf37953f2d51eea85e20dd6c4570d6c54bfebf
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -13485,12 +14618,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"p-each-series@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "p-each-series@npm:1.0.0"
-  dependencies:
-    p-reduce: ^1.0.0
-  checksum: 5acdaedd36e0c7b9617f4924dccfd681cbe4dd9f98b0eb0fde7c00dc701eeceaba55c0dc1dfde13207bdab3715a4c5040d806d7ddc493f27498110bdc1e9dd5d
+"p-each-series@npm:^2.1.0":
+  version: 2.2.0
+  resolution: "p-each-series@npm:2.2.0"
+  checksum: 5fbe2f1f1966f55833bd401fe36f7afe410707d5e9fb6032c6dde8aa716d50521c3bb201fdb584130569b5941d5e84993e09e0b3f76a474288e0ede8f632983c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -13508,16 +14639,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"p-limit@npm:^1.1.0":
-  version: 1.3.0
-  resolution: "p-limit@npm:1.3.0"
-  dependencies:
-    p-try: ^1.0.0
-  checksum: 281c1c0b8c82e1ac9f81acd72a2e35d402bf572e09721ce5520164e9de07d8274451378a3470707179ad13240535558f4b277f02405ad752e08c7d5b0d54fbfd
-  languageName: node
-  linkType: hard
-
-"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0, p-limit@npm:^2.3.0":
+"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0":
   version: 2.3.0
   resolution: "p-limit@npm:2.3.0"
   dependencies:
   version: 2.3.0
   resolution: "p-limit@npm:2.3.0"
   dependencies:
@@ -13526,12 +14648,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"p-locate@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "p-locate@npm:2.0.0"
+"p-limit@npm:^3.0.2":
+  version: 3.1.0
+  resolution: "p-limit@npm:3.1.0"
   dependencies:
   dependencies:
-    p-limit: ^1.1.0
-  checksum: e2dceb9b49b96d5513d90f715780f6f4972f46987dc32a0e18bc6c3fc74a1a5d73ec5f81b1398af5e58b99ea1ad03fd41e9181c01fa81b4af2833958696e3081
+    yocto-queue: ^0.1.0
+  checksum: 7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -13560,15 +14682,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"p-map@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "p-map@npm:3.0.0"
-  dependencies:
-    aggregate-error: ^3.0.0
-  checksum: 49b0fcbc66b1ef9cd379de1b4da07fa7a9f84b41509ea3f461c31903623aaba8a529d22f835e0d77c7cb9fcc16e4fae71e308fd40179aea514ba68f27032b5d5
-  languageName: node
-  linkType: hard
-
 "p-map@npm:^4.0.0":
   version: 4.0.0
   resolution: "p-map@npm:4.0.0"
 "p-map@npm:^4.0.0":
   version: 4.0.0
   resolution: "p-map@npm:4.0.0"
@@ -13578,13 +14691,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"p-reduce@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "p-reduce@npm:1.0.0"
-  checksum: 7b0f25c861ca2319c1fd6d28d1421edca12eb5b780b2f2bcdb418e634b4c2ef07bd85f75ad41594474ec512e5505b49c36e7b22a177d43c60cc014576eab8888
-  languageName: node
-  linkType: hard
-
 "p-retry@npm:^3.0.1":
   version: 3.0.1
   resolution: "p-retry@npm:3.0.1"
 "p-retry@npm:^3.0.1":
   version: 3.0.1
   resolution: "p-retry@npm:3.0.1"
@@ -13594,13 +14700,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"p-try@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "p-try@npm:1.0.0"
-  checksum: 3b5303f77eb7722144154288bfd96f799f8ff3e2b2b39330efe38db5dd359e4fb27012464cd85cb0a76e9b7edd1b443568cb3192c22e7cffc34989df0bafd605
-  languageName: node
-  linkType: hard
-
 "p-try@npm:^2.0.0":
   version: 2.2.0
   resolution: "p-try@npm:2.2.0"
 "p-try@npm:^2.0.0":
   version: 2.2.0
   resolution: "p-try@npm:2.2.0"
@@ -13645,7 +14744,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.5":
+"parse-asn1@npm:^5.0.0":
   version: 5.1.6
   resolution: "parse-asn1@npm:5.1.6"
   dependencies:
   version: 5.1.6
   resolution: "parse-asn1@npm:5.1.6"
   dependencies:
@@ -13658,6 +14757,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"parse-asn1@npm:^5.1.7":
+  version: 5.1.7
+  resolution: "parse-asn1@npm:5.1.7"
+  dependencies:
+    asn1.js: ^4.10.1
+    browserify-aes: ^1.2.0
+    evp_bytestokey: ^1.0.3
+    hash-base: ~3.0
+    pbkdf2: ^3.1.2
+    safe-buffer: ^5.2.1
+  checksum: 93c7194c1ed63a13e0b212d854b5213ad1aca0ace41c66b311e97cca0519cf9240f79435a0306a3b412c257f0ea3f1953fd0d9549419a0952c9e995ab361fd6c
+  languageName: node
+  linkType: hard
+
 "parse-duration@npm:0.4.4":
   version: 0.4.4
   resolution: "parse-duration@npm:0.4.4"
 "parse-duration@npm:0.4.4":
   version: 0.4.4
   resolution: "parse-duration@npm:0.4.4"
@@ -13665,15 +14778,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"parse-json@npm:^2.2.0":
-  version: 2.2.0
-  resolution: "parse-json@npm:2.2.0"
-  dependencies:
-    error-ex: ^1.2.0
-  checksum: dda78a63e57a47b713a038630868538f718a7ca0cd172a36887b0392ccf544ed0374902eb28f8bf3409e8b71d62b79d17062f8543afccf2745f9b0b2d2bb80ca
-  languageName: node
-  linkType: hard
-
 "parse-json@npm:^4.0.0":
   version: 4.0.0
   resolution: "parse-json@npm:4.0.0"
 "parse-json@npm:^4.0.0":
   version: 4.0.0
   resolution: "parse-json@npm:4.0.0"
@@ -13705,21 +14809,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"parse5@npm:4.0.0":
-  version: 4.0.0
-  resolution: "parse5@npm:4.0.0"
-  checksum: 2123cec690689fed44e6c76aa8a08215d2dadece7eff7b35156dda7485e6a232c9b737313688ee715eb0678b6a87a31026927dd74690154f8a0811059845ba46
-  languageName: node
-  linkType: hard
-
-"parse5@npm:5.1.0":
-  version: 5.1.0
-  resolution: "parse5@npm:5.1.0"
-  checksum: 13c44c6d47035a3cc75303655ae5630dc264f9b9ab8344feb3f79ca195d8b57a2a246af902abef1d780ad1eee92eb9b88cd03098a7ee7dd111f032152ebaf0a6
-  languageName: node
-  linkType: hard
-
-"parse5@npm:^6.0.1":
+"parse5@npm:6.0.1, parse5@npm:^6.0.1":
   version: 6.0.1
   resolution: "parse5@npm:6.0.1"
   checksum: 7d569a176c5460897f7c8f3377eff640d54132b9be51ae8a8fa4979af940830b2b0c296ce75e5bd8f4041520aadde13170dbdec44889975f906098ea0002f4bd
   version: 6.0.1
   resolution: "parse5@npm:6.0.1"
   checksum: 7d569a176c5460897f7c8f3377eff640d54132b9be51ae8a8fa4979af940830b2b0c296ce75e5bd8f4041520aadde13170dbdec44889975f906098ea0002f4bd
@@ -13764,15 +14854,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"path-exists@npm:^2.0.0":
-  version: 2.1.0
-  resolution: "path-exists@npm:2.1.0"
-  dependencies:
-    pinkie-promise: ^2.0.0
-  checksum: fdb734f1d00f225f7a0033ce6d73bff6a7f76ea08936abf0e5196fa6e54a645103538cd8aedcb90d6d8c3fa3705ded0c58a4da5948ae92aa8834892c1ab44a84
-  languageName: node
-  linkType: hard
-
 "path-exists@npm:^3.0.0":
   version: 3.0.0
   resolution: "path-exists@npm:3.0.0"
 "path-exists@npm:^3.0.0":
   version: 3.0.0
   resolution: "path-exists@npm:3.0.0"
@@ -13787,7 +14868,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"path-is-absolute@npm:^1.0.0, path-is-absolute@npm:^1.0.1":
+"path-is-absolute@npm:^1.0.0":
   version: 1.0.1
   resolution: "path-is-absolute@npm:1.0.1"
   checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8
   version: 1.0.1
   resolution: "path-is-absolute@npm:1.0.1"
   checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8
@@ -13814,56 +14895,27 @@ __metadata:
   checksum: 55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020
   languageName: node
   linkType: hard
   checksum: 55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020
   languageName: node
   linkType: hard
-
-"path-parse@npm:^1.0.6":
-  version: 1.0.7
-  resolution: "path-parse@npm:1.0.7"
-  checksum: 49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a
-  languageName: node
-  linkType: hard
-
-"path-to-regexp@npm:0.1.7":
-  version: 0.1.7
-  resolution: "path-to-regexp@npm:0.1.7"
-  checksum: 69a14ea24db543e8b0f4353305c5eac6907917031340e5a8b37df688e52accd09e3cebfe1660b70d76b6bd89152f52183f28c74813dbf454ba1a01c82a38abce
-  languageName: node
-  linkType: hard
-
-"path-to-regexp@npm:^1.7.0":
-  version: 1.8.0
-  resolution: "path-to-regexp@npm:1.8.0"
-  dependencies:
-    isarray: 0.0.1
-  checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd
-  languageName: node
-  linkType: hard
-
-"path-type@npm:^1.0.0":
-  version: 1.1.0
-  resolution: "path-type@npm:1.1.0"
-  dependencies:
-    graceful-fs: ^4.1.2
-    pify: ^2.0.0
-    pinkie-promise: ^2.0.0
-  checksum: 59a4b2c0e566baf4db3021a1ed4ec09a8b36fca960a490b54a6bcefdb9987dafe772852982b6011cd09579478a96e57960a01f75fa78a794192853c9d468fc79
+
+"path-parse@npm:^1.0.6, path-parse@npm:^1.0.7":
+  version: 1.0.7
+  resolution: "path-parse@npm:1.0.7"
+  checksum: 49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"path-type@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "path-type@npm:2.0.0"
-  dependencies:
-    pify: ^2.0.0
-  checksum: 749dc0c32d4ebe409da155a0022f9be3d08e6fd276adb3dfa27cb2486519ab2aa277d1453b3fde050831e0787e07b0885a75653fefcc82d883753c5b91121b1c
+"path-to-regexp@npm:0.1.7":
+  version: 0.1.7
+  resolution: "path-to-regexp@npm:0.1.7"
+  checksum: 69a14ea24db543e8b0f4353305c5eac6907917031340e5a8b37df688e52accd09e3cebfe1660b70d76b6bd89152f52183f28c74813dbf454ba1a01c82a38abce
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"path-type@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "path-type@npm:3.0.0"
+"path-to-regexp@npm:^1.7.0":
+  version: 1.8.0
+  resolution: "path-to-regexp@npm:1.8.0"
   dependencies:
   dependencies:
-    pify: ^3.0.0
-  checksum: 735b35e256bad181f38fa021033b1c33cfbe62ead42bb2222b56c210e42938eecb272ae1949f3b6db4ac39597a61b44edd8384623ec4d79bfdc9a9c0f12537a6
+    isarray: 0.0.1
+  checksum: 709f6f083c0552514ef4780cb2e7e4cf49b0cc89a97439f2b7cc69a608982b7690fb5d1720a7473a59806508fc2dae0be751ba49f495ecf89fd8fbc62abccbcd
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -13874,7 +14926,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pbkdf2@npm:^3.0.3":
+"pbkdf2@npm:^3.0.3, pbkdf2@npm:^3.1.2":
   version: 3.1.2
   resolution: "pbkdf2@npm:3.1.2"
   dependencies:
   version: 3.1.2
   resolution: "pbkdf2@npm:3.1.2"
   dependencies:
@@ -13922,7 +14974,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"picomatch@npm:^2.3.1":
+"picomatch@npm:^2.2.2, picomatch@npm:^2.3.1":
   version: 2.3.1
   resolution: "picomatch@npm:2.3.1"
   checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf
   version: 2.3.1
   resolution: "picomatch@npm:2.3.1"
   checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf
@@ -13936,13 +14988,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pify@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "pify@npm:3.0.0"
-  checksum: 6cdcbc3567d5c412450c53261a3f10991665d660961e06605decf4544a61a97a54fefe70a68d5c37080ff9d6f4cf51444c90198d1ba9f9309a6c0d6e9f5c4fde
-  languageName: node
-  linkType: hard
-
 "pify@npm:^4.0.1":
   version: 4.0.1
   resolution: "pify@npm:4.0.1"
 "pify@npm:^4.0.1":
   version: 4.0.1
   resolution: "pify@npm:4.0.1"
@@ -13975,24 +15020,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pkg-dir@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "pkg-dir@npm:1.0.0"
-  dependencies:
-    find-up: ^1.0.0
-  checksum: ce49878797dd81a5cee1cb7f05fdd431729309e4854c9f83d7748491b9d25c5f8ef04b3b7658134361fa036934c0aaa7fc7f984e46970dd227aa490f3869d36a
-  languageName: node
-  linkType: hard
-
-"pkg-dir@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "pkg-dir@npm:2.0.0"
-  dependencies:
-    find-up: ^2.1.0
-  checksum: 8c72b712305b51e1108f0ffda5ec1525a8307e54a5855db8fb1dcf77561a5ae98e2ba3b4814c9806a679f76b2f7e5dd98bde18d07e594ddd9fdd25e9cf242ea1
-  languageName: node
-  linkType: hard
-
 "pkg-dir@npm:^3.0.0":
   version: 3.0.0
   resolution: "pkg-dir@npm:3.0.0"
 "pkg-dir@npm:^3.0.0":
   version: 3.0.0
   resolution: "pkg-dir@npm:3.0.0"
@@ -14002,7 +15029,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pkg-dir@npm:^4.1.0":
+"pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0":
   version: 4.2.0
   resolution: "pkg-dir@npm:4.2.0"
   dependencies:
   version: 4.2.0
   resolution: "pkg-dir@npm:4.2.0"
   dependencies:
@@ -14011,7 +15038,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pkg-up@npm:3.1.0, pkg-up@npm:^3.1.0":
+"pkg-up@npm:3.1.0":
   version: 3.1.0
   resolution: "pkg-up@npm:3.1.0"
   dependencies:
   version: 3.1.0
   resolution: "pkg-up@npm:3.1.0"
   dependencies:
@@ -14020,13 +15047,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pn@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "pn@npm:1.1.0"
-  checksum: e4654186dc92a187c8c7fe4ccda902f4d39dd9c10f98d1c5a08ce5fad5507ef1e33ddb091240c3950bee81bd201b4c55098604c433a33b5e8bdd97f38b732fa0
-  languageName: node
-  linkType: hard
-
 "pnp-webpack-plugin@npm:1.6.4":
   version: 1.6.4
   resolution: "pnp-webpack-plugin@npm:1.6.4"
 "pnp-webpack-plugin@npm:1.6.4":
   version: 1.6.4
   resolution: "pnp-webpack-plugin@npm:1.6.4"
@@ -14061,6 +15081,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"possible-typed-array-names@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "possible-typed-array-names@npm:1.0.0"
+  checksum: b32d403ece71e042385cc7856385cecf1cd8e144fa74d2f1de40d1e16035dba097bc189715925e79b67bdd1472796ff168d3a90d296356c9c94d272d5b95f3ae
+  languageName: node
+  linkType: hard
+
 "postcss-attribute-case-insensitive@npm:^4.0.1":
   version: 4.0.2
   resolution: "postcss-attribute-case-insensitive@npm:4.0.2"
 "postcss-attribute-case-insensitive@npm:^4.0.1":
   version: 4.0.2
   resolution: "postcss-attribute-case-insensitive@npm:4.0.2"
@@ -14274,12 +15301,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"postcss-flexbugs-fixes@npm:4.1.0":
-  version: 4.1.0
-  resolution: "postcss-flexbugs-fixes@npm:4.1.0"
+"postcss-flexbugs-fixes@npm:4.2.1":
+  version: 4.2.1
+  resolution: "postcss-flexbugs-fixes@npm:4.2.1"
   dependencies:
   dependencies:
-    postcss: ^7.0.0
-  checksum: b5f2c39f4315a0eacfc23cafe6d20cff36e4605d266aa38f261e1db7f65e913e5fe3044d952d9435850f67525d5b1c7cc22eb6edeb51e19657c7a9a53b361dc5
+    postcss: ^7.0.26
+  checksum: 51a626bc80dbe42fcc8b0895b4f23a558bb809ec52cdc05aa27fb24cdffd4c9dc53f25218085ddf407c53d76573bc6d7568219c912161609f02532a8f5f59b43
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -14472,7 +15499,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"postcss-modules-local-by-default@npm:^3.0.2":
+"postcss-modules-local-by-default@npm:^3.0.3":
   version: 3.0.3
   resolution: "postcss-modules-local-by-default@npm:3.0.3"
   dependencies:
   version: 3.0.3
   resolution: "postcss-modules-local-by-default@npm:3.0.3"
   dependencies:
@@ -14484,7 +15511,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"postcss-modules-scope@npm:^2.1.1":
+"postcss-modules-scope@npm:^2.2.0":
   version: 2.2.0
   resolution: "postcss-modules-scope@npm:2.2.0"
   dependencies:
   version: 2.2.0
   resolution: "postcss-modules-scope@npm:2.2.0"
   dependencies:
@@ -14752,12 +15779,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"postcss-safe-parser@npm:4.0.1":
-  version: 4.0.1
-  resolution: "postcss-safe-parser@npm:4.0.1"
+"postcss-safe-parser@npm:5.0.2":
+  version: 5.0.2
+  resolution: "postcss-safe-parser@npm:5.0.2"
   dependencies:
   dependencies:
-    postcss: ^7.0.0
-  checksum: e4db1e5153521cfa77c046ea5c2600605339148c1ed039c61e8acea37e74ceea245f4ec4047bcea7782a34866a9c4b1321981c35374f211c292e8648e2ac4e33
+    postcss: ^8.1.0
+  checksum: b786eca091f856f2d31856d903c24c1b591ecbc0b607af0824e1cf12b9b254b5e1f24bc842cc2b95bc561f097d8b358fb4c9e04c73c1ba9c118d21bde9a83253
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -14870,14 +15897,14 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"postcss@npm:7.0.21":
-  version: 7.0.21
-  resolution: "postcss@npm:7.0.21"
+"postcss@npm:7.0.36":
+  version: 7.0.36
+  resolution: "postcss@npm:7.0.36"
   dependencies:
     chalk: ^2.4.2
     source-map: ^0.6.1
     supports-color: ^6.1.0
   dependencies:
     chalk: ^2.4.2
     source-map: ^0.6.1
     supports-color: ^6.1.0
-  checksum: 5c11d58a4ffd54ddaf2f2f18ef7be10fc44405559ee56b52e41db8305d1b184d162138994dcce506ab77eef7283887a72d1b81cd1036c7fee106f50af0ef86d3
+  checksum: 4cfc0989b9ad5d0e8971af80d87f9c5beac5c84cb89ff22ad69852edf73c0a2fa348e7e0a135b5897bf893edad0fe86c428769050431ad9b532f072ff530828d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -14892,7 +15919,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"postcss@npm:^7, postcss@npm:^7.0.0, postcss@npm:^7.0.1, postcss@npm:^7.0.14, postcss@npm:^7.0.17, postcss@npm:^7.0.2, postcss@npm:^7.0.23, postcss@npm:^7.0.27, postcss@npm:^7.0.32, postcss@npm:^7.0.5, postcss@npm:^7.0.6":
+"postcss@npm:^7, postcss@npm:^7.0.0, postcss@npm:^7.0.1, postcss@npm:^7.0.14, postcss@npm:^7.0.17, postcss@npm:^7.0.2, postcss@npm:^7.0.26, postcss@npm:^7.0.27, postcss@npm:^7.0.32, postcss@npm:^7.0.5, postcss@npm:^7.0.6":
   version: 7.0.39
   resolution: "postcss@npm:7.0.39"
   dependencies:
   version: 7.0.39
   resolution: "postcss@npm:7.0.39"
   dependencies:
@@ -14902,6 +15929,24 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"postcss@npm:^8.1.0":
+  version: 8.4.38
+  resolution: "postcss@npm:8.4.38"
+  dependencies:
+    nanoid: ^3.3.7
+    picocolors: ^1.0.0
+    source-map-js: ^1.2.0
+  checksum: 649f9e60a763ca4b5a7bbec446a069edf07f057f6d780a5a0070576b841538d1ecf7dd888f2fbfd1f76200e26c969e405aeeae66332e6927dbdc8bdcb90b9451
+  languageName: node
+  linkType: hard
+
+"prelude-ls@npm:^1.2.1":
+  version: 1.2.1
+  resolution: "prelude-ls@npm:1.2.1"
+  checksum: cd192ec0d0a8e4c6da3bb80e4f62afe336df3f76271ac6deb0e6a36187133b6073a19e9727a1ff108cd8b9982e4768850d413baa71214dd80c7979617dca827a
+  languageName: node
+  linkType: hard
+
 "prelude-ls@npm:~1.1.2":
   version: 1.1.2
   resolution: "prelude-ls@npm:1.1.2"
 "prelude-ls@npm:~1.1.2":
   version: 1.1.2
   resolution: "prelude-ls@npm:1.1.2"
@@ -14916,7 +15961,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pretty-bytes@npm:^5.1.0, pretty-bytes@npm:^5.6.0":
+"pretty-bytes@npm:^5.3.0, pretty-bytes@npm:^5.6.0":
   version: 5.6.0
   resolution: "pretty-bytes@npm:5.6.0"
   checksum: 9c082500d1e93434b5b291bd651662936b8bd6204ec9fa17d563116a192d6d86b98f6d328526b4e8d783c07d5499e2614a807520249692da9ec81564b2f439cd
   version: 5.6.0
   resolution: "pretty-bytes@npm:5.6.0"
   checksum: 9c082500d1e93434b5b291bd651662936b8bd6204ec9fa17d563116a192d6d86b98f6d328526b4e8d783c07d5499e2614a807520249692da9ec81564b2f439cd
@@ -14933,19 +15978,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"pretty-format@npm:^24.9.0":
-  version: 24.9.0
-  resolution: "pretty-format@npm:24.9.0"
-  dependencies:
-    "@jest/types": ^24.9.0
-    ansi-regex: ^4.0.0
-    ansi-styles: ^3.2.0
-    react-is: ^16.8.4
-  checksum: ba9291c8dafd50d2fea1fbad5d2863a6f94e0c8835cce9778ec03bc11bb0f52b9ed0e4ee56aaa331d022ccae2fe52b92f73465a0af58fd0edb59deb6391c6847
-  languageName: node
-  linkType: hard
-
-"pretty-format@npm:^26.0.0, pretty-format@npm:^26.6.2":
+"pretty-format@npm:^26.0.0, pretty-format@npm:^26.6.0, pretty-format@npm:^26.6.2":
   version: 26.6.2
   resolution: "pretty-format@npm:26.6.2"
   dependencies:
   version: 26.6.2
   resolution: "pretty-format@npm:26.6.2"
   dependencies:
@@ -14957,13 +15990,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"private@npm:^0.1.8":
-  version: 0.1.8
-  resolution: "private@npm:0.1.8"
-  checksum: a00abd713d25389f6de7294f0e7879b8a5d09a9ec5fd81cc2f21b29d4f9a80ec53bc4222927d3a281d4aadd4cd373d9a28726fca3935921950dc75fd71d1fdbb
-  languageName: node
-  linkType: hard
-
 "process-nextick-args@npm:~2.0.0":
   version: 2.0.1
   resolution: "process-nextick-args@npm:2.0.1"
 "process-nextick-args@npm:~2.0.0":
   version: 2.0.1
   resolution: "process-nextick-args@npm:2.0.1"
@@ -15011,12 +16037,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"promise@npm:^8.0.3":
-  version: 8.1.0
-  resolution: "promise@npm:8.1.0"
+"promise@npm:^8.1.0":
+  version: 8.3.0
+  resolution: "promise@npm:8.3.0"
   dependencies:
     asap: ~2.0.6
   dependencies:
     asap: ~2.0.6
-  checksum: 89b71a56154ed7d66a73236d8e8351a9c59adddba3929ecc845f75421ff37fc08ea0c67ad76cd5c0b0d81812c7d07a32bed27e7df5fcc960c6d68b0c1cd771f7
+  checksum: a69f0ddbddf78ffc529cffee7ad950d307347615970564b17988ce43fbe767af5c738a9439660b24a9a8cbea106c0dcbb6c2b20e23b7e96a8e89e5c2679e94d5
+  languageName: node
+  linkType: hard
+
+"prompts@npm:2.4.0":
+  version: 2.4.0
+  resolution: "prompts@npm:2.4.0"
+  dependencies:
+    kleur: ^3.0.3
+    sisteransi: ^1.0.5
+  checksum: 96c7bef8eb3c0bb2076d2bc5ee473f06e6d8ac01ac4d0f378dfeb0ddaf2f31c339360ec8f0f8486f78601d16ebef7c6bd9886d44b937ba01bab568b937190265
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -15052,7 +16088,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"prop-types@npm:^15.5.6":
+"prop-types@npm:^15.5.6, prop-types@npm:^15.8.1":
   version: 15.8.1
   resolution: "prop-types@npm:15.8.1"
   dependencies:
   version: 15.8.1
   resolution: "prop-types@npm:15.8.1"
   dependencies:
@@ -15063,7 +16099,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"proxy-addr@npm:~2.0.5":
+"proxy-addr@npm:~2.0.7":
   version: 2.0.7
   resolution: "proxy-addr@npm:2.0.7"
   dependencies:
   version: 2.0.7
   resolution: "proxy-addr@npm:2.0.7"
   dependencies:
@@ -15080,6 +16116,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"proxy-from-env@npm:^1.1.0":
+  version: 1.1.0
+  resolution: "proxy-from-env@npm:1.1.0"
+  checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4
+  languageName: node
+  linkType: hard
+
 "prr@npm:~1.0.1":
   version: 1.0.1
   resolution: "prr@npm:1.0.1"
 "prr@npm:~1.0.1":
   version: 1.0.1
   resolution: "prr@npm:1.0.1"
@@ -15183,10 +16226,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"qs@npm:6.7.0":
-  version: 6.7.0
-  resolution: "qs@npm:6.7.0"
-  checksum: dfd5f6adef50e36e908cfa70a6233871b5afe66fbaca37ecc1da352ba29eb2151a3797991948f158bb37fccde51bd57845cb619a8035287bfc24e4591172c347
+"qs@npm:6.11.0":
+  version: 6.11.0
+  resolution: "qs@npm:6.11.0"
+  dependencies:
+    side-channel: ^1.0.4
+  checksum: 6e1f29dd5385f7488ec74ac7b6c92f4d09a90408882d0c208414a34dd33badc1a621019d4c799a3df15ab9b1d0292f97c1dd71dc7c045e69f81a8064e5af7297
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -15232,6 +16277,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"querystring@npm:^0.2.0":
+  version: 0.2.1
+  resolution: "querystring@npm:0.2.1"
+  checksum: 7b83b45d641e75fd39cd6625ddfd44e7618e741c61e95281b57bbae8fde0afcc12cf851924559e5cc1ef9baa3b1e06e22b164ea1397d65dd94b801f678d9c8ce
+  languageName: node
+  linkType: hard
+
 "querystringify@npm:^2.1.1":
   version: 2.2.0
   resolution: "querystringify@npm:2.2.0"
 "querystringify@npm:^2.1.1":
   version: 2.2.0
   resolution: "querystringify@npm:2.2.0"
@@ -15305,29 +16357,29 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"raw-body@npm:2.4.0":
-  version: 2.4.0
-  resolution: "raw-body@npm:2.4.0"
+"raw-body@npm:2.5.2":
+  version: 2.5.2
+  resolution: "raw-body@npm:2.5.2"
   dependencies:
   dependencies:
-    bytes: 3.1.0
-    http-errors: 1.7.2
+    bytes: 3.1.2
+    http-errors: 2.0.0
     iconv-lite: 0.4.24
     unpipe: 1.0.0
     iconv-lite: 0.4.24
     unpipe: 1.0.0
-  checksum: 6343906939e018c6e633a34a938a5d6d1e93ffcfa48646e00207d53b418e941953b521473950c079347220944dc75ba10e7b3c08bf97e3ac72c7624882db09bb
+  checksum: ba1583c8d8a48e8fbb7a873fdbb2df66ea4ff83775421bfe21ee120140949ab048200668c47d9ae3880012f6e217052690628cf679ddfbd82c9fc9358d574676
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"react-app-polyfill@npm:^1.0.6":
-  version: 1.0.6
-  resolution: "react-app-polyfill@npm:1.0.6"
+"react-app-polyfill@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "react-app-polyfill@npm:2.0.0"
   dependencies:
   dependencies:
-    core-js: ^3.5.0
+    core-js: ^3.6.5
     object-assign: ^4.1.1
     object-assign: ^4.1.1
-    promise: ^8.0.3
+    promise: ^8.1.0
     raf: ^3.4.1
     raf: ^3.4.1
-    regenerator-runtime: ^0.13.3
-    whatwg-fetch: ^3.0.0
-  checksum: d38fb0e5f773eb618e39832e78e34b2382a33a2f633ecbc7aba3af819134938f25f4b6915f40dcbb46efa1096efdfabe44030165142000dcf522f564db7cb3b9
+    regenerator-runtime: ^0.13.7
+    whatwg-fetch: ^3.4.1
+  checksum: 99e52a6b2229c7ca730cfd44ac95640f955be71d144225bd6c24fa47922a742658a371d0a2f0876d732533f1055b7cd7e9d534c89c29f8ca889ecd1b8d15f065
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -15343,35 +16395,35 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"react-dev-utils@npm:^10.2.1":
-  version: 10.2.1
-  resolution: "react-dev-utils@npm:10.2.1"
+"react-dev-utils@npm:^11.0.1":
+  version: 11.0.4
+  resolution: "react-dev-utils@npm:11.0.4"
   dependencies:
   dependencies:
-    "@babel/code-frame": 7.8.3
+    "@babel/code-frame": 7.10.4
     address: 1.1.2
     address: 1.1.2
-    browserslist: 4.10.0
+    browserslist: 4.14.2
     chalk: 2.4.2
     chalk: 2.4.2
-    cross-spawn: 7.0.1
+    cross-spawn: 7.0.3
     detect-port-alt: 1.1.6
     escape-string-regexp: 2.0.0
     detect-port-alt: 1.1.6
     escape-string-regexp: 2.0.0
-    filesize: 6.0.1
+    filesize: 6.1.0
     find-up: 4.1.0
     find-up: 4.1.0
-    fork-ts-checker-webpack-plugin: 3.1.1
+    fork-ts-checker-webpack-plugin: 4.1.6
     global-modules: 2.0.0
     global-modules: 2.0.0
-    globby: 8.0.2
+    globby: 11.0.1
     gzip-size: 5.1.1
     gzip-size: 5.1.1
-    immer: 1.10.0
-    inquirer: 7.0.4
+    immer: 8.0.1
     is-root: 2.1.0
     is-root: 2.1.0
-    loader-utils: 1.2.3
+    loader-utils: 2.0.0
     open: ^7.0.2
     pkg-up: 3.1.0
     open: ^7.0.2
     pkg-up: 3.1.0
-    react-error-overlay: ^6.0.7
+    prompts: 2.4.0
+    react-error-overlay: ^6.0.9
     recursive-readdir: 2.2.2
     shell-quote: 1.7.2
     strip-ansi: 6.0.0
     text-table: 0.2.0
     recursive-readdir: 2.2.2
     shell-quote: 1.7.2
     strip-ansi: 6.0.0
     text-table: 0.2.0
-  checksum: af58950075c69d5b179b5d527d59fe7072b18258042c412665a4e7425b796a4af24456e05b93ff837bdeec84746cd7d9ed9dce2119a8d57139b8ff71a6053dfc
+  checksum: b41c95010a4fb60d4ea6309423520e6268757b68df34de7e9e8dbc72549236a1f5a698ff99ad72a034ac51b042aa79ee53994330ce4df05bf867e63c5464bb3f
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -15429,10 +16481,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"react-error-overlay@npm:^6.0.7":
-  version: 6.0.9
-  resolution: "react-error-overlay@npm:6.0.9"
-  checksum: 695853bc885e798008a00c10d8d94e5ac91626e8130802fea37345f9c037f41b80104345db2ee87f225feb4a4ef71b0df572b17c378a6d397b6815f6d4a84293
+"react-error-overlay@npm:^6.0.9":
+  version: 6.0.11
+  resolution: "react-error-overlay@npm:6.0.11"
+  checksum: ce7b44c38fadba9cedd7c095cf39192e632daeccf1d0747292ed524f17dcb056d16bc197ddee5723f9dd888f0b9b19c3b486c430319e30504289b9296f2d2c42
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -15473,7 +16525,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"react-is@npm:^16.13.1, react-is@npm:^16.6.3, react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.4, react-is@npm:^16.8.6":
+"react-is@npm:^16.13.1, react-is@npm:^16.6.3, react-is@npm:^16.7.0, react-is@npm:^16.8.1, react-is@npm:^16.8.6":
   version: 16.13.1
   resolution: "react-is@npm:16.13.1"
   checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f
   version: 16.13.1
   resolution: "react-is@npm:16.13.1"
   checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f
@@ -15531,6 +16583,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"react-refresh@npm:^0.8.3":
+  version: 0.8.3
+  resolution: "react-refresh@npm:0.8.3"
+  checksum: 3cffe5a9cbac1c5d59bf74bf9fff43c987d87ef32098b9092ea94b6637377d86c08565b9374d9397f446b3fbcd95de986ec77220a16f979687cb39b7b89e2f91
+  languageName: node
+  linkType: hard
+
 "react-router-dom@npm:4.3.1":
   version: 4.3.1
   resolution: "react-router-dom@npm:4.3.1"
 "react-router-dom@npm:4.3.1":
   version: 4.3.1
   resolution: "react-router-dom@npm:4.3.1"
@@ -15598,63 +16657,69 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"react-scripts@npm:3.4.4":
-  version: 3.4.4
-  resolution: "react-scripts@npm:3.4.4"
-  dependencies:
-    "@babel/core": 7.9.0
-    "@svgr/webpack": 4.3.3
-    "@typescript-eslint/eslint-plugin": ^2.10.0
-    "@typescript-eslint/parser": ^2.10.0
-    babel-eslint: 10.1.0
-    babel-jest: ^24.9.0
+"react-scripts@npm:4.0.1":
+  version: 4.0.1
+  resolution: "react-scripts@npm:4.0.1"
+  dependencies:
+    "@babel/core": 7.12.3
+    "@pmmmwh/react-refresh-webpack-plugin": 0.4.2
+    "@svgr/webpack": 5.4.0
+    "@typescript-eslint/eslint-plugin": ^4.5.0
+    "@typescript-eslint/parser": ^4.5.0
+    babel-eslint: ^10.1.0
+    babel-jest: ^26.6.0
     babel-loader: 8.1.0
     babel-loader: 8.1.0
-    babel-plugin-named-asset-import: ^0.3.6
-    babel-preset-react-app: ^9.1.2
-    camelcase: ^5.3.1
+    babel-plugin-named-asset-import: ^0.3.7
+    babel-preset-react-app: ^10.0.0
+    bfj: ^7.0.2
+    camelcase: ^6.1.0
     case-sensitive-paths-webpack-plugin: 2.3.0
     case-sensitive-paths-webpack-plugin: 2.3.0
-    css-loader: 3.4.2
+    css-loader: 4.3.0
     dotenv: 8.2.0
     dotenv-expand: 5.1.0
     dotenv: 8.2.0
     dotenv-expand: 5.1.0
-    eslint: ^6.6.0
-    eslint-config-react-app: ^5.2.1
-    eslint-loader: 3.0.3
-    eslint-plugin-flowtype: 4.6.0
-    eslint-plugin-import: 2.20.1
-    eslint-plugin-jsx-a11y: 6.2.3
-    eslint-plugin-react: 7.19.0
-    eslint-plugin-react-hooks: ^1.6.1
-    file-loader: 4.3.0
-    fs-extra: ^8.1.0
-    fsevents: 2.1.2
-    html-webpack-plugin: 4.0.0-beta.11
+    eslint: ^7.11.0
+    eslint-config-react-app: ^6.0.0
+    eslint-plugin-flowtype: ^5.2.0
+    eslint-plugin-import: ^2.22.1
+    eslint-plugin-jest: ^24.1.0
+    eslint-plugin-jsx-a11y: ^6.3.1
+    eslint-plugin-react: ^7.21.5
+    eslint-plugin-react-hooks: ^4.2.0
+    eslint-plugin-testing-library: ^3.9.2
+    eslint-webpack-plugin: ^2.1.0
+    file-loader: 6.1.1
+    fs-extra: ^9.0.1
+    fsevents: ^2.1.3
+    html-webpack-plugin: 4.5.0
     identity-obj-proxy: 3.0.0
     identity-obj-proxy: 3.0.0
-    jest: 24.9.0
-    jest-environment-jsdom-fourteen: 1.0.1
-    jest-resolve: 24.9.0
-    jest-watch-typeahead: 0.4.2
-    mini-css-extract-plugin: 0.9.0
-    optimize-css-assets-webpack-plugin: 5.0.3
+    jest: 26.6.0
+    jest-circus: 26.6.0
+    jest-resolve: 26.6.0
+    jest-watch-typeahead: 0.6.1
+    mini-css-extract-plugin: 0.11.3
+    optimize-css-assets-webpack-plugin: 5.0.4
     pnp-webpack-plugin: 1.6.4
     pnp-webpack-plugin: 1.6.4
-    postcss-flexbugs-fixes: 4.1.0
+    postcss-flexbugs-fixes: 4.2.1
     postcss-loader: 3.0.0
     postcss-normalize: 8.0.1
     postcss-preset-env: 6.7.0
     postcss-loader: 3.0.0
     postcss-normalize: 8.0.1
     postcss-preset-env: 6.7.0
-    postcss-safe-parser: 4.0.1
-    react-app-polyfill: ^1.0.6
-    react-dev-utils: ^10.2.1
-    resolve: 1.15.0
-    resolve-url-loader: 3.1.2
+    postcss-safe-parser: 5.0.2
+    prompts: 2.4.0
+    react-app-polyfill: ^2.0.0
+    react-dev-utils: ^11.0.1
+    react-refresh: ^0.8.3
+    resolve: 1.18.1
+    resolve-url-loader: ^3.1.2
     sass-loader: 8.0.2
     sass-loader: 8.0.2
-    semver: 6.3.0
-    style-loader: 0.23.1
-    terser-webpack-plugin: 2.3.8
-    ts-pnp: 1.1.6
-    url-loader: 2.3.0
-    webpack: 4.42.0
+    semver: 7.3.2
+    style-loader: 1.3.0
+    terser-webpack-plugin: 4.2.3
+    ts-pnp: 1.2.0
+    url-loader: 4.1.1
+    webpack: 4.44.2
     webpack-dev-server: 3.11.0
     webpack-manifest-plugin: 2.2.0
     webpack-dev-server: 3.11.0
     webpack-manifest-plugin: 2.2.0
-    workbox-webpack-plugin: 4.3.1
+    workbox-webpack-plugin: 5.1.4
   peerDependencies:
     typescript: ^3.2.1
   dependenciesMeta:
   peerDependencies:
     typescript: ^3.2.1
   dependenciesMeta:
@@ -15664,8 +16729,8 @@ __metadata:
     typescript:
       optional: true
   bin:
     typescript:
       optional: true
   bin:
-    react-scripts: bin/react-scripts.js
-  checksum: a3ea2dfbecb595c471b23153c86fcd9da09093576f90646ca79e2653baaf209c1c5b73eeb4ed02f591e91e2505a6b963ec4de7f4ef76f2e52c73839d735dc680
+    react-scripts: ./bin/react-scripts.js
+  checksum: 5f3d284c63c3649f175daa72f40be43cd33f539370225c395b31a3fc812d5cea26135a7796760a0f0701489e6212739c72b87e01ede716c815016f1295b20aaa
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -15768,36 +16833,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"read-pkg-up@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "read-pkg-up@npm:1.0.1"
-  dependencies:
-    find-up: ^1.0.0
-    read-pkg: ^1.0.0
-  checksum: d18399a0f46e2da32beb2f041edd0cda49d2f2cc30195a05c759ef3ed9b5e6e19ba1ad1bae2362bdec8c6a9f2c3d18f4d5e8c369e808b03d498d5781cb9122c7
-  languageName: node
-  linkType: hard
-
-"read-pkg-up@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "read-pkg-up@npm:2.0.0"
-  dependencies:
-    find-up: ^2.0.0
-    read-pkg: ^2.0.0
-  checksum: 22f9026fb72219ecd165f94f589461c70a88461dc7ea0d439a310ef2a5271ff176a4df4e5edfad087d8ac89b8553945eb209476b671e8ed081c990f30fc40b27
-  languageName: node
-  linkType: hard
-
-"read-pkg-up@npm:^4.0.0":
-  version: 4.0.0
-  resolution: "read-pkg-up@npm:4.0.0"
-  dependencies:
-    find-up: ^3.0.0
-    read-pkg: ^3.0.0
-  checksum: dd867d9a912707bc11340aebc91780be9f36f34ee1d27a5dafb8520e0cb6344138b80eb8bf8325bebf519d26ecf14cbf6190d9e5f765f0120da5ede4013f4d13
-  languageName: node
-  linkType: hard
-
 "read-pkg-up@npm:^7.0.1":
   version: 7.0.1
   resolution: "read-pkg-up@npm:7.0.1"
 "read-pkg-up@npm:^7.0.1":
   version: 7.0.1
   resolution: "read-pkg-up@npm:7.0.1"
@@ -15809,39 +16844,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"read-pkg@npm:^1.0.0":
-  version: 1.1.0
-  resolution: "read-pkg@npm:1.1.0"
-  dependencies:
-    load-json-file: ^1.0.0
-    normalize-package-data: ^2.3.2
-    path-type: ^1.0.0
-  checksum: a0f5d5e32227ec8e6a028dd5c5134eab229768dcb7a5d9a41a284ed28ad4b9284fecc47383dc1593b5694f4de603a7ffaee84b738956b9b77e0999567485a366
-  languageName: node
-  linkType: hard
-
-"read-pkg@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "read-pkg@npm:2.0.0"
-  dependencies:
-    load-json-file: ^2.0.0
-    normalize-package-data: ^2.3.2
-    path-type: ^2.0.0
-  checksum: 85c5bf35f2d96acdd756151ba83251831bb2b1040b7d96adce70b2cb119b5320417f34876de0929f2d06c67f3df33ef4636427df3533913876f9ef2487a6f48f
-  languageName: node
-  linkType: hard
-
-"read-pkg@npm:^3.0.0":
-  version: 3.0.0
-  resolution: "read-pkg@npm:3.0.0"
-  dependencies:
-    load-json-file: ^4.0.0
-    normalize-package-data: ^2.3.2
-    path-type: ^3.0.0
-  checksum: 398903ebae6c7e9965419a1062924436cc0b6f516c42c4679a90290d2f87448ed8f977e7aa2dbba4aa1ac09248628c43e493ac25b2bc76640e946035200e34c6
-  languageName: node
-  linkType: hard
-
 "read-pkg@npm:^5.2.0":
   version: 5.2.0
   resolution: "read-pkg@npm:5.2.0"
 "read-pkg@npm:^5.2.0":
   version: 5.2.0
   resolution: "read-pkg@npm:5.2.0"
@@ -15869,6 +16871,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"readable-stream@npm:^2.3.8":
+  version: 2.3.8
+  resolution: "readable-stream@npm:2.3.8"
+  dependencies:
+    core-util-is: ~1.0.0
+    inherits: ~2.0.3
+    isarray: ~1.0.0
+    process-nextick-args: ~2.0.0
+    safe-buffer: ~5.1.1
+    string_decoder: ~1.1.1
+    util-deprecate: ~1.0.1
+  checksum: 65645467038704f0c8aaf026a72fbb588a9e2ef7a75cd57a01702ee9db1c4a1e4b03aaad36861a6a0926546a74d174149c8c207527963e0c2d3eee2f37678a42
+  languageName: node
+  linkType: hard
+
 "readable-stream@npm:^3.0.6, readable-stream@npm:^3.6.0":
   version: 3.6.0
   resolution: "readable-stream@npm:3.6.0"
 "readable-stream@npm:^3.0.6, readable-stream@npm:^3.6.0":
   version: 3.6.0
   resolution: "readable-stream@npm:3.6.0"
@@ -15900,15 +16917,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"realpath-native@npm:^1.1.0":
-  version: 1.1.0
-  resolution: "realpath-native@npm:1.1.0"
-  dependencies:
-    util.promisify: ^1.0.0
-  checksum: 75ef0595dea6186384b785a9e0993c58ec604f8be2e39b602fec6d7837c7f770af4a4eb3c81f864a7d81c518a7167a6eaabbc7695b7a88c56e1ef04b91c1d586
-  languageName: node
-  linkType: hard
-
 "recompose@npm:0.28.0 - 0.30.0":
   version: 0.30.0
   resolution: "recompose@npm:0.30.0"
 "recompose@npm:0.28.0 - 0.30.0":
   version: 0.30.0
   resolution: "recompose@npm:0.30.0"
@@ -15966,16 +16974,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"redent@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "redent@npm:1.0.0"
-  dependencies:
-    indent-string: ^2.1.0
-    strip-indent: ^1.0.1
-  checksum: 2bb8f76fda9c9f44e26620047b0ba9dd1834b0a80309d0badcc23fdcf7bb27a7ca74e66b683baa0d4b8cb5db787f11be086504036d63447976f409dd3e73fd7d
-  languageName: node
-  linkType: hard
-
 "redent@npm:^3.0.0":
   version: 3.0.0
   resolution: "redent@npm:3.0.0"
 "redent@npm:^3.0.0":
   version: 3.0.0
   resolution: "redent@npm:3.0.0"
@@ -16089,6 +17087,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"reflect.getprototypeof@npm:^1.0.4":
+  version: 1.0.6
+  resolution: "reflect.getprototypeof@npm:1.0.6"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.1
+    es-errors: ^1.3.0
+    get-intrinsic: ^1.2.4
+    globalthis: ^1.0.3
+    which-builtin-type: ^1.1.3
+  checksum: 88e9e65a7eaa0bf8e9a8bbf8ac07571363bc333ba8b6769ed5e013e0042ed7c385e97fae9049510b3b5fe4b42472d8f32de9ce8ce84902bc4297d4bbe3777dba
+  languageName: node
+  linkType: hard
+
 "reflect.ownkeys@npm:^0.2.0":
   version: 0.2.0
   resolution: "reflect.ownkeys@npm:0.2.0"
 "reflect.ownkeys@npm:^0.2.0":
   version: 0.2.0
   resolution: "reflect.ownkeys@npm:0.2.0"
@@ -16096,16 +17109,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"regenerate-unicode-properties@npm:^8.2.0":
-  version: 8.2.0
-  resolution: "regenerate-unicode-properties@npm:8.2.0"
+"regenerate-unicode-properties@npm:^10.1.0":
+  version: 10.1.1
+  resolution: "regenerate-unicode-properties@npm:10.1.1"
   dependencies:
   dependencies:
-    regenerate: ^1.4.0
-  checksum: ee7db70ab25b95f2e3f39537089fc3eddba0b39fc9b982d6602f127996ce873d8c55584d5428486ca00dc0a85d174d943354943cd4a745cda475c8fe314b4f8a
+    regenerate: ^1.4.2
+  checksum: b80958ef40f125275824c2c47d5081dfaefebd80bff26c76761e9236767c748a4a95a69c053fe29d2df881177f2ca85df4a71fe70a82360388b31159ef19adcf
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"regenerate@npm:^1.4.0":
+"regenerate@npm:^1.4.2":
   version: 1.4.2
   resolution: "regenerate@npm:1.4.2"
   checksum: 3317a09b2f802da8db09aa276e469b57a6c0dd818347e05b8862959c6193408242f150db5de83c12c3fa99091ad95fb42a6db2c3329bfaa12a0ea4cbbeb30cb0
   version: 1.4.2
   resolution: "regenerate@npm:1.4.2"
   checksum: 3317a09b2f802da8db09aa276e469b57a6c0dd818347e05b8862959c6193408242f150db5de83c12c3fa99091ad95fb42a6db2c3329bfaa12a0ea4cbbeb30cb0
@@ -16126,19 +17139,33 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"regenerator-runtime@npm:^0.13.3, regenerator-runtime@npm:^0.13.4":
+"regenerator-runtime@npm:^0.13.4":
   version: 0.13.7
   resolution: "regenerator-runtime@npm:0.13.7"
   checksum: 52b66e6669152c0b1bccd95c8e11aabbfe67bb97bdf00e223bdf723b0f0052d4da5c02001d4c4bef576bdc5bcdc38a20496d1b5363b65c950c8434ed5071d9e0
   languageName: node
   linkType: hard
 
   version: 0.13.7
   resolution: "regenerator-runtime@npm:0.13.7"
   checksum: 52b66e6669152c0b1bccd95c8e11aabbfe67bb97bdf00e223bdf723b0f0052d4da5c02001d4c4bef576bdc5bcdc38a20496d1b5363b65c950c8434ed5071d9e0
   languageName: node
   linkType: hard
 
-"regenerator-transform@npm:^0.14.2":
-  version: 0.14.5
-  resolution: "regenerator-transform@npm:0.14.5"
+"regenerator-runtime@npm:^0.13.7":
+  version: 0.13.11
+  resolution: "regenerator-runtime@npm:0.13.11"
+  checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4
+  languageName: node
+  linkType: hard
+
+"regenerator-runtime@npm:^0.14.0":
+  version: 0.14.1
+  resolution: "regenerator-runtime@npm:0.14.1"
+  checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38
+  languageName: node
+  linkType: hard
+
+"regenerator-transform@npm:^0.15.2":
+  version: 0.15.2
+  resolution: "regenerator-transform@npm:0.15.2"
   dependencies:
     "@babel/runtime": ^7.8.4
   dependencies:
     "@babel/runtime": ^7.8.4
-  checksum: a467a3b652b4ec26ff964e9c5f1817523a73fc44cb928b8d21ff11aebeac5d10a84d297fe02cea9f282bcec81a0b0d562237da69ef0f40a0160b30a4fa98bc94
+  checksum: 20b6f9377d65954980fe044cfdd160de98df415b4bff38fbade67b3337efaf078308c4fed943067cd759827cc8cfeca9cb28ccda1f08333b85d6a2acbd022c27
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16159,7 +17186,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"regexp.prototype.flags@npm:^1.2.0, regexp.prototype.flags@npm:^1.3.1":
+"regexp.prototype.flags@npm:^1.2.0":
   version: 1.3.1
   resolution: "regexp.prototype.flags@npm:1.3.1"
   dependencies:
   version: 1.3.1
   resolution: "regexp.prototype.flags@npm:1.3.1"
   dependencies:
@@ -16169,10 +17196,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"regexpp@npm:^2.0.1":
-  version: 2.0.1
-  resolution: "regexpp@npm:2.0.1"
-  checksum: 1f41cf80ac08514c6665812e3dcc0673569431d3285db27053f8b237a758992fb55d6ddfbc264db399ff4f7a7db432900ca3a029daa28a75e0436231872091b1
+"regexp.prototype.flags@npm:^1.5.2":
+  version: 1.5.2
+  resolution: "regexp.prototype.flags@npm:1.5.2"
+  dependencies:
+    call-bind: ^1.0.6
+    define-properties: ^1.2.1
+    es-errors: ^1.3.0
+    set-function-name: ^2.0.1
+  checksum: d7f333667d5c564e2d7a97c56c3075d64c722c9bb51b2b4df6822b2e8096d623a5e63088fb4c83df919b6951ef8113841de8b47de7224872fa6838bc5d8a7d64
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16183,35 +17215,28 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"regexpu-core@npm:^4.7.1":
-  version: 4.7.1
-  resolution: "regexpu-core@npm:4.7.1"
+"regexpu-core@npm:^5.3.1":
+  version: 5.3.2
+  resolution: "regexpu-core@npm:5.3.2"
   dependencies:
   dependencies:
-    regenerate: ^1.4.0
-    regenerate-unicode-properties: ^8.2.0
-    regjsgen: ^0.5.1
-    regjsparser: ^0.6.4
-    unicode-match-property-ecmascript: ^1.0.4
-    unicode-match-property-value-ecmascript: ^1.2.0
-  checksum: 368b4aab72132ba3c8bd114822572c920d390ae99d3d219e0c7f872c6a0a3b1fbe30c88188ff90ec6f8e681667fa8e51d84a78bb05c460996a0df6a060b7ae80
-  languageName: node
-  linkType: hard
-
-"regjsgen@npm:^0.5.1":
-  version: 0.5.2
-  resolution: "regjsgen@npm:0.5.2"
-  checksum: 87c83d8488affae2493a823904de1a29a1867a07433c5e1142ad749b5606c5589b305fe35bfcc0972cf5a3b0d66b1f7999009e541be39a5d42c6041c59e2fb52
+    "@babel/regjsgen": ^0.8.0
+    regenerate: ^1.4.2
+    regenerate-unicode-properties: ^10.1.0
+    regjsparser: ^0.9.1
+    unicode-match-property-ecmascript: ^2.0.0
+    unicode-match-property-value-ecmascript: ^2.1.0
+  checksum: 95bb97088419f5396e07769b7de96f995f58137ad75fac5811fb5fe53737766dfff35d66a0ee66babb1eb55386ef981feaef392f9df6d671f3c124812ba24da2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"regjsparser@npm:^0.6.4":
-  version: 0.6.9
-  resolution: "regjsparser@npm:0.6.9"
+"regjsparser@npm:^0.9.1":
+  version: 0.9.1
+  resolution: "regjsparser@npm:0.9.1"
   dependencies:
     jsesc: ~0.5.0
   bin:
     regjsparser: bin/parser
   dependencies:
     jsesc: ~0.5.0
   bin:
     regjsparser: bin/parser
-  checksum: 1c439ec46a0be7834ec82fbb109396e088b6b73f0e9562cd67c37e3bdf85cc7cffe0192b3324da4491c7f709ce2b06fb2d59e12f0f9836b2e0cf26d5e54263aa
+  checksum: 5e1b76afe8f1d03c3beaf9e0d935dd467589c3625f6d65fb8ffa14f224d783a0fed4bf49c2c1b8211043ef92b6117313419edf055a098ed8342e340586741afc
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16256,15 +17281,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"repeating@npm:^2.0.0":
-  version: 2.0.1
-  resolution: "repeating@npm:2.0.1"
-  dependencies:
-    is-finite: ^1.0.0
-  checksum: d2db0b69c5cb0c14dd750036e0abcd6b3c3f7b2da3ee179786b755cf737ca15fa0fff417ca72de33d6966056f4695440e680a352401fc02c95ade59899afbdd0
-  languageName: node
-  linkType: hard
-
 "request-progress@npm:^3.0.0":
   version: 3.0.0
   resolution: "request-progress@npm:3.0.0"
 "request-progress@npm:^3.0.0":
   version: 3.0.0
   resolution: "request-progress@npm:3.0.0"
@@ -16285,7 +17301,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"request-promise-native@npm:^1.0.5, request-promise-native@npm:^1.0.8":
+"request-promise-native@npm:^1.0.8":
   version: 1.0.9
   resolution: "request-promise-native@npm:1.0.9"
   dependencies:
   version: 1.0.9
   resolution: "request-promise-native@npm:1.0.9"
   dependencies:
@@ -16298,7 +17314,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"request@npm:^2.87.0, request@npm:^2.88.0, request@npm:^2.88.2":
+"request@npm:^2.88.2":
   version: 2.88.2
   resolution: "request@npm:2.88.2"
   dependencies:
   version: 2.88.2
   resolution: "request@npm:2.88.2"
   dependencies:
@@ -16333,10 +17349,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"require-main-filename@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "require-main-filename@npm:1.0.1"
-  checksum: 1fef30754da961f4e13c450c3eb60c7ae898a529c6ad6fa708a70bd2eed01564ceb299187b2899f5562804d797a059f39a5789884d0ac7b7ae1defc68fba4abf
+"require-from-string@npm:^2.0.2":
+  version: 2.0.2
+  resolution: "require-from-string@npm:2.0.2"
+  checksum: a03ef6895445f33a4015300c426699bc66b2b044ba7b670aa238610381b56d3f07c686251740d575e22f4c87531ba662d06937508f0f3c0f1ddc04db3130560b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16370,6 +17386,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"resolve-cwd@npm:^3.0.0":
+  version: 3.0.0
+  resolution: "resolve-cwd@npm:3.0.0"
+  dependencies:
+    resolve-from: ^5.0.0
+  checksum: 546e0816012d65778e580ad62b29e975a642989108d9a3c5beabfb2304192fa3c9f9146fbdfe213563c6ff51975ae41bac1d3c6e047dd9572c94863a057b4d81
+  languageName: node
+  linkType: hard
+
 "resolve-from@npm:^3.0.0":
   version: 3.0.0
   resolution: "resolve-from@npm:3.0.0"
 "resolve-from@npm:^3.0.0":
   version: 3.0.0
   resolution: "resolve-from@npm:3.0.0"
@@ -16384,6 +17409,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"resolve-from@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "resolve-from@npm:5.0.0"
+  checksum: 4ceeb9113e1b1372d0cd969f3468fa042daa1dd9527b1b6bb88acb6ab55d8b9cd65dbf18819f9f9ddf0db804990901dcdaade80a215e7b2c23daae38e64f5bdf
+  languageName: node
+  linkType: hard
+
 "resolve-pathname@npm:^3.0.0":
   version: 3.0.0
   resolution: "resolve-pathname@npm:3.0.0"
 "resolve-pathname@npm:^3.0.0":
   version: 3.0.0
   resolution: "resolve-pathname@npm:3.0.0"
@@ -16391,21 +17423,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"resolve-url-loader@npm:3.1.2":
-  version: 3.1.2
-  resolution: "resolve-url-loader@npm:3.1.2"
+"resolve-url-loader@npm:^3.1.2":
+  version: 3.1.5
+  resolution: "resolve-url-loader@npm:3.1.5"
   dependencies:
     adjust-sourcemap-loader: 3.0.0
     camelcase: 5.3.1
     compose-function: 3.0.3
     convert-source-map: 1.7.0
     es6-iterator: 2.0.3
   dependencies:
     adjust-sourcemap-loader: 3.0.0
     camelcase: 5.3.1
     compose-function: 3.0.3
     convert-source-map: 1.7.0
     es6-iterator: 2.0.3
-    loader-utils: 1.2.3
-    postcss: 7.0.21
+    loader-utils: ^1.2.3
+    postcss: 7.0.36
     rework: 1.0.1
     rework-visit: 1.0.0
     source-map: 0.6.1
     rework: 1.0.1
     rework-visit: 1.0.0
     source-map: 0.6.1
-  checksum: 02e559af8d10a8fda8d2cb1c61290b932787309309839288820438b4f25339a8c8cbd52598af89c1c1d277133d74914407e7a760e49acd966425a038798a6e70
+  checksum: eb52911eff20723f07409cc12138d254fa0dd4a4f3b1ba11ee1b29912afb03f1272aaddb523658be1e3a946e0d1bf6f603d0e107753ab83d48ad2116cf04b7f6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16416,23 +17448,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"resolve@npm:1.1.7":
-  version: 1.1.7
-  resolution: "resolve@npm:1.1.7"
-  checksum: afd20873fbde7641c9125efe3f940c2a99f6b1f90f1b7b743e744bdaac1cb105b2e4e0317bcc052ed7e31d57afa86b394a4dc9a1b33a297977be134fdf0250ab
-  languageName: node
-  linkType: hard
-
-"resolve@npm:1.15.0":
-  version: 1.15.0
-  resolution: "resolve@npm:1.15.0"
+"resolve@npm:1.18.1":
+  version: 1.18.1
+  resolution: "resolve@npm:1.18.1"
   dependencies:
   dependencies:
+    is-core-module: ^2.0.0
     path-parse: ^1.0.6
     path-parse: ^1.0.6
-  checksum: 6d5a48c4ddaad067d7d0d0557c58d0db342d878abccd4843af81ffd439b865157e8bf38029d05fdc02137da5bc7528d5120e3c270b38db2cb26973ffa9e2bebc
+  checksum: bab3686fa87576ac7e7f68481e25494f99b8413f3bc5048c5284eabe021f98917a50c625f8a1920a87ffc347b076c12a4a685d46d5fc98f337cf2dd3792014f4
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.13.1, resolve@npm:^1.14.2, resolve@npm:^1.15.1, resolve@npm:^1.3.2, resolve@npm:^1.8.1":
+"resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.3.2":
   version: 1.20.0
   resolution: "resolve@npm:1.20.0"
   dependencies:
   version: 1.20.0
   resolution: "resolve@npm:1.20.0"
   dependencies:
@@ -16442,23 +17468,43 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"resolve@patch:resolve@1.1.7#~builtin<compat/resolve>":
-  version: 1.1.7
-  resolution: "resolve@patch:resolve@npm%3A1.1.7#~builtin<compat/resolve>::version=1.1.7&hash=07638b"
-  checksum: e9dbca78600ae56835c43a09f1276876c883e4b4bbd43e2683fa140671519d2bdebeb1c1576ca87c8c508ae2987b3ec481645ac5d3054b0f23254cfc1ce49942
+"resolve@npm:^1.17.0, resolve@npm:^1.18.1, resolve@npm:^1.19.0, resolve@npm:^1.22.4":
+  version: 1.22.8
+  resolution: "resolve@npm:1.22.8"
+  dependencies:
+    is-core-module: ^2.13.0
+    path-parse: ^1.0.7
+    supports-preserve-symlinks-flag: ^1.0.0
+  bin:
+    resolve: bin/resolve
+  checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"resolve@patch:resolve@1.15.0#~builtin<compat/resolve>":
-  version: 1.15.0
-  resolution: "resolve@patch:resolve@npm%3A1.15.0#~builtin<compat/resolve>::version=1.15.0&hash=07638b"
+"resolve@npm:^2.0.0-next.5":
+  version: 2.0.0-next.5
+  resolution: "resolve@npm:2.0.0-next.5"
+  dependencies:
+    is-core-module: ^2.13.0
+    path-parse: ^1.0.7
+    supports-preserve-symlinks-flag: ^1.0.0
+  bin:
+    resolve: bin/resolve
+  checksum: a73ac69a1c4bd34c56b213d91f5b17ce390688fdb4a1a96ed3025cc7e08e7bfb90b3a06fcce461780cb0b589c958afcb0080ab802c71c01a7ecc8c64feafc89f
+  languageName: node
+  linkType: hard
+
+"resolve@patch:resolve@1.18.1#~builtin<compat/resolve>":
+  version: 1.18.1
+  resolution: "resolve@patch:resolve@npm%3A1.18.1#~builtin<compat/resolve>::version=1.18.1&hash=07638b"
   dependencies:
   dependencies:
+    is-core-module: ^2.0.0
     path-parse: ^1.0.6
     path-parse: ^1.0.6
-  checksum: fd308e2054e85f829a29e2a5f4634942272b9bfc2771f95fbd1c2068f47a7f4f0f5d4ccb11cb4522f9a8490edd036299f7a85e76327ed8b91bd1a41d314e2bf0
+  checksum: 7439c8f3d8fa00c9dc800ef3c8ed0bd8e8772823e6e4948b1a77487759e0fb905381808caae96398d135619af90654d8e74cac778e5b8c9d7138f2dd52bb2bba
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"resolve@patch:resolve@^1.10.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.12.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.13.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.15.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.3.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.8.1#~builtin<compat/resolve>":
+"resolve@patch:resolve@^1.10.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.12.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.3.2#~builtin<compat/resolve>":
   version: 1.20.0
   resolution: "resolve@patch:resolve@npm%3A1.20.0#~builtin<compat/resolve>::version=1.20.0&hash=07638b"
   dependencies:
   version: 1.20.0
   resolution: "resolve@patch:resolve@npm%3A1.20.0#~builtin<compat/resolve>::version=1.20.0&hash=07638b"
   dependencies:
@@ -16468,6 +17514,32 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"resolve@patch:resolve@^1.17.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.18.1#~builtin<compat/resolve>, resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.4#~builtin<compat/resolve>":
+  version: 1.22.8
+  resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin<compat/resolve>::version=1.22.8&hash=07638b"
+  dependencies:
+    is-core-module: ^2.13.0
+    path-parse: ^1.0.7
+    supports-preserve-symlinks-flag: ^1.0.0
+  bin:
+    resolve: bin/resolve
+  checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847
+  languageName: node
+  linkType: hard
+
+"resolve@patch:resolve@^2.0.0-next.5#~builtin<compat/resolve>":
+  version: 2.0.0-next.5
+  resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#~builtin<compat/resolve>::version=2.0.0-next.5&hash=07638b"
+  dependencies:
+    is-core-module: ^2.13.0
+    path-parse: ^1.0.7
+    supports-preserve-symlinks-flag: ^1.0.0
+  bin:
+    resolve: bin/resolve
+  checksum: 064d09c1808d0c51b3d90b5d27e198e6d0c5dad0eb57065fd40803d6a20553e5398b07f76739d69cbabc12547058bec6b32106ea66622375fb0d7e8fca6a846c
+  languageName: node
+  linkType: hard
+
 "restore-cursor@npm:^3.1.0":
   version: 3.1.0
   resolution: "restore-cursor@npm:3.1.0"
 "restore-cursor@npm:^3.1.0":
   version: 3.1.0
   resolution: "restore-cursor@npm:3.1.0"
@@ -16537,7 +17609,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"rimraf@npm:2, rimraf@npm:^2.5.4, rimraf@npm:^2.6.3, rimraf@npm:^2.7.1":
+"rimraf@npm:2, rimraf@npm:^2.5.4, rimraf@npm:^2.6.3":
   version: 2.7.1
   resolution: "rimraf@npm:2.7.1"
   dependencies:
   version: 2.7.1
   resolution: "rimraf@npm:2.7.1"
   dependencies:
@@ -16548,17 +17620,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"rimraf@npm:2.6.3":
-  version: 2.6.3
-  resolution: "rimraf@npm:2.6.3"
-  dependencies:
-    glob: ^7.1.3
-  bin:
-    rimraf: ./bin.js
-  checksum: 3ea587b981a19016297edb96d1ffe48af7e6af69660e3b371dbfc73722a73a0b0e9be5c88089fbeeb866c389c1098e07f64929c7414290504b855f54f901ab10
-  languageName: node
-  linkType: hard
-
 "rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
   version: 3.0.2
   resolution: "rimraf@npm:3.0.2"
 "rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
   version: 3.0.2
   resolution: "rimraf@npm:3.0.2"
@@ -16580,6 +17641,56 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"rollup-plugin-babel@npm:^4.3.3":
+  version: 4.4.0
+  resolution: "rollup-plugin-babel@npm:4.4.0"
+  dependencies:
+    "@babel/helper-module-imports": ^7.0.0
+    rollup-pluginutils: ^2.8.1
+  peerDependencies:
+    "@babel/core": 7 || ^7.0.0-rc.2
+    rollup: ">=0.60.0 <3"
+  checksum: 5b8ed7c0a4192d7c74689074c910c1670eb07dfc875b1f4af5694a94c46bcb168ba85e2c9753030131efd6261ece7c252b9695953d0ea96d944977c6e79930d3
+  languageName: node
+  linkType: hard
+
+"rollup-plugin-terser@npm:^5.3.1":
+  version: 5.3.1
+  resolution: "rollup-plugin-terser@npm:5.3.1"
+  dependencies:
+    "@babel/code-frame": ^7.5.5
+    jest-worker: ^24.9.0
+    rollup-pluginutils: ^2.8.2
+    serialize-javascript: ^4.0.0
+    terser: ^4.6.2
+  peerDependencies:
+    rollup: ">=0.66.0 <3"
+  checksum: 50f9e8fa6737fa5e8aeca6a52b59ea3bc66faebe743bdfe9ce0484298cd1978082026721b182d79bcc88240429842dc58feae88d6c238b47cafc1684e0320a73
+  languageName: node
+  linkType: hard
+
+"rollup-pluginutils@npm:^2.8.1, rollup-pluginutils@npm:^2.8.2":
+  version: 2.8.2
+  resolution: "rollup-pluginutils@npm:2.8.2"
+  dependencies:
+    estree-walker: ^0.6.1
+  checksum: 339fdf866d8f4ff6e408fa274c0525412f7edb01dc46b5ccda51f575b7e0d20ad72965773376fb5db95a77a7fcfcab97bf841ec08dbadf5d6b08af02b7a2cf5e
+  languageName: node
+  linkType: hard
+
+"rollup@npm:^1.31.1":
+  version: 1.32.1
+  resolution: "rollup@npm:1.32.1"
+  dependencies:
+    "@types/estree": "*"
+    "@types/node": "*"
+    acorn: ^7.1.0
+  bin:
+    rollup: dist/bin/rollup
+  checksum: 3a02731c20c71321fae647c9c9cab0febee0580c6af029fdcd5dd6f424b8c85119d92c8554c6837327fd323c2458e92d955bbebc90ca6bed87cc626695e7c31f
+  languageName: node
+  linkType: hard
+
 "rst-selector-parser@npm:^2.2.3":
   version: 2.2.3
   resolution: "rst-selector-parser@npm:2.2.3"
 "rst-selector-parser@npm:^2.2.3":
   version: 2.2.3
   resolution: "rst-selector-parser@npm:2.2.3"
@@ -16597,13 +17708,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"run-async@npm:^2.2.0, run-async@npm:^2.4.0":
-  version: 2.4.1
-  resolution: "run-async@npm:2.4.1"
-  checksum: a2c88aa15df176f091a2878eb840e68d0bdee319d8d97bbb89112223259cebecb94bc0defd735662b83c2f7a30bed8cddb7d1674eb48ae7322dc602b22d03797
-  languageName: node
-  linkType: hard
-
 "run-parallel@npm:^1.1.9":
   version: 1.2.0
   resolution: "run-parallel@npm:1.2.0"
 "run-parallel@npm:^1.1.9":
   version: 1.2.0
   resolution: "run-parallel@npm:1.2.0"
@@ -16622,7 +17726,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"rxjs@npm:^6.5.3, rxjs@npm:^6.5.5, rxjs@npm:^6.6.0":
+"rxjs@npm:^6.5.5":
   version: 6.6.7
   resolution: "rxjs@npm:6.6.7"
   dependencies:
   version: 6.6.7
   resolution: "rxjs@npm:6.6.7"
   dependencies:
@@ -16635,8 +17739,20 @@ __metadata:
   version: 7.8.1
   resolution: "rxjs@npm:7.8.1"
   dependencies:
   version: 7.8.1
   resolution: "rxjs@npm:7.8.1"
   dependencies:
-    tslib: ^2.1.0
-  checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119
+    tslib: ^2.1.0
+  checksum: de4b53db1063e618ec2eca0f7965d9137cabe98cf6be9272efe6c86b47c17b987383df8574861bcced18ebd590764125a901d5506082be84a8b8e364bf05f119
+  languageName: node
+  linkType: hard
+
+"safe-array-concat@npm:^1.1.2":
+  version: 1.1.2
+  resolution: "safe-array-concat@npm:1.1.2"
+  dependencies:
+    call-bind: ^1.0.7
+    get-intrinsic: ^1.2.4
+    has-symbols: ^1.0.3
+    isarray: ^2.0.5
+  checksum: a3b259694754ddfb73ae0663829e396977b99ff21cbe8607f35a469655656da8e271753497e59da8a7575baa94d2e684bea3e10ddd74ba046c0c9b4418ffa0c4
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16647,13 +17763,24 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0":
+"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
   version: 5.2.1
   resolution: "safe-buffer@npm:5.2.1"
   checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491
   languageName: node
   linkType: hard
 
   version: 5.2.1
   resolution: "safe-buffer@npm:5.2.1"
   checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491
   languageName: node
   linkType: hard
 
+"safe-regex-test@npm:^1.0.3":
+  version: 1.0.3
+  resolution: "safe-regex-test@npm:1.0.3"
+  dependencies:
+    call-bind: ^1.0.6
+    es-errors: ^1.3.0
+    is-regex: ^1.1.4
+  checksum: 6c7d392ff1ae7a3ae85273450ed02d1d131f1d2c76e177d6b03eb88e6df8fa062639070e7d311802c1615f351f18dc58f9454501c58e28d5ffd9b8f502ba6489
+  languageName: node
+  linkType: hard
+
 "safe-regex@npm:^1.1.0":
   version: 1.1.0
   resolution: "safe-regex@npm:1.1.0"
 "safe-regex@npm:^1.1.0":
   version: 1.1.0
   resolution: "safe-regex@npm:1.1.0"
@@ -16696,20 +17823,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"sass-graph@npm:^2.2.4":
-  version: 2.2.6
-  resolution: "sass-graph@npm:2.2.6"
-  dependencies:
-    glob: ^7.0.0
-    lodash: ^4.0.0
-    scss-tokenizer: ^0.2.3
-    yargs: ^7.0.0
-  bin:
-    sassgraph: bin/sassgraph
-  checksum: 1fb1719c659fdea00a9f55be9722c5902c3d1f1a0919d2e5ceb8a318064f2b214981d98b7d7fecaafc25f522302f919a948351e4ae1d1680b9c045d563550a93
-  languageName: node
-  linkType: hard
-
 "sass-graph@npm:^4.0.1":
   version: 4.0.1
   resolution: "sass-graph@npm:4.0.1"
 "sass-graph@npm:^4.0.1":
   version: 4.0.1
   resolution: "sass-graph@npm:4.0.1"
@@ -16749,19 +17862,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"sax@npm:^1.2.4, sax@npm:~1.2.4":
+"sax@npm:~1.2.4":
   version: 1.2.4
   resolution: "sax@npm:1.2.4"
   checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe
   languageName: node
   linkType: hard
 
   version: 1.2.4
   resolution: "sax@npm:1.2.4"
   checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe
   languageName: node
   linkType: hard
 
-"saxes@npm:^3.1.9":
-  version: 3.1.11
-  resolution: "saxes@npm:3.1.11"
+"saxes@npm:^5.0.1":
+  version: 5.0.1
+  resolution: "saxes@npm:5.0.1"
   dependencies:
   dependencies:
-    xmlchars: ^2.1.1
-  checksum: 3b69918c013fffae51c561f629a0f620c02dba70f762dab38f3cd92676dfe5edf1f0a523ca567882838f1a80e26e4671a8c2c689afa05c68f45a78261445aba0
+    xmlchars: ^2.2.0
+  checksum: 5636b55cf15f7cf0baa73f2797bf992bdcf75d1b39d82c0aa4608555c774368f6ac321cb641fd5f3d3ceb87805122cd47540da6a7b5960fe0dbdb8f8c263f000
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16786,7 +17899,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"schema-utils@npm:^2.5.0, schema-utils@npm:^2.6.0, schema-utils@npm:^2.6.1, schema-utils@npm:^2.6.5, schema-utils@npm:^2.6.6":
+"schema-utils@npm:^2.6.1, schema-utils@npm:^2.6.5, schema-utils@npm:^2.7.0, schema-utils@npm:^2.7.1":
   version: 2.7.1
   resolution: "schema-utils@npm:2.7.1"
   dependencies:
   version: 2.7.1
   resolution: "schema-utils@npm:2.7.1"
   dependencies:
@@ -16797,13 +17910,14 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"scss-tokenizer@npm:^0.2.3":
-  version: 0.2.3
-  resolution: "scss-tokenizer@npm:0.2.3"
+"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1":
+  version: 3.3.0
+  resolution: "schema-utils@npm:3.3.0"
   dependencies:
   dependencies:
-    js-base64: ^2.1.8
-    source-map: ^0.4.2
-  checksum: ad78bba4466ff7aa6449931a57a980479223c3cad9eccf2180251c2f6fce5b3d982a51f924709e0a0bb2d328dedbb2fad0ccb2a5fdc175513a27cb4e8cf8cfd2
+    "@types/json-schema": ^7.0.8
+    ajv: ^6.12.5
+    ajv-keywords: ^3.5.2
+  checksum: ea56971926fac2487f0757da939a871388891bc87c6a82220d125d587b388f1704788f3706e7f63a7b70e49fc2db974c41343528caea60444afd5ce0fe4b85c0
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16833,7 +17947,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.5.1, semver@npm:^5.6.0, semver@npm:^5.7.0, semver@npm:^5.7.1":
+"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.3.0, semver@npm:^5.4.1, semver@npm:^5.5.0, semver@npm:^5.6.0, semver@npm:^5.7.0, semver@npm:^5.7.1":
   version: 5.7.2
   resolution: "semver@npm:5.7.2"
   bin:
   version: 5.7.2
   resolution: "semver@npm:5.7.2"
   bin:
@@ -16842,25 +17956,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"semver@npm:6.3.0":
-  version: 6.3.0
-  resolution: "semver@npm:6.3.0"
-  bin:
-    semver: ./bin/semver.js
-  checksum: 1b26ecf6db9e8292dd90df4e781d91875c0dcc1b1909e70f5d12959a23c7eebb8f01ea581c00783bbee72ceeaad9505797c381756326073850dc36ed284b21b9
-  languageName: node
-  linkType: hard
-
-"semver@npm:7.0.0":
-  version: 7.0.0
-  resolution: "semver@npm:7.0.0"
+"semver@npm:7.3.2":
+  version: 7.3.2
+  resolution: "semver@npm:7.3.2"
   bin:
     semver: bin/semver.js
   bin:
     semver: bin/semver.js
-  checksum: 272c11bf8d083274ef79fe40a81c55c184dff84dd58e3c325299d0927ba48cece1f020793d138382b85f89bab5002a35a5ba59a3a68a7eebbb597eb733838778
+  checksum: 692f4900dadb43919614b0df9af23fe05743051cda0d1735b5e4d76f93c9e43a266fae73cfc928f5d1489f022c5c0e65dfd2900fcf5b1839c4e9a239729afa7b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"semver@npm:^6.0.0, semver@npm:^6.1.1, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0":
+"semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1":
   version: 6.3.1
   resolution: "semver@npm:6.3.1"
   bin:
   version: 6.3.1
   resolution: "semver@npm:6.3.1"
   bin:
@@ -16869,46 +17974,46 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"semver@npm:^7.3.4, semver@npm:^7.3.5":
-  version: 7.5.4
-  resolution: "semver@npm:7.5.4"
+"semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.5.3":
+  version: 7.6.0
+  resolution: "semver@npm:7.6.0"
   dependencies:
     lru-cache: ^6.0.0
   bin:
     semver: bin/semver.js
   dependencies:
     lru-cache: ^6.0.0
   bin:
     semver: bin/semver.js
-  checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3
+  checksum: 7427f05b70786c696640edc29fdd4bc33b2acf3bbe1740b955029044f80575fc664e1a512e4113c3af21e767154a94b4aa214bf6cd6e42a1f6dba5914e0b208c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"semver@npm:^7.5.3":
-  version: 7.6.0
-  resolution: "semver@npm:7.6.0"
+"semver@npm:^7.3.4, semver@npm:^7.3.5":
+  version: 7.5.4
+  resolution: "semver@npm:7.5.4"
   dependencies:
     lru-cache: ^6.0.0
   bin:
     semver: bin/semver.js
   dependencies:
     lru-cache: ^6.0.0
   bin:
     semver: bin/semver.js
-  checksum: 7427f05b70786c696640edc29fdd4bc33b2acf3bbe1740b955029044f80575fc664e1a512e4113c3af21e767154a94b4aa214bf6cd6e42a1f6dba5914e0b208c
+  checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"send@npm:0.17.1":
-  version: 0.17.1
-  resolution: "send@npm:0.17.1"
+"send@npm:0.18.0":
+  version: 0.18.0
+  resolution: "send@npm:0.18.0"
   dependencies:
     debug: 2.6.9
   dependencies:
     debug: 2.6.9
-    depd: ~1.1.2
-    destroy: ~1.0.4
+    depd: 2.0.0
+    destroy: 1.2.0
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
     etag: ~1.8.1
     fresh: 0.5.2
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
     etag: ~1.8.1
     fresh: 0.5.2
-    http-errors: ~1.7.2
+    http-errors: 2.0.0
     mime: 1.6.0
     mime: 1.6.0
-    ms: 2.1.1
-    on-finished: ~2.3.0
+    ms: 2.1.3
+    on-finished: 2.4.1
     range-parser: ~1.2.1
     range-parser: ~1.2.1
-    statuses: ~1.5.0
-  checksum: d214c2fa42e7fae3f8fc1aa3931eeb3e6b78c2cf141574e09dbe159915c1e3a337269fc6b7512e7dfddcd7d6ff5974cb62f7c3637ba86a55bde20a92c18bdca0
+    statuses: 2.0.1
+  checksum: 74fc07ebb58566b87b078ec63e5a3e41ecd987e4272ba67b7467e86c6ad51bc6b0b0154133b6d8b08a2ddda360464f71382f7ef864700f34844a76c8027817a8
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16921,6 +18026,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"serialize-javascript@npm:^5.0.1":
+  version: 5.0.1
+  resolution: "serialize-javascript@npm:5.0.1"
+  dependencies:
+    randombytes: ^2.1.0
+  checksum: bb45a427690c3d2711e28499de0fbf25036af1e23c63c6a9237ed0aa572fd0941fcdefe50a2dccf26d9df8c8b86ae38659e19d8ba7afd3fbc1f1c7539a2a48d2
+  languageName: node
+  linkType: hard
+
 "serve-index@npm:^1.9.1":
   version: 1.9.1
   resolution: "serve-index@npm:1.9.1"
 "serve-index@npm:^1.9.1":
   version: 1.9.1
   resolution: "serve-index@npm:1.9.1"
@@ -16936,15 +18050,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"serve-static@npm:1.14.1":
-  version: 1.14.1
-  resolution: "serve-static@npm:1.14.1"
+"serve-static@npm:1.15.0":
+  version: 1.15.0
+  resolution: "serve-static@npm:1.15.0"
   dependencies:
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
     parseurl: ~1.3.3
   dependencies:
     encodeurl: ~1.0.2
     escape-html: ~1.0.3
     parseurl: ~1.3.3
-    send: 0.17.1
-  checksum: c6b268e8486d39ecd54b86c7f2d0ee4a38cd7514ddd9c92c8d5793bb005afde5e908b12395898ae206782306ccc848193d93daa15b86afb3cbe5a8414806abe8
+    send: 0.18.0
+  checksum: af57fc13be40d90a12562e98c0b7855cf6e8bd4c107fe9a45c212bf023058d54a1871b1c89511c3958f70626fff47faeb795f5d83f8cf88514dbaeb2b724464d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -16955,6 +18069,32 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"set-function-length@npm:^1.2.1":
+  version: 1.2.2
+  resolution: "set-function-length@npm:1.2.2"
+  dependencies:
+    define-data-property: ^1.1.4
+    es-errors: ^1.3.0
+    function-bind: ^1.1.2
+    get-intrinsic: ^1.2.4
+    gopd: ^1.0.1
+    has-property-descriptors: ^1.0.2
+  checksum: a8248bdacdf84cb0fab4637774d9fb3c7a8e6089866d04c817583ff48e14149c87044ce683d7f50759a8c50fb87c7a7e173535b06169c87ef76f5fb276dfff72
+  languageName: node
+  linkType: hard
+
+"set-function-name@npm:^2.0.1, set-function-name@npm:^2.0.2":
+  version: 2.0.2
+  resolution: "set-function-name@npm:2.0.2"
+  dependencies:
+    define-data-property: ^1.1.4
+    es-errors: ^1.3.0
+    functions-have-names: ^1.2.3
+    has-property-descriptors: ^1.0.2
+  checksum: d6229a71527fd0404399fc6227e0ff0652800362510822a291925c9d7b48a1ca1a468b11b281471c34cd5a2da0db4f5d7ff315a61d26655e77f6e971e6d0c80f
+  languageName: node
+  linkType: hard
+
 "set-value@npm:2.0.1, set-value@npm:^2.0.0, set-value@npm:^2.0.1":
   version: 2.0.1
   resolution: "set-value@npm:2.0.1"
 "set-value@npm:2.0.1, set-value@npm:^2.0.0, set-value@npm:^2.0.1":
   version: 2.0.1
   resolution: "set-value@npm:2.0.1"
@@ -16981,10 +18121,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"setprototypeof@npm:1.1.1":
-  version: 1.1.1
-  resolution: "setprototypeof@npm:1.1.1"
-  checksum: a8bee29c1c64c245d460ce53f7460af8cbd0aceac68d66e5215153992cc8b3a7a123416353e0c642060e85cc5fd4241c92d1190eec97eda0dcb97436e8fcca3b
+"setprototypeof@npm:1.2.0":
+  version: 1.2.0
+  resolution: "setprototypeof@npm:1.2.0"
+  checksum: be18cbbf70e7d8097c97f713a2e76edf84e87299b40d085c6bf8b65314e994cc15e2e317727342fa6996e38e1f52c59720b53fe621e2eb593a6847bf0356db89
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -17000,18 +18140,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"shallow-clone@npm:^0.1.2":
-  version: 0.1.2
-  resolution: "shallow-clone@npm:0.1.2"
-  dependencies:
-    is-extendable: ^0.1.1
-    kind-of: ^2.0.1
-    lazy-cache: ^0.2.3
-    mixin-object: ^2.0.1
-  checksum: cc4c85c6e42186fec33a81a85622c48dbcfdf280f3a7bd0800b4de57df8e365a8760aa2e31dd79df365b317dddb2fd0bbd92be0aab14dbd2de6a65992eab2177
-  languageName: node
-  linkType: hard
-
 "shallow-clone@npm:^3.0.0":
   version: 3.0.1
   resolution: "shallow-clone@npm:3.0.1"
 "shallow-clone@npm:^3.0.0":
   version: 3.0.1
   resolution: "shallow-clone@npm:3.0.1"
@@ -17092,6 +18220,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"side-channel@npm:^1.0.6":
+  version: 1.0.6
+  resolution: "side-channel@npm:1.0.6"
+  dependencies:
+    call-bind: ^1.0.7
+    es-errors: ^1.3.0
+    get-intrinsic: ^1.2.4
+    object-inspect: ^1.13.1
+  checksum: bfc1afc1827d712271453e91b7cd3878ac0efd767495fd4e594c4c2afaa7963b7b510e249572bfd54b0527e66e4a12b61b80c061389e129755f34c493aad9b97
+  languageName: node
+  linkType: hard
+
 "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2":
   version: 3.0.3
   resolution: "signal-exit@npm:3.0.3"
 "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2":
   version: 3.0.3
   resolution: "signal-exit@npm:3.0.3"
@@ -17137,20 +18277,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"slash@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "slash@npm:1.0.0"
-  checksum: 4b6e21b1fba6184a7e2efb1dd173f692d8a845584c1bbf9dc818ff86f5a52fc91b413008223d17cc684604ee8bb9263a420b1182027ad9762e35388434918860
-  languageName: node
-  linkType: hard
-
-"slash@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "slash@npm:2.0.0"
-  checksum: 512d4350735375bd11647233cb0e2f93beca6f53441015eea241fe784d8068281c3987fbaa93e7ef1c38df68d9c60013045c92837423c69115297d6169aa85e6
-  languageName: node
-  linkType: hard
-
 "slash@npm:^3.0.0":
   version: 3.0.0
   resolution: "slash@npm:3.0.0"
 "slash@npm:^3.0.0":
   version: 3.0.0
   resolution: "slash@npm:3.0.0"
@@ -17158,17 +18284,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"slice-ansi@npm:^2.1.0":
-  version: 2.1.0
-  resolution: "slice-ansi@npm:2.1.0"
-  dependencies:
-    ansi-styles: ^3.2.0
-    astral-regex: ^1.0.0
-    is-fullwidth-code-point: ^2.0.0
-  checksum: 4e82995aa59cef7eb03ef232d73c2239a15efa0ace87a01f3012ebb942e963fbb05d448ce7391efcd52ab9c32724164aba2086f5143e0445c969221dde3b6b1e
-  languageName: node
-  linkType: hard
-
 "slice-ansi@npm:^3.0.0":
   version: 3.0.0
   resolution: "slice-ansi@npm:3.0.0"
 "slice-ansi@npm:^3.0.0":
   version: 3.0.0
   resolution: "slice-ansi@npm:3.0.0"
@@ -17335,6 +18450,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"source-map-js@npm:^1.2.0":
+  version: 1.2.0
+  resolution: "source-map-js@npm:1.2.0"
+  checksum: 791a43306d9223792e84293b00458bf102a8946e7188f3db0e4e22d8d530b5f80a4ce468eb5ec0bf585443ad55ebbd630bf379c98db0b1f317fd902500217f97
+  languageName: node
+  linkType: hard
+
 "source-map-resolve@npm:^0.5.0, source-map-resolve@npm:^0.5.2":
   version: 0.5.3
   resolution: "source-map-resolve@npm:0.5.3"
 "source-map-resolve@npm:^0.5.0, source-map-resolve@npm:^0.5.2":
   version: 0.5.3
   resolution: "source-map-resolve@npm:0.5.3"
@@ -17348,15 +18470,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"source-map-support@npm:^0.4.15":
-  version: 0.4.18
-  resolution: "source-map-support@npm:0.4.18"
-  dependencies:
-    source-map: ^0.5.6
-  checksum: 669aa7e992fec586fac0ba9a8dea8ce81b7328f92806335f018ffac5709afb2920e3870b4e56c68164282607229f04b8bbcf5d0e5c845eb1b5119b092e7585c0
-  languageName: node
-  linkType: hard
-
 "source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12":
   version: 0.5.19
   resolution: "source-map-support@npm:0.5.19"
 "source-map-support@npm:^0.5.6, source-map-support@npm:~0.5.12":
   version: 0.5.19
   resolution: "source-map-support@npm:0.5.19"
@@ -17367,6 +18480,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"source-map-support@npm:~0.5.20":
+  version: 0.5.21
+  resolution: "source-map-support@npm:0.5.21"
+  dependencies:
+    buffer-from: ^1.0.0
+    source-map: ^0.6.0
+  checksum: 43e98d700d79af1d36f859bdb7318e601dfc918c7ba2e98456118ebc4c4872b327773e5a1df09b0524e9e5063bb18f0934538eace60cca2710d1fa687645d137
+  languageName: node
+  linkType: hard
+
 "source-map-url@npm:^0.4.0":
   version: 0.4.1
   resolution: "source-map-url@npm:0.4.1"
 "source-map-url@npm:^0.4.0":
   version: 0.4.1
   resolution: "source-map-url@npm:0.4.1"
@@ -17381,16 +18504,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"source-map@npm:^0.4.2":
-  version: 0.4.4
-  resolution: "source-map@npm:0.4.4"
-  dependencies:
-    amdefine: ">=0.0.4"
-  checksum: b31992fcb4a2a6c335617f187bd36f392896dfcc111830ebdb8b716923cf6554b665833b975fc998bdf3a63881b2c8b4c5c34fda0280357b8c18fe6aa5d148ea
-  languageName: node
-  linkType: hard
-
-"source-map@npm:^0.5.0, source-map@npm:^0.5.6, source-map@npm:^0.5.7":
+"source-map@npm:^0.5.0, source-map@npm:^0.5.6":
   version: 0.5.7
   resolution: "source-map@npm:0.5.7"
   checksum: 5dc2043b93d2f194142c7f38f74a24670cd7a0063acdaf4bf01d2964b402257ae843c2a8fa822ad5b71013b5fcafa55af7421383da919752f22ff488bc553f4d
   version: 0.5.7
   resolution: "source-map@npm:0.5.7"
   checksum: 5dc2043b93d2f194142c7f38f74a24670cd7a0063acdaf4bf01d2964b402257ae843c2a8fa822ad5b71013b5fcafa55af7421383da919752f22ff488bc553f4d
@@ -17404,6 +18518,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"sourcemap-codec@npm:^1.4.8":
+  version: 1.4.8
+  resolution: "sourcemap-codec@npm:1.4.8"
+  checksum: b57981c05611afef31605732b598ccf65124a9fcb03b833532659ac4d29ac0f7bfacbc0d6c5a28a03e84c7510e7e556d758d0bb57786e214660016fb94279316
+  languageName: node
+  linkType: hard
+
 "spdx-correct@npm:^3.0.0":
   version: 3.1.1
   resolution: "spdx-correct@npm:3.1.1"
 "spdx-correct@npm:^3.0.0":
   version: 3.1.1
   resolution: "spdx-correct@npm:3.1.1"
@@ -17539,16 +18660,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ssri@npm:^7.0.0":
-  version: 7.1.1
-  resolution: "ssri@npm:7.1.1"
-  dependencies:
-    figgy-pudding: ^3.5.1
-    minipass: ^3.1.1
-  checksum: 8bdb3c198a3cebda54344b3cd9599338c18a4b29f1c857c0ab98cb39ff11a36b4cb6ea5a388c22bd71ac1ae6d8129103336173f77487d94d772eeb9aa0c8545f
-  languageName: node
-  linkType: hard
-
 "ssri@npm:^8.0.0, ssri@npm:^8.0.1":
   version: 8.0.1
   resolution: "ssri@npm:8.0.1"
 "ssri@npm:^8.0.0, ssri@npm:^8.0.1":
   version: 8.0.1
   resolution: "ssri@npm:8.0.1"
@@ -17574,12 +18685,28 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"stack-utils@npm:^1.0.1":
-  version: 1.0.5
-  resolution: "stack-utils@npm:1.0.5"
+"stack-utils@npm:^2.0.2":
+  version: 2.0.6
+  resolution: "stack-utils@npm:2.0.6"
   dependencies:
     escape-string-regexp: ^2.0.0
   dependencies:
     escape-string-regexp: ^2.0.0
-  checksum: f82baf8d89536252a55c76866d5be3d04c96b09693a8d2ab3794b9fdec3674e05bd3f3d19345093e2cbba116a1f8f413858e0537bc3c81c605249261c3d26182
+  checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7
+  languageName: node
+  linkType: hard
+
+"stackframe@npm:^1.3.4":
+  version: 1.3.4
+  resolution: "stackframe@npm:1.3.4"
+  checksum: bae1596873595c4610993fa84f86a3387d67586401c1816ea048c0196800c0646c4d2da98c2ee80557fd9eff05877efe33b91ba6cd052658ed96ddc85d19067d
+  languageName: node
+  linkType: hard
+
+"static-eval@npm:2.0.2":
+  version: 2.0.2
+  resolution: "static-eval@npm:2.0.2"
+  dependencies:
+    escodegen: ^1.8.1
+  checksum: 335a923c5ccb29add404ac23d0a55c0da6cee3071f6f67a7053aeac0dedc6dbfc53ac9269e9c25f403f5b7603a291ef47d7114f99bde241184f7aa3f9286dc32
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -17593,7 +18720,14 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"statuses@npm:>= 1.4.0 < 2, statuses@npm:>= 1.5.0 < 2, statuses@npm:~1.5.0":
+"statuses@npm:2.0.1":
+  version: 2.0.1
+  resolution: "statuses@npm:2.0.1"
+  checksum: 18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb
+  languageName: node
+  linkType: hard
+
+"statuses@npm:>= 1.4.0 < 2":
   version: 1.5.0
   resolution: "statuses@npm:1.5.0"
   checksum: c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c
   version: 1.5.0
   resolution: "statuses@npm:1.5.0"
   checksum: c469b9519de16a4bb19600205cffb39ee471a5f17b82589757ca7bd40a8d92ebb6ed9f98b5a540c5d302ccbc78f15dc03cc0280dd6e00df1335568a5d5758a5c
@@ -17670,34 +18804,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"string-length@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "string-length@npm:2.0.0"
-  dependencies:
-    astral-regex: ^1.0.0
-    strip-ansi: ^4.0.0
-  checksum: 3a339b63fd39d6a1077dfbbe3279545e1b67fa4b0a558906158cf0121632b280f34c8768ec7270fb25db732d6323eceb9c7254f6026509694b6a7533ca8cb89e
-  languageName: node
-  linkType: hard
-
-"string-length@npm:^3.1.0":
-  version: 3.1.0
-  resolution: "string-length@npm:3.1.0"
+"string-length@npm:^4.0.1":
+  version: 4.0.2
+  resolution: "string-length@npm:4.0.2"
   dependencies:
   dependencies:
-    astral-regex: ^1.0.0
-    strip-ansi: ^5.2.0
-  checksum: b09ccacc2f96ba3ade9f2b3163901e05f668a2b14bc353853165c1f3b19185421ac004e9957b62827083d163e049c41a1b15170e252eaf44fdd686553c372714
+    char-regex: ^1.0.2
+    strip-ansi: ^6.0.0
+  checksum: ce85533ef5113fcb7e522bcf9e62cb33871aa99b3729cec5595f4447f660b0cefd542ca6df4150c97a677d58b0cb727a3fe09ac1de94071d05526c73579bf505
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"string-width@npm:^1.0.1, string-width@npm:^1.0.2":
-  version: 1.0.2
-  resolution: "string-width@npm:1.0.2"
-  dependencies:
-    code-point-at: ^1.0.0
-    is-fullwidth-code-point: ^1.0.0
-    strip-ansi: ^3.0.0
-  checksum: 5c79439e95bc3bd7233a332c5f5926ab2ee90b23816ed4faa380ce3b2576d7800b0a5bb15ae88ed28737acc7ea06a518c2eef39142dd727adad0e45c776cd37e
+"string-natural-compare@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "string-natural-compare@npm:3.0.1"
+  checksum: 65910d9995074086e769a68728395effbba9b7186be5b4c16a7fad4f4ef50cae95ca16e3e9086e019cbb636ae8daac9c7b8fe91b5f21865c5c0f26e3c0725406
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -17734,19 +18854,23 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"string.prototype.matchall@npm:^4.0.2":
-  version: 4.0.5
-  resolution: "string.prototype.matchall@npm:4.0.5"
+"string.prototype.matchall@npm:^4.0.10":
+  version: 4.0.11
+  resolution: "string.prototype.matchall@npm:4.0.11"
   dependencies:
   dependencies:
-    call-bind: ^1.0.2
-    define-properties: ^1.1.3
-    es-abstract: ^1.18.2
-    get-intrinsic: ^1.1.1
-    has-symbols: ^1.0.2
-    internal-slot: ^1.0.3
-    regexp.prototype.flags: ^1.3.1
-    side-channel: ^1.0.4
-  checksum: 0a9d64661ecf089e7712aed18a4b0d7e4093ae1dfc6d8134747a98271564065a2a667a3408fced4a77137528b3b2c0efe9d37868acae000ee13d0857a3d0f430
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.2
+    es-errors: ^1.3.0
+    es-object-atoms: ^1.0.0
+    get-intrinsic: ^1.2.4
+    gopd: ^1.0.1
+    has-symbols: ^1.0.3
+    internal-slot: ^1.0.7
+    regexp.prototype.flags: ^1.5.2
+    set-function-name: ^2.0.2
+    side-channel: ^1.0.6
+  checksum: 6ac6566ed065c0c8489c91156078ca077db8ff64d683fda97ae652d00c52dfa5f39aaab0a710d8243031a857fd2c7c511e38b45524796764d25472d10d7075ae
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -17761,6 +18885,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"string.prototype.trim@npm:^1.2.9":
+  version: 1.2.9
+  resolution: "string.prototype.trim@npm:1.2.9"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-abstract: ^1.23.0
+    es-object-atoms: ^1.0.0
+  checksum: ea2df6ec1e914c9d4e2dc856fa08228e8b1be59b59e50b17578c94a66a176888f417264bb763d4aac638ad3b3dad56e7a03d9317086a178078d131aa293ba193
+  languageName: node
+  linkType: hard
+
 "string.prototype.trimend@npm:^1.0.4":
   version: 1.0.4
   resolution: "string.prototype.trimend@npm:1.0.4"
 "string.prototype.trimend@npm:^1.0.4":
   version: 1.0.4
   resolution: "string.prototype.trimend@npm:1.0.4"
@@ -17771,6 +18907,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"string.prototype.trimend@npm:^1.0.8":
+  version: 1.0.8
+  resolution: "string.prototype.trimend@npm:1.0.8"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-object-atoms: ^1.0.0
+  checksum: cc3bd2de08d8968a28787deba9a3cb3f17ca5f9f770c91e7e8fa3e7d47f079bad70fadce16f05dda9f261788be2c6e84a942f618c3bed31e42abc5c1084f8dfd
+  languageName: node
+  linkType: hard
+
 "string.prototype.trimstart@npm:^1.0.4":
   version: 1.0.4
   resolution: "string.prototype.trimstart@npm:1.0.4"
 "string.prototype.trimstart@npm:^1.0.4":
   version: 1.0.4
   resolution: "string.prototype.trimstart@npm:1.0.4"
@@ -17781,6 +18928,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"string.prototype.trimstart@npm:^1.0.8":
+  version: 1.0.8
+  resolution: "string.prototype.trimstart@npm:1.0.8"
+  dependencies:
+    call-bind: ^1.0.7
+    define-properties: ^1.2.1
+    es-object-atoms: ^1.0.0
+  checksum: df1007a7f580a49d692375d996521dc14fd103acda7f3034b3c558a60b82beeed3a64fa91e494e164581793a8ab0ae2f59578a49896a7af6583c1f20472bce96
+  languageName: node
+  linkType: hard
+
 "string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1":
   version: 1.3.0
   resolution: "string_decoder@npm:1.3.0"
 "string_decoder@npm:^1.0.0, string_decoder@npm:^1.1.1":
   version: 1.3.0
   resolution: "string_decoder@npm:1.3.0"
@@ -17819,7 +18977,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1":
+"strip-ansi@npm:^3.0.1":
   version: 3.0.1
   resolution: "strip-ansi@npm:3.0.1"
   dependencies:
   version: 3.0.1
   resolution: "strip-ansi@npm:3.0.1"
   dependencies:
@@ -17828,15 +18986,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"strip-ansi@npm:^4.0.0":
-  version: 4.0.0
-  resolution: "strip-ansi@npm:4.0.0"
-  dependencies:
-    ansi-regex: ^3.0.0
-  checksum: d9186e6c0cf78f25274f6750ee5e4a5725fb91b70fdd79aa5fe648eab092a0ec5b9621b22d69d4534a56319f75d8944efbd84e3afa8d4ad1b9a9491f12c84eca
-  languageName: node
-  linkType: hard
-
 "strip-ansi@npm:^5.0.0, strip-ansi@npm:^5.1.0, strip-ansi@npm:^5.2.0":
   version: 5.2.0
   resolution: "strip-ansi@npm:5.2.0"
 "strip-ansi@npm:^5.0.0, strip-ansi@npm:^5.1.0, strip-ansi@npm:^5.2.0":
   version: 5.2.0
   resolution: "strip-ansi@npm:5.2.0"
@@ -17855,15 +19004,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"strip-bom@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "strip-bom@npm:2.0.0"
-  dependencies:
-    is-utf8: ^0.2.0
-  checksum: 08efb746bc67b10814cd03d79eb31bac633393a782e3f35efbc1b61b5165d3806d03332a97f362822cf0d4dd14ba2e12707fcff44fe1c870c48a063a0c9e4944
-  languageName: node
-  linkType: hard
-
 "strip-bom@npm:^3.0.0":
   version: 3.0.0
   resolution: "strip-bom@npm:3.0.0"
 "strip-bom@npm:^3.0.0":
   version: 3.0.0
   resolution: "strip-bom@npm:3.0.0"
@@ -17871,6 +19011,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"strip-bom@npm:^4.0.0":
+  version: 4.0.0
+  resolution: "strip-bom@npm:4.0.0"
+  checksum: 9dbcfbaf503c57c06af15fe2c8176fb1bf3af5ff65003851a102749f875a6dbe0ab3b30115eccf6e805e9d756830d3e40ec508b62b3f1ddf3761a20ebe29d3f3
+  languageName: node
+  linkType: hard
+
 "strip-comments@npm:^1.0.2":
   version: 1.0.2
   resolution: "strip-comments@npm:1.0.2"
 "strip-comments@npm:^1.0.2":
   version: 1.0.2
   resolution: "strip-comments@npm:1.0.2"
@@ -17895,17 +19042,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"strip-indent@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "strip-indent@npm:1.0.1"
-  dependencies:
-    get-stdin: ^4.0.1
-  bin:
-    strip-indent: cli.js
-  checksum: 81ad9a0b8a558bdbd05b66c6c437b9ab364aa2b5479ed89969ca7908e680e21b043d40229558c434b22b3d640622e39b66288e0456d601981ac9289de9700fbd
-  languageName: node
-  linkType: hard
-
 "strip-indent@npm:^3.0.0":
   version: 3.0.0
   resolution: "strip-indent@npm:3.0.0"
 "strip-indent@npm:^3.0.0":
   version: 3.0.0
   resolution: "strip-indent@npm:3.0.0"
@@ -17915,20 +19051,22 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"strip-json-comments@npm:^3.0.1":
+"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1":
   version: 3.1.1
   resolution: "strip-json-comments@npm:3.1.1"
   checksum: 492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443
   languageName: node
   linkType: hard
 
   version: 3.1.1
   resolution: "strip-json-comments@npm:3.1.1"
   checksum: 492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443
   languageName: node
   linkType: hard
 
-"style-loader@npm:0.23.1":
-  version: 0.23.1
-  resolution: "style-loader@npm:0.23.1"
+"style-loader@npm:1.3.0":
+  version: 1.3.0
+  resolution: "style-loader@npm:1.3.0"
   dependencies:
   dependencies:
-    loader-utils: ^1.1.0
-    schema-utils: ^1.0.0
-  checksum: 0a513a2d881e88bbfd574750df3dc61f57424684458d94cb6ae41e635d03abfa8974bb591eab9051650082c5f5502994dc17c7ca9fb0fc9e8d31f651f6737479
+    loader-utils: ^2.0.0
+    schema-utils: ^2.7.0
+  peerDependencies:
+    webpack: ^4.0.0 || ^5.0.0
+  checksum: 1be9e8705307f5b8eb89e80f3703fa27296dccec349d790eace7aabe212f08c7c8f3ea6b6cb97bc53e82fbebfb9aa0689259671a8315f4655e24a850781e062a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -17970,13 +19108,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"supports-color@npm:^2.0.0":
-  version: 2.0.0
-  resolution: "supports-color@npm:2.0.0"
-  checksum: 602538c5812b9006404370b5a4b885d3e2a1f6567d314f8b4a41974ffe7d08e525bf92ae0f9c7030e3b4c78e4e34ace55d6a67a74f1571bc205959f5972f88f0
-  languageName: node
-  linkType: hard
-
 "supports-color@npm:^5.3.0, supports-color@npm:^5.5.0":
   version: 5.5.0
   resolution: "supports-color@npm:5.5.0"
 "supports-color@npm:^5.3.0, supports-color@npm:^5.5.0":
   version: 5.5.0
   resolution: "supports-color@npm:5.5.0"
@@ -18004,7 +19135,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"supports-color@npm:^8.1.1":
+"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1":
   version: 8.1.1
   resolution: "supports-color@npm:8.1.1"
   dependencies:
   version: 8.1.1
   resolution: "supports-color@npm:8.1.1"
   dependencies:
@@ -18013,7 +19144,24 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"svg-parser@npm:^2.0.0":
+"supports-hyperlinks@npm:^2.0.0":
+  version: 2.3.0
+  resolution: "supports-hyperlinks@npm:2.3.0"
+  dependencies:
+    has-flag: ^4.0.0
+    supports-color: ^7.0.0
+  checksum: 9ee0de3c8ce919d453511b2b1588a8205bd429d98af94a01df87411391010fe22ca463f268c84b2ce2abad019dfff8452aa02806eeb5c905a8d7ad5c4f4c52b8
+  languageName: node
+  linkType: hard
+
+"supports-preserve-symlinks-flag@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "supports-preserve-symlinks-flag@npm:1.0.0"
+  checksum: 53b1e247e68e05db7b3808b99b892bd36fb096e6fba213a06da7fab22045e97597db425c724f2bbd6c99a3c295e1e73f3e4de78592289f38431049e1277ca0ae
+  languageName: node
+  linkType: hard
+
+"svg-parser@npm:^2.0.2":
   version: 2.0.4
   resolution: "svg-parser@npm:2.0.4"
   checksum: b3de6653048212f2ae7afe4a423e04a76ec6d2d06e1bf7eacc618a7c5f7df7faa5105561c57b94579ec831fbbdbf5f190ba56a9205ff39ed13eabdf8ab086ddf
   version: 2.0.4
   resolution: "svg-parser@npm:2.0.4"
   checksum: b3de6653048212f2ae7afe4a423e04a76ec6d2d06e1bf7eacc618a7c5f7df7faa5105561c57b94579ec831fbbdbf5f190ba56a9205ff39ed13eabdf8ab086ddf
@@ -18050,7 +19198,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"symbol-tree@npm:^3.2.2":
+"symbol-tree@npm:^3.2.4":
   version: 3.2.4
   resolution: "symbol-tree@npm:3.2.4"
   checksum: 6e8fc7e1486b8b54bea91199d9535bb72f10842e40c79e882fc94fb7b14b89866adf2fd79efa5ebb5b658bc07fb459ccce5ac0e99ef3d72f474e74aaf284029d
   version: 3.2.4
   resolution: "symbol-tree@npm:3.2.4"
   checksum: 6e8fc7e1486b8b54bea91199d9535bb72f10842e40c79e882fc94fb7b14b89866adf2fd79efa5ebb5b658bc07fb459ccce5ac0e99ef3d72f474e74aaf284029d
@@ -18064,15 +19212,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"table@npm:^5.2.3":
-  version: 5.4.6
-  resolution: "table@npm:5.4.6"
+"table@npm:^6.0.9":
+  version: 6.8.2
+  resolution: "table@npm:6.8.2"
   dependencies:
   dependencies:
-    ajv: ^6.10.2
-    lodash: ^4.17.14
-    slice-ansi: ^2.1.0
-    string-width: ^3.0.0
-  checksum: 9e35d3efa788edc17237eef8852f8e4b9178efd65a7d115141777b2ee77df4b7796c05f4ed3712d858f98894ac5935a481ceeb6dcb9895e2f67a61cce0e63b6c
+    ajv: ^8.0.1
+    lodash.truncate: ^4.4.2
+    slice-ansi: ^4.0.0
+    string-width: ^4.2.3
+    strip-ansi: ^6.0.1
+  checksum: 61188652f53a980d1759ca460ca8dea5c5322aece3210457e7084882f053c2b6a870041295e08a82cb1d676e31b056406845d94b0abf3c79a4b104777bec413b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18084,8 +19233,8 @@ __metadata:
   linkType: hard
 
 "tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2":
   linkType: hard
 
 "tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2":
-  version: 6.2.0
-  resolution: "tar@npm:6.2.0"
+  version: 6.2.1
+  resolution: "tar@npm:6.2.1"
   dependencies:
     chownr: ^2.0.0
     fs-minipass: ^2.0.0
   dependencies:
     chownr: ^2.0.0
     fs-minipass: ^2.0.0
@@ -18093,26 +19242,54 @@ __metadata:
     minizlib: ^2.1.1
     mkdirp: ^1.0.3
     yallist: ^4.0.0
     minizlib: ^2.1.1
     mkdirp: ^1.0.3
     yallist: ^4.0.0
-  checksum: db4d9fe74a2082c3a5016630092c54c8375ff3b280186938cfd104f2e089c4fd9bad58688ef6be9cf186a889671bf355c7cda38f09bbf60604b281715ca57f5c
+  checksum: f1322768c9741a25356c11373bce918483f40fa9a25c69c59410c8a1247632487edef5fe76c5f12ac51a6356d2f1829e96d2bc34098668a2fc34d76050ac2b6c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"terser-webpack-plugin@npm:2.3.8":
-  version: 2.3.8
-  resolution: "terser-webpack-plugin@npm:2.3.8"
+"temp-dir@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "temp-dir@npm:1.0.0"
+  checksum: cb2b58ddfb12efa83e939091386ad73b425c9a8487ea0095fe4653192a40d49184a771a1beba99045fbd011e389fd563122d79f54f82be86a55620667e08a6b2
+  languageName: node
+  linkType: hard
+
+"tempy@npm:^0.3.0":
+  version: 0.3.0
+  resolution: "tempy@npm:0.3.0"
+  dependencies:
+    temp-dir: ^1.0.0
+    type-fest: ^0.3.1
+    unique-string: ^1.0.0
+  checksum: f81ef72a7ee6d512439af8d8891e4fc6595309451910d7ac9d760f1242a1f87272b2b97c830c8f74aaa93a3aa550483bb16db17e6345601f64d614d240edc322
+  languageName: node
+  linkType: hard
+
+"terminal-link@npm:^2.0.0":
+  version: 2.1.1
+  resolution: "terminal-link@npm:2.1.1"
+  dependencies:
+    ansi-escapes: ^4.2.1
+    supports-hyperlinks: ^2.0.0
+  checksum: ce3d2cd3a438c4a9453947aa664581519173ea40e77e2534d08c088ee6dda449eabdbe0a76d2a516b8b73c33262fedd10d5270ccf7576ae316e3db170ce6562f
+  languageName: node
+  linkType: hard
+
+"terser-webpack-plugin@npm:4.2.3":
+  version: 4.2.3
+  resolution: "terser-webpack-plugin@npm:4.2.3"
   dependencies:
   dependencies:
-    cacache: ^13.0.1
+    cacache: ^15.0.5
     find-cache-dir: ^3.3.1
     find-cache-dir: ^3.3.1
-    jest-worker: ^25.4.0
-    p-limit: ^2.3.0
-    schema-utils: ^2.6.6
-    serialize-javascript: ^4.0.0
+    jest-worker: ^26.5.0
+    p-limit: ^3.0.2
+    schema-utils: ^3.0.0
+    serialize-javascript: ^5.0.1
     source-map: ^0.6.1
     source-map: ^0.6.1
-    terser: ^4.6.12
+    terser: ^5.3.4
     webpack-sources: ^1.4.3
   peerDependencies:
     webpack: ^4.0.0 || ^5.0.0
     webpack-sources: ^1.4.3
   peerDependencies:
     webpack: ^4.0.0 || ^5.0.0
-  checksum: a772d7d58a4730b619f71c4a8d7cf1fa90ded0d01b4fb9a094437c3380e3c35ce78caa030c2867a10cdd12527dfc2fb46bee949bd067ee0cd41e9890cbd85263
+  checksum: ec1b3a85e2645c57e359d5e4831f3e1d78eca2a0c94b156db70eb846ae35b5e6e98ad8784b12e153fc273e57445ce69d017075bbe9fc42868a258ef121f11537
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18135,7 +19312,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"terser@npm:^4.1.2, terser@npm:^4.6.12, terser@npm:^4.6.3":
+"terser@npm:^4.1.2, terser@npm:^4.6.2, terser@npm:^4.6.3":
   version: 4.8.1
   resolution: "terser@npm:4.8.1"
   dependencies:
   version: 4.8.1
   resolution: "terser@npm:4.8.1"
   dependencies:
@@ -18148,15 +19325,28 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"test-exclude@npm:^5.2.3":
-  version: 5.2.3
-  resolution: "test-exclude@npm:5.2.3"
+"terser@npm:^5.3.4":
+  version: 5.30.3
+  resolution: "terser@npm:5.30.3"
   dependencies:
   dependencies:
-    glob: ^7.1.3
+    "@jridgewell/source-map": ^0.3.3
+    acorn: ^8.8.2
+    commander: ^2.20.0
+    source-map-support: ~0.5.20
+  bin:
+    terser: bin/terser
+  checksum: 8c680ed32a948f806fade0969c52aab94b6de174e4a78610f5d3abf9993b161eb19b88b2ceadff09b153858727c02deb6709635e4bfbd519f67d54e0394e2983
+  languageName: node
+  linkType: hard
+
+"test-exclude@npm:^6.0.0":
+  version: 6.0.0
+  resolution: "test-exclude@npm:6.0.0"
+  dependencies:
+    "@istanbuljs/schema": ^0.1.2
+    glob: ^7.1.4
     minimatch: ^3.0.4
     minimatch: ^3.0.4
-    read-pkg-up: ^4.0.0
-    require-main-filename: ^2.0.0
-  checksum: 3a67bee51b0afb0b7a51b649a7dacd920d929de2b3eccb52fa818f0b0bf2ebfced1d1a77a206b74f95c50f6682e313eedb8000cfdd5ac2f9cc6ed8a32fc4ff2e
+  checksum: 3b34a3d77165a2cb82b34014b3aba93b1c4637a5011807557dc2f3da826c59975a5ccad765721c4648b39817e3472789f9b0fa98fc854c5c1c7a1e632aacdc28
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18167,10 +19357,10 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"throat@npm:^4.0.0":
-  version: 4.1.0
-  resolution: "throat@npm:4.1.0"
-  checksum: 43519b0cea6d3b2a8fe056fcbc319e289037be67d2204d4d33513d20d6ee9da6255f7ba8c89e2ec8c97b0f188a910b8666def38d1058d2bf4a39613812c36d98
+"throat@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "throat@npm:5.0.0"
+  checksum: 031ff7f4431618036c1dedd99c8aa82f5c33077320a8358ed829e84b320783781d1869fe58e8f76e948306803de966f5f7573766a437562c9f5c033297ad2fe2
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18191,7 +19381,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"through@npm:^2.3.6, through@npm:^2.3.8":
+"through@npm:^2.3.8":
   version: 2.3.8
   resolution: "through@npm:2.3.8"
   checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd
   version: 2.3.8
   resolution: "through@npm:2.3.8"
   checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd
@@ -18244,15 +19434,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"tmp@npm:^0.0.33":
-  version: 0.0.33
-  resolution: "tmp@npm:0.0.33"
-  dependencies:
-    os-tmpdir: ~1.0.2
-  checksum: 902d7aceb74453ea02abbf58c203f4a8fc1cead89b60b31e354f74ed5b3fb09ea817f94fb310f884a5d16987dd9fa5a735412a7c2dd088dd3d415aa819ae3a28
-  languageName: node
-  linkType: hard
-
 "tmp@npm:~0.2.1":
   version: 0.2.1
   resolution: "tmp@npm:0.2.1"
 "tmp@npm:~0.2.1":
   version: 0.2.1
   resolution: "tmp@npm:0.2.1"
@@ -18272,14 +19453,7 @@ __metadata:
 "to-arraybuffer@npm:^1.0.0":
   version: 1.0.1
   resolution: "to-arraybuffer@npm:1.0.1"
 "to-arraybuffer@npm:^1.0.0":
   version: 1.0.1
   resolution: "to-arraybuffer@npm:1.0.1"
-  checksum: 31433c10b388722729f5da04c6b2a06f40dc84f797bb802a5a171ced1e599454099c6c5bc5118f4b9105e7d049d3ad9d0f71182b77650e4fdb04539695489941
-  languageName: node
-  linkType: hard
-
-"to-fast-properties@npm:^1.0.3":
-  version: 1.0.3
-  resolution: "to-fast-properties@npm:1.0.3"
-  checksum: bd0abb58c4722851df63419de3f6d901d5118f0440d3f71293ed776dd363f2657edaaf2dc470e3f6b7b48eb84aa411193b60db8a4a552adac30de9516c5cc580
+  checksum: 31433c10b388722729f5da04c6b2a06f40dc84f797bb802a5a171ced1e599454099c6c5bc5118f4b9105e7d049d3ad9d0f71182b77650e4fdb04539695489941
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18337,14 +19511,14 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"toidentifier@npm:1.0.0":
-  version: 1.0.0
-  resolution: "toidentifier@npm:1.0.0"
-  checksum: 199e6bfca1531d49b3506cff02353d53ec987c9ee10ee272ca6484ed97f1fc10fb77c6c009079ca16d5c5be4a10378178c3cacdb41ce9ec954c3297c74c6053e
+"toidentifier@npm:1.0.1":
+  version: 1.0.1
+  resolution: "toidentifier@npm:1.0.1"
+  checksum: 952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"tough-cookie@npm:^2.3.3, tough-cookie@npm:^2.3.4, tough-cookie@npm:^2.5.0, tough-cookie@npm:~2.5.0":
+"tough-cookie@npm:^2.3.3, tough-cookie@npm:~2.5.0":
   version: 2.5.0
   resolution: "tough-cookie@npm:2.5.0"
   dependencies:
   version: 2.5.0
   resolution: "tough-cookie@npm:2.5.0"
   dependencies:
@@ -18354,7 +19528,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"tough-cookie@npm:^4.1.3":
+"tough-cookie@npm:^4.0.0, tough-cookie@npm:^4.1.3":
   version: 4.1.3
   resolution: "tough-cookie@npm:4.1.3"
   dependencies:
   version: 4.1.3
   resolution: "tough-cookie@npm:4.1.3"
   dependencies:
@@ -18366,12 +19540,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"tr46@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "tr46@npm:1.0.1"
+"tr46@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "tr46@npm:2.1.0"
   dependencies:
   dependencies:
-    punycode: ^2.1.0
-  checksum: 96d4ed46bc161db75dbf9247a236ea0bfcaf5758baae6749e92afab0bc5a09cb59af21788ede7e55080f2bf02dce3e4a8f2a484cc45164e29f4b5e68f7cbcc1a
+    punycode: ^2.1.1
+  checksum: ffe6049b9dca3ae329b059aada7f515b0f0064c611b39b51ff6b53897e954650f6f63d9319c6c008d36ead477c7b55e5f64c9dc60588ddc91ff720d64eb710b3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18382,29 +19556,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"trim-newlines@npm:^1.0.0, trim-newlines@npm:^3.0.0":
+"trim-newlines@npm:^3.0.0":
   version: 3.0.1
   resolution: "trim-newlines@npm:3.0.1"
   checksum: b530f3fadf78e570cf3c761fb74fef655beff6b0f84b29209bac6c9622db75ad1417f4a7b5d54c96605dcd72734ad44526fef9f396807b90839449eb543c6206
   languageName: node
   linkType: hard
 
   version: 3.0.1
   resolution: "trim-newlines@npm:3.0.1"
   checksum: b530f3fadf78e570cf3c761fb74fef655beff6b0f84b29209bac6c9622db75ad1417f4a7b5d54c96605dcd72734ad44526fef9f396807b90839449eb543c6206
   languageName: node
   linkType: hard
 
-"trim-right@npm:^1.0.1":
-  version: 1.0.1
-  resolution: "trim-right@npm:1.0.1"
-  checksum: 9120af534e006a7424a4f9358710e6e707887b6ccf7ea69e50d6ac6464db1fe22268400def01752f09769025d480395159778153fb98d4a2f6f40d4cf5d4f3b6
-  languageName: node
-  linkType: hard
-
-"true-case-path@npm:^1.0.2":
-  version: 1.0.3
-  resolution: "true-case-path@npm:1.0.3"
-  dependencies:
-    glob: ^7.1.2
-  checksum: 2e2e3bf37b4b05db2e2a1d60329960a4aa697ad7a89bd97c66f5f4da83977897c29c704276e62bca62d055d8078065bc08a1c7a01f409de11c6592af8b442cbe
-  languageName: node
-  linkType: hard
-
 "true-case-path@npm:^2.2.1":
   version: 2.2.1
   resolution: "true-case-path@npm:2.2.1"
 "true-case-path@npm:^2.2.1":
   version: 2.2.1
   resolution: "true-case-path@npm:2.2.1"
@@ -18412,6 +19570,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"tryer@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "tryer@npm:1.0.1"
+  checksum: 1cf14d7f67c79613f054b569bfc9a89c7020d331573a812dfcf7437244e8f8e6eb6893b210cbd9cc217f67c1d72617f89793df231e4fe7d53634ed91cf3a89d1
+  languageName: node
+  linkType: hard
+
 "ts-mock-imports@npm:1.3.7":
   version: 1.3.7
   resolution: "ts-mock-imports@npm:1.3.7"
 "ts-mock-imports@npm:1.3.7":
   version: 1.3.7
   resolution: "ts-mock-imports@npm:1.3.7"
@@ -18422,17 +19587,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ts-pnp@npm:1.1.6":
-  version: 1.1.6
-  resolution: "ts-pnp@npm:1.1.6"
-  peerDependenciesMeta:
-    typescript:
-      optional: true
-  checksum: 78a05096ac3b1391bbb8d0b292d76d433f841fb3e698b9bdff961ca1f794e0644e3f0b93d78c6b42dd0e90ef34676cf164855e6010f7b71fed63f2102a387eb9
-  languageName: node
-  linkType: hard
-
-"ts-pnp@npm:^1.1.6":
+"ts-pnp@npm:1.2.0, ts-pnp@npm:^1.1.6":
   version: 1.2.0
   resolution: "ts-pnp@npm:1.2.0"
   peerDependenciesMeta:
   version: 1.2.0
   resolution: "ts-pnp@npm:1.2.0"
   peerDependenciesMeta:
@@ -18442,6 +19597,18 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"tsconfig-paths@npm:^3.15.0":
+  version: 3.15.0
+  resolution: "tsconfig-paths@npm:3.15.0"
+  dependencies:
+    "@types/json5": ^0.0.29
+    json5: ^1.0.2
+    minimist: ^1.2.6
+    strip-bom: ^3.0.0
+  checksum: 59f35407a390d9482b320451f52a411a256a130ff0e7543d18c6f20afab29ac19fbe55c360a93d6476213cc335a4d76ce90f67df54c4e9037f7d240920832201
+  languageName: node
+  linkType: hard
+
 "tslib@npm:2.5.0":
   version: 2.5.0
   resolution: "tslib@npm:2.5.0"
 "tslib@npm:2.5.0":
   version: 2.5.0
   resolution: "tslib@npm:2.5.0"
@@ -18536,7 +19703,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"tsutils@npm:^3.0.0, tsutils@npm:^3.21.0":
+"tsutils@npm:^3.0.0, tsutils@npm:^3.17.1, tsutils@npm:^3.21.0":
   version: 3.21.0
   resolution: "tsutils@npm:3.21.0"
   dependencies:
   version: 3.21.0
   resolution: "tsutils@npm:3.21.0"
   dependencies:
@@ -18570,6 +19737,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
+  version: 0.4.0
+  resolution: "type-check@npm:0.4.0"
+  dependencies:
+    prelude-ls: ^1.2.1
+  checksum: ec688ebfc9c45d0c30412e41ca9c0cdbd704580eb3a9ccf07b9b576094d7b86a012baebc95681999dd38f4f444afd28504cb3a89f2ef16b31d4ab61a0739025a
+  languageName: node
+  linkType: hard
+
 "type-check@npm:~0.3.2":
   version: 0.3.2
   resolution: "type-check@npm:0.3.2"
 "type-check@npm:~0.3.2":
   version: 0.3.2
   resolution: "type-check@npm:0.3.2"
@@ -18593,6 +19769,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"type-fest@npm:^0.20.2":
+  version: 0.20.2
+  resolution: "type-fest@npm:0.20.2"
+  checksum: 4fb3272df21ad1c552486f8a2f8e115c09a521ad7a8db3d56d53718d0c907b62c6e9141ba5f584af3f6830d0872c521357e512381f24f7c44acae583ad517d73
+  languageName: node
+  linkType: hard
+
 "type-fest@npm:^0.21.3":
   version: 0.21.3
   resolution: "type-fest@npm:0.21.3"
 "type-fest@npm:^0.21.3":
   version: 0.21.3
   resolution: "type-fest@npm:0.21.3"
@@ -18600,6 +19783,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"type-fest@npm:^0.3.1":
+  version: 0.3.1
+  resolution: "type-fest@npm:0.3.1"
+  checksum: 347ff46c2285616635cb59f722e7f396bee81b8988b6fc1f1536b725077f2abf6ccfa22ab7a78e9b6ce7debea0e6614bbf5946cbec6674ec1bde12113af3a65c
+  languageName: node
+  linkType: hard
+
 "type-fest@npm:^0.6.0":
   version: 0.6.0
   resolution: "type-fest@npm:0.6.0"
 "type-fest@npm:^0.6.0":
   version: 0.6.0
   resolution: "type-fest@npm:0.6.0"
@@ -18614,7 +19804,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"type-is@npm:~1.6.17, type-is@npm:~1.6.18":
+"type-is@npm:~1.6.18":
   version: 1.6.18
   resolution: "type-is@npm:1.6.18"
   dependencies:
   version: 1.6.18
   resolution: "type-is@npm:1.6.18"
   dependencies:
@@ -18638,6 +19828,74 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"type@npm:^2.7.2":
+  version: 2.7.2
+  resolution: "type@npm:2.7.2"
+  checksum: 0f42379a8adb67fe529add238a3e3d16699d95b42d01adfe7b9a7c5da297f5c1ba93de39265ba30ffeb37dfd0afb3fb66ae09f58d6515da442219c086219f6f4
+  languageName: node
+  linkType: hard
+
+"typed-array-buffer@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "typed-array-buffer@npm:1.0.2"
+  dependencies:
+    call-bind: ^1.0.7
+    es-errors: ^1.3.0
+    is-typed-array: ^1.1.13
+  checksum: 02ffc185d29c6df07968272b15d5319a1610817916ec8d4cd670ded5d1efe72901541ff2202fcc622730d8a549c76e198a2f74e312eabbfb712ed907d45cbb0b
+  languageName: node
+  linkType: hard
+
+"typed-array-byte-length@npm:^1.0.1":
+  version: 1.0.1
+  resolution: "typed-array-byte-length@npm:1.0.1"
+  dependencies:
+    call-bind: ^1.0.7
+    for-each: ^0.3.3
+    gopd: ^1.0.1
+    has-proto: ^1.0.3
+    is-typed-array: ^1.1.13
+  checksum: f65e5ecd1cf76b1a2d0d6f631f3ea3cdb5e08da106c6703ffe687d583e49954d570cc80434816d3746e18be889ffe53c58bf3e538081ea4077c26a41055b216d
+  languageName: node
+  linkType: hard
+
+"typed-array-byte-offset@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "typed-array-byte-offset@npm:1.0.2"
+  dependencies:
+    available-typed-arrays: ^1.0.7
+    call-bind: ^1.0.7
+    for-each: ^0.3.3
+    gopd: ^1.0.1
+    has-proto: ^1.0.3
+    is-typed-array: ^1.1.13
+  checksum: c8645c8794a621a0adcc142e0e2c57b1823bbfa4d590ad2c76b266aa3823895cf7afb9a893bf6685e18454ab1b0241e1a8d885a2d1340948efa4b56add4b5f67
+  languageName: node
+  linkType: hard
+
+"typed-array-length@npm:^1.0.6":
+  version: 1.0.6
+  resolution: "typed-array-length@npm:1.0.6"
+  dependencies:
+    call-bind: ^1.0.7
+    for-each: ^0.3.3
+    gopd: ^1.0.1
+    has-proto: ^1.0.3
+    is-typed-array: ^1.1.13
+    possible-typed-array-names: ^1.0.0
+  checksum: f0315e5b8f0168c29d390ff410ad13e4d511c78e6006df4a104576844812ee447fcc32daab1f3a76c9ef4f64eff808e134528b5b2439de335586b392e9750e5c
+  languageName: node
+  linkType: hard
+
+"typedarray-to-buffer@npm:^3.1.5":
+  version: 3.1.5
+  resolution: "typedarray-to-buffer@npm:3.1.5"
+  dependencies:
+    is-typedarray: ^1.0.0
+  checksum: 99c11aaa8f45189fcfba6b8a4825fd684a321caa9bd7a76a27cf0c7732c174d198b99f449c52c3818107430b5f41c0ccbbfb75cb2ee3ca4a9451710986d61a60
+  languageName: node
+  linkType: hard
+
 "typedarray@npm:^0.0.6":
   version: 0.0.6
   resolution: "typedarray@npm:0.0.6"
 "typedarray@npm:^0.0.6":
   version: 0.0.6
   resolution: "typedarray@npm:0.0.6"
@@ -18684,34 +19942,53 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"unicode-canonical-property-names-ecmascript@npm:^1.0.4":
-  version: 1.0.4
-  resolution: "unicode-canonical-property-names-ecmascript@npm:1.0.4"
-  checksum: cc1973b18d0e1a151711e5551f87f4b3086c4f542cd5142aa691307d5720fd725fa7d36c24e12e944e108b91c72554237b0c236772d35592839434da5506c40f
+"unbox-primitive@npm:^1.0.2":
+  version: 1.0.2
+  resolution: "unbox-primitive@npm:1.0.2"
+  dependencies:
+    call-bind: ^1.0.2
+    has-bigints: ^1.0.2
+    has-symbols: ^1.0.3
+    which-boxed-primitive: ^1.0.2
+  checksum: b7a1cf5862b5e4b5deb091672ffa579aa274f648410009c81cca63fed3b62b610c4f3b773f912ce545bb4e31edc3138975b5bc777fc6e4817dca51affb6380e9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"unicode-match-property-ecmascript@npm:^1.0.4":
-  version: 1.0.4
-  resolution: "unicode-match-property-ecmascript@npm:1.0.4"
+"underscore@npm:1.12.1":
+  version: 1.12.1
+  resolution: "underscore@npm:1.12.1"
+  checksum: ec327603aa112b99fe9d74cd9bf3b3b7451465a9d2610ceab269a532e3f191650ab017903be34dc86fe406a11d04d8905a3b04dd4c129493e51bee09a3f3074c
+  languageName: node
+  linkType: hard
+
+"unicode-canonical-property-names-ecmascript@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0"
+  checksum: 39be078afd014c14dcd957a7a46a60061bc37c4508ba146517f85f60361acf4c7539552645ece25de840e17e293baa5556268d091ca6762747fdd0c705001a45
+  languageName: node
+  linkType: hard
+
+"unicode-match-property-ecmascript@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "unicode-match-property-ecmascript@npm:2.0.0"
   dependencies:
   dependencies:
-    unicode-canonical-property-names-ecmascript: ^1.0.4
-    unicode-property-aliases-ecmascript: ^1.0.4
-  checksum: 08e269fac71b5ace0f8331df9e87b9b533fe97b00c43ea58de69ae81816581490f846050e0c472279a3e7434524feba99915a93816f90dbbc0a30bcbd082da88
+    unicode-canonical-property-names-ecmascript: ^2.0.0
+    unicode-property-aliases-ecmascript: ^2.0.0
+  checksum: 1f34a7434a23df4885b5890ac36c5b2161a809887000be560f56ad4b11126d433c0c1c39baf1016bdabed4ec54829a6190ee37aa24919aa116dc1a5a8a62965a
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"unicode-match-property-value-ecmascript@npm:^1.2.0":
-  version: 1.2.0
-  resolution: "unicode-match-property-value-ecmascript@npm:1.2.0"
-  checksum: 2e663cfec8e2cf317b69613566314979f717034ea8f58a237dd63234795044a87337410064fe839774d71e1d7e12195520e9edd69ed8e28f2a9eb28a2db38595
+"unicode-match-property-value-ecmascript@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "unicode-match-property-value-ecmascript@npm:2.1.0"
+  checksum: 8d6f5f586b9ce1ed0e84a37df6b42fdba1317a05b5df0c249962bd5da89528771e2d149837cad11aa26bcb84c35355cb9f58a10c3d41fa3b899181ece6c85220
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"unicode-property-aliases-ecmascript@npm:^1.0.4":
-  version: 1.1.0
-  resolution: "unicode-property-aliases-ecmascript@npm:1.1.0"
-  checksum: 1a96dc462d251bb1c5237f7bc77956b29f01cefce7f3e7448430742930961557c3d1515a9669715ebb06209bf01072e2f78ba1627247017daa84346414bc02f1
+"unicode-property-aliases-ecmascript@npm:^2.0.0":
+  version: 2.1.0
+  resolution: "unicode-property-aliases-ecmascript@npm:2.1.0"
+  checksum: 243524431893649b62cc674d877bd64ef292d6071dd2fd01ab4d5ad26efbc104ffcd064f93f8a06b7e4ec54c172bf03f6417921a0d8c3a9994161fe1f88f815b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18784,6 +20061,15 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"unique-string@npm:^1.0.0":
+  version: 1.0.0
+  resolution: "unique-string@npm:1.0.0"
+  dependencies:
+    crypto-random-string: ^1.0.0
+  checksum: 588f16bd4ec99b5130f237793d1a5694156adde20460366726573238e41e93b739b87987e863792aeb2392b26f8afb292490ace119c82ed12c46816c9c859f5f
+  languageName: node
+  linkType: hard
+
 "universalify@npm:^0.1.0":
   version: 0.1.2
   resolution: "universalify@npm:0.1.2"
 "universalify@npm:^0.1.0":
   version: 0.1.2
   resolution: "universalify@npm:0.1.2"
@@ -18836,7 +20122,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"upath@npm:^1.1.1":
+"upath@npm:^1.1.1, upath@npm:^1.1.2, upath@npm:^1.2.0":
   version: 1.2.0
   resolution: "upath@npm:1.2.0"
   checksum: 4c05c094797cb733193a0784774dbea5b1889d502fc9f0572164177e185e4a59ba7099bf0b0adf945b232e2ac60363f9bf18aac9b2206fb99cbef971a8455445
   version: 1.2.0
   resolution: "upath@npm:1.2.0"
   checksum: 4c05c094797cb733193a0784774dbea5b1889d502fc9f0572164177e185e4a59ba7099bf0b0adf945b232e2ac60363f9bf18aac9b2206fb99cbef971a8455445
@@ -18873,20 +20159,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"url-loader@npm:2.3.0":
-  version: 2.3.0
-  resolution: "url-loader@npm:2.3.0"
+"url-loader@npm:4.1.1":
+  version: 4.1.1
+  resolution: "url-loader@npm:4.1.1"
   dependencies:
   dependencies:
-    loader-utils: ^1.2.3
-    mime: ^2.4.4
-    schema-utils: ^2.5.0
+    loader-utils: ^2.0.0
+    mime-types: ^2.1.27
+    schema-utils: ^3.0.0
   peerDependencies:
     file-loader: "*"
   peerDependencies:
     file-loader: "*"
-    webpack: ^4.0.0
+    webpack: ^4.0.0 || ^5.0.0
   peerDependenciesMeta:
     file-loader:
       optional: true
   peerDependenciesMeta:
     file-loader:
       optional: true
-  checksum: c0a8a6e728331e2189a6538373b9ce4b5589389805c4e98a0386ee5d18cc4ba4c5dec5514d8c852dd533b857461c30c3efc135ed07b6b31a96c5a6fb812a4757
+  checksum: c1122a992c6cff70a7e56dfc2b7474534d48eb40b2cc75467cde0c6972e7597faf8e43acb4f45f93c2473645dfd803bcbc20960b57544dd1e4c96e77f72ba6fd
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -18934,19 +20220,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"util.promisify@npm:^1.0.0":
-  version: 1.1.1
-  resolution: "util.promisify@npm:1.1.1"
-  dependencies:
-    call-bind: ^1.0.0
-    define-properties: ^1.1.3
-    for-each: ^0.3.3
-    has-symbols: ^1.0.1
-    object.getownpropertydescriptors: ^2.1.1
-  checksum: ea371c30b90576862487ae4efd7182aa5855019549a4019d82629acc2709e8ccb0f38944403eebec622fff8ebb44ac3f46a52d745d5f543d30606132a4905f96
-  languageName: node
-  linkType: hard
-
 "util.promisify@npm:~1.0.0":
   version: 1.0.1
   resolution: "util.promisify@npm:1.0.1"
 "util.promisify@npm:~1.0.0":
   version: 1.0.1
   resolution: "util.promisify@npm:1.0.1"
@@ -19009,7 +20282,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"uuid@npm:^8.3.2":
+"uuid@npm:^8.3.0, uuid@npm:^8.3.2":
   version: 8.3.2
   resolution: "uuid@npm:8.3.2"
   bin:
   version: 8.3.2
   resolution: "uuid@npm:8.3.2"
   bin:
@@ -19025,6 +20298,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"v8-to-istanbul@npm:^7.0.0":
+  version: 7.1.2
+  resolution: "v8-to-istanbul@npm:7.1.2"
+  dependencies:
+    "@types/istanbul-lib-coverage": ^2.0.1
+    convert-source-map: ^1.6.0
+    source-map: ^0.7.3
+  checksum: e52b48764f55aed62ff87f2b5f710c874f992cd1313eac8f438bf65aeeb0689153d85bb76e39514fd90ba3521d6ebea929a8ae1339b6d7b0cf18fb0ed13d8b40
+  languageName: node
+  linkType: hard
+
 "validate-npm-package-license@npm:^3.0.1":
   version: 3.0.4
   resolution: "validate-npm-package-license@npm:3.0.4"
 "validate-npm-package-license@npm:^3.0.1":
   version: 3.0.4
   resolution: "validate-npm-package-license@npm:3.0.4"
@@ -19074,7 +20358,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"w3c-hr-time@npm:^1.0.1":
+"w3c-hr-time@npm:^1.0.2":
   version: 1.0.2
   resolution: "w3c-hr-time@npm:1.0.2"
   dependencies:
   version: 1.0.2
   resolution: "w3c-hr-time@npm:1.0.2"
   dependencies:
@@ -19083,14 +20367,12 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"w3c-xmlserializer@npm:^1.1.2":
-  version: 1.1.2
-  resolution: "w3c-xmlserializer@npm:1.1.2"
+"w3c-xmlserializer@npm:^2.0.0":
+  version: 2.0.0
+  resolution: "w3c-xmlserializer@npm:2.0.0"
   dependencies:
   dependencies:
-    domexception: ^1.0.1
-    webidl-conversions: ^4.0.2
     xml-name-validator: ^3.0.0
     xml-name-validator: ^3.0.0
-  checksum: 1683e083d0dfc1529988f8956510a3a26e90738b41c4df0c7eb95283bfbeabeb492308117dcd32afef2a141e2a959ddf10ce562983d91b9f474a530b9dcdd337
+  checksum: ae25c51cf71f1fb2516df1ab33a481f83461a117565b95e3d0927432522323f93b1b2846cbb60196d337970c421adb604fc2d0d180c6a47a839da01db5b9973b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -19146,7 +20428,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"watchpack@npm:^1.6.0":
+"watchpack@npm:^1.7.4":
   version: 1.7.5
   resolution: "watchpack@npm:1.7.5"
   dependencies:
   version: 1.7.5
   resolution: "watchpack@npm:1.7.5"
   dependencies:
@@ -19179,10 +20461,17 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"webidl-conversions@npm:^4.0.2":
-  version: 4.0.2
-  resolution: "webidl-conversions@npm:4.0.2"
-  checksum: c93d8dfe908a0140a4ae9c0ebc87a33805b416a33ee638a605b551523eec94a9632165e54632f6d57a39c5f948c4bab10e0e066525e9a4b87a79f0d04fbca374
+"webidl-conversions@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "webidl-conversions@npm:5.0.0"
+  checksum: ccf1ec2ca7c0b5671e5440ace4a66806ae09c49016ab821481bec0c05b1b82695082dc0a27d1fe9d804d475a408ba0c691e6803fd21be608e710955d4589cd69
+  languageName: node
+  linkType: hard
+
+"webidl-conversions@npm:^6.1.0":
+  version: 6.1.0
+  resolution: "webidl-conversions@npm:6.1.0"
+  checksum: 1f526507aa491f972a0c1409d07f8444e1d28778dfa269a9971f2e157182f3d496dc33296e4ed45b157fdb3bf535bb90c90bf10c50dcf1dd6caacb2a34cc84fb
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -19273,7 +20562,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"webpack-sources@npm:^1.1.0, webpack-sources@npm:^1.4.0, webpack-sources@npm:^1.4.1, webpack-sources@npm:^1.4.3":
+"webpack-sources@npm:^1.1.0, webpack-sources@npm:^1.3.0, webpack-sources@npm:^1.4.0, webpack-sources@npm:^1.4.1, webpack-sources@npm:^1.4.3":
   version: 1.4.3
   resolution: "webpack-sources@npm:1.4.3"
   dependencies:
   version: 1.4.3
   resolution: "webpack-sources@npm:1.4.3"
   dependencies:
@@ -19283,36 +20572,41 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"webpack@npm:4.42.0":
-  version: 4.42.0
-  resolution: "webpack@npm:4.42.0"
+"webpack@npm:4.44.2":
+  version: 4.44.2
+  resolution: "webpack@npm:4.44.2"
   dependencies:
   dependencies:
-    "@webassemblyjs/ast": 1.8.5
-    "@webassemblyjs/helper-module-context": 1.8.5
-    "@webassemblyjs/wasm-edit": 1.8.5
-    "@webassemblyjs/wasm-parser": 1.8.5
-    acorn: ^6.2.1
+    "@webassemblyjs/ast": 1.9.0
+    "@webassemblyjs/helper-module-context": 1.9.0
+    "@webassemblyjs/wasm-edit": 1.9.0
+    "@webassemblyjs/wasm-parser": 1.9.0
+    acorn: ^6.4.1
     ajv: ^6.10.2
     ajv-keywords: ^3.4.1
     chrome-trace-event: ^1.0.2
     ajv: ^6.10.2
     ajv-keywords: ^3.4.1
     chrome-trace-event: ^1.0.2
-    enhanced-resolve: ^4.1.0
+    enhanced-resolve: ^4.3.0
     eslint-scope: ^4.0.3
     json-parse-better-errors: ^1.0.2
     loader-runner: ^2.4.0
     loader-utils: ^1.2.3
     memory-fs: ^0.4.1
     micromatch: ^3.1.10
     eslint-scope: ^4.0.3
     json-parse-better-errors: ^1.0.2
     loader-runner: ^2.4.0
     loader-utils: ^1.2.3
     memory-fs: ^0.4.1
     micromatch: ^3.1.10
-    mkdirp: ^0.5.1
+    mkdirp: ^0.5.3
     neo-async: ^2.6.1
     node-libs-browser: ^2.2.1
     schema-utils: ^1.0.0
     tapable: ^1.1.3
     terser-webpack-plugin: ^1.4.3
     neo-async: ^2.6.1
     node-libs-browser: ^2.2.1
     schema-utils: ^1.0.0
     tapable: ^1.1.3
     terser-webpack-plugin: ^1.4.3
-    watchpack: ^1.6.0
+    watchpack: ^1.7.4
     webpack-sources: ^1.4.1
     webpack-sources: ^1.4.1
+  peerDependenciesMeta:
+    webpack-cli:
+      optional: true
+    webpack-command:
+      optional: true
   bin:
     webpack: bin/webpack.js
   bin:
     webpack: bin/webpack.js
-  checksum: c297519655f431d749abf0383a0c565d3fdad14baef70fc96b09285f1da6cdad22b24f7f1974ef07314f0d088235ad2e26090ebd497fb4b7fbe5e8372117d734
+  checksum: 3d42ee6af7a0ff14fc00136d02f4a36381fd5b6ad0636b95a8b83e6d99bc7e02f888f4994c095ae986e567033fe7bb1d445e27afe49d2872b8fe5c3a57d20de6
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -19343,7 +20637,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"whatwg-encoding@npm:^1.0.1, whatwg-encoding@npm:^1.0.3, whatwg-encoding@npm:^1.0.5":
+"whatwg-encoding@npm:^1.0.5":
   version: 1.0.5
   resolution: "whatwg-encoding@npm:1.0.5"
   dependencies:
   version: 1.0.5
   resolution: "whatwg-encoding@npm:1.0.5"
   dependencies:
@@ -19352,14 +20646,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"whatwg-fetch@npm:>=0.10.0, whatwg-fetch@npm:^3.0.0":
+"whatwg-fetch@npm:>=0.10.0":
   version: 3.6.2
   resolution: "whatwg-fetch@npm:3.6.2"
   checksum: ee976b7249e7791edb0d0a62cd806b29006ad7ec3a3d89145921ad8c00a3a67e4be8f3fb3ec6bc7b58498724fd568d11aeeeea1f7827e7e1e5eae6c8a275afed
   languageName: node
   linkType: hard
 
   version: 3.6.2
   resolution: "whatwg-fetch@npm:3.6.2"
   checksum: ee976b7249e7791edb0d0a62cd806b29006ad7ec3a3d89145921ad8c00a3a67e4be8f3fb3ec6bc7b58498724fd568d11aeeeea1f7827e7e1e5eae6c8a275afed
   languageName: node
   linkType: hard
 
-"whatwg-mimetype@npm:^2.1.0, whatwg-mimetype@npm:^2.2.0, whatwg-mimetype@npm:^2.3.0":
+"whatwg-fetch@npm:^3.4.1":
+  version: 3.6.20
+  resolution: "whatwg-fetch@npm:3.6.20"
+  checksum: c58851ea2c4efe5c2235f13450f426824cf0253c1d45da28f45900290ae602a20aff2ab43346f16ec58917d5562e159cd691efa368354b2e82918c2146a519c5
+  languageName: node
+  linkType: hard
+
+"whatwg-mimetype@npm:^2.3.0":
   version: 2.3.0
   resolution: "whatwg-mimetype@npm:2.3.0"
   checksum: 23eb885940bcbcca4ff841c40a78e9cbb893ec42743993a42bf7aed16085b048b44b06f3402018931687153550f9a32d259dfa524e4f03577ab898b6965e5383
   version: 2.3.0
   resolution: "whatwg-mimetype@npm:2.3.0"
   checksum: 23eb885940bcbcca4ff841c40a78e9cbb893ec42743993a42bf7aed16085b048b44b06f3402018931687153550f9a32d259dfa524e4f03577ab898b6965e5383
@@ -19376,25 +20677,14 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"whatwg-url@npm:^6.4.1":
-  version: 6.5.0
-  resolution: "whatwg-url@npm:6.5.0"
-  dependencies:
-    lodash.sortby: ^4.7.0
-    tr46: ^1.0.1
-    webidl-conversions: ^4.0.2
-  checksum: a10bd5e29f4382cd19789c2a7bbce25416e606b6fefc241c7fe34a2449de5bc5709c165bd13634eda433942d917ca7386a52841780b82dc37afa8141c31a8ebd
-  languageName: node
-  linkType: hard
-
-"whatwg-url@npm:^7.0.0":
-  version: 7.1.0
-  resolution: "whatwg-url@npm:7.1.0"
+"whatwg-url@npm:^8.0.0, whatwg-url@npm:^8.5.0":
+  version: 8.7.0
+  resolution: "whatwg-url@npm:8.7.0"
   dependencies:
   dependencies:
-    lodash.sortby: ^4.7.0
-    tr46: ^1.0.1
-    webidl-conversions: ^4.0.2
-  checksum: fecb07c87290b47d2ec2fb6d6ca26daad3c9e211e0e531dd7566e7ff95b5b3525a57d4f32640ad4adf057717e0c215731db842ad761e61d947e81010e05cf5fd
+    lodash: ^4.7.0
+    tr46: ^2.1.0
+    webidl-conversions: ^6.1.0
+  checksum: a87abcc6cefcece5311eb642858c8fdb234e51ec74196bfacf8def2edae1bfbffdf6acb251646ed6301f8cee44262642d8769c707256125a91387e33f405dd1e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -19411,10 +20701,35 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"which-module@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "which-module@npm:1.0.0"
-  checksum: 98434f7deb36350cb543c1f15612188541737e1f12d39b23b1c371dff5cf4aa4746210f2bdec202d5fe9da8682adaf8e3f7c44c520687d30948cfc59d5534edb
+"which-builtin-type@npm:^1.1.3":
+  version: 1.1.3
+  resolution: "which-builtin-type@npm:1.1.3"
+  dependencies:
+    function.prototype.name: ^1.1.5
+    has-tostringtag: ^1.0.0
+    is-async-function: ^2.0.0
+    is-date-object: ^1.0.5
+    is-finalizationregistry: ^1.0.2
+    is-generator-function: ^1.0.10
+    is-regex: ^1.1.4
+    is-weakref: ^1.0.2
+    isarray: ^2.0.5
+    which-boxed-primitive: ^1.0.2
+    which-collection: ^1.0.1
+    which-typed-array: ^1.1.9
+  checksum: 43730f7d8660ff9e33d1d3f9f9451c4784265ee7bf222babc35e61674a11a08e1c2925019d6c03154fcaaca4541df43abe35d2720843b9b4cbcebdcc31408f36
+  languageName: node
+  linkType: hard
+
+"which-collection@npm:^1.0.1":
+  version: 1.0.2
+  resolution: "which-collection@npm:1.0.2"
+  dependencies:
+    is-map: ^2.0.3
+    is-set: ^2.0.3
+    is-weakmap: ^2.0.2
+    is-weakset: ^2.0.3
+  checksum: c51821a331624c8197916598a738fc5aeb9a857f1e00d89f5e4c03dc7c60b4032822b8ec5696d28268bb83326456a8b8216344fb84270d18ff1d7628051879d9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -19425,7 +20740,20 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"which@npm:^1.2.9, which@npm:^1.3.0, which@npm:^1.3.1":
+"which-typed-array@npm:^1.1.14, which-typed-array@npm:^1.1.15, which-typed-array@npm:^1.1.9":
+  version: 1.1.15
+  resolution: "which-typed-array@npm:1.1.15"
+  dependencies:
+    available-typed-arrays: ^1.0.7
+    call-bind: ^1.0.7
+    for-each: ^0.3.3
+    gopd: ^1.0.1
+    has-tostringtag: ^1.0.2
+  checksum: 65227dcbfadf5677aacc43ec84356d17b5500cb8b8753059bb4397de5cd0c2de681d24e1a7bd575633f976a95f88233abfd6549c2105ef4ebd58af8aa1807c75
+  languageName: node
+  linkType: hard
+
+"which@npm:^1.2.9, which@npm:^1.3.1":
   version: 1.3.1
   resolution: "which@npm:1.3.1"
   dependencies:
   version: 1.3.1
   resolution: "which@npm:1.3.1"
   dependencies:
@@ -19447,7 +20775,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5":
+"wide-align@npm:^1.1.5":
   version: 1.1.5
   resolution: "wide-align@npm:1.1.5"
   dependencies:
   version: 1.1.5
   resolution: "wide-align@npm:1.1.5"
   dependencies:
@@ -19463,172 +20791,190 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-background-sync@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-background-sync@npm:4.3.1"
+"workbox-background-sync@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-background-sync@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: 25564fb0adc36396ea60308c4f8184cffe245eca9bd931a8154fc25736297071448be43de85b0b477da74e61410cdf60a295b25d4d3e780fa36b73ef983cc678
+    workbox-core: ^5.1.4
+  checksum: 14655d0254813d2580935c88fe4768eb4794158a3c0700505aa06784dcd8d7498563e8b55152f0a4afb609163e76787a3a3eb61813b810bd76830c866d6ceb9e
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-broadcast-update@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-broadcast-update@npm:4.3.1"
+"workbox-broadcast-update@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-broadcast-update@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: f62035645d37b0763f09a5b688dbdba14b28ac69c2b8d609b6a68be888c8a9c384186cde01fc1c41ac9d45e383320a6cf743a9209d7390d97d27c61c5ace64f3
+    workbox-core: ^5.1.4
+  checksum: b56df2fde652c2efa8afbb8880562aaac6932be313ddcbbb688bb48beeb3164c928a644407f359168789a31592c765f63526608afe6cd803ac89402f786064d1
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-build@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-build@npm:4.3.1"
+"workbox-build@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-build@npm:5.1.4"
   dependencies:
   dependencies:
-    "@babel/runtime": ^7.3.4
-    "@hapi/joi": ^15.0.0
+    "@babel/core": ^7.8.4
+    "@babel/preset-env": ^7.8.4
+    "@babel/runtime": ^7.8.4
+    "@hapi/joi": ^15.1.0
+    "@rollup/plugin-node-resolve": ^7.1.1
+    "@rollup/plugin-replace": ^2.3.1
+    "@surma/rollup-plugin-off-main-thread": ^1.1.1
     common-tags: ^1.8.0
     common-tags: ^1.8.0
-    fs-extra: ^4.0.2
-    glob: ^7.1.3
-    lodash.template: ^4.4.0
-    pretty-bytes: ^5.1.0
+    fast-json-stable-stringify: ^2.1.0
+    fs-extra: ^8.1.0
+    glob: ^7.1.6
+    lodash.template: ^4.5.0
+    pretty-bytes: ^5.3.0
+    rollup: ^1.31.1
+    rollup-plugin-babel: ^4.3.3
+    rollup-plugin-terser: ^5.3.1
+    source-map: ^0.7.3
+    source-map-url: ^0.4.0
     stringify-object: ^3.3.0
     strip-comments: ^1.0.2
     stringify-object: ^3.3.0
     strip-comments: ^1.0.2
-    workbox-background-sync: ^4.3.1
-    workbox-broadcast-update: ^4.3.1
-    workbox-cacheable-response: ^4.3.1
-    workbox-core: ^4.3.1
-    workbox-expiration: ^4.3.1
-    workbox-google-analytics: ^4.3.1
-    workbox-navigation-preload: ^4.3.1
-    workbox-precaching: ^4.3.1
-    workbox-range-requests: ^4.3.1
-    workbox-routing: ^4.3.1
-    workbox-strategies: ^4.3.1
-    workbox-streams: ^4.3.1
-    workbox-sw: ^4.3.1
-    workbox-window: ^4.3.1
-  checksum: 3bf0f400512b621a67f2f7ab9f1beb7964c12cb12186da1a2a51ec456a8b63e0c9a2e0fbd31c003aecd2779fb4061e8cad73b8fe94e790e141aa110169d6504b
-  languageName: node
-  linkType: hard
-
-"workbox-cacheable-response@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-cacheable-response@npm:4.3.1"
+    tempy: ^0.3.0
+    upath: ^1.2.0
+    workbox-background-sync: ^5.1.4
+    workbox-broadcast-update: ^5.1.4
+    workbox-cacheable-response: ^5.1.4
+    workbox-core: ^5.1.4
+    workbox-expiration: ^5.1.4
+    workbox-google-analytics: ^5.1.4
+    workbox-navigation-preload: ^5.1.4
+    workbox-precaching: ^5.1.4
+    workbox-range-requests: ^5.1.4
+    workbox-routing: ^5.1.4
+    workbox-strategies: ^5.1.4
+    workbox-streams: ^5.1.4
+    workbox-sw: ^5.1.4
+    workbox-window: ^5.1.4
+  checksum: 873833d0ea5c39c3f9adae9b2cd8ff33c013ff57f189dbec94d4d02917281495f38bbfa508d24425176ea8d31d6a27590658c83c30d44d9d5a9f4eb4d0798694
+  languageName: node
+  linkType: hard
+
+"workbox-cacheable-response@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-cacheable-response@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: c281f40388891a7920b7ecf73a61b0b9274174c17d703ef2a4c6ecb2e0a277ff447c24205594e50a922adca40de39767ebc34c79cfba9040abf10e4b879142b5
+    workbox-core: ^5.1.4
+  checksum: 3d8940dbee11880fdd86d76f85c063cf0a42d722be828332acf2f69ff5eaaedc8a0d779e44175adba4e8485f98392052539b2126df79125cebcec57dea0bee3c
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-core@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-core@npm:4.3.1"
-  checksum: c3e31bb24c4bfbc2be129c7745c12512c6e061dfa032b0dbe3620aa1b15fe12df433c6f39f17bcaebef2d2826a5ca18760b778d12c86876295e2cf121725ca09
+"workbox-core@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-core@npm:5.1.4"
+  checksum: 6062bc3131bb7fcf1922be619cbc28ba528b033ba18acced5e42eb62b6c0a763814e905106c081c1c100a5d520ef104957e99e592e5e954767df76db49a7c874
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-expiration@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-expiration@npm:4.3.1"
+"workbox-expiration@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-expiration@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: c1bfa47278720d1729a88562b1e2a5d0d7d27d6b625190ad6db2a3518ad4907833b1b9182a6e7dae687e4f12e13047b102b1b82f6fe9529523c82e74729d023a
+    workbox-core: ^5.1.4
+  checksum: c4648a008d19ee1281d5d588e10f14bd01530d8601c6ebf27e63b109663530fd381709539f1dd8a32e75d68a04e40e5f31ec6fbcc9ea052ee39000a2d76ade50
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-google-analytics@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-google-analytics@npm:4.3.1"
+"workbox-google-analytics@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-google-analytics@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-background-sync: ^4.3.1
-    workbox-core: ^4.3.1
-    workbox-routing: ^4.3.1
-    workbox-strategies: ^4.3.1
-  checksum: 225cea09758767bba9be553578e5d6f509ef055149a07df0b366c6f17dcd98220a939e48d1cacb3132f1d4d2e896d093a6ee00744498522b3aa25d48e9f21eb4
+    workbox-background-sync: ^5.1.4
+    workbox-core: ^5.1.4
+    workbox-routing: ^5.1.4
+    workbox-strategies: ^5.1.4
+  checksum: 2783e93f8a5aeccc038f51a9960c05aebd104fd8d113b5fd78a09bac2da8ed8e2be4c9fd7d8a6751682301d6b5e36ba055240a74a3591b4e887aabb2784cd531
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-navigation-preload@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-navigation-preload@npm:4.3.1"
+"workbox-navigation-preload@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-navigation-preload@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: 50c2bc59b66f980e5d5c9798f8e8883a6fd5af982ccfd4938e17de126cb2f4a614b143e3cff8862e140ccb7db3ce695162c98be8cf798d69e41266b20f74a74c
+    workbox-core: ^5.1.4
+  checksum: ed6b19f063f17e2dd12ef08594ea338fcf96d994ea8f7d9b2987099cb08a890c73f139a23b68c9c5523308fba4634f24aca079deb7d00684c8d76fdfb07b0fc9
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-precaching@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-precaching@npm:4.3.1"
+"workbox-precaching@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-precaching@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: afac7991d4f1d660d0fa97437f18a3e67ed978991c4f1f159b0bb3d267f3d6b6aa34f0a7f505e298acb0d66af33224b0f1b8eac0f05c39a319d1a3b4203c6ee5
+    workbox-core: ^5.1.4
+  checksum: 5593c5b9c3c928bb5d3b4c998625be610d05a3b55523e5abb0fc5f12ff2e32412114e933e60d54ba9e2661fa3cbbbab7e11f91c7170742cfe9525437d1c44ae8
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-range-requests@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-range-requests@npm:4.3.1"
+"workbox-range-requests@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-range-requests@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: bf0a2daebc4611c97f83c068911f7724e8c92c7270ee40f1e815fc227eb29a920fced0ce88421b413ca688574d1b5731bc45b0c34a208f9e0eace4d2b302eb5f
+    workbox-core: ^5.1.4
+  checksum: c67b467023e85a45599c411079907585c4d4b7aab77205dd905cd0d8b1487aa248469bc2f89045e8bd4a08eed4ede14795fc9089d01beff65ff3c6f2f1deff45
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-routing@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-routing@npm:4.3.1"
+"workbox-routing@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-routing@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: fb8bc5f67246c418b6fd15d9763a4200633cc099edb13d4a266ddf8c23f5a0c1fe2e2fc8380928eb1c1ee0d821d677355706e294113650638bd809c589ae24d4
+    workbox-core: ^5.1.4
+  checksum: 4199a02b433eb645dfcaf2a5056a04d79f337b6f368b1ab5aa18262857835d4b995536062c294d6f4db6da236235b5736af4b29d0ea1b0c3f0db339b04d3cd40
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-strategies@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-strategies@npm:4.3.1"
+"workbox-strategies@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-strategies@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: dc49af50ddc9c240160f997e195cbe57efe8cb764eb2652174778bc44f3697b9680784a00af2c55ad56d41ed507c4140c4985c2f74ee9d4b3f68e99c889a54f4
+    workbox-core: ^5.1.4
+    workbox-routing: ^5.1.4
+  checksum: 6ed247bfc0037331043cd0e772c6fd8d48e487875fac75d6692eb3936536ca2d4ac5ac9d12ec9b0ad5eefd4a69afd1ad2a993829ce3a373390880a019fd33d3d
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-streams@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-streams@npm:4.3.1"
+"workbox-streams@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-streams@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: 7a06e4a10eb30ed6ba90ed6647049355db251d970e9f3d1e3f4d20b4ca9d25082275301d31c0f506761bbc494956cd542c073db67d6a6bb4ff069f5e77bce510
+    workbox-core: ^5.1.4
+    workbox-routing: ^5.1.4
+  checksum: daaedb22dae6eb4723e7a21d758854adb36b75f1fa2453a914b6768628d91555e3db76fccb70a101f5cf1a39056e783eab1c8b0f4a59649f7ef4fad173c6f7d3
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-sw@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-sw@npm:4.3.1"
-  checksum: 349a9b1a3c9b57dc1925a8709f9af3e90d6c6b8e56f10c88c70236abdf5ba8e3a66f8c004356fc1cb7c24cfabf0f162b132930454e1fb390d29e2ce46696f3a5
+"workbox-sw@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-sw@npm:5.1.4"
+  checksum: eda970f62c26715b806828cab3000240843bab2e6577c341ccd30747a77a60d23f4f08d8d85fba680bfefa95c673c4d48a62a969a2540916dcf6506c627c69cc
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-webpack-plugin@npm:4.3.1":
-  version: 4.3.1
-  resolution: "workbox-webpack-plugin@npm:4.3.1"
+"workbox-webpack-plugin@npm:5.1.4":
+  version: 5.1.4
+  resolution: "workbox-webpack-plugin@npm:5.1.4"
   dependencies:
   dependencies:
-    "@babel/runtime": ^7.0.0
-    json-stable-stringify: ^1.0.1
-    workbox-build: ^4.3.1
+    "@babel/runtime": ^7.5.5
+    fast-json-stable-stringify: ^2.0.0
+    source-map-url: ^0.4.0
+    upath: ^1.1.2
+    webpack-sources: ^1.3.0
+    workbox-build: ^5.1.4
   peerDependencies:
   peerDependencies:
-    webpack: ^2.0.0 || ^3.0.0 || ^4.0.0
-  checksum: 7282d849d96c90a82b985784279d28bf1a95534429b3f95cc9f028142f10cea78e13a608f7e29a545e4f82cbe7b081a6e82ad57131dc09604c239a4b53a7a860
+    webpack: ^4.0.0
+  checksum: 7a9093d4ccfedc27ee6716443bcb7ce12d1f92831f48d09e6cf829a62d2ba7948a84ed38964923136d6b296e8f60bda359645a82c5a19e2c5a8e8aab6dae0a55
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"workbox-window@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "workbox-window@npm:4.3.1"
+"workbox-window@npm:^5.1.4":
+  version: 5.1.4
+  resolution: "workbox-window@npm:5.1.4"
   dependencies:
   dependencies:
-    workbox-core: ^4.3.1
-  checksum: 60b854fb0febdde236b0285eb050131043446a0c011629f8480224b1cee3a2f9f13f4c851f4a30dd8a76aa58503436c16f17662ef0eb40d0e1c630842165e718
+    workbox-core: ^5.1.4
+  checksum: bd5bc967ea1202c555db4360892518f5479027d05e4bd02fd38ebef3faf6605ee7e3887225e0920624cd2685e5217c3c4bd43a7d458860d186400c12f410df5b
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
@@ -19650,16 +20996,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"wrap-ansi@npm:^2.0.0":
-  version: 2.1.0
-  resolution: "wrap-ansi@npm:2.1.0"
-  dependencies:
-    string-width: ^1.0.1
-    strip-ansi: ^3.0.1
-  checksum: 2dacd4b3636f7a53ee13d4d0fe7fa2ed9ad81e9967e17231924ea88a286ec4619a78288de8d41881ee483f4449ab2c0287cde8154ba1bd0126c10271101b2ee3
-  languageName: node
-  linkType: hard
-
 "wrap-ansi@npm:^5.1.0":
   version: 5.1.0
   resolution: "wrap-ansi@npm:5.1.0"
 "wrap-ansi@npm:^5.1.0":
   version: 5.1.0
   resolution: "wrap-ansi@npm:5.1.0"
@@ -19700,36 +21036,19 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"write-file-atomic@npm:2.4.1":
-  version: 2.4.1
-  resolution: "write-file-atomic@npm:2.4.1"
+"write-file-atomic@npm:^3.0.0":
+  version: 3.0.3
+  resolution: "write-file-atomic@npm:3.0.3"
   dependencies:
   dependencies:
-    graceful-fs: ^4.1.11
     imurmurhash: ^0.1.4
     imurmurhash: ^0.1.4
+    is-typedarray: ^1.0.0
     signal-exit: ^3.0.2
     signal-exit: ^3.0.2
-  checksum: 9a032212214fb281fa7004e53115dfe38cd6f7191902ac7b691524c42f565f9083f2bb810aa30936b25559ed9f9b1772a2e385c29e5e7e4ef1253388610acdf1
-  languageName: node
-  linkType: hard
-
-"write@npm:1.0.3":
-  version: 1.0.3
-  resolution: "write@npm:1.0.3"
-  dependencies:
-    mkdirp: ^0.5.1
-  checksum: 6496197ceb2d6faeeb8b5fe2659ca804e801e4989dff9fb8a66fe76179ce4ccc378c982ef906733caea1220c8dbe05a666d82127959ac4456e70111af8b8df73
+    typedarray-to-buffer: ^3.1.5
+  checksum: c55b24617cc61c3a4379f425fc62a386cc51916a9b9d993f39734d005a09d5a4bb748bc251f1304e7abd71d0a26d339996c275955f527a131b1dcded67878280
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"ws@npm:^5.2.0":
-  version: 5.2.3
-  resolution: "ws@npm:5.2.3"
-  dependencies:
-    async-limiter: ~1.0.0
-  checksum: bdb2223a40c2c68cf91b25a6c9b8c67d5275378ec6187f343314d3df7530e55b77cb9fe79fb1c6a9758389ac5aefc569d24236924b5c65c5dbbaff409ef739fc
-  languageName: node
-  linkType: hard
-
-"ws@npm:^6.1.2, ws@npm:^6.2.1":
+"ws@npm:^6.2.1":
   version: 6.2.2
   resolution: "ws@npm:6.2.2"
   dependencies:
   version: 6.2.2
   resolution: "ws@npm:6.2.2"
   dependencies:
@@ -19738,6 +21057,21 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"ws@npm:^7.4.6":
+  version: 7.5.9
+  resolution: "ws@npm:7.5.9"
+  peerDependencies:
+    bufferutil: ^4.0.1
+    utf-8-validate: ^5.0.2
+  peerDependenciesMeta:
+    bufferutil:
+      optional: true
+    utf-8-validate:
+      optional: true
+  checksum: c3c100a181b731f40b7f2fddf004aa023f79d64f489706a28bc23ff88e87f6a64b3c6651fbec3a84a53960b75159574d7a7385709847a62ddb7ad6af76f49138
+  languageName: node
+  linkType: hard
+
 "xml-name-validator@npm:^3.0.0":
   version: 3.0.0
   resolution: "xml-name-validator@npm:3.0.0"
 "xml-name-validator@npm:^3.0.0":
   version: 3.0.0
   resolution: "xml-name-validator@npm:3.0.0"
@@ -19745,22 +21079,13 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"xmlchars@npm:^2.1.1":
+"xmlchars@npm:^2.2.0":
   version: 2.2.0
   resolution: "xmlchars@npm:2.2.0"
   checksum: 8c70ac94070ccca03f47a81fcce3b271bd1f37a591bf5424e787ae313fcb9c212f5f6786e1fa82076a2c632c0141552babcd85698c437506dfa6ae2d58723062
   languageName: node
   linkType: hard
 
   version: 2.2.0
   resolution: "xmlchars@npm:2.2.0"
   checksum: 8c70ac94070ccca03f47a81fcce3b271bd1f37a591bf5424e787ae313fcb9c212f5f6786e1fa82076a2c632c0141552babcd85698c437506dfa6ae2d58723062
   languageName: node
   linkType: hard
 
-"xregexp@npm:^4.3.0":
-  version: 4.4.1
-  resolution: "xregexp@npm:4.4.1"
-  dependencies:
-    "@babel/runtime-corejs3": ^7.12.1
-  checksum: 134d70116655f0de90725a0d2aaf73b2a69f8b4cd7f1908e394c7ff4de53819a0a2d9595e1722d71334a33d9392071b1f983f5954c57d83ab3e451116d9f8499
-  languageName: node
-  linkType: hard
-
 "xtend@npm:^4.0.0, xtend@npm:~4.0.1":
   version: 4.0.2
   resolution: "xtend@npm:4.0.2"
 "xtend@npm:^4.0.0, xtend@npm:~4.0.1":
   version: 4.0.2
   resolution: "xtend@npm:4.0.2"
@@ -19768,13 +21093,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"y18n@npm:^3.2.1":
-  version: 3.2.2
-  resolution: "y18n@npm:3.2.2"
-  checksum: 6154fd7544f8bbf5b18cdf77692ed88d389be49c87238ecb4e0d6a5276446cd2a5c29cc4bdbdddfc7e4e498b08df9d7e38df4a1453cf75eecfead392246ea74a
-  languageName: node
-  linkType: hard
-
 "y18n@npm:^4.0.0":
   version: 4.0.3
   resolution: "y18n@npm:4.0.3"
 "y18n@npm:^4.0.0":
   version: 4.0.3
   resolution: "y18n@npm:4.0.3"
@@ -19803,7 +21121,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"yaml@npm:^1.7.2":
+"yaml@npm:^1.10.0":
   version: 1.10.2
   resolution: "yaml@npm:1.10.2"
   checksum: ce4ada136e8a78a0b08dc10b4b900936912d15de59905b2bf415b4d33c63df1d555d23acb2a41b23cf9fb5da41c256441afca3d6509de7247daa062fd2c5ea5f
   version: 1.10.2
   resolution: "yaml@npm:1.10.2"
   checksum: ce4ada136e8a78a0b08dc10b4b900936912d15de59905b2bf415b4d33c63df1d555d23acb2a41b23cf9fb5da41c256441afca3d6509de7247daa062fd2c5ea5f
@@ -19833,6 +21151,16 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"yargs-parser@npm:^18.1.2":
+  version: 18.1.3
+  resolution: "yargs-parser@npm:18.1.3"
+  dependencies:
+    camelcase: ^5.0.0
+    decamelize: ^1.2.0
+  checksum: 60e8c7d1b85814594d3719300ecad4e6ae3796748b0926137bfec1f3042581b8646d67e83c6fc80a692ef08b8390f21ddcacb9464476c39bbdf52e34961dd4d9
+  languageName: node
+  linkType: hard
+
 "yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3":
   version: 20.2.9
   resolution: "yargs-parser@npm:20.2.9"
 "yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.3":
   version: 20.2.9
   resolution: "yargs-parser@npm:20.2.9"
@@ -19847,17 +21175,7 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"yargs-parser@npm:^5.0.1":
-  version: 5.0.1
-  resolution: "yargs-parser@npm:5.0.1"
-  dependencies:
-    camelcase: ^3.0.0
-    object.assign: ^4.1.0
-  checksum: 8eff7f3653afc9185cb917ee034d189c1ba4bc0fd5005c9588442e25557e9bf69c7331663a6f9a2bb897cd4c1544ba9675ed3335133e19e660a3086fedc259db
-  languageName: node
-  linkType: hard
-
-"yargs@npm:^13.3.0, yargs@npm:^13.3.2":
+"yargs@npm:^13.3.2":
   version: 13.3.2
   resolution: "yargs@npm:13.3.2"
   dependencies:
   version: 13.3.2
   resolution: "yargs@npm:13.3.2"
   dependencies:
@@ -19875,6 +21193,25 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
+"yargs@npm:^15.4.1":
+  version: 15.4.1
+  resolution: "yargs@npm:15.4.1"
+  dependencies:
+    cliui: ^6.0.0
+    decamelize: ^1.2.0
+    find-up: ^4.1.0
+    get-caller-file: ^2.0.1
+    require-directory: ^2.1.1
+    require-main-filename: ^2.0.0
+    set-blocking: ^2.0.0
+    string-width: ^4.2.0
+    which-module: ^2.0.0
+    y18n: ^4.0.0
+    yargs-parser: ^18.1.2
+  checksum: 40b974f508d8aed28598087720e086ecd32a5fd3e945e95ea4457da04ee9bdb8bdd17fd91acff36dc5b7f0595a735929c514c40c402416bbb87c03f6fb782373
+  languageName: node
+  linkType: hard
+
 "yargs@npm:^17.0.0":
   version: 17.0.1
   resolution: "yargs@npm:17.0.1"
 "yargs@npm:^17.0.0":
   version: 17.0.1
   resolution: "yargs@npm:17.0.1"
@@ -19905,27 +21242,6 @@ __metadata:
   languageName: node
   linkType: hard
 
   languageName: node
   linkType: hard
 
-"yargs@npm:^7.0.0":
-  version: 7.1.2
-  resolution: "yargs@npm:7.1.2"
-  dependencies:
-    camelcase: ^3.0.0
-    cliui: ^3.2.0
-    decamelize: ^1.1.1
-    get-caller-file: ^1.0.1
-    os-locale: ^1.4.0
-    read-pkg-up: ^1.0.1
-    require-directory: ^2.1.1
-    require-main-filename: ^1.0.1
-    set-blocking: ^2.0.0
-    string-width: ^1.0.2
-    which-module: ^1.0.0
-    y18n: ^3.2.1
-    yargs-parser: ^5.0.1
-  checksum: 0c330ce1338cd9f293157bf8955af6833ae59032ab1bc936510ce7a216de9bb65b05b39a82ff0e7359bfb643342cc05de5049ce50ee9404b0818f65911fb59a5
-  languageName: node
-  linkType: hard
-
 "yauzl@npm:^2.10.0":
   version: 2.10.0
   resolution: "yauzl@npm:2.10.0"
 "yauzl@npm:^2.10.0":
   version: 2.10.0
   resolution: "yauzl@npm:2.10.0"
@@ -19935,3 +21251,10 @@ __metadata:
   checksum: 7f21fe0bbad6e2cb130044a5d1d0d5a0e5bf3d8d4f8c4e6ee12163ce798fee3de7388d22a7a0907f563ac5f9d40f8699a223d3d5c1718da90b0156da6904022b
   languageName: node
   linkType: hard
   checksum: 7f21fe0bbad6e2cb130044a5d1d0d5a0e5bf3d8d4f8c4e6ee12163ce798fee3de7388d22a7a0907f563ac5f9d40f8699a223d3d5c1718da90b0156da6904022b
   languageName: node
   linkType: hard
+
+"yocto-queue@npm:^0.1.0":
+  version: 0.1.0
+  resolution: "yocto-queue@npm:0.1.0"
+  checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700
+  languageName: node
+  linkType: hard
index d02b1999392bdce12dcb62f65a007945ece1a012..172f74ce7abea6bda96b1250e040ec01b90f0a55 100644 (file)
@@ -7,11 +7,14 @@ package ws
 import (
        "database/sql"
        "fmt"
 import (
        "database/sql"
        "fmt"
+       "io/ioutil"
+       "sort"
        "sync"
        "time"
 
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
        "sync"
        "time"
 
        "git.arvados.org/arvados.git/sdk/go/arvados"
        "git.arvados.org/arvados.git/sdk/go/ctxlog"
+       "github.com/ghodss/yaml"
        "github.com/prometheus/client_golang/prometheus"
        check "gopkg.in/check.v1"
 )
        "github.com/prometheus/client_golang/prometheus"
        check "gopkg.in/check.v1"
 )
@@ -41,6 +44,18 @@ func testDB() *sql.DB {
 }
 
 func (*eventSourceSuite) TestEventSource(c *check.C) {
 }
 
 func (*eventSourceSuite) TestEventSource(c *check.C) {
+       var logfixtures map[string]struct {
+               ID int
+       }
+       yamldata, err := ioutil.ReadFile("../api/test/fixtures/logs.yml")
+       c.Assert(err, check.IsNil)
+       c.Assert(yaml.Unmarshal(yamldata, &logfixtures), check.IsNil)
+       var logIDs []int
+       for _, logfixture := range logfixtures {
+               logIDs = append(logIDs, logfixture.ID)
+       }
+       sort.Ints(logIDs)
+
        cfg := testDBConfig()
        db := testDB()
        pges := &pgEventSource{
        cfg := testDBConfig()
        db := testDB()
        pges := &pgEventSource{
@@ -61,8 +76,8 @@ func (*eventSourceSuite) TestEventSource(c *check.C) {
        done := make(chan bool, 1)
 
        go func() {
        done := make(chan bool, 1)
 
        go func() {
-               for i := range sinks {
-                       _, err := db.Exec(fmt.Sprintf(`NOTIFY logs, '%d'`, i))
+               for _, id := range logIDs {
+                       _, err := db.Exec(fmt.Sprintf(`NOTIFY logs, '%d'`, id))
                        if err != nil {
                                done <- true
                                c.Fatal(err)
                        if err != nil {
                                done <- true
                                c.Fatal(err)
@@ -77,17 +92,13 @@ func (*eventSourceSuite) TestEventSource(c *check.C) {
                go func(si int, s eventSink) {
                        defer wg.Done()
                        defer sinks[si].Stop()
                go func(si int, s eventSink) {
                        defer wg.Done()
                        defer sinks[si].Stop()
-                       for i := 0; i <= si; i++ {
+                       for _, logID := range logIDs {
                                ev := <-sinks[si].Channel()
                                ev := <-sinks[si].Channel()
-                               c.Logf("sink %d received event %d", si, i)
-                               c.Check(ev.LogID, check.Equals, int64(i))
+                               c.Logf("sink %d received event %d", si, logID)
+                               c.Check(ev.LogID, check.Equals, int64(logID))
                                row := ev.Detail()
                                row := ev.Detail()
-                               if i == 0 {
-                                       // no matching row, null event
-                                       c.Check(row, check.IsNil)
-                               } else {
-                                       c.Check(row, check.NotNil)
-                                       c.Check(row.ID, check.Equals, int64(i))
+                               if c.Check(row, check.NotNil) {
+                                       c.Check(row.ID, check.Equals, int64(logID))
                                        c.Check(row.UUID, check.Not(check.Equals), "")
                                }
                        }
                                        c.Check(row.UUID, check.Not(check.Equals), "")
                                }
                        }
index 4665dfcd9ee9208fcb71794189ba115d0285fa55..f38bbbe5ad946cce77123ce85c58060e79aeb67e 100644 (file)
@@ -21,5 +21,5 @@ func (*eventSuite) TestDetail(c *check.C) {
        c.Check(logRow.UUID, check.Equals, "zzzzz-57u5n-containerlog006")
        c.Check(logRow.ObjectUUID, check.Equals, "zzzzz-dz642-logscontainer03")
        c.Check(logRow.EventType, check.Equals, "crunchstat")
        c.Check(logRow.UUID, check.Equals, "zzzzz-57u5n-containerlog006")
        c.Check(logRow.ObjectUUID, check.Equals, "zzzzz-dz642-logscontainer03")
        c.Check(logRow.EventType, check.Equals, "crunchstat")
-       c.Check(logRow.Properties["text"], check.Equals, "2013-11-07_23:33:41 zzzzz-8i9sb-ahd7cie8jah9qui 29610 1 stderr crunchstat: cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user 0.9900 sys")
+       c.Check(logRow.Properties["text"], check.Equals, "2013-11-07_23:33:41 zzzzz-dz642-logscontainer03 29610 1 stderr crunchstat: cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user 0.9900 sys")
 }
 }
index d8b240883169e72b6914a5f23ca7d62f8aef9447..927b975f8f4baef077acbad3d560266d3f62a7d5 100644 (file)
@@ -137,8 +137,7 @@ RUN echo arvados_version is git commit $arvados_version
 
 COPY $workdir/fuse.conf /etc/
 
 
 COPY $workdir/fuse.conf /etc/
 
-COPY $workdir/gitolite.rc \
-    $workdir/keep-setup.sh $workdir/common.sh $workdir/createusers.sh \
+COPY $workdir/keep-setup.sh $workdir/common.sh $workdir/createusers.sh \
     $workdir/logger $workdir/runsu.sh $workdir/waitforpostgres.sh \
     $workdir/yml_override.py $workdir/api-setup.sh \
     $workdir/go-setup.sh $workdir/devenv.sh $workdir/cluster-config.sh $workdir/edit_users.py \
     $workdir/logger $workdir/runsu.sh $workdir/waitforpostgres.sh \
     $workdir/yml_override.py $workdir/api-setup.sh \
     $workdir/go-setup.sh $workdir/devenv.sh $workdir/cluster-config.sh $workdir/edit_users.py \
index 81a5369f5ebcb9a9a749c1a4d713458466e2c897..dfbeaa448cc6bbe92a4164df6085a30bb42f639e 100644 (file)
@@ -37,7 +37,6 @@ RUN sudo -u arvbox /var/lib/arvbox/service/keep-web/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/doc/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/vm/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/keepproxy/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/doc/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/vm/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/keepproxy/run-service --only-deps
-RUN sudo -u arvbox /var/lib/arvbox/service/arv-git-httpd/run-service --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/websockets/run --only-deps
 RUN sudo -u arvbox /usr/local/lib/arvbox/keep-setup.sh --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/sdk/run-service
 RUN sudo -u arvbox /var/lib/arvbox/service/websockets/run --only-deps
 RUN sudo -u arvbox /usr/local/lib/arvbox/keep-setup.sh --only-deps
 RUN sudo -u arvbox /var/lib/arvbox/service/sdk/run-service
index 29cea1ecbe3ee2a5e39e04b66c64dab65f010a3a..b2d20a455c62a1f619882fdba1f65aae3f5e6af0 100755 (executable)
@@ -38,8 +38,6 @@ $RAILS_ENV:
   blob_signing_key: $blob_signing_key
   workbench_address: "https://$localip/"
   websocket_address: "wss://$localip:${services[websockets-ssl]}/websocket"
   blob_signing_key: $blob_signing_key
   workbench_address: "https://$localip/"
   websocket_address: "wss://$localip:${services[websockets-ssl]}/websocket"
-  git_repo_ssh_base: "git@$localip:"
-  git_repo_https_base: "http://$localip:${services[arv-git-httpd]}/"
   new_users_are_active: true
   auto_admin_first_user: true
   auto_setup_new_users: true
   new_users_are_active: true
   auto_admin_first_user: true
   auto_setup_new_users: true
index 9b55181c9169a52c6a8eb7fda8760eedcade0d9a..d07fc3d34d550c8ad4ba2f826a29ec01488e03ad 100755 (executable)
@@ -87,12 +87,6 @@ Clusters:
         ExternalURL: "wss://$localip:${services[websockets-ssl]}/websocket"
         InternalURLs:
           "http://localhost:${services[websockets]}": {}
         ExternalURL: "wss://$localip:${services[websockets-ssl]}/websocket"
         InternalURLs:
           "http://localhost:${services[websockets]}": {}
-      GitSSH:
-        ExternalURL: "ssh://git@$localip:"
-      GitHTTP:
-        InternalURLs:
-          "http://localhost:${services[arv-git-httpd]}/": {}
-        ExternalURL: "https://$localip:${services[arv-git-httpd-ssl]}/"
       WebDAV:
         InternalURLs:
           "http://localhost:${services[keep-web]}/": {}
       WebDAV:
         InternalURLs:
           "http://localhost:${services[keep-web]}/": {}
@@ -132,11 +126,6 @@ Clusters:
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
       AutoSetupNewUsersWithVmUUID: $vm_uuid
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
       AutoSetupNewUsersWithVmUUID: $vm_uuid
-      AutoSetupNewUsersWithRepository: true
-    Git:
-      GitCommand: /usr/share/gitolite3/gitolite-shell
-      GitoliteHome: $ARVADOS_CONTAINER_PATH/git
-      Repositories: $ARVADOS_CONTAINER_PATH/git/repositories
     Volumes:
       ${uuid_prefix}-nyw5e-000000000000000:
         Driver: Directory
     Volumes:
       ${uuid_prefix}-nyw5e-000000000000000:
         Driver: Directory
index 54ec9403ad9135179682eca94817b7be6973d6e9..2232b63c4f9bca74823550ab4275d2cc7e2b6145 100644 (file)
@@ -2,9 +2,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-export RUBY_VERSION=3.2.2
-export BUNDLER_VERSION=2.4.22
-
 export DEBIAN_FRONTEND=noninteractive
 export PATH=${PATH}:/usr/local/go/bin:/var/lib/arvados/bin:/opt/arvados-py/bin:/usr/src/arvados/sdk/cli/binstubs
 export npm_config_cache=/var/lib/npm
 export DEBIAN_FRONTEND=noninteractive
 export PATH=${PATH}:/usr/local/go/bin:/var/lib/arvados/bin:/opt/arvados-py/bin:/usr/src/arvados/sdk/cli/binstubs
 export npm_config_cache=/var/lib/npm
@@ -40,8 +37,6 @@ services=(
   [api]=8004
   [controller]=8003
   [controller-ssl]=8000
   [api]=8004
   [controller]=8003
   [controller-ssl]=8000
-  [arv-git-httpd-ssl]=9000
-  [arv-git-httpd]=9001
   [keep-web]=9003
   [keep-web-ssl]=9002
   [keep-web-dl-ssl]=9004
   [keep-web]=9003
   [keep-web-ssl]=9002
   [keep-web-dl-ssl]=9004
@@ -66,7 +61,7 @@ else
 fi
 
 run_bundler() {
 fi
 
 run_bundler() {
-    flock $GEMLOCK /var/lib/arvados/bin/gem install --no-document --user bundler:$BUNDLER_VERSION
+    flock $GEMLOCK /var/lib/arvados/bin/gem install --conservative --no-document --user --version '~> 2.4.0' bundler
 
     BUNDLER=bundle
     if test -x $PWD/bin/bundle ; then
 
     BUNDLER=bundle
     if test -x $PWD/bin/bundle ; then
diff --git a/tools/arvbox/lib/arvbox/docker/gitolite.rc b/tools/arvbox/lib/arvbox/docker/gitolite.rc
deleted file mode 100644 (file)
index 07a9ce0..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-# This is based on the default Gitolite configuration file with the following
-# changes applied as described here:
-# http://doc.arvados.org/install/install-arv-git-httpd.html
-
-# configuration variables for gitolite
-
-# This file is in perl syntax.  But you do NOT need to know perl to edit it --
-# just mind the commas, use single quotes unless you know what you're doing,
-# and make sure the brackets and braces stay matched up!
-
-# (Tip: perl allows a comma after the last item in a list also!)
-
-# HELP for commands can be had by running the command with "-h".
-
-# HELP for all the other FEATURES can be found in the documentation (look for
-# "list of non-core programs shipped with gitolite" in the master index) or
-# directly in the corresponding source file.
-
-my $repo_aliases;
-my $aliases_src = "$ENV{HOME}/.gitolite/arvadosaliases.pl";
-if ($ENV{HOME} && (-e $aliases_src)) {
-    $repo_aliases = do $aliases_src;
-}
-$repo_aliases ||= {};
-
-%RC = (
-
-    REPO_ALIASES => $repo_aliases,
-
-    # ------------------------------------------------------------------
-
-    # default umask gives you perms of '0700'; see the rc file docs for
-    # how/why you might change this
-    UMASK                           =>  0022,
-
-    # look for "git-config" in the documentation
-    GIT_CONFIG_KEYS                 =>  '',
-
-    # comment out if you don't need all the extra detail in the logfile
-    LOG_EXTRA                       =>  1,
-    # logging options
-    # 1. leave this section as is for 'normal' gitolite logging (default)
-    # 2. uncomment this line to log ONLY to syslog:
-    # LOG_DEST                      => 'syslog',
-    # 3. uncomment this line to log to syslog and the normal gitolite log:
-    # LOG_DEST                      => 'syslog,normal',
-    # 4. prefixing "repo-log," to any of the above will **also** log just the
-    #    update records to "gl-log" in the bare repo directory:
-    # LOG_DEST                      => 'repo-log,normal',
-    # LOG_DEST                      => 'repo-log,syslog',
-    # LOG_DEST                      => 'repo-log,syslog,normal',
-
-    # roles.  add more roles (like MANAGER, TESTER, ...) here.
-    #   WARNING: if you make changes to this hash, you MUST run 'gitolite
-    #   compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
-    ROLES => {
-        READERS                     =>  1,
-        WRITERS                     =>  1,
-    },
-
-    # enable caching (currently only Redis).  PLEASE RTFM BEFORE USING!!!
-    # CACHE                         =>  'Redis',
-
-    # ------------------------------------------------------------------
-
-    # rc variables used by various features
-
-    # the 'info' command prints this as additional info, if it is set
-        # SITE_INFO                 =>  'Please see http://blahblah/gitolite for more help',
-
-    # the CpuTime feature uses these
-        # display user, system, and elapsed times to user after each git operation
-        # DISPLAY_CPU_TIME          =>  1,
-        # display a warning if total CPU times (u, s, cu, cs) crosses this limit
-        # CPU_TIME_WARN_LIMIT       =>  0.1,
-
-    # the Mirroring feature needs this
-        # HOSTNAME                  =>  "foo",
-
-    # TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING!
-        # CACHE_TTL                 =>  600,
-
-    # ------------------------------------------------------------------
-
-    # suggested locations for site-local gitolite code (see cust.html)
-
-        # this one is managed directly on the server
-        # LOCAL_CODE                =>  "$ENV{HOME}/local",
-
-        # or you can use this, which lets you put everything in a subdirectory
-        # called "local" in your gitolite-admin repo.  For a SECURITY WARNING
-        # on this, see http://gitolite.com/gitolite/non-core.html#pushcode
-        # LOCAL_CODE                =>  "$rc{GL_ADMIN_BASE}/local",
-
-    # ------------------------------------------------------------------
-
-    # List of commands and features to enable
-
-    ENABLE => [
-
-        # COMMANDS
-
-            # These are the commands enabled by default
-            'help',
-            'desc',
-            'info',
-            'perms',
-            'writable',
-
-            # Uncomment or add new commands here.
-            # 'create',
-            # 'fork',
-            # 'mirror',
-            # 'readme',
-            # 'sskm',
-            # 'D',
-
-        # These FEATURES are enabled by default.
-
-            # essential (unless you're using smart-http mode)
-            'ssh-authkeys',
-
-            # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz'
-            'git-config',
-
-            # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out
-            'daemon',
-
-            # creates projects.list file; if you don't use gitweb, comment this out
-            'gitweb',
-
-        # These FEATURES are disabled by default; uncomment to enable.  If you
-        # need to add new ones, ask on the mailing list :-)
-
-        # user-visible behaviour
-
-            # prevent wild repos auto-create on fetch/clone
-            # 'no-create-on-read',
-            # no auto-create at all (don't forget to enable the 'create' command!)
-            # 'no-auto-create',
-
-            # access a repo by another (possibly legacy) name
-            'Alias',
-
-            # give some users direct shell access.  See documentation in
-            # sts.html for details on the following two choices.
-            # "Shell $ENV{HOME}/.gitolite.shell-users",
-            # 'Shell alice bob',
-
-            # set default roles from lines like 'option default.roles-1 = ...', etc.
-            # 'set-default-roles',
-
-            # show more detailed messages on deny
-            # 'expand-deny-messages',
-
-            # show a message of the day
-            # 'Motd',
-
-        # system admin stuff
-
-            # enable mirroring (don't forget to set the HOSTNAME too!)
-            # 'Mirroring',
-
-            # allow people to submit pub files with more than one key in them
-            # 'ssh-authkeys-split',
-
-            # selective read control hack
-            # 'partial-copy',
-
-            # manage local, gitolite-controlled, copies of read-only upstream repos
-            # 'upstream',
-
-            # updates 'description' file instead of 'gitweb.description' config item
-            # 'cgit',
-
-            # allow repo-specific hooks to be added
-            # 'repo-specific-hooks',
-
-        # performance, logging, monitoring...
-
-            # be nice
-            # 'renice 10',
-
-            # log CPU times (user, system, cumulative user, cumulative system)
-            # 'CpuTime',
-
-        # syntactic_sugar for gitolite.conf and included files
-
-            # allow backslash-escaped continuation lines in gitolite.conf
-            # 'continuation-lines',
-
-            # create implicit user groups from directory names in keydir/
-            # 'keysubdirs-as-groups',
-
-            # allow simple line-oriented macros
-            # 'macros',
-
-        # Kindergarten mode
-
-            # disallow various things that sensible people shouldn't be doing anyway
-            # 'Kindergarten',
-    ],
-
-);
-
-# ------------------------------------------------------------------------------
-# per perl rules, this should be the last line in such a file:
-1;
-
-# Local variables:
-# mode: perl
-# End:
-# vim: set syn=perl:
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/main/.gitstub
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/log/run
deleted file mode 120000 (symlink)
index d6aef4a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run
deleted file mode 120000 (symlink)
index a388c8b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service b/tools/arvbox/lib/arvbox/docker/service/arv-git-httpd/run-service
deleted file mode 100755 (executable)
index b598a75..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-exec 2>&1
-set -ex -o pipefail
-
-. /usr/local/lib/arvbox/common.sh
-. /usr/local/lib/arvbox/go-setup.sh
-
-(cd /usr/local/bin && ln -sf arvados-server arvados-git-httpd)
-
-if test "$1" = "--only-deps" ; then
-    exit
-fi
-
-flock $ARVADOS_CONTAINER_PATH/cluster_config.yml.lock /usr/local/lib/arvbox/cluster-config.sh
-
-export PATH="$PATH:$ARVADOS_CONTAINER_PATH/git/bin"
-cd ~git
-exec /usr/local/bin/arvados-git-httpd
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub b/tools/arvbox/lib/arvbox/docker/service/gitolite/log/main/.gitstub
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/log/run b/tools/arvbox/lib/arvbox/docker/service/gitolite/log/run
deleted file mode 120000 (symlink)
index d6aef4a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/usr/local/lib/arvbox/logger
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/run b/tools/arvbox/lib/arvbox/docker/service/gitolite/run
deleted file mode 120000 (symlink)
index a388c8b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/usr/local/lib/arvbox/runsu.sh
\ No newline at end of file
diff --git a/tools/arvbox/lib/arvbox/docker/service/gitolite/run-service b/tools/arvbox/lib/arvbox/docker/service/gitolite/run-service
deleted file mode 100755 (executable)
index 5f2cbc8..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/bin/bash
-# Copyright (C) The Arvados Authors. All rights reserved.
-#
-# SPDX-License-Identifier: AGPL-3.0
-
-exec 2>&1
-set -eux -o pipefail
-
-. /usr/local/lib/arvbox/common.sh
-
-if test "$1" != "--only-deps" ; then
-  while [ ! -f $ARVADOS_CONTAINER_PATH/api.ready ]; do
-    sleep 1
-  done
-fi
-
-mkdir -p $ARVADOS_CONTAINER_PATH/git
-
-export ARVADOS_API_HOST=$localip:${services[controller-ssl]}
-export ARVADOS_API_HOST_INSECURE=1
-export ARVADOS_API_TOKEN=$(cat $ARVADOS_CONTAINER_PATH/superuser_token)
-
-export USER=git
-export USERNAME=git
-export LOGNAME=git
-export HOME=$ARVADOS_CONTAINER_PATH/git
-
-cd ~arvbox
-
-mkdir -p ~arvbox/.ssh ~git/.ssh
-chmod 0700 ~arvbox/.ssh ~git/.ssh
-
-if ! test -s ~arvbox/.ssh/id_rsa ; then
-    ssh-keygen -t rsa -P '' -f .ssh/id_rsa
-    cp ~arvbox/.ssh/id_rsa ~arvbox/.ssh/id_rsa.pub ~git/.ssh
-fi
-
-if test -s ~arvbox/.ssh/known_hosts ; then
-    ssh-keygen -f ".ssh/known_hosts" -R localhost
-fi
-
-if ! test -f $ARVADOS_CONTAINER_PATH/gitolite-setup ; then
-    cd ~git
-
-    # Do a no-op login to populate known_hosts
-    # with the hostkey, so it won't try to ask
-    # about it later.
-    cp .ssh/id_rsa.pub .ssh/authorized_keys
-    ssh -o stricthostkeychecking=no git@localhost true
-    rm .ssh/authorized_keys
-
-    cp /usr/local/lib/arvbox/gitolite.rc .gitolite.rc
-
-    gitolite setup -pk .ssh/id_rsa.pub
-
-    if ! test -d gitolite-admin ; then
-        git clone git@localhost:gitolite-admin
-    fi
-
-    cd gitolite-admin
-    git config user.email arvados
-    git config user.name arvados
-    git config push.default simple
-    git push
-
-    touch $ARVADOS_CONTAINER_PATH/gitolite-setup
-else
-    # Do a no-op login to populate known_hosts
-    # with the hostkey, so it won't try to ask
-    # about it later.  Don't run anything,
-    # get the default gitolite behavior.
-    ssh -o stricthostkeychecking=no git@localhost
-fi
-
-prefix=$(arv --format=uuid user current | cut -d- -f1)
-
-if ! test -s $ARVADOS_CONTAINER_PATH/arvados-git-uuid ; then
-    repo_uuid=$(arv --format=uuid repository create --repository "{\"owner_uuid\":\"$prefix-tpzed-000000000000000\", \"name\":\"arvados\"}")
-    echo $repo_uuid > $ARVADOS_CONTAINER_PATH/arvados-git-uuid
-fi
-
-repo_uuid=$(cat $ARVADOS_CONTAINER_PATH/arvados-git-uuid)
-
-if ! test -s $ARVADOS_CONTAINER_PATH/arvados-git-link-uuid ; then
-    all_users_group_uuid="$prefix-j7d0g-fffffffffffffff"
-
-    set +e
-    read -rd $'\000' newlink <<EOF
-{
- "tail_uuid":"$all_users_group_uuid",
- "head_uuid":"$repo_uuid",
- "link_class":"permission",
- "name":"can_read"
-}
-EOF
-    set -e
-    link_uuid=$(arv --format=uuid link create --link "$newlink")
-    echo $link_uuid > $ARVADOS_CONTAINER_PATH/arvados-git-link-uuid
-fi
-
-if ! test -d $ARVADOS_CONTAINER_PATH/git/repositories/$repo_uuid.git ; then
-    git clone --bare /usr/src/arvados $ARVADOS_CONTAINER_PATH/git/repositories/$repo_uuid.git
-else
-    git --git-dir=$ARVADOS_CONTAINER_PATH/git/repositories/$repo_uuid.git fetch -f /usr/src/arvados main:main
-fi
-
-cd /usr/src/arvados/services/api
-
-if test -s $ARVADOS_CONTAINER_PATH/api_rails_env ; then
-  RAILS_ENV=$(cat $ARVADOS_CONTAINER_PATH/api_rails_env)
-else
-  RAILS_ENV=development
-fi
-
-git_user_key=$(cat ~git/.ssh/id_rsa.pub)
-
-cat > config/arvados-clients.yml <<EOF
-$RAILS_ENV:
-  gitolite_url: $ARVADOS_CONTAINER_PATH/git/repositories/gitolite-admin.git
-  gitolite_tmp: $ARVADOS_CONTAINER_PATH/git
-  arvados_api_host: $localip:${services[controller-ssl]}
-  arvados_api_token: "$ARVADOS_API_TOKEN"
-  arvados_api_host_insecure: false
-  gitolite_arvados_git_user_key: "$git_user_key"
-EOF
-
-while true ; do
-    flock $GEMLOCK bundle exec script/arvados-git-sync.rb $RAILS_ENV
-    sleep 120
-done
index 528c6be8274a145a3e4cd3ad61e7972078f5d6ea..1ac38035bcd75b96a32c5e93a2b068bebf4fb88a 100755 (executable)
@@ -233,75 +233,51 @@ http {
     }
   }
 
     }
   }
 
-  upstream arvados-git-httpd {
-    server localhost:${services[arv-git-httpd]};
+  upstream arvados-webshell {
+    server                localhost:${services[webshell]};
   }
   server {
   }
   server {
-    listen *:${services[arv-git-httpd-ssl]} ssl default_server;
-    server_name arvados-git-httpd;
+    listen                ${services[webshell-ssl]} ssl;
+    server_name           arvados-webshell;
+
     proxy_connect_timeout 90s;
     proxy_connect_timeout 90s;
-    proxy_read_timeout 300s;
+    proxy_read_timeout    300s;
 
 
-    ssl on;
+    ssl                   on;
     ssl_certificate "${server_cert}";
     ssl_certificate_key "${server_cert_key}";
     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;
-    }
-  }
-
 
 
-upstream arvados-webshell {
-  server                localhost:${services[webshell]};
-}
-server {
-  listen                ${services[webshell-ssl]} ssl;
-  server_name           arvados-webshell;
-
-  proxy_connect_timeout 90s;
-  proxy_read_timeout    300s;
-
-  ssl                   on;
-  ssl_certificate "${server_cert}";
-  ssl_certificate_key "${server_cert_key}";
-
-  location / {
-    if (\$request_method = 'OPTIONS') {
-       add_header 'Access-Control-Allow-Origin' '*';
-       add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-       add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
-       add_header 'Access-Control-Max-Age' 1728000;
-       add_header 'Content-Type' 'text/plain charset=UTF-8';
-       add_header 'Content-Length' 0;
-       return 204;
-    }
-    if (\$request_method = 'POST') {
-       add_header 'Access-Control-Allow-Origin' '*';
-       add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-       add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
-    }
-    if (\$request_method = 'GET') {
-       add_header 'Access-Control-Allow-Origin' '*';
-       add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-       add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+    location / {
+      if (\$request_method = 'OPTIONS') {
+         add_header 'Access-Control-Allow-Origin' '*';
+         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+         add_header 'Access-Control-Max-Age' 1728000;
+         add_header 'Content-Type' 'text/plain charset=UTF-8';
+         add_header 'Content-Length' 0;
+         return 204;
+      }
+      if (\$request_method = 'POST') {
+         add_header 'Access-Control-Allow-Origin' '*';
+         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+      }
+      if (\$request_method = 'GET') {
+         add_header 'Access-Control-Allow-Origin' '*';
+         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+         add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
+      }
+
+      proxy_ssl_session_reuse off;
+      proxy_read_timeout  90;
+      proxy_set_header    X-Forwarded-Proto https;
+      proxy_set_header    Host \$http_host;
+      proxy_set_header    X-Real-IP \$remote_addr;
+      proxy_set_header    X-Forwarded-For \$proxy_add_x_forwarded_for;
+      proxy_pass          http://arvados-webshell;
     }
     }
-
-    proxy_ssl_session_reuse off;
-    proxy_read_timeout  90;
-    proxy_set_header    X-Forwarded-Proto https;
-    proxy_set_header    Host \$http_host;
-    proxy_set_header    X-Real-IP \$remote_addr;
-    proxy_set_header    X-Forwarded-For \$proxy_add_x_forwarded_for;
-    proxy_pass          http://arvados-webshell;
   }
 }
   }
 }
-}
 
 EOF
 
 
 EOF
 
index 61430d02bd0d4111b76e08d8a6db4bf1c9e41b41..17b661442eb11ec2d22b3c67d7f79467592e1f08 100755 (executable)
@@ -7,9 +7,6 @@ set -e
 
 . /usr/local/lib/arvbox/common.sh
 
 
 . /usr/local/lib/arvbox/common.sh
 
-git config --system "credential.http://$localip:${services[arv-git-httpd]}/.username" none
-git config --system "credential.http://$localip:${services[arv-git-httpd]}/.helper" '!cred(){ cat >/dev/null; if [ "$1" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred'
-
 /usr/local/lib/arvbox/runsu.sh $0-service
 
 cd /usr/src/arvados/services/login-sync
 /usr/local/lib/arvbox/runsu.sh $0-service
 
 cd /usr/src/arvados/services/login-sync
index 370c3f3a3a2794b4889adc545db556a56958d3e6..c19febdc0136a3f2cda3c2c36820caf70f51ee76 100644 (file)
@@ -15,8 +15,8 @@ wait_for_apt_locks() {
   done
 }
 
   done
 }
 
-# $DIST should not have a dot if there is one in /etc/os-release (e.g. 18.04)
-DIST=$(. /etc/os-release; echo $ID$VERSION_ID | tr -d '.')
+. /etc/os-release
+DISTRO_ID="$ID"
 
 # Run apt-get update
 $SUDO DEBIAN_FRONTEND=noninteractive apt-get --yes update
 
 # Run apt-get update
 $SUDO DEBIAN_FRONTEND=noninteractive apt-get --yes update
@@ -36,9 +36,6 @@ if [[ ! -d /var/lib/cloud/scripts/per-boot ]]; then
   mkdir -p /var/lib/cloud/scripts/per-boot
 fi
 
   mkdir -p /var/lib/cloud/scripts/per-boot
 fi
 
-TMP_LSB=`/usr/bin/lsb_release -c -s`
-LSB_RELEASE_CODENAME=${TMP_LSB//[$'\t\r\n ']}
-
 SET_RESOLVER=
 if [ -n "$RESOLVER" ]; then
   SET_RESOLVER="--dns ${RESOLVER}"
 SET_RESOLVER=
 if [ -n "$RESOLVER" ]; then
   SET_RESOLVER="--dns ${RESOLVER}"
@@ -46,7 +43,7 @@ fi
 
 # Add the arvados apt repository
 echo "# apt.arvados.org" |$SUDO tee --append /etc/apt/sources.list.d/apt.arvados.org.list
 
 # Add the arvados apt repository
 echo "# apt.arvados.org" |$SUDO tee --append /etc/apt/sources.list.d/apt.arvados.org.list
-echo "deb http://apt.arvados.org/$LSB_RELEASE_CODENAME $LSB_RELEASE_CODENAME${REPOSUFFIX} main" |$SUDO tee --append /etc/apt/sources.list.d/apt.arvados.org.list
+echo "deb http://apt.arvados.org/$VERSION_CODENAME $VERSION_CODENAME${REPOSUFFIX} main" |$SUDO tee --append /etc/apt/sources.list.d/apt.arvados.org.list
 
 # Add the arvados signing key
 cat /tmp/1078ECD7.asc | $SUDO apt-key add -
 
 # Add the arvados signing key
 cat /tmp/1078ECD7.asc | $SUDO apt-key add -
@@ -75,32 +72,12 @@ wait_for_apt_locks && $SUDO DEBIAN_FRONTEND=noninteractive apt-get -qq --yes ins
   python3-arvados-fuse \
   arvados-docker-cleaner
 
   python3-arvados-fuse \
   arvados-docker-cleaner
 
-# We want Docker 20.10 or later so that we support glibc 2.33 and up in the container, cf.
-# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1005906
-dockerversion=5:20.10.13~3-0
-if [[ "$DIST" =~ ^debian ]]; then
-  family="debian"
-  if [ "$DIST" == "debian11" ]; then
-    distro="bullseye"
-  elif [ "$DIST" == "debian12" ]; then
-    distro="bookworm"
-  fi
-elif [[ "$DIST" =~ ^ubuntu ]]; then
-  family="ubuntu"
-  if [ "$DIST" == "ubuntu2004" ]; then
-    distro="focal"
-  elif [ "$DIST" == "ubuntu2204" ]; then
-    distro="jammy"
-  fi
-else
-  echo "Unsupported distribution $DIST"
-  exit 1
-fi
-curl -fsSL https://download.docker.com/linux/$family/gpg | $SUDO gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
-echo deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/$family/ $distro stable | \
+DOCKER_URL="https://download.docker.com/linux/$DISTRO_ID"
+curl -fsSL "$DOCKER_URL/gpg" | $SUDO gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
+echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] $DOCKER_URL/ $VERSION_CODENAME stable" | \
     $SUDO tee /etc/apt/sources.list.d/docker.list
 $SUDO apt-get update
     $SUDO tee /etc/apt/sources.list.d/docker.list
 $SUDO apt-get update
-$SUDO apt-get -yq --no-install-recommends install docker-ce=${dockerversion}~${family}-${distro}
+$SUDO apt-get -yq --no-install-recommends install docker-ce
 
 # Set a higher ulimit and the resolver (if set) for docker
 $SUDO sed "s/ExecStart=\(.*\)/ExecStart=\1 --default-ulimit nofile=10000:10000 ${SET_RESOLVER}/g" \
 
 # Set a higher ulimit and the resolver (if set) for docker
 $SUDO sed "s/ExecStart=\(.*\)/ExecStart=\1 --default-ulimit nofile=10000:10000 ${SET_RESOLVER}/g" \
@@ -173,7 +150,7 @@ $SUDO chown root:root /etc/cloud/cloud.cfg.d/07_compute_arvados_dispatch_cloud.c
 
 if [ "$NVIDIA_GPU_SUPPORT" == "1" ]; then
   # We need a kernel and matching headers
 
 if [ "$NVIDIA_GPU_SUPPORT" == "1" ]; then
   # We need a kernel and matching headers
-  if [[ "$DIST" =~ ^debian ]]; then
+  if [[ "$DISTRO_ID" == debian ]]; then
     $SUDO apt-get -y install linux-image-cloud-amd64 linux-headers-cloud-amd64
   elif [ "$CLOUD" == "azure" ]; then
     $SUDO apt-get -y install linux-image-azure linux-headers-azure
     $SUDO apt-get -y install linux-image-cloud-amd64 linux-headers-cloud-amd64
   elif [ "$CLOUD" == "azure" ]; then
     $SUDO apt-get -y install linux-image-azure linux-headers-azure
@@ -182,10 +159,11 @@ if [ "$NVIDIA_GPU_SUPPORT" == "1" ]; then
   fi
 
   # Install CUDA
   fi
 
   # Install CUDA
-  $SUDO apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/$DIST/x86_64/7fa2af80.pub
-  $SUDO apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/$DIST/x86_64/3bf863cc.pub
+  NVIDIA_URL="https://developer.download.nvidia.com/compute/cuda/repos/$(echo "$DISTRO_ID$VERSION_ID" | tr -d .)/x86_64"
+  $SUDO apt-key adv --fetch-keys "$NVIDIA_URL/7fa2af80.pub"
+  $SUDO apt-key adv --fetch-keys "$NVIDIA_URL/3bf863cc.pub"
   $SUDO apt-get -y install software-properties-common
   $SUDO apt-get -y install software-properties-common
-  $SUDO add-apt-repository "deb https://developer.download.nvidia.com/compute/cuda/repos/$DIST/x86_64/ /"
+  $SUDO add-apt-repository "deb $NVIDIA_URL/ /"
   $SUDO add-apt-repository contrib
   $SUDO apt-get update
   $SUDO apt-get -y install cuda
   $SUDO add-apt-repository contrib
   $SUDO apt-get update
   $SUDO apt-get -y install cuda
@@ -193,7 +171,7 @@ if [ "$NVIDIA_GPU_SUPPORT" == "1" ]; then
   # Install libnvidia-container, the tooling for Docker/Singularity
   curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | \
     $SUDO apt-key add -
   # Install libnvidia-container, the tooling for Docker/Singularity
   curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | \
     $SUDO apt-key add -
-  if [ "$DIST" == "debian11" ]; then
+  if [[ "$VERSION_CODENAME" == bullseye ]]; then
     # As of 2021-12-16 libnvidia-container and friends are only available for
     # Debian 10, not yet Debian 11. Install experimental rc1 package as per this
     # workaround:
     # As of 2021-12-16 libnvidia-container and friends are only available for
     # Debian 10, not yet Debian 11. Install experimental rc1 package as per this
     # workaround:
@@ -202,9 +180,7 @@ if [ "$NVIDIA_GPU_SUPPORT" == "1" ]; then
       $SUDO tee /etc/apt/sources.list.d/libnvidia-container.list
     $SUDO sed -i -e '/experimental/ s/^#//g' /etc/apt/sources.list.d/libnvidia-container.list
   else
       $SUDO tee /etc/apt/sources.list.d/libnvidia-container.list
     $SUDO sed -i -e '/experimental/ s/^#//g' /etc/apt/sources.list.d/libnvidia-container.list
   else
-    # here, $DIST should have a dot if there is one in /etc/os-release (e.g. 18.04)...
-    DIST=$(. /etc/os-release; echo $ID$VERSION_ID)
-    curl -s -L https://nvidia.github.io/libnvidia-container/$DIST/libnvidia-container.list | \
+    curl -s -L "https://nvidia.github.io/libnvidia-container/$DISTRO_ID$VERSION_ID/libnvidia-container.list" | \
       $SUDO tee /etc/apt/sources.list.d/libnvidia-container.list
   fi
 
       $SUDO tee /etc/apt/sources.list.d/libnvidia-container.list
   fi
 
index d8eec3d9ee98bcdf1bd2ea603d237c5265c1750d..ca20de4c5813d4d0a9bf7dc6a1e74ce7ac68c43c 100644 (file)
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
+#
+# This file runs in one of three modes:
+#
+# 1. If the ARVADOS_BUILDING_VERSION environment variable is set, it writes
+#    _version.py and generates dependencies based on that value.
+# 2. If running from an arvados Git checkout, it writes _version.py
+#    and generates dependencies from Git.
+# 3. Otherwise, we expect this is source previously generated from Git, and
+#    it reads _version.py and generates dependencies from it.
 
 
-import subprocess
-import time
 import os
 import re
 import os
 import re
+import runpy
+import subprocess
 import sys
 
 import sys
 
-SETUP_DIR = os.path.dirname(os.path.abspath(__file__))
-VERSION_PATHS = {
-        SETUP_DIR,
-        os.path.abspath(os.path.join(SETUP_DIR, "../../sdk/python")),
-        os.path.abspath(os.path.join(SETUP_DIR, "../../build/version-at-commit.sh"))
-        }
+from pathlib import Path
+
+# These maps explain the relationships between different Python modules in
+# the arvados repository. We use these to help generate setup.py.
+PACKAGE_DEPENDENCY_MAP = {
+    'arvados-cwl-runner': ['arvados-python-client', 'crunchstat_summary'],
+    'arvados-user-activity': ['arvados-python-client'],
+    'arvados_fuse': ['arvados-python-client'],
+    'crunchstat_summary': ['arvados-python-client'],
+}
+PACKAGE_MODULE_MAP = {
+    'arvados-cwl-runner': 'arvados_cwl',
+    'arvados-docker-cleaner': 'arvados_docker',
+    'arvados-python-client': 'arvados',
+    'arvados-user-activity': 'arvados_user_activity',
+    'arvados_fuse': 'arvados_fuse',
+    'crunchstat_summary': 'crunchstat_summary',
+}
+PACKAGE_SRCPATH_MAP = {
+    'arvados-cwl-runner': Path('sdk', 'cwl'),
+    'arvados-docker-cleaner': Path('services', 'dockercleaner'),
+    'arvados-python-client': Path('sdk', 'python'),
+    'arvados-user-activity': Path('tools', 'user-activity'),
+    'arvados_fuse': Path('services', 'fuse'),
+    'crunchstat_summary': Path('tools', 'crunchstat-summary'),
+}
+
+ENV_VERSION = os.environ.get("ARVADOS_BUILDING_VERSION")
+SETUP_DIR = Path(__file__).absolute().parent
+try:
+    REPO_PATH = Path(subprocess.check_output(
+        ['git', '-C', str(SETUP_DIR), 'rev-parse', '--show-toplevel'],
+        stderr=subprocess.DEVNULL,
+        text=True,
+    ).rstrip('\n'))
+except (subprocess.CalledProcessError, OSError):
+    REPO_PATH = None
+else:
+    # Verify this is the arvados monorepo
+    if all((REPO_PATH / path).exists() for path in PACKAGE_SRCPATH_MAP.values()):
+        PACKAGE_NAME, = (
+            pkg_name for pkg_name, path in PACKAGE_SRCPATH_MAP.items()
+            if (REPO_PATH / path) == SETUP_DIR
+        )
+        MODULE_NAME = PACKAGE_MODULE_MAP[PACKAGE_NAME]
+        VERSION_SCRIPT_PATH = Path(REPO_PATH, 'build', 'version-at-commit.sh')
+    else:
+        REPO_PATH = None
+if REPO_PATH is None:
+    (PACKAGE_NAME, MODULE_NAME), = (
+        (pkg_name, mod_name)
+        for pkg_name, mod_name in PACKAGE_MODULE_MAP.items()
+        if (SETUP_DIR / mod_name).is_dir()
+    )
+
+def git_log_output(path, *args):
+    return subprocess.check_output(
+        ['git', '-C', str(REPO_PATH),
+         'log', '--first-parent', '--max-count=1',
+         *args, str(path)],
+        text=True,
+    ).rstrip('\n')
 
 def choose_version_from():
 
 def choose_version_from():
-    ts = {}
-    for path in VERSION_PATHS:
-        ts[subprocess.check_output(
-            ['git', 'log', '--first-parent', '--max-count=1',
-             '--format=format:%ct', path]).strip()] = path
-
-    sorted_ts = sorted(ts.items())
-    getver = sorted_ts[-1][1]
-    print("Using "+getver+" for version number calculation of "+SETUP_DIR, file=sys.stderr)
+    ver_paths = [SETUP_DIR, VERSION_SCRIPT_PATH, *(
+        PACKAGE_SRCPATH_MAP[pkg]
+        for pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ())
+    )]
+    getver = max(ver_paths, key=lambda path: git_log_output(path, '--format=format:%ct'))
+    print(f"Using {getver} for version number calculation of {SETUP_DIR}", file=sys.stderr)
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
-    myhash = subprocess.check_output(['git', 'log', '-n1', '--first-parent',
-                                       '--format=%H', curdir]).strip()
-    myversion = subprocess.check_output([SETUP_DIR+'/../../build/version-at-commit.sh', myhash]).strip().decode()
-    return myversion
+    myhash = git_log_output(curdir, '--format=%H')
+    return subprocess.check_output(
+        [str(VERSION_SCRIPT_PATH), myhash],
+        text=True,
+    ).rstrip('\n')
 
 def save_version(setup_dir, module, v):
 
 def save_version(setup_dir, module, v):
-    v = v.replace("~dev", ".dev").replace("~rc", "rc")
-    with open(os.path.join(setup_dir, module, "_version.py"), 'wt') as fp:
-        return fp.write("__version__ = '%s'\n" % v)
+    with Path(setup_dir, module, '_version.py').open('w') as fp:
+        print(f"__version__ = {v!r}", file=fp)
 
 def read_version(setup_dir, module):
 
 def read_version(setup_dir, module):
-    with open(os.path.join(setup_dir, module, "_version.py"), 'rt') as fp:
-        return re.match("__version__ = '(.*)'$", fp.read()).groups()[0]
+    file_vars = runpy.run_path(Path(setup_dir, module, '_version.py'))
+    return file_vars['__version__']
 
 
-def get_version(setup_dir, module):
-    env_version = os.environ.get("ARVADOS_BUILDING_VERSION")
-
-    if env_version:
-        save_version(setup_dir, module, env_version)
+def get_version(setup_dir=SETUP_DIR, module=MODULE_NAME):
+    if ENV_VERSION:
+        version = ENV_VERSION
+    elif REPO_PATH is None:
+        return read_version(setup_dir, module)
     else:
     else:
-        try:
-            save_version(setup_dir, module, git_version_at_commit())
-        except (subprocess.CalledProcessError, OSError) as err:
-            print("ERROR: {0}".format(err), file=sys.stderr)
-            pass
+        version = git_version_at_commit()
+    version = version.replace("~dev", ".dev").replace("~rc", "rc")
+    save_version(setup_dir, module, version)
+    return version
+
+def iter_dependencies(version=None):
+    if version is None:
+        version = get_version()
+    # A packaged development release should be installed with other
+    # development packages built from the same source, but those
+    # dependencies may have earlier "dev" versions (read: less recent
+    # Git commit timestamps). This compatible version dependency
+    # expresses that as closely as possible. Allowing versions
+    # compatible with .dev0 allows any development release.
+    # Regular expression borrowed partially from
+    # <https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers-regex>
+    dep_ver, match_count = re.subn(r'\.dev(0|[1-9][0-9]*)$', '.dev0', version, 1)
+    dep_op = '~=' if match_count else '=='
+    for dep_pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ()):
+        yield f'{dep_pkg}{dep_op}{dep_ver}'
 
 
-    return read_version(setup_dir, module)
+# Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
+if __name__ == '__main__':
+    print(get_version())
index 3c18829189072b39c6e4337af55c16c584784a65..bfd85ec9e1a3eb451fe382b7f0e7baea4bfff56b 100755 (executable)
@@ -3,8 +3,6 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import print_function
-
 import crunchstat_summary.command
 import crunchstat_summary.summarizer
 import logging
 import crunchstat_summary.command
 import crunchstat_summary.summarizer
 import logging
diff --git a/tools/crunchstat-summary/pytest.ini b/tools/crunchstat-summary/pytest.ini
new file mode 120000 (symlink)
index 0000000..05a82db
--- /dev/null
@@ -0,0 +1 @@
+../../sdk/python/pytest.ini
\ No newline at end of file
index 98be9f27025b4c3c7828626dd7676fddbe845dfd..d5adc92367a9f61aa84bb0044c3950c3a5c4b295 100755 (executable)
@@ -3,28 +3,15 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
-SETUP_DIR = os.path.dirname(__file__) or '.'
-README = os.path.join(SETUP_DIR, 'README.rst')
-
 import arvados_version
 import arvados_version
-version = arvados_version.get_version(SETUP_DIR, "crunchstat_summary")
-if os.environ.get('ARVADOS_BUILDING_VERSION', False):
-    pysdk_dep = "=={}".format(version)
-else:
-    # On dev releases, arvados-python-client may have a different timestamp
-    pysdk_dep = "<={}".format(version)
-
-short_tests_only = False
-if '--short-tests-only' in sys.argv:
-    short_tests_only = True
-    sys.argv.remove('--short-tests-only')
+version = arvados_version.get_version()
+README = os.path.join(arvados_version.SETUP_DIR, 'README.rst')
 
 setup(name='crunchstat_summary',
       version=version,
 
 setup(name='crunchstat_summary',
       version=version,
@@ -43,10 +30,9 @@ setup(name='crunchstat_summary',
           ('share/doc/crunchstat_summary', ['agpl-3.0.txt']),
       ],
       install_requires=[
           ('share/doc/crunchstat_summary', ['agpl-3.0.txt']),
       ],
       install_requires=[
-          'arvados-python-client{}'.format(pysdk_dep),
+          *arvados_version.iter_dependencies(version),
       ],
       python_requires="~=3.8",
       test_suite='tests',
       ],
       python_requires="~=3.8",
       test_suite='tests',
-      tests_require=['pbr<1.7.0', 'mock>=1.0'],
       zip_safe=False,
 )
       zip_safe=False,
 )
index 5a20d3283f813341cc47e51b5e46231dc92b6829..7d97fd3edc474fc48b6fa7eede25134397c98c65 100644 (file)
@@ -10,11 +10,12 @@ import glob
 import gzip
 import io
 import logging
 import gzip
 import io
 import logging
-import mock
 import os
 import sys
 import unittest
 
 import os
 import sys
 import unittest
 
+from unittest import mock
+
 from crunchstat_summary.command import UTF8Decode
 from crunchstat_summary import logger, reader
 
 from crunchstat_summary.command import UTF8Decode
 from crunchstat_summary import logger, reader
 
index d77e5936402df5c67f7657affa999182c4b91f90..77e8bc5d9189ec44de475dede8c7a2cb45851d15 100755 (executable)
@@ -5,7 +5,6 @@
 # SPDX-License-Identifier: AGPL-3.0
 #
 
 # SPDX-License-Identifier: AGPL-3.0
 #
 
-from __future__ import print_function, absolute_import
 import argparse
 import arvados
 import arvados.util
 import argparse
 import arvados
 import arvados.util
index 16e686ab8069747227a9a206c100496f2929b8ce..ce95a256b08a1fd647f3020688a170d0fd7d0406 100644 (file)
@@ -166,7 +166,6 @@ arvados:
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
-      AutoSetupNewUsersWithRepository: true
 
     Services:
       Controller:
 
     Services:
       Controller:
diff --git a/tools/salt-install/config_examples/multi_host/aws/pillars/logrotate.sls b/tools/salt-install/config_examples/multi_host/aws/pillars/logrotate.sls
new file mode 100644 (file)
index 0000000..8c455e9
--- /dev/null
@@ -0,0 +1,14 @@
+---
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+# The logrotate formula checks that an associated service is running.
+# The default it checks is cron.
+# All the distributions Arvados supports (Debian 11+/Ubuntu 20.04+)
+# have switched to a systemd timer, so check that instead.
+# Refer to logrotate-formula's documentation for details
+# https://github.com/salt-formulas/salt-formula-logrotate/blob/master/README.rst
+
+logrotate:
+  service: logrotate.timer
index bfe0386e9316fe848bccf5e775d452c1462e653c..d27552f6fbecada020d4d9aac8d093d567b32b59 100644 (file)
@@ -25,4 +25,5 @@ nginx:
             - access_log: /var/log/nginx/api.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/api.__DOMAIN__-upstream.error.log
             - passenger_enabled: 'on'
             - access_log: /var/log/nginx/api.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/api.__DOMAIN__-upstream.error.log
             - passenger_enabled: 'on'
+            - passenger_preload_bundler: 'on'
             - client_max_body_size: 128m
             - client_max_body_size: 128m
index 271ab502908578c70a7373787cb0d29213a576f5..a2bfdb19b0ddf3d75c357c2c3e3ba19c2d4d801a 100644 (file)
@@ -128,7 +128,6 @@ arvados:
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
-      AutoSetupNewUsersWithRepository: true
 
     Services:
       Controller:
 
     Services:
       Controller:
diff --git a/tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/logrotate.sls b/tools/salt-install/config_examples/single_host/multiple_hostnames/pillars/logrotate.sls
new file mode 100644 (file)
index 0000000..8c455e9
--- /dev/null
@@ -0,0 +1,14 @@
+---
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+# The logrotate formula checks that an associated service is running.
+# The default it checks is cron.
+# All the distributions Arvados supports (Debian 11+/Ubuntu 20.04+)
+# have switched to a systemd timer, so check that instead.
+# Refer to logrotate-formula's documentation for details
+# https://github.com/salt-formulas/salt-formula-logrotate/blob/master/README.rst
+
+logrotate:
+  service: logrotate.timer
index 54087f6d6d0fe43ae9c1a12e71ac2604935a2635..b567af90d7fbff5bf6be71d2c8a6e9c4b84fb95c 100644 (file)
@@ -31,4 +31,5 @@ nginx:
             - access_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.error.log
             - passenger_enabled: 'on'
             - access_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.error.log
             - passenger_enabled: 'on'
+            - passenger_preload_bundler: 'on'
             - client_max_body_size: 128m
             - client_max_body_size: 128m
index 9e3a293110afaa76c0ad3d9ca27300174747a287..03db62ab5de38af869cdd1e074f904ac0602dc6c 100644 (file)
@@ -131,7 +131,6 @@ arvados:
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
       NewUsersAreActive: true
       AutoAdminFirstUser: true
       AutoSetupNewUsers: true
-      AutoSetupNewUsersWithRepository: true
 
     Services:
       Controller:
 
     Services:
       Controller:
diff --git a/tools/salt-install/config_examples/single_host/single_hostname/pillars/logrotate.sls b/tools/salt-install/config_examples/single_host/single_hostname/pillars/logrotate.sls
new file mode 100644 (file)
index 0000000..8c455e9
--- /dev/null
@@ -0,0 +1,14 @@
+---
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+# The logrotate formula checks that an associated service is running.
+# The default it checks is cron.
+# All the distributions Arvados supports (Debian 11+/Ubuntu 20.04+)
+# have switched to a systemd timer, so check that instead.
+# Refer to logrotate-formula's documentation for details
+# https://github.com/salt-formulas/salt-formula-logrotate/blob/master/README.rst
+
+logrotate:
+  service: logrotate.timer
index 04195ae5b9b23e25f21ad1703b66c4a2116cfb21..3bf7bf54abbbff0e04c4a64849136f17bcca9de8 100644 (file)
@@ -31,4 +31,5 @@ nginx:
             - access_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.error.log
             - passenger_enabled: 'on'
             - access_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/api.__CLUSTER__.__DOMAIN__-upstream.error.log
             - passenger_enabled: 'on'
+            - passenger_preload_bundler: 'on'
             - client_max_body_size: 128m
             - client_max_body_size: 128m
index 59fb43e57af40d70736dc27822c304bdce76f1c6..5d5d0af6684c272d296f2143045f22d196337880 100644 (file)
@@ -78,6 +78,7 @@ nginx:
             - root: /var/www/arvados-workbench/current/public
             - index:  index.html index.htm
             - passenger_enabled: 'on'
             - root: /var/www/arvados-workbench/current/public
             - index:  index.html index.htm
             - passenger_enabled: 'on'
+            - passenger_preload_bundler: 'on'
             # yamllint disable-line rule:line-length
             - access_log: /var/log/nginx/workbench.__CLUSTER__.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/workbench.__CLUSTER__.__DOMAIN__-upstream.error.log
             # yamllint disable-line rule:line-length
             - access_log: /var/log/nginx/workbench.__CLUSTER__.__DOMAIN__-upstream.access.log combined
             - error_log: /var/log/nginx/workbench.__CLUSTER__.__DOMAIN__-upstream.error.log
index 439293c2967325318adfe76f8c46b67ee607edc8..36e87cca91f1e774cc20193409815cc58f5f4c9a 100755 (executable)
@@ -449,14 +449,38 @@ diagnostics)
   arvados-client diagnostics $LOCATION
   ;;
 
   arvados-client diagnostics $LOCATION
   ;;
 
+diagnostics-internal)
+  loadconfig
+  set -u
+
+  if [ -z "${ROLE2NODES['shell']:-}" ]; then
+    echo "No node with 'shell' role was found, cannot run diagnostics-internal"
+    exit 1
+  fi
+
+  # Pick the first shell node for test running
+  declare TESTNODE=$(echo ${ROLE2NODES['shell']} | cut -d\, -f1)
+  declare SSH=$(ssh_cmd "$TESTNODE")
+
+  # Run diagnostics
+  echo "Running diagnostics in $TESTNODE..."
+  $SSH $DEPLOY_USER@$TESTNODE bash <<EOF
+  export ARVADOS_API_HOST="${DOMAIN}:${CONTROLLER_EXT_SSL_PORT}"
+  export ARVADOS_API_TOKEN="$SYSTEM_ROOT_TOKEN"
+  sudo --preserve-env=ARVADOS_API_HOST,ARVADOS_API_TOKEN arvados-client diagnostics -internal-client
+EOF
+
+  ;;
+
 *)
   echo "Arvados installer"
   echo ""
 *)
   echo "Arvados installer"
   echo ""
-  echo "initialize        initialize the setup directory for configuration"
-  echo "terraform         create cloud resources using terraform"
-  echo "terraform-destroy destroy cloud resources created by terraform"
-  echo "generate-tokens   generate random values for tokens"
-  echo "deploy            deploy the configuration from the setup directory"
-  echo "diagnostics       check your install using diagnostics"
+  echo "initialize             initialize the setup directory for configuration"
+  echo "terraform              create cloud resources using terraform"
+  echo "terraform-destroy      destroy cloud resources created by terraform"
+  echo "generate-tokens        generate random values for tokens"
+  echo "deploy                 deploy the configuration from the setup directory"
+  echo "diagnostics            check your install running diagnostics locally"
+  echo "diagnostics-internal   check your install running diagnostics on a shell node"
   ;;
 esac
   ;;
 esac
index 8dd07020c349942ff9f3936e8462e8a7b5b44026..c78f65e9ca4c4426a7154a4774ae38d5bb53d04a 100755 (executable)
@@ -284,17 +284,15 @@ VERSION="latest"
 
 # We pin the salt version to avoid potential incompatibilities when a new
 # stable version is released.
 
 # We pin the salt version to avoid potential incompatibilities when a new
 # stable version is released.
-SALT_VERSION="3004"
+SALT_VERSION="3006"
 
 # Other formula versions we depend on
 
 # Other formula versions we depend on
-#POSTGRES_TAG="v0.44.0"
-#POSTGRES_URL="https://github.com/saltstack-formulas/postgres-formula.git"
-POSTGRES_TAG="0.45.0-bugfix327"
-POSTGRES_URL="https://github.com/arvados/postgres-formula.git"
+POSTGRES_TAG="7529300c287b1c288af0f494ca668c2217bd1c5d"
+POSTGRES_URL="https://github.com/saltstack-formulas/postgres-formula.git"
 NGINX_TAG="v2.8.1"
 DOCKER_TAG="v2.4.2"
 NGINX_TAG="v2.8.1"
 DOCKER_TAG="v2.4.2"
-LOCALE_TAG="v0.3.4"
-LETSENCRYPT_TAG="v2.1.0"
+LOCALE_TAG="v0.3.5"
+LETSENCRYPT_TAG="v3.2.0"
 LOGROTATE_TAG="v0.14.0"
 PROMETHEUS_TAG="v5.6.5"
 GRAFANA_TAG="v3.1.3"
 LOGROTATE_TAG="v0.14.0"
 PROMETHEUS_TAG="v5.6.5"
 GRAFANA_TAG="v3.1.3"
@@ -362,23 +360,24 @@ fi
 if [ "${DUMP_CONFIG}" = "yes" ]; then
   echo "The provision installer will just dump a config under ${DUMP_SALT_CONFIG_DIR} and exit"
 else
 if [ "${DUMP_CONFIG}" = "yes" ]; then
   echo "The provision installer will just dump a config under ${DUMP_SALT_CONFIG_DIR} and exit"
 else
-  # Install a few dependency packages
-  # First, let's figure out the OS we're working on
   OS_IDS="$(. /etc/os-release && echo "${ID:-} ${ID_LIKE:-}")"
   echo "Detected distro families: $OS_IDS"
 
   OS_IDS="$(. /etc/os-release && echo "${ID:-} ${ID_LIKE:-}")"
   echo "Detected distro families: $OS_IDS"
 
+  # Several of our formulas use the cron module, which requires the crontab
+  # command. We install systemd-cron to ensure we have that.
+  # The rest of these packages are required by the rest of the script.
   for OS_ID in $OS_IDS; do
     case "$OS_ID" in
       rhel)
         echo "WARNING! Disabling SELinux, see https://dev.arvados.org/issues/18019"
         sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/sysconfig/selinux
         setenforce permissive
   for OS_ID in $OS_IDS; do
     case "$OS_ID" in
       rhel)
         echo "WARNING! Disabling SELinux, see https://dev.arvados.org/issues/18019"
         sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/sysconfig/selinux
         setenforce permissive
-        yum install -y  curl git jq
+        yum install -y curl git jq systemd-cron
         break
         ;;
       debian)
         DEBIAN_FRONTEND=noninteractive apt -o DPkg::Lock::Timeout=120 update
         break
         ;;
       debian)
         DEBIAN_FRONTEND=noninteractive apt -o DPkg::Lock::Timeout=120 update
-        DEBIAN_FRONTEND=noninteractive apt install -y curl git jq
+        DEBIAN_FRONTEND=noninteractive apt install -y curl git jq systemd-cron
         break
         ;;
     esac
         break
         ;;
     esac
@@ -388,7 +387,7 @@ else
     echo "Salt already installed"
   else
     curl -L https://bootstrap.saltstack.com -o /tmp/bootstrap_salt.sh
     echo "Salt already installed"
   else
     curl -L https://bootstrap.saltstack.com -o /tmp/bootstrap_salt.sh
-    sh /tmp/bootstrap_salt.sh -XdfP -x python3 old-stable ${SALT_VERSION}
+    sh /tmp/bootstrap_salt.sh -XdfP -x python3 stable ${SALT_VERSION}
     /bin/systemctl stop salt-minion.service
     /bin/systemctl disable salt-minion.service
   fi
     /bin/systemctl stop salt-minion.service
     /bin/systemctl disable salt-minion.service
   fi
@@ -431,7 +430,7 @@ test -d nginx && ( cd nginx && git fetch ) \
 echo "...postgres"
 test -d postgres && ( cd postgres && git fetch ) \
   || git clone --quiet ${POSTGRES_URL} ${F_DIR}/postgres
 echo "...postgres"
 test -d postgres && ( cd postgres && git fetch ) \
   || git clone --quiet ${POSTGRES_URL} ${F_DIR}/postgres
-( cd postgres && git checkout --quiet tags/"${POSTGRES_TAG}" )
+( cd postgres && git checkout --quiet "${POSTGRES_TAG}" )
 
 echo "...prometheus"
 test -d prometheus && ( cd prometheus && git fetch ) \
 
 echo "...prometheus"
 test -d prometheus && ( cd prometheus && git fetch ) \
@@ -620,6 +619,7 @@ if [ -z "${ROLES:-}" ]; then
   # Pillars
   echo "    - docker" >> ${PILLARS_TOP}
   echo "    - nginx_api_configuration" >> ${PILLARS_TOP}
   # Pillars
   echo "    - docker" >> ${PILLARS_TOP}
   echo "    - nginx_api_configuration" >> ${PILLARS_TOP}
+  echo "    - logrotate" >> ${PILLARS_TOP}
   echo "    - logrotate_api" >> ${PILLARS_TOP}
   echo "    - nginx_controller_configuration" >> ${PILLARS_TOP}
   echo "    - nginx_keepproxy_configuration" >> ${PILLARS_TOP}
   echo "    - logrotate_api" >> ${PILLARS_TOP}
   echo "    - nginx_controller_configuration" >> ${PILLARS_TOP}
   echo "    - nginx_keepproxy_configuration" >> ${PILLARS_TOP}
@@ -855,6 +855,7 @@ else
         grep -q "arvados.controller" ${STATES_TOP} || echo "    - arvados.controller" >> ${STATES_TOP}
 
         ### Pillars ###
         grep -q "arvados.controller" ${STATES_TOP} || echo "    - arvados.controller" >> ${STATES_TOP}
 
         ### Pillars ###
+        grep -q "logrotate" ${PILLARS_TOP}                || echo "    - logrotate" >> ${PILLARS_TOP}
         grep -q "logrotate_api" ${PILLARS_TOP}            || echo "    - logrotate_api" >> ${PILLARS_TOP}
         grep -q "aws_credentials" ${PILLARS_TOP}          || echo "    - aws_credentials" >> ${PILLARS_TOP}
         grep -q "postgresql" ${PILLARS_TOP}               || echo "    - postgresql" >> ${PILLARS_TOP}
         grep -q "logrotate_api" ${PILLARS_TOP}            || echo "    - logrotate_api" >> ${PILLARS_TOP}
         grep -q "aws_credentials" ${PILLARS_TOP}          || echo "    - aws_credentials" >> ${PILLARS_TOP}
         grep -q "postgresql" ${PILLARS_TOP}               || echo "    - postgresql" >> ${PILLARS_TOP}
index 54e2fc412bc8b87883ab8fe83bfaab5b9230104a..06564edad6cb3efd6c82f67b2874b300def493d3 100644 (file)
@@ -70,6 +70,7 @@ resource "aws_instance" "arvados_service" {
   metadata_options {
     # Sets IMDSv2 to required. Default is "optional".
     http_tokens = "required"
   metadata_options {
     # Sets IMDSv2 to required. Default is "optional".
     http_tokens = "required"
+    http_endpoint = "enabled"
   }
   lifecycle {
     ignore_changes = [
   }
   lifecycle {
     ignore_changes = [
index d8eec3d9ee98bcdf1bd2ea603d237c5265c1750d..ca20de4c5813d4d0a9bf7dc6a1e74ce7ac68c43c 100644 (file)
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
 # Copyright (C) The Arvados Authors. All rights reserved.
 #
 # SPDX-License-Identifier: Apache-2.0
+#
+# This file runs in one of three modes:
+#
+# 1. If the ARVADOS_BUILDING_VERSION environment variable is set, it writes
+#    _version.py and generates dependencies based on that value.
+# 2. If running from an arvados Git checkout, it writes _version.py
+#    and generates dependencies from Git.
+# 3. Otherwise, we expect this is source previously generated from Git, and
+#    it reads _version.py and generates dependencies from it.
 
 
-import subprocess
-import time
 import os
 import re
 import os
 import re
+import runpy
+import subprocess
 import sys
 
 import sys
 
-SETUP_DIR = os.path.dirname(os.path.abspath(__file__))
-VERSION_PATHS = {
-        SETUP_DIR,
-        os.path.abspath(os.path.join(SETUP_DIR, "../../sdk/python")),
-        os.path.abspath(os.path.join(SETUP_DIR, "../../build/version-at-commit.sh"))
-        }
+from pathlib import Path
+
+# These maps explain the relationships between different Python modules in
+# the arvados repository. We use these to help generate setup.py.
+PACKAGE_DEPENDENCY_MAP = {
+    'arvados-cwl-runner': ['arvados-python-client', 'crunchstat_summary'],
+    'arvados-user-activity': ['arvados-python-client'],
+    'arvados_fuse': ['arvados-python-client'],
+    'crunchstat_summary': ['arvados-python-client'],
+}
+PACKAGE_MODULE_MAP = {
+    'arvados-cwl-runner': 'arvados_cwl',
+    'arvados-docker-cleaner': 'arvados_docker',
+    'arvados-python-client': 'arvados',
+    'arvados-user-activity': 'arvados_user_activity',
+    'arvados_fuse': 'arvados_fuse',
+    'crunchstat_summary': 'crunchstat_summary',
+}
+PACKAGE_SRCPATH_MAP = {
+    'arvados-cwl-runner': Path('sdk', 'cwl'),
+    'arvados-docker-cleaner': Path('services', 'dockercleaner'),
+    'arvados-python-client': Path('sdk', 'python'),
+    'arvados-user-activity': Path('tools', 'user-activity'),
+    'arvados_fuse': Path('services', 'fuse'),
+    'crunchstat_summary': Path('tools', 'crunchstat-summary'),
+}
+
+ENV_VERSION = os.environ.get("ARVADOS_BUILDING_VERSION")
+SETUP_DIR = Path(__file__).absolute().parent
+try:
+    REPO_PATH = Path(subprocess.check_output(
+        ['git', '-C', str(SETUP_DIR), 'rev-parse', '--show-toplevel'],
+        stderr=subprocess.DEVNULL,
+        text=True,
+    ).rstrip('\n'))
+except (subprocess.CalledProcessError, OSError):
+    REPO_PATH = None
+else:
+    # Verify this is the arvados monorepo
+    if all((REPO_PATH / path).exists() for path in PACKAGE_SRCPATH_MAP.values()):
+        PACKAGE_NAME, = (
+            pkg_name for pkg_name, path in PACKAGE_SRCPATH_MAP.items()
+            if (REPO_PATH / path) == SETUP_DIR
+        )
+        MODULE_NAME = PACKAGE_MODULE_MAP[PACKAGE_NAME]
+        VERSION_SCRIPT_PATH = Path(REPO_PATH, 'build', 'version-at-commit.sh')
+    else:
+        REPO_PATH = None
+if REPO_PATH is None:
+    (PACKAGE_NAME, MODULE_NAME), = (
+        (pkg_name, mod_name)
+        for pkg_name, mod_name in PACKAGE_MODULE_MAP.items()
+        if (SETUP_DIR / mod_name).is_dir()
+    )
+
+def git_log_output(path, *args):
+    return subprocess.check_output(
+        ['git', '-C', str(REPO_PATH),
+         'log', '--first-parent', '--max-count=1',
+         *args, str(path)],
+        text=True,
+    ).rstrip('\n')
 
 def choose_version_from():
 
 def choose_version_from():
-    ts = {}
-    for path in VERSION_PATHS:
-        ts[subprocess.check_output(
-            ['git', 'log', '--first-parent', '--max-count=1',
-             '--format=format:%ct', path]).strip()] = path
-
-    sorted_ts = sorted(ts.items())
-    getver = sorted_ts[-1][1]
-    print("Using "+getver+" for version number calculation of "+SETUP_DIR, file=sys.stderr)
+    ver_paths = [SETUP_DIR, VERSION_SCRIPT_PATH, *(
+        PACKAGE_SRCPATH_MAP[pkg]
+        for pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ())
+    )]
+    getver = max(ver_paths, key=lambda path: git_log_output(path, '--format=format:%ct'))
+    print(f"Using {getver} for version number calculation of {SETUP_DIR}", file=sys.stderr)
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
     return getver
 
 def git_version_at_commit():
     curdir = choose_version_from()
-    myhash = subprocess.check_output(['git', 'log', '-n1', '--first-parent',
-                                       '--format=%H', curdir]).strip()
-    myversion = subprocess.check_output([SETUP_DIR+'/../../build/version-at-commit.sh', myhash]).strip().decode()
-    return myversion
+    myhash = git_log_output(curdir, '--format=%H')
+    return subprocess.check_output(
+        [str(VERSION_SCRIPT_PATH), myhash],
+        text=True,
+    ).rstrip('\n')
 
 def save_version(setup_dir, module, v):
 
 def save_version(setup_dir, module, v):
-    v = v.replace("~dev", ".dev").replace("~rc", "rc")
-    with open(os.path.join(setup_dir, module, "_version.py"), 'wt') as fp:
-        return fp.write("__version__ = '%s'\n" % v)
+    with Path(setup_dir, module, '_version.py').open('w') as fp:
+        print(f"__version__ = {v!r}", file=fp)
 
 def read_version(setup_dir, module):
 
 def read_version(setup_dir, module):
-    with open(os.path.join(setup_dir, module, "_version.py"), 'rt') as fp:
-        return re.match("__version__ = '(.*)'$", fp.read()).groups()[0]
+    file_vars = runpy.run_path(Path(setup_dir, module, '_version.py'))
+    return file_vars['__version__']
 
 
-def get_version(setup_dir, module):
-    env_version = os.environ.get("ARVADOS_BUILDING_VERSION")
-
-    if env_version:
-        save_version(setup_dir, module, env_version)
+def get_version(setup_dir=SETUP_DIR, module=MODULE_NAME):
+    if ENV_VERSION:
+        version = ENV_VERSION
+    elif REPO_PATH is None:
+        return read_version(setup_dir, module)
     else:
     else:
-        try:
-            save_version(setup_dir, module, git_version_at_commit())
-        except (subprocess.CalledProcessError, OSError) as err:
-            print("ERROR: {0}".format(err), file=sys.stderr)
-            pass
+        version = git_version_at_commit()
+    version = version.replace("~dev", ".dev").replace("~rc", "rc")
+    save_version(setup_dir, module, version)
+    return version
+
+def iter_dependencies(version=None):
+    if version is None:
+        version = get_version()
+    # A packaged development release should be installed with other
+    # development packages built from the same source, but those
+    # dependencies may have earlier "dev" versions (read: less recent
+    # Git commit timestamps). This compatible version dependency
+    # expresses that as closely as possible. Allowing versions
+    # compatible with .dev0 allows any development release.
+    # Regular expression borrowed partially from
+    # <https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers-regex>
+    dep_ver, match_count = re.subn(r'\.dev(0|[1-9][0-9]*)$', '.dev0', version, 1)
+    dep_op = '~=' if match_count else '=='
+    for dep_pkg in PACKAGE_DEPENDENCY_MAP.get(PACKAGE_NAME, ()):
+        yield f'{dep_pkg}{dep_op}{dep_ver}'
 
 
-    return read_version(setup_dir, module)
+# Called from calculate_python_sdk_cwl_package_versions() in run-library.sh
+if __name__ == '__main__':
+    print(get_version())
diff --git a/tools/user-activity/pytest.ini b/tools/user-activity/pytest.ini
new file mode 120000 (symlink)
index 0000000..05a82db
--- /dev/null
@@ -0,0 +1 @@
+../../sdk/python/pytest.ini
\ No newline at end of file
index 4b7ec16b934881540e45081e77f9c67ba01519c5..a455a87fe7f280ed92279ca61e319e28f68043b0 100755 (executable)
@@ -3,18 +3,15 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-from __future__ import absolute_import
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
 import os
 import sys
 import re
 
 from setuptools import setup, find_packages
 
-SETUP_DIR = os.path.dirname(__file__) or '.'
-README = os.path.join(SETUP_DIR, 'README.rst')
-
 import arvados_version
 import arvados_version
-version = arvados_version.get_version(SETUP_DIR, "arvados_user_activity")
+version = arvados_version.get_version()
+README = os.path.join(arvados_version.SETUP_DIR, 'README.rst')
 
 setup(name='arvados-user-activity',
       version=version,
 
 setup(name='arvados-user-activity',
       version=version,
@@ -31,7 +28,7 @@ setup(name='arvados-user-activity',
           ('share/doc/arvados_user_activity', ['agpl-3.0.txt']),
       ],
       install_requires=[
           ('share/doc/arvados_user_activity', ['agpl-3.0.txt']),
       ],
       install_requires=[
-          'arvados-python-client >= 2.2.0.dev20201118185221',
+          *arvados_version.iter_dependencies(version),
       ],
       python_requires="~=3.8",
       zip_safe=True,
       ],
       python_requires="~=3.8",
       zip_safe=True,