release_manager 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.
- checksums.yaml +7 -0
- data/.bash_profile +27 -0
- data/.dockerignore +1 -0
- data/.env +3 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +55 -0
- data/Dockerfile +14 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +72 -0
- data/LICENSE.txt +21 -0
- data/README.md +311 -0
- data/Rakefile +6 -0
- data/app_startup_script.sh +4 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +37 -0
- data/exe/bump-changelog +5 -0
- data/exe/deploy-mod +5 -0
- data/exe/release-mod +5 -0
- data/exe/sandbox-create +13 -0
- data/lib/release_manager.rb +33 -0
- data/lib/release_manager/changelog.rb +130 -0
- data/lib/release_manager/cli/deploy_mod_cli.rb +44 -0
- data/lib/release_manager/cli/release_mod_cli.rb +43 -0
- data/lib/release_manager/cli/sandbox_create_cli.rb +138 -0
- data/lib/release_manager/control_mod.rb +83 -0
- data/lib/release_manager/control_repo.rb +35 -0
- data/lib/release_manager/errors.rb +13 -0
- data/lib/release_manager/git/credentials.rb +98 -0
- data/lib/release_manager/git/utilites.rb +263 -0
- data/lib/release_manager/logger.rb +52 -0
- data/lib/release_manager/module_deployer.rb +77 -0
- data/lib/release_manager/puppet_module.rb +211 -0
- data/lib/release_manager/puppetfile.rb +148 -0
- data/lib/release_manager/release.rb +174 -0
- data/lib/release_manager/sandbox.rb +272 -0
- data/lib/release_manager/vcs_manager.rb +22 -0
- data/lib/release_manager/vcs_manager/gitlab_adapter.rb +112 -0
- data/lib/release_manager/vcs_manager/vcs_adapter.rb +22 -0
- data/lib/release_manager/version.rb +3 -0
- data/lib/release_manager/workflow_action.rb +5 -0
- data/release_manager.gemspec +38 -0
- data/setup_repos.rb +95 -0
- metadata +175 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Author: Corey Osman
|
4
|
+
# Purpose: release a new version of a module or r10k-control from the src branch by performing
|
5
|
+
# the following tasks:
|
6
|
+
# - bump version in metadata file
|
7
|
+
# - bump changelog version using version in metadata file
|
8
|
+
# - tag the code matching the version in the metadata file
|
9
|
+
# - push to upstream
|
10
|
+
# This script can be used on modules or r10k-control. If using on a module
|
11
|
+
# be sure to pass in the repo path using --repo. The repo is where this script
|
12
|
+
# pushes too.
|
13
|
+
#
|
14
|
+
# You should also use the -d feature which simulates a run of the script without doing
|
15
|
+
# anything harmful.
|
16
|
+
#
|
17
|
+
# Run with -h to see the help
|
18
|
+
require 'json'
|
19
|
+
require_relative 'puppet_module'
|
20
|
+
|
21
|
+
class Release
|
22
|
+
attr_reader :path, :options
|
23
|
+
include ReleaseManager::Logger
|
24
|
+
|
25
|
+
def initialize(path = Dir.getwd, options = {})
|
26
|
+
@path = path || Dir.getwd
|
27
|
+
@options = options
|
28
|
+
end
|
29
|
+
|
30
|
+
def puppet_module
|
31
|
+
@puppet_module ||= PuppetModule.new(path, upstream_repo)
|
32
|
+
end
|
33
|
+
|
34
|
+
def upstream_repo
|
35
|
+
options[:repo] || ENV['UPSTREAM_REPO']
|
36
|
+
end
|
37
|
+
|
38
|
+
# @returns [String] the version found in the metadata file
|
39
|
+
def version
|
40
|
+
dry_run? ? puppet_module.version.next : puppet_module.version
|
41
|
+
end
|
42
|
+
|
43
|
+
def tag
|
44
|
+
if dry_run?
|
45
|
+
logger.info "Would have just tagged the module to #{version}"
|
46
|
+
return
|
47
|
+
end
|
48
|
+
puppet_module.tag_module
|
49
|
+
end
|
50
|
+
|
51
|
+
def bump
|
52
|
+
if dry_run?
|
53
|
+
logger.info "Would have just bumped the version to #{version}"
|
54
|
+
return
|
55
|
+
end
|
56
|
+
puppet_module.bump_patch_version unless options[:bump]
|
57
|
+
# save the update version to the metadata file, then commit
|
58
|
+
puppet_module.commit_metadata
|
59
|
+
end
|
60
|
+
|
61
|
+
def bump_log
|
62
|
+
if dry_run?
|
63
|
+
logger.info "Would have just bumped the CHANGELOG to version #{version}"
|
64
|
+
return
|
65
|
+
end
|
66
|
+
log = Changelog.new(puppet_module.path, version, {:commit => true})
|
67
|
+
log.run
|
68
|
+
end
|
69
|
+
|
70
|
+
def push
|
71
|
+
if dry_run?
|
72
|
+
logger.info "Would have just pushed the code and tag to #{puppet_module.source}"
|
73
|
+
return
|
74
|
+
end
|
75
|
+
puppet_module.push_to_upstream
|
76
|
+
end
|
77
|
+
|
78
|
+
def dry_run?
|
79
|
+
options[:dry_run] == true
|
80
|
+
end
|
81
|
+
|
82
|
+
def auto_release?
|
83
|
+
options[:auto] || ENV['AUTO_RELEASE'] == 'true'
|
84
|
+
end
|
85
|
+
|
86
|
+
def check_requirements
|
87
|
+
begin
|
88
|
+
PuppetModule.check_requirements(puppet_module.path)
|
89
|
+
Changelog.check_requirements(puppet_module.path)
|
90
|
+
rescue NoUnreleasedLine
|
91
|
+
logger.fatal "No Unreleased line in the CHANGELOG.md file, please add a Unreleased line and retry"
|
92
|
+
exit 1
|
93
|
+
rescue UpstreamSourceMatch
|
94
|
+
logger.fatal "The upstream remote url does not match the source url in the metadata.json source"
|
95
|
+
add_upstream_remote
|
96
|
+
exit 1
|
97
|
+
rescue InvalidMetadataSource
|
98
|
+
logger.fatal "The puppet module's metadata.json source field must be a git url: ie. git@someserver.com:devops/module.git"
|
99
|
+
exit 1
|
100
|
+
rescue NoChangeLogFile
|
101
|
+
logger.fatal "CHANGELOG.md does not exist, please create one"
|
102
|
+
exit 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# runs all the required steps to release the software
|
107
|
+
# currently this must be done manually by a release manager
|
108
|
+
#
|
109
|
+
def release
|
110
|
+
unless auto_release?
|
111
|
+
print "Have you merged your code? Did you fetch and rebase against the upstream? Want to continue (y/n)?: ".yellow
|
112
|
+
answer = gets.downcase.chomp
|
113
|
+
if answer == 'n'
|
114
|
+
return false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# updates the metadata.js file to the next version
|
119
|
+
bump
|
120
|
+
# updates the changelog to the next version based on the metadata file
|
121
|
+
bump_log
|
122
|
+
# tags the r10k-module with the version found in the metadata.json file
|
123
|
+
tag
|
124
|
+
# pushes the updated code and tags to the upstream repo
|
125
|
+
if auto_release?
|
126
|
+
push
|
127
|
+
return
|
128
|
+
end
|
129
|
+
print "Ready to release version #{version} to #{puppet_module.source}\n and forever change history(y/n)?: ".yellow
|
130
|
+
answer = gets.downcase.chomp
|
131
|
+
if answer == 'y'
|
132
|
+
push
|
133
|
+
$?.success?
|
134
|
+
else
|
135
|
+
puts "Nah, forget it, this release wasn't that cool anyways.".yellow
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def add_upstream_remote
|
141
|
+
answer = nil
|
142
|
+
while answer !~ /y|n/
|
143
|
+
print "Ok to change your upstream remote from #{puppet_module.upstream}\n to #{puppet_module.source}? (y/n): "
|
144
|
+
answer = gets.downcase.chomp
|
145
|
+
end
|
146
|
+
puppet_module.add_upstream_remote if answer == 'y'
|
147
|
+
end
|
148
|
+
|
149
|
+
def verbose?
|
150
|
+
options[:verbose]
|
151
|
+
end
|
152
|
+
|
153
|
+
def run
|
154
|
+
begin
|
155
|
+
check_requirements
|
156
|
+
puppet_module.create_dev_branch
|
157
|
+
value = release
|
158
|
+
unless value
|
159
|
+
exit 1
|
160
|
+
end
|
161
|
+
logger.info "Releasing Version #{version} to #{puppet_module.source}"
|
162
|
+
logger.info "Version #{version} has been released successfully"
|
163
|
+
puts "This was a dry run so nothing actually happen".green if dry_run?
|
164
|
+
exit 0
|
165
|
+
rescue GitError
|
166
|
+
logger.fatal "There was an issue when running a git command"
|
167
|
+
rescue InvalidMetadataSource
|
168
|
+
logger.fatal "The puppet module's metadata.json source field must be a git url: ie. git@someserver.com:devops/module.git"
|
169
|
+
rescue ModNotFoundException
|
170
|
+
logger.fatal "Invalid module path for #{path}"
|
171
|
+
exit -1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require_relative 'puppet_module'
|
2
|
+
require_relative 'control_repo'
|
3
|
+
require 'gitlab'
|
4
|
+
require 'rugged'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'release_manager/logger'
|
7
|
+
require 'release_manager/vcs_manager'
|
8
|
+
|
9
|
+
class Sandbox
|
10
|
+
attr_reader :modules, :name, :repos_dir, :options,
|
11
|
+
:control_repo, :module_names, :control_repo_path, :vcs
|
12
|
+
|
13
|
+
include ReleaseManager::Logger
|
14
|
+
|
15
|
+
def initialize(name, modules, control_repo_path, repos_dir = nil, options = {})
|
16
|
+
@name = name
|
17
|
+
@repos_dir = repos_dir
|
18
|
+
@module_names = modules
|
19
|
+
@control_repo_path = control_repo_path
|
20
|
+
@vcs = ReleaseManager::VCSManager.default_instance
|
21
|
+
@options = options
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [String] repos_path - the path to the repos directory where you want to clone modules
|
25
|
+
# @return [String] the repos_path
|
26
|
+
# Creates the repos path using mkdir_p unless the path already exists
|
27
|
+
def setup_repos_dir(repos_path)
|
28
|
+
FileUtils.mkdir_p(repos_path) unless File.exists?(repos_path)
|
29
|
+
repos_path
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String] the repos_path, defaults to ~/repos
|
33
|
+
def repos_dir
|
34
|
+
@repos_dir ||= File.expand_path(File.join(ENV['HOME'], 'repos'))
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String] the r10k control repo path, defaults to ~/repos/r10k-control
|
38
|
+
def control_repo_path
|
39
|
+
@control_repo_path ||= File.expand_path(File.join(repos_dir, 'r10k-control'))
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [ControlRepo] - a ControlRepo object
|
43
|
+
def control_repo
|
44
|
+
@control_repo ||= ControlRepo.new(control_repo_path)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [ControlRepo] - creates a new control repo object and clones the url unless already cloned
|
48
|
+
# @param [String] url - the url to clone and fork
|
49
|
+
def setup_control_repo(url)
|
50
|
+
# clone r10k unless already cloned
|
51
|
+
puts "## r10k-control ##".yellow
|
52
|
+
fork = create_repo_fork(url)
|
53
|
+
c = ControlRepo.create(control_repo_path, fork.ssh_url_to_repo)
|
54
|
+
c.add_remote(fork.ssh_url_to_repo, 'myfork')
|
55
|
+
c.fetch('myfork')
|
56
|
+
c.fetch('origin')
|
57
|
+
c.add_remote(url, 'upstream')
|
58
|
+
# if the user doesn't have the branch, we create from upstream
|
59
|
+
# and then checkout from the fork, we defer pushing the branch to later after updating the puppetfile
|
60
|
+
target = c.branch_exist?("upstream/#{name}") ? "upstream/#{name}" : 'upstream/dev'
|
61
|
+
# if the user has previously created the branch but doesn't exist locally, no need to create
|
62
|
+
c.create_branch(name, target)
|
63
|
+
c.checkout_branch(name)
|
64
|
+
c
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [PuppetModule] - creates a new puppet_module object and clones the url unless already cloned
|
68
|
+
# @param [ControlMod] mod - the module to clone and fork
|
69
|
+
# @param [Boolean] create_fork - defaults to true which creates a fork
|
70
|
+
# if the fork is already created, do nothing
|
71
|
+
def setup_module_repo(mod)
|
72
|
+
raise InvalidModule.new(mod) unless mod.instance_of?(ControlMod)
|
73
|
+
fork = create_repo_fork(mod.repo)
|
74
|
+
m = PuppetModule.create(File.join(repos_dir, mod.name), fork.ssh_url_to_repo, name)
|
75
|
+
m.fetch('origin')
|
76
|
+
m.add_remote(fork.ssh_url_to_repo, 'myfork')
|
77
|
+
# without the following, we risk accidently setting the upstream to the newly forked url
|
78
|
+
# this occurs because r10k-control branch contains the forked url instead of the upstream url
|
79
|
+
# we assume the metadata.source attribute contains the correct upstream url
|
80
|
+
begin
|
81
|
+
delay_source_change = false
|
82
|
+
if m.source =~ /\Agit\@/
|
83
|
+
m.add_remote(m.source, 'upstream', true)
|
84
|
+
else
|
85
|
+
logger.warn("Module's source is not defined correctly for #{m.name} should be a git url, fixing...")
|
86
|
+
# delay the changing of metadata source until we checkout the branch
|
87
|
+
delay_source_change = true
|
88
|
+
m.add_remote(mod.repo, 'upstream', true)
|
89
|
+
end
|
90
|
+
rescue ModNotFoundException => e
|
91
|
+
logger.error("Is #{mod.name} a puppet module? Can't find the metadata source")
|
92
|
+
end
|
93
|
+
# if the user doesn't have the branch, we create from upstream
|
94
|
+
# and then checkout from the fork
|
95
|
+
# if the user has previously created the branch but doesn't exist locally, no need to create
|
96
|
+
if m.remote_exists?('upstream')
|
97
|
+
target = m.branch_exist?("myfork/#{name}") ? "myfork/#{name}" : 'upstream/master'
|
98
|
+
else
|
99
|
+
# don't create from upstream since the upstream remote does not exist
|
100
|
+
# upstream does not exist because the url in the metadata source is not a git url
|
101
|
+
target = 'master'
|
102
|
+
end
|
103
|
+
m.create_branch(name, target)
|
104
|
+
m.push_branch('myfork', name)
|
105
|
+
m.checkout_branch(name)
|
106
|
+
if delay_source_change
|
107
|
+
m.source = mod.repo
|
108
|
+
m.commit_metadata_source
|
109
|
+
end
|
110
|
+
logger.info("Updating r10k-control Puppetfile to use fork: #{fork.ssh_url_to_repo} with branch: #{name}")
|
111
|
+
puppetfile.write_source(mod.name, fork.ssh_url_to_repo, name )
|
112
|
+
m
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_new_module(mod_name)
|
116
|
+
repo_url = nil
|
117
|
+
loop do
|
118
|
+
print "Please enter the git url of the source repo : ".yellow
|
119
|
+
repo_url = gets.chomp
|
120
|
+
break if repo_url =~ /git\@/
|
121
|
+
puts "Repo Url must be a git url".red
|
122
|
+
end
|
123
|
+
puppetfile.add_module(mod_name, git: repo_url)
|
124
|
+
end
|
125
|
+
|
126
|
+
# checkout and/or create branch
|
127
|
+
# get modules
|
128
|
+
# fork module unless already exists
|
129
|
+
# clone fork of module
|
130
|
+
# create branch of fork
|
131
|
+
# set module fork
|
132
|
+
# set module branch
|
133
|
+
# set upstream to original namespace
|
134
|
+
# cleanup branches
|
135
|
+
def create(r10k_url)
|
136
|
+
setup_repos_dir(repos_dir)
|
137
|
+
@control_repo = setup_control_repo(r10k_url)
|
138
|
+
# get modules we are interested in
|
139
|
+
module_names.each do | mod_name |
|
140
|
+
puts "## #{mod_name} ##".yellow
|
141
|
+
begin
|
142
|
+
mod = puppetfile.find_mod(mod_name)
|
143
|
+
setup_module_repo(mod)
|
144
|
+
rescue InvalidModuleNameException => e
|
145
|
+
logger.error(e.message)
|
146
|
+
value = nil
|
147
|
+
loop do
|
148
|
+
print "Do you want to create a new entry in the Puppetfile for the module named #{mod_name}?(y/n): ".yellow
|
149
|
+
value = gets.downcase.chomp
|
150
|
+
break if value =~ /y|n/
|
151
|
+
end
|
152
|
+
next if value == 'n'
|
153
|
+
mod = setup_new_module(mod_name)
|
154
|
+
setup_module_repo(mod)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
@control_repo.checkout_branch(name)
|
158
|
+
puppetfile.write_to_file
|
159
|
+
logger.info("Committing Puppetfile changes to r10k-control branch: #{name}")
|
160
|
+
puppetfile.commit("Sandbox Creation for #{name} environment")
|
161
|
+
logger.info("Pushing new environment branch: #{name} to upstream")
|
162
|
+
puppetfile.push('upstream', name, true)
|
163
|
+
return self
|
164
|
+
end
|
165
|
+
|
166
|
+
# @param [String] url - a git url
|
167
|
+
# @return [String] a string representing the project id from gitlab
|
168
|
+
# gets the project id from gitlab using the remote API
|
169
|
+
def repo_id(url)
|
170
|
+
# ie. git@server:namespace/project.git
|
171
|
+
proj = url.match(/:(.*\/.*)\.git/)
|
172
|
+
raise RepoNotFound unless proj
|
173
|
+
# the gitlab api is supposed to encode the slash, but currently that doesn't seem to work
|
174
|
+
proj[1].gsub('/', '%2F')
|
175
|
+
end
|
176
|
+
|
177
|
+
# TODO: extract this out to an adapter
|
178
|
+
def verify_api_token
|
179
|
+
begin
|
180
|
+
Gitlab.user
|
181
|
+
rescue Exception => e
|
182
|
+
raise InvalidToken.new(e.message)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# TODO: extract this out to an adapter
|
187
|
+
# replaces namespace from the url with the supplied or default namespace
|
188
|
+
def swap_namespace(url, namespace = nil)
|
189
|
+
url.gsub(/\:([\w-]+)\//, ":#{namespace || Gitlab.user.username}/")
|
190
|
+
end
|
191
|
+
|
192
|
+
# @return [Gitlab::ObjectifiedHash] Information about the forked project
|
193
|
+
# @param [ControlMod] the module you want to fork
|
194
|
+
# TODO: extract this out to an adapter
|
195
|
+
def create_repo_fork(url, namespace = nil )
|
196
|
+
new_url = swap_namespace(url, namespace)
|
197
|
+
repo = repo_exists?(new_url)
|
198
|
+
unless repo
|
199
|
+
upstream_repo_id = repo_id(url)
|
200
|
+
logger.info("Forking project from #{url} to #{new_url}")
|
201
|
+
repo = Gitlab.create_fork(upstream_repo_id)
|
202
|
+
# gitlab lies about having completed the forking process, so lets sleep until it is actually done
|
203
|
+
loop do
|
204
|
+
sleep(1)
|
205
|
+
break if repo_exists?(repo.ssh_url_to_repo)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
vcs.add_permissions(repo.id, options[:default_members])
|
209
|
+
repo
|
210
|
+
end
|
211
|
+
|
212
|
+
# @param [String] url - the git url of the repository
|
213
|
+
# @return [Boolean] returns the project object (true) if found, false otherwise
|
214
|
+
# TODO: extract this out to an adapter
|
215
|
+
def repo_exists?(url)
|
216
|
+
upstream_repo_id = repo_id(url)
|
217
|
+
begin
|
218
|
+
Gitlab.project(upstream_repo_id)
|
219
|
+
rescue
|
220
|
+
false
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# @return String - the branch name that was created
|
225
|
+
# TODO: extract this out to an adapter
|
226
|
+
def create_repo_branch(repo_id, branch_name)
|
227
|
+
Gitlab.repo_create_branch(repo_id, branch_name)
|
228
|
+
end
|
229
|
+
|
230
|
+
# TODO: extract this out to an adapter
|
231
|
+
def clone_repo(mod_name, url)
|
232
|
+
path = File.join(repos_dir, mod_name)
|
233
|
+
Rugged::Repository.clone_at(url, path, checkout_branch: name)
|
234
|
+
end
|
235
|
+
|
236
|
+
# @returns [Hash[PuppetModules]] an hash of puppet modules
|
237
|
+
def modules
|
238
|
+
@modules ||= puppetfile.find_mods(module_names)
|
239
|
+
end
|
240
|
+
|
241
|
+
def puppetfile
|
242
|
+
@puppetfile ||= control_repo.puppetfile
|
243
|
+
end
|
244
|
+
|
245
|
+
def check_requirements
|
246
|
+
begin
|
247
|
+
vcs.add_ssh_key
|
248
|
+
rescue InvalidModuleNameException => e
|
249
|
+
logger.error(e.message)
|
250
|
+
exit 1
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# @param [String] name - the name of the sandbox
|
255
|
+
# @param [Array[String] modules - the names of the modules that should be forked and branched
|
256
|
+
# @param [String] repos_dir - the path to the repos directory
|
257
|
+
# @param [String] control_repo_path - the url or path to the r10k control repo
|
258
|
+
# @option [String] fork_namespace - the namespace from which you fork modules to
|
259
|
+
#
|
260
|
+
# the user passes in the r10k git url or path or we assume the path
|
261
|
+
# if the user does not pass in the git url we assume the repo already exist
|
262
|
+
# if the repo doesn't exist we clone the url
|
263
|
+
def self.create(name, options)
|
264
|
+
box = Sandbox.new(name, options[:modules],
|
265
|
+
options[:r10k_repo_path],
|
266
|
+
options[:repos_path], options)
|
267
|
+
box.check_requirements
|
268
|
+
box.verify_api_token
|
269
|
+
box.create(options[:r10k_repo_url])
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'release_manager/vcs_manager/gitlab_adapter'
|
2
|
+
|
3
|
+
module ReleaseManager
|
4
|
+
module VCSManager
|
5
|
+
def self.default_instance
|
6
|
+
ReleaseManager::VCSManager::GitlabAdapter.create
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.adapter_types
|
10
|
+
[:gitlab]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.adapter_instance(type)
|
14
|
+
case type
|
15
|
+
when :gitlab
|
16
|
+
ReleaseManager::VCSManager::GitlabAdapter.create
|
17
|
+
else
|
18
|
+
default_instance
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|