goodot 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +46 -0
- data/.rspec +3 -0
- data/DEPENDENCIES.md +604 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/LICENSE.rb +5 -0
- data/README.md +550 -0
- data/Rakefile +74 -0
- data/bin/goodot +24 -0
- data/dependency_decisions.yml +104 -0
- data/goodot.gemspec +31 -0
- data/img/hello_world.graffle +710 -0
- data/img/hello_world.png +0 -0
- data/img/master_segment.graffle +432 -0
- data/img/master_segment.png +0 -0
- data/img/project_etl.graffle +385 -0
- data/img/project_etl.png +0 -0
- data/img/relase.graffle +1120 -0
- data/img/release.png +0 -0
- data/img/rollback.graffle +924 -0
- data/img/rollback.png +0 -0
- data/img/service_etl.graffle +605 -0
- data/img/service_etl.png +0 -0
- data/lib/goodot.rb +7 -0
- data/lib/goodot/cli/app.rb +33 -0
- data/lib/goodot/cli/cli.rb +7 -0
- data/lib/goodot/cli/cmd/app_cmd.rb +170 -0
- data/lib/goodot/cli/cmd/clients_cmd.rb +89 -0
- data/lib/goodot/cli/cmd/jack_in.rb +17 -0
- data/lib/goodot/cli/cmd/masters_cmd.rb +67 -0
- data/lib/goodot/cli/cmd/segments_cmd.rb +81 -0
- data/lib/goodot/cli/cmd/version_cmd.rb +20 -0
- data/lib/goodot/cli/shared.rb +93 -0
- data/lib/goodot/version.rb +11 -0
- data/spec/spec_helper.rb +50 -0
- metadata +200 -0
data/img/service_etl.png
ADDED
Binary file
|
data/lib/goodot.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright (c) 2010-2016 GoodData Corporation. All rights reserved.
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright (c) 2010-2016 GoodData Corporation. All rights reserved.
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
require 'gli'
|
8
|
+
require 'pathname'
|
9
|
+
require 'pp'
|
10
|
+
|
11
|
+
require_relative 'shared'
|
12
|
+
require_relative '../version'
|
13
|
+
|
14
|
+
module GoodData
|
15
|
+
module GoodotCLI
|
16
|
+
include GLI::App
|
17
|
+
extend self
|
18
|
+
|
19
|
+
def self.launch(args)
|
20
|
+
exit run args
|
21
|
+
end
|
22
|
+
|
23
|
+
subcommand_option_handling :normal
|
24
|
+
program_desc 'Toolset for helping with lifecycle management of the project'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
cmds = File.absolute_path(File.join(File.dirname(__FILE__), 'cmd'))
|
29
|
+
Dir.glob(cmds + '/*.rb').each do |file|
|
30
|
+
require file
|
31
|
+
end
|
32
|
+
|
33
|
+
GoodData::GoodotCLI.launch(ARGV)
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module GoodData
|
2
|
+
module GoodotCLI
|
3
|
+
desc 'Manage app'
|
4
|
+
command :app do |c|
|
5
|
+
c.desc 'Synchronize clients'
|
6
|
+
c.command :synchronize do |sync|
|
7
|
+
sync.desc 'Segment'
|
8
|
+
sync.flag [:s, :segment]
|
9
|
+
|
10
|
+
sync.action do |global_options, options, _args|
|
11
|
+
ok_key = 'OK'
|
12
|
+
|
13
|
+
domain = global_options[:gd_domain]
|
14
|
+
results = if options[:segment]
|
15
|
+
segment = domain.segments(options[:segment])
|
16
|
+
[segment.synchronize_clients]
|
17
|
+
else
|
18
|
+
domain.synchronize_clients
|
19
|
+
end
|
20
|
+
res = results.pmapcat { |r| r.details.items }.group_by { |x| x['status'] }
|
21
|
+
if res.key?(ok_key) && res.keys.count == 1
|
22
|
+
puts HighLine.color("#{res[ok_key].count} syncs ended up in state #{ok_key}", :green)
|
23
|
+
else
|
24
|
+
res.except(ok_key).each do |k, v|
|
25
|
+
puts "#{v.count} syncs ended up in state #{k}."
|
26
|
+
puts 'Printing first 10'
|
27
|
+
v.each { |x| puts x['id'] }
|
28
|
+
end
|
29
|
+
fail HighLine.color("Some synchronizations ended up in different state than #{ok_key}", :red) unless res.except(ok_key).empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
c.desc 'Export association for backup'
|
35
|
+
c.command :'export-association' do |export|
|
36
|
+
export.desc 'Without project ids'
|
37
|
+
export.switch [:'without-project']
|
38
|
+
export.default_value false
|
39
|
+
|
40
|
+
export.desc 'File for output'
|
41
|
+
export.flag [:f, :file]
|
42
|
+
|
43
|
+
export.action do |global_options, options, _args|
|
44
|
+
domain = global_options[:gd_domain]
|
45
|
+
results = domain.segments.pmapcat { |s| s.clients.map { |cl| [s, cl] } }
|
46
|
+
res = results.map do |s, cl|
|
47
|
+
{
|
48
|
+
segment: s.id,
|
49
|
+
id: cl.id
|
50
|
+
}.tap do |h|
|
51
|
+
h[:project] = cl.project.uri unless options['without-project']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
file = options[:file]
|
55
|
+
if file
|
56
|
+
File.open(file, 'w') { |f| f << MultiJson.dump(res, pretty: true) }
|
57
|
+
else
|
58
|
+
puts MultiJson.dump(res, pretty: true)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
c.desc 'Import association from backup'
|
64
|
+
c.command :'import-association' do |export|
|
65
|
+
export.desc 'File for output'
|
66
|
+
export.flag [:f, :file]
|
67
|
+
|
68
|
+
export.action do |global_options, options, _args|
|
69
|
+
domain = global_options[:gd_domain]
|
70
|
+
specification = MultiJson.load(File.read(options[:file]), symbolize_keys: true)
|
71
|
+
domain.update_clients(specification, delete_extra: true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
c.desc 'Spin up projects'
|
76
|
+
c.command :'spin-up-projects' do |spin|
|
77
|
+
spin.desc 'Segment'
|
78
|
+
spin.flag [:s, :segment]
|
79
|
+
|
80
|
+
spin.action do |global_options, _options, _args|
|
81
|
+
domain = global_options[:gd_domain]
|
82
|
+
results = domain.provision_client_projects
|
83
|
+
if results.to_a.empty?
|
84
|
+
puts HighLine.color('Everything seems to be up to date. Nothing was done!', :green)
|
85
|
+
else
|
86
|
+
puts HighLine.color("#{results.count} operations were performed", :green)
|
87
|
+
puts
|
88
|
+
results.group_by(&:status).each do |status, group|
|
89
|
+
puts HighLine.color("#{group.count} #{status} operations were performed", :green)
|
90
|
+
puts table_with_headers(group, [:id, :project_uri])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
c.desc 'Add segment'
|
97
|
+
c.command :'create-segment' do |seg|
|
98
|
+
seg.desc 'Segment'
|
99
|
+
seg.flag [:s, :segment]
|
100
|
+
|
101
|
+
seg.desc 'Master project'
|
102
|
+
seg.flag [:p, :project]
|
103
|
+
|
104
|
+
seg.action do |global_options, options, _args|
|
105
|
+
client = global_options[:gd_client]
|
106
|
+
domain = global_options[:gd_domain]
|
107
|
+
segment_id = options[:segment]
|
108
|
+
project_id = options[:project]
|
109
|
+
fail 'Segment needs to be provided' unless segment_id
|
110
|
+
fail 'Project id needs to be provided' unless project_id
|
111
|
+
project = client.projects(project_id)
|
112
|
+
|
113
|
+
begin
|
114
|
+
s = domain.create_segment(segment_id: segment_id, master_project: project)
|
115
|
+
puts HighLine.color("Segment #{s.id} created!", :green)
|
116
|
+
rescue RuntimeError => e
|
117
|
+
raise create_api_error_message(e)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
c.desc 'Add client'
|
123
|
+
c.command :'add-client' do |add|
|
124
|
+
add.desc 'Segment'
|
125
|
+
add.flag [:s, :segment]
|
126
|
+
|
127
|
+
add.desc 'Client ID'
|
128
|
+
add.flag [:c, :client]
|
129
|
+
|
130
|
+
add.action do |global_options, options, _args|
|
131
|
+
domain = global_options[:gd_domain]
|
132
|
+
segment_id = options[:segment]
|
133
|
+
client_id = options[:client]
|
134
|
+
fail 'Segment needs to be provided' unless segment_id
|
135
|
+
fail 'client ID needs to be provided.' unless client_id
|
136
|
+
|
137
|
+
s = domain.segments(segment_id)
|
138
|
+
begin
|
139
|
+
c = s.create_client(id: client_id)
|
140
|
+
puts HighLine.color("client #{c.id} created!", :green)
|
141
|
+
rescue RuntimeError => e
|
142
|
+
raise create_api_error_message(e)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
c.desc 'App stats'
|
148
|
+
c.command :stats do |add|
|
149
|
+
add.action do |global_options, _options, _args|
|
150
|
+
client = global_options[:gd_client]
|
151
|
+
domain = global_options[:gd_domain]
|
152
|
+
|
153
|
+
all_projects = client.projects.to_a
|
154
|
+
master_projects = domain.segments.pmap(&:master_project)
|
155
|
+
client_projects = domain.segments.pmapcat { |s| s.clients.map(&:project).compact }
|
156
|
+
|
157
|
+
puts HighLine.color('Projects', :bold)
|
158
|
+
puts "This app contains #{all_projects.count} projects"
|
159
|
+
puts "Out of those #{master_projects.count} are linked as masters"
|
160
|
+
puts "Out of those #{client_projects.count} are linked as clients"
|
161
|
+
|
162
|
+
unused_projects_pids = (all_projects.map(&:pid) - master_projects.map(&:pid) - client_projects.map(&:pid))
|
163
|
+
puts "Additional #{unused_projects_pids.count} projects are in this account"
|
164
|
+
puts 'Printing first 10'
|
165
|
+
puts table_with_headers(unused_projects_pids.take(10).pmap { |pid| client.projects(pid) }, [:pid, :title])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright (c) 2015, Tomas Korcak <korczis@gmail.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative '../shared'
|
24
|
+
|
25
|
+
module GoodData
|
26
|
+
module GoodotCLI
|
27
|
+
desc 'Manage clients in your application'
|
28
|
+
command :clients do |c|
|
29
|
+
c.desc 'List clients'
|
30
|
+
c.command :list do |add|
|
31
|
+
add.desc 'Filter on particular segment'
|
32
|
+
add.default_value :all
|
33
|
+
add.flag [:s, :segment]
|
34
|
+
|
35
|
+
add.action do |global_options, options, _args|
|
36
|
+
domain = global_options[:gd_domain]
|
37
|
+
segment_id = options[:segment]
|
38
|
+
table = Terminal::Table.new do |t|
|
39
|
+
t << ['Client Id', 'Segment URI', 'Project URI']
|
40
|
+
t << :separator
|
41
|
+
Array(domain.segments(segment_id)).pmapcat(&:clients).each { |s| t.add_row([s.client_id, s.segment_uri, s.project_uri]) }
|
42
|
+
end
|
43
|
+
puts table
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
c.desc 'Remove client'
|
48
|
+
c.command :remove do |remove|
|
49
|
+
remove.desc 'Client'
|
50
|
+
remove.flag [:c, :client]
|
51
|
+
|
52
|
+
remove.desc 'Delete the project'
|
53
|
+
remove.default_value false
|
54
|
+
remove.switch [:d, :'delete-project']
|
55
|
+
|
56
|
+
remove.action do |global_options, options, _args|
|
57
|
+
domain = global_options[:gd_domain]
|
58
|
+
client_name = options[:client]
|
59
|
+
fail 'Client needs to be provided' unless client_name
|
60
|
+
client = domain.clients(segment_name)
|
61
|
+
project = client.project
|
62
|
+
client.delete
|
63
|
+
project.delete if options[:d]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
c.desc 'Reset client. Removes the project from the client.'
|
68
|
+
c.command :reset do |reset|
|
69
|
+
reset.desc 'Client'
|
70
|
+
reset.flag [:c, :client]
|
71
|
+
|
72
|
+
reset.desc 'Delete the project'
|
73
|
+
reset.default_value false
|
74
|
+
reset.switch [:d, :'delete-project']
|
75
|
+
|
76
|
+
reset.action do |global_options, options, _args|
|
77
|
+
domain = global_options[:gd_domain]
|
78
|
+
client_name = options[:client]
|
79
|
+
fail 'Client needs to be provided' unless client_name
|
80
|
+
client = domain.clients.find { |cl| cl.id == client_name }
|
81
|
+
project = client.project
|
82
|
+
client.project = nil
|
83
|
+
client.save
|
84
|
+
project.delete if options[:d]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../shared'
|
2
|
+
|
3
|
+
module GoodData
|
4
|
+
module GoodotCLI
|
5
|
+
desc 'Jacks you in to work interactively with your project.'
|
6
|
+
command ['jack-in', 'jack_in'] do |c|
|
7
|
+
c.action do |global_options, _options, _args|
|
8
|
+
client = global_options[:gd_client]
|
9
|
+
domain = global_options[:gd_domain]
|
10
|
+
binding.pry(quiet: true, # rubocop:disable Lint/Debugger
|
11
|
+
prompt: [proc do |_target_self, _nest_level, _pry|
|
12
|
+
'app_live_session: '
|
13
|
+
end])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# # encoding: utf-8
|
2
|
+
#
|
3
|
+
# # Copyright (c) 2015, Tomas Korcak <korczis@gmail.com>
|
4
|
+
# #
|
5
|
+
# # Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# # of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# # in the Software without restriction, including without limitation the rights
|
8
|
+
# # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# # copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# # furnished to do so, subject to the following conditions:
|
11
|
+
# #
|
12
|
+
# # The above copyright notice and this permission notice shall be included in
|
13
|
+
# # all copies or substantial portions of the Software.
|
14
|
+
# #
|
15
|
+
# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# # THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative '../shared'
|
24
|
+
|
25
|
+
module GoodData
|
26
|
+
module GoodotCLI
|
27
|
+
desc 'Manage masters'
|
28
|
+
command :masters do |c|
|
29
|
+
c.desc 'List masters'
|
30
|
+
c.command :list do |add|
|
31
|
+
add.action do |global_options, _options, _args|
|
32
|
+
domain = global_options[:gd_domain]
|
33
|
+
currently_used = domain.segments.pmap { |s| [s, s.master_project] }.compact
|
34
|
+
|
35
|
+
puts table_with_headers(currently_used,
|
36
|
+
[
|
37
|
+
proc { |_, p| p && p.pid },
|
38
|
+
proc { |_, p| p && p.title },
|
39
|
+
proc { |s, _| s.id },
|
40
|
+
proc { |_, p| p && p.metadata['GD_LCM_VERSION'] }
|
41
|
+
],
|
42
|
+
headers: ['PID', 'Title', 'Segment ID', 'Version'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
c.desc 'Clone master of particular segment'
|
47
|
+
c.command :clone do |add|
|
48
|
+
add.desc 'Segment which master will be cloned'
|
49
|
+
add.default_value nil
|
50
|
+
add.flag [:s, :segment]
|
51
|
+
|
52
|
+
add.desc 'Authorization token'
|
53
|
+
add.default_value nil
|
54
|
+
add.flag [:t, :token]
|
55
|
+
|
56
|
+
add.action do |global_options, options, _args|
|
57
|
+
domain = global_options[:gd_domain]
|
58
|
+
fail 'You have to specify segment' unless options[:segment]
|
59
|
+
master_project = domain.segments(options[:segment]).master_project
|
60
|
+
clone = GoodData::Project.clone_with_etl(master_project, auth_token: options[:token])
|
61
|
+
puts "Project \"#{master_project.title}\" (#{master_project.pid}) was cloned."
|
62
|
+
puts "\"#{clone.title}\" (#{clone.pid}) was created."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module GoodData
|
2
|
+
module GoodotCLI
|
3
|
+
desc 'Manage segments'
|
4
|
+
command :segments do |c|
|
5
|
+
c.desc 'List segments'
|
6
|
+
c.command :list do |add|
|
7
|
+
add.desc 'Format'
|
8
|
+
add.default_value :table
|
9
|
+
add.flag [:f, :format]
|
10
|
+
|
11
|
+
add.action do |global_options, options, _args|
|
12
|
+
domain = global_options[:gd_domain]
|
13
|
+
|
14
|
+
segments = domain.segments
|
15
|
+
case options[:format].to_sym
|
16
|
+
when :table
|
17
|
+
puts table_with_headers(segments, [:id, :master_project_id])
|
18
|
+
when :json
|
19
|
+
output = segments.map do |s|
|
20
|
+
{
|
21
|
+
name: s.id,
|
22
|
+
master_project_id: s.master_project_id
|
23
|
+
}
|
24
|
+
end
|
25
|
+
puts MultiJson.dump(output, pretty: true)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
c.desc 'Remove segment'
|
31
|
+
c.command [:remove, :delete] do |add|
|
32
|
+
add.desc 'Segment'
|
33
|
+
add.flag [:s, :segment]
|
34
|
+
|
35
|
+
add.desc 'Delete also the clients in that particular segment'
|
36
|
+
add.default_value false
|
37
|
+
add.switch [:c, :cascade]
|
38
|
+
|
39
|
+
add.action do |global_options, options, _args|
|
40
|
+
domain = global_options[:gd_domain]
|
41
|
+
segment_name = options[:segment]
|
42
|
+
fail 'Segment needs to be provided' unless segment_name
|
43
|
+
segment = domain.segments(segment_name)
|
44
|
+
segment.clients.peach(&:delete) if options[:cascade]
|
45
|
+
segment.delete
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
c.desc 'Exchange master on segment.'
|
50
|
+
c.command :exchange do |exchange|
|
51
|
+
exchange.desc 'Segment'
|
52
|
+
exchange.flag [:s, :segment]
|
53
|
+
|
54
|
+
exchange.desc 'Master project ID'
|
55
|
+
exchange.flag [:p, :project]
|
56
|
+
|
57
|
+
exchange.desc 'Delete the old project'
|
58
|
+
exchange.default_value false
|
59
|
+
exchange.switch [:d, :delete]
|
60
|
+
|
61
|
+
exchange.action do |global_options, options, _args|
|
62
|
+
client = global_options[:gd_client]
|
63
|
+
domain = global_options[:gd_domain]
|
64
|
+
|
65
|
+
segment_name = options[:segment]
|
66
|
+
fail 'Segment needs to be provided' unless segment_name
|
67
|
+
segment = domain.segments(segment_name)
|
68
|
+
|
69
|
+
project_id = options[:project]
|
70
|
+
fail 'Project Master id needs to be provided' unless project_id
|
71
|
+
old_master = segment.master_project
|
72
|
+
new_master_project = client.projects(project_id)
|
73
|
+
|
74
|
+
segment.master_project = new_master_project
|
75
|
+
segment.save
|
76
|
+
old_master.delete if options[:delete]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|