cyclid-client 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +174 -0
- data/README.md +587 -0
- data/bin/cyclid +20 -0
- data/lib/cyclid/auth_methods.rb +25 -0
- data/lib/cyclid/cli.rb +90 -0
- data/lib/cyclid/cli/admin.rb +32 -0
- data/lib/cyclid/cli/admin/organization.rb +122 -0
- data/lib/cyclid/cli/admin/user.rb +142 -0
- data/lib/cyclid/cli/job.rb +114 -0
- data/lib/cyclid/cli/organization.rb +129 -0
- data/lib/cyclid/cli/organization/config.rb +90 -0
- data/lib/cyclid/cli/organization/member.rb +126 -0
- data/lib/cyclid/cli/secret.rb +42 -0
- data/lib/cyclid/cli/stage.rb +121 -0
- data/lib/cyclid/cli/user.rb +84 -0
- data/lib/cyclid/client.rb +98 -0
- data/lib/cyclid/client/api.rb +114 -0
- data/lib/cyclid/client/api/basic.rb +30 -0
- data/lib/cyclid/client/api/hmac.rb +59 -0
- data/lib/cyclid/client/api/none.rb +29 -0
- data/lib/cyclid/client/api/token.rb +30 -0
- data/lib/cyclid/client/auth.rb +36 -0
- data/lib/cyclid/client/health.rb +34 -0
- data/lib/cyclid/client/job.rb +88 -0
- data/lib/cyclid/client/organization.rb +187 -0
- data/lib/cyclid/client/stage.rb +79 -0
- data/lib/cyclid/client/user.rb +134 -0
- data/lib/cyclid/config.rb +92 -0
- metadata +157 -0
@@ -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
|