bbc-cosmos-tools 0.5.2 → 0.6.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/bbc-cosmos-tools.gemspec +5 -4
- data/bin/bbc-cosmos-tools +3 -3
- data/lib/bbc/cosmos/tools/api.rb +6 -8
- data/lib/bbc/cosmos/tools/app.rb +4 -4
- data/lib/bbc/cosmos/tools/cloudformation/generator.rb +20 -23
- data/lib/bbc/cosmos/tools/commands/base.rb +8 -10
- data/lib/bbc/cosmos/tools/commands/component.rb +79 -19
- data/lib/bbc/cosmos/tools/commands/config.rb +24 -33
- data/lib/bbc/cosmos/tools/commands/release.rb +100 -112
- data/lib/bbc/cosmos/tools/commands/stack.rb +60 -72
- data/lib/bbc/cosmos/tools/config.rb +19 -22
- data/lib/bbc/cosmos/tools/cosmos_configuration.rb +4 -6
- data/lib/bbc/cosmos/tools/types/base_type.rb +6 -7
- data/lib/bbc/cosmos/tools/types/cfndsl.rb +2 -3
- data/lib/bbc/cosmos/tools/version.rb +1 -1
- metadata +19 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 682aa789050e6b06223d07a9da105df6abdc966d
|
4
|
+
data.tar.gz: 2256eaa0c3a4c060fb26fdaa8fa637796602723c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cb7f2f87c0d45e710caa0fd6f85b887410927bd37570894cfbee4eb88d7bd8df4c2976a14bd2a5e05bb505b52e95a950ec5ea7192b55e9fdf81c22643264188
|
7
|
+
data.tar.gz: 9b4b400d1c6cd2da4bb81c22518dfb5ee8e80217a9b7f3879bbec47cd50edbce62bf9a0478c87131e4f60b37b9318b68150f1170d101565815b45a78ffa021cc
|
data/Gemfile
CHANGED
data/bbc-cosmos-tools.gemspec
CHANGED
@@ -8,12 +8,12 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = BBC::Cosmos::Tools::VERSION
|
9
9
|
spec.authors = ["Steven Jack"]
|
10
10
|
spec.email = ["stevenmajack@gmail.com"]
|
11
|
-
spec.description =
|
12
|
-
spec.summary =
|
11
|
+
spec.description = "Tool for interacting with BBC Cosmos platform"
|
12
|
+
spec.summary = "Tool for pusing config to cosmos and controlling cloudformation templates"
|
13
13
|
spec.homepage = ""
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
-
spec.files = `git ls-files`.split(
|
16
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
17
|
spec.executables << "bbc-cosmos-tools"
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
@@ -24,5 +24,6 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_runtime_dependency "thor"
|
25
25
|
spec.add_runtime_dependency "faraday"
|
26
26
|
spec.add_runtime_dependency "cfndsl"
|
27
|
-
spec.add_runtime_dependency "aws-sdk", "~> 1"
|
27
|
+
spec.add_runtime_dependency "aws-sdk", "~> 1.63.0"
|
28
|
+
spec.add_runtime_dependency "highline"
|
28
29
|
end
|
data/bin/bbc-cosmos-tools
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "pathname"
|
4
4
|
|
5
5
|
root = Pathname.new(__FILE__).dirname.parent
|
6
|
-
lib_path = (root +
|
6
|
+
lib_path = (root + "lib").realdirpath
|
7
7
|
|
8
8
|
$LOAD_PATH.unshift(lib_path)
|
9
9
|
|
10
|
-
require
|
10
|
+
require "bbc/cosmos/tools/app"
|
11
11
|
|
12
12
|
include BBC::Cosmos::Tools
|
13
13
|
|
data/lib/bbc/cosmos/tools/api.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "pathname"
|
2
|
+
require "faraday"
|
3
|
+
require "openssl"
|
4
4
|
|
5
5
|
module BBC
|
6
6
|
module Cosmos
|
7
7
|
module Tools
|
8
8
|
class API
|
9
|
-
|
10
9
|
def initialize(host, key_path)
|
11
10
|
@connection = Faraday.new host, :ssl => {
|
12
11
|
:client_key => client_key(key_path),
|
13
12
|
:client_cert => client_cert(key_path),
|
14
13
|
:verify => false,
|
15
|
-
:version =>
|
14
|
+
:version => "TLSv1"
|
16
15
|
}
|
17
16
|
end
|
18
17
|
|
@@ -28,12 +27,12 @@ module BBC
|
|
28
27
|
@connection.get path
|
29
28
|
end
|
30
29
|
|
31
|
-
def put(path, payload, content_type =
|
30
|
+
def put(path, payload, content_type = "application/json")
|
32
31
|
content_type_from content_type
|
33
32
|
@connection.put path, payload
|
34
33
|
end
|
35
34
|
|
36
|
-
def post(path, payload, content_type =
|
35
|
+
def post(path, payload, content_type = "application/json")
|
37
36
|
content_type_from content_type
|
38
37
|
@connection.post path, payload
|
39
38
|
end
|
@@ -41,7 +40,6 @@ module BBC
|
|
41
40
|
def content_type_from(type)
|
42
41
|
@connection.headers[:content_type] = type
|
43
42
|
end
|
44
|
-
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|
data/lib/bbc/cosmos/tools/app.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require "thor"
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
2
|
+
require "bbc/cosmos/tools/commands/config"
|
3
|
+
require "bbc/cosmos/tools/commands/release"
|
4
|
+
require "bbc/cosmos/tools/commands/stack"
|
5
|
+
require "bbc/cosmos/tools/commands/component"
|
6
6
|
|
7
7
|
module BBC
|
8
8
|
module Cosmos
|
@@ -1,20 +1,19 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "cfndsl"
|
2
|
+
require "json"
|
3
3
|
|
4
4
|
module BBC
|
5
5
|
module Cosmos
|
6
6
|
module Tools
|
7
7
|
module Cloudformation
|
8
8
|
class Generator
|
9
|
-
|
10
|
-
RESERVED_MAIN_STACK_PARAMS = ['Environment', 'PublicSubnet1Id', 'PublicSubnet2Id', 'PublicSubnet3Id', 'ImageId', 'VpcId', 'PrivateSubnet1Id', 'PrivateSubnet2Id', 'PrivateSubnet3Id', 'BastionAccessSecurityGroup', 'DomainNameBase', 'KeyName', 'S3BucketArn']
|
9
|
+
RESERVED_MAIN_STACK_PARAMS = %w(Environment PublicSubnet1Id PublicSubnet2Id PublicSubnet3Id ImageId VpcId PrivateSubnet1Id PrivateSubnet2Id PrivateSubnet3Id BastionAccessSecurityGroup DomainNameBase KeyName S3BucketArn)
|
11
10
|
|
12
11
|
def self.component_exists?(component, config)
|
13
|
-
config.resources[
|
12
|
+
config.resources["cloudformation"]["components"][component]
|
14
13
|
end
|
15
14
|
|
16
15
|
def self.stack_exists?(component, config, stack)
|
17
|
-
config.resources[
|
16
|
+
config.resources["cloudformation"]["components"][component][stack]
|
18
17
|
end
|
19
18
|
|
20
19
|
def self.create(component, config, options, to_json = false, &block)
|
@@ -22,28 +21,28 @@ module BBC
|
|
22
21
|
template.declare(&block)
|
23
22
|
|
24
23
|
if template.checkRefs || CfnDsl::Errors.errors?
|
25
|
-
|
24
|
+
raise_error template
|
26
25
|
else
|
27
26
|
hash = JSON.parse(template.to_json)
|
28
|
-
hash[
|
27
|
+
hash["Parameters"] ||= {}
|
29
28
|
|
30
29
|
if component_exists?(component, config) && stack_exists?(component, config, options[:stack])
|
31
30
|
component_parameters(config.resources, options, component).each do |key, value|
|
32
|
-
hash[
|
31
|
+
hash["Parameters"][key]["Default"] = value if hash["Parameters"][key]
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
|
-
hash.delete_if { |k, v| k ==
|
35
|
+
hash.delete_if { |k, v| k == "Parameters" && v == {} }
|
37
36
|
|
38
37
|
to_json ? JSON.pretty_generate(hash) : hash
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
42
41
|
def self.parameters(data, main_stack = true)
|
43
|
-
if data[
|
44
|
-
data[
|
42
|
+
if data["Parameters"]
|
43
|
+
data["Parameters"].reduce({}) do |object, (key, value)|
|
45
44
|
object.tap do |o|
|
46
|
-
o[key] = value[
|
45
|
+
o[key] = value["Default"] unless reserved_main_stack_param_for(key, main_stack) && value["Default"].nil?
|
47
46
|
end
|
48
47
|
end
|
49
48
|
else
|
@@ -53,27 +52,27 @@ module BBC
|
|
53
52
|
|
54
53
|
def self.raise_error(template)
|
55
54
|
if CfnDsl::Errors.errors?
|
56
|
-
|
57
|
-
elsif (invalid_references = template.checkRefs
|
58
|
-
|
55
|
+
fail("Errors in template: #{CfnDsl::Errors.errors.join(' # ')}")
|
56
|
+
elsif (invalid_references = template.checkRefs)
|
57
|
+
fail("Invalid Refs: #{invalid_references.join(' # ')}")
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
62
61
|
private
|
63
62
|
|
64
63
|
def self.component_stack_details(resources, options, component)
|
65
|
-
|
64
|
+
fail("cloudformation: -> components: node is missing from the environment resource file") unless resources.key?("cloudformation") && resources["cloudformation"].key?("components")
|
66
65
|
|
67
|
-
components = resources[
|
68
|
-
|
66
|
+
components = resources["cloudformation"]["components"]
|
67
|
+
fail("'#{component}' node missing from cloudformation: -> components: nodes") unless components.key?(component)
|
69
68
|
component = components[component]
|
70
69
|
|
71
|
-
|
70
|
+
fail("'#{options[:stack]}' stack node missing from cloudformation: -> components: -> #{component}:") unless component.key?(options[:stack])
|
72
71
|
component[options[:stack]]
|
73
72
|
end
|
74
73
|
|
75
74
|
def self.variants(component_meta, variant = nil)
|
76
|
-
component_meta.
|
75
|
+
component_meta.key?("variants") ? component_meta["variants"][variant] : component_meta.fetch("parameters", {})
|
77
76
|
end
|
78
77
|
|
79
78
|
def self.component_parameters(resources, options, component)
|
@@ -84,10 +83,8 @@ module BBC
|
|
84
83
|
def self.reserved_main_stack_param_for(key, main_stack)
|
85
84
|
main_stack && RESERVED_MAIN_STACK_PARAMS.include?(key)
|
86
85
|
end
|
87
|
-
|
88
86
|
end
|
89
87
|
end
|
90
88
|
end
|
91
89
|
end
|
92
90
|
end
|
93
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require "thor"
|
2
2
|
require "pathname"
|
3
|
-
require
|
3
|
+
require "json"
|
4
4
|
require "bbc/cosmos/tools/config"
|
5
5
|
|
6
6
|
module BBC
|
@@ -14,10 +14,10 @@ module BBC
|
|
14
14
|
|
15
15
|
package_name "BBC Cosmos config tool"
|
16
16
|
|
17
|
-
class_option :key_path, :type => :string, :default => ENV[
|
18
|
-
class_option :env, :type => :string, :default =>
|
17
|
+
class_option :key_path, :type => :string, :default => ENV["BBC_COSMOS_TOOLS_CERT"], :desc => "The path to your dev cert, by default the value of the env variable $BBC_COSMOS_TOOLS_CERT is used"
|
18
|
+
class_option :env, :type => :string, :default => "int", :desc => "The environment to target"
|
19
19
|
class_option :base, :type => :string, :desc => "The base path for the configs repo"
|
20
|
-
class_option :project, :type => :string, :default => ENV[
|
20
|
+
class_option :project, :type => :string, :default => ENV["BBC_COSMOS_TOOLS_PROJECT"], :desc => "The name of the project, by default the value of the env variable $BBC_COSMOS_TOOLS_PROJECT is used"
|
21
21
|
|
22
22
|
def initialize(args = [], local_options = {}, config = {})
|
23
23
|
super(args, local_options, config)
|
@@ -33,7 +33,7 @@ module BBC
|
|
33
33
|
def banner(component = nil)
|
34
34
|
output = component.nil? ? "\n" : "\n#{get_key_value('Component', component)}\n"
|
35
35
|
options.reduce(output) do |string, (key, value)|
|
36
|
-
string += "#{get_key_value(key.capitalize.gsub(/_/,
|
36
|
+
string += "#{get_key_value(key.capitalize.gsub(/_/, ' '), value)}\n"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -42,7 +42,7 @@ module BBC
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def api_error(response, die = true)
|
45
|
-
error_message = JSON.parse(response.body)[
|
45
|
+
error_message = JSON.parse(response.body)["message"]
|
46
46
|
error(error_message, die)
|
47
47
|
end
|
48
48
|
|
@@ -66,18 +66,16 @@ module BBC
|
|
66
66
|
private
|
67
67
|
|
68
68
|
def setup_config
|
69
|
-
config_base = !options[:base].nil? && File.directory?(options[:base]) ? Pathname.new(options[:base]) : Pathname.pwd.join(
|
70
|
-
|
69
|
+
config_base = !options[:base].nil? && File.directory?(options[:base]) ? Pathname.new(options[:base]) : Pathname.pwd.join(".")
|
70
|
+
fail("Config directory: #{config_base} doesn't exist") unless File.directory?(config_base)
|
71
71
|
@config = BBC::Cosmos::Tools::Config.new(
|
72
72
|
config_base,
|
73
73
|
options[:project],
|
74
74
|
options[:env]
|
75
75
|
)
|
76
76
|
end
|
77
|
-
|
78
77
|
end
|
79
78
|
end
|
80
79
|
end
|
81
80
|
end
|
82
81
|
end
|
83
|
-
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require "bbc/cosmos/tools/api"
|
2
2
|
require "aws-sdk"
|
3
3
|
require "json"
|
4
|
+
require "highline/import"
|
4
5
|
|
5
6
|
module BBC
|
6
7
|
module Cosmos
|
7
8
|
module Tools
|
8
9
|
module Commands
|
9
10
|
class Component < Base
|
10
|
-
|
11
11
|
desc "instances <COMPONENT>", "Show instances of the given component"
|
12
12
|
method_option :show_tag, :type => :string, :desc => "Show tag"
|
13
13
|
def instances(component)
|
@@ -18,46 +18,106 @@ module BBC
|
|
18
18
|
tag = options[:show_tag]
|
19
19
|
|
20
20
|
rows = instance_ids(component).map do |id|
|
21
|
-
[id].tap
|
22
|
-
|
23
|
-
|
21
|
+
[id].tap do |row|
|
22
|
+
row << ec2.instances[id].tags[tag] if tag
|
23
|
+
end
|
24
24
|
end
|
25
25
|
|
26
|
-
headers = [
|
26
|
+
headers = ["Instance ID"]
|
27
27
|
headers << tag if tag
|
28
28
|
|
29
29
|
print_table build_table(headers, rows)
|
30
30
|
end
|
31
31
|
|
32
|
+
desc "SSH <COMPONENT>", "Gain SSH"
|
33
|
+
def ssh(component)
|
34
|
+
meta = make_request("env/#{options[:env]}/component/#{component}/instances")
|
35
|
+
|
36
|
+
meta.count == 1 ? single_instance(component, meta)
|
37
|
+
: multiple_instance(component, meta)
|
38
|
+
end
|
39
|
+
|
32
40
|
private
|
41
|
+
|
42
|
+
def api
|
43
|
+
@api ||= BBC::Cosmos::Tools::API.new(
|
44
|
+
"https://api.live.bbc.co.uk", options[:key_path]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def single_instance(component, meta)
|
49
|
+
session(component, meta[0]["id"], meta[0]["private_ip_address"])
|
50
|
+
end
|
51
|
+
|
52
|
+
def multiple_instance(component, meta)
|
53
|
+
instance_id = select_instance(meta)
|
54
|
+
session(component, instance_id, extract_ipaddress(meta, instance_id))
|
55
|
+
end
|
56
|
+
|
57
|
+
def session(component, instance_id, ip_address)
|
58
|
+
post_data(component, instance_id)
|
59
|
+
output_message(component, ip_address, instance_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
def select_instance(meta)
|
63
|
+
choose do |menu|
|
64
|
+
say "Multiple instances detected, please select one", :green
|
65
|
+
|
66
|
+
meta.map do |i|
|
67
|
+
menu.choice(i["id"].to_sym)
|
68
|
+
end
|
69
|
+
end.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_ipaddress(meta, instance)
|
73
|
+
meta.detect { |hash| hash.value?(instance) }.fetch("private_ip_address")
|
74
|
+
end
|
75
|
+
|
76
|
+
def output_message(component, ip, id)
|
77
|
+
say "SSH setup for #{component} on #{options[:env]}, your IP for #{id} is #{ip}", :green
|
78
|
+
end
|
79
|
+
|
80
|
+
def post_data(component, id)
|
81
|
+
post_data = { "instance_id" => id }
|
82
|
+
|
83
|
+
endpoint = "/cosmos/env/#{options[:env]}/component/#{component}/logins/create"
|
84
|
+
api.post(endpoint, post_data.to_json).tap do |response|
|
85
|
+
fail Exception, response.body if response.status != 201
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def make_request(path)
|
90
|
+
JSON.parse(api.get("/cosmos/#{path}").tap do |request|
|
91
|
+
fail Exception, request.body if request.status != 200
|
92
|
+
end.body)
|
93
|
+
end
|
94
|
+
|
33
95
|
def instance_ids(component)
|
34
96
|
response = BBC::Cosmos::Tools::API.new(
|
35
|
-
config.app[
|
97
|
+
config.app["api"],
|
36
98
|
options[:key_path]
|
37
|
-
)
|
38
|
-
.get "/cosmos/env/#{options[:env]}/component/#{component}/instances"
|
99
|
+
).get "/cosmos/env/#{options[:env]}/component/#{component}/instances"
|
39
100
|
|
40
|
-
|
101
|
+
fail Exception, response.body if response.status != 200
|
41
102
|
|
42
|
-
JSON.parse(response.body).map
|
103
|
+
JSON.parse(response.body).map do |instance|
|
43
104
|
instance.fetch "id"
|
44
|
-
|
105
|
+
end
|
45
106
|
end
|
46
107
|
|
47
108
|
def aws_credentials
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
.get "/login/credentials"
|
109
|
+
response = BBC::Cosmos::Tools::API.new(
|
110
|
+
"https://wormhole.cloud.bbc.co.uk",
|
111
|
+
options[:key_path]
|
112
|
+
).get "/login/credentials"
|
53
113
|
|
54
|
-
|
114
|
+
fail Exception, response.body if response.status != 200
|
55
115
|
|
56
116
|
credentials = JSON.parse response.body
|
57
117
|
|
58
118
|
{ :access_key_id => credentials["accessKeyId"],
|
59
|
-
:secret_access_key => credentials[
|
60
|
-
:session_token => credentials[
|
119
|
+
:secret_access_key => credentials["secretAccessKey"],
|
120
|
+
:session_token => credentials["sessionToken"],
|
61
121
|
:region => "eu-west-1" }
|
62
122
|
end
|
63
123
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require "json"
|
2
|
+
require "pathname"
|
3
|
+
require "bbc/cosmos/tools/commands/base"
|
4
|
+
require "bbc/cosmos/tools/cosmos_configuration"
|
5
|
+
require "bbc/cosmos/tools/api"
|
6
6
|
|
7
7
|
module BBC
|
8
8
|
module Cosmos
|
@@ -22,12 +22,11 @@ module BBC
|
|
22
22
|
method_option :cosmos_format, :type => :boolean, :desc => "Toogles if the output should be in the cosmos format"
|
23
23
|
def generate(component)
|
24
24
|
if options[:write]
|
25
|
-
File.open("#{component}.json",
|
25
|
+
File.open("#{component}.json", "w") do |f|
|
26
26
|
f.write(JSON.pretty_generate(cosmos_config.component(component)))
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
30
|
if options[:output] == "machine"
|
32
31
|
say JSON.pretty_generate(cosmos_config.cosmos_component(component)), :white
|
33
32
|
|
@@ -46,7 +45,6 @@ module BBC
|
|
46
45
|
rescue Exception => e
|
47
46
|
error e.message
|
48
47
|
end
|
49
|
-
|
50
48
|
end
|
51
49
|
|
52
50
|
desc "config list", "Lists all components for a given project"
|
@@ -71,48 +69,41 @@ module BBC
|
|
71
69
|
rescue Exception => e
|
72
70
|
error e.message
|
73
71
|
end
|
74
|
-
|
75
72
|
end
|
76
73
|
|
77
74
|
desc "config push [COMPONENT_ID = nil]", "Push config for a component to the cosmos API"
|
78
75
|
method_option :force, :type => :boolean, :desc => "This skips asking of you want to push the changes"
|
79
76
|
def push(component_id = nil)
|
80
|
-
|
77
|
+
components = component_id.nil? ? cosmos_config.component_keys : [component_id]
|
78
|
+
say "#{banner}\n"
|
81
79
|
|
82
|
-
|
83
|
-
|
80
|
+
reply = yes?("Are you sure you want to push changes for #{components.length} components(s) to #{options[:env]}?", :yellow) unless options[:force]
|
81
|
+
if reply || reply.nil?
|
84
82
|
|
85
|
-
|
86
|
-
|
83
|
+
components.each do |id|
|
84
|
+
say "\n#{get_key_value('Component', id)}\n"
|
87
85
|
|
88
|
-
|
86
|
+
component_config = JSON.generate(cosmos_config.cosmos_component id)
|
89
87
|
|
90
|
-
|
88
|
+
@api = BBC::Cosmos::Tools::API.new(config.app["api"], options[:key_path])
|
89
|
+
response = @api.put sprintf(config.app["config_endpoint"], options[:env], id), component_config
|
91
90
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
response
|
96
|
-
|
97
|
-
if response.success?
|
98
|
-
say 'Successfully saved', :green
|
99
|
-
else
|
100
|
-
api_error response
|
101
|
-
end
|
91
|
+
if response.success?
|
92
|
+
say "Successfully saved", :green
|
93
|
+
else
|
94
|
+
api_error response
|
102
95
|
end
|
103
|
-
|
104
|
-
else
|
105
|
-
error 'Action cancelled'
|
106
96
|
end
|
107
97
|
|
108
|
-
|
109
|
-
error
|
98
|
+
else
|
99
|
+
error "Action cancelled"
|
110
100
|
end
|
111
|
-
end
|
112
101
|
|
102
|
+
rescue Exception => e
|
103
|
+
error e.message
|
104
|
+
end
|
113
105
|
end
|
114
106
|
end
|
115
107
|
end
|
116
108
|
end
|
117
109
|
end
|
118
|
-
|