looker-sdk 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +22 -0
- data/LICENSE +21 -0
- data/Rakefile +37 -0
- data/authentication.md +104 -0
- data/examples/add_delete_users.rb +94 -0
- data/examples/change_credentials_email_address_for_users.rb +23 -0
- data/examples/create_credentials_email_for_users.rb +19 -0
- data/examples/delete_all_user_sessions.rb +15 -0
- data/examples/delete_credentials_google_for_users.rb +19 -0
- data/examples/generate_password_reset_tokens_for_users.rb +19 -0
- data/examples/ldap_roles_test.rb +50 -0
- data/examples/me.rb +3 -0
- data/examples/refresh_user_notification_addresses.rb +10 -0
- data/examples/roles_and_users_with_permission.rb +22 -0
- data/examples/sdk_setup.rb +21 -0
- data/examples/streaming_downloads.rb +20 -0
- data/examples/users_with_credentials_email.rb +6 -0
- data/examples/users_with_credentials_google.rb +8 -0
- data/examples/users_with_credentials_google_without_credentials_email.rb +6 -0
- data/lib/looker-sdk.rb +32 -0
- data/lib/looker-sdk/authentication.rb +104 -0
- data/lib/looker-sdk/client.rb +445 -0
- data/lib/looker-sdk/client/dynamic.rb +107 -0
- data/lib/looker-sdk/configurable.rb +116 -0
- data/lib/looker-sdk/default.rb +148 -0
- data/lib/looker-sdk/error.rb +235 -0
- data/lib/looker-sdk/rate_limit.rb +33 -0
- data/lib/looker-sdk/response/raise_error.rb +20 -0
- data/lib/looker-sdk/sawyer_patch.rb +33 -0
- data/lib/looker-sdk/version.rb +7 -0
- data/looker-sdk.gemspec +27 -0
- data/readme.md +117 -0
- data/shell/.gitignore +41 -0
- data/shell/Gemfile +6 -0
- data/shell/readme.md +18 -0
- data/shell/shell.rb +37 -0
- data/streaming.md +59 -0
- data/test/helper.rb +46 -0
- data/test/looker/swagger.json +1998 -0
- data/test/looker/test_client.rb +258 -0
- data/test/looker/test_dynamic_client.rb +158 -0
- data/test/looker/test_dynamic_client_agent.rb +131 -0
- data/test/looker/user.json +1 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a9c372139d8df28af2b71bce4382f3badfa6d1c8
|
4
|
+
data.tar.gz: 66910d788e066ef3767f76966e1973ca7312e3c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 043052d8df89d53724b1fe23e181ae5eafb598ff4aca2dd44d8d030510ffe98dffeb8c51a9a180c594b64597500d42b13db84f08d1969fcdb3d571c98e9eb49e
|
7
|
+
data.tar.gz: 8627156eb5061245b16330094381786800e1c32c1fbb8340297e962429a97ad14cc75e2fc2fe8073297e4c7b61751298c629da8ee65721c38c0b5975b643f644
|
data/.gitignore
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Numerous always-ignore extensions
|
2
|
+
*.diff
|
3
|
+
*.err
|
4
|
+
*.orig
|
5
|
+
*.log
|
6
|
+
*.rej
|
7
|
+
*.swo
|
8
|
+
*.swp
|
9
|
+
*.vi
|
10
|
+
*~
|
11
|
+
*.sass-cache
|
12
|
+
*.iml
|
13
|
+
|
14
|
+
# OS or Editor folders
|
15
|
+
.DS_Store
|
16
|
+
.cache
|
17
|
+
.project
|
18
|
+
.settings
|
19
|
+
.tmproj
|
20
|
+
nbproject
|
21
|
+
Thumbs.db
|
22
|
+
|
23
|
+
# Folders to ignore
|
24
|
+
intermediate
|
25
|
+
publish
|
26
|
+
target
|
27
|
+
.idea
|
28
|
+
out
|
29
|
+
|
30
|
+
# markdown previews
|
31
|
+
*.md.html
|
32
|
+
|
33
|
+
# bundler suggested ignores
|
34
|
+
*.gem
|
35
|
+
*.rbc
|
36
|
+
.bundle
|
37
|
+
.config
|
38
|
+
.yardoc
|
39
|
+
Gemfile.lock
|
40
|
+
Gemfile.optional.lock
|
41
|
+
InstalledFiles
|
42
|
+
_yardoc
|
43
|
+
coverage
|
44
|
+
doc/
|
45
|
+
lib/bundler/man
|
46
|
+
pkg
|
47
|
+
rdoc
|
48
|
+
spec/reports
|
49
|
+
test/tmp
|
50
|
+
test/version_tmp
|
51
|
+
tmp
|
52
|
+
*.netrc
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
looker-sdk-ruby
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem 'awesome_print', '~>1.6.1', :require => 'ap'
|
5
|
+
gem 'redcarpet', '~>3.1.2', :platforms => :ruby
|
6
|
+
end
|
7
|
+
|
8
|
+
group :development, :test do
|
9
|
+
gem 'rake', '< 11.0'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :test do
|
13
|
+
# gem 'json', '~> 1.7', :platforms => [:jruby] look TODO needed?
|
14
|
+
gem 'minitest', '5.3.5'
|
15
|
+
gem 'mocha', '1.1.0'
|
16
|
+
gem 'rack', '1.6.4'
|
17
|
+
gem 'rack-test', '0.6.2'
|
18
|
+
gem 'netrc', '~> 0.7.7'
|
19
|
+
gem 'simplecov', '~> 0.7.1', :require => false
|
20
|
+
end
|
21
|
+
|
22
|
+
gemspec
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Looker Data Sciences, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require "rake/testtask"
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << "test"
|
7
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
8
|
+
t.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :test do
|
12
|
+
desc "Run tests against all supported Rubies"
|
13
|
+
task :all do
|
14
|
+
supported_rubies = ['ruby-1.9.3', 'ruby-2.0', 'ruby-2.1', 'ruby-2.3.1', 'jruby-1.7.19', 'jruby-9.1.5.0']
|
15
|
+
failing_rubies = []
|
16
|
+
|
17
|
+
supported_rubies.each do |ruby|
|
18
|
+
cmd = "rvm install #{ruby} && rvm #{ruby} exec gem install bundler && rvm #{ruby} exec bundle install && rvm #{ruby} exec bundle exec rake"
|
19
|
+
system cmd
|
20
|
+
if $? != 0
|
21
|
+
failing_rubies << ruby
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
failing_rubies.each do |ruby|
|
26
|
+
puts "FAIL: #{ruby}. Problem with the tests on #{ruby}."
|
27
|
+
end
|
28
|
+
|
29
|
+
if failing_rubies
|
30
|
+
exit 1
|
31
|
+
else
|
32
|
+
exit 0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :test
|
data/authentication.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
## How to authenticate to Looker's API 3
|
2
|
+
|
3
|
+
The preferred way to authenticate is to use the looker SDK to manage the login and the passing of
|
4
|
+
access_tokens as needed. This doc, however, explains how to do this authentication in a generic way without the SDK and using curl instead for illustration.
|
5
|
+
|
6
|
+
Looker API 3 implements OAuth 2's "Resource Owner Password Credentials Grant" pattern,
|
7
|
+
See: http://tools.ietf.org/html/rfc6749#section-4.3
|
8
|
+
|
9
|
+
### Setup an API key
|
10
|
+
An 'API 3' key is required in order to login and use the API. The key consists of a client_id and a client_secret.
|
11
|
+
'Login' consists of using these credentials to generate a short-term access_token which is then used to make API calls.
|
12
|
+
|
13
|
+
The client_id could be considered semi-public. While, the client_secret is a secret password and MUST be
|
14
|
+
carefully protected. These credentials should not be hard-coded into client code. They should be read from
|
15
|
+
a closely guarded data file when used by client processes.
|
16
|
+
|
17
|
+
Admins can create an API 3 key for a user on looker's user edit page. All requests made using these
|
18
|
+
credentials are made 'as' that user and limited to the role permissions specified for that user. A user
|
19
|
+
account with an appropriate role may be created as needed for the API client's use.
|
20
|
+
|
21
|
+
Note that API 3 tokens should be created for 'regular' Looker users and *not* via the legacy 'Add API User' button.
|
22
|
+
|
23
|
+
|
24
|
+
### Ensure that the API is accessible
|
25
|
+
Looker versions 3.4 (and beyond) expose the 3.0 API via a port different from the port used by the web app.
|
26
|
+
The default port is 19999. It may be necessary to have the Ops team managing the looker instance ensure that this
|
27
|
+
port is made accessible network-wise to client software running on non-local hosts.
|
28
|
+
|
29
|
+
The '/alive' url can be used to detect if the server is reachable
|
30
|
+
|
31
|
+
|
32
|
+
### Login
|
33
|
+
To access the API it is necessary to 'login' using the client_id and client_secret in order to acquire an
|
34
|
+
access_token that will be used in actual API requests. This is done by POSTing to the /login url. The access_token is returned in a short json body and has a limited time before it expires (the default at this point is 1 hour).
|
35
|
+
An 'expires_in' field is provided to tell client software how long they should expect the token to last.
|
36
|
+
|
37
|
+
A new token is created for each /login call and remains valid until it expires or is revoked via /logout.
|
38
|
+
|
39
|
+
It is VERY important that these tokens never be sent in the clear or exposed in any other way.
|
40
|
+
|
41
|
+
|
42
|
+
### Call the API
|
43
|
+
API calls then pass the access_token to looker using an 'Authorization' header. API calls are
|
44
|
+
done using GET, PUT, POST, PATCH, or DELETE as appropriate for the specific call. Normal REST stuff.
|
45
|
+
|
46
|
+
|
47
|
+
### Logout
|
48
|
+
A '/logout' url is available if the client wants to revoke an access_token. It requires a DELETE request.
|
49
|
+
Looker reserves the right to limit the number of 'live' access_tokens per user.
|
50
|
+
|
51
|
+
-------------------------------------------------------------------------------------------------
|
52
|
+
|
53
|
+
The following is an example session using Curl. The '-i' param is used to show returned headers.
|
54
|
+
|
55
|
+
The simple flow in this example is to login, get info about the current user, then logout.
|
56
|
+
|
57
|
+
Note that in this example the client_id and client_secret params are passed using '-d' which causes the
|
58
|
+
request to be done as a POST. And, the -H param is used to specify http headers to add for API requests.
|
59
|
+
|
60
|
+
```
|
61
|
+
# Check that the port is reachable
|
62
|
+
> curl -i https://localhost:19999/alive
|
63
|
+
HTTP/1.1 200 OK
|
64
|
+
Content-Type: application/json;charset=utf-8
|
65
|
+
Vary: Accept-Encoding
|
66
|
+
X-Content-Type-Options: nosniff
|
67
|
+
Content-Length: 0
|
68
|
+
|
69
|
+
|
70
|
+
# Do the login to get an access_token
|
71
|
+
> curl -i -d "client_id=4j3SD8W5RchHw5gvZ5Yd&client_secret=sVySctSMpQQG3TzdNQ5d2dND" https://localhost:19999/login
|
72
|
+
HTTP/1.1 200 OK
|
73
|
+
Content-Type: application/json;charset=utf-8
|
74
|
+
Vary: Accept-Encoding
|
75
|
+
X-Content-Type-Options: nosniff
|
76
|
+
Content-Length: 99
|
77
|
+
|
78
|
+
{"access_token":"4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4","token_type":"Bearer","expires_in":3600}
|
79
|
+
|
80
|
+
# Use an access_token (the token can be used over and over for API calls until it expires)
|
81
|
+
> curl -i -H "Authorization: token 4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4" https://localhost:19999/api/3.0/user
|
82
|
+
HTTP/1.1 200 OK
|
83
|
+
Content-Type: application/json;charset=utf-8
|
84
|
+
Vary: Accept-Encoding
|
85
|
+
X-Content-Type-Options: nosniff
|
86
|
+
Content-Length: 502
|
87
|
+
|
88
|
+
{"id":14,"first_name":"Plain","last_name":"User","email":"dude+1@looker.com","models_dir":null,"is_disabled":false,"look_access":[14],"avatar_url":"https://www.gravatar.com/avatar/b7f792a6180a36a4058f36875584bc45?s=156&d=mm","credentials_email":{"email":"dude+1@looker.com","url":"https://localhost:19999/api/3.0/users/14/credentials_email","user_url":"https://localhost:19999/api/3.0/users/14","password_reset_url":"https://localhost:19999/api/3.0"},"url":"https://localhost:19999/api/3.0/users/14"}
|
89
|
+
|
90
|
+
# Logout to revoke an access_token
|
91
|
+
> curl -i -X DELETE -H "Authorization: token 4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4" https://localhost:19999/logout
|
92
|
+
HTTP/1.1 204 No Content
|
93
|
+
X-Content-Type-Options: nosniff
|
94
|
+
|
95
|
+
# Show that the access_token is no longer valid
|
96
|
+
> curl -i -X DELETE -H "Authorization: token 4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4" https://localhost:19999/logout
|
97
|
+
HTTP/1.1 404 Not Found
|
98
|
+
Content-Type: application/json;charset=utf-8
|
99
|
+
Vary: Accept-Encoding
|
100
|
+
X-Content-Type-Options: nosniff
|
101
|
+
Content-Length: 69
|
102
|
+
|
103
|
+
{"message":"Not found","documentation_url":"http://docs.looker.com/"}
|
104
|
+
```
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require './sdk_setup'
|
2
|
+
|
3
|
+
############################################################################################
|
4
|
+
# simulate a list read from file
|
5
|
+
|
6
|
+
user_list = <<-ENDMARK
|
7
|
+
|
8
|
+
Joe Williams, joe@mycoolcompany.com, Admin
|
9
|
+
Jane Schumacher, jane@mycoolcompany.com, User SuperDeveloper
|
10
|
+
Jim Watson, jim@mycoolcompany.com, User
|
11
|
+
Jim Wu, jimw@mycoolcompany.com, User
|
12
|
+
|
13
|
+
ENDMARK
|
14
|
+
|
15
|
+
############################################################################################
|
16
|
+
|
17
|
+
def create_users(lines)
|
18
|
+
# create a hash to use below. role.name => role.id
|
19
|
+
roles_by_name = Hash[ sdk.all_roles.map{|role| [role.name, role.id] } ]
|
20
|
+
|
21
|
+
# for each line, try to create that user with name, email credentials, and roles
|
22
|
+
lines.each_line do |line|
|
23
|
+
line.strip!
|
24
|
+
next if line.empty?
|
25
|
+
|
26
|
+
# quicky parsing: note lack of error handling!
|
27
|
+
|
28
|
+
name, email, roles = line.split(',')
|
29
|
+
fname, lname = name.split(' ')
|
30
|
+
[fname, lname, email, roles].each(&:strip!)
|
31
|
+
|
32
|
+
role_ids = []
|
33
|
+
roles.split(' ').each do |role_name|
|
34
|
+
if id = roles_by_name[role_name]
|
35
|
+
role_ids << id
|
36
|
+
else
|
37
|
+
raise "#{role_name} does not exist. ABORTING!"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# for display
|
42
|
+
user_info = "#{fname} #{lname} <#{email}> as #{roles}"
|
43
|
+
|
44
|
+
begin
|
45
|
+
# call the SDK to create user with names, add email/password login credentials, set user roles
|
46
|
+
user = sdk.create_user({:first_name => fname, :last_name => lname})
|
47
|
+
sdk.create_user_credentials_email(user.id, {:email => email})
|
48
|
+
sdk.set_user_roles(user.id, role_ids)
|
49
|
+
puts "Created user: #{user_info}"
|
50
|
+
rescue LookerSDK::Error => e
|
51
|
+
# if any errors occur above then 'undo' by deleting the given user (if we got that far)
|
52
|
+
sdk.delete_user(user.id) if user
|
53
|
+
puts "FAILED to create user: #{user_info} (#{e.message}) "
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##################################################################
|
59
|
+
|
60
|
+
def delete_users(lines)
|
61
|
+
# create a hash to use below. user.credentials_email.email => user.id
|
62
|
+
users_by_email = Hash[ sdk.all_users.map{|user| [user.credentials_email.email, user.id] if user.credentials_email}.compact ]
|
63
|
+
|
64
|
+
lines.each_line do |line|
|
65
|
+
line.strip!
|
66
|
+
next if line.empty?
|
67
|
+
|
68
|
+
# quicky parsing: note lack of error handling!
|
69
|
+
name, email, roles = line.split(',')
|
70
|
+
fname, lname = name.split(' ')
|
71
|
+
[fname, lname, email, roles].each(&:strip!)
|
72
|
+
|
73
|
+
# for display
|
74
|
+
user_info = "#{fname} #{lname} <#{email}>"
|
75
|
+
|
76
|
+
begin
|
77
|
+
if id = users_by_email[email]
|
78
|
+
sdk.delete_user(id)
|
79
|
+
puts "Deleted user: #{user_info}>"
|
80
|
+
else
|
81
|
+
puts "Did not find user: #{user_info}>"
|
82
|
+
end
|
83
|
+
rescue LookerSDK::Error => e
|
84
|
+
puts "FAILED to delete user: #{user_info} (#{e.message}) "
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##################################################################
|
90
|
+
|
91
|
+
# call the methods
|
92
|
+
create_users(user_list)
|
93
|
+
puts
|
94
|
+
delete_users(user_list)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require './sdk_setup'
|
2
|
+
|
3
|
+
users = Hash[
|
4
|
+
sdk.all_users(:fields => 'id, credentials_email').
|
5
|
+
map{|u| [u.credentials_email.email, u.id] if u.credentials_email }.compact
|
6
|
+
]
|
7
|
+
|
8
|
+
$stdin.each_line do |line|
|
9
|
+
line.chomp!
|
10
|
+
|
11
|
+
_, old_email, new_email = line.split(',', 3).map(&:strip)
|
12
|
+
|
13
|
+
if id = users[old_email]
|
14
|
+
begin
|
15
|
+
sdk.update_user_credentials_email(id, {:email => new_email})
|
16
|
+
puts "Successfully changed '#{old_email}' => '#{new_email}'"
|
17
|
+
rescue => e
|
18
|
+
puts "FAILED to changed '#{old_email}' => '#{new_email}' because of: #{e.class}:#{e.message}"
|
19
|
+
end
|
20
|
+
else
|
21
|
+
puts "FAILED: Could not find user with email '#{old_email}'"
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require './sdk_setup'
|
2
|
+
|
3
|
+
$stdin.each_line do |line|
|
4
|
+
line.chomp!
|
5
|
+
|
6
|
+
id, email = line.split(',', 2).map(&:strip)
|
7
|
+
|
8
|
+
begin
|
9
|
+
user = sdk.user(id)
|
10
|
+
if user.credentials_email
|
11
|
+
puts "Error: User with id '#{id}' Already has credentials_email"
|
12
|
+
else
|
13
|
+
sdk.create_user_credentials_email(id, {:email => email})
|
14
|
+
puts "Success: Created credentials_email for User with id '#{id}' and email '#{email}'"
|
15
|
+
end
|
16
|
+
rescue LookerSDK::NotFound
|
17
|
+
puts "Error: User with id '#{id}' Not found"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require './sdk_setup'
|
2
|
+
|
3
|
+
total_count = 0
|
4
|
+
sdk.all_users(:fields => 'id, display_name').each do |user|
|
5
|
+
count = 0
|
6
|
+
sdk.all_user_sessions(user.id, :fields => 'id').each do |session|
|
7
|
+
sdk.delete_user_session(user.id, session.id)
|
8
|
+
count += 1
|
9
|
+
end
|
10
|
+
puts "Deleted #{count} sessions for #{user.id} #{user.display_name}"
|
11
|
+
total_count += count
|
12
|
+
end
|
13
|
+
puts "Deleted #{total_count} sessions"
|
14
|
+
|
15
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require './sdk_setup'
|
2
|
+
|
3
|
+
$stdin.each_line do |line|
|
4
|
+
line.chomp!
|
5
|
+
|
6
|
+
id, _ = line.split(',', 2).map(&:strip)
|
7
|
+
|
8
|
+
begin
|
9
|
+
user = sdk.user(id)
|
10
|
+
if user.credentials_google
|
11
|
+
sdk.delete_user_credentials_google(id)
|
12
|
+
puts "Success: Deleted credentials_google for User with id '#{id}'"
|
13
|
+
else
|
14
|
+
puts "Error: User with id '#{id}' Does not have credentials_google"
|
15
|
+
end
|
16
|
+
rescue LookerSDK::NotFound
|
17
|
+
puts "Error: User with id '#{id}' Not found"
|
18
|
+
end
|
19
|
+
end
|