cyclid-client 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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