cyclid-client 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/cyclid ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ $LOAD_PATH.push File.expand_path('../../lib', __FILE__)
17
+
18
+ require 'cyclid/cli'
19
+
20
+ Cyclid::Cli::Command.start(ARGV)
@@ -0,0 +1,25 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Cyclid
16
+ module Client
17
+ # Possible authentication methods
18
+ module AuthMethods
19
+ AUTH_NONE = 0,
20
+ AUTH_HMAC = 1,
21
+ AUTH_BASIC = 2,
22
+ AUTH_TOKEN = 3
23
+ end
24
+ end
25
+ end
data/lib/cyclid/cli.rb ADDED
@@ -0,0 +1,90 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'thor'
16
+ require 'require_all'
17
+
18
+ require_rel 'cli/*.rb'
19
+ require 'cyclid/client'
20
+
21
+ # Add some helpers to the Thor base class
22
+ class Thor
23
+ private
24
+
25
+ def client
26
+ @client ||= Cyclid::Client::Tilapia.new(path: options[:config], log_level: debug?)
27
+ end
28
+
29
+ def debug?
30
+ options[:debug] ? Logger::DEBUG : Logger::FATAL
31
+ end
32
+
33
+ # Open a text editor against a temporary file with the data rendered to
34
+ # JSON, and then re-parse the file after the user has completed editing.
35
+ def invoke_editor(data)
36
+ # Sanity check that EDITOR is set in the environment before we try to run
37
+ # it
38
+ abort('ERROR: '.colorize(:red) + 'You must set your EDITOR environment variable') \
39
+ if ENV['EDITOR'].nil?
40
+
41
+ # Write the data to a temporary file
42
+ tmpfile = Tempfile.new('cyclid')
43
+ tmpfile.write(JSON.pretty_generate(data))
44
+ tmpfile.flush
45
+
46
+ # Run the editor
47
+ system("#{ENV['EDITOR']} #{tmpfile.path}")
48
+
49
+ # Re-open and read it back in now that the user has finished editing it
50
+ tmpfile.open
51
+ data = JSON.parse(tmpfile.read)
52
+
53
+ tmpfile.close
54
+ tmpfile.unlink
55
+
56
+ return data
57
+ end
58
+ end
59
+
60
+ module Cyclid
61
+ module Cli
62
+ CYCLID_CONFIG_DIR = File.join(ENV['HOME'], '.cyclid')
63
+ CYCLID_CONFIG_PATH = File.join(CYCLID_CONFIG_DIR, 'config')
64
+
65
+ # Top level Thor-based CLI
66
+ class Command < Thor
67
+ class_option :config, aliases: '-c', type: :string, default: CYCLID_CONFIG_PATH
68
+ class_option :debug, aliases: '-d', type: :boolean, default: false
69
+
70
+ desc 'admin', 'Administrator commands'
71
+ subcommand 'admin', Admin
72
+
73
+ desc 'user', 'Manage users'
74
+ subcommand 'user', User
75
+
76
+ desc 'organization', 'Manage organizations'
77
+ subcommand 'organization', Organization
78
+ map 'org' => :organization
79
+
80
+ desc 'job', 'Manage jobs'
81
+ subcommand 'job', Job
82
+
83
+ desc 'secret', 'Manage secrets'
84
+ subcommand 'secret', Secret
85
+
86
+ desc 'stage', 'Manage stages'
87
+ subcommand 'stage', Stage
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'base64'
16
+ require 'openssl'
17
+
18
+ require_rel 'admin/*.rb'
19
+
20
+ module Cyclid
21
+ module Cli
22
+ # 'admin' sub-command
23
+ class Admin < Thor
24
+ desc 'user', 'Manage users'
25
+ subcommand 'user', AdminUser
26
+
27
+ desc 'organization', 'Manage organizations'
28
+ subcommand 'organization', AdminOrganization
29
+ map 'org' => :organization
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,122 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Cyclid
16
+ module Cli
17
+ # 'admin organization' sub-commands
18
+ class AdminOrganization < Thor
19
+ desc 'list', 'List all of the organizations'
20
+ def list
21
+ orgs = client.org_list
22
+ orgs.each do |org|
23
+ puts org
24
+ end
25
+ rescue StandardError => ex
26
+ abort "Failed to retrieve list of organizations: #{ex}"
27
+ end
28
+
29
+ desc 'show NAME', 'Show details of the organization NAME'
30
+ def show(name)
31
+ org = client.org_get(name)
32
+
33
+ # Convert the public key to PEM
34
+ der_key = Base64.decode64(org['public_key'])
35
+ public_key = OpenSSL::PKey::RSA.new(der_key)
36
+
37
+ # Pretty print the organization details
38
+ puts 'Name: '.colorize(:cyan) + org['name']
39
+ puts 'Owner Email: '.colorize(:cyan) + org['owner_email']
40
+ puts 'Public Key: '.colorize(:cyan) + public_key.to_pem
41
+ puts 'Members:'.colorize(:cyan)
42
+ if org['users'].any?
43
+ org['users'].each do |user|
44
+ puts "\t#{user}"
45
+ end
46
+ else
47
+ puts "\tNone"
48
+ end
49
+ rescue StandardError => ex
50
+ abort "Failed to get organization: #{ex}"
51
+ end
52
+
53
+ desc 'create NAME OWNER-EMAIL', 'Create a new organization NAME'
54
+ long_desc <<-LONGDESC
55
+ Create an organization NAME with the owner email address EMAIL.
56
+
57
+ The --admin option adds a user as the initial organization administrator.
58
+ LONGDESC
59
+ option :admin, aliases: '-a'
60
+ def create(name, email)
61
+ client.org_add(name, email)
62
+
63
+ if options[:admin]
64
+ # Add the user to the organization and create the appropriate admin
65
+ # permissions for them.
66
+ client.org_modify(name,
67
+ members: options[:admin])
68
+
69
+ perms = { 'admin' => true, 'write' => true, 'read' => true }
70
+ client.org_user_permissions(name,
71
+ options[:admin],
72
+ perms)
73
+ end
74
+ rescue StandardError => ex
75
+ abort "Failed to create new organization: #{ex}"
76
+ end
77
+
78
+ desc 'modify NAME', 'Modify the organization NAME'
79
+ long_desc <<-LONGDESC
80
+ Modify the organization NAME.
81
+
82
+ The --email option sets the owners email address.
83
+
84
+ The --members options sets the list of organization members.
85
+
86
+ *WARNING* --members will overwrite the existing list of members, so use with care!
87
+ LONGDESC
88
+ option :email, aliases: '-e'
89
+ option :members, aliases: '-m', type: :array
90
+ def modify(name)
91
+ client.org_modify(name,
92
+ owner_email: options[:email],
93
+ members: options[:members])
94
+ rescue StandardError => ex
95
+ abort "Failed to modify organization: #{ex}"
96
+ end
97
+
98
+ desc 'delete NAME', 'Delete the organization NAME'
99
+ long_desc <<-LONGDESC
100
+ Delete the organization NAME from the server.
101
+
102
+ The --force option will delete the organization without asking for confirmation.
103
+ LONGDESC
104
+ option :force, aliases: '-f', type: :boolean
105
+ def delete(name)
106
+ if options[:force]
107
+ delete = true
108
+ else
109
+ print "Delete organization #{name}: are you sure? (Y/n): ".colorize(:red)
110
+ delete = STDIN.getc.chr.casecmp('y') == 0
111
+ end
112
+ abort unless delete
113
+
114
+ begin
115
+ client.org_delete(name)
116
+ rescue StandardError => ex
117
+ abort "Failed to delete organization: #{ex}"
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,142 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Cyclid
16
+ module Cli
17
+ # 'admin user' sub-commands
18
+ class AdminUser < Thor
19
+ desc 'list', 'List all of the users'
20
+ def list
21
+ users = client.user_list
22
+ users.each do |user|
23
+ puts user
24
+ end
25
+ rescue StandardError => ex
26
+ abort "Failed to retrieve list of users: #{ex}"
27
+ end
28
+
29
+ desc 'show USERNAME', 'Show details of the user USERNAME'
30
+ def show(username)
31
+ user = client.user_get(username)
32
+
33
+ # Pretty print the user details
34
+ puts 'Username: '.colorize(:cyan) + user['username']
35
+ puts 'Name: '.colorize(:cyan) + (user['name'] || '')
36
+ puts 'Email: '.colorize(:cyan) + user['email']
37
+ puts 'Organizations:'.colorize(:cyan)
38
+ if user['organizations'].any?
39
+ user['organizations'].each do |org|
40
+ puts "\t#{org}"
41
+ end
42
+ else
43
+ puts "\tNone"
44
+ end
45
+ rescue StandardError => ex
46
+ abort "Failed to get user: #{ex}"
47
+ end
48
+
49
+ desc 'create USERNAME EMAIL', 'Create a new user USERNAME'
50
+ long_desc <<-LONGDESC
51
+ Create a user USERNAME with the email address EMAIL. The new user will not be a member of
52
+ any organization.
53
+
54
+ The --name option sets the users real name.
55
+
56
+ The --password option sets an encrypted password for HTTP Basic authentication and Cyclid
57
+ UI console logins.
58
+
59
+ The --secret option sets a shared secret which is used for signing Cyclid API requests.
60
+
61
+ One of either --password or --secret should be used if you want the user to be able to
62
+ authenticate with the server.
63
+ LONGDESC
64
+ option :name, aliases: '-n'
65
+ option :password, aliases: '-p'
66
+ option :secret, aliases: '-s'
67
+ def create(username, email)
68
+ client.user_add(username, email, options[:name], options[:password], options[:secret])
69
+ rescue StandardError => ex
70
+ abort "Failed to create new user: #{ex}"
71
+ end
72
+
73
+ desc 'modify USERNAME', 'Modify the user USERNAME'
74
+ long_desc <<-LONGDESC
75
+ Modify the user USERNAME.
76
+
77
+ The --name option sets the users real name.
78
+
79
+ The --email option sets the users email address.
80
+
81
+ The --password option sets an encrypted password for HTTP Basic authentication and Cyclid
82
+ UI console logins.
83
+
84
+ The --secret option sets a shared secret which is used for signing Cyclid API requests.
85
+ LONGDESC
86
+ option :name, aliases: '-n'
87
+ option :email, aliases: '-e'
88
+ option :password, aliases: '-p'
89
+ option :secret, aliases: '-s'
90
+ def modify(username)
91
+ client.user_modify(username,
92
+ name: options[:name],
93
+ email: options[:email],
94
+ password: options[:password],
95
+ secret: options[:secret])
96
+ rescue StandardError => ex
97
+ abort "Failed to modify user: #{ex}"
98
+ end
99
+
100
+ desc 'passwd USERNAME', 'Change the password of the user USERNAME'
101
+ def passwd(username)
102
+ # Get the new password
103
+ print 'Password: '
104
+ password = STDIN.noecho(&:gets).chomp
105
+ print "\nConfirm password: "
106
+ confirm = STDIN.noecho(&:gets).chomp
107
+ print "\n"
108
+ abort 'Passwords do not match' unless password == confirm
109
+
110
+ # Modify the user with the new password
111
+ begin
112
+ client.user_modify(username, password: password)
113
+ rescue StandardError => ex
114
+ abort "Failed to modify user: #{ex}"
115
+ end
116
+ end
117
+
118
+ desc 'delete USERNAME', 'Delete the user USERNAME'
119
+ long_desc <<-LONGDESC
120
+ Delete the user USERNAME from the server.
121
+
122
+ The --force option will delete the user without asking for confirmation.
123
+ LONGDESC
124
+ option :force, aliases: '-f', type: :boolean
125
+ def delete(username)
126
+ if options[:force]
127
+ delete = true
128
+ else
129
+ print "Delete user #{username}: are you sure? (Y/n): ".colorize(:red)
130
+ delete = STDIN.getc.chr.casecmp('y') == 0
131
+ end
132
+ abort unless delete
133
+
134
+ begin
135
+ client.user_delete(username)
136
+ rescue StandardError => ex
137
+ abort "Failed to delete user: #{ex}"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,114 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'yaml'
16
+ require 'json'
17
+ require 'cyclid/constants'
18
+
19
+ module Cyclid
20
+ module Cli
21
+ # 'job' sub-command
22
+ class Job < Thor
23
+ desc 'submit FILENAME', 'Submit a job to be run'
24
+ long_desc <<-LONGDESC
25
+ Submit a job to be run by the server. FILENAME should be the path to a valid Cyclid job
26
+ file, in either YAML or JSON format.
27
+
28
+ Cyclid will attempt to detect the format of the job file automatically. You can force the
29
+ parsing format using either the --yaml or --json options.
30
+
31
+ The --yaml option causes the job file to be parsed as YAML.
32
+
33
+ The --json option causes the job file to be parsed as JSON.
34
+ LONGDESC
35
+ option :yaml, aliases: '-y'
36
+ option :json, aliases: '-j'
37
+ def submit(filename)
38
+ job_file = File.expand_path(filename)
39
+ raise 'Cannot open file' unless File.exist?(job_file)
40
+
41
+ job_type = if options[:yaml]
42
+ 'yaml'
43
+ elsif options[:json]
44
+ 'json'
45
+ else
46
+ # Detect format
47
+ match = job_file.match(/\A.*\.(json|yml|yaml)\z/)
48
+ match[1]
49
+ end
50
+ job_type = 'yaml' if job_type == 'yml'
51
+
52
+ # Do a client-side sanity check by attempting to parse the file; we
53
+ # don't do anything with the data but it fails-fast if the file has a
54
+ # syntax error
55
+ job = File.read(job_file)
56
+ if job_type == 'yaml'
57
+ YAML.load(job)
58
+ elsif job_type == 'json'
59
+ JSON.parse(job)
60
+ else
61
+ raise 'Unknown or unsupported file type'
62
+ end
63
+
64
+ job_info = client.job_submit(client.config.organization, job, job_type)
65
+ puts 'Job: '.colorize(:cyan) + job_info['job_id'].to_s
66
+ rescue StandardError => ex
67
+ abort "Failed to submit job: #{ex}"
68
+ end
69
+
70
+ desc 'show JOBID', 'Show details of a job'
71
+ def show(jobid)
72
+ job = client.job_get(client.config.organization, jobid)
73
+
74
+ status_id = job['status']
75
+ status = Cyclid::API::Constants::JOB_STATUSES[status_id]
76
+
77
+ started = job['started'].nil? ? nil : Time.parse(job['started'])
78
+ ended = job['ended'].nil? ? nil : Time.parse(job['ended'])
79
+
80
+ # Pretty-print the job details (without the log)
81
+ puts 'Job: '.colorize(:cyan) + job['id'].to_s
82
+ puts 'Name: '.colorize(:cyan) + (job['job_name'] || '')
83
+ puts 'Version: '.colorize(:cyan) + (job['job_version'] || '')
84
+ puts 'Started: '.colorize(:cyan) + (started ? started.asctime : '')
85
+ puts 'Ended: '.colorize(:cyan) + (ended ? ended.asctime : '')
86
+ puts 'Status: '.colorize(:cyan) + status
87
+ rescue StandardError => ex
88
+ abort "Failed to get job status: #{ex}"
89
+ end
90
+
91
+ desc 'status JOBID', 'Show the status of a job'
92
+ def status(jobid)
93
+ job_status = client.job_status(client.config.organization, jobid)
94
+
95
+ status_id = job_status['status']
96
+ status = Cyclid::API::Constants::JOB_STATUSES[status_id]
97
+
98
+ # Pretty-print the job status
99
+ puts 'Status: '.colorize(:cyan) + status
100
+ rescue StandardError => ex
101
+ abort "Failed to get job status: #{ex}"
102
+ end
103
+
104
+ desc 'log JOBID', 'Show the job log'
105
+ def log(jobid)
106
+ job_log = client.job_log(client.config.organization, jobid)
107
+
108
+ puts job_log['log']
109
+ rescue StandardError => ex
110
+ abort "Failed to get job log: #{ex}"
111
+ end
112
+ end
113
+ end
114
+ end