uglifier (~> 2.0)
BUNDLED WITH
- 2.0.2
+ 1.17.3
def show_pane_list
if current_user.andand.is_admin
- super | %w(Admin)
+ %w(Admin) | super
else
super
end
<div class="modal-header">
<button type="button" class="close" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">×</button>
<div>
- <div class="col-sm-6"> <h4 class="modal-title">Setup Shell Account</h4> </div>
+ <div class="col-sm-6"> <h4 class="modal-title">Setup Account</h4> </div>
<div class="spinner spinner-32px spinner-h-center col-sm-1" hidden="true"></div>
</div>
<br/>
<% end %>
</div>
<div class="form-group">
- <label for="vm_uuid">Virtual Machine</label>
+ <label for="vm_uuid">Virtual Machine (optional)</label>
<select class="form-control" name="vm_uuid">
<option value="" <%= 'selected' unless selected_vm %>>
Choose One:
</select>
</div>
<div class="groups-group">
- <label for="groups">Groups for virtual machine (comma separated list)</label>
+ <label for="groups">Groups for virtual machine (comma separated list) (optional)</label>
<input class="form-control" id="groups" maxlength="250" name="groups" type="text" value="<%=groups%>">
</div>
</div>
<div class="row">
<div class="col-md-6">
+
<p>
- As an admin, you can log in as this user. When you’ve
- finished, you will need to log out and log in again with your
- own account.
+ This page enables you to <a href="https://doc.arvados.org/master/admin/user-management.html">manage users</a>.
</p>
- <blockquote>
- <%= button_to "Log in as #{@object.full_name}", sudo_user_url(id: @object.uuid), class: 'btn btn-primary' %>
- </blockquote>
-
<p>
- As an admin, you can setup a shell account for this user.
- The login name is automatically generated from the user's e-mail address.
+ This button sets up a user. After setup, they will be able use
+ Arvados. This dialog box also allows you to optionally set up a
+ shell account for this user. The login name is automatically
+ generated from the user's e-mail address.
</p>
- <blockquote>
- <%= link_to "Setup shell account #{'for ' if @object.full_name.present?} #{@object.full_name}", setup_popup_user_url(id: @object.uuid), {class: 'btn btn-primary', :remote => true, 'data-toggle' => "modal", 'data-target' => '#user-setup-modal-window'} %>
- </blockquote>
+ <%= link_to "Setup account #{'for ' if @object.full_name.present?} #{@object.full_name}", setup_popup_user_url(id: @object.uuid), {class: 'btn btn-primary', :remote => true, 'data-toggle' => "modal", 'data-target' => '#user-setup-modal-window'} %>
- <p>
+ <p style="margin-top: 3em">
As an admin, you can deactivate and reset this user. This will
remove all repository/VM permissions for the user. If you
"setup" the user again, the user will have to sign the user
- agreement again.
+ agreement again. You may also want to <a href="https://doc.arvados.org/master/admin/reassign-ownership.html">reassign data ownership</a>.
+ </p>
+
+ <%= button_to "Deactivate #{@object.full_name}", unsetup_user_url(id: @object.uuid), class: 'btn btn-primary', data: {confirm: "Are you sure you want to deactivate #{@object.full_name}?"} %>
+
+ <p style="margin-top: 3em">
+ As an admin, you can log in as this user. When you’ve
+ finished, you will need to log out and log in again with your
+ own account.
</p>
- <blockquote>
- <%= button_to "Deactivate #{@object.full_name}", unsetup_user_url(id: @object.uuid), class: 'btn btn-primary', data: {confirm: "Are you sure you want to deactivate #{@object.full_name}?"} %>
- </blockquote>
+ <%= button_to "Log in as #{@object.full_name}", sudo_user_url(id: @object.uuid), class: 'btn btn-primary' %>
</div>
<div class="col-md-6">
<div class="panel panel-default">
find('a', text: 'Show').
click
+ click_link 'Attributes'
+
assert page.has_text? 'modified_by_user_uuid'
page.within(:xpath, '//span[@data-name="is_active"]') do
assert_equal "false", text, "Expected new user's is_active to be false"
click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? 'can_login' # make sure page is rendered / ready
+ assert page.has_text? 'can_read' # make sure page is rendered / ready
assert page.has_no_text? 'VirtualMachine:'
end
# Setup user
click_link 'Admin'
- assert page.has_text? 'As an admin, you can setup'
+ assert page.has_text? 'This button sets up a user'
- click_link 'Setup shell account for Active User'
+ click_link 'Setup account for Active User'
within '.modal-content' do
find 'label', text: 'Virtual Machine'
end
visit user_url
+ click_link 'Attributes'
assert page.has_text? 'modified_by_client_uuid'
click_link 'Advanced'
# Click on Setup button again and this time also choose a VM
click_link 'Admin'
- click_link 'Setup shell account for Active User'
+ click_link 'Setup account for Active User'
within '.modal-content' do
select("testvm.shell", :from => 'vm_uuid')
end
visit user_url
+ click_link 'Attributes'
find '#Attributes', text: 'modified_by_client_uuid'
click_link 'Advanced'
click_link 'Metadata'
assert page.has_text? 'VirtualMachine: testvm.shell'
assert page.has_text? '["test group one", "test-group-two"]'
+ vm_links = all("a", text: "VirtualMachine:")
+ assert_equal(2, vm_links.size)
end
test "unsetup active user" do
user_url = page.current_url
# Verify that is_active is set
- find('a,button', text: 'Attributes').click
+ click_link 'Attributes'
assert page.has_text? 'modified_by_user_uuid'
page.within(:xpath, '//span[@data-name="is_active"]') do
assert_equal "true", text, "Expected user's is_active to be true"
# poltergeist returns true for confirm(), so we don't need to accept.
end
+ click_link 'Attributes'
+
# Should now be back in the Attributes tab for the user
assert page.has_text? 'modified_by_user_uuid'
page.within(:xpath, '//span[@data-name="is_active"]') do
# setup user again and verify links present
click_link 'Admin'
- click_link 'Setup shell account for Active User'
+ click_link 'Setup account for Active User'
within '.modal-content' do
select("testvm.shell", :from => 'vm_uuid')
end
visit user_url
+ click_link 'Attributes'
assert page.has_text? 'modified_by_client_uuid'
click_link 'Advanced'
# Setup user
click_link 'Admin'
- assert page.has_text? 'As an admin, you can setup'
+ assert page.has_text? 'This button sets up a user'
click_link 'Add new group'
n += 1
raise if n > 2 || e.is_a?(Skip)
STDERR.puts "Test failed, retrying (##{n})"
+ ActiveSupport::TestCase.reset_api_fixtures_now
retry
end
rescue *PASSTHROUGH_EXCEPTIONS
test -d ubuntu1804/generated || mkdir ubuntu1804/generated
cp -rlt ubuntu1804/generated common-generated/*
-GOTARBALL=go1.12.7.linux-amd64.tar.gz
+GOTARBALL=go1.13.4.linux-amd64.tar.gz
NODETARBALL=node-v6.11.2-linux-x64.tar.xz
RVMKEY1=mpapis.asc
RVMKEY2=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 bundler --version 2.0.2 && \
/usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.10.2
# Install Bash 4.4.12 // see https://dev.arvados.org/issues/15612
&& ln -sf /usr/local/src/bash-4.4.12/bash /bin/bash
# Install golang binary
-ADD generated/go1.12.7.linux-amd64.tar.gz /usr/local/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
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 bundler --version 2.0.2 && \
/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/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
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 bundler --version 2.0.2 && \
/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/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
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 bundler --version 2.0.2 && \
/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/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
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 bundler --version 2.0.2 && \
/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/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
--- /dev/null
+../../tools/vocabulary-migrate/vocabulary-migrate.py
\ No newline at end of file
To access a monitoring endpoint, the requester must provide the HTTP header @Authorization: Bearer (ManagementToken)@.
-h2. API server
-
-Set @ManagementToken@ in the appropriate section of @application.yml@
-
-<pre>
-production:
- # Token to be included in all healthcheck requests. Disabled by default.
- # Server expects request header of the format "Authorization: Bearer xxx"
- ManagementToken: xxx
-</pre>
-
h2. Node Manager
Set @port@ (the listen port) and @ManagementToken@ in the @Manage@ section of @node-manager.ini@.
ManagementToken = xxx
</pre>
-h2. Other services
+h2. API server and other services
-The following services also support monitoring. Set @ManagementToken@ in the respective yaml config file for each service.
+The following services also support monitoring.
+* API server
+* arv-git-httpd
+* controller
+* keep-balance
+* keepproxy
* keepstore
* keep-web
-* keepproxy
-* arv-git-httpd
* websockets
+
+Set @ManagementToken@ in the appropriate section of @/etc/arvados/config.yml@.
+
+<notextile>
+<pre><code>Clusters:
+ <span class="userinput">uuid_prefix</span>:
+ # Token to be included in all healthcheck requests. Disabled by default.
+ # Server expects request header of the format "Authorization: Bearer xxx"
+ ManagementToken: xxx
+</code></pre>
+</notextile>
When any key or value has more than one label option, Workbench2's user interface will allow the user to select any of the options. But because only the IDs are saved in the system, when the property is displayed in the user interface, the label shown will be the first of each group defined in the vocabulary file. For example, the user could select the property key @Species@ and @Homo sapiens@ as its value, but the user interface will display it as @Animal: Human@ because those labels are the first in the vocabulary definition.
-Internally, Workbench2 uses the IDs to do property based searches, so if the user searches by @Animal: Human@ or @Species: Homo sapiens@, both will return the same results.
\ No newline at end of file
+Internally, Workbench2 uses the IDs to do property based searches, so if the user searches by @Animal: Human@ or @Species: Homo sapiens@, both will return the same results.
+
+h2. Properties migration
+
+After installing the new vocabulary definition, it may be necessary to migrate preexisting properties that were set up using literal strings. This can be a big task depending on the number of properties on the vocabulary and the amount of collections and projects on the cluster.
+
+To help with this task we provide below a migration example script that accepts the new vocabulary definition file as an input, and uses the @ARVADOS_API_TOKEN@ and @ARVADOS_API_HOST@ environment variables to connect to the cluster, search for every collection and group that has properties with labels defined on the vocabulary file, and migrates them to the corresponding identifiers.
+
+This script will not run if the vocabulary file has duplicated labels for different keys or for different values inside a key, this is a failsafe mechanism to avoid migration errors.
+
+Please take into account that this script requires admin credentials. It also offers a @--dry-run@ flag that will report what changes are required without applying them, so it can be reviewed by an administrator.
+
+Also, take into consideration that this example script does case-sensitive matching on labels.
+
+{% codeblock as python %}
+{% include 'vocabulary_migrate_py' %}
+{% endcodeblock %}
\ No newline at end of file
table(table table-bordered table-condensed).
|_. Distribution|_. State|_. Last supported version|
|CentOS 7|Supported|Latest|
+|Debian 10 ("buster")|Supported|Latest|
|Debian 9 ("stretch")|Supported|Latest|
-|Ubuntu 16.04 ("xenial")|Supported|Latest|
|Ubuntu 18.04 ("bionic")|Supported|Latest|
+|Ubuntu 16.04 ("xenial")|Supported|Latest|
|Ubuntu 14.04 ("trusty")|EOL|5f943cd451acfbdcddd84e791738c3aa5926bfed (2019-07-10)|
|Debian 8 ("jessie")|EOL|5f943cd451acfbdcddd84e791738c3aa5926bfed (2019-07-10)|
|Ubuntu 12.04 ("precise")|EOL|8ed7b6dd5d4df93a3f37096afe6d6f81c2a7ef6e (2017-05-03)|
"os"
"os/exec"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/ctxlog"
"github.com/ghodss/yaml"
"github.com/sirupsen/logrus"
if err != nil {
return 1
}
+ problems := false
+ if warnAboutProblems(logger, withDepr) {
+ problems = true
+ }
cmd := exec.Command("diff", "-u", "--label", "without-deprecated-configs", "--label", "relying-on-deprecated-configs", "/dev/fd/3", "/dev/fd/4")
for _, obj := range []interface{}{withoutDepr, withDepr} {
y, _ := yaml.Marshal(obj)
if logbuf.Len() > 0 {
return 1
}
- return 0
+
+ if problems {
+ return 1
+ } else {
+ return 0
+ }
+}
+
+func warnAboutProblems(logger logrus.FieldLogger, cfg *arvados.Config) bool {
+ warned := false
+ for id, cc := range cfg.Clusters {
+ if cc.SystemRootToken == "" {
+ logger.Warnf("Clusters.%s.SystemRootToken is empty; see https://doc.arvados.org/master/install/install-keepstore.html", id)
+ warned = true
+ }
+ if cc.ManagementToken == "" {
+ logger.Warnf("Clusters.%s.ManagementToken is empty; see https://doc.arvados.org/admin/management-token.html", id)
+ warned = true
+ }
+ }
+ return warned
}
var DumpDefaultsCommand defaultsCommand
os.Unsetenv("ARVADOS_API_TOKEN")
}
-func (s *CommandSuite) TestBadArg(c *check.C) {
+func (s *CommandSuite) TestDump_BadArg(c *check.C) {
var stderr bytes.Buffer
code := DumpCommand.RunCommand("arvados config-dump", []string{"-badarg"}, bytes.NewBuffer(nil), bytes.NewBuffer(nil), &stderr)
c.Check(code, check.Equals, 2)
c.Check(stderr.String(), check.Matches, `(?ms)flag provided but not defined: -badarg\nUsage:\n.*`)
}
-func (s *CommandSuite) TestEmptyInput(c *check.C) {
+func (s *CommandSuite) TestDump_EmptyInput(c *check.C) {
var stdout, stderr bytes.Buffer
code := DumpCommand.RunCommand("arvados config-dump", []string{"-config", "-"}, &bytes.Buffer{}, &stdout, &stderr)
c.Check(code, check.Equals, 1)
c.Check(stderr.String(), check.Matches, `config does not define any clusters\n`)
}
-func (s *CommandSuite) TestCheckNoDeprecatedKeys(c *check.C) {
+func (s *CommandSuite) TestCheck_NoWarnings(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
z1234:
+ ManagementToken: xyzzy
+ SystemRootToken: xyzzy
API:
MaxItemsPerResponse: 1234
PostgreSQL:
c.Check(stderr.String(), check.Equals, "")
}
-func (s *CommandSuite) TestCheckDeprecatedKeys(c *check.C) {
+func (s *CommandSuite) TestCheck_DeprecatedKeys(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
c.Check(stdout.String(), check.Matches, `(?ms).*\n\- +.*MaxItemsPerResponse: 1000\n\+ +MaxItemsPerResponse: 1234\n.*`)
}
-func (s *CommandSuite) TestCheckOldKeepstoreConfigFile(c *check.C) {
+func (s *CommandSuite) TestCheck_OldKeepstoreConfigFile(c *check.C) {
f, err := ioutil.TempFile("", "")
c.Assert(err, check.IsNil)
defer os.Remove(f.Name())
c.Check(stderr.String(), check.Matches, `(?ms).*you should remove the legacy keepstore config file.*\n`)
}
-func (s *CommandSuite) TestCheckUnknownKey(c *check.C) {
+func (s *CommandSuite) TestCheck_UnknownKey(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
c.Check(stderr.String(), check.Matches, `(?ms).*unexpected object in config entry: Clusters.z1234.PostgreSQL.ConnectionPool"\n.*`)
}
-func (s *CommandSuite) TestDumpFormatting(c *check.C) {
+func (s *CommandSuite) TestDump_Formatting(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
c.Check(stdout.String(), check.Matches, `(?ms).*http://localhost:12345: {}\n.*`)
}
-func (s *CommandSuite) TestDumpUnknownKey(c *check.C) {
+func (s *CommandSuite) TestDump_UnknownKey(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
"net/url"
"regexp"
"strings"
+ "time"
"git.curoverse.com/arvados.git/lib/config"
"git.curoverse.com/arvados.git/lib/controller/localdb"
}
}
+func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
+ return conn.generated_CollectionList(ctx, options)
+}
+
func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
return conn.chooseBackend(options.UUID).CollectionProvenance(ctx, options)
}
return conn.chooseBackend(options.UUID).CollectionUntrash(ctx, options)
}
+func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
+ return conn.generated_ContainerList(ctx, options)
+}
+
func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
return conn.chooseBackend(options.ClusterID).ContainerCreate(ctx, options)
}
return conn.chooseBackend(options.UUID).ContainerUnlock(ctx, options)
}
+func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
+ return conn.generated_SpecimenList(ctx, options)
+}
+
func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
return conn.chooseBackend(options.ClusterID).SpecimenCreate(ctx, options)
}
return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
}
+var userAttrsCachedFromLoginCluster = map[string]bool{
+ "created_at": true,
+ "email": true,
+ "first_name": true,
+ "is_active": true,
+ "is_admin": true,
+ "last_name": true,
+ "modified_at": true,
+ "modified_by_client_uuid": true,
+ "modified_by_user_uuid": true,
+ "prefs": true,
+ "username": true,
+
+ "full_name": false,
+ "identity_url": false,
+ "is_invited": false,
+ "owner_uuid": false,
+ "uuid": false,
+}
+
+func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ logger := ctxlog.FromContext(ctx)
+ if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID {
+ resp, err := conn.chooseBackend(id).UserList(ctx, options)
+ if err != nil {
+ return resp, err
+ }
+ batchOpts := arvados.UserBatchUpdateOptions{Updates: map[string]map[string]interface{}{}}
+ for _, user := range resp.Items {
+ if !strings.HasPrefix(user.UUID, id) {
+ continue
+ }
+ logger.Debugf("cache user info for uuid %q", user.UUID)
+
+ // If the remote cluster has null timestamps
+ // (e.g., test server with incomplete
+ // fixtures) use dummy timestamps (instead of
+ // the zero time, which causes a Rails API
+ // error "year too big to marshal: 1 UTC").
+ if user.ModifiedAt.IsZero() {
+ user.ModifiedAt = time.Now()
+ }
+ if user.CreatedAt.IsZero() {
+ user.CreatedAt = time.Now()
+ }
+
+ var allFields map[string]interface{}
+ buf, err := json.Marshal(user)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error encoding user record from remote response: %s", err)
+ }
+ err = json.Unmarshal(buf, &allFields)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error transcoding user record from remote response: %s", err)
+ }
+ updates := allFields
+ if len(options.Select) > 0 {
+ updates = map[string]interface{}{}
+ for _, k := range options.Select {
+ if v, ok := allFields[k]; ok && userAttrsCachedFromLoginCluster[k] {
+ updates[k] = v
+ }
+ }
+ } else {
+ for k := range updates {
+ if !userAttrsCachedFromLoginCluster[k] {
+ delete(updates, k)
+ }
+ }
+ }
+ batchOpts.Updates[user.UUID] = updates
+ }
+ if len(batchOpts.Updates) > 0 {
+ ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
+ _, err = conn.local.UserBatchUpdate(ctxRoot, batchOpts)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error updating local user records: %s", err)
+ }
+ }
+ return resp, nil
+ } else {
+ return conn.generated_UserList(ctx, options)
+ }
+}
+
+func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.ClusterID).UserCreate(ctx, options)
+}
+
+func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUpdate(ctx, options)
+}
+
+func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUpdateUUID(ctx, options)
+}
+
+func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.OldUserUUID).UserMerge(ctx, options)
+}
+
+func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserActivate(ctx, options)
+}
+
+func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ return conn.chooseBackend(options.UUID).UserSetup(ctx, options)
+}
+
+func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUnsetup(ctx, options)
+}
+
+func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGet(ctx, options)
+}
+
+func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGetCurrent(ctx, options)
+}
+
+func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGetSystem(ctx, options)
+}
+
+func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserDelete(ctx, options)
+}
+
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ return conn.local.UserBatchUpdate(ctx, options)
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options)
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package federation
+
+import (
+ "context"
+ "net/url"
+ "os"
+ "testing"
+
+ "git.curoverse.com/arvados.git/lib/controller/router"
+ "git.curoverse.com/arvados.git/lib/controller/rpc"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
+ "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+ "git.curoverse.com/arvados.git/sdk/go/auth"
+ "git.curoverse.com/arvados.git/sdk/go/ctxlog"
+ "git.curoverse.com/arvados.git/sdk/go/httpserver"
+ check "gopkg.in/check.v1"
+)
+
+// Gocheck boilerplate
+func Test(t *testing.T) {
+ check.TestingT(t)
+}
+
+// FederationSuite does some generic setup/teardown. Don't add Test*
+// methods to FederationSuite itself.
+type FederationSuite struct {
+ cluster *arvados.Cluster
+ ctx context.Context
+ fed *Conn
+}
+
+func (s *FederationSuite) SetUpTest(c *check.C) {
+ s.cluster = &arvados.Cluster{
+ ClusterID: "aaaaa",
+ SystemRootToken: arvadostest.SystemRootToken,
+ RemoteClusters: map[string]arvados.RemoteCluster{
+ "aaaaa": arvados.RemoteCluster{
+ Host: os.Getenv("ARVADOS_API_HOST"),
+ },
+ },
+ }
+ arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
+ s.cluster.TLS.Insecure = true
+ s.cluster.API.MaxItemsPerResponse = 3
+
+ ctx := context.Background()
+ ctx = ctxlog.Context(ctx, ctxlog.TestLogger(c))
+ ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
+ s.ctx = ctx
+
+ s.fed = New(s.cluster)
+}
+
+func (s *FederationSuite) addDirectRemote(c *check.C, id string, backend backend) {
+ s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
+ Host: "in-process.local",
+ }
+ s.fed.remotes[id] = backend
+}
+
+func (s *FederationSuite) addHTTPRemote(c *check.C, id string, backend backend) {
+ srv := httpserver.Server{Addr: ":"}
+ srv.Handler = router.New(backend)
+ c.Check(srv.Start(), check.IsNil)
+ s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
+ Scheme: "http",
+ Host: srv.Addr,
+ Proxy: true,
+ }
+ s.fed.remotes[id] = rpc.NewConn(id, &url.URL{Scheme: "http", Host: srv.Addr}, true, saltedTokenProvider(s.fed.local, id))
+}
if err != nil {
panic(err)
}
- orig := regexp.MustCompile(`(?ms)\nfunc [^\n]*CollectionList\(.*?\n}\n`).Find(buf)
+ orig := regexp.MustCompile(`(?ms)\nfunc [^\n]*generated_CollectionList\(.*?\n}\n`).Find(buf)
if len(orig) == 0 {
panic("can't find CollectionList func")
}
defer out.Close()
out.Write(regexp.MustCompile(`(?ms)^.*package .*?import.*?\n\)\n`).Find(buf))
io.WriteString(out, "//\n// -- this file is auto-generated -- do not edit -- edit list.go and run \"go generate\" instead --\n//\n\n")
- for _, t := range []string{"Container", "Specimen"} {
+ for _, t := range []string{"Container", "Specimen", "User"} {
_, err := out.Write(bytes.ReplaceAll(orig, []byte("Collection"), []byte(t)))
if err != nil {
panic(err)
// -- this file is auto-generated -- do not edit -- edit list.go and run "go generate" instead --
//
-func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
+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
return merged, err
}
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
+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
}
return merged, err
}
+
+func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ var mtx sync.Mutex
+ var merged arvados.UserList
+ 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.UserList(ctx, options)
+ if err != nil {
+ return nil, err
+ }
+ mtx.Lock()
+ defer mtx.Unlock()
+ if len(merged.Items) == 0 {
+ merged = cl
+ } else if len(cl.Items) > 0 {
+ merged.Items = append(merged.Items, cl.Items...)
+ needSort.Store(true)
+ }
+ uuids := make([]string, 0, len(cl.Items))
+ for _, item := range cl.Items {
+ uuids = append(uuids, item.UUID)
+ }
+ return uuids, nil
+ })
+ if needSort.Load().(bool) {
+ // Apply the default/implied order, "modified_at desc"
+ sort.Slice(merged.Items, func(i, j int) bool {
+ mi, mj := merged.Items[i].ModifiedAt, merged.Items[j].ModifiedAt
+ return mj.Before(mi)
+ })
+ }
+ if merged.Items == nil {
+ // Return empty results as [], not null
+ // (https://github.com/golang/go/issues/27589 might be
+ // a better solution in the future)
+ merged.Items = []arvados.User{}
+ }
+ return merged, err
+}
// CollectionList is used as a template to auto-generate List()
// methods for other types; see generate.go.
-func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
+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
"context"
"fmt"
"net/http"
- "net/url"
- "os"
"sort"
- "testing"
- "git.curoverse.com/arvados.git/lib/controller/router"
- "git.curoverse.com/arvados.git/lib/controller/rpc"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "git.curoverse.com/arvados.git/sdk/go/auth"
- "git.curoverse.com/arvados.git/sdk/go/ctxlog"
- "git.curoverse.com/arvados.git/sdk/go/httpserver"
check "gopkg.in/check.v1"
)
-// Gocheck boilerplate
-func Test(t *testing.T) {
- check.TestingT(t)
-}
-
-var (
- _ = check.Suite(&FederationSuite{})
- _ = check.Suite(&CollectionListSuite{})
-)
-
-type FederationSuite struct {
- cluster *arvados.Cluster
- ctx context.Context
- fed *Conn
-}
-
-func (s *FederationSuite) SetUpTest(c *check.C) {
- s.cluster = &arvados.Cluster{
- ClusterID: "aaaaa",
- RemoteClusters: map[string]arvados.RemoteCluster{
- "aaaaa": arvados.RemoteCluster{
- Host: os.Getenv("ARVADOS_API_HOST"),
- },
- },
- }
- arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
- s.cluster.TLS.Insecure = true
- s.cluster.API.MaxItemsPerResponse = 3
-
- ctx := context.Background()
- ctx = ctxlog.Context(ctx, ctxlog.TestLogger(c))
- ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
- s.ctx = ctx
-
- s.fed = New(s.cluster)
-}
-
-func (s *FederationSuite) addDirectRemote(c *check.C, id string, backend backend) {
- s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
- Host: "in-process.local",
- }
- s.fed.remotes[id] = backend
-}
-
-func (s *FederationSuite) addHTTPRemote(c *check.C, id string, backend backend) {
- srv := httpserver.Server{Addr: ":"}
- srv.Handler = router.New(backend)
- c.Check(srv.Start(), check.IsNil)
- s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
- Host: srv.Addr,
- Proxy: true,
- }
- s.fed.remotes[id] = rpc.NewConn(id, &url.URL{Scheme: "http", Host: srv.Addr}, true, saltedTokenProvider(s.fed.local, id))
-}
+var _ = check.Suite(&CollectionListSuite{})
type collectionLister struct {
arvadostest.APIStub
check "gopkg.in/check.v1"
)
-func (s *FederationSuite) TestDeferToLoginCluster(c *check.C) {
+var _ = check.Suite(&LoginSuite{})
+
+type LoginSuite struct {
+ FederationSuite
+}
+
+func (s *LoginSuite) TestDeferToLoginCluster(c *check.C) {
s.addHTTPRemote(c, "zhome", &arvadostest.APIStub{})
s.cluster.Login.LoginCluster = "zhome"
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package federation
+
+import (
+ "encoding/json"
+ "errors"
+ "net/url"
+ "os"
+ "strings"
+
+ "git.curoverse.com/arvados.git/lib/controller/rpc"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
+ "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+ check "gopkg.in/check.v1"
+)
+
+var _ = check.Suite(&UserSuite{})
+
+type UserSuite struct {
+ FederationSuite
+}
+
+func (s *UserSuite) TestLoginClusterUserList(c *check.C) {
+ s.cluster.ClusterID = "local"
+ s.cluster.Login.LoginCluster = "zzzzz"
+ s.fed = New(s.cluster)
+ s.addDirectRemote(c, "zzzzz", rpc.NewConn("zzzzz", &url.URL{Scheme: "https", Host: os.Getenv("ARVADOS_API_HOST")}, true, rpc.PassthroughTokenProvider))
+
+ for _, updateFail := range []bool{false, true} {
+ for _, opts := range []arvados.ListOptions{
+ {Offset: 0, Limit: -1, Select: nil},
+ {Offset: 1, Limit: 1, Select: nil},
+ {Offset: 0, Limit: 2, Select: []string{"uuid"}},
+ {Offset: 0, Limit: 2, Select: []string{"uuid", "email"}},
+ } {
+ c.Logf("updateFail %v, opts %#v", updateFail, opts)
+ spy := arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
+ stub := &arvadostest.APIStub{Error: errors.New("local cluster failure")}
+ if updateFail {
+ s.fed.local = stub
+ } else {
+ s.fed.local = rpc.NewConn(s.cluster.ClusterID, spy.URL, true, rpc.PassthroughTokenProvider)
+ }
+ userlist, err := s.fed.UserList(s.ctx, opts)
+ if updateFail && err == nil {
+ // All local updates fail, so the only
+ // cases expected to succeed are the
+ // ones with 0 results.
+ c.Check(userlist.Items, check.HasLen, 0)
+ c.Check(stub.Calls(nil), check.HasLen, 0)
+ } else if updateFail {
+ c.Logf("... err %#v", err)
+ calls := stub.Calls(stub.UserBatchUpdate)
+ if c.Check(calls, check.HasLen, 1) {
+ c.Logf("... stub.UserUpdate called with options: %#v", calls[0].Options)
+ shouldUpdate := map[string]bool{
+ "uuid": false,
+ "email": true,
+ "first_name": true,
+ "last_name": true,
+ "is_admin": true,
+ "is_active": true,
+ "prefs": true,
+ // can't safely update locally
+ "owner_uuid": false,
+ "identity_url": false,
+ // virtual attrs
+ "full_name": false,
+ "is_invited": false,
+ }
+ if opts.Select != nil {
+ // Only the selected
+ // fields (minus uuid)
+ // should be updated.
+ for k := range shouldUpdate {
+ shouldUpdate[k] = false
+ }
+ for _, k := range opts.Select {
+ if k != "uuid" {
+ shouldUpdate[k] = true
+ }
+ }
+ }
+ var uuid string
+ for uuid = range calls[0].Options.(arvados.UserBatchUpdateOptions).Updates {
+ }
+ for k, shouldFind := range shouldUpdate {
+ _, found := calls[0].Options.(arvados.UserBatchUpdateOptions).Updates[uuid][k]
+ c.Check(found, check.Equals, shouldFind, check.Commentf("offending attr: %s", k))
+ }
+ }
+ } else {
+ updates := 0
+ for _, d := range spy.RequestDumps {
+ d := string(d)
+ if strings.Contains(d, "PATCH /arvados/v1/users/batch") {
+ c.Check(d, check.Matches, `(?ms).*Authorization: Bearer `+arvadostest.SystemRootToken+`.*`)
+ updates++
+ }
+ }
+ c.Check(err, check.IsNil)
+ c.Check(updates, check.Equals, 1)
+ c.Logf("... response items %#v", userlist.Items)
+ }
+ }
+ }
+}
+
+// userAttrsCachedFromLoginCluster must have an entry for every field
+// in the User struct.
+func (s *UserSuite) TestUserAttrsUpdateWhitelist(c *check.C) {
+ buf, err := json.Marshal(&arvados.User{})
+ c.Assert(err, check.IsNil)
+ var allFields map[string]interface{}
+ err = json.Unmarshal(buf, &allFields)
+ c.Assert(err, check.IsNil)
+ for k := range allFields {
+ _, ok := userAttrsCachedFromLoginCluster[k]
+ c.Check(ok, check.Equals, true, check.Commentf("field name %q missing from userAttrsCachedFromLoginCluster", k))
+ }
+}
if h.Cluster.EnableBetaController14287 {
mux.Handle("/arvados/v1/collections", rtr)
mux.Handle("/arvados/v1/collections/", rtr)
+ mux.Handle("/arvados/v1/users", rtr)
+ mux.Handle("/arvados/v1/users/", rtr)
mux.Handle("/login", rtr)
}
"strconv"
"strings"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
)
+func guessAndParse(k, v string) (interface{}, error) {
+ // All of these form values arrive as strings, so we need some
+ // type-guessing to accept non-string inputs:
+ //
+ // Values for parameters that take ints (limit=1) or bools
+ // (include_trash=1) are parsed accordingly.
+ //
+ // "null" and "" are nil.
+ //
+ // Values that look like JSON objects, arrays, or strings are
+ // parsed as JSON.
+ //
+ // The rest are left as strings.
+ switch {
+ case intParams[k]:
+ return strconv.ParseInt(v, 10, 64)
+ case boolParams[k]:
+ return stringToBool(v), nil
+ case v == "null" || v == "":
+ return nil, nil
+ case strings.HasPrefix(v, "["):
+ var j []interface{}
+ err := json.Unmarshal([]byte(v), &j)
+ return j, err
+ case strings.HasPrefix(v, "{"):
+ var j map[string]interface{}
+ err := json.Unmarshal([]byte(v), &j)
+ return j, err
+ case strings.HasPrefix(v, "\""):
+ var j string
+ err := json.Unmarshal([]byte(v), &j)
+ return j, err
+ default:
+ return v, nil
+ }
+ // TODO: Need to accept "?foo[]=bar&foo[]=baz" as
+ // foo=["bar","baz"]?
+}
+
// Parse req as an Arvados V1 API request and return the request
// parameters.
//
// Content-Type is application/x-www-form-urlencoded -- the
// request body.
for k, values := range req.Form {
- // All of these form values arrive as strings, so we
- // need some type-guessing to accept non-string
- // inputs:
- //
- // Values for parameters that take ints (limit=1) or
- // bools (include_trash=1) are parsed accordingly.
- //
- // "null" and "" are nil.
- //
- // Values that look like JSON objects, arrays, or
- // strings are parsed as JSON.
- //
- // The rest are left as strings.
for _, v := range values {
- switch {
- case intParams[k]:
- params[k], err = strconv.ParseInt(v, 10, 64)
- if err != nil {
- return nil, err
- }
- case boolParams[k]:
- params[k] = stringToBool(v)
- case v == "null" || v == "":
- params[k] = nil
- case strings.HasPrefix(v, "["):
- var j []interface{}
- err := json.Unmarshal([]byte(v), &j)
- if err != nil {
- return nil, err
- }
- params[k] = j
- case strings.HasPrefix(v, "{"):
- var j map[string]interface{}
- err := json.Unmarshal([]byte(v), &j)
- if err != nil {
- return nil, err
- }
- params[k] = j
- case strings.HasPrefix(v, "\""):
- var j string
- err := json.Unmarshal([]byte(v), &j)
- if err != nil {
- return nil, err
- }
- params[k] = j
- default:
- params[k] = v
+ params[k], err = guessAndParse(k, v)
+ if err != nil {
+ return nil, err
}
- // TODO: Need to accept "?foo[]=bar&foo[]=baz"
- // as foo=["bar","baz"]?
}
}
return nil, httpError(http.StatusBadRequest, err)
}
for k, v := range jsonParams {
- params[k] = v
+ switch v := v.(type) {
+ case string:
+ // The Ruby "arv" cli tool sends a
+ // JSON-encode params map with
+ // JSON-encoded values.
+ dec, err := guessAndParse(k, v)
+ if err != nil {
+ return nil, err
+ }
+ jsonParams[k] = dec
+ params[k] = dec
+ default:
+ params[k] = v
+ }
}
if attrsKey != "" && params[attrsKey] == nil {
// Copy top-level parameters from JSON request
}
}
- routeParams, _ := req.Context().Value(httprouter.ParamsKey).(httprouter.Params)
- for _, p := range routeParams {
- params[p.Key] = p.Value
+ for k, v := range mux.Vars(req) {
+ params[k] = v
}
if v, ok := params[attrsKey]; ok && attrsKey != "" {
} else if tr.json {
if tr.jsonAttrsTop {
for k, v := range tr.attrs {
- param[k] = v
+ if tr.jsonStringParam {
+ j, err := json.Marshal(v)
+ if err != nil {
+ panic(err)
+ }
+ param[k] = string(j)
+ } else {
+ param[k] = v
+ }
}
} else if tr.attrs != nil {
- param[tr.attrsKey] = tr.attrs
+ if tr.jsonStringParam {
+ j, err := json.Marshal(tr.attrs)
+ if err != nil {
+ panic(err)
+ }
+ param[tr.attrsKey] = string(j)
+ } else {
+ param[tr.attrsKey] = tr.attrs
+ }
}
tr.body = bytes.NewBuffer(nil)
err := json.NewEncoder(tr.body).Encode(param)
for _, tr := range []testReq{
{attrsKey: "model_name", json: true, attrs: attrs},
{attrsKey: "model_name", json: true, attrs: attrs, jsonAttrsTop: true},
+ {attrsKey: "model_name", json: true, attrs: attrs, jsonAttrsTop: true, jsonStringParam: true},
+ {attrsKey: "model_name", json: true, attrs: attrs, jsonAttrsTop: false, jsonStringParam: true},
} {
c.Logf("tr: %#v", tr)
req := tr.Request()
"git.curoverse.com/arvados.git/sdk/go/auth"
"git.curoverse.com/arvados.git/sdk/go/ctxlog"
"git.curoverse.com/arvados.git/sdk/go/httpserver"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type router struct {
- mux *httprouter.Router
+ mux *mux.Router
fed arvados.API
}
func New(fed arvados.API) *router {
rtr := &router{
- mux: httprouter.New(),
+ mux: mux.NewRouter(),
fed: fed,
}
rtr.addRoutes()
return rtr.fed.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
},
},
+ {
+ arvados.EndpointUserCreate,
+ func() interface{} { return &arvados.CreateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserCreate(ctx, *opts.(*arvados.CreateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserMerge,
+ func() interface{} { return &arvados.UserMergeOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserMerge(ctx, *opts.(*arvados.UserMergeOptions))
+ },
+ },
+ {
+ arvados.EndpointUserActivate,
+ func() interface{} { return &arvados.UserActivateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserActivate(ctx, *opts.(*arvados.UserActivateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserSetup,
+ func() interface{} { return &arvados.UserSetupOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserSetup(ctx, *opts.(*arvados.UserSetupOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUnsetup,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUnsetup(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGetCurrent,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGetCurrent(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGetSystem,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGetSystem(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGet,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGet(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUpdateUUID,
+ func() interface{} { return &arvados.UpdateUUIDOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUpdateUUID(ctx, *opts.(*arvados.UpdateUUIDOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUpdate,
+ func() interface{} { return &arvados.UpdateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUpdate(ctx, *opts.(*arvados.UpdateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserList,
+ func() interface{} { return &arvados.ListOptions{Limit: -1} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserList(ctx, *opts.(*arvados.ListOptions))
+ },
+ },
+ {
+ arvados.EndpointUserBatchUpdate,
+ func() interface{} { return &arvados.UserBatchUpdateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserBatchUpdate(ctx, *opts.(*arvados.UserBatchUpdateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserDelete,
+ func() interface{} { return &arvados.DeleteOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserDelete(ctx, *opts.(*arvados.DeleteOptions))
+ },
+ },
} {
rtr.addRoute(route.endpoint, route.defaultOpts, route.exec)
if route.endpoint.Method == "PATCH" {
rtr.addRoute(endpointPUT, route.defaultOpts, route.exec)
}
}
- rtr.mux.NotFound = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusNotFound)
})
- rtr.mux.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusMethodNotAllowed)
})
}
func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() interface{}, exec routableFunc) {
- rtr.mux.HandlerFunc(endpoint.Method, "/"+endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.Methods(endpoint.Method).Path("/" + endpoint.Path).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
logger := ctxlog.FromContext(req.Context())
params, err := rtr.loadRequestParams(req, endpoint.AttrsKey)
if err != nil {
}
creds := auth.CredentialsFromRequest(req)
+ err = creds.LoadTokensFromHTTPRequestBody(req)
+ if err != nil {
+ rtr.sendError(w, fmt.Errorf("error loading tokens from request body: %s", err))
+ return
+ }
if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
for _, t := range rt {
if t, ok := t.(string); ok {
"git.curoverse.com/arvados.git/lib/controller/rpc"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
check "gopkg.in/check.v1"
)
func (s *RouterSuite) SetUpTest(c *check.C) {
s.stub = arvadostest.APIStub{}
s.rtr = &router{
- mux: httprouter.New(),
+ mux: mux.NewRouter(),
fed: &s.stub,
}
s.rtr.addRoutes()
params["reader_tokens"] = tokens[1:]
}
path := ep.Path
- if strings.Contains(ep.Path, "/:uuid") {
+ if strings.Contains(ep.Path, "/{uuid}") {
uuid, _ := params["uuid"].(string)
- path = strings.Replace(path, "/:uuid", "/"+uuid, 1)
+ path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
delete(params, "uuid")
}
return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
return resp, err
}
+func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserCreate
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdate
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp map[string]interface{}
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGet
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGetCurrent
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGetSystem
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ ep := arvados.EndpointUserList
+ var resp arvados.UserList
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserDelete
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
ep := arvados.EndpointAPIClientAuthorizationCurrent
var resp arvados.APIClientAuthorization
err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
return resp, err
}
+
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ ep := arvados.APIEndpoint{Method: "PATCH", Path: "arvados/v1/users/batch_update"}
+ var resp arvados.UserList
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
EndpointConfigGet = APIEndpoint{"GET", "arvados/v1/config", ""}
EndpointLogin = APIEndpoint{"GET", "login", ""}
EndpointCollectionCreate = APIEndpoint{"POST", "arvados/v1/collections", "collection"}
- EndpointCollectionUpdate = APIEndpoint{"PATCH", "arvados/v1/collections/:uuid", "collection"}
- EndpointCollectionGet = APIEndpoint{"GET", "arvados/v1/collections/:uuid", ""}
+ EndpointCollectionUpdate = APIEndpoint{"PATCH", "arvados/v1/collections/{uuid}", "collection"}
+ EndpointCollectionGet = APIEndpoint{"GET", "arvados/v1/collections/{uuid}", ""}
EndpointCollectionList = APIEndpoint{"GET", "arvados/v1/collections", ""}
- EndpointCollectionProvenance = APIEndpoint{"GET", "arvados/v1/collections/:uuid/provenance", ""}
- EndpointCollectionUsedBy = APIEndpoint{"GET", "arvados/v1/collections/:uuid/used_by", ""}
- EndpointCollectionDelete = APIEndpoint{"DELETE", "arvados/v1/collections/:uuid", ""}
- EndpointCollectionTrash = APIEndpoint{"POST", "arvados/v1/collections/:uuid/trash", ""}
- EndpointCollectionUntrash = APIEndpoint{"POST", "arvados/v1/collections/:uuid/untrash", ""}
+ EndpointCollectionProvenance = APIEndpoint{"GET", "arvados/v1/collections/{uuid}/provenance", ""}
+ EndpointCollectionUsedBy = APIEndpoint{"GET", "arvados/v1/collections/{uuid}/used_by", ""}
+ EndpointCollectionDelete = APIEndpoint{"DELETE", "arvados/v1/collections/{uuid}", ""}
+ EndpointCollectionTrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/trash", ""}
+ EndpointCollectionUntrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/untrash", ""}
EndpointSpecimenCreate = APIEndpoint{"POST", "arvados/v1/specimens", "specimen"}
- EndpointSpecimenUpdate = APIEndpoint{"PATCH", "arvados/v1/specimens/:uuid", "specimen"}
- EndpointSpecimenGet = APIEndpoint{"GET", "arvados/v1/specimens/:uuid", ""}
+ EndpointSpecimenUpdate = APIEndpoint{"PATCH", "arvados/v1/specimens/{uuid}", "specimen"}
+ EndpointSpecimenGet = APIEndpoint{"GET", "arvados/v1/specimens/{uuid}", ""}
EndpointSpecimenList = APIEndpoint{"GET", "arvados/v1/specimens", ""}
- EndpointSpecimenDelete = APIEndpoint{"DELETE", "arvados/v1/specimens/:uuid", ""}
+ EndpointSpecimenDelete = APIEndpoint{"DELETE", "arvados/v1/specimens/{uuid}", ""}
EndpointContainerCreate = APIEndpoint{"POST", "arvados/v1/containers", "container"}
- EndpointContainerUpdate = APIEndpoint{"PATCH", "arvados/v1/containers/:uuid", "container"}
- EndpointContainerGet = APIEndpoint{"GET", "arvados/v1/containers/:uuid", ""}
+ EndpointContainerUpdate = APIEndpoint{"PATCH", "arvados/v1/containers/{uuid}", "container"}
+ EndpointContainerGet = APIEndpoint{"GET", "arvados/v1/containers/{uuid}", ""}
EndpointContainerList = APIEndpoint{"GET", "arvados/v1/containers", ""}
- EndpointContainerDelete = APIEndpoint{"DELETE", "arvados/v1/containers/:uuid", ""}
- EndpointContainerLock = APIEndpoint{"POST", "arvados/v1/containers/:uuid/lock", ""}
- EndpointContainerUnlock = APIEndpoint{"POST", "arvados/v1/containers/:uuid/unlock", ""}
+ EndpointContainerDelete = APIEndpoint{"DELETE", "arvados/v1/containers/{uuid}", ""}
+ EndpointContainerLock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/lock", ""}
+ EndpointContainerUnlock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/unlock", ""}
+ EndpointUserActivate = APIEndpoint{"POST", "arvados/v1/users/{uuid}/activate", ""}
+ EndpointUserCreate = APIEndpoint{"POST", "arvados/v1/users", "user"}
+ EndpointUserCurrent = APIEndpoint{"GET", "arvados/v1/users/current", ""}
+ EndpointUserDelete = APIEndpoint{"DELETE", "arvados/v1/users/{uuid}", ""}
+ EndpointUserGet = APIEndpoint{"GET", "arvados/v1/users/{uuid}", ""}
+ EndpointUserGetCurrent = APIEndpoint{"GET", "arvados/v1/users/current", ""}
+ EndpointUserGetSystem = APIEndpoint{"GET", "arvados/v1/users/system", ""}
+ EndpointUserList = APIEndpoint{"GET", "arvados/v1/users", ""}
+ EndpointUserMerge = APIEndpoint{"POST", "arvados/v1/users/merge", ""}
+ EndpointUserSetup = APIEndpoint{"POST", "arvados/v1/users/setup", ""}
+ EndpointUserSystem = APIEndpoint{"GET", "arvados/v1/users/system", ""}
+ EndpointUserUnsetup = APIEndpoint{"POST", "arvados/v1/users/{uuid}/unsetup", ""}
+ EndpointUserUpdate = APIEndpoint{"PATCH", "arvados/v1/users/{uuid}", "user"}
+ EndpointUserUpdateUUID = APIEndpoint{"POST", "arvados/v1/users/{uuid}/update_uuid", ""}
+ EndpointUserBatchUpdate = APIEndpoint{"PATCH", "arvados/v1/users/batch", ""}
EndpointAPIClientAuthorizationCurrent = APIEndpoint{"GET", "arvados/v1/api_client_authorizations/current", ""}
)
Attrs map[string]interface{} `json:"attrs"`
}
+type UpdateUUIDOptions struct {
+ UUID string `json:"uuid"`
+ NewUUID string `json:"new_uuid"`
+}
+
+type UserActivateOptions struct {
+ UUID string `json:"uuid"`
+}
+
+type UserSetupOptions struct {
+ UUID string `json:"uuid"`
+ Email string `json:"email"`
+ OpenIDPrefix string `json:"openid_prefix"`
+ RepoName string `json:"repo_name"`
+ VMUUID string `json:"vm_uuid"`
+ SendNotificationEmail bool `json:"send_notification_email"`
+ Attrs map[string]interface{} `json:"attrs"`
+}
+
+type UserMergeOptions struct {
+ NewUserUUID string `json:"new_user_uuid,omitempty"`
+ OldUserUUID string `json:"old_user_uuid,omitempty"`
+ NewUserToken string `json:"new_user_token,omitempty"`
+}
+
+type UserBatchUpdateOptions struct {
+ Updates map[string]map[string]interface{} `json:"updates"`
+}
+
+type UserBatchUpdateResponse struct{}
+
type DeleteOptions struct {
UUID string `json:"uuid"`
}
SpecimenGet(ctx context.Context, options GetOptions) (Specimen, error)
SpecimenList(ctx context.Context, options ListOptions) (SpecimenList, error)
SpecimenDelete(ctx context.Context, options DeleteOptions) (Specimen, error)
+ UserCreate(ctx context.Context, options CreateOptions) (User, error)
+ UserUpdate(ctx context.Context, options UpdateOptions) (User, error)
+ UserUpdateUUID(ctx context.Context, options UpdateUUIDOptions) (User, error)
+ UserMerge(ctx context.Context, options UserMergeOptions) (User, error)
+ UserActivate(ctx context.Context, options UserActivateOptions) (User, error)
+ UserSetup(ctx context.Context, options UserSetupOptions) (map[string]interface{}, error)
+ UserUnsetup(ctx context.Context, options GetOptions) (User, error)
+ UserGet(ctx context.Context, options GetOptions) (User, error)
+ UserGetCurrent(ctx context.Context, options GetOptions) (User, error)
+ UserGetSystem(ctx context.Context, options GetOptions) (User, error)
+ UserList(ctx context.Context, options ListOptions) (UserList, error)
+ UserDelete(ctx context.Context, options DeleteOptions) (User, error)
+ UserBatchUpdate(context.Context, UserBatchUpdateOptions) (UserList, error)
APIClientAuthorizationCurrent(ctx context.Context, options GetOptions) (APIClientAuthorization, error)
}
package arvados
+import "time"
+
// User is an arvados#user record
type User struct {
- UUID string `json:"uuid"`
- IsActive bool `json:"is_active"`
- IsAdmin bool `json:"is_admin"`
- Username string `json:"username"`
- Email string `json:"email"`
+ UUID string `json:"uuid"`
+ IsActive bool `json:"is_active"`
+ IsAdmin bool `json:"is_admin"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ FullName string `json:"full_name"`
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ IdentityURL string `json:"identity_url"`
+ IsInvited bool `json:"is_invited"`
+ OwnerUUID string `json:"owner_uuid"`
+ CreatedAt time.Time `json:"created_at"`
+ ModifiedAt time.Time `json:"modified_at"`
+ ModifiedByUserUUID string `json:"modified_by_user_uuid"`
+ ModifiedByClientUUID string `json:"modified_by_client_uuid"`
+ Prefs map[string]interface{} `json:"prefs"`
}
// UserList is an arvados#userList resource.
as.appendCall(as.SpecimenDelete, ctx, options)
return arvados.Specimen{}, as.Error
}
+func (as *APIStub) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ as.appendCall(as.UserCreate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ as.appendCall(as.UserUpdate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ as.appendCall(as.UserUpdateUUID, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ as.appendCall(as.UserActivate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ as.appendCall(as.UserSetup, ctx, options)
+ return nil, as.Error
+}
+func (as *APIStub) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserUnsetup, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGet, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGetCurrent, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGetSystem, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ as.appendCall(as.UserList, ctx, options)
+ return arvados.UserList{}, as.Error
+}
+func (as *APIStub) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ as.appendCall(as.UserDelete, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ as.appendCall(as.UserMerge, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ as.appendCall(as.UserBatchUpdate, ctx, options)
+ return arvados.UserList{}, as.Error
+}
func (as *APIStub) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
as.appendCall(as.APIClientAuthorizationCurrent, ctx, options)
return arvados.APIClientAuthorization{}, as.Error
defer as.mtx.Unlock()
var calls []APIStubCall
for _, call := range as.calls {
-
if method == nil || (runtime.FuncForPC(reflect.ValueOf(call.Method).Pointer()).Name() ==
runtime.FuncForPC(reflect.ValueOf(method).Pointer()).Name()) {
calls = append(calls, call)
AdminToken = "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h"
AnonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
DataManagerToken = "320mkve8qkswstz7ff61glpk3mhgghmg67wmic7elw4z41pke1"
+ SystemRootToken = "systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy"
ManagementToken = "jg3ajndnq63sywcd50gbs5dskdc9ckkysb0nsqmfz08nwf17nl"
ActiveUserUUID = "zzzzz-tpzed-xurymjxw79nv3jz"
FederatedActiveUserUUID = "zbbbb-tpzed-xurymjxw79nv3jz"
"zzzzz": {
"EnableBetaController14287": ('14287' in os.environ.get('ARVADOS_EXPERIMENTAL', '')),
"ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
- "SystemRootToken": auth_token('data_manager'),
+ "SystemRootToken": auth_token('system_user'),
"API": {
"RequestTimeout": "30s",
},
uglifier (~> 2.0)
BUNDLED WITH
- 2.0.2
+ 1.17.3
class Arvados::V1::UsersController < ApplicationController
accept_attribute_as_json :prefs, Hash
+ accept_param_as_json :updates
skip_before_action :find_object_by_uuid, only:
- [:activate, :current, :system, :setup, :merge]
+ [:activate, :current, :system, :setup, :merge, :batch_update]
skip_before_action :render_404_if_no_object, only:
- [:activate, :current, :system, :setup, :merge]
- before_action :admin_required, only: [:setup, :unsetup, :update_uuid]
+ [:activate, :current, :system, :setup, :merge, :batch_update]
+ before_action :admin_required, only: [:setup, :unsetup, :update_uuid, :batch_update]
+
+ # Internal API used by controller to update local cache of user
+ # records from LoginCluster.
+ def batch_update
+ @objects = []
+ params[:updates].andand.each do |uuid, attrs|
+ begin
+ u = User.find_or_create_by(uuid: uuid)
+ rescue ActiveRecord::RecordNotUnique
+ retry
+ end
+ u.update_attributes!(attrs)
+ @objects << u
+ end
+ @offset = 0
+ @limit = -1
+ render_list
+ end
def current
if current_user
# Sync user record.
if remote_user_prefix == Rails.configuration.Login.LoginCluster
- # Remote cluster controls our user database, copy both
- # 'is_active' and 'is_admin'
- user.is_active = remote_user['is_active']
+ # Remote cluster controls our user database, set is_active if
+ # remote is active. If remote is not active, user will be
+ # unsetup (see below).
+ user.is_active = true if remote_user['is_active']
user.is_admin = remote_user['is_admin']
else
if Rails.configuration.Users.NewUsersAreActive ||
Rails.configuration.RemoteClusters[remote_user_prefix].andand["ActivateUsers"]
- # Default policy is to activate users, so match activate
- # with the remote record.
- user.is_active = remote_user['is_active']
- elsif !remote_user['is_active']
- # Deactivate user if the remote is inactive, otherwise don't
- # change 'is_active'.
- user.is_active = false
+ # Default policy is to activate users
+ user.is_active = true if remote_user['is_active']
end
end
end
act_as_system_user do
+ if user.is_active && !remote_user['is_active']
+ user.unsetup
+ end
+
user.save!
# We will accept this token (and avoid reloading the user
},
uniqueness: true,
allow_nil: true)
+ validate :must_unsetup_to_deactivate
before_update :prevent_privilege_escalation
before_update :prevent_inactive_admin
before_update :verify_repositories_empty, :if => Proc.new { |user|
# create links
def setup(openid_prefix:, repo_name: nil, vm_uuid: nil)
- oid_login_perm = create_oid_login_perm openid_prefix
repo_perm = create_user_repo_link repo_name
vm_login_perm = create_vm_login_permission_link(vm_uuid, username) if vm_uuid
group_perm = create_user_group_link
- return [oid_login_perm, repo_perm, vm_login_perm, group_perm, self].compact
+ return [repo_perm, vm_login_perm, group_perm, self].compact
end
# delete user signatures, login, repo, and vm perms, and mark as inactive
def unsetup
# delete oid_login_perms for this user
+ #
+ # note: these permission links are obsolete, they have no effect
+ # on anything and they are not created for new users.
Link.where(tail_uuid: self.email,
link_class: 'permission',
name: 'can_login').destroy_all
self.save!
end
+ def must_unsetup_to_deactivate
+ if self.is_active_changed? &&
+ self.is_active_was == true &&
+ !self.is_active
+
+ group = Group.where(name: 'All users').select do |g|
+ g[:uuid].match(/-f+$/)
+ end.first
+
+ # When a user is set up, they are added to the "All users"
+ # group. A user that is part of the "All users" group is
+ # allowed to self-activate.
+ #
+ # It doesn't make sense to deactivate a user (set is_active =
+ # false) without first removing them from the "All users" group,
+ # because they would be able to immediately reactivate
+ # themselves.
+ #
+ # The 'unsetup' method removes the user from the "All users"
+ # group (and also sets is_active = false) so send a message
+ # explaining the correct way to deactivate a user.
+ #
+ if Link.where(tail_uuid: self.uuid,
+ head_uuid: group[:uuid],
+ link_class: 'permission',
+ name: 'can_read').any?
+ errors.add :is_active, "cannot be set to false directly, use the 'Deactivate' button on Workbench, or the 'unsetup' API call"
+ end
+ end
+ end
+
def set_initial_username(requested: false)
if !requested.is_a?(String) || requested.empty?
email_parts = email.partition("@")
merged
end
- def create_oid_login_perm(openid_prefix)
- # Check oid_login_perm
- oid_login_perms = Link.where(tail_uuid: self.email,
- head_uuid: self.uuid,
- link_class: 'permission',
- name: 'can_login')
-
- if !oid_login_perms.any?
- # create openid login permission
- oid_login_perm = Link.create!(link_class: 'permission',
- name: 'can_login',
- tail_uuid: self.email,
- head_uuid: self.uuid,
- properties: {
- "identity_url_prefix" => openid_prefix,
- })
- logger.info { "openid login permission: " + oid_login_perm[:uuid] }
- else
- oid_login_perm = oid_login_perms.first
- end
-
- return oid_login_perm
- end
-
def create_user_repo_link(repo_name)
# repo_name is optional
if not repo_name
post 'unsetup', on: :member
post 'update_uuid', on: :member
post 'merge', on: :collection
+ patch 'batch_update', on: :collection
end
resources :virtual_machines do
get 'logins', on: :member
end
act_as_system_user do
u = users(:active)
- u.is_active = false
+ u.unsetup
u.save!
end
authorize_with :admin
assert_not_nil created['email'], 'expected non-nil email'
assert_nil created['identity_url'], 'expected no identity_url'
- # arvados#user, repo link and link add user to 'All users' group
- verify_links_added 4
-
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'User'
+ # repo link and link add user to 'All users' group
+ verify_links_added 3
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
"foo/#{repo_name}", created['uuid'], 'arvados#repository', true, 'Repository'
assert_not_nil response_object['uuid'], 'expected uuid for the new user'
assert_equal response_object['email'], 'foo@example.com', 'expected given email'
- # four extra links; system_group, login, group and repo perms
- verify_links_added 4
+ # three extra links; system_group, group and repo perms
+ verify_links_added 3
end
test "setup user with fake vm and expect error" do
assert_not_nil response_object['uuid'], 'expected uuid for the new user'
assert_equal response_object['email'], 'foo@example.com', 'expected given email'
- # five extra links; system_group, login, group, vm, repo
- verify_links_added 5
+ # four extra links; system_group, group, vm, repo
+ verify_links_added 4
end
test "setup user with valid email, no vm and no repo as input" do
assert_not_nil response_object['uuid'], 'expected uuid for new user'
assert_equal response_object['email'], 'foo@example.com', 'expected given email'
- # three extra links; system_group, login, and group
- verify_links_added 3
-
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- response_object['uuid'], response_object['email'], 'arvados#user', false, 'User'
+ # two extra links; system_group, and group
+ verify_links_added 2
verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
'All users', response_object['uuid'], 'arvados#group', true, 'Group'
assert_equal 'test_first_name', response_object['first_name'],
'expecting first name'
- # five extra links; system_group, login, group, repo and vm
- verify_links_added 5
+ # four extra links; system_group, group, repo and vm
+ verify_links_added 4
end
test "setup user with an existing user email and check different object is created" do
assert_not_equal response_object['uuid'], inactive_user['uuid'],
'expected different uuid after create operation'
assert_equal inactive_user['email'], response_object['email'], 'expected given email'
- # system_group, openid, group, and repo. No vm link.
- verify_links_added 4
+ # system_group, group, and repo. No vm link.
+ verify_links_added 3
end
test "setup user with openid prefix" do
assert_nil created['identity_url'], 'expected no identity_url'
# verify links
- # four new links: system_group, arvados#user, repo, and 'All users' group.
- verify_links_added 4
-
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'User'
+ # three new links: system_group, repo, and 'All users' group.
+ verify_links_added 3
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
assert_not_nil created['email'], 'expected non-nil email'
assert_nil created['identity_url'], 'expected no identity_url'
- # five new links: system_group, arvados#user, repo, vm and 'All
- # users' group link
- verify_links_added 5
+ # four new links: system_group, repo, vm and 'All users' group link
+ verify_links_added 4
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'User'
+ # system_group isn't part of the response. See User#add_system_group_permission_link
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
assert_nil(users(:project_viewer).redirect_to_user_uuid)
end
+ test "batch update fails for non-admin" do
+ authorize_with(:active)
+ patch(:batch_update, params: {updates: {}})
+ assert_response(403)
+ end
+
+ test "batch update" do
+ existinguuid = 'remot-tpzed-foobarbazwazqux'
+ newuuid = 'remot-tpzed-newnarnazwazqux'
+ act_as_system_user do
+ User.create!(uuid: existinguuid, email: 'root@existing.example.com')
+ end
+
+ authorize_with(:admin)
+ patch(:batch_update,
+ params: {
+ updates: {
+ existinguuid => {
+ 'first_name' => 'root',
+ 'email' => 'root@remot.example.com',
+ 'is_active' => true,
+ 'is_admin' => true,
+ 'prefs' => {'foo' => 'bar'},
+ },
+ newuuid => {
+ 'first_name' => 'noot',
+ 'email' => 'root@remot.example.com',
+ },
+ }})
+ assert_response(:success)
+
+ assert_equal('root', User.find_by_uuid(existinguuid).first_name)
+ assert_equal('root@remot.example.com', User.find_by_uuid(existinguuid).email)
+ assert_equal(true, User.find_by_uuid(existinguuid).is_active)
+ assert_equal(true, User.find_by_uuid(existinguuid).is_admin)
+ assert_equal({'foo' => 'bar'}, User.find_by_uuid(existinguuid).prefs)
+
+ assert_equal('noot', User.find_by_uuid(newuuid).first_name)
+ assert_equal('root@remot.example.com', User.find_by_uuid(newuuid).email)
+ end
+
NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
"last_name", "username"].sort
oid_login_perms = Link.where(tail_uuid: email,
link_class: 'permission',
name: 'can_login').where("head_uuid like ?", User.uuid_like_pattern)
- if expect_oid_login_perms
- assert oid_login_perms.any?, "expected oid_login_perms"
- else
- assert !oid_login_perms.any?, "expected all oid_login_perms deleted"
- end
+
+ # these don't get added any more! they shouldn't appear ever.
+ assert !oid_login_perms.any?, "expected all oid_login_perms deleted"
repo_perms = Link.where(tail_uuid: uuid,
link_class: 'permission',
assert_equal 'blarney@example.com', json_response['email']
end
+ test 'remote user is deactivated' do
+ Rails.configuration.RemoteClusters['zbbbb'].ActivateUsers = true
+ get '/arvados/v1/users/current',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_response :success
+ assert_equal true, json_response['is_active']
+
+ # revoke original token
+ @stub_content[:is_active] = false
+
+ # simulate cache expiry
+ ApiClientAuthorization.where(
+ uuid: salted_active_token(remote: 'zbbbb').split('/')[1]).
+ update_all(expires_at: db_current_time - 1.minute)
+
+ # re-authorize after cache expires
+ get '/arvados/v1/users/current',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_equal false, json_response['is_active']
+
+ end
+
test 'authenticate with remote token, remote username conflicts with local' do
@stub_content[:username] = 'active'
get '/arvados/v1/users/current',
refute_includes(group_uuids, groups(:testusergroup_admins).uuid)
end
+ test 'do not auto-activate user from untrusted cluster' do
+ Rails.configuration.RemoteClusters['zbbbb'].AutoSetupNewUsers = false
+ Rails.configuration.RemoteClusters['zbbbb'].ActivateUsers = false
+ get '/arvados/v1/users/current',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_response :success
+ assert_equal 'zbbbb-tpzed-000000000000000', json_response['uuid']
+ assert_equal false, json_response['is_admin']
+ assert_equal false, json_response['is_active']
+ assert_equal 'foo@example.com', json_response['email']
+ assert_equal 'barney', json_response['username']
+ post '/arvados/v1/users/zbbbb-tpzed-000000000000000/activate',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_response 422
+ end
+
test 'auto-activate user from trusted cluster' do
Rails.configuration.RemoteClusters['zbbbb'].ActivateUsers = true
get '/arvados/v1/users/current',
].each do |testcase|
test "user auto-activate #{testcase.inspect}" do
# Configure auto_setup behavior according to testcase[:cfg]
+ Rails.configuration.Users.NewUsersAreActive = false
Rails.configuration.Users.AutoSetupNewUsers = testcase[:cfg][:auto]
Rails.configuration.Users.AutoSetupNewUsersWithVmUUID =
(testcase[:cfg][:vm] ? virtual_machines(:testvm).uuid : "")
assert_not_nil created['email'], 'expected non-nil email'
assert_nil created['identity_url'], 'expected no identity_url'
- # arvados#user, repo link and link add user to 'All users' group
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'arvados#user'
+ # repo link and link add user to 'All users' group
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
assert_not_nil created['email'], 'expected non-nil email'
assert_equal created['email'], 'foo@example.com', 'expected input email'
- # three new links: system_group, arvados#user, and 'All users' group.
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'arvados#user'
+ # two new links: system_group, and 'All users' group.
verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
'All users', created['uuid'], 'arvados#group', true, 'Group'
assert_not_nil created['uuid'], 'expected uuid for the new user'
assert_equal created['email'], 'foo@example.com', 'expected given email'
- # five extra links: system_group, login, group, repo and vm
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'arvados#user'
+ # four extra links: system_group, login, group, repo and vm
verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
'All users', created['uuid'], 'arvados#group', true, 'Group'
end
+ test "cannot set is_activate to false directly" do
+ post('/arvados/v1/users',
+ params: {
+ user: {
+ email: "bob@example.com",
+ username: "bobby"
+ },
+ },
+ headers: auth(:admin))
+ assert_response(:success)
+ user = json_response
+ assert_equal false, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: auth(:admin))
+ assert_response(:success)
+ user = json_response
+ assert_equal true, user['is_active']
+
+ put("/arvados/v1/users/#{user['uuid']}",
+ params: {
+ user: {is_active: false}
+ },
+ headers: auth(:admin))
+ assert_response 422
+ end
+
+ test "cannot self activate when AutoSetupNewUsers is false" do
+ Rails.configuration.Users.NewUsersAreActive = false
+ Rails.configuration.Users.AutoSetupNewUsers = false
+
+ user = nil
+ token = nil
+ act_as_system_user do
+ user = User.create!(email: "bob@example.com", username: "bobby")
+ ap = ApiClientAuthorization.create!(user: user, api_client: ApiClient.all.first)
+ token = ap.api_token
+ end
+
+ get("/arvados/v1/users/#{user['uuid']}",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response(:success)
+ user = json_response
+ assert_equal false, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response 422
+ assert_match(/Cannot activate without being invited/, json_response['errors'][0])
+ end
+
+
+ test "cannot self activate after unsetup" do
+ Rails.configuration.Users.NewUsersAreActive = false
+ Rails.configuration.Users.AutoSetupNewUsers = false
+
+ user = nil
+ token = nil
+ act_as_system_user do
+ user = User.create!(email: "bob@example.com", username: "bobby")
+ ap = ApiClientAuthorization.create!(user: user, api_client_id: 0)
+ token = ap.api_token
+ end
+
+ post("/arvados/v1/users/setup",
+ params: {uuid: user['uuid']},
+ headers: auth(:admin))
+ assert_response :success
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response 403
+ assert_match(/Cannot activate without user agreements/, json_response['errors'][0])
+
+ post("/arvados/v1/user_agreements/sign",
+ params: {uuid: 'zzzzz-4zz18-t68oksiu9m80s4y'},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response :success
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response :success
+
+ get("/arvados/v1/users/#{user['uuid']}",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response(:success)
+ user = json_response
+ assert_equal true, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/unsetup",
+ params: {},
+ headers: auth(:admin))
+ assert_response :success
+
+ get("/arvados/v1/users/#{user['uuid']}",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response(:success)
+ user = json_response
+ assert_equal false, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response 422
+ assert_match(/Cannot activate without being invited/, json_response['errors'][0])
+ end
+
+
end
resp_user = find_obj_in_resp response, 'User'
verify_user resp_user, email
- oid_login_perm = find_obj_in_resp response, 'Link', 'arvados#user'
-
- verify_link oid_login_perm, 'permission', 'can_login', resp_user[:email],
- resp_user[:uuid]
-
- assert_equal openid_prefix, oid_login_perm[:properties]['identity_url_prefix'],
- 'expected identity_url_prefix not found for oid_login_perm'
-
group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
resp_user = find_obj_in_resp response, 'User'
verify_user resp_user, email
- oid_login_perm = find_obj_in_resp response, 'Link', 'arvados#user'
-
- verify_link oid_login_perm, 'permission', 'can_login', resp_user[:email],
- resp_user[:uuid]
-
- assert_equal openid_prefix, oid_login_perm[:properties]['identity_url_prefix'],
- 'expected identity_url_prefix not found for oid_login_perm'
-
group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
resp_user = find_obj_in_resp response, 'User'
verify_user resp_user, email
- oid_login_perm = find_obj_in_resp response, 'Link', 'arvados#user'
- verify_link oid_login_perm, 'permission', 'can_login', resp_user[:email],
- resp_user[:uuid]
- assert_equal openid_prefix, oid_login_perm[:properties]['identity_url_prefix'],
- 'expected identity_url_prefix not found for oid_login_perm'
-
group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
verify_link_exists(Rails.configuration.Users.AutoSetupNewUsers || active,
groups(:all_users).uuid, user.uuid,
"permission", "can_read")
- # Check for OID login link.
- verify_link_exists(Rails.configuration.Users.AutoSetupNewUsers || active,
- user.uuid, user.email, "permission", "can_login")
+
# Check for repository.
if named_repo = (prior_repo or
Repository.where(name: expect_repo_name).first)
arvadostest.StartKeep(4, true)
arv, err := arvadosclient.MakeArvadosClient()
- arv.ApiToken = arvadostest.DataManagerToken
+ arv.ApiToken = arvadostest.SystemRootToken
c.Assert(err, check.IsNil)
s.keepClient, err = keepclient.MakeKeepClient(arv)
s.client = &arvados.Client{
APIHost: os.Getenv("ARVADOS_API_HOST"),
- AuthToken: arvadostest.DataManagerToken,
+ AuthToken: arvadostest.SystemRootToken,
Insecure: true,
}
}
_, _, err = kc.PutB([]byte("some-more-index-data"))
c.Check(err, IsNil)
- kc.Arvados.ApiToken = arvadostest.DataManagerToken
+ kc.Arvados.ApiToken = arvadostest.SystemRootToken
// Invoke GetIndex
for _, spec := range []struct {
if err != nil {
t.Fatal(err)
}
- cluster.SystemRootToken = arvadostest.DataManagerToken
+ cluster.SystemRootToken = arvadostest.SystemRootToken
cluster.ManagementToken = arvadostest.ManagementToken
cluster.Collections.BlobSigning = false
return cluster
c.Check(resp.Body.String(), check.Equals, "Unauthorized\n")
}
- tok := arvadostest.DataManagerToken
+ tok := arvadostest.SystemRootToken
// Nonexistent mount UUID
resp = s.call("GET", "/mounts/X/blocks", tok, nil)
s.remoteAPI.StartTLS()
s.cluster = testCluster(c)
s.cluster.Collections.BlobSigningKey = knownKey
- s.cluster.SystemRootToken = arvadostest.DataManagerToken
+ s.cluster.SystemRootToken = arvadostest.SystemRootToken
s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
s.remoteClusterID: arvados.RemoteCluster{
Host: strings.Split(s.remoteAPI.URL, "//")[1],
rake
BUNDLED WITH
- 2.0.2
+ 1.17.3
cat <<EOF > /usr/src/workbench2/public/config.json
{
"API_HOST": "${localip}:${services[controller-ssl]}",
- "VOCABULARY_URL": "vocabulary-example.json",
- "FILE_VIEWERS_CONFIG_URL": "file-viewers-example.json"
+ "VOCABULARY_URL": "/vocabulary-example.json",
+ "FILE_VIEWERS_CONFIG_URL": "/file-viewers-example.json"
}
EOF
// srcConfig
var srcConfig apiConfig
srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- srcConfig.APIToken = arvadostest.DataManagerToken
+ srcConfig.APIToken = arvadostest.SystemRootToken
srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
// dstConfig
var dstConfig apiConfig
dstConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- dstConfig.APIToken = arvadostest.DataManagerToken
+ dstConfig.APIToken = arvadostest.SystemRootToken
dstConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
if enforcePermissions {
c.Check(err, IsNil)
c.Assert(srcConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
- c.Assert(srcConfig.APIToken, Equals, arvadostest.DataManagerToken)
+ c.Assert(srcConfig.APIToken, Equals, arvadostest.SystemRootToken)
c.Assert(srcConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
c.Assert(srcConfig.ExternalClient, Equals, false)
c.Check(err, IsNil)
c.Assert(dstConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
- c.Assert(dstConfig.APIToken, Equals, arvadostest.DataManagerToken)
+ c.Assert(dstConfig.APIToken, Equals, arvadostest.SystemRootToken)
c.Assert(dstConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
c.Assert(dstConfig.ExternalClient, Equals, false)
func (s *ServerNotRequiredSuite) TestSetupKeepClient_NoBlobSignatureTTL(c *C) {
var srcConfig apiConfig
srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- srcConfig.APIToken = arvadostest.DataManagerToken
+ srcConfig.APIToken = arvadostest.SystemRootToken
srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
_, ttl, err := setupKeepClient(srcConfig, srcKeepServicesJSON, false, 0, 0)
c.Check(err, IsNil)
fileContent := "ARVADOS_API_HOST=" + os.Getenv("ARVADOS_API_HOST") + "\n"
- fileContent += "ARVADOS_API_TOKEN=" + arvadostest.DataManagerToken + "\n"
+ fileContent += "ARVADOS_API_TOKEN=" + arvadostest.SystemRootToken + "\n"
fileContent += "ARVADOS_API_HOST_INSECURE=" + os.Getenv("ARVADOS_API_HOST_INSECURE") + "\n"
fileContent += "ARVADOS_EXTERNAL_CLIENT=false\n"
fileContent += "ARVADOS_BLOB_SIGNING_KEY=abcdefg"
--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+import argparse
+import copy
+import json
+import logging
+import os
+import sys
+
+import arvados
+import arvados.util
+
+logger = logging.getLogger('arvados.vocabulary_migrate')
+logger.setLevel(logging.INFO)
+
+class VocabularyError(Exception):
+ pass
+
+opts = argparse.ArgumentParser(add_help=False)
+opts.add_argument('--vocabulary-file', type=str, metavar='PATH', required=True,
+ help="""
+Use vocabulary definition file at PATH for migration decisions.
+""")
+opts.add_argument('--dry-run', action='store_true', default=False,
+ help="""
+Don't actually migrate properties, but only check if any collection/project
+should be migrated.
+""")
+opts.add_argument('--debug', action='store_true', default=False,
+ help="""
+Sets logging level to DEBUG.
+""")
+arg_parser = argparse.ArgumentParser(
+ description='Migrate collections & projects properties to the new vocabulary format.',
+ parents=[opts])
+
+def parse_arguments(arguments):
+ args = arg_parser.parse_args(arguments)
+ if args.debug:
+ logger.setLevel(logging.DEBUG)
+ if not os.path.isfile(args.vocabulary_file):
+ arg_parser.error("{} doesn't exist or isn't a file.".format(args.vocabulary_file))
+ return args
+
+def _label_to_id_mappings(data, obj_name):
+ result = {}
+ for obj_id, obj_data in data.items():
+ for lbl in obj_data['labels']:
+ obj_lbl = lbl['label']
+ if obj_lbl not in result:
+ result[obj_lbl] = obj_id
+ else:
+ raise VocabularyError('{} label "{}" for {} ID "{}" already seen at {} ID "{}".'.format(obj_name, obj_lbl, obj_name, obj_id, obj_name, result[obj_lbl]))
+ return result
+
+def key_labels_to_ids(vocab):
+ return _label_to_id_mappings(vocab['tags'], 'key')
+
+def value_labels_to_ids(vocab, key_id):
+ if key_id in vocab['tags'] and 'values' in vocab['tags'][key_id]:
+ return _label_to_id_mappings(vocab['tags'][key_id]['values'], 'value')
+ return {}
+
+def migrate_properties(properties, key_map, vocab):
+ result = {}
+ for k, v in properties.items():
+ key = key_map.get(k, k)
+ value = value_labels_to_ids(vocab, key).get(v, v)
+ result[key] = value
+ return result
+
+def main(arguments=None):
+ args = parse_arguments(arguments)
+ vocab = None
+ with open(args.vocabulary_file, 'r') as f:
+ vocab = json.load(f)
+ arv = arvados.api('v1')
+ if 'tags' not in vocab or vocab['tags'] == {}:
+ logger.warning('Empty vocabulary file, exiting.')
+ return 1
+ if not arv.users().current().execute()['is_admin']:
+ logger.error('Admin privileges required.')
+ return 1
+ key_label_to_id_map = key_labels_to_ids(vocab)
+ migrated_counter = 0
+
+ for key_label in key_label_to_id_map:
+ logger.debug('Querying objects with property key "{}"'.format(key_label))
+ for resource in [arv.collections(), arv.groups()]:
+ objs = arvados.util.list_all(
+ resource.list,
+ order=['created_at'],
+ select=['uuid', 'properties'],
+ filters=[['properties', 'exists', key_label]]
+ )
+ for o in objs:
+ props = copy.copy(o['properties'])
+ migrated_props = migrate_properties(props, key_label_to_id_map, vocab)
+ if not args.dry_run:
+ logger.debug('Migrating {}: {} -> {}'.format(o['uuid'], props, migrated_props))
+ arv.collections().update(uuid=o['uuid'], body={
+ 'properties': migrated_props
+ }).execute()
+ else:
+ logger.info('Should migrate {}: {} -> {}'.format(o['uuid'], props, migrated_props))
+ migrated_counter += 1
+ if not args.dry_run and migrated_counter % 100 == 0:
+ logger.info('Migrating {} objects...'.format(migrated_counter))
+
+ if args.dry_run and migrated_counter == 0:
+ logger.info('Nothing to do.')
+ elif not args.dry_run:
+ logger.info('Done, total objects migrated: {}.'.format(migrated_counter))
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
\ No newline at end of file