cyclid-client 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,129 @@
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 'colorize'
16
+ require 'uri'
17
+
18
+ require_rel 'organization/*.rb'
19
+
20
+ module Cyclid
21
+ module Cli
22
+ # 'organization' sub-command
23
+ class Organization < Thor
24
+ desc 'show', 'Show details of the organization'
25
+ def show
26
+ org = client.org_get(client.config.organization)
27
+
28
+ # Convert the public key to PEM
29
+ der_key = Base64.decode64(org['public_key'])
30
+ public_key = OpenSSL::PKey::RSA.new(der_key)
31
+
32
+ # Pretty print the organization details
33
+ puts 'Name: '.colorize(:cyan) + org['name']
34
+ puts 'Owner Email: '.colorize(:cyan) + org['owner_email']
35
+ puts 'Public Key: '.colorize(:cyan) + public_key.to_pem
36
+ puts 'Members:'.colorize(:cyan)
37
+ if org['users'].any?
38
+ org['users'].each do |user|
39
+ puts "\t#{user}"
40
+ end
41
+ else
42
+ puts "\tNone"
43
+ end
44
+ rescue StandardError => ex
45
+ abort "Failed to get organization: #{ex}"
46
+ end
47
+
48
+ desc 'modify', 'Modify the organization'
49
+ long_desc <<-LONGDESC
50
+ Modify the organization.
51
+
52
+ The --email option sets the owners email address.
53
+ LONGDESC
54
+ option :email, aliases: '-e'
55
+ def modify
56
+ client.org_modify(client.config.organization,
57
+ owner_email: options[:email])
58
+ rescue StandardError => ex
59
+ abort "Failed to modify organization: #{ex}"
60
+ end
61
+
62
+ desc 'list', 'List your available organizations'
63
+ def list
64
+ Dir.glob("#{CYCLID_CONFIG_DIR}/*").each do |fname|
65
+ next if File.symlink?(fname)
66
+ next unless File.file?(fname)
67
+
68
+ begin
69
+ # Create a Config from this file and display the details
70
+ config = Cyclid::Client::Config.new(path: fname)
71
+
72
+ puts File.basename(fname).colorize(:cyan)
73
+ uri = URI::HTTP.build(host: config.server, port: config.port)
74
+ puts "\tServer: ".colorize(:cyan) + uri.to_s
75
+ puts "\tOrganization: ".colorize(:cyan) + config.organization
76
+ puts "\tUsername: ".colorize(:cyan) + config.username
77
+ rescue StandardError => ex
78
+ $stderr.puts "Failed to load config file #{fname}: #{ex}"
79
+ end
80
+ end
81
+ end
82
+
83
+ desc 'use NAME', 'Select the organization NAME to use by default'
84
+ def use(name = nil)
85
+ # If 'use' was called without an argument, print the name of the
86
+ # current configuration
87
+ if name.nil?
88
+ fname = if File.symlink?(options[:config])
89
+ File.readlink(options[:config])
90
+ else
91
+ options[:config]
92
+ end
93
+ puts File.basename(fname)
94
+ else
95
+ # List the avialble configurations
96
+ fname = File.join(CYCLID_CONFIG_DIR, name)
97
+
98
+ # Sanity check that the configuration file exists and is valid
99
+ abort 'No such organization' unless File.exist?(fname)
100
+ abort 'Not a valid organization' unless File.file?(fname)
101
+
102
+ begin
103
+ config = Cyclid::Client::Config.new(path: fname)
104
+
105
+ raise if config.server.nil? or \
106
+ config.organization.nil? or \
107
+ config.username.nil? or \
108
+ config.secret.nil?
109
+ rescue StandardError
110
+ abort 'Invalid configuration file'
111
+ end
112
+
113
+ # The configuration file exists and appears to be sane, so switch the
114
+ # 'config' symlink to point to it.
115
+ Dir.chdir(CYCLID_CONFIG_DIR) do
116
+ File.delete('config')
117
+ File.symlink(name, 'config')
118
+ end
119
+ end
120
+ end
121
+
122
+ desc 'member', 'Manage organization members'
123
+ subcommand 'member', Member
124
+
125
+ desc 'config', 'Manage organization configuration'
126
+ subcommand 'config', Config
127
+ end
128
+ end
129
+ end
@@ -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
+ module Cyclid
16
+ module Cli
17
+ # Commands for managing per. organization configuration
18
+ class Config < Thor
19
+ desc 'show TYPE PLUGIN', 'Show a plugin configuration'
20
+ def show(type, plugin)
21
+ plugin_data = client.org_config_get(client.config.organization, type, plugin)
22
+
23
+ config = plugin_data['config']
24
+ schema = plugin_data['schema']
25
+ schema.each do |setting|
26
+ name = setting['name']
27
+ type = setting['type']
28
+
29
+ case type
30
+ when 'string', 'integer'
31
+ data = config[name] || 'Not set'
32
+ puts "#{setting['description']}: ".colorize(:cyan) + data
33
+ when 'boolean'
34
+ data = config[name] || 'Not set'
35
+ puts "#{setting['description']}: ".colorize(:cyan) + (data ? 'true' : 'false')
36
+ when 'list'
37
+ puts setting['description'].colorize(:cyan)
38
+ data = config[name]
39
+ if data.empty?
40
+ puts "\tNone"
41
+ else
42
+ data.each do |item|
43
+ puts "\t#{item}"
44
+ end
45
+ end
46
+ when 'hash-list'
47
+ puts setting['description'].colorize(:cyan)
48
+ data = config[name]
49
+ if data.empty?
50
+ puts "\tNone"
51
+ else
52
+ data.each do |item|
53
+ item.each do |k, v|
54
+ puts "\t#{k}: #{v}"
55
+ end
56
+ end
57
+ end
58
+ else
59
+ raise "unknown schema type #{type}"
60
+ end
61
+ end
62
+ rescue StandardError => ex
63
+ abort "Failed to get plugin configuration: #{ex}"
64
+ end
65
+
66
+ desc 'edit TYPE PLUGIN', 'Edit a plugin configuration'
67
+ def edit(type, plugin)
68
+ plugin_data = client.org_config_get(client.config.organization, type, plugin)
69
+
70
+ # Inject the schema description into each config item
71
+ schema = plugin_data['schema']
72
+ config = plugin_data['config'].each do |k, v|
73
+ description = ''
74
+ schema.each do |item|
75
+ description = item['description'] if item['name'] == k
76
+ end
77
+ { k => v, 'description' => description }
78
+ end
79
+
80
+ # Open a text editor on the configuration
81
+ config = invoke_editor(config)
82
+
83
+ # Submit it to the server
84
+ client.org_config_set(client.config.organization, type, plugin, config)
85
+ rescue StandardError => ex
86
+ abort "Failed to update plugin configuration: #{ex}"
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,126 @@
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
+ # Commands for managing organization members
18
+ class Member < Thor
19
+ desc 'add USERS', 'Add users to the organization'
20
+ def add(*users)
21
+ org = client.org_get(client.config.organization)
22
+
23
+ # Concat the new list with the existing list and remove any
24
+ # duplicates.
25
+ user_list = org['users']
26
+ user_list.concat users
27
+ user_list.uniq!
28
+
29
+ client.org_modify(client.config.organization,
30
+ members: user_list)
31
+ rescue StandardError => ex
32
+ abort "Failed to add users to the organization: #{ex}"
33
+ end
34
+
35
+ desc 'permission USER PERMISSION', 'Modify a members organization permissions'
36
+ long_desc <<-LONGDESC
37
+ Modify the organization permissions for USER.
38
+
39
+ PERMISSION must be one of:
40
+ * admin - Give the user organization administrator permissions.
41
+ * write - Give the user organization create/modify permissions.
42
+ * read - Give the user organization read permissions.
43
+ * none - Remove all organization permissions.
44
+
45
+ With 'none' the user remains an organization member but can not
46
+ interact with it. See the 'member remove' command if you want to
47
+ actually remove a user from your organization.
48
+ LONGDESC
49
+ def permission(user, permission)
50
+ perms = case permission.downcase
51
+ when 'admin'
52
+ { 'admin' => true, 'write' => true, 'read' => true }
53
+ when 'write'
54
+ { 'admin' => false, 'write' => true, 'read' => true }
55
+ when 'read'
56
+ { 'admin' => false, 'write' => false, 'read' => true }
57
+ when 'none'
58
+ { 'admin' => false, 'write' => false, 'read' => false }
59
+ else
60
+ raise "invalid permission #{permission}"
61
+ end
62
+
63
+ client.org_user_permissions(client.config.organization, user, perms)
64
+ rescue StandardError => ex
65
+ abort "Failed to modify user permissions: #{ex}"
66
+ end
67
+ map 'perms' => :permission
68
+
69
+ desc 'list', 'List organization members'
70
+ def list
71
+ org = client.org_get(client.config.organization)
72
+ org['users'].each do |user|
73
+ puts user
74
+ end
75
+ rescue StandardError => ex
76
+ abort "Failed to get organization members: #{ex}"
77
+ end
78
+
79
+ desc 'show USER', 'Show details of an organization member'
80
+ def show(user)
81
+ user = client.org_user_get(client.config.organization, user)
82
+
83
+ # Pretty print the user details
84
+ puts 'Username: '.colorize(:cyan) + user['username']
85
+ puts 'Email: '.colorize(:cyan) + user['email']
86
+ puts 'Permissions'.colorize(:cyan)
87
+ user['permissions'].each do |k, v|
88
+ puts "\t#{k.capitalize}: ".colorize(:cyan) + v.to_s
89
+ end
90
+ rescue StandardError => ex
91
+ abort "Failed to get user: #{ex}"
92
+ end
93
+
94
+ desc 'remove USERS', 'Remove users from the organization'
95
+ long_desc <<-LONGDESC
96
+ Remove the list of USERS from the organization.
97
+
98
+ The --force option will remove the users without asking for confirmation.
99
+ LONGDESC
100
+ option :force, aliases: '-f', type: :boolean
101
+ def remove(*users)
102
+ org = client.org_get(client.config.organization)
103
+
104
+ # Remove any users that exist as members of this organization;
105
+ # ask for confirmation on a per-user basis (unless '-f' was passed)
106
+ user_list = org['users']
107
+ user_list.delete_if do |user|
108
+ if users.include? user
109
+ if options[:force]
110
+ true
111
+ else
112
+ print "Remove user #{user}: are you sure? (Y/n): ".colorize(:red)
113
+ STDIN.getc.chr.casecmp('y') == 0
114
+ end
115
+ end
116
+ end
117
+ user_list.uniq!
118
+
119
+ client.org_modify(client.config.organization,
120
+ members: user_list)
121
+ rescue StandardError => ex
122
+ abort "Failed to remove users from the organization: #{ex}"
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,42 @@
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 'colorize'
16
+
17
+ module Cyclid
18
+ module Cli
19
+ # 'secret' sub-command
20
+ class Secret < Thor
21
+ desc 'encrypt', 'Encrypt a secret with the organizations public key'
22
+ def encrypt
23
+ # Get the organizations public key in a form we can use
24
+ org = client.org_get(client.config.organization)
25
+ der_key = Base64.decode64(org['public_key'])
26
+ public_key = OpenSSL::PKey::RSA.new(der_key)
27
+
28
+ # Get the secret in a safe manner
29
+ print 'Secret: '
30
+ secret = STDIN.noecho(&:gets).chomp
31
+ print "\r"
32
+
33
+ # Encrypt with the public key
34
+ encrypted = public_key.public_encrypt(secret)
35
+
36
+ puts 'Secret: '.colorize(:cyan) + Base64.strict_encode64(encrypted)
37
+ rescue StandardError => ex
38
+ abort "Failed to encrypt secret: #{ex}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,121 @@
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
+ # 'stage' sub-command
18
+ class Stage < Thor
19
+ desc 'list', 'List the defined stages'
20
+ def list
21
+ stages = client.stage_list(client.config.organization)
22
+ stages.each do |stage|
23
+ puts "#{stage[:name]} v#{stage[:version]}"
24
+ end
25
+ rescue StandardError => ex
26
+ abort "Failed to get stages: #{ex}"
27
+ end
28
+
29
+ desc 'show NAME', 'Show details of a stage'
30
+ def show(name)
31
+ stages = client.stage_get(client.config.organization, name)
32
+
33
+ # Pretty print the stage details
34
+ stages.each do |stage|
35
+ puts 'Name: '.colorize(:cyan) + stage['name']
36
+ puts 'Version: '.colorize(:cyan) + stage['version']
37
+ puts 'Steps'.colorize(:cyan)
38
+ stage['steps'].each do |step|
39
+ puts "\t\tAction: ".colorize(:cyan) + step['action']
40
+ step.delete('action')
41
+ step.each do |k, v|
42
+ puts "\t\t#{k.capitalize}: ".colorize(:cyan) + v.to_s
43
+ end
44
+ end
45
+ end
46
+ rescue StandardError => ex
47
+ abort "Failed to get stage: #{ex}"
48
+ end
49
+
50
+ desc 'create FILENAME', 'Create a new stage from a file'
51
+ long_desc <<-LONGDESC
52
+ Create a new stage. FILENAME should be the path to a valid Cyclid stage definition, in
53
+ either YAML or JSON format.
54
+
55
+ You can create multiple versions of a stage with the same name. You can either set the
56
+ version in the stage definition itself or use the --version option.
57
+
58
+ Cyclid will attempt to detect the format of the file automatically. You can force the
59
+ parsing format using either the --yaml or --json options.
60
+
61
+ The --yaml option causes the file to be parsed as YAML.
62
+
63
+ The --json option causes the file to be parsed as JSON.
64
+ LONGDESC
65
+ option :yaml, aliases: '-y'
66
+ option :json, aliases: '-j'
67
+ option :version, aliases: '-v'
68
+ def create(filename)
69
+ stage_file = File.expand_path(filename)
70
+ raise 'Cannot open file' unless File.exist?(stage_file)
71
+
72
+ stage_type = if options[:yaml]
73
+ 'yaml'
74
+ elsif options[:json]
75
+ 'json'
76
+ else
77
+ # Detect format
78
+ match = stage_file.match(/\A.*\.(json|yml|yaml)\z/)
79
+ match[1]
80
+ end
81
+ stage_type = 'yaml' if stage_type == 'yml'
82
+
83
+ # Do a client-side sanity check by attempting to parse the file; it
84
+ # will fail-fast if the file has a syntax error
85
+ stage = File.read(stage_file)
86
+ stage_data = if stage_type == 'yaml'
87
+ YAML.load(stage)
88
+ elsif stage_type == 'json'
89
+ JSON.parse(stage)
90
+ else
91
+ raise 'Unknown or unsupported file type'
92
+ end
93
+
94
+ # Inject the version if it was passed on the command line
95
+ stage_data['version'] = options[:version] if options[:version]
96
+
97
+ client.stage_create(client.config.organization, stage_data)
98
+ rescue StandardError => ex
99
+ abort "Failed to create stage: #{ex}"
100
+ end
101
+
102
+ desc 'edit NAME', 'Edit a stage definition'
103
+ long_desc <<-LONGDESC
104
+ Edit a stage. Individual stages are immutable, but you may create a new
105
+ version of an existing stage using this command.
106
+ LONGDESC
107
+ def edit(name)
108
+ stages = client.stage_get(client.config.organization, name)
109
+
110
+ # XXX This is a hack. The API returns all stages from this endpoint;
111
+ # we might need to add or extend the API to return "latest" only.
112
+ stage = stages.last
113
+
114
+ stage = invoke_editor(stage)
115
+ client.stage_modify(client.config.organization, stage)
116
+ rescue StandardError => ex
117
+ abort "Failed to edit stage: #{ex}"
118
+ end
119
+ end
120
+ end
121
+ end