15720: Merge branch 'master' into 15720-fed-user-list
authorTom Clegg <tclegg@veritasgenetics.com>
Mon, 25 Nov 2019 20:20:21 +0000 (15:20 -0500)
committerTom Clegg <tclegg@veritasgenetics.com>
Mon, 25 Nov 2019 20:20:21 +0000 (15:20 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

77 files changed:
apps/workbench/Gemfile.lock
apps/workbench/test/controllers/projects_controller_test.rb
build/package-build-dockerfiles/Makefile
build/package-build-dockerfiles/debian10/Dockerfile [new file with mode: 0644]
build/package-test-dockerfiles/Makefile
build/package-test-dockerfiles/debian10/Dockerfile [new file with mode: 0644]
build/package-testing/deb-common-test-packages.sh
build/package-testing/test-packages-debian10.sh [new symlink]
build/run-build-packages-one-target.sh
build/run-build-packages-sso.sh
build/run-build-packages.sh
build/run-build-test-packages-one-target.sh
build/run-library.sh
build/run-tests.sh
doc/Rakefile
doc/_config.yml
doc/_includes/_navbar_left.liquid
doc/admin/User account states.odg [new file with mode: 0644]
doc/admin/activation.html.textile.liquid [changed from file to symlink]
doc/admin/group-management.html.textile.liquid [new file with mode: 0644]
doc/admin/logging.html.textile.liquid [new file with mode: 0644]
doc/admin/migrating-providers.html.textile.liquid
doc/admin/reassign-ownership.html.textile.liquid [new file with mode: 0644]
doc/admin/troubleshooting.html.textile.liquid [changed from file to symlink]
doc/admin/user-management-cli.html.textile.liquid [new file with mode: 0644]
doc/admin/user-management.html.textile.liquid [new file with mode: 0644]
doc/api/methods.html.textile.liquid
doc/api/methods/container_requests.html.textile.liquid
doc/api/methods/containers.html.textile.liquid
doc/api/methods/user_agreements.html.textile.liquid [new file with mode: 0644]
doc/api/methods/users.html.textile.liquid
doc/css/images.css
doc/images/user-account-states.svg [new file with mode: 0644]
doc/install/cheat_sheet.html.textile.liquid [changed from file to symlink]
lib/config/config.default.yml
lib/config/export.go
lib/config/generated_config.go
lib/controller/federation/conn.go
lib/controller/federation/generated.go
lib/controller/federation/list.go
lib/controller/federation/list_test.go
lib/controller/federation/login_test.go
lib/controller/localdb/login.go
lib/controller/localdb/login_test.go
lib/controller/router/router_test.go
lib/controller/rpc/conn.go
lib/dispatchcloud/container/queue.go
lib/dispatchcloud/scheduler/sync.go
sdk/cwl/tests/federation/arvbox-make-federation.cwl
sdk/cwl/tests/federation/arvbox/fed-config.cwl
sdk/cwl/tests/federation/arvbox/start.cwl
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_deferred.go
sdk/go/arvados/specimen.go
sdk/python/arvados/commands/federation_migrate.py
sdk/python/tests/fed-migrate/CWLFile [new file with mode: 0644]
sdk/python/tests/fed-migrate/arvbox-make-federation.cwl
sdk/python/tests/fed-migrate/check.py
sdk/python/tests/fed-migrate/cwlex.cwl [new file with mode: 0644]
sdk/python/tests/fed-migrate/fed-migrate.cwl
sdk/python/tests/fed-migrate/fed-migrate.cwlex
sdk/python/tests/fed-migrate/set_login.py [deleted file]
services/api/Gemfile.lock
services/api/app/models/arvados_model.rb
services/api/app/models/user.rb
services/api/lib/record_filters.rb
services/api/test/functional/arvados/v1/container_requests_controller_test.rb
services/api/test/unit/permission_test.rb
services/keep-balance/collection.go
services/keep-balance/collection_test.go
services/login-sync/Gemfile.lock
tools/arvbox/lib/arvbox/docker/cluster-config.sh
tools/arvbox/lib/arvbox/docker/go-setup.sh
tools/arvbox/lib/arvbox/docker/service/doc/run-service

index b4b6100f4a533a2e5e71bddbeafc64f9b65392d7..f16f298bac7f610ea4ce546bac5f405be7107fae 100644 (file)
@@ -3,7 +3,7 @@ GIT
   revision: dd9f2403f43bcb93da5908ddde57d8c0491bb4c2
   glob: sdk/ruby/arvados.gemspec
   specs:
-    arvados (1.4.1.20191019025325)
+    arvados (1.4.2.20191019025325)
       activesupport (>= 3)
       andand (~> 1.3, >= 1.3.3)
       arvados-google-api-client (>= 0.7, < 0.8.9)
@@ -375,4 +375,4 @@ DEPENDENCIES
   uglifier (~> 2.0)
 
 BUNDLED WITH
-   1.17.3
+   2.0.2
index dd828952be2fb828e8f1e1b11b1cfc1616515683..27d7dedc91d070bd4da02a1f80ab87c4c9a9563e 100644 (file)
@@ -151,7 +151,7 @@ class ProjectsControllerTest < ActionController::TestCase
   end
 
   ['', ' asc', ' desc'].each do |direction|
-    test "projects#show tab partial orders correctly by #{direction}" do
+    test "projects#show tab partial orders correctly by created_at#{direction}" do
       _test_tab_content_order direction
     end
   end
index 6972415152a8f9456906b9c90bf7ddceb70691ae..db53ab096dd8c4c6752b2aa42a2fc0ebc7eef0ad 100644 (file)
@@ -2,7 +2,7 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-all: centos7/generated debian9/generated ubuntu1604/generated ubuntu1804/generated
+all: centos7/generated debian9/generated debian10/generated ubuntu1604/generated ubuntu1804/generated
 
 centos7/generated: common-generated-all
        test -d centos7/generated || mkdir centos7/generated
@@ -12,6 +12,11 @@ debian9/generated: common-generated-all
        test -d debian9/generated || mkdir debian9/generated
        cp -rlt debian9/generated common-generated/*
 
+debian10/generated: common-generated-all
+       test -d debian10/generated || mkdir debian10/generated
+       cp -rlt debian10/generated common-generated/*
+
+
 ubuntu1604/generated: common-generated-all
        test -d ubuntu1604/generated || mkdir ubuntu1604/generated
        cp -rlt ubuntu1604/generated common-generated/*
diff --git a/build/package-build-dockerfiles/debian10/Dockerfile b/build/package-build-dockerfiles/debian10/Dockerfile
new file mode 100644 (file)
index 0000000..bab447f
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+## dont use debian:10 here since the word 'buster' is used for rvm precompiled binaries
+FROM debian:buster
+MAINTAINER Ward Vandewege <wvandewege@veritasgenetics.com>
+
+ENV DEBIAN_FRONTEND noninteractive
+
+# Install dependencies.
+RUN /usr/bin/apt-get update && /usr/bin/apt-get install -q -y python2.7-dev python3 python-setuptools python3-setuptools python3-pip libcurl4-gnutls-dev curl git procps libattr1-dev libfuse-dev libgnutls28-dev libpq-dev python-pip unzip python3-venv python3-dev
+
+# Install virtualenv
+RUN /usr/bin/pip install virtualenv
+
+# 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.5 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.5 && \
+    /usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.10.2
+
+# Install golang binary
+ADD generated/go1.12.7.linux-amd64.tar.gz /usr/local/
+RUN ln -s /usr/local/go/bin/go /usr/local/bin/
+
+# Install nodejs and npm
+ADD generated/node-v6.11.2-linux-x64.tar.xz /usr/local/
+RUN ln -s /usr/local/node-v6.11.2-linux-x64/bin/* /usr/local/bin/
+
+RUN git clone --depth 1 git://git.curoverse.com/arvados.git /tmp/arvados && cd /tmp/arvados/services/api && /usr/local/rvm/bin/rvm-exec default bundle && cd /tmp/arvados/apps/workbench && /usr/local/rvm/bin/rvm-exec default bundle
+
+ENV WORKSPACE /arvados
+CMD ["/usr/local/rvm/bin/rvm-exec", "default", "bash", "/jenkins/run-build-packages.sh", "--target", "debian10"]
index c7b32968ff044f52b2616b74815d53c8b2d17fa2..1066750fe551c583edc1059a0fcb750f98799e8b 100644 (file)
@@ -2,7 +2,7 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
-all: centos7/generated debian9/generated ubuntu1604/generated ubuntu1804/generated
+all: centos7/generated debian9/generated  debian10/generated ubuntu1604/generated ubuntu1804/generated
 
 centos7/generated: common-generated-all
        test -d centos7/generated || mkdir centos7/generated
@@ -12,6 +12,10 @@ debian9/generated: common-generated-all
        test -d debian9/generated || mkdir debian9/generated
        cp -rlt debian9/generated common-generated/*
 
+debian10/generated: common-generated-all
+       test -d debian10/generated || mkdir debian10/generated
+       cp -rlt debian10/generated common-generated/*
+
 ubuntu1604/generated: common-generated-all
        test -d ubuntu1604/generated || mkdir ubuntu1604/generated
        cp -rlt ubuntu1604/generated common-generated/*
diff --git a/build/package-test-dockerfiles/debian10/Dockerfile b/build/package-test-dockerfiles/debian10/Dockerfile
new file mode 100644 (file)
index 0000000..3aa6fdc
--- /dev/null
@@ -0,0 +1,26 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+FROM debian:buster
+MAINTAINER Ward Vandewege <wvandewege@veritasgenetics.com>
+
+ENV DEBIAN_FRONTEND noninteractive
+
+# 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.5 && \
+    /usr/local/rvm/bin/rvm alias create default ruby-2.5
+
+# udev daemon can't start in a container, so don't try.
+RUN mkdir -p /etc/udev/disabled
+
+RUN echo "deb file:///arvados/packages/debian10/ /" >>/etc/apt/sources.list
index 77017ba9702cb870ed83cb982fe3b135a779e6e6..32fb2009e15fa063de0b85622b5b98b0ef74cd1a 100755 (executable)
@@ -23,7 +23,7 @@ export ARV_PACKAGES_DIR="/arvados/packages/$target"
 
 dpkg-query --show > "$ARV_PACKAGES_DIR/$1.before"
 
-apt-get $DASHQQ_UNLESS_DEBUG update
+apt-get $DASHQQ_UNLESS_DEBUG --allow-insecure-repositories update
 
 apt-get $DASHQQ_UNLESS_DEBUG -y --allow-unauthenticated install "$1" >"$STDOUT_IF_DEBUG" 2>"$STDERR_IF_DEBUG"
 
diff --git a/build/package-testing/test-packages-debian10.sh b/build/package-testing/test-packages-debian10.sh
new file mode 120000 (symlink)
index 0000000..54ce94c
--- /dev/null
@@ -0,0 +1 @@
+deb-common-test-packages.sh
\ No newline at end of file
index 378c9bbfa39dc9dfedc134636ce5806bf6d6cf65..7f3ca3242b453976ffa5f77623f0b0a8c8bad98a 100755 (executable)
@@ -10,7 +10,7 @@ Syntax:
         WORKSPACE=/path/to/arvados $(basename $0) [options]
 
 --target <target>
-    Distribution to build packages for (default: debian9)
+    Distribution to build packages for (default: debian10)
 --command
     Build command to execute (default: use built-in Docker image command)
 --test-packages
@@ -21,6 +21,9 @@ Syntax:
     Build only a specific package
 --only-test <package>
     Test only a specific package
+--force-build
+    Build even if the package exists upstream or if it has already been
+    built locally
 --force-test
     Test even if there is no new untested package
 --build-version <string>
@@ -51,15 +54,16 @@ if ! [[ -d "$WORKSPACE" ]]; then
 fi
 
 PARSEDOPTS=$(getopt --name "$0" --longoptions \
-    help,debug,test-packages,target:,command:,only-test:,force-test,only-build:,build-version: \
+    help,debug,test-packages,target:,command:,only-test:,force-test,only-build:,force-build,build-version: \
     -- "" "$@")
 if [ $? -ne 0 ]; then
     exit 1
 fi
 
-TARGET=debian9
+TARGET=debian10
+FORCE_BUILD=0
 COMMAND=
-DEBUG=
+DEBUG=${ARVADOS_DEBUG:-0}
 
 eval set -- "$PARSEDOPTS"
 while [ $# -gt 0 ]; do
@@ -80,6 +84,9 @@ while [ $# -gt 0 ]; do
         --force-test)
             FORCE_TEST=true
             ;;
+        --force-build)
+            FORCE_BUILD=1
+            ;;
         --only-build)
             ONLY_BUILD="$2"; shift
             ;;
@@ -269,6 +276,7 @@ else
         --env ARVADOS_BUILDING_ITERATION="$ARVADOS_BUILDING_ITERATION" \
         --env ARVADOS_DEBUG=$ARVADOS_DEBUG \
         --env "ONLY_BUILD=$ONLY_BUILD" \
+        --env "FORCE_BUILD=$FORCE_BUILD" \
         "$IMAGE" $COMMAND
     then
         echo
index e7a3aacda3ca5aaea64dac8c1fd52d7b9f766b24..d8d9b984a0c8f0c6db009866e162bcc3aa1142c8 100755 (executable)
@@ -17,7 +17,7 @@ Options:
 --debug
     Output debug information (default: false)
 --target
-    Distribution to build packages for (default: debian9)
+    Distribution to build packages for (default: debian10)
 
 WORKSPACE=path         Path to the Arvados SSO source tree to build packages from
 
@@ -25,7 +25,7 @@ EOF
 
 EXITCODE=0
 DEBUG=${ARVADOS_DEBUG:-0}
-TARGET=debian9
+TARGET=debian10
 
 PARSEDOPTS=$(getopt --name "$0" --longoptions \
     help,build-bundle-packages,debug,target: \
index a07b308179990a7ac96b1185ab1f42e7d2e45350..d6c8f5ac64ca13431560c56fe5f3d95962d90fb9 100755 (executable)
@@ -19,9 +19,12 @@ Options:
 --debug
     Output debug information (default: false)
 --target <target>
-    Distribution to build packages for (default: debian9)
+    Distribution to build packages for (default: debian10)
 --only-build <package>
     Build only a specific package (or $ONLY_BUILD from environment)
+--force-build
+    Build even if the package exists upstream or if it has already been
+    built locally
 --command
     Build command to execute (defaults to the run command defined in the
     Docker image)
@@ -41,12 +44,13 @@ VENDOR="Veritas Genetics, Inc."
 # End of user configuration
 
 DEBUG=${ARVADOS_DEBUG:-0}
+FORCE_BUILD=${FORCE_BUILD:-0}
 EXITCODE=0
-TARGET=debian9
+TARGET=debian10
 COMMAND=
 
 PARSEDOPTS=$(getopt --name "$0" --longoptions \
-    help,build-bundle-packages,debug,target:,only-build: \
+    help,build-bundle-packages,debug,target:,only-build:,force-build \
     -- "" "$@")
 if [ $? -ne 0 ]; then
     exit 1
@@ -66,6 +70,9 @@ while [ $# -gt 0 ]; do
         --only-build)
             ONLY_BUILD="$2"; shift
             ;;
+        --force-build)
+            FORCE_BUILD=1
+            ;;
         --debug)
             DEBUG=1
             ;;
@@ -157,14 +164,6 @@ if [[ "$?" != 0 ]]; then
   exit 1
 fi
 
-PYTHON2_FPM_INSTALLER=(--python-easyinstall "$(find_python_program easy_install-$PYTHON2_VERSION easy_install)")
-install3=$(find_python_program easy_install-$PYTHON3_VERSION easy_install3 pip-$PYTHON3_VERSION pip3)
-if [[ $install3 =~ easy_ ]]; then
-    PYTHON3_FPM_INSTALLER=(--python-easyinstall "$install3")
-else
-    PYTHON3_FPM_INSTALLER=(--python-pip "$install3")
-fi
-
 RUN_BUILD_PACKAGES_PATH="`dirname \"$0\"`"
 RUN_BUILD_PACKAGES_PATH="`( cd \"$RUN_BUILD_PACKAGES_PATH\" && pwd )`"  # absolutized and normalized
 if [ -z "$RUN_BUILD_PACKAGES_PATH" ] ; then
index d75e2785eca730d748f6e59446f6509a233fa1dd..8539ec4f0aad262b7845f225396769b710b7827c 100755 (executable)
@@ -10,7 +10,7 @@ Syntax:
         WORKSPACE=/path/to/arvados $(basename $0) [options]
 
 --target <target>
-    Distribution to build packages for (default: debian9)
+    Distribution to build packages for (default: debian10)
 --upload
     If the build and test steps are successful, upload the packages
     to a remote apt repository (default: false)
@@ -50,7 +50,7 @@ if [ $? -ne 0 ]; then
     exit 1
 fi
 
-TARGET=debian9
+TARGET=debian10
 UPLOAD=0
 RC=0
 DEBUG=
index a4cebbc8a71d07de545a8382dd001a736802a548..f173504c588b58c8e82ce93ade95ab4a8ce9acf3 100755 (executable)
@@ -316,7 +316,9 @@ test_package_presence() {
     # sure it gets picked up by the test and/or upload steps.
     # Get the list of packages from the repos
 
-    if [[ "$FORMAT" == "deb" ]]; then
+    if [[ "$FORCE_BUILD" == "1" ]]; then
+      echo "Package $full_pkgname build forced with --force-build, building"
+    elif [[ "$FORMAT" == "deb" ]]; then
       declare -A dd
       dd[debian9]=stretch
       dd[debian10]=buster
index 38005070c70454dc3b07045f2d0d9d7feaa35458..b32fe6a43f6e5b0b227dc208cb05ba3051cd5a0c 100755 (executable)
@@ -520,6 +520,10 @@ setup_ruby_environment() {
             || fatal 'rvm gemset setup'
 
         rvm env
+        (bundle version | grep -q 2.0.2) || gem install bundler -v 2.0.2
+        bundle="$(which bundle)"
+        echo "$bundle"
+        "$bundle" version | grep 2.0.2 || 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
@@ -545,9 +549,14 @@ setup_ruby_environment() {
         echo "Will install dependencies to $(gem env gemdir)"
         echo "Will install arvados gems to $tmpdir_gem_home"
         echo "Gem search path is GEM_PATH=$GEM_PATH"
+        bundle="$(gem env gempath | cut -f1 -d:)/bin/bundle"
+        (
+            export HOME=$GEMHOME
+            ("$bundle" version | grep -q 2.0.2) \
+                || gem install --user bundler -v 2.0.2
+            "$bundle" version | grep 2.0.2
+        ) || fatal 'install bundler'
     fi
-    bundle config || gem install bundler \
-        || fatal 'install bundler'
 }
 
 with_test_gemset() {
@@ -665,11 +674,6 @@ Warning: python3 could not be found. Python 3 tests will be skipped.
 
 EOF
     fi
-
-    if ! which bundler >/dev/null
-    then
-        gem install --user-install bundler || fatal 'Could not install bundler'
-    fi
 }
 
 retry() {
@@ -881,11 +885,11 @@ bundle_install_trylocal() {
     (
         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.)"
-            bundle install --no-deployment
+            "$bundle" install --no-deployment
         fi
-        bundle package --all
+        "$bundle" package --all
     )
 }
 
@@ -933,7 +937,8 @@ install_services/login-sync() {
 install_services/api() {
     stop_services
     cd "$WORKSPACE/services/api" \
-        && RAILS_ENV=test bundle_install_trylocal
+        && RAILS_ENV=test bundle_install_trylocal \
+            || return 1
 
     rm -f config/environments/test.rb
     cp config/environments/test.rb.example config/environments/test.rb
@@ -969,11 +974,11 @@ install_services/api() {
 
     (cd "$WORKSPACE/services/api"
      export RAILS_ENV=test
-     if bundle exec rails db:environment:set ; then
-        bundle exec rake db:drop
+     if "$bundle" exec rails db:environment:set ; then
+        "$bundle" exec rake db:drop
      fi
-     bundle exec rake db:setup \
-        && bundle exec rake db:fixtures:load
+     "$bundle" exec rake db:setup \
+        && "$bundle" exec rake db:fixtures:load
     )
 }
 
@@ -999,7 +1004,7 @@ install_apps/workbench() {
     cd "$WORKSPACE/apps/workbench" \
         && mkdir -p tmp/cache \
         && RAILS_ENV=test bundle_install_trylocal \
-        && RAILS_ENV=test RAILS_GROUPS=assets bundle exec rake npm:install
+        && RAILS_ENV=test RAILS_GROUPS=assets "$bundle" exec rake npm:install
 }
 
 test_doc() {
@@ -1009,7 +1014,7 @@ test_doc() {
         ARVADOS_API_HOST=qr1hi.arvadosapi.com
         # Make sure python-epydoc is installed or the next line won't
         # do much good!
-        PYTHONPATH=$WORKSPACE/sdk/python/ bundle exec rake linkchecker baseurl=file://$WORKSPACE/doc/.site/ arvados_workbench_host=https://workbench.$ARVADOS_API_HOST arvados_api_host=$ARVADOS_API_HOST
+        PYTHONPATH=$WORKSPACE/sdk/python/ "$bundle" exec rake linkchecker baseurl=file://$WORKSPACE/doc/.site/ arvados_workbench_host=https://workbench.$ARVADOS_API_HOST arvados_api_host=$ARVADOS_API_HOST
     )
 }
 
@@ -1022,12 +1027,12 @@ test_gofmt() {
 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" \
-        && bundle exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
+        && "$bundle" exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
 }
 
 test_sdk/R() {
@@ -1040,7 +1045,7 @@ test_sdk/R() {
 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() {
@@ -1049,7 +1054,7 @@ test_sdk/java-v2() {
 
 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/nodemanager_integration() {
@@ -1060,31 +1065,31 @@ test_services/nodemanager_integration() {
 test_apps/workbench_units() {
     local TASK="test:units"
     cd "$WORKSPACE/apps/workbench" \
-        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} bundle exec rake ${TASK} TESTOPTS=\'-v -d\' ${testargs[apps/workbench]} ${testargs[apps/workbench_units]}
+        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} "$bundle" exec rake ${TASK} TESTOPTS=\'-v -d\' ${testargs[apps/workbench]} ${testargs[apps/workbench_units]}
 }
 
 test_apps/workbench_functionals() {
     local TASK="test:functionals"
     cd "$WORKSPACE/apps/workbench" \
-        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} bundle exec rake ${TASK} TESTOPTS=\'-v -d\' ${testargs[apps/workbench]} ${testargs[apps/workbench_functionals]}
+        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} "$bundle" exec rake ${TASK} TESTOPTS=\'-v -d\' ${testargs[apps/workbench]} ${testargs[apps/workbench_functionals]}
 }
 
 test_apps/workbench_integration() {
     local TASK="test:integration"
     cd "$WORKSPACE/apps/workbench" \
-        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} bundle exec rake ${TASK} TESTOPTS=\'-v -d\' ${testargs[apps/workbench]} ${testargs[apps/workbench_integration]} 
+        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} "$bundle" exec rake ${TASK} TESTOPTS=\'-v -d\' ${testargs[apps/workbench]} ${testargs[apps/workbench_integration]} 
 }
 
 test_apps/workbench_benchmark() {
     local TASK="test:benchmark"
     cd "$WORKSPACE/apps/workbench" \
-        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} bundle exec rake ${TASK} ${testargs[apps/workbench_benchmark]}
+        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} "$bundle" exec rake ${TASK} ${testargs[apps/workbench_benchmark]}
 }
 
 test_apps/workbench_profile() {
     local TASK="test:profile"
     cd "$WORKSPACE/apps/workbench" \
-        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} bundle exec rake ${TASK} ${testargs[apps/workbench_profile]}
+        && eval env RAILS_ENV=test ${short:+RAILS_TEST_SHORT=1} "$bundle" exec rake ${TASK} ${testargs[apps/workbench_profile]}
 }
 
 install_deps() {
index f1aa3bfce87495ced721ef499a1417afcb6c4eca..63dc16d25d6d84747a649d0685151dec7bdbfcbf 100644 (file)
@@ -3,6 +3,12 @@
 #
 # SPDX-License-Identifier: CC-BY-SA-3.0
 
+# As a convenience to the documentation writer, you can touch a file
+# called 'no-sdk' in the 'doc' directory and it will suppress
+# generating the documentation for the SDKs, which (the R docs
+# especially) take a fair bit of time and slow down the edit-preview
+# cycle.
+
 require "rubygems"
 require "colorize"
 
@@ -16,6 +22,9 @@ task :generate => [ :realclean, 'sdk/python/arvados/index.html', 'sdk/R/arvados/
 end
 
 file "sdk/python/arvados/index.html" do |t|
+  if File.exists? "no-sdk"
+    next
+  end
   `which epydoc`
   if $? == 0
     STDERR.puts `epydoc --html --parse-only -o sdk/python/arvados ../sdk/python/arvados/ 2>&1`
@@ -26,6 +35,9 @@ file "sdk/python/arvados/index.html" do |t|
 end
 
 file "sdk/R/arvados/index.html" do |t|
+  if File.exists? "no-sdk"
+    next
+  end
   `which R`
   if $? == 0
     tgt = Dir.pwd
@@ -88,6 +100,9 @@ EOF
 end
 
 file "sdk/java-v2/javadoc/index.html" do |t|
+  if File.exists? "no-sdk"
+    next
+  end
   `which java`
   if $? == 0
     `which gradle`
index 404d2f6c63a90606ea9b8099cd329da2e764ecd1..bcb66fdb39ee2dd29ca29edb22c2974533f0b47a 100644 (file)
@@ -114,6 +114,7 @@ navbar:
       - api/methods/authorized_keys.html.textile.liquid
       - api/methods/groups.html.textile.liquid
       - api/methods/users.html.textile.liquid
+      - api/methods/user_agreements.html.textile.liquid
     - System resources:
       - api/methods/keep_services.html.textile.liquid
       - api/methods/links.html.textile.liquid
@@ -155,14 +156,17 @@ navbar:
       - admin/upgrading.html.textile.liquid
       - admin/config-migration.html.textile.liquid
     - Users and Groups:
-      - install/cheat_sheet.html.textile.liquid
-      - admin/activation.html.textile.liquid
+      - admin/user-management.html.textile.liquid
+      - admin/reassign-ownership.html.textile.liquid
+      - admin/user-management-cli.html.textile.liquid
+      - admin/group-management.html.textile.liquid
       - admin/merge-remote-account.html.textile.liquid
       - admin/migrating-providers.html.textile.liquid
       - user/topics/arvados-sync-groups.html.textile.liquid
     - Monitoring:
-      - admin/health-checks.html.textile.liquid
+      - admin/logging.html.textile.liquid
       - admin/metrics.html.textile.liquid
+      - admin/health-checks.html.textile.liquid
       - admin/management-token.html.textile.liquid
     - Cloud:
       - admin/storage-classes.html.textile.liquid
@@ -175,7 +179,6 @@ navbar:
       - admin/controlling-container-reuse.html.textile.liquid
       - admin/logs-table-management.html.textile.liquid
     - Other:
-      - admin/troubleshooting.html.textile.liquid
       - install/migrate-docker19.html.textile.liquid
       - admin/upgrade-crunch2.html.textile.liquid
       - admin/workbench2-vocabulary.html.textile.liquid
index cba6c46e4de20fff526ebc257f0ce50d8a7aeb6f..d3ac2932d3503e5216237c117d64625943b4dc63 100644 (file)
@@ -11,9 +11,9 @@ SPDX-License-Identifier: CC-BY-SA-3.0
       {% for entry in section %}
       <li><span class="nav-header">{{ entry[0] }}</span>
        <ol class="nav nav-list">
-          {% for item in entry[1] %}        
+          {% for item in entry[1] %}
           {% assign p = site.pages[item] %}
-          <li {% if p.url == page.url %} class="active activesubnav" {% elsif p.title == page.subnavsection %} class="activesubnav" {% endif %}>
+          <li {% if p.url == page.url or p.title == page.title %} class="active activesubnav" {% elsif p.title == page.subnavsection %} class="activesubnav" {% endif %}>
             <a href="{{ site.baseurl }}{{ p.url }}">{{ p.title }}</a></li>
           {% endfor %}
         </ol>
diff --git a/doc/admin/User account states.odg b/doc/admin/User account states.odg
new file mode 100644 (file)
index 0000000..866e07a
Binary files /dev/null and b/doc/admin/User account states.odg differ
deleted file mode 100644 (file)
index 4a08e509c1520c70900be53ec53907456e4b0619..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,229 +0,0 @@
----
-layout: default
-navsection: admin
-title: User activation
-...
-
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-This page describes how new users are created and activated.
-
-"Browser login and management of API tokens is described here.":{{site.baseurl}}/api/tokens.html
-
-h3. Authentication
-
-After completing the authentication process, a callback is made from the SSO server to the API server, providing a user record and @identity_url@ (despite the name, this is actually an Arvados user uuid).
-
-The API server searches for a user record with the @identity_url@ supplied by the SSO.  If found, that user account will be used, unless the account has @redirect_to_user_uuid@ set, in which case it will use the user in @redirect_to_user_uuid@ instead (this is used for the "link account":{{site.baseurl}}/user/topics/link-accounts.html feature).
-
-Next, it searches by email address for a "pre-activated account.":#pre-activated
-
-If no existing user record is found, a new user object will be created.
-
-A federated user follows a slightly different flow, whereby a special token is presented and the API server verifies user's identity with the home cluster, however it also results in a user object (representing the remote user) being created.
-
-h3. User setup
-
-If @auto_setup_new_users@ is true, as part of creating the new user object, the user is immediately set up with:
-
-* @can_login@ @permission@ link going (email address &rarr; user uuid) which records @identity_url_prefix@
-* Membership in the "All users" group (can read all users, all users can see new user)
-* A new git repo and @can_manage@ permission if @auto_setup_new_users_with_repository@ is true
-* @can_login@ permission to a shell node if @auto_setup_new_users_with_vm_uuid@ is set to the uuid of a vm
-
-Otherwise, an admin must explicitly invoke "setup" on the user via workbench or the API.
-
-h3. User activation
-
-A newly created user is inactive (@is_active@ is false) by default unless @new_users_are_active@.
-
-An inactive user cannot create or update any object, but can read Arvados objects that the user account has permission to read.  This implies that if @auto_setup_new_users@ is true, an "inactive" user who has been set up may still be able to do things, such as read things shared with "All users", clone and push to the git repository, or login to a VM.
-
-{% comment %}
-Maybe these services should check is_active.
-
-I believe that when this was originally designed, being able to access git and VM required an ssh key, and an inactive user could not register an ssh key because that required creating a record.  However, it is now possible to authenticate to shell VMs and http+git with just an API token.
-{% endcomment %}
-
-At this point, there are two ways a user can be activated.
-
-# An admin can set the @is_active@ field directly.  This runs @setup_on_activate@ which sets up oid_login_perm and group membership, but does not set repo or vm (even if if @auto_setup_new_users_with_repository@ and/or @auto_setup_new_users_with_vm_uuid@ are set).
-# Self-activation using the @activate@ method of the users controller.
-
-h3. User agreements
-
-The @activate@ method of the users controller checks if the user @is_invited@ and whether the user has "signed" all the user agreements.
-
-@is_invited@ is true if any of these are true:
-* @is_active@ is true
-* @new_users_are_active@ is true
-* the user account has a permission link to read the system "all users" group.
-
-User agreements are accessed by getting a listing on the @user_agreements@ endpoint.  This returns a list of collection uuids.  This is executed as a system user, so it bypasses normal read permission checks.
-
-The available user agreements are represented in the Links table as
-
-<pre>
-{
-  "link_class": "signature",
-  "name": "require",
-  "tail_uuid": "*system user uuid*",
-  "head_uuid: "*collection uuid*"
-}
-</pre>
-
-The collection contains the user agreement text file.
-
-On workbench, it checks @is_invited@.  If true, it displays the clickthrough agreements which the user can "sign".  If @is_invited@ is false, the user ends up at the "inactive user" page.
-
-The @user_agreements/sign@ endpoint creates a Link object:
-
-<pre>
-{
-  "link_class": "signature"
-  "name": "click",
-  "tail_uuid": "*user uuid*",
-  "head_uuid: "*collection uuid*"
-}
-</pre>
-
-This is executed as a system user, so it bypasses the restriction that inactive users cannot create objects.
-
-The @user_agreements/signatures@ endpoint returns the list of Link objects that represent signatures by the current user (created by @sign@).
-
-h3. User profile
-
-The user profile is checked by workbench after checking if user agreements need to be signed.  The requirement to fill out the user profile is not enforced by the API server.
-
-h3(#pre-activated). Pre-activate user by email address
-
-You may create a user account for a user that has not yet logged in, and identify the user by email address.
-
-1. As an admin, create a user object:
-
-<pre>
-{
-  "email": "foo@example.com",
-  "username": "barney",
-  "is_active": true
-}
-</pre>
-
-2. Create a link object, where @tail_uuid@ is the user's email address, @head_uuid@ is the user object created in the previous step, and @xxxxx@ is the value of @uuid_prefix@ of the SSO server.
-
-<pre>
-{
-  "link_class": "permission",
-  "name": "can_login",
-  "tail_uuid": "email address",
-  "head_uuid: "user uuid",
-  "properties": {
-    "identity_url_prefix": "xxxxx-tpzed-"
-  }
-}
-</pre>
-
-3. When the user logs in the first time, the email address will be recognized and the user will be associated with the linked user object.
-
-h3. Pre-activate federated user
-
-1. As admin, create a user object with the @uuid@ of the federated user (this is the user's uuid on their home cluster):
-
-<pre>
-{
-  "uuid": "home1-tpzed-000000000000000",
-  "email": "foo@example.com",
-  "username": "barney",
-  "is_active": true
-}
-</pre>
-
-2. When the user logs in, they will be associated with the existing user object.
-
-h3. Auto-activate federated users from trusted clusters
-
-In the API server config, configure @auto_activate_users_from@ with a list of one or more five-character cluster ids.  A federated user from one of the listed clusters which @is_active@ on the home cluster will be automatically set up and activated on this cluster.
-
-h3(#deactivating_users). Deactivating users
-
-Setting @is_active@ is not sufficient to lock out a user.  The user can call @activate@ to become active again.  Instead, use @unsetup@:
-
-* Delete oid_login_perms
-* Delete git repository permission links
-* Delete VM login permission links
-* Remove from "All users" group
-* Delete any "signatures"
-* Clear preferences / profile
-* Mark as inactive
-
-{% comment %}
-Does not revoke @is_admin@, so you can't unsetup an admin unless you turn admin off first.
-
-"inactive" does not prevent user from reading things they previously had access to.
-
-Does not revoke API tokens.
-{% endcomment %}
-
-h3. Activation flows
-
-h4. Private instance
-
-Policy: users must be manually approved.
-
-<pre>
-auto_setup_new_users: false
-new_users_are_active: false
-</pre>
-
-# User is created.  Not set up.  @is_active@ is false.
-# Workbench checks @is_invited@ and finds it is false.  User gets "inactive user" page.
-# Admin goes to user page and clicks either "setup user" or manually @is_active@ to true.
-# Clicking "setup user" sets up the user.  This includes adding the user to "All users" which qualifies the user as @is_invited@.
-# On refreshing workbench, the user is still inactive, but is able to self-activate after signing clickthrough agreements (if any).
-# Alternately, directly setting @is_active@ to true also sets up the user, but workbench won't display clickthrough agreements (because the user is already active).
-
-h4. Federated instance
-
-Policy: users from other clusters in the federation are activated, users from outside the federation must be manually approved
-
-<pre>
-auto_setup_new_users: false
-new_users_are_active: false
-auto_activate_users_from: [home1]
-</pre>
-
-# Federated user arrives claiming to be from cluster 'home1'
-# API server authenticates user as being from cluster 'home1'
-# Because 'home1' is in @auto_activate_users_from@ the user is set up and activated.
-# User can immediately start using workbench.
-
-h4. Open instance
-
-Policy: anybody who shows up and signs the agreements is activated.
-
-<pre>
-auto_setup_new_users: true
-new_users_are_active: false
-</pre>
-
-# User is created and auto-setup.  At this point, @is_active@ is false, but user has been added to "All users" group.
-# Workbench checks @is_invited@ and finds it is true, because the user is a member of "All users" group.
-# Workbench presents user with list of user agreements, user reads and clicks "sign" for each one.
-# Workbench tries to activate user.
-# User is activated.
-
-h4. Developer instance
-
-Policy: avoid wasting developer's time during development/testing
-
-<pre>
-auto_setup_new_users: true
-new_users_are_active: true
-</pre>
-
-# User is created, immediately auto-setup, and auto-activated.
-# User can immediately start using workbench.
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..5e599a6b53f44e85db7164d50d53d3a61c1ece01
--- /dev/null
@@ -0,0 +1 @@
+user-management.html.textile.liquid
\ No newline at end of file
diff --git a/doc/admin/group-management.html.textile.liquid b/doc/admin/group-management.html.textile.liquid
new file mode 100644 (file)
index 0000000..127b914
--- /dev/null
@@ -0,0 +1,104 @@
+---
+layout: default
+navsection: admin
+title: Group management
+...
+
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+This page describes how to manage groups at the command line.  You should be familiar with the "permission system":{{site.baseurl}}/api/permission-model.html .
+
+h2. Create a group
+
+User groups are entries in the "groups" table with @"group_class": "role"@.
+
+<pre>
+arv group create --group '{"name": "My new group", "group_class": "role"}'
+</pre>
+
+h2. Add a user to a group
+
+There are two separate permissions associated with group membership.  The first link grants the user @can_manage@ permission to manage things that the group can manage.  The second link grants permission for other users of the group to see that this user is part of the group.
+
+<pre>
+arv link create --link '{
+  "link_class": "permission",
+  "name": "can_manage",
+  "tail_uuid": "the_user_uuid",
+  "head_uuid": "the_group_uuid"}'
+
+arv link create --link '{
+  "link_class": "permission",
+  "name": "can_read",
+  "tail_uuid": "the_group_uuid",
+  "head_uuid": "the_user_uuid"}'
+</pre>
+
+A user can also be given read-only access to a group.  In that case, the first link should be created with @can_read@ instead of @can_manage@.
+
+h2. List groups
+
+<pre>
+arv group list --filters '[["group_class", "=", "role"]]'
+</pre>
+
+h2. List members of a group
+
+Use the command "jq":https://stedolan.github.io/jq/ to extract the tail_uuid of each permission link which has the user uuid.
+
+<pre>
+arv link list --filters '[["link_class", "=", "permission"],
+  ["head_uuid", "=", "the_group_uuid"]]' | jq .items[].tail_uuid
+</pre>
+
+h2. Share a project with a group
+
+This will give all members of the group @can_manage@ access.
+
+<pre>
+arv link create --link '{
+  "link_class": "permission",
+  "name": "can_manage",
+  "tail_uuid": "the_group_uuid",
+  "head_uuid": "the_project_uuid"}'
+</pre>
+
+A project can also be shared read-only.  In that case, the first link should be created with @can_read@ instead of @can_manage@.
+
+h2. List things shared with the group
+
+Use the command "jq":https://stedolan.github.io/jq/ to extract the head_uuid of each permission link which has the object uuid.
+
+<pre>
+arv link list --filters '[["link_class", "=", "permission"],
+  ["tail_uuid", "=", "the_group_uuid"]]' | jq .items[].head_uuid
+</pre>
+
+h2. Stop sharing a project with a group
+
+This will remove access for members of the group.
+
+The first step is to find the permission link objects.  The second step is to delete them.
+
+<pre>
+arv --format=uuid link list --filters '[["link_class", "=", "permission"],
+  ["tail_uuid", "=", "the_group_uuid"], ["head_uuid", "=", "the_project_uuid"]]'
+
+arv link delete --uuid each_link_uuid
+</pre>
+
+h2. Remove user from a group
+
+The first step is to find the permission link objects.  The second step is to delete them.
+
+<pre>
+arv --format=uuid link list --filters '[["link_class", "=", "permission"],
+  ["tail_uuid", "in", ["the_user_uuid", "the_group_uuid"]],
+  ["head_uuid", "in", ["the_user_uuid", "the_group_uuid"]]'
+
+arv link delete --uuid each_link_uuid
+</pre>
diff --git a/doc/admin/logging.html.textile.liquid b/doc/admin/logging.html.textile.liquid
new file mode 100644 (file)
index 0000000..45dc11d
--- /dev/null
@@ -0,0 +1,78 @@
+---
+layout: default
+navsection: admin
+title: Logging
+...
+
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+Most Arvados services write JSON-format structured logs to stderr, which can be parsed by any operational tools that support JSON.
+
+h2. Request ids
+
+Using a distributed system with several services working together sometimes makes it difficult to find the root cause of errors, as one single client request usually means several different requests to more than one service.
+
+To deal with this difficulty, Arvados creates a request ID that gets carried over different services as the requests take place. This ID has a specific format and it's comprised of the prefix "@req-@" followed by 20 random alphanumeric characters:
+
+<pre>req-frdyrcgdh4rau1ajiq5q</pre>
+
+This ID gets propagated via an HTTP @X-Request-Id@ header, and gets logged on every service.
+
+h3. API Server error reporting and logging
+
+In addition to providing the request ID on every HTTP response, the API Server adds it to every error message so that all clients show enough information to the user to be able to track a particular issue. As an example, let's suppose that we get the following error when trying to create a collection using the CLI tools:
+
+<pre>
+$ arv collection create --collection '{}'
+Error: #<RuntimeError: Whoops, something bad happened> (req-ku5ct9ehw0y71f1c5p79)
+</pre>
+
+The API Server logs every request in JSON format on the @production.log@ (usually under @/var/www/arvados-api/current/log/@ when installing from packages) file, so we can retrieve more information about this by using @grep@ and @jq@ tools:
+
+<pre>
+# grep req-ku5ct9ehw0y71f1c5p79 /var/www/arvados-api/current/log/production.log | jq .
+{
+  "method": "POST",
+  "path": "/arvados/v1/collections",
+  "format": "json",
+  "controller": "Arvados::V1::CollectionsController",
+  "action": "create",
+  "status": 422,
+  "duration": 1.52,
+  "view": 0.25,
+  "db": 0,
+  "request_id": "req-ku5ct9ehw0y71f1c5p79",
+  "client_ipaddr": "127.0.0.1",
+  "client_auth": "zzzzz-gj3su-jllemyj9v3s5emu",
+  "exception": "#<RuntimeError: Whoops, something bad happened>",
+  "exception_backtrace": "/var/www/arvados-api/current/app/controllers/arvados/v1/collections_controller.rb:43:in `create'\n/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'\n ...[snipped]",
+  "params": {
+    "collection": "{}",
+    "_profile": "true",
+    "cluster_id": "",
+    "collection_given": "true",
+    "ensure_unique_name": "false",
+    "help": "false"
+  },
+  "@timestamp": "2019-07-15T16:40:41.726634182Z",
+  "@version": "1",
+  "message": "[422] POST /arvados/v1/collections (Arvados::V1::CollectionsController#create)"
+}
+</pre>
+
+When logging a request that produced an error, the API Server adds @exception@ and @exception_backtrace@ keys to the JSON log. The latter includes the complete error stack trace as a string, and can be displayed in a more readable form like so:
+
+<pre>
+# grep req-ku5ct9ehw0y71f1c5p79 /var/www/arvados-api/current/log/production.log | jq -r .exception_backtrace
+/var/www/arvados-api/current/app/controllers/arvados/v1/collections_controller.rb:43:in `create'
+/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
+/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/abstract_controller/base.rb:188:in `process_action'
+/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/action_controller/metal/rendering.rb:30:in `process_action'
+/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/abstract_controller/callbacks.rb:20:in `block in process_action'
+/var/lib/gems/ruby/2.3.0/gems/activesupport-5.0.7.2/lib/active_support/callbacks.rb:126:in `call'
+...
+</pre>
index 9231dc292696d2a86136e5c50e0dd2d868d46290..6dd0d866e74c80e1f8c39fa3fbfa9c0456d08787 100644 (file)
@@ -1,7 +1,7 @@
 ---
 layout: default
 navsection: admin
-title: "Migrating account providers"
+title: Changing upstream login providers
 ...
 {% comment %}
 Copyright (C) The Arvados Authors. All rights reserved.
@@ -9,11 +9,11 @@ Copyright (C) The Arvados Authors. All rights reserved.
 SPDX-License-Identifier: CC-BY-SA-3.0
 {% endcomment %}
 
-This page describes how to enable users to use more than one provider to log into the same Arvados account.  This can be used to migrate account providers, for example, from LDAP to Google.  In order to do this, users must be able to log into both the "old" and "new" providers.
+This page describes how to enable users to use more than one upstream identity provider to log into the same Arvados account.  This can be used to migrate account providers, for example, from LDAP to Google.  In order to do this, users must be able to log into both the "old" and "new" providers.
 
-h2. Configure multiple providers in SSO
+h2. Configure multiple or alternate provider in SSO
 
-In @application.yml@ for the SSO server, enable both @google_oauth2@ and @ldap@ providers:
+In @application.yml@ for the SSO server, you can enable both @google_oauth2@ and @ldap@ providers:
 
 <pre>
 production:
@@ -32,9 +32,13 @@ production:
 
 Restart the SSO server after changing the configuration.
 
+h2. Matching on email address
+
+If the new account provider supplies an email address (primary or alternate) that matches an existing user account, the user will be logged into that account.  No further migration is necessary, and the old provider can be removed from the SSO configuration.
+
 h2. Link accounts
 
-Instruct users to go through the process of "linking accounts":{{site.baseurl}}/user/topics/link-accounts.html
+If the new provider cannot provide matching email addresses, users will have to migrate manually by "linking accounts":{{site.baseurl}}/user/topics/link-accounts.html
 
 After linking accounts, users can use the new provider to access their existing Arvados account.
 
diff --git a/doc/admin/reassign-ownership.html.textile.liquid b/doc/admin/reassign-ownership.html.textile.liquid
new file mode 100644 (file)
index 0000000..9c33e18
--- /dev/null
@@ -0,0 +1,51 @@
+---
+layout: default
+navsection: admin
+title: "Reassign user data ownership"
+...
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+If a user leaves an organization and stops using their Arvados account, it may be desirable to reassign the data owned by that user to another user to maintain easy access.
+
+This is currently a command line based, admin-only feature.
+
+h3. Step 1: Determine user uuids
+
+User uuids can be determined by browsing workbench or using @arv user list@ at the command line.
+
+The "old user" is the user that is leaving the organization.
+
+The "new user" is the user that will gain ownership of the old user's data.  This includes collections, projects, container requests, workflows, and git repositories owned by the old user.  It also transfers any permissions granted to the old user, to the new user.
+
+In the example below, @x1u39-tpzed-3kz0nwtjehhl0u4@ is the old user and @x1u39-tpzed-fr97h9t4m5jffxs@ is the new user.
+
+h3. Step 2: Create a project
+
+Create a project owned by the new user that will hold the data from the old user.
+
+<pre>
+$ arv --format=uuid group create --group '{"group_class": "project", "name": "Data from old user", "owner_uuid": "x1u39-tpzed-fr97h9t4m5jffxs"}'
+x1u39-j7d0g-mczqiguhil13083
+</pre>
+
+h3. Step 3: Reassign data from the old user to the new user and project
+
+The @user merge@ method reassigns data from the old user to the new user.
+
+<pre>
+$ arv user merge --old-user-uuid=x1u39-tpzed-3kz0nwtjehhl0u4 \
+  --new-user-uuid=x1u39-tpzed-fr97h9t4m5jffxs \
+  --new-owner-uuid=x1u39-j7d0g-mczqiguhil13083
+</pre>
+
+After reassigning data, use @unsetup@ to deactivate the old user's account.
+
+<pre>
+$ arv user unsetup --uuid=x1u39-tpzed-3kz0nwtjehhl0u4
+</pre>
+
+Note that authorization credentials (API tokens, ssh keys) are *not* transferred to the new user, as this would potentially give the old user access to the new user's account.
deleted file mode 100644 (file)
index 66c75f344daca4b8613b5235fc7f1ce91a50c7c9..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,74 +0,0 @@
----
-layout: default
-navsection: admin
-title: Troubleshooting
-...
-
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-Using a distributed system with several services working together sometimes makes it difficult to find the root cause of errors, as one single client request usually means several different requests to more than one service.
-
-To deal with this difficulty, Arvados creates a request ID that gets carried over different services as the requests take place. This ID has a specific format and it's comprised of the prefix "@req-@" followed by 20 random alphanumeric characters:
-
-<pre>req-frdyrcgdh4rau1ajiq5q</pre>
-
-This ID gets propagated via an HTTP @X-Request-Id@ header, and gets logged on every service.
-
-h3. API Server error reporting and logging
-
-In addition to providing the request ID on every HTTP response, the API Server adds it to every error message so that all clients show enough information to the user to be able to track a particular issue. As an example, let's suppose that we get the following error when trying to create a collection using the CLI tools:
-
-<pre>
-$ arv collection create --collection '{}'
-Error: #<RuntimeError: Whoops, something bad happened> (req-ku5ct9ehw0y71f1c5p79)
-</pre>
-
-The API Server logs every request in JSON format on the @production.log@ (usually under @/var/www/arvados-api/current/log/@ when installing from packages) file, so we can retrieve more information about this by using @grep@ and @jq@ tools:
-
-<pre>
-# grep req-ku5ct9ehw0y71f1c5p79 /var/www/arvados-api/current/log/production.log | jq .
-{
-  "method": "POST",
-  "path": "/arvados/v1/collections",
-  "format": "json",
-  "controller": "Arvados::V1::CollectionsController",
-  "action": "create",
-  "status": 422,
-  "duration": 1.52,
-  "view": 0.25,
-  "db": 0,
-  "request_id": "req-ku5ct9ehw0y71f1c5p79",
-  "client_ipaddr": "127.0.0.1",
-  "client_auth": "zzzzz-gj3su-jllemyj9v3s5emu",
-  "exception": "#<RuntimeError: Whoops, something bad happened>",
-  "exception_backtrace": "/var/www/arvados-api/current/app/controllers/arvados/v1/collections_controller.rb:43:in `create'\n/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'\n ...[snipped]",
-  "params": {
-    "collection": "{}",
-    "_profile": "true",
-    "cluster_id": "",
-    "collection_given": "true",
-    "ensure_unique_name": "false",
-    "help": "false"
-  },
-  "@timestamp": "2019-07-15T16:40:41.726634182Z",
-  "@version": "1",
-  "message": "[422] POST /arvados/v1/collections (Arvados::V1::CollectionsController#create)"
-}
-</pre>
-
-When logging a request that produced an error, the API Server adds @exception@ and @exception_backtrace@ keys to the JSON log. The latter includes the complete error stack trace as a string, and can be displayed in a more readable form like so:
-
-<pre>
-# grep req-ku5ct9ehw0y71f1c5p79 /var/www/arvados-api/current/log/production.log | jq -r .exception_backtrace
-/var/www/arvados-api/current/app/controllers/arvados/v1/collections_controller.rb:43:in `create'
-/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/action_controller/metal/basic_implicit_render.rb:4:in `send_action'
-/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/abstract_controller/base.rb:188:in `process_action'
-/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/action_controller/metal/rendering.rb:30:in `process_action'
-/var/lib/gems/ruby/2.3.0/gems/actionpack-5.0.7.2/lib/abstract_controller/callbacks.rb:20:in `block in process_action'
-/var/lib/gems/ruby/2.3.0/gems/activesupport-5.0.7.2/lib/active_support/callbacks.rb:126:in `call'
-...
-</pre>
\ No newline at end of file
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..88f52eafabe116a792acb8e4610910b540820586
--- /dev/null
@@ -0,0 +1 @@
+logging.html.textile.liquid
\ No newline at end of file
diff --git a/doc/admin/user-management-cli.html.textile.liquid b/doc/admin/user-management-cli.html.textile.liquid
new file mode 100644 (file)
index 0000000..33969ea
--- /dev/null
@@ -0,0 +1,88 @@
+---
+layout: default
+navsection: admin
+title: User management at the CLI
+...
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+Initial setup
+
+<pre>
+ARVADOS_API_HOST={{ site.arvados_api_host }}
+ARVADOS_API_TOKEN=1234567890qwertyuiopasdfghjklzxcvbnm1234567890zzzz
+</pre>
+
+In these examples, @x1u39-tpzed-3kz0nwtjehhl0u4@ is the sample user account.  Replace with the uuid of the user you wish to manipulate.
+
+See "user management":{{site.baseurl}}/admin/activation.html for an overview of how to use these commands.
+
+h3. Setup a user
+
+This creates a default git repository and VM login.  Enables user to self-activate using Workbench.
+
+<pre>
+arv user setup --uuid x1u39-tpzed-3kz0nwtjehhl0u4
+</pre>
+
+h3. Deactivate user
+
+<pre>
+arv user unsetup --uuid x1u39-tpzed-3kz0nwtjehhl0u4
+</pre>
+
+When deactivating a user, you may also want to "reassign ownership of their data":{{site.baseurl}}/admin/reassign-ownership.html .
+
+h3. Directly activate user
+
+<pre>
+arv user update --uuid "x1u39-tpzed-3kz0nwtjehhl0u4" --user '{"is_active":true}'
+</pre>
+
+Note this bypasses user agreements checks, and does not set up the user with a default git repository or VM login.
+
+
+h2. Permissions
+
+h3. VM login
+
+Give @$user_uuid@ permission to log in to @$vm_uuid@ as @$target_username@
+
+<pre>
+user_uuid=xxxxxxxchangeme
+vm_uuid=xxxxxxxchangeme
+target_username=xxxxxxxchangeme
+
+read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
+{
+"tail_uuid":"$user_uuid",
+"head_uuid":"$vm_uuid",
+"link_class":"permission",
+"name":"can_login",
+"properties":{"username":"$target_username"}
+}
+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>
diff --git a/doc/admin/user-management.html.textile.liquid b/doc/admin/user-management.html.textile.liquid
new file mode 100644 (file)
index 0000000..3de1c66
--- /dev/null
@@ -0,0 +1,185 @@
+---
+layout: default
+navsection: admin
+title: User management
+...
+
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+{% comment %}
+TODO: Link to relevant workbench documentation when it gets written
+{% endcomment %}
+
+This page describes how user accounts are created, set up and activated.
+
+h2. Authentication
+
+"Browser login and management of API tokens is described here.":{{site.baseurl}}/api/tokens.html
+
+After completing the log in and authentication process, the API server receives a user record from the upstream identity provider (Google, LDAP, etc) consisting of the user's name, primary email address, alternate email addresses, and optional unique provider identifier (@identity_url@).
+
+If a provider identifier is given, the API server searches for a matching user record.
+
+If a provider identifier is not given, no match is found, it next searches by primary email and then alternate email address.  This enables "provider migration":migrating-providers.html and a "pre-activated accounts.":#pre-activated
+
+If no user account is found, a new user account is created with the information from the identity provider.
+
+If a user account has been "linked":{{site.baseurl}}/user/topics/link-accounts.html or "migrated":merge-remote-account.html the API server may follow internal redirects (@redirect_to_user_uuid@) to select the linked or migrated user account.
+
+h3. Federated Authentication
+
+A federated user follows a slightly different flow.  The client presents a token issued by the remote cluster.  The local API server contacts the remote cluster to verify the user's identity.  This results in a user object (representing the remote user) being created on the local cluster.  If the user cannot be verified, the token will be rejected.  If the user is inactive on the remote cluster, a user record will be created, but it will also be inactive.
+
+h2. User activation
+
+This section describes the different user account states.
+
+!(side){{site.baseurl}}/images/user-account-states.svg!
+
+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}}/install/cheat_sheet.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.
+# The user is active.  User has normal access to the system.
+# From steps (1) and (3), an admin user can directly update the @is_active@ flag.  This bypasses enforcement that user agreements are signed.
+If the user was not yet set up (still in step (1)), it adds the user to the "All users", but bypasses creating default git repository and assigning default VM access.
+# An existing user can have their access revoked using @unsetup@ and "ownership reassigned":reassign-ownership.html .
+Unsetup removes the user from the "All users" group and makes them inactive, preventing them from re-activating themselves.
+"Ownership reassignment":reassign-ownership.html moves any objects or permission from the old user to a new user and deletes any credentials for the old user.
+
+notextile. </div>
+
+User management can be performed through the web using Workbench or the command line.  See "user management at the CLI":{{site.baseurl}}/install/cheat_sheet.html for specific examples.
+
+h2(#user_agreements). User agreements and self-activation
+
+The @activate@ method of the users controller checks if the user account is part of the "All Users" group and whether the user has "signed" all the user agreements.
+
+User agreements are accessed through the "user_agreements API":{{site.baseurl}}/api/methods/user_agreements.html .  This returns a list of collection records.
+
+The user agreements that users are required to sign should be added to the @links@ table this way:
+
+<pre>
+$ arv link create --link '{
+  "link_class": "signature",
+  "name": "require",
+  "tail_uuid": "*system user uuid*",
+  "head_uuid: "*collection uuid*"
+}'
+</pre>
+
+The collection should contain a single HTML file with the user agreement text.
+
+Workbench displays the clickthrough agreements which the user can "sign".
+
+The @user_agreements/sign@ endpoint creates a Link object:
+
+<pre>
+{
+  "link_class": "signature"
+  "name": "click",
+  "tail_uuid": "*user uuid*",
+  "head_uuid: "*collection uuid*"
+}
+</pre>
+
+The @user_agreements/signatures@ endpoint returns the list of Link objects that represent signatures by the current user (created by @sign@).
+
+h2. User profile
+
+The fields making up the user profile are described in @Workbench.UserProfileFormFields@ .  See "Configuration reference":config.html .
+
+The user profile is checked by workbench after checking if user agreements need to be signed.  The values entered are stored in the @properties@ field on the user object.  Unlike user agreements, the requirement to fill out the user profile is not enforced by the API server.
+
+h2(#pre-activated). Pre-setup user by email address
+
+You may create a user account for a user that has not yet logged in, and identify the user by email address.
+
+1. As an admin, create a user object:
+
+<pre>
+$ arv --format=uuid user create --user '{"email": "foo@example.com", "username": "foo"}'
+clsr1-tpzed-1234567890abcdf
+$ arv user setup --uuid clsr1-tpzed-1234567890abcdf
+</pre>
+
+2. When the user logs in the first time, the email address will be recognized and the user will be associated with the existing user object.
+
+h2. Pre-activate federated user
+
+1. As admin, create a user object with the @uuid@ of the federated user (this is the user's uuid on their home cluster, called @clsr2@ in this example):
+
+<pre>
+$ arv user create --user '{"uuid": "clsr2-tpzed-1234567890abcdf", "email": "foo@example.com", "username": "foo", "is_active": true}'
+</pre>
+
+2. When the user logs in, they will be associated with the existing user object.
+
+h2. Auto-setup federated users from trusted clusters
+
+By setting @ActivateUsers: true@ for each federated cluster in @RemoteClusters@, a federated user from one of the listed clusters will be automatically set up and activated on this cluster.  See configuration example in "Federated instance":#federated .
+
+h2. Activation flows
+
+h3. Private instance
+
+Policy: users must be manually set up by the admin.
+
+Here is the configuration for this policy.  This is also the default if not provided.
+(However, be aware that developer/demo builds such as "arvbox":{{site.baseurl}}/install/arvbox.html are configured with the "Open instance" policy described below.)
+
+<pre>
+Users:
+  AutoSetupNewUsers: false
+</pre>
+
+# User is created.  Not set up.  @is_active@ is false.
+# Workbench checks @is_invited@ and finds it is false.  User gets "inactive user" page.
+# Admin goes to user page and clicks "setup user" or sets @is_active@ to true.
+# On refreshing workbench, the user is able to self-activate after signing clickthrough agreements (if any).
+# Alternately, directly setting @is_active@ to true also sets up the user, but skips clickthrough agreements (because the user is already active).
+
+h3(#federated). Federated instance
+
+Policy: users from other clusters in the federation are activated, users from outside the federation must be manually approved.
+
+Here is the configuration for this policy and an example remote cluster @clsr2@.
+
+<pre>
+Users:
+  AutoSetupNewUsers: false
+RemoteClusters:
+  clsr2:
+    ActivateUsers: true
+</pre>
+
+# Federated user arrives claiming to be from cluster 'clsr2'
+# API server authenticates user as being from cluster 'clsr2'
+# Because 'clsr2' has @ActivateUsers@ the user is set up and activated.
+# User can immediately start using Workbench.
+
+h3. Open instance
+
+Policy: anybody who shows up and signs the agreements is activated.
+
+<pre>
+Users:
+  AutoSetupNewUsers: true
+</pre>
+
+"Set up user agreements":#user_agreements by creating "signature" "require" links as described earlier.
+
+# User is created and auto-setup.  At this point, @is_active@ is false, but user has been added to "All users" group.
+# Workbench checks @is_invited@ and finds it is true, because the user is a member of "All users" group.
+# Workbench presents user with list of user agreements, user reads and clicks "sign" for each one.
+# Workbench tries to activate user.
+# User is activated.
index 0e01b3c6dd740abe04210381bd0877931e1366c3..175463c3253a36c73ff10bff6f62e3f97f261f3a 100644 (file)
@@ -96,7 +96,7 @@ table(table table-bordered table-condensed).
 |1|operator|string|Comparison operator|@>@, @>=@, @like@, @not in@|
 |2|operand|string, array, or null|Value to compare with the resource attribute|@"d00220fb%"@, @"1234"@, @["foo","bar"]@, @nil@|
 
-The following operators are available.
+The following operators are available.[1]
 
 table(table table-bordered table-condensed).
 |_. Operator|_. Operand type|_. Description|_. Example|
@@ -107,6 +107,7 @@ table(table table-bordered table-condensed).
 |@is_a@|string|Arvados object type|@["head_uuid","is_a","arvados#collection"]@|
 |@exists@|string|Test if a subproperty is present.|@["properties","exists","my_subproperty"]@|
 
+Note:
 
 h4(#substringsearchfilter). Filtering using substring search
 
@@ -168,3 +169,5 @@ table(table table-bordered table-condensed).
 |_. Argument |_. Type |_. Description |_. Location |
 {background:#ccffcc}.|uuid|string|The UUID of the resource in question.|path||
 |{resource_type}|object||query||
+
+fn1^. NOTE: The filter operator for full-text search (@@) which previously worked (but was undocumented) is deprecated and will be removed in a future release.
index b9a21fc0a0a312ceb9e320113a3a1795e95e136e..cd566f5ce42dcf29edab62c6d38a103f9737ba94 100644 (file)
@@ -141,11 +141,11 @@ table(table table-bordered table-condensed).
 
 h3. list
 
-List container_requests.
+List container requests.
 
 See "common resource list method.":{{site.baseurl}}/api/methods.html#index
 
-See the create method documentation for more information about container request-specific filters.
+The @filters@ argument can also filter on attributes of the container referenced by @container_uuid@. For example, @[["container.state", "=", "Running"]]@ will match any container request whose container is running now.
 
 h3. update
 
index 5ec95cee62ab18a7f8b69ac42382a4f42e7ac1cd..8a7ebc36e5b33613fc6947c88f833ad093aaf351 100644 (file)
@@ -136,8 +136,6 @@ List containers.
 
 See "common resource list method.":{{site.baseurl}}/api/methods.html#index
 
-See the create method documentation for more information about Container-specific filters.
-
 h3. update
 
 Update attributes of an existing Container.
diff --git a/doc/api/methods/user_agreements.html.textile.liquid b/doc/api/methods/user_agreements.html.textile.liquid
new file mode 100644 (file)
index 0000000..9be5cb7
--- /dev/null
@@ -0,0 +1,44 @@
+---
+layout: default
+navsection: api
+navmenu: API Methods
+title: "user_agreements"
+
+...
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+API endpoint base: @https://{{ site.arvados_api_host }}/arvados/v1/user_agreements@
+
+h2. Resource
+
+This provides an API for inactive users to sign clickthrough agreements prior to being activated.
+
+h2. Methods
+
+Required arguments are displayed in %{background:#ccffcc}green%.
+
+h3. list
+
+List user agreements.  This is a list of collections which contain HTML files with the text of the clickthrough agreement(s) which can be rendered by Workbench.
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+
+h3. signatures
+
+List user agreements that have already been signed.  These are recorded as link objects of @{"link_class": "signature", "name": "click"}@.
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+
+h3. sign
+
+Sign a user agreement.
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The UUID of the user agreement collection.|path||
index 098c2ca118c71c85a9ce56576594e2d9b90926fb..4c33f2afe820df5e662622b5880a9fd75f3561a6 100644 (file)
@@ -110,7 +110,7 @@ Arguments:
 table(table table-bordered table-condensed).
 |_. Argument |_. Type |_. Description |_. Location |_. Example |
 {background:#ccffcc}.|uuid|string|The UUID of the User in question.|path||
-|user|object||query||
+|user|object|The new attributes.|query||
 
 h3(#update_uuid). update_uuid
 
@@ -124,3 +124,33 @@ table(table table-bordered table-condensed).
 |_. Argument |_. Type |_. Description |_. Location |_. Example |
 {background:#ccffcc}.|uuid|string|The current UUID of the user in question.|path|@zzzzz-tpzed-12345abcde12345@|
 {background:#ccffcc}.|new_uuid|string|The desired new UUID. It is an error to use a UUID belonging to an existing user.|query|@zzzzz-tpzed-abcde12345abcde@|
+
+h3. setup
+
+Set up a user.  Adds the user to the "All users" group.  Enables the user to invoke @activate@.  See "user management":{{site.baseurl}}/admin/activation.html for details.
+
+Arguments:
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The UUID of the User in question.|query||
+
+h3. activate
+
+Check that a user has is set up and has signed all the user agreements.  If so, activate the user.  Users can invoke this for themselves.  See "user agreements":{{site.baseurl}}/admin/activation.html#user_agreements for details.
+
+Arguments:
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The UUID of the User in question.|query||
+
+h3. unsetup
+
+Remove the user from the "All users" group and deactivate the user.  See "user management":{{site.baseurl}}/admin/activation.html for details.
+
+Arguments:
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The UUID of the User in question.|path||
index 73a1119f36253b5d010fc454f72cc34a22545783..50c59f947efb2b197dfef7083af5b2615d44eeaf 100644 (file)
@@ -13,3 +13,8 @@ img.screenshot {
     margin-left: 2em;
     margin-bottom: 2em;
 }
+
+img.side {
+    float: left;
+    width: 50%;
+}
diff --git a/doc/images/user-account-states.svg b/doc/images/user-account-states.svg
new file mode 100644 (file)
index 0000000..2fc965d
--- /dev/null
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.2" width="228.6mm" height="152.4mm" viewBox="0 0 22860 15240" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
+ <defs class="ClipPathGroup">
+  <clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
+   <rect x="0" y="0" width="22860" height="15240"/>
+  </clipPath>
+  <clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
+   <rect x="22" y="15" width="22815" height="15210"/>
+  </clipPath>
+ </defs>
+ <defs>
+  <font id="EmbeddedFont_1" horiz-adv-x="2048">
+   <font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1852" descent="423"/>
+   <missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
+   <glyph unicode="”" horiz-adv-x="557" d="M 607,1264 C 607,1229 605,1197 602,1168 599,1139 594,1113 588,1088 581,1063 573,1039 563,1017 553,995 541,973 528,952 L 407,952 C 437,995 460,1039 477,1083 493,1127 501,1171 501,1214 L 413,1214 413,1409 607,1409 607,1264 Z M 276,1264 C 276,1229 275,1197 272,1168 269,1139 264,1113 257,1088 250,1063 242,1039 233,1017 223,995 211,973 198,952 L 75,952 C 105,995 128,1039 145,1083 161,1127 169,1171 169,1214 L 81,1214 81,1409 276,1409 276,1264 Z"/>
+   <glyph unicode="“" horiz-adv-x="557" d="M 407,952 L 407,1098 C 407,1133 409,1164 412,1193 415,1222 420,1249 427,1274 433,1299 441,1322 451,1344 460,1366 472,1388 485,1409 L 607,1409 C 577,1366 554,1322 538,1278 521,1234 513,1190 513,1147 L 601,1147 601,952 407,952 Z M 75,952 L 75,1098 C 75,1133 77,1164 80,1193 83,1222 88,1249 95,1274 102,1299 110,1322 120,1344 130,1366 142,1388 155,1409 L 276,1409 C 246,1366 223,1322 206,1278 189,1234 181,1190 181,1147 L 270,1147 270,952 75,952 Z"/>
+   <glyph unicode="v" horiz-adv-x="1033" d="M 613,0 L 400,0 7,1082 199,1082 437,378 C 442,363 447,346 454,325 460,304 466,282 473,259 480,236 486,215 492,194 497,173 502,155 506,141 510,155 515,173 522,194 528,215 534,236 541,258 548,280 555,302 562,323 569,344 575,361 580,376 L 826,1082 1017,1082 613,0 Z"/>
+   <glyph unicode="u" horiz-adv-x="874" d="M 314,1082 L 314,396 C 314,343 318,299 326,264 333,229 346,200 363,179 380,157 403,142 432,133 460,124 495,119 537,119 580,119 618,127 653,142 687,157 716,178 741,207 765,235 784,270 797,312 810,353 817,401 817,455 L 817,1082 997,1082 997,231 C 997,208 997,185 998,160 998,135 998,111 999,89 1000,66 1000,47 1001,31 1002,15 1002,5 1003,0 L 833,0 C 832,3 832,12 831,27 830,42 830,59 829,78 828,97 827,116 826,136 825,155 825,172 825,185 L 822,185 C 805,154 786,125 765,100 744,75 720,53 693,36 666,18 634,4 599,-6 564,-15 523,-20 476,-20 416,-20 364,-13 321,2 278,17 242,39 214,70 186,101 166,140 153,188 140,236 133,294 133,361 L 133,1082 314,1082 Z"/>
+   <glyph unicode="t" horiz-adv-x="531" d="M 554,8 C 527,1 499,-5 471,-10 442,-14 409,-16 372,-16 228,-16 156,66 156,229 L 156,951 31,951 31,1082 163,1082 216,1324 336,1324 336,1082 536,1082 536,951 336,951 336,268 C 336,216 345,180 362,159 379,138 408,127 450,127 467,127 484,128 501,131 517,134 535,137 554,141 L 554,8 Z"/>
+   <glyph unicode="s" horiz-adv-x="901" d="M 950,299 C 950,248 940,203 921,164 901,124 872,91 835,64 798,37 752,16 698,2 643,-13 581,-20 511,-20 448,-20 392,-15 342,-6 291,4 247,20 209,41 171,62 139,91 114,126 88,161 69,203 57,254 L 216,285 C 231,227 263,185 311,158 359,131 426,117 511,117 550,117 585,120 618,125 650,130 678,140 701,153 724,166 743,183 756,205 769,226 775,253 775,285 775,318 767,345 752,366 737,387 715,404 688,418 661,432 628,444 589,455 550,465 507,476 460,489 417,500 374,513 331,527 288,541 250,560 216,583 181,606 153,634 132,668 111,702 100,745 100,796 100,895 135,970 206,1022 276,1073 378,1099 513,1099 632,1099 727,1078 798,1036 868,994 912,927 931,834 L 769,814 C 763,842 752,866 736,885 720,904 701,919 678,931 655,942 630,951 602,956 573,961 544,963 513,963 432,963 372,951 333,926 294,901 275,864 275,814 275,785 282,761 297,742 311,723 331,707 357,694 382,681 413,669 449,660 485,650 525,640 568,629 597,622 626,614 656,606 686,597 715,587 744,576 772,564 799,550 824,535 849,519 870,500 889,478 908,456 923,430 934,401 945,372 950,338 950,299 Z"/>
+   <glyph unicode="r" horiz-adv-x="530" d="M 142,0 L 142,830 C 142,853 142,876 142,900 141,923 141,946 140,968 139,990 139,1011 138,1030 137,1049 137,1067 136,1082 L 306,1082 C 307,1067 308,1049 309,1030 310,1010 311,990 312,969 313,948 313,929 314,910 314,891 314,874 314,861 L 318,861 C 331,902 344,938 359,969 373,999 390,1024 409,1044 428,1063 451,1078 478,1088 505,1097 537,1102 575,1102 590,1102 604,1101 617,1099 630,1096 641,1094 648,1092 L 648,927 C 636,930 622,933 606,935 590,936 572,937 552,937 511,937 476,928 447,909 418,890 394,865 376,832 357,799 344,759 335,714 326,668 322,618 322,564 L 322,0 142,0 Z"/>
+   <glyph unicode="p" horiz-adv-x="953" d="M 1053,546 C 1053,464 1046,388 1033,319 1020,250 998,190 967,140 936,90 895,51 844,23 793,-6 730,-20 655,-20 578,-20 510,-5 452,24 394,53 350,101 319,168 L 314,168 C 315,167 315,161 316,150 316,139 316,126 317,110 317,94 317,76 318,57 318,37 318,17 318,-2 L 318,-425 138,-425 138,861 C 138,887 138,912 138,936 137,960 137,982 136,1002 135,1021 135,1038 134,1052 133,1066 133,1076 132,1082 L 306,1082 C 307,1080 308,1073 309,1061 310,1049 311,1035 312,1018 313,1001 314,982 315,963 316,944 316,925 316,908 L 320,908 C 337,943 356,972 377,997 398,1021 423,1041 450,1057 477,1072 508,1084 542,1091 575,1098 613,1101 655,1101 730,1101 793,1088 844,1061 895,1034 936,997 967,949 998,900 1020,842 1033,774 1046,705 1053,629 1053,546 Z M 864,542 C 864,609 860,668 852,720 844,772 830,816 811,852 791,888 765,915 732,934 699,953 658,962 609,962 569,962 531,956 496,945 461,934 430,912 404,880 377,848 356,804 341,748 326,691 318,618 318,528 318,451 324,387 337,334 350,281 368,238 393,205 417,172 447,149 483,135 519,120 560,113 607,113 657,113 699,123 732,142 765,161 791,189 811,226 830,263 844,308 852,361 860,414 864,474 864,542 Z"/>
+   <glyph unicode="o" horiz-adv-x="980" d="M 1053,542 C 1053,353 1011,212 928,119 845,26 724,-20 565,-20 490,-20 422,-9 363,14 304,37 254,71 213,118 172,165 140,223 119,294 97,364 86,447 86,542 86,915 248,1102 571,1102 655,1102 728,1090 789,1067 850,1044 900,1009 939,962 978,915 1006,857 1025,787 1044,717 1053,635 1053,542 Z M 864,542 C 864,626 858,695 845,750 832,805 813,848 788,881 763,914 732,937 696,950 660,963 619,969 574,969 528,969 487,962 450,949 413,935 381,912 355,879 329,846 309,802 296,747 282,692 275,624 275,542 275,458 282,389 297,334 312,279 332,235 358,202 383,169 414,146 449,133 484,120 522,113 563,113 609,113 651,120 688,133 725,146 757,168 783,201 809,234 829,278 843,333 857,388 864,458 864,542 Z"/>
+   <glyph unicode="n" horiz-adv-x="874" d="M 825,0 L 825,686 C 825,739 821,783 814,818 806,853 793,882 776,904 759,925 736,941 708,950 679,959 644,963 602,963 559,963 521,956 487,941 452,926 423,904 399,876 374,847 355,812 342,771 329,729 322,681 322,627 L 322,0 142,0 142,851 C 142,874 142,898 142,923 141,948 141,971 140,994 139,1016 139,1035 138,1051 137,1067 137,1077 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 312,966 313,947 314,927 314,910 314,897 L 317,897 C 334,928 353,957 374,982 395,1007 419,1029 446,1047 473,1064 505,1078 540,1088 575,1097 616,1102 663,1102 723,1102 775,1095 818,1080 861,1065 897,1043 925,1012 953,981 974,942 987,894 1000,845 1006,788 1006,721 L 1006,0 825,0 Z"/>
+   <glyph unicode="l" horiz-adv-x="187" d="M 138,0 L 138,1484 318,1484 318,0 138,0 Z"/>
+   <glyph unicode="i" horiz-adv-x="187" d="M 137,1312 L 137,1484 317,1484 317,1312 137,1312 Z M 137,0 L 137,1082 317,1082 317,0 137,0 Z"/>
+   <glyph unicode="g" horiz-adv-x="927" d="M 548,-425 C 486,-425 431,-419 383,-406 335,-393 294,-375 260,-352 226,-328 198,-300 177,-267 156,-234 140,-198 131,-158 L 312,-132 C 324,-182 351,-220 392,-248 433,-274 486,-288 553,-288 594,-288 631,-282 664,-271 697,-260 726,-241 749,-217 772,-191 790,-159 803,-119 816,-79 822,-30 822,27 L 822,201 820,201 C 807,174 790,148 771,123 751,98 727,75 699,56 670,37 637,21 600,10 563,-2 520,-8 472,-8 403,-8 345,4 296,27 247,50 207,84 176,130 145,176 122,233 108,302 93,370 86,449 86,539 86,626 93,704 108,773 122,842 145,901 178,950 210,998 252,1035 304,1061 355,1086 418,1099 492,1099 569,1099 635,1082 692,1047 748,1012 791,962 822,897 L 824,897 C 824,914 825,933 826,953 827,974 828,994 829,1012 830,1031 831,1046 832,1060 833,1073 835,1080 836,1080 L 1007,1080 C 1006,1074 1006,1064 1005,1050 1004,1035 1004,1018 1003,998 1002,978 1002,956 1002,932 1001,907 1001,882 1001,856 L 1001,30 C 1001,-121 964,-234 890,-311 815,-387 701,-425 548,-425 Z M 822,541 C 822,616 814,681 798,735 781,788 760,832 733,866 706,900 676,925 642,941 607,957 572,965 536,965 490,965 451,957 418,941 385,925 357,900 336,866 314,831 298,787 288,734 277,680 272,616 272,541 272,463 277,398 288,345 298,292 314,249 335,216 356,183 383,160 416,146 449,132 488,125 533,125 569,125 604,133 639,148 673,163 704,188 731,221 758,254 780,297 797,350 814,403 822,466 822,541 Z"/>
+   <glyph unicode="e" horiz-adv-x="980" d="M 276,503 C 276,446 282,394 294,347 305,299 323,258 348,224 372,189 403,163 441,144 479,125 525,115 578,115 656,115 719,131 766,162 813,193 844,233 861,281 L 1019,236 C 1008,206 992,176 972,146 951,115 924,88 890,64 856,39 814,19 763,4 712,-12 650,-20 578,-20 418,-20 296,28 213,123 129,218 87,360 87,548 87,649 100,735 125,806 150,876 185,933 229,977 273,1021 324,1053 383,1073 442,1092 504,1102 571,1102 662,1102 738,1087 799,1058 860,1029 909,988 946,937 983,885 1009,824 1025,754 1040,684 1048,608 1048,527 L 1048,503 276,503 Z M 862,641 C 852,755 823,838 775,891 727,943 658,969 568,969 538,969 507,964 474,955 441,945 410,928 382,903 354,878 330,845 311,803 292,760 281,706 278,641 L 862,641 Z"/>
+   <glyph unicode="d" horiz-adv-x="927" d="M 821,174 C 788,105 744,55 689,25 634,-5 565,-20 484,-20 347,-20 247,26 183,118 118,210 86,349 86,536 86,913 219,1102 484,1102 566,1102 634,1087 689,1057 744,1027 788,979 821,914 L 823,914 C 823,921 823,931 823,946 822,960 822,975 822,991 821,1006 821,1021 821,1035 821,1049 821,1059 821,1065 L 821,1484 1001,1484 1001,223 C 1001,197 1001,172 1002,148 1002,124 1002,102 1003,82 1004,62 1004,45 1005,31 1006,16 1006,6 1007,0 L 835,0 C 834,7 833,16 832,29 831,41 830,55 829,71 828,87 827,104 826,122 825,139 825,157 825,174 L 821,174 Z M 275,542 C 275,467 280,403 289,350 298,297 313,253 334,219 355,184 381,159 413,143 445,127 484,119 530,119 577,119 619,127 656,142 692,157 722,182 747,217 771,251 789,296 802,351 815,406 821,474 821,554 821,631 815,696 802,749 789,802 771,844 746,877 721,910 691,933 656,948 620,962 579,969 532,969 488,969 450,961 418,946 386,931 359,906 338,872 317,838 301,794 291,740 280,685 275,619 275,542 Z"/>
+   <glyph unicode="c" horiz-adv-x="901" d="M 275,546 C 275,484 280,427 289,375 298,323 313,278 334,241 355,203 384,174 419,153 454,132 497,122 548,122 612,122 666,139 709,174 752,209 778,262 788,334 L 970,322 C 964,277 951,234 931,193 911,152 884,115 850,84 815,53 773,28 724,9 675,-10 618,-20 553,-20 468,-20 396,-6 337,23 278,52 230,91 193,142 156,192 129,251 112,320 95,388 87,462 87,542 87,615 93,679 105,735 117,790 134,839 156,881 177,922 203,957 232,986 261,1014 293,1037 328,1054 362,1071 398,1083 436,1091 474,1098 512,1102 551,1102 612,1102 666,1094 713,1077 760,1060 801,1038 836,1009 870,980 898,945 919,906 940,867 955,824 964,779 L 779,765 C 770,825 746,873 708,908 670,943 616,961 546,961 495,961 452,953 418,936 383,919 355,893 334,859 313,824 298,781 289,729 280,677 275,616 275,546 Z"/>
+   <glyph unicode="a" horiz-adv-x="1060" d="M 414,-20 C 305,-20 224,9 169,66 114,123 87,202 87,302 87,373 101,432 128,478 155,523 190,559 234,585 277,611 327,629 383,639 439,649 496,655 554,656 L 797,660 797,719 C 797,764 792,802 783,833 774,864 759,890 740,909 721,928 697,943 668,952 639,961 604,965 565,965 530,965 499,963 471,958 443,953 419,944 398,931 377,918 361,900 348,878 335,855 327,827 323,793 L 135,810 C 142,853 154,892 173,928 192,963 218,994 253,1020 287,1046 330,1066 382,1081 433,1095 496,1102 569,1102 705,1102 807,1071 876,1009 945,946 979,856 979,738 L 979,272 C 979,219 986,179 1000,152 1014,125 1041,111 1080,111 1090,111 1100,112 1110,113 1120,114 1130,116 1139,118 L 1139,6 C 1116,1 1094,-3 1072,-6 1049,-9 1025,-10 1000,-10 966,-10 937,-5 913,4 888,13 868,26 853,45 838,63 826,86 818,113 810,140 805,171 803,207 L 797,207 C 778,172 757,141 734,113 711,85 684,61 653,42 622,22 588,7 549,-4 510,-15 465,-20 414,-20 Z M 455,115 C 512,115 563,126 606,147 649,168 684,194 713,227 741,260 762,295 776,334 790,373 797,410 797,445 L 797,534 600,530 C 556,529 514,526 475,521 435,515 400,504 370,487 340,470 316,447 299,417 281,387 272,348 272,299 272,240 288,195 320,163 351,131 396,115 455,115 Z"/>
+   <glyph unicode="_" horiz-adv-x="1218" d="M -31,-407 L -31,-277 1162,-277 1162,-407 -31,-407 Z"/>
+   <glyph unicode="U" horiz-adv-x="1192" d="M 731,-20 C 654,-20 580,-10 511,11 442,32 381,64 329,108 276,151 235,207 204,274 173,341 158,420 158,512 L 158,1409 349,1409 349,528 C 349,457 359,396 378,347 397,297 423,256 457,225 491,194 531,171 578,157 624,142 675,135 730,135 785,135 836,142 885,157 934,172 976,195 1013,227 1050,259 1079,301 1100,353 1121,404 1131,467 1131,541 L 1131,1409 1321,1409 1321,530 C 1321,436 1306,355 1275,286 1244,217 1201,159 1148,114 1095,69 1032,35 961,13 889,-9 812,-20 731,-20 Z"/>
+   <glyph unicode="S" horiz-adv-x="1192" d="M 1272,389 C 1272,330 1261,275 1238,225 1215,175 1179,132 1131,96 1083,59 1023,31 950,11 877,-10 790,-20 690,-20 515,-20 378,11 280,72 182,133 120,222 93,338 L 278,375 C 287,338 302,305 321,275 340,245 367,219 400,198 433,176 473,159 522,147 571,135 629,129 697,129 754,129 806,134 853,144 900,153 941,168 975,188 1009,208 1036,234 1055,266 1074,297 1083,335 1083,379 1083,425 1073,462 1052,491 1031,520 1001,543 963,562 925,581 880,596 827,609 774,622 716,635 652,650 613,659 573,668 534,679 494,689 456,701 420,716 383,730 349,747 317,766 285,785 257,809 234,836 211,863 192,894 179,930 166,965 159,1006 159,1053 159,1120 173,1177 200,1225 227,1272 264,1311 312,1342 360,1373 417,1395 482,1409 547,1423 618,1430 694,1430 781,1430 856,1423 918,1410 980,1396 1032,1375 1075,1348 1118,1321 1152,1287 1178,1247 1203,1206 1224,1159 1239,1106 L 1051,1073 C 1042,1107 1028,1137 1011,1164 993,1191 970,1213 941,1231 912,1249 878,1263 837,1272 796,1281 747,1286 692,1286 627,1286 572,1280 528,1269 483,1257 448,1241 421,1221 394,1201 374,1178 363,1151 351,1124 345,1094 345,1063 345,1021 356,987 377,960 398,933 426,910 462,892 498,874 540,859 587,847 634,835 685,823 738,811 781,801 825,791 868,781 911,770 952,758 991,744 1030,729 1067,712 1102,693 1136,674 1166,650 1191,622 1216,594 1236,561 1251,523 1265,485 1272,440 1272,389 Z"/>
+   <glyph unicode="P" horiz-adv-x="1112" d="M 1258,985 C 1258,924 1248,867 1228,814 1207,761 1177,715 1137,676 1096,637 1046,606 985,583 924,560 854,549 773,549 L 359,549 359,0 168,0 168,1409 761,1409 C 844,1409 917,1399 979,1379 1041,1358 1093,1330 1134,1293 1175,1256 1206,1211 1227,1159 1248,1106 1258,1048 1258,985 Z M 1066,983 C 1066,1072 1039,1140 984,1187 929,1233 847,1256 738,1256 L 359,1256 359,700 746,700 C 856,700 937,724 989,773 1040,822 1066,892 1066,983 Z"/>
+   <glyph unicode="L" horiz-adv-x="927" d="M 168,0 L 168,1409 359,1409 359,156 1071,156 1071,0 168,0 Z"/>
+   <glyph unicode="G" horiz-adv-x="1377" d="M 103,711 C 103,821 118,920 148,1009 177,1098 222,1173 281,1236 340,1298 413,1346 500,1380 587,1413 689,1430 804,1430 891,1430 967,1422 1032,1407 1097,1392 1154,1370 1202,1341 1250,1312 1291,1278 1324,1237 1357,1196 1386,1149 1409,1098 L 1227,1044 C 1210,1079 1189,1110 1165,1139 1140,1167 1111,1191 1076,1211 1041,1231 1001,1247 956,1258 910,1269 858,1274 799,1274 714,1274 640,1261 577,1234 514,1207 461,1169 420,1120 379,1071 348,1011 328,942 307,873 297,796 297,711 297,626 308,549 330,479 352,408 385,348 428,297 471,246 525,206 590,178 654,149 728,135 813,135 868,135 919,140 966,149 1013,158 1055,171 1093,186 1130,201 1163,217 1192,236 1221,254 1245,272 1264,291 L 1264,545 843,545 843,705 1440,705 1440,219 C 1409,187 1372,157 1330,128 1287,99 1240,73 1187,51 1134,29 1077,12 1014,-1 951,-14 884,-20 813,-20 694,-20 591,-2 502,35 413,71 340,122 281,187 222,252 177,329 148,418 118,507 103,605 103,711 Z"/>
+   <glyph unicode="D" horiz-adv-x="1218" d="M 1381,719 C 1381,602 1363,498 1328,409 1293,319 1244,244 1183,184 1122,123 1049,78 966,47 882,16 792,0 695,0 L 168,0 168,1409 634,1409 C 743,1409 843,1396 935,1369 1026,1342 1105,1300 1171,1244 1237,1187 1289,1116 1326,1029 1363,942 1381,839 1381,719 Z M 1189,719 C 1189,814 1175,896 1148,964 1121,1031 1082,1087 1033,1130 984,1173 925,1205 856,1226 787,1246 712,1256 630,1256 L 359,1256 359,153 673,153 C 747,153 816,165 879,189 942,213 996,249 1042,296 1088,343 1124,402 1150,473 1176,544 1189,626 1189,719 Z"/>
+   <glyph unicode="C" horiz-adv-x="1324" d="M 792,1274 C 712,1274 641,1261 580,1234 518,1207 466,1169 425,1120 383,1071 351,1011 330,942 309,873 298,796 298,711 298,626 310,549 333,479 356,408 389,348 432,297 475,246 527,207 590,179 652,151 722,137 800,137 855,137 905,144 950,159 995,173 1035,193 1072,219 1108,245 1140,276 1169,312 1198,347 1223,387 1245,430 L 1401,352 C 1376,299 1344,250 1307,205 1270,160 1226,120 1176,87 1125,54 1068,28 1005,9 941,-10 870,-20 791,-20 677,-20 577,-2 492,35 406,71 334,122 277,187 219,252 176,329 147,418 118,507 104,605 104,711 104,821 119,920 150,1009 180,1098 224,1173 283,1236 341,1298 413,1346 498,1380 583,1413 681,1430 790,1430 940,1430 1065,1401 1166,1342 1267,1283 1341,1196 1388,1081 L 1207,1021 C 1194,1054 1176,1086 1153,1117 1130,1147 1102,1174 1068,1197 1034,1220 994,1239 949,1253 903,1267 851,1274 792,1274 Z"/>
+   <glyph unicode="A" horiz-adv-x="1377" d="M 1167,0 L 1006,412 364,412 202,0 4,0 579,1409 796,1409 1362,0 1167,0 Z M 768,1026 C 757,1053 747,1080 738,1107 728,1134 719,1159 712,1182 705,1204 699,1223 694,1238 689,1253 686,1262 685,1265 684,1262 681,1252 676,1237 671,1222 665,1203 658,1180 650,1157 641,1132 632,1105 622,1078 612,1051 602,1024 L 422,561 949,561 768,1026 Z"/>
+   <glyph unicode="7" horiz-adv-x="954" d="M 1036,1263 C 965,1155 900,1051 841,952 782,852 731,752 688,651 645,550 612,446 589,340 565,233 553,120 553,0 L 365,0 C 365,113 378,223 405,332 432,440 468,546 513,651 558,755 611,857 671,958 731,1059 795,1158 862,1256 L 105,1256 105,1409 1036,1409 1036,1263 Z"/>
+   <glyph unicode="6" horiz-adv-x="980" d="M 1049,461 C 1049,390 1039,326 1020,267 1000,208 971,157 933,115 894,72 847,39 790,16 733,-8 668,-20 594,-20 512,-20 440,-4 379,27 318,58 267,104 226,163 185,222 155,294 135,380 114,465 104,563 104,672 104,797 116,907 139,1002 162,1097 195,1176 238,1239 281,1302 334,1350 397,1382 459,1414 529,1430 608,1430 656,1430 701,1425 743,1415 785,1405 823,1389 858,1367 892,1344 922,1315 948,1278 974,1241 995,1196 1010,1143 L 838,1112 C 819,1173 790,1217 749,1244 708,1271 660,1284 606,1284 557,1284 512,1272 472,1249 432,1226 398,1191 370,1145 342,1098 321,1040 306,970 291,900 283,818 283,725 316,786 362,832 421,864 480,895 548,911 625,911 689,911 747,901 799,880 851,859 896,830 933,791 970,752 998,704 1019,649 1039,593 1049,530 1049,461 Z M 866,453 C 866,502 860,546 848,585 836,624 818,658 794,686 770,713 740,735 705,750 670,765 629,772 582,772 549,772 516,767 483,758 450,748 420,732 393,711 366,689 344,660 327,625 310,590 301,547 301,496 301,444 308,396 321,351 334,306 354,266 379,233 404,200 434,173 469,154 504,135 544,125 588,125 631,125 670,133 705,148 739,163 768,184 792,213 816,241 834,275 847,316 860,357 866,402 866,453 Z"/>
+   <glyph unicode="5" horiz-adv-x="980" d="M 1053,459 C 1053,388 1042,324 1021,265 1000,206 968,156 926,114 884,71 832,38 770,15 707,-8 635,-20 553,-20 479,-20 415,-11 360,6 305,23 258,47 220,78 182,108 152,143 130,184 107,225 91,268 82,315 L 264,336 C 271,309 282,284 295,259 308,234 327,211 350,192 373,172 401,156 435,145 468,133 509,127 557,127 604,127 646,134 684,149 722,163 755,184 782,212 809,240 829,274 844,315 859,356 866,402 866,455 866,498 859,538 845,575 831,611 811,642 785,669 759,695 727,715 690,730 652,745 609,752 561,752 531,752 503,749 478,744 453,739 429,731 408,722 386,713 366,702 349,690 331,677 314,664 299,651 L 123,651 170,1409 971,1409 971,1256 334,1256 307,809 C 339,834 379,855 427,873 475,890 532,899 598,899 668,899 731,888 787,867 843,846 891,816 930,777 969,738 1000,691 1021,637 1042,583 1053,524 1053,459 Z"/>
+   <glyph unicode="4" horiz-adv-x="1060" d="M 881,319 L 881,0 711,0 711,319 47,319 47,459 692,1409 881,1409 881,461 1079,461 1079,319 881,319 Z M 711,1206 C 710,1203 706,1196 701,1187 696,1177 690,1166 683,1154 676,1142 670,1130 663,1118 656,1105 649,1095 644,1087 L 283,555 C 280,550 275,543 269,534 262,525 256,517 249,508 242,499 236,490 229,481 222,472 217,466 213,461 L 711,461 711,1206 Z"/>
+   <glyph unicode="3" horiz-adv-x="1006" d="M 1049,389 C 1049,324 1039,267 1018,216 997,165 966,123 926,88 885,53 835,26 776,8 716,-11 648,-20 571,-20 484,-20 410,-9 351,13 291,34 242,63 203,99 164,134 135,175 116,221 97,266 84,313 78,362 L 264,379 C 269,342 279,308 294,277 308,246 327,220 352,198 377,176 407,159 443,147 479,135 522,129 571,129 662,129 733,151 785,196 836,241 862,307 862,395 862,447 851,489 828,521 805,552 776,577 742,595 707,612 670,624 630,630 589,636 552,639 518,639 L 416,639 416,795 514,795 C 548,795 583,799 620,806 657,813 690,825 721,844 751,862 776,887 796,918 815,949 825,989 825,1038 825,1113 803,1173 759,1217 714,1260 648,1282 561,1282 482,1282 418,1262 369,1221 320,1180 291,1123 283,1049 L 102,1063 C 109,1125 126,1179 153,1225 180,1271 214,1309 255,1340 296,1370 342,1393 395,1408 448,1423 504,1430 563,1430 642,1430 709,1420 766,1401 823,1381 869,1354 905,1321 941,1287 968,1247 985,1202 1002,1157 1010,1108 1010,1057 1010,1016 1004,977 993,941 982,905 964,873 940,844 916,815 886,791 849,770 812,749 767,734 715,723 L 715,719 C 772,713 821,700 863,681 905,661 940,636 967,607 994,578 1015,544 1029,507 1042,470 1049,430 1049,389 Z"/>
+   <glyph unicode="2" horiz-adv-x="954" d="M 103,0 L 103,127 C 137,205 179,274 228,334 277,393 328,447 382,496 436,544 490,589 543,630 596,671 643,713 686,754 729,795 763,839 790,884 816,929 829,981 829,1038 829,1078 823,1113 811,1144 799,1174 782,1199 759,1220 736,1241 709,1256 678,1267 646,1277 611,1282 572,1282 536,1282 502,1277 471,1267 439,1257 411,1242 386,1222 361,1202 341,1177 326,1148 310,1118 300,1083 295,1044 L 111,1061 C 117,1112 131,1159 153,1204 175,1249 205,1288 244,1322 283,1355 329,1382 384,1401 438,1420 501,1430 572,1430 642,1430 704,1422 759,1405 814,1388 860,1364 898,1331 935,1298 964,1258 984,1210 1004,1162 1014,1107 1014,1044 1014,997 1006,952 989,909 972,866 949,826 921,787 892,748 859,711 822,675 785,639 746,604 705,570 664,535 623,501 582,468 541,434 502,400 466,366 429,332 397,298 368,263 339,228 317,191 301,153 L 1036,153 1036,0 103,0 Z"/>
+   <glyph unicode="1" horiz-adv-x="927" d="M 156,0 L 156,153 515,153 515,1237 197,1010 197,1180 530,1409 696,1409 696,153 1039,153 1039,0 156,0 Z"/>
+   <glyph unicode="/" horiz-adv-x="583" d="M 0,-20 L 411,1484 569,1484 162,-20 0,-20 Z"/>
+   <glyph unicode="." horiz-adv-x="213" d="M 187,0 L 187,219 382,219 382,0 187,0 Z"/>
+   <glyph unicode=")" horiz-adv-x="557" d="M 555,528 C 555,435 548,346 534,262 520,177 498,96 468,18 438,-60 400,-136 353,-209 306,-282 251,-354 186,-424 L 12,-424 C 75,-354 129,-282 175,-209 220,-136 258,-60 287,19 316,98 338,179 353,264 367,349 374,437 374,530 374,623 367,711 353,796 338,881 316,962 287,1041 258,1119 220,1195 175,1269 129,1342 75,1414 12,1484 L 186,1484 C 251,1414 306,1342 353,1269 400,1196 438,1120 468,1042 498,964 520,883 534,798 548,713 555,625 555,532 L 555,528 Z"/>
+   <glyph unicode="(" horiz-adv-x="583" d="M 127,532 C 127,625 134,713 148,798 162,883 184,964 214,1042 244,1120 282,1196 329,1269 376,1342 431,1414 496,1484 L 670,1484 C 607,1414 553,1342 508,1269 462,1195 424,1119 395,1041 366,962 344,881 330,796 315,711 308,623 308,530 308,437 315,349 330,264 344,179 366,98 395,19 424,-60 462,-136 508,-209 553,-282 607,-354 670,-424 L 496,-424 C 431,-354 376,-282 329,-209 282,-136 244,-60 214,18 184,96 162,177 148,262 134,346 127,435 127,528 L 127,532 Z"/>
+   <glyph unicode=" " horiz-adv-x="556"/>
+  </font>
+ </defs>
+ <defs class="TextShapeIndex">
+  <g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16"/>
+ </defs>
+ <defs class="EmbeddedBulletChars">
+  <g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
+  </g>
+  <g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
+  </g>
+  <g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
+  </g>
+  <g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
+  </g>
+  <g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
+  </g>
+  <g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
+  </g>
+  <g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
+  </g>
+  <g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
+  </g>
+  <g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
+  </g>
+  <g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
+   <path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
+  </g>
+ </defs>
+ <defs class="TextEmbeddedBitmaps"/>
+ <g>
+  <g id="id2" class="Master_Slide">
+   <g id="bg-id2" class="Background"/>
+   <g id="bo-id2" class="BackgroundObjects"/>
+  </g>
+ </g>
+ <g class="SlideGroup">
+  <g>
+   <g id="container-id1">
+    <g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
+     <g class="Page">
+      <g class="com.sun.star.drawing.CustomShape">
+       <g id="id3">
+        <rect class="BoundingBox" stroke="none" fill="none" x="8202" y="4046" width="3556" height="1781"/>
+        <path fill="rgb(114,159,207)" stroke="none" d="M 9980,5825 L 8203,5825 8203,4047 11756,4047 11756,5825 9980,5825 Z"/>
+        <path fill="none" stroke="rgb(52,101,164)" d="M 9980,5825 L 8203,5825 8203,4047 11756,4047 11756,5825 9980,5825 Z"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8500" y="4801"><tspan fill="rgb(0,0,0)" stroke="none">1. Created</tspan></tspan></tspan><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8695" y="5512"><tspan fill="rgb(0,0,0)" stroke="none">(inactive)</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.CustomShape">
+       <g id="id4">
+        <rect class="BoundingBox" stroke="none" fill="none" x="8072" y="7983" width="3812" height="1781"/>
+        <path fill="rgb(114,159,207)" stroke="none" d="M 9978,9762 L 8073,9762 8073,7984 11882,7984 11882,9762 9978,9762 Z"/>
+        <path fill="none" stroke="rgb(52,101,164)" d="M 9978,9762 L 8073,9762 8073,7984 11882,7984 11882,9762 9978,9762 Z"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8708" y="8738"><tspan fill="rgb(0,0,0)" stroke="none">3. Set up</tspan></tspan></tspan><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8693" y="9449"><tspan fill="rgb(0,0,0)" stroke="none">(inactive)</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.CustomShape">
+       <g id="id5">
+        <rect class="BoundingBox" stroke="none" fill="none" x="8234" y="11665" width="3430" height="1908"/>
+        <path fill="rgb(114,159,207)" stroke="none" d="M 9949,13571 L 8235,13571 8235,11666 11662,11666 11662,13571 9949,13571 Z"/>
+        <path fill="none" stroke="rgb(52,101,164)" d="M 9949,13571 L 8235,13571 8235,11666 11662,11666 11662,13571 9949,13571 Z"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="8751" y="12839"><tspan fill="rgb(0,0,0)" stroke="none">5. Active</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.ConnectorShape">
+       <g id="id6">
+        <rect class="BoundingBox" stroke="none" fill="none" x="9828" y="5824" width="302" height="2161"/>
+        <path fill="none" stroke="rgb(0,0,0)" d="M 9980,5825 L 9978,7554"/>
+        <path fill="rgb(0,0,0)" stroke="none" d="M 9978,7984 L 10128,7534 9828,7534 9978,7984 Z"/>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.ConnectorShape">
+       <g id="id7">
+        <rect class="BoundingBox" stroke="none" fill="none" x="9805" y="9761" width="302" height="1907"/>
+        <path fill="none" stroke="rgb(0,0,0)" d="M 9978,9762 L 9956,11237"/>
+        <path fill="rgb(0,0,0)" stroke="none" d="M 9949,11667 L 10106,11219 9806,11215 9949,11667 Z"/>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.ConnectorShape">
+       <g id="id8">
+        <rect class="BoundingBox" stroke="none" fill="none" x="11662" y="4807" width="3803" height="7814"/>
+        <path fill="none" stroke="rgb(0,0,0)" d="M 11663,12619 C 16668,12619 16620,5368 12161,4954"/>
+        <path fill="rgb(0,0,0)" stroke="none" d="M 11757,4936 L 12199,5107 12214,4808 11757,4936 Z"/>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.TextShape">
+       <g id="id9">
+        <rect class="BoundingBox" stroke="none" fill="none" x="10234" y="1155" width="5843" height="4440"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10484" y="1856"><tspan fill="rgb(0,0,0)" stroke="none">User logs via </tspan></tspan><tspan class="TextPosition" x="10484" y="2567"><tspan fill="rgb(0,0,0)" stroke="none">Google/LDAP etc</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.ConnectorShape">
+       <g id="id10">
+        <rect class="BoundingBox" stroke="none" fill="none" x="9833" y="1241" width="302" height="2807"/>
+        <path fill="none" stroke="rgb(0,0,0)" d="M 10001,1242 L 9983,3617"/>
+        <path fill="rgb(0,0,0)" stroke="none" d="M 9980,4047 L 10133,3598 9833,3596 9980,4047 Z"/>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.TextShape">
+       <g id="id11">
+        <rect class="BoundingBox" stroke="none" fill="none" x="10361" y="6460" width="2921" height="3023"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10611" y="7161"><tspan fill="rgb(0,0,0)" stroke="none">2. setup</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.TextShape">
+       <g id="id12">
+        <rect class="BoundingBox" stroke="none" fill="none" x="10266" y="10270" width="3393" height="3023"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="10516" y="10971"><tspan fill="rgb(0,0,0)" stroke="none">4. activate</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.ConnectorShape">
+       <g id="id13">
+        <rect class="BoundingBox" stroke="none" fill="none" x="5111" y="4935" width="3126" height="7805"/>
+        <path fill="none" stroke="rgb(0,0,0)" d="M 8204,4936 C 4151,4936 4141,12106 7833,12593"/>
+        <path fill="rgb(0,0,0)" stroke="none" d="M 8236,12619 L 7797,12439 7777,12738 8236,12619 Z"/>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.TextShape">
+       <g id="id14">
+        <rect class="BoundingBox" stroke="none" fill="none" x="1543" y="7708" width="3390" height="2564"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="1793" y="8409"><tspan fill="rgb(0,0,0)" stroke="none">6. update </tspan></tspan></tspan><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="1793" y="9120"><tspan fill="rgb(0,0,0)" stroke="none">“</tspan><tspan fill="rgb(0,0,0)" stroke="none">is_active” </tspan></tspan><tspan class="TextPosition" x="1793" y="9831"><tspan fill="rgb(0,0,0)" stroke="none">to “true”</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.TextShape">
+       <g id="id15">
+        <rect class="BoundingBox" stroke="none" fill="none" x="15727" y="8165" width="3464" height="3023"/>
+        <text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="635px" font-weight="400"><tspan class="TextPosition" x="15977" y="8866"><tspan fill="rgb(0,0,0)" stroke="none">7. unsetup</tspan></tspan></tspan></text>
+       </g>
+      </g>
+      <g class="com.sun.star.drawing.ConnectorShape">
+       <g id="id16">
+        <rect class="BoundingBox" stroke="none" fill="none" x="5916" y="8872" width="2321" height="3869"/>
+        <path fill="none" stroke="rgb(0,0,0)" d="M 8074,8873 C 5278,8873 5211,12261 7812,12593"/>
+        <path fill="rgb(0,0,0)" stroke="none" d="M 8236,12619 L 7797,12440 7777,12740 8236,12619 Z"/>
+       </g>
+      </g>
+     </g>
+    </g>
+   </g>
+  </g>
+ </g>
+</svg>
\ No newline at end of file
deleted file mode 100644 (file)
index 562b76ddf0a01855d066f8c1c0724a78e33fcd71..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,75 +0,0 @@
----
-layout: default
-navsection: admin
-title: User management at the CLI
-...
-{% comment %}
-Copyright (C) The Arvados Authors. All rights reserved.
-
-SPDX-License-Identifier: CC-BY-SA-3.0
-{% endcomment %}
-
-h3. Workbench: user management
-
-As an Admin user, use the gear icon on the top right to visit the Users page. From there, use the 'Add new user' button to create a new user. Alternatively, visit an existing user with the 'Show' button next to the user's name. Then use the 'Admin' tab and click the 'Setup' button to activate the user, and create a virtual machine login as well as git repository for them.
-
-h3. CLI setup
-
-<pre>
-ARVADOS_API_HOST={{ site.arvados_api_host }}
-ARVADOS_API_TOKEN=1234567890qwertyuiopasdfghjklzxcvbnm1234567890zzzz
-</pre>
-
-h3. CLI: Create VM
-
-<pre>
-arv virtual_machine create --virtual-machine '{"hostname":"xxxxxxxchangeme.example.com"}'
-</pre>
-
-h3. CLI: Activate user
-
-<pre>
-user_uuid=xxxxxxxchangeme
-
-arv user update --uuid "$user_uuid" --user '{"is_active":true}'
-</pre>
-
-h3. User &rarr; VM
-
-Give @$user_uuid@ permission to log in to @$vm_uuid@ as @$target_username@
-
-<pre>
-user_uuid=xxxxxxxchangeme
-vm_uuid=xxxxxxxchangeme
-target_username=xxxxxxxchangeme
-
-read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
-{
-"tail_uuid":"$user_uuid",
-"head_uuid":"$vm_uuid",
-"link_class":"permission",
-"name":"can_login",
-"properties":{"username":"$target_username"}
-}
-EOF
-</pre>
-
-h3. CLI: User &rarr; repo
-
-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>
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..7917e28b3f6e1ecc8f36186160ea650f4a9a4f64
--- /dev/null
@@ -0,0 +1 @@
+../admin/user-management-cli.html.textile.liquid
\ No newline at end of file
index 81c36b9bfbe2b00626403cfde5cbdfd2894c34f9..98e6bd3720506232d1764979f46c45ea650a4b38 100644 (file)
@@ -275,6 +275,12 @@ Clusters:
       # in the directory where your API server is running.
       AnonymousUserToken: ""
 
+      # If a new user has an alternate email address (local@domain)
+      # with the domain given here, its local part becomes the new
+      # user's default username. Otherwise, the user's primary email
+      # address is used.
+      PreferDomainForUsername: ""
+
     AuditLogs:
       # Time to keep audit logs, in seconds. (An audit log is a row added
       # to the "logs" table in the PostgreSQL database each time an
@@ -519,7 +525,7 @@ Clusters:
 
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
-      # (login cluster must appear in RemoteHosts with Proxy: true)
+      # (login cluster must appear in RemoteClusters with Proxy: true)
       LoginCluster: ""
 
       # How long a cached token belonging to a remote cluster will
index 7adacab4c8df392ef35f57c6ecb6d1cbe8f7d749..413ff9578c3af7ca79e5cf20add2a0dc563c10d1 100644 (file)
@@ -167,6 +167,7 @@ var whitelist = map[string]bool{
        "Users.NewInactiveUserNotificationRecipients":  false,
        "Users.NewUserNotificationRecipients":          false,
        "Users.NewUsersAreActive":                      false,
+       "Users.PreferDomainForUsername":                false,
        "Users.UserNotifierEmailFrom":                  false,
        "Users.UserProfileNotificationAddress":         false,
        "Volumes":                                      true,
index 68dea169f843072aa33c4f6905e6651011c57fe4..ece3a627fd6a5b7ed2fe5179068b6a10ba20439f 100644 (file)
@@ -281,6 +281,12 @@ Clusters:
       # in the directory where your API server is running.
       AnonymousUserToken: ""
 
+      # If a new user has an alternate email address (local@domain)
+      # with the domain given here, its local part becomes the new
+      # user's default username. Otherwise, the user's primary email
+      # address is used.
+      PreferDomainForUsername: ""
+
     AuditLogs:
       # Time to keep audit logs, in seconds. (An audit log is a row added
       # to the "logs" table in the PostgreSQL database each time an
@@ -525,7 +531,7 @@ Clusters:
 
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
-      # (login cluster must appear in RemoteHosts with Proxy: true)
+      # (login cluster must appear in RemoteClusters with Proxy: true)
       LoginCluster: ""
 
       # How long a cached token belonging to a remote cluster will
index 1d8fa7e462cf96480adba03d7d814b1c246911c6..887102f8e58f4d659d3ed4c52b95d92a8e460003 100644 (file)
@@ -198,10 +198,13 @@ func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arva
                if err != nil {
                        return arvados.LoginResponse{}, fmt.Errorf("internal error getting redirect target: %s", err)
                }
-               target.RawQuery = url.Values{
+               params := url.Values{
                        "return_to": []string{options.ReturnTo},
-                       "remote":    []string{options.Remote},
-               }.Encode()
+               }
+               if options.Remote != "" {
+                       params.Set("remote", options.Remote)
+               }
+               target.RawQuery = params.Encode()
                return arvados.LoginResponse{
                        RedirectLocation: target.String(),
                }, nil
index 961cd5a401e16b0bdda33507496b1b6a2ef5b7b9..0a66644985662596c06f2d706035a8b2097a99de 100755 (executable)
@@ -8,6 +8,7 @@ import (
        "context"
        "sort"
        "sync"
+       "sync/atomic"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
 )
@@ -19,6 +20,8 @@ import (
 func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
        var mtx sync.Mutex
        var merged arvados.ContainerList
+       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) {
                cl, err := backend.ContainerList(ctx, options)
                if err != nil {
@@ -28,8 +31,9 @@ func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.L
                defer mtx.Unlock()
                if len(merged.Items) == 0 {
                        merged = cl
-               } else {
+               } 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 {
@@ -37,13 +41,27 @@ func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.L
                }
                return uuids, nil
        })
-       sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
+       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.Container{}
+       }
        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) {
                cl, err := backend.SpecimenList(ctx, options)
                if err != nil {
@@ -53,8 +71,9 @@ func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.Li
                defer mtx.Unlock()
                if len(merged.Items) == 0 {
                        merged = cl
-               } else {
+               } 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 {
@@ -62,7 +81,19 @@ func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.Li
                }
                return uuids, nil
        })
-       sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
+       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
 }
 
index 7178d7b0aff630bc28834af3042129b4a1e9983b..26b6b254e8e9fbdbb59638ca441412ade5575abb 100644 (file)
@@ -10,6 +10,7 @@ import (
        "net/http"
        "sort"
        "sync"
+       "sync/atomic"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
@@ -23,6 +24,8 @@ import (
 func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
        var mtx sync.Mutex
        var merged arvados.CollectionList
+       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) {
                cl, err := backend.CollectionList(ctx, options)
                if err != nil {
@@ -32,8 +35,9 @@ func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.
                defer mtx.Unlock()
                if len(merged.Items) == 0 {
                        merged = cl
-               } else {
+               } 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 {
@@ -41,7 +45,19 @@ func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.
                }
                return uuids, nil
        })
-       sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
+       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.Collection{}
+       }
        return merged, err
 }
 
index 5a630a9450ef861ed7c91c5f001f3fa642378638..a9c4f588f12a38b017d1c89b2995263a8c12d3d6 100644 (file)
@@ -8,6 +8,7 @@ import (
        "context"
        "fmt"
        "net/http"
+       "sort"
 
        "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
@@ -365,10 +366,13 @@ func (s *CollectionListSuite) test(c *check.C, trial listTrial) {
                c.Logf("returned error string is %q", err)
        } else {
                c.Check(err, check.IsNil)
-               var expectItems []arvados.Collection
+               expectItems := []arvados.Collection{}
                for _, uuid := range trial.expectUUIDs {
                        expectItems = append(expectItems, arvados.Collection{UUID: uuid})
                }
+               // expectItems is sorted by UUID, so sort resp.Items
+               // by UUID before checking DeepEquals.
+               sort.Slice(resp.Items, func(i, j int) bool { return resp.Items[i].UUID < resp.Items[j].UUID })
                c.Check(resp, check.DeepEquals, arvados.CollectionList{
                        Items: expectItems,
                })
index f83f5fb9359bd0765a84dc86cde1f94b35ab9655..8ec2bd5a4910db98d04d5d042453371d78455870 100644 (file)
@@ -32,7 +32,9 @@ func (s *LoginSuite) TestDeferToLoginCluster(c *check.C) {
                c.Check(err, check.IsNil)
                c.Check(target.Host, check.Equals, s.cluster.RemoteClusters["zhome"].Host)
                c.Check(target.Scheme, check.Equals, "http")
-               c.Check(target.Query().Get("remote"), check.Equals, remote)
                c.Check(target.Query().Get("return_to"), check.Equals, returnTo)
+               c.Check(target.Query().Get("remote"), check.Equals, remote)
+               _, remotePresent := target.Query()["remote"]
+               c.Check(remotePresent, check.Equals, remote != "")
        }
 }
index 13ae366eb434bded6dd1a4dc034a7c843a5fe86d..dc634e8d8f6fdba47989ce81b8b3bee59741bcd9 100644 (file)
@@ -207,6 +207,9 @@ func (ctrl *googleLoginController) getAuthInfo(ctx context.Context, cluster *arv
        for ae := range altEmails {
                if ae != ret.Email {
                        ret.AlternateEmails = append(ret.AlternateEmails, ae)
+                       if i := strings.Index(ae, "@"); i > 0 && strings.ToLower(ae[i+1:]) == strings.ToLower(cluster.Users.PreferDomainForUsername) {
+                               ret.Username = strings.SplitN(ae[:i], "+", 2)[0]
+                       }
                }
        }
        return &ret, nil
index c5b9ee06832cd663b10893ed3858a3a901437370..d409a21a999aff07915df26c2dc0b4d6c5237bd3 100644 (file)
@@ -148,6 +148,7 @@ func (s *LoginSuite) SetUpTest(c *check.C) {
        s.cluster, err = cfg.GetCluster("")
        s.cluster.Login.GoogleClientID = "test%client$id"
        s.cluster.Login.GoogleClientSecret = "test#client/secret"
+       s.cluster.Users.PreferDomainForUsername = "PreferDomainForUsername.example.com"
        c.Assert(err, check.IsNil)
 
        s.localdb = NewConn(s.cluster)
@@ -364,6 +365,10 @@ func (s *LoginSuite) TestGoogleLogin_AlternateEmailAddresses_Primary(c *check.C)
                                "metadata": map[string]interface{}{"verified": true},
                                "value":    "joe.smith@alternate.example.com",
                        },
+                       {
+                               "metadata": map[string]interface{}{"verified": true},
+                               "value":    "jsmith+123@preferdomainforusername.example.com",
+                       },
                },
        }
        state := s.startLogin(c)
@@ -373,7 +378,8 @@ func (s *LoginSuite) TestGoogleLogin_AlternateEmailAddresses_Primary(c *check.C)
        })
        authinfo := s.getCallbackAuthInfo(c)
        c.Check(authinfo.Email, check.Equals, "joe.smith@primary.example.com")
-       c.Check(authinfo.AlternateEmails, check.DeepEquals, []string{"joe.smith@alternate.example.com"})
+       c.Check(authinfo.AlternateEmails, check.DeepEquals, []string{"joe.smith@alternate.example.com", "jsmith+123@preferdomainforusername.example.com"})
+       c.Check(authinfo.Username, check.Equals, "jsmith")
 }
 
 func (s *LoginSuite) TestGoogleLogin_NoPrimaryEmailAddress(c *check.C) {
@@ -400,6 +406,7 @@ func (s *LoginSuite) TestGoogleLogin_NoPrimaryEmailAddress(c *check.C) {
        authinfo := s.getCallbackAuthInfo(c)
        c.Check(authinfo.Email, check.Equals, "joe.smith@work.example.com") // first verified email in People response
        c.Check(authinfo.AlternateEmails, check.DeepEquals, []string{"joe.smith@home.example.com"})
+       c.Check(authinfo.Username, check.Equals, "")
 }
 
 func (s *LoginSuite) getCallbackAuthInfo(c *check.C) (authinfo rpc.UserSessionAuthInfo) {
index 6a9fd311ba2b91cd66b03aa4dc7013eb878ea665..b1bc9bce32b202548942dd3869f3ed2073ddaa85 100644 (file)
@@ -225,6 +225,13 @@ func (s *RouterIntegrationSuite) TestContainerList(c *check.C) {
        c.Check(rr.Code, check.Equals, http.StatusOK)
        c.Check(jresp["items_available"], check.FitsTypeOf, float64(0))
        c.Check(jresp["items_available"].(float64) > 2, check.Equals, true)
+       c.Check(jresp["items"], check.NotNil)
+       c.Check(jresp["items"], check.HasLen, 0)
+
+       _, rr, jresp = doRequest(c, s.rtr, token, "GET", `/arvados/v1/containers?filters=[["uuid","in",[]]]`, nil, nil)
+       c.Check(rr.Code, check.Equals, http.StatusOK)
+       c.Check(jresp["items_available"], check.Equals, float64(0))
+       c.Check(jresp["items"], check.NotNil)
        c.Check(jresp["items"], check.HasLen, 0)
 
        _, rr, jresp = doRequest(c, s.rtr, token, "GET", `/arvados/v1/containers?limit=2&select=["uuid","command"]`, nil, nil)
index 66523e5ac3203215de84795f76db632083451372..f4bc1733eaff7112caf4bdfc7c69f62ddae5c4a8 100644 (file)
@@ -393,6 +393,7 @@ type UserSessionAuthInfo struct {
        AlternateEmails []string `json:"alternate_emails"`
        FirstName       string   `json:"first_name"`
        LastName        string   `json:"last_name"`
+       Username        string   `json:"username"`
 }
 
 type UserSessionCreateOptions struct {
index 50e73189efbc854433f8713e0a7762efafc0fe70..f999ee80ad49b7d2801307362255822e6430fd1f 100644 (file)
@@ -133,7 +133,7 @@ func (cq *Queue) Forget(uuid string) {
        cq.mtx.Lock()
        defer cq.mtx.Unlock()
        ctr := cq.current[uuid].Container
-       if ctr.State == arvados.ContainerStateComplete || ctr.State == arvados.ContainerStateCancelled {
+       if ctr.State == arvados.ContainerStateComplete || ctr.State == arvados.ContainerStateCancelled || (ctr.State == arvados.ContainerStateQueued && ctr.Priority == 0) {
                cq.delEnt(uuid, ctr.State)
        }
 }
index 205ee5018710a47749e276f73985ceb3b1b1af01..e306db00ceacbdbf5fe35bdcece9d9673069a1c8 100644 (file)
@@ -51,7 +51,7 @@ func (sch *Scheduler) sync() {
                                sch.logger.WithFields(logrus.Fields{
                                        "ContainerUUID": uuid,
                                        "State":         ent.Container.State,
-                               }).Info("container finished")
+                               }).Info("container finished -- dropping from queue")
                                sch.queue.Forget(uuid)
                        }
                case arvados.ContainerStateQueued:
@@ -66,7 +66,7 @@ func (sch *Scheduler) sync() {
                                        "ContainerUUID": uuid,
                                        "State":         ent.Container.State,
                                        "Priority":      ent.Container.Priority,
-                               }).Info("container on hold")
+                               }).Info("container on hold -- dropping from queue")
                                sch.queue.Forget(uuid)
                        }
                case arvados.ContainerStateLocked:
index 81b542057c2d780f4e2eb1a87f64bacb25ecf7bf..5872dbef5a8ef429f44da0696881bb8272e74e3f 100644 (file)
@@ -27,6 +27,12 @@ inputs:
     default:
       class: File
       location: ../../../../tools/arvbox/bin/arvbox
+  branch:
+    type: string
+    default: master
+  logincluster:
+    type: boolean
+    default: false
 outputs:
   arvados_api_token:
     type: string
@@ -64,6 +70,7 @@ steps:
       container_name: containers
       arvbox_data: mkdir/arvbox_data
       arvbox_bin: arvbox
+      branch: branch
     out: [cluster_id, container_host, arvbox_data_out, superuser_token]
     scatter: [container_name, arvbox_data]
     scatterMethod: dotproduct
@@ -76,6 +83,7 @@ steps:
       cluster_hosts: start/container_host
       arvbox_data: start/arvbox_data_out
       arvbox_bin: arvbox
+      logincluster: logincluster
     out: []
     scatter: [container_name, this_cluster_id, arvbox_data]
     scatterMethod: dotproduct
index 76523a56befed127cebd920d9f9b594f85baafb1..37936df6351b6d86e1dfe6619ab19ddc395b3a73 100644 (file)
@@ -14,6 +14,9 @@ inputs:
   cluster_hosts: string[]
   arvbox_data: Directory
   arvbox_bin: File
+  logincluster:
+    type: boolean
+    default: false
 outputs:
   arvbox_data_out:
     type: Directory
@@ -39,6 +42,9 @@ requirements:
           }
           var r = {"Clusters": {}};
           r["Clusters"][inputs.this_cluster_id] = {"RemoteClusters": remoteClusters};
+          if (r["Clusters"][inputs.this_cluster_id]) {
+            r["Clusters"][inputs.this_cluster_id]["Login"] = {"LoginCluster": inputs.cluster_ids[0]};
+          }
           return JSON.stringify(r);
           }
       - entryname: application.yml.override
index a0b3e1864ba8f7d1418f1b396e908aee15079154..d26a6b28ec8b82f08c4034e09a0688b9a6247398 100644 (file)
@@ -11,6 +11,9 @@ inputs:
   container_name: string
   arvbox_data: Directory
   arvbox_bin: File
+  branch:
+    type: string
+    default: master
 outputs:
   cluster_id:
     type: string
@@ -68,6 +71,24 @@ arguments:
   - shellQuote: false
     valueFrom: |
       set -ex
-      $(inputs.arvbox_bin.path) start dev
+      mkdir -p $ARVBOX_DATA
+      if ! test -d $ARVBOX_DATA/arvados ; then
+        cd $ARVBOX_DATA
+        git clone https://github.com/curoverse/arvados.git
+      fi
+      cd $ARVBOX_DATA/arvados
+      gitver=`git rev-parse HEAD`
+      git fetch
+      git checkout -f $(inputs.branch)
+      git pull
+      pulled=`git rev-parse HEAD`
+      git --no-pager log -n1 $pulled
+
+      cd $(runtime.outdir)
+      if test "$gitver" = "$pulled" ; then
+        $(inputs.arvbox_bin.path) start dev
+      else
+        $(inputs.arvbox_bin.path) restart dev
+      fi
       $(inputs.arvbox_bin.path) status > status.txt
       $(inputs.arvbox_bin.path) cat /var/lib/arvados/superuser_token > superuser_token.txt
index 5b919bea74a325173109c5f2cc12a56053116f56..e8b0f9cc986b7219cac645c3b8e2771abf0f3bb4 100644 (file)
@@ -22,15 +22,17 @@ type Collection struct {
        ManifestText              string                 `json:"manifest_text"`
        UnsignedManifestText      string                 `json:"unsigned_manifest_text"`
        Name                      string                 `json:"name"`
-       CreatedAt                 *time.Time             `json:"created_at"`
-       ModifiedAt                *time.Time             `json:"modified_at"`
+       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"`
        PortableDataHash          string                 `json:"portable_data_hash"`
        ReplicationConfirmed      *int                   `json:"replication_confirmed"`
        ReplicationConfirmedAt    *time.Time             `json:"replication_confirmed_at"`
        ReplicationDesired        *int                   `json:"replication_desired"`
        StorageClassesDesired     []string               `json:"storage_classes_desired"`
        StorageClassesConfirmed   []string               `json:"storage_classes_confirmed"`
-       StorageClassesConfirmedAt *time.Time             `json:"storage_classes_confirmed_at"`
+       StorageClassesConfirmedAt time.Time              `json:"storage_classes_confirmed_at"`
        DeleteAt                  *time.Time             `json:"delete_at"`
        IsTrashed                 bool                   `json:"is_trashed"`
        Properties                map[string]interface{} `json:"properties"`
index 805efb7db287d61a9049cf0abf69ac61236989d4..72128a9dcd385d5d7274567bde7c836417af09f3 100644 (file)
@@ -174,6 +174,7 @@ type Cluster struct {
                NewUsersAreActive                     bool
                UserNotifierEmailFrom                 string
                UserProfileNotificationAddress        string
+               PreferDomainForUsername               string
        }
        Volumes   map[string]Volume
        Workbench struct {
index fb095481bb07b2aa97489a17347e56c65166b356..1d3b0962f7da1ff93d386f067195b977ef02de10 100644 (file)
@@ -10,6 +10,9 @@ import "time"
 type Container struct {
        UUID                 string                 `json:"uuid"`
        CreatedAt            time.Time              `json:"created_at"`
+       ModifiedByClientUUID string                 `json:"modified_by_client_uuid"`
+       ModifiedByUserUUID   string                 `json:"modified_by_user_uuid"`
+       ModifiedAt           time.Time              `json:"modified_at"`
        Command              []string               `json:"command"`
        ContainerImage       string                 `json:"container_image"`
        Cwd                  string                 `json:"cwd"`
index b3e6aa96e46473a11a6a839aa1a3e35693b25cdb..40c8908024a88f87f8979d7dff653cbcb42a2239 100644 (file)
@@ -49,11 +49,9 @@ type collectionFileSystem struct {
 
 // FileSystem returns a CollectionFileSystem for the collection.
 func (c *Collection) FileSystem(client apiClient, kc keepClient) (CollectionFileSystem, error) {
-       var modTime time.Time
-       if c.ModifiedAt == nil {
+       modTime := c.ModifiedAt
+       if modTime.IsZero() {
                modTime = time.Now()
-       } else {
-               modTime = *c.ModifiedAt
        }
        fs := &collectionFileSystem{
                uuid: c.UUID,
index a84f64fe7e254334a00a42226d0003f682d53684..439eaec7c2a5dbde49f2fd2851551238a22166ec 100644 (file)
@@ -12,10 +12,8 @@ import (
 )
 
 func deferredCollectionFS(fs FileSystem, parent inode, coll Collection) inode {
-       var modTime time.Time
-       if coll.ModifiedAt != nil {
-               modTime = *coll.ModifiedAt
-       } else {
+       modTime := coll.ModifiedAt
+       if modTime.IsZero() {
                modTime = time.Now()
        }
        placeholder := &treenode{
index e320ca2c3386fe0ff3e028340ffbe95972e1e274..b561fb20ae6c79e89f4bede1cb553560b1eec073 100644 (file)
@@ -7,12 +7,13 @@ 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"`
-       UpdatedAt  time.Time              `json:"updated_at"`
-       Properties map[string]interface{} `json:"properties"`
+       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 {
index 885d6fda03560fccb735216ff10b6c730dd1c1c2..e74d6215c79cb76af6fdab7170f3cd989a77143e 100755 (executable)
@@ -197,14 +197,17 @@ def choose_new_user(args, by_email, email, userhome, username, old_user_uuid, cl
             return None
         print("(%s) No user listed with same email to migrate %s to %s, will create new user with username '%s'" % (email, old_user_uuid, userhome, username))
         if not args.dry_run:
+            oldhomecluster = old_user_uuid[0:5]
+            oldhomearv = clusters[oldhomecluster]
             newhomecluster = userhome[0:5]
             homearv = clusters[userhome]
             user = None
             try:
+                olduser = oldhomearv.users().get(uuid=old_user_uuid).execute()
                 conflicts = homearv.users().list(filters=[["username", "=", username]]).execute()
                 if conflicts["items"]:
                     homearv.users().update(uuid=conflicts["items"][0]["uuid"], body={"user": {"username": username+"migrate"}}).execute()
-                user = homearv.users().create(body={"user": {"email": email, "username": username}}).execute()
+                user = homearv.users().create(body={"user": {"email": email, "username": username, "is_active": olduser["is_active"]}}).execute()
             except arvados.errors.ApiError as e:
                 print("(%s) Could not create user: %s" % (email, str(e)))
                 return None
diff --git a/sdk/python/tests/fed-migrate/CWLFile b/sdk/python/tests/fed-migrate/CWLFile
new file mode 100644 (file)
index 0000000..18b2ed8
--- /dev/null
@@ -0,0 +1,28 @@
+cwlVersion: v1.0
+class: Workflow
+requirements:
+  ScatterFeatureRequirement: {}
+inputs:
+  exfiles:
+    type: string[]
+    default:
+      - fed-migrate.cwlex
+      - run-test.cwlex
+  dir:
+    type: Directory
+    default:
+      class: Directory
+      location: .
+outputs:
+  out:
+    type: File[]
+    outputSource: step1/converted
+
+steps:
+  step1:
+    in:
+      inpdir: dir
+      inpfile: exfiles
+    out: [converted]
+    scatter: inpfile
+    run: cwlex.cwl
index 5057d4cb182ef7f16b56be264ec6b89511d0cbc1..0aa6f177aad0bdc272914fa4b52aea3f98aae7b1 100644 (file)
@@ -3,8 +3,12 @@ class: Workflow
 $namespaces:
   arv: "http://arvados.org/cwl#"
   cwltool: "http://commonwl.org/cwltool#"
+
 inputs:
   arvbox_base: Directory
+  branch:
+    type: string
+    default: master
 outputs:
   arvados_api_hosts:
     type: string[]
@@ -21,13 +25,21 @@ outputs:
   arvbox_bin:
     type: File
     outputSource: start/arvbox_bin
+  refspec:
+    type: string
+    outputSource: branch
 requirements:
   SubworkflowFeatureRequirement: {}
+  ScatterFeatureRequirement: {}
+  StepInputExpressionRequirement: {}
   cwltool:LoadListingRequirement:
     loadListing: no_listing
 steps:
   start:
     in:
       arvbox_base: arvbox_base
+      branch: branch
+      logincluster:
+        default: true
     out: [arvados_api_hosts, arvados_cluster_ids, arvado_api_host_insecure, superuser_tokens, arvbox_containers, arvbox_bin]
     run: ../../../cwl/tests/federation/arvbox-make-federation.cwl
index 8f494be2fb4448ed3135c05328b12f3d398f55a1..85d2d31f2309fe3182fc1d606982d1bea34e41c1 100644 (file)
@@ -8,6 +8,10 @@ apiA = arvados.api(host=j["arvados_api_hosts"][0], token=j["superuser_tokens"][0
 apiB = arvados.api(host=j["arvados_api_hosts"][1], token=j["superuser_tokens"][1], insecure=True)
 apiC = arvados.api(host=j["arvados_api_hosts"][2], token=j["superuser_tokens"][2], insecure=True)
 
+###
+### Check users on API server "A" (the LoginCluster) ###
+###
+
 users = apiA.users().list().execute()
 
 assert len(users["items"]) == 11
@@ -22,6 +26,15 @@ for i in range(1, 10):
             by_username[u["username"]] = u["uuid"]
     assert found
 
+# Should be active
+for i in (1, 2, 3, 4, 5, 6, 7, 8):
+    found = False
+    for u in users["items"]:
+        if u["username"] == ("case%d" % i) and u["email"] == ("case%d@test" % i) and u["is_active"] is True:
+            found = True
+    assert found, "Not found case%i" % i
+
+# case9 should not be active
 found = False
 for u in users["items"]:
     if (u["username"] == "case9" and u["email"] == "case9@test" and
@@ -29,23 +42,40 @@ for u in users["items"]:
         found = True
 assert found
 
+
+###
+### Check users on API server "B" (federation member) ###
+###
 users = apiB.users().list().execute()
 assert len(users["items"]) == 11
 
-for i in range(2, 10):
+for i in range(2, 9):
     found = False
     for u in users["items"]:
-        if u["username"] == ("case%d" % i) and u["email"] == ("case%d@test" % i) and u["uuid"] == by_username[u["username"]]:
+        if (u["username"] == ("case%d" % i) and u["email"] == ("case%d@test" % i) and
+            u["uuid"] == by_username[u["username"]] and u["is_active"] is True):
             found = True
-    assert found
+    assert found, "Not found case%i" % i
+
+found = False
+for u in users["items"]:
+    if (u["username"] == "case9" and u["email"] == "case9@test" and
+        u["uuid"] == by_username[u["username"]] and u["is_active"] is False):
+        found = True
+assert found
+
 
+###
+### Check users on API server "C" (federation member) ###
+###
 users = apiC.users().list().execute()
 assert len(users["items"]) == 8
 
 for i in (2, 4, 6, 7, 8):
     found = False
     for u in users["items"]:
-        if u["username"] == ("case%d" % i) and u["email"] == ("case%d@test" % i) and u["uuid"] == by_username[u["username"]]:
+        if (u["username"] == ("case%d" % i) and u["email"] == ("case%d@test" % i) and
+            u["uuid"] == by_username[u["username"]] and u["is_active"] is True):
             found = True
     assert found
 
@@ -54,7 +84,8 @@ for i in (2, 4, 6, 7, 8):
 for i in (3, 5, 9):
     found = False
     for u in users["items"]:
-        if u["username"] == ("case%d" % i) and u["email"] == ("case%d@test" % i) and u["uuid"] == by_username[u["username"]]:
+        if (u["username"] == ("case%d" % i) and u["email"] == ("case%d@test" % i) and
+            u["uuid"] == by_username[u["username"]] and u["is_active"] is True):
             found = True
     assert not found
 
diff --git a/sdk/python/tests/fed-migrate/cwlex.cwl b/sdk/python/tests/fed-migrate/cwlex.cwl
new file mode 100644 (file)
index 0000000..1e72fed
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/env cwl-runner
+arguments:
+  - cwlex
+  - '$(inputs.inp ? inputs.inp.path : inputs.inpdir.path+''/''+inputs.inpfile)'
+class: CommandLineTool
+cwlVersion: v1.0
+id: '#main'
+inputs:
+  - id: inp
+    type:
+      - 'null'
+      - File
+  - id: inpdir
+    type:
+      - 'null'
+      - Directory
+  - id: inpfile
+    type:
+      - 'null'
+      - string
+  - id: outname
+    type:
+      - 'null'
+      - string
+outputs:
+  - id: converted
+    outputBinding:
+      glob: $(outname(inputs))
+    type: File
+requirements:
+  - class: DockerRequirement
+    dockerPull: commonworkflowlanguage/cwlex
+  - class: InlineJavascriptRequirement
+    expressionLib:
+      - |
+
+        function outname(inputs) {
+          return inputs.outname ? inputs.outname : (inputs.inp ? inputs.inp.nameroot+'.cwl' : inputs.inpfile.replace(/(.*).cwlex/, '$1.cwl'));
+        }
+stdout: $(outname(inputs))
+
index 1c8fcca59e637c52933cff7ada6ee30e5c2d38a7..19c2b58ef7ca4aee10af8413155042eb418d56b1 100644 (file)
@@ -337,7 +337,7 @@ $graph:
         type: string
       - id: arvbox_bin
         type: File
-      - default: 15531-logincluster-migrate
+      - default: master
         id: refspec
         type: string
     outputs:
@@ -407,73 +407,15 @@ $graph:
               type: string
           outputs:
             - id: supertok
-              outputSource: superuser_tok_3/superuser_token
+              outputSource: superuser_tok_2/superuser_token
               type: string
           requirements:
-            - class: EnvVarRequirement
-              envDef:
-                ARVBOX_CONTAINER: $(inputs.container)
+            InlineJavascriptRequirement: {}
           steps:
             - id: main_2_embed_1
-              in:
-                cluster_id:
-                  source: cluster_id
-                container:
-                  source: container
-                logincluster:
-                  source: logincluster
-                set_login:
-                  default:
-                    class: File
-                    location: set_login.py
-              out:
-                - c
-              run:
-                arguments:
-                  - sh
-                  - _script
-                class: CommandLineTool
-                id: main_2_embed_1_embed
-                inputs:
-                  - id: container
-                    type: string
-                  - id: cluster_id
-                    type: string
-                  - id: logincluster
-                    type: string
-                  - id: set_login
-                    type: File
-                outputs:
-                  - id: c
-                    outputBinding:
-                      outputEval: $(inputs.container)
-                    type: string
-                requirements:
-                  InitialWorkDirRequirement:
-                    listing:
-                      - entry: >
-                          set -x
-
-                          docker cp
-                          $(inputs.container):/var/lib/arvados/cluster_config.yml.override
-                          .
-
-                          chmod +w cluster_config.yml.override
-
-                          python $(inputs.set_login.path)
-                          cluster_config.yml.override $(inputs.cluster_id)
-                          $(inputs.logincluster)
-
-                          docker cp cluster_config.yml.override
-                          $(inputs.container):/var/lib/arvados
-                        entryname: _script
-                  InlineJavascriptRequirement: {}
-            - id: main_2_embed_2
               in:
                 arvbox_bin:
                   source: arvbox_bin
-                c:
-                  source: main_2_embed_1/c
                 container:
                   source: container
                 host:
@@ -487,7 +429,7 @@ $graph:
                   - sh
                   - _script
                 class: CommandLineTool
-                id: main_2_embed_2_embed
+                id: main_2_embed_1_embed
                 inputs:
                   - id: container
                     type: string
@@ -495,21 +437,21 @@ $graph:
                     type: string
                   - id: arvbox_bin
                     type: File
-                  - id: c
-                    type: string
                   - id: refspec
                     type: string
                 outputs:
                   - id: d
                     outputBinding:
-                      outputEval: $(inputs.c)
+                      outputEval: $(inputs.container)
                     type: string
                 requirements:
                   InitialWorkDirRequirement:
                     listing:
-                      - entry: >
+                      - entry: >+
                           set -xe
 
+                          export ARVBOX_CONTAINER="$(inputs.container)"
+
                           $(inputs.arvbox_bin.path) pipe <<EOF
 
                           cd /usr/src/arvados
@@ -532,27 +474,26 @@ $graph:
                           https://$(inputs.host)/discovery/v1/apis/arvados/v1/rest
                           >/dev/null ; do sleep 3 ; done
 
-                          export ARVADOS_API_HOST=$(inputs.host)
-
-                          export ARVADOS_API_TOKEN=\$($(inputs.arvbox_bin.path)
-                          cat /var/lib/arvados/superuser_token)
-
-                          export ARVADOS_API_HOST_INSECURE=1
 
                           ARVADOS_VIRTUAL_MACHINE_UUID=\$($(inputs.arvbox_bin.path)
                           cat /var/lib/arvados/vm-uuid)
 
-                          while ! python -c "import arvados ;
-                          arvados.api().virtual_machines().get(uuid='$ARVADOS_VIRTUAL_MACHINE_UUID').execute()"
-                          2>/dev/null ; do sleep 3; done
+                          ARVADOS_API_TOKEN=\$($(inputs.arvbox_bin.path) cat
+                          /var/lib/arvados/superuser_token)
+
+                          while ! curl --fail --insecure --silent -H
+                          "Authorization: Bearer $ARVADOS_API_TOKEN"
+                          https://$(inputs.host)/arvados/v1/virtual_machines/$ARVADOS_VIRTUAL_MACHINE_UUID
+                          >/dev/null ; do sleep 3 ; done
+
                         entryname: _script
                   InlineJavascriptRequirement: {}
-            - id: superuser_tok_3
+            - id: superuser_tok_2
               in:
                 container:
                   source: container
                 d:
-                  source: main_2_embed_2/d
+                  source: main_2_embed_1/d
               out:
                 - superuser_token
               run: '#superuser_tok'
index 22bc95a83390084d0dc0fdd3f33a5e462a33aac9..e0beaa91d6f47c3ef648c71abe8f02d8f48629fa 100644 (file)
@@ -8,7 +8,7 @@ def workflow main(
   arvbox_containers string[],
   fed_migrate="arv-federation-migrate",
   arvbox_bin File,
-  refspec="15531-logincluster-migrate"
+  refspec="master"
 ) {
 
   logincluster = run expr (arvados_cluster_ids) string (inputs.arvados_cluster_ids[0])
@@ -18,27 +18,10 @@ def workflow main(
          arvados_api_hosts as host
     do run workflow(logincluster, arvbox_bin, refspec)
   {
-    requirements {
-      EnvVarRequirement {
-        envDef: {
-          ARVBOX_CONTAINER: "$(inputs.container)"
-        }
-      }
-    }
-
-    run tool(container, cluster_id, logincluster, set_login = File("set_login.py")) {
-sh <<<
-set -x
-docker cp $(inputs.container):/var/lib/arvados/cluster_config.yml.override .
-chmod +w cluster_config.yml.override
-python $(inputs.set_login.path) cluster_config.yml.override $(inputs.cluster_id) $(inputs.logincluster)
-docker cp cluster_config.yml.override $(inputs.container):/var/lib/arvados
->>>
-      return container as c
-    }
-    run tool(container, host, arvbox_bin, c, refspec) {
+    run tool(container, host, arvbox_bin, refspec) {
 sh <<<
 set -xe
+export ARVBOX_CONTAINER="$(inputs.container)"
 $(inputs.arvbox_bin.path) pipe <<EOF
 cd /usr/src/arvados
 git fetch
@@ -50,13 +33,13 @@ EOF
 $(inputs.arvbox_bin.path) hotreset
 
 while ! curl --fail --insecure --silent https://$(inputs.host)/discovery/v1/apis/arvados/v1/rest >/dev/null ; do sleep 3 ; done
-export ARVADOS_API_HOST=$(inputs.host)
-export ARVADOS_API_TOKEN=\$($(inputs.arvbox_bin.path) cat /var/lib/arvados/superuser_token)
-export ARVADOS_API_HOST_INSECURE=1
+
 ARVADOS_VIRTUAL_MACHINE_UUID=\$($(inputs.arvbox_bin.path) cat /var/lib/arvados/vm-uuid)
-while ! python -c "import arvados ; arvados.api().virtual_machines().get(uuid='$ARVADOS_VIRTUAL_MACHINE_UUID').execute()" 2>/dev/null ; do sleep 3; done
+ARVADOS_API_TOKEN=\$($(inputs.arvbox_bin.path) cat /var/lib/arvados/superuser_token)
+while ! curl --fail --insecure --silent -H "Authorization: Bearer $ARVADOS_API_TOKEN" https://$(inputs.host)/arvados/v1/virtual_machines/$ARVADOS_VIRTUAL_MACHINE_UUID >/dev/null ; do sleep 3 ; done
+
 >>>
-      return c as d
+      return container as d
     }
     supertok = superuser_tok(container, d)
     return supertok
diff --git a/sdk/python/tests/fed-migrate/set_login.py b/sdk/python/tests/fed-migrate/set_login.py
deleted file mode 100644 (file)
index 2900af1..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-import json
-import sys
-
-f = open(sys.argv[1], "r+")
-j = json.load(f)
-j["Clusters"][sys.argv[2]]["Login"] = {"LoginCluster": sys.argv[3]}
-for r in j["Clusters"][sys.argv[2]]["RemoteClusters"]:
-    j["Clusters"][sys.argv[2]]["RemoteClusters"][r]["Insecure"] = True
-f.seek(0)
-json.dump(j, f)
index 5ebdff0ca725e5f05aab1b496bec21165427666a..c4bd33fda6136bbf241cc3fb702684be58636ab4 100644 (file)
@@ -3,7 +3,7 @@ GIT
   revision: dd9f2403f43bcb93da5908ddde57d8c0491bb4c2
   glob: sdk/*/*.gemspec
   specs:
-    arvados (1.4.1.20191019025325)
+    arvados (1.4.2.20191019025325)
       activesupport (>= 3)
       andand (~> 1.3, >= 1.3.3)
       arvados-google-api-client (>= 0.7, < 0.8.9)
@@ -11,7 +11,7 @@ GIT
       i18n (~> 0)
       json (>= 1.7.7, < 3)
       jwt (>= 0.1.5, < 2)
-    arvados-cli (1.4.1.20191017145711)
+    arvados-cli (1.4.2.20191017145711)
       activesupport (>= 3.2.13, < 5.1)
       andand (~> 1.3, >= 1.3.3)
       arvados (>= 1.4.1.20190320201707)
@@ -334,4 +334,4 @@ DEPENDENCIES
   uglifier (~> 2.0)
 
 BUNDLED WITH
-   1.17.3
+   2.0.2
index f93312693307c39f1c39a85694cfe51011ecdc09..946c4262e3a0eeeb1297cf51f1fba9ab183a1d96 100644 (file)
@@ -457,6 +457,9 @@ class ArvadosModel < ApplicationRecord
     if not ft[:cond_out].any?
       return query
     end
+    ft[:joins].each do |t|
+      query = query.joins(t)
+    end
     query.where('(' + ft[:cond_out].join(') AND (') + ')',
                           *ft[:param_out])
   end
index 7a3a854b3a17826117a6ff913ff5f20743f86483..a49aa6f56a22ead2398198401c15be2a8860ec97 100644 (file)
@@ -435,7 +435,7 @@ class User < ArvadosModel
                               :is_admin => false,
                               :is_active => Rails.configuration.Users.NewUsersAreActive)
 
-      primary_user.set_initial_username(requested: info['username']) if info['username']
+      primary_user.set_initial_username(requested: info['username']) if info['username'] && !info['username'].blank?
       primary_user.identity_url = info['identity_url'] if identity_url
     end
 
index c8f024291c2c677c086445b294658a3c221211c0..994e8503106ad6ec3e931e555f2f28d8d455a1da 100644 (file)
@@ -21,12 +21,14 @@ module RecordFilters
   # Output:
   # Hash with two keys:
   # :cond_out  array of SQL fragments for each filter expression
-  # :param_out  array of values for parameter substitution in cond_out
+  # :param_out array of values for parameter substitution in cond_out
+  # :joins     array of joins: either [] or ["JOIN containers ON ..."]
   def record_filters filters, model_class
     conds_out = []
     param_out = []
+    joins = []
 
-    ar_table_name = model_class.table_name
+    model_table_name = model_class.table_name
     filters.each do |filter|
       attrs_in, operator, operand = filter
       if attrs_in == 'any' && operator != '@@'
@@ -71,78 +73,91 @@ module RecordFilters
       attrs.each do |attr|
         subproperty = attr.split(".", 2)
 
-        col = model_class.columns.select { |c| c.name == subproperty[0] }.first
+        if subproperty.length == 2 && subproperty[0] == 'container' && model_table_name == "container_requests"
+          # attr is "tablename.colname" -- e.g., ["container.state", "=", "Complete"]
+          joins = ["JOIN containers ON container_requests.container_uuid = containers.uuid"]
+          attr_model_class = Container
+          attr_table_name = "containers"
+          subproperty = subproperty[1].split(".", 2)
+        else
+          attr_model_class = model_class
+          attr_table_name = model_table_name
+        end
+
+        attr = subproperty[0]
+        proppath = subproperty[1]
+        col = attr_model_class.columns.select { |c| c.name == attr }.first
 
-        if subproperty.length == 2
+        if proppath
           if col.nil? or col.type != :jsonb
-            raise ArgumentError.new("Invalid attribute '#{subproperty[0]}' for subproperty filter")
+            raise ArgumentError.new("Invalid attribute '#{attr}' for subproperty filter")
           end
 
-          if subproperty[1][0] == "<" and subproperty[1][-1] == ">"
-            subproperty[1] = subproperty[1][1..-2]
+          if proppath[0] == "<" and proppath[-1] == ">"
+            proppath = proppath[1..-2]
           end
 
           # jsonb search
           case operator.downcase
           when '=', '!='
             not_in = if operator.downcase == "!=" then "NOT " else "" end
-            cond_out << "#{not_in}(#{ar_table_name}.#{subproperty[0]} @> ?::jsonb)"
-            param_out << SafeJSON.dump({subproperty[1] => operand})
+            cond_out << "#{not_in}(#{attr_table_name}.#{attr} @> ?::jsonb)"
+            param_out << SafeJSON.dump({proppath => operand})
           when 'in'
             if operand.is_a? Array
               operand.each do |opr|
-                cond_out << "#{ar_table_name}.#{subproperty[0]} @> ?::jsonb"
-                param_out << SafeJSON.dump({subproperty[1] => opr})
+                cond_out << "#{attr_table_name}.#{attr} @> ?::jsonb"
+                param_out << SafeJSON.dump({proppath => opr})
               end
             else
               raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
                                       "for '#{operator}' operator in filters")
             end
           when '<', '<=', '>', '>='
-            cond_out << "#{ar_table_name}.#{subproperty[0]}->? #{operator} ?::jsonb"
-            param_out << subproperty[1]
+            cond_out << "#{attr_table_name}.#{attr}->? #{operator} ?::jsonb"
+            param_out << proppath
             param_out << SafeJSON.dump(operand)
           when 'like', 'ilike'
-            cond_out << "#{ar_table_name}.#{subproperty[0]}->>? #{operator} ?"
-            param_out << subproperty[1]
+            cond_out << "#{attr_table_name}.#{attr}->>? #{operator} ?"
+            param_out << proppath
             param_out << operand
           when 'not in'
             if operand.is_a? Array
-              cond_out << "#{ar_table_name}.#{subproperty[0]}->>? NOT IN (?) OR #{ar_table_name}.#{subproperty[0]}->>? IS NULL"
-              param_out << subproperty[1]
+              cond_out << "#{attr_table_name}.#{attr}->>? NOT IN (?) OR #{attr_table_name}.#{attr}->>? IS NULL"
+              param_out << proppath
               param_out << operand
-              param_out << subproperty[1]
+              param_out << proppath
             else
               raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
                                       "for '#{operator}' operator in filters")
             end
           when 'exists'
             if operand == true
-              cond_out << "jsonb_exists(#{ar_table_name}.#{subproperty[0]}, ?)"
+              cond_out << "jsonb_exists(#{attr_table_name}.#{attr}, ?)"
             elsif operand == false
-              cond_out << "(NOT jsonb_exists(#{ar_table_name}.#{subproperty[0]}, ?)) OR #{ar_table_name}.#{subproperty[0]} is NULL"
+              cond_out << "(NOT jsonb_exists(#{attr_table_name}.#{attr}, ?)) OR #{attr_table_name}.#{attr} is NULL"
             else
               raise ArgumentError.new("Invalid operand '#{operand}' for '#{operator}' must be true or false")
             end
-            param_out << subproperty[1]
+            param_out << proppath
           else
             raise ArgumentError.new("Invalid operator for subproperty search '#{operator}'")
           end
         elsif operator.downcase == "exists"
           if col.type != :jsonb
-            raise ArgumentError.new("Invalid attribute '#{subproperty[0]}' for operator '#{operator}' in filter")
+            raise ArgumentError.new("Invalid attribute '#{attr}' for operator '#{operator}' in filter")
           end
 
-          cond_out << "jsonb_exists(#{ar_table_name}.#{subproperty[0]}, ?)"
+          cond_out << "jsonb_exists(#{attr_table_name}.#{attr}, ?)"
           param_out << operand
         else
-          if !model_class.searchable_columns(operator).index subproperty[0]
-            raise ArgumentError.new("Invalid attribute '#{subproperty[0]}' in filter")
+          if !attr_model_class.searchable_columns(operator).index attr
+            raise ArgumentError.new("Invalid attribute '#{attr}' in filter")
           end
 
           case operator.downcase
           when '=', '<', '<=', '>', '>=', '!=', 'like', 'ilike'
-            attr_type = model_class.attribute_column(attr).type
+            attr_type = attr_model_class.attribute_column(attr).type
             operator = '<>' if operator == '!='
             if operand.is_a? String
               if attr_type == :boolean
@@ -162,9 +177,9 @@ module RecordFilters
               end
               if operator == '<>'
                 # explicitly allow NULL
-                cond_out << "#{ar_table_name}.#{attr} #{operator} ? OR #{ar_table_name}.#{attr} IS NULL"
+                cond_out << "#{attr_table_name}.#{attr} #{operator} ? OR #{attr_table_name}.#{attr} IS NULL"
               else
-                cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+                cond_out << "#{attr_table_name}.#{attr} #{operator} ?"
               end
               if (# any operator that operates on value rather than
                 # representation:
@@ -173,15 +188,15 @@ module RecordFilters
               end
               param_out << operand
             elsif operand.nil? and operator == '='
-              cond_out << "#{ar_table_name}.#{attr} is null"
+              cond_out << "#{attr_table_name}.#{attr} is null"
             elsif operand.nil? and operator == '<>'
-              cond_out << "#{ar_table_name}.#{attr} is not null"
+              cond_out << "#{attr_table_name}.#{attr} is not null"
             elsif (attr_type == :boolean) and ['=', '<>'].include?(operator) and
                  [true, false].include?(operand)
-              cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+              cond_out << "#{attr_table_name}.#{attr} #{operator} ?"
               param_out << operand
             elsif (attr_type == :integer)
-              cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
+              cond_out << "#{attr_table_name}.#{attr} #{operator} ?"
               param_out << operand
             else
               raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
@@ -189,11 +204,11 @@ module RecordFilters
             end
           when 'in', 'not in'
             if operand.is_a? Array
-              cond_out << "#{ar_table_name}.#{attr} #{operator} (?)"
+              cond_out << "#{attr_table_name}.#{attr} #{operator} (?)"
               param_out << operand
               if operator == 'not in' and not operand.include?(nil)
                 # explicitly allow NULL
-                cond_out[-1] = "(#{cond_out[-1]} OR #{ar_table_name}.#{attr} IS NULL)"
+                cond_out[-1] = "(#{cond_out[-1]} OR #{attr_table_name}.#{attr} IS NULL)"
               end
             else
               raise ArgumentError.new("Invalid operand type '#{operand.class}' "\
@@ -206,14 +221,14 @@ module RecordFilters
               cl = ArvadosModel::kind_class op
               if cl
                 if attr == 'uuid'
-                  if model_class.uuid_prefix == cl.uuid_prefix
+                  if attr_model_class.uuid_prefix == cl.uuid_prefix
                     cond << "1=1"
                   else
                     cond << "1=0"
                   end
                 else
                   # Use a substring query to support remote uuids
-                  cond << "substring(#{ar_table_name}.#{attr}, 7, 5) = ?"
+                  cond << "substring(#{attr_table_name}.#{attr}, 7, 5) = ?"
                   param_out << cl.uuid_prefix
                 end
               else
@@ -229,7 +244,7 @@ module RecordFilters
       conds_out << cond_out.join(' OR ') if cond_out.any?
     end
 
-    {:cond_out => conds_out, :param_out => param_out}
+    {:cond_out => conds_out, :param_out => param_out, :joins => joins}
   end
 
 end
index e77e2ed3c6bdc11ac7f6fcd3ad54c5869ece2c46..95c477f4118adc269684dd3cbdbab3d81b51600a 100644 (file)
@@ -98,4 +98,48 @@ class Arvados::V1::ContainerRequestsControllerTest < ActionController::TestCase
     assert_equal api_client_authorizations(:spectator).token, req.runtime_token
   end
 
+  %w(Running Complete).each do |state|
+    test "filter on container.state = #{state}" do
+      authorize_with :active
+      get :index, params: {
+            filters: [['container.state', '=', state]],
+          }
+      assert_response :success
+      assert_operator json_response['items'].length, :>, 0
+      json_response['items'].each do |cr|
+        assert_equal state, Container.find_by_uuid(cr['container_uuid']).state
+      end
+    end
+  end
+
+  test "filter on container success" do
+    authorize_with :active
+    get :index, params: {
+          filters: [
+            ['container.state', '=', 'Complete'],
+            ['container.exit_code', '=', '0'],
+          ],
+        }
+    assert_response :success
+    assert_operator json_response['items'].length, :>, 0
+    json_response['items'].each do |cr|
+      assert_equal 'Complete', Container.find_by_uuid(cr['container_uuid']).state
+      assert_equal 0, Container.find_by_uuid(cr['container_uuid']).exit_code
+    end
+  end
+
+  test "filter on container subproperty runtime_status[foo] = bar" do
+    ctr = containers(:running)
+    act_as_system_user do
+      ctr.update_attributes!(runtime_status: {foo: 'bar'})
+    end
+    authorize_with :active
+    get :index, params: {
+          filters: [
+            ['container.runtime_status.foo', '=', 'bar'],
+          ],
+        }
+    assert_response :success
+    assert_equal [ctr.uuid], json_response['items'].collect { |cr| cr['container_uuid'] }.uniq
+  end
 end
index 275d2a651b8d2edc0dabbb16c55e55cccef15bc2..18d2fbbcb5f7cef9d87cc71df1077b5a4c8cf1d6 100644 (file)
@@ -287,6 +287,12 @@ class PermissionTest < ActiveSupport::TestCase
     a = create :active_user, first_name: "A"
     b = create :active_user, first_name: "B"
     other = create :active_user, first_name: "OTHER"
+
+    assert_empty(User.readable_by(b).where(uuid: a.uuid),
+                     "#{b.first_name} should not be able to see 'a' in the user list")
+    assert_empty(User.readable_by(a).where(uuid: b.uuid),
+                     "#{a.first_name} should not be able to see 'b' in the user list")
+
     act_as_system_user do
       g = create :group
       [a,b].each do |u|
@@ -296,6 +302,12 @@ class PermissionTest < ActiveSupport::TestCase
                name: 'can_read', head_uuid: u.uuid, tail_uuid: g.uuid)
       end
     end
+
+    assert_not_empty(User.readable_by(b).where(uuid: a.uuid),
+                     "#{b.first_name} should be able to see 'a' 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
       Specimen.create!
     end
index 534928bc82340bb23574f71ceac85c7872d8deb0..73d129e9cdac8d0048f0622746f7b630e056b21c 100644 (file)
@@ -80,7 +80,7 @@ func EachCollection(c *arvados.Client, pageSize int, f func(arvados.Collection)
                        return err
                }
                for _, coll := range page.Items {
-                       if last.ModifiedAt != nil && *last.ModifiedAt == *coll.ModifiedAt && last.UUID >= coll.UUID {
+                       if last.ModifiedAt == coll.ModifiedAt && last.UUID >= coll.UUID {
                                continue
                        }
                        callCount++
@@ -92,9 +92,9 @@ func EachCollection(c *arvados.Client, pageSize int, f func(arvados.Collection)
                }
                if len(page.Items) == 0 && !gettingExactTimestamp {
                        break
-               } else if last.ModifiedAt == nil {
+               } else if last.ModifiedAt.IsZero() {
                        return fmt.Errorf("BUG: Last collection on the page (%s) has no modified_at timestamp; cannot make progress", last.UUID)
-               } else if len(page.Items) > 0 && *last.ModifiedAt == filterTime {
+               } else if len(page.Items) > 0 && last.ModifiedAt == filterTime {
                        // If we requested time>=X and never got a
                        // time>X then we might not have received all
                        // items with time==X yet. Switch to
@@ -135,7 +135,7 @@ func EachCollection(c *arvados.Client, pageSize int, f func(arvados.Collection)
                        // avoiding that would add overhead in the
                        // overwhelmingly common cases, so we don't
                        // bother.
-                       filterTime = *last.ModifiedAt
+                       filterTime = last.ModifiedAt
                        params.Filters = []arvados.Filter{{
                                Attr:     "modified_at",
                                Operator: ">=",
index a2200e1db90a4ddf69fd65112c432df9bbcba2c6..e6925c4afd7d32765100f5253f89493a648f12c5 100644 (file)
@@ -30,7 +30,7 @@ func (s *integrationSuite) TestIdenticalTimestamps(c *check.C) {
                        var lastMod time.Time
                        sawUUID := make(map[string]bool)
                        err := EachCollection(s.client, pageSize, func(c arvados.Collection) error {
-                               if c.ModifiedAt == nil {
+                               if c.ModifiedAt.IsZero() {
                                        return nil
                                }
                                if sawUUID[c.UUID] {
@@ -39,14 +39,14 @@ func (s *integrationSuite) TestIdenticalTimestamps(c *check.C) {
                                }
                                got[trial] = append(got[trial], c.UUID)
                                sawUUID[c.UUID] = true
-                               if lastMod == *c.ModifiedAt {
+                               if lastMod == c.ModifiedAt {
                                        streak++
                                        if streak > longestStreak {
                                                longestStreak = streak
                                        }
                                } else {
                                        streak = 0
-                                       lastMod = *c.ModifiedAt
+                                       lastMod = c.ModifiedAt
                                }
                                return nil
                        }, nil)
index 5f163e87c32188e04ac4a05573ad9d922f185a07..44c3b36ee43ccedc7dc61def4d3945d225ed32ee 100644 (file)
@@ -85,4 +85,4 @@ DEPENDENCIES
   rake
 
 BUNDLED WITH
-   1.17.3
+   2.0.2
index 8fd088a13741b190b828ed32ceb0c9dab00ecf50..ed4795d1cc8676cfdd93c052cd44cbffae08de98 100755 (executable)
@@ -147,7 +147,6 @@ Clusters:
       AutoSetupNewUsers: true
       AutoSetupNewUsersWithVmUUID: $vm_uuid
       AutoSetupNewUsersWithRepository: true
-      AnonymousUserToken: $(cat /var/lib/arvados/superuser_token)
     Workbench:
       SecretKeyBase: $workbench_secret_key_base
       ArvadosDocsite: http://$localip:${services[doc]}/
index 15a11b05805b15a54edf37fedeeaa5a9c4b62d4d..e09cf7ae61d68536de1bd32ae49ba57aeac3f384 100644 (file)
@@ -9,9 +9,9 @@ mkdir -p $GOPATH
 cd /usr/src/arvados
 if [[ $UID = 0 ]] ; then
     /usr/local/lib/arvbox/runsu.sh flock /var/lib/gopath/gopath.lock go mod download
-    /usr/local/lib/arvbox/runsu.sh flock /var/lib/gopath/gopath.lock go get ./cmd/arvados-server
+    /usr/local/lib/arvbox/runsu.sh flock /var/lib/gopath/gopath.lock go get git.curoverse.com/arvados.git/cmd/arvados-server
 else
     flock /var/lib/gopath/gopath.lock go mod download
-    flock /var/lib/gopath/gopath.lock go get ./cmd/arvados-server
+    flock /var/lib/gopath/gopath.lock go get git.curoverse.com/arvados.git/cmd/arvados-server
 fi
 install $GOPATH/bin/arvados-server /usr/local/bin
index ea66cfd7a2155d3cb7db85e064e54d915bf7eb84..66a4a28ec5ad24e6f8fb0dcb786491f0007c80fd 100755 (executable)
@@ -8,11 +8,16 @@ set -ex -o pipefail
 
 . /usr/local/lib/arvbox/common.sh
 
+
 cd /usr/src/arvados/doc
 run_bundler --without=development
 
-cd /usr/src/arvados/sdk/R
-R --quiet --vanilla --file=install_deps.R
+# Generating the R docs is expensive, so for development if the file
+# "no-sdk" exists then skip the R stuff.
+if [[ ! -f /usr/src/arvados/doc/no-sdk ]] ; then
+    cd /usr/src/arvados/sdk/R
+    R --quiet --vanilla --file=install_deps.R
+fi
 
 if test "$1" = "--only-deps" ; then
     exit