vagabond 0.1.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.
Files changed (63) hide show
  1. data/CHANGELOG.md +2 -0
  2. data/README.md +94 -0
  3. data/bin/vagabond +6 -0
  4. data/lib/vagabond.rb +1 -0
  5. data/lib/vagabond/actions/create.rb +29 -0
  6. data/lib/vagabond/actions/destroy.rb +31 -0
  7. data/lib/vagabond/actions/freeze.rb +14 -0
  8. data/lib/vagabond/actions/provision.rb +31 -0
  9. data/lib/vagabond/actions/ssh.rb +13 -0
  10. data/lib/vagabond/actions/status.rb +29 -0
  11. data/lib/vagabond/actions/thaw.rb +14 -0
  12. data/lib/vagabond/actions/up.rb +27 -0
  13. data/lib/vagabond/bootstraps/server.erb +62 -0
  14. data/lib/vagabond/commands.rb +58 -0
  15. data/lib/vagabond/config.rb +7 -0
  16. data/lib/vagabond/cookbooks/lxc/CHANGELOG.md +21 -0
  17. data/lib/vagabond/cookbooks/lxc/Gemfile +3 -0
  18. data/lib/vagabond/cookbooks/lxc/Gemfile.lock +132 -0
  19. data/lib/vagabond/cookbooks/lxc/README.md +83 -0
  20. data/lib/vagabond/cookbooks/lxc/attributes/default.rb +26 -0
  21. data/lib/vagabond/cookbooks/lxc/files/default/knife_lxc +228 -0
  22. data/lib/vagabond/cookbooks/lxc/libraries/lxc.rb +279 -0
  23. data/lib/vagabond/cookbooks/lxc/libraries/lxc_expanded_resources.rb +40 -0
  24. data/lib/vagabond/cookbooks/lxc/libraries/lxc_file_config.rb +81 -0
  25. data/lib/vagabond/cookbooks/lxc/metadata.rb +11 -0
  26. data/lib/vagabond/cookbooks/lxc/providers/config.rb +82 -0
  27. data/lib/vagabond/cookbooks/lxc/providers/container.rb +342 -0
  28. data/lib/vagabond/cookbooks/lxc/providers/fstab.rb +71 -0
  29. data/lib/vagabond/cookbooks/lxc/providers/interface.rb +99 -0
  30. data/lib/vagabond/cookbooks/lxc/providers/service.rb +53 -0
  31. data/lib/vagabond/cookbooks/lxc/recipes/containers.rb +13 -0
  32. data/lib/vagabond/cookbooks/lxc/recipes/default.rb +45 -0
  33. data/lib/vagabond/cookbooks/lxc/recipes/install_dependencies.rb +15 -0
  34. data/lib/vagabond/cookbooks/lxc/recipes/knife.rb +37 -0
  35. data/lib/vagabond/cookbooks/lxc/resources/#container.rb# +28 -0
  36. data/lib/vagabond/cookbooks/lxc/resources/config.rb +19 -0
  37. data/lib/vagabond/cookbooks/lxc/resources/container.rb +28 -0
  38. data/lib/vagabond/cookbooks/lxc/resources/fstab.rb +11 -0
  39. data/lib/vagabond/cookbooks/lxc/resources/interface.rb +10 -0
  40. data/lib/vagabond/cookbooks/lxc/resources/service.rb +5 -0
  41. data/lib/vagabond/cookbooks/lxc/templates/default/client.rb.erb +13 -0
  42. data/lib/vagabond/cookbooks/lxc/templates/default/default-lxc.erb +3 -0
  43. data/lib/vagabond/cookbooks/lxc/templates/default/fstab.erb +5 -0
  44. data/lib/vagabond/cookbooks/lxc/templates/default/interface.erb +21 -0
  45. data/lib/vagabond/cookbooks/lxc/test/kitchen/Kitchenfile +7 -0
  46. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/metadata.rb +2 -0
  47. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/centos_lxc.rb +0 -0
  48. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/chef-bootstrap.rb +0 -0
  49. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/lxc_files.rb +0 -0
  50. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/lxc_templates.rb +0 -0
  51. data/lib/vagabond/cookbooks/lxc/test/kitchen/cookbooks/lxc_test/recipes/ubuntu_lxc.rb +0 -0
  52. data/lib/vagabond/cookbooks/vagabond/attributes/default.rb +2 -0
  53. data/lib/vagabond/cookbooks/vagabond/libraries/vagabond.rb +10 -0
  54. data/lib/vagabond/cookbooks/vagabond/metadata.rb +6 -0
  55. data/lib/vagabond/cookbooks/vagabond/recipes/create.rb +3 -0
  56. data/lib/vagabond/cookbooks/vagabond/recipes/default.rb +30 -0
  57. data/lib/vagabond/internal_configuration.rb +147 -0
  58. data/lib/vagabond/server.rb +158 -0
  59. data/lib/vagabond/vagabond.rb +109 -0
  60. data/lib/vagabond/vagabondfile.rb +34 -0
  61. data/lib/vagabond/version.rb +10 -0
  62. data/vagabond.gemspec +18 -0
  63. metadata +125 -0
@@ -0,0 +1,21 @@
1
+ ## v0.1.0
2
+ * Abstracted out packages for cross-platform support later.
3
+ * Added the 'containers' recipe to create containers for the members of the node['lxc']['containers'] hash
4
+ * Add support for use of the apt::cacher-client settings if a proxy is in use.
5
+ * chef_enabled defaults to false on lxc_containers
6
+ * Better idempotency checks when building new containers
7
+ * Refactoring of lxc_service
8
+ * Container based commands run via knife::ssh providing proper logging feedback
9
+ * New networking related attributes added to lxc_container for easy basic network setups
10
+
11
+ ## v0.0.3
12
+ * Remove resource for deprecated template
13
+
14
+ ## v0.0.2
15
+ * Cleanup current config and container LWRPs
16
+ * Add new LWRPs (fstab and interface)
17
+ * Add better configuration build to prevent false updates
18
+ * Thanks to Sean Porter (https://github.com/portertech) for help debugging LWRP updates
19
+
20
+ ## v0.0.1
21
+ * Initial release
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gem 'test-kitchen'
@@ -0,0 +1,132 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ archive-tar-minitar (0.5.2)
5
+ builder (3.1.4)
6
+ bunny (0.7.9)
7
+ chef (10.18.2)
8
+ bunny (>= 0.6.0, < 0.8.0)
9
+ erubis
10
+ highline (>= 1.6.9)
11
+ json (>= 1.4.4, <= 1.6.1)
12
+ mixlib-authentication (>= 1.3.0)
13
+ mixlib-cli (>= 1.1.0)
14
+ mixlib-config (>= 1.1.2)
15
+ mixlib-log (>= 1.3.0)
16
+ mixlib-shellout
17
+ moneta (< 0.7.0)
18
+ net-ssh (~> 2.2.2)
19
+ net-ssh-multi (~> 1.1.0)
20
+ ohai (>= 0.6.0)
21
+ rest-client (>= 1.0.4, < 1.7.0)
22
+ treetop (~> 1.4.9)
23
+ uuidtools
24
+ yajl-ruby (~> 1.1)
25
+ childprocess (0.3.8)
26
+ ffi (~> 1.0, >= 1.0.11)
27
+ coderay (1.0.9)
28
+ erubis (2.7.0)
29
+ excon (0.18.3)
30
+ ffi (1.4.0)
31
+ fog (1.9.0)
32
+ builder
33
+ excon (~> 0.14)
34
+ formatador (~> 0.2.0)
35
+ mime-types
36
+ multi_json (~> 1.0)
37
+ net-scp (~> 1.0.4)
38
+ net-ssh (>= 2.1.3)
39
+ nokogiri (~> 1.5.0)
40
+ ruby-hmac
41
+ foodcritic (1.7.0)
42
+ erubis
43
+ gherkin (~> 2.11.1)
44
+ gist (~> 3.1.0)
45
+ nokogiri (~> 1.5.4)
46
+ pry (~> 0.9.8.4)
47
+ rak (~> 1.4)
48
+ treetop (~> 1.4.10)
49
+ yajl-ruby (~> 1.1.0)
50
+ formatador (0.2.4)
51
+ gherkin (2.11.5)
52
+ json (>= 1.4.6)
53
+ gist (3.1.1)
54
+ hashr (0.0.22)
55
+ highline (1.6.15)
56
+ i18n (0.6.1)
57
+ ipaddress (0.8.0)
58
+ json (1.5.5)
59
+ librarian (0.0.26)
60
+ archive-tar-minitar (>= 0.5.2)
61
+ chef (>= 0.10)
62
+ highline
63
+ thor (~> 0.15)
64
+ log4r (1.1.10)
65
+ method_source (0.7.1)
66
+ mime-types (1.21)
67
+ mixlib-authentication (1.3.0)
68
+ mixlib-log
69
+ mixlib-cli (1.2.2)
70
+ mixlib-config (1.1.2)
71
+ mixlib-log (1.4.1)
72
+ mixlib-shellout (1.1.0)
73
+ moneta (0.6.0)
74
+ multi_json (1.6.1)
75
+ net-scp (1.0.4)
76
+ net-ssh (>= 1.99.1)
77
+ net-ssh (2.2.2)
78
+ net-ssh-gateway (1.1.0)
79
+ net-ssh (>= 1.99.1)
80
+ net-ssh-multi (1.1)
81
+ net-ssh (>= 2.1.4)
82
+ net-ssh-gateway (>= 0.99.0)
83
+ nokogiri (1.5.6)
84
+ ohai (6.16.0)
85
+ ipaddress
86
+ mixlib-cli
87
+ mixlib-config
88
+ mixlib-log
89
+ mixlib-shellout
90
+ systemu
91
+ yajl-ruby
92
+ polyglot (0.3.3)
93
+ pry (0.9.8.4)
94
+ coderay (~> 1.0.5)
95
+ method_source (~> 0.7.1)
96
+ slop (>= 2.4.4, < 3)
97
+ rak (1.4)
98
+ rest-client (1.6.7)
99
+ mime-types (>= 1.16)
100
+ ruby-hmac (0.4.0)
101
+ slop (2.4.4)
102
+ systemu (2.5.2)
103
+ test-kitchen (0.7.0)
104
+ fog
105
+ foodcritic (>= 1.4.0)
106
+ hashr (~> 0.0.20)
107
+ highline (>= 1.6.9)
108
+ librarian (~> 0.0.20)
109
+ mixlib-cli (~> 1.2.2)
110
+ vagrant (~> 1.0.2)
111
+ yajl-ruby (~> 1.1.0)
112
+ thor (0.17.0)
113
+ treetop (1.4.12)
114
+ polyglot
115
+ polyglot (>= 0.3.1)
116
+ uuidtools (2.1.3)
117
+ vagrant (1.0.6)
118
+ archive-tar-minitar (= 0.5.2)
119
+ childprocess (~> 0.3.1)
120
+ erubis (~> 2.7.0)
121
+ i18n (~> 0.6.0)
122
+ json (~> 1.5.1)
123
+ log4r (~> 1.1.9)
124
+ net-scp (~> 1.0.4)
125
+ net-ssh (~> 2.2.2)
126
+ yajl-ruby (1.1.0)
127
+
128
+ PLATFORMS
129
+ ruby
130
+
131
+ DEPENDENCIES
132
+ test-kitchen
@@ -0,0 +1,83 @@
1
+ ## LXC
2
+
3
+ Manage linux containers with Chef.
4
+
5
+ ### Recipes
6
+
7
+ #### default
8
+
9
+ Installs the packages and configuration files needed for lxc on the server. If
10
+ the node uses apt-cacher-ng as a client, the server will be reused when building
11
+ containers.
12
+
13
+ #### install_dependencies
14
+
15
+ Installs the packages needed to support lxc's containers.
16
+
17
+ #### containers
18
+
19
+ This recipe creates all of the containers defined in the `['lxc']['containers']`
20
+ hash. Here is an example of an `example` container:
21
+
22
+ ```ruby
23
+ node['lxc']['containers']['example'] = {
24
+ 'template' => 'ubuntu',
25
+ 'trim' => , false,
26
+ 'debug' => , true
27
+ }
28
+ ```
29
+
30
+ You may set `trim` and `debug` to `true` if you need them (default is `false`).
31
+
32
+ Backing store file system and template options are not yet supported.
33
+
34
+ #### knife
35
+
36
+ Install and manage containers via the knife-remotelxc plugin.
37
+
38
+ ### Example
39
+
40
+ ```ruby
41
+ include_recipe 'lxc'
42
+
43
+ lxc_container 'my_container' do
44
+ action :create
45
+ validation_client 'my-validator'
46
+ server_uri 'https://api.opscode.com/organizations/myorg'
47
+ validator_pem content_from_encrypted_dbag
48
+ run_list ['role[base]']
49
+ chef_enabled true
50
+ end
51
+
52
+ lxc_container 'my_container_clone' do
53
+ action :clone
54
+ base_container 'my_container'
55
+ chef_enabled true
56
+ end
57
+
58
+ lxc_service 'my_container_clone' do
59
+ action :start
60
+ end
61
+ ```
62
+
63
+ Containers do not have to be Chef enabled but it does make them
64
+ extremely easy to configure. If you want the Omnibus installer
65
+ cached, you can set the attribute
66
+
67
+ ```ruby
68
+ node['omnibus_updater']['cache_omnibus_installer'] = true
69
+ ```
70
+
71
+ in a role or environment (default is false). The `lxc_container`
72
+ resource also provides `initialize_commands` which an array of
73
+ commands can be provided that will be run after the container is
74
+ created.
75
+
76
+ ### Repository:
77
+
78
+ * https://github.com/hw-cookbooks/lxc
79
+
80
+ ### Contributors
81
+
82
+ * Sean Porter (https://github.com/portertech)
83
+ * Matt Ray (https://github.com/mattray)
@@ -0,0 +1,26 @@
1
+ default[:lxc][:start_ipaddress] = nil
2
+ default[:lxc][:validator_pem] = nil
3
+ default[:lxc][:auto_start] = true
4
+ default[:lxc][:bridge] = 'lxcbr0'
5
+ default[:lxc][:use_bridge] = true
6
+ default[:lxc][:addr] = '10.0.3.1'
7
+ default[:lxc][:netmask] = '255.255.255.0'
8
+ default[:lxc][:network] = '10.0.3.0/24'
9
+ default[:lxc][:dhcp_range] = '10.0.3.2,10.0.3.254'
10
+ default[:lxc][:dhcp_max] = '253'
11
+ default[:lxc][:shutdown_timeout] = 120
12
+ default[:lxc][:allowed_types] = %w(debian ubuntu fedora)
13
+ default[:lxc][:container_directory] = '/var/lib/lxc'
14
+ default[:lxc][:dnsmasq_lease_file] = '/var/lib/misc/dnsmasq.leases'
15
+
16
+ default[:lxc][:knife] = {}
17
+ default[:lxc][:knife][:static_range] = ''
18
+ default[:lxc][:knife][:static_ips] = []
19
+
20
+ default[:lxc][:user_pass][:debian] = {:username => 'root', :password => 'root'}
21
+ default[:lxc][:user_pass][:ubuntu] = {:username => 'ubuntu', :password => 'ubuntu'}
22
+ default[:lxc][:user_pass][:fedora] = {:username => 'root', :password => 'root'}
23
+
24
+ default[:lxc][:packages] = %w(lxc)
25
+ default[:lxc][:mirror] = 'http://archive.ubuntu.com/ubuntu'
26
+ default[:lxc][:containers] = {}
@@ -0,0 +1,228 @@
1
+ #!/opt/chef/embedded/bin/ruby
2
+
3
+ require 'json'
4
+ require 'getoptlong'
5
+
6
+ LXC_HOME = '/var/lib/lxc'
7
+
8
+ opts = GetoptLong.new(
9
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
10
+ ['--version', '-v', GetoptLong::NO_ARGUMENT],
11
+ ['--template', '-t', GetoptLong::REQUIRED_ARGUMENT]
12
+ )
13
+
14
+ # Default
15
+ template = 'ubuntu'
16
+
17
+ opts.each do |opt, arg|
18
+ case opt
19
+ when '--help'
20
+ show_usage
21
+ exit 0
22
+ when '--version'
23
+ show_version
24
+ exit 0
25
+ when '--template'
26
+ template = arg
27
+ raise 'Unsupported template provided' unless %w(debian fedora ubuntu).include?(template)
28
+ end
29
+ end
30
+
31
+ def conf
32
+ @c ||= JSON.load(File.read('/etc/knife-lxc/config.json'))
33
+ end
34
+
35
+ def lxc_exists?(lxc_name)
36
+ current_names = Dir.glob(File.join(LXC_HOME, '*')).map{|c| File.basename(c)}
37
+ current_names.include?(lxc_name)
38
+ end
39
+
40
+ def ensure_name_availability!(name)
41
+ raise 'Name already in use' if lxc_exists?(name)
42
+ end
43
+
44
+ def available_ips
45
+ # TODO: Add range calculation
46
+ range = conf['address']['range']
47
+ if(range.to_s.empty?)
48
+ range = (range.split('-').first.split('.').last..range.split('-').last).map{|oct|
49
+ "#{range.split('-').first.split('.').slice(0,3).join('.')}.#{oct}"
50
+ }
51
+ else
52
+ range = []
53
+ end
54
+ (conf['addresses']['static'] + range).compact
55
+ end
56
+
57
+ def used_ips
58
+ Dir.glob(File.join(LXC_HOME, '*')).map{ |ctn|
59
+ File.readlines(File.join(ctn, 'rootfs', 'etc', 'network', 'interfaces')).detect{ |line|
60
+ line.include?('address')
61
+ }.to_s.split(' ').last.to_s.strip
62
+ }.reject{ |addr|
63
+ addr.empty?
64
+ }
65
+ end
66
+
67
+ def update_container_ip(name)
68
+ new_ip = (available_ips - used_ips).pop
69
+ raise 'no ips available' unless new_ip
70
+ update_network_interfaces(name, new_ip)
71
+ new_ip
72
+ end
73
+
74
+ def update_network_interfaces(name, address)
75
+ default_nm = '255.255.255.0'
76
+ (parts = address.split('.')).last.replace('1')
77
+ default_gw = parts.join('.')
78
+ File.open(File.join(LXC_HOME, name, 'rootfs', 'etc', 'network', 'interfaces'), 'w') do |file|
79
+ file.puts "auto lo eth0"
80
+ file.puts "iface lo inet loopback"
81
+ file.puts "iface eth0 inet static"
82
+ file.puts " address #{address}"
83
+ file.puts " gateway #{conf['gateway'] || default_gw}"
84
+ file.puts " netmask #{conf['netmask'] || default_nm}"
85
+ end
86
+ end
87
+
88
+ def sudoable_ubuntu(name)
89
+ path = File.join(LXC_HOME, name, 'rootfs', 'etc', 'sudoers')
90
+ content = File.readlines(path).map do |line|
91
+ if(line.start_with?('%sudo'))
92
+ '%sudo ALL=(ALL) NOPASSWD:ALL'
93
+ else
94
+ line
95
+ end
96
+ end
97
+ File.open(path, 'w') do |file|
98
+ file.write(content.join("\n"))
99
+ end
100
+ end
101
+
102
+ def clone_container(name, template)
103
+ %x{lxc-clone -o #{template}_base -n #{name}}
104
+ end
105
+
106
+ def start_container(name)
107
+ %x{lxc-start -n #{name} -d}
108
+ %x{lxc-wait -n #{name} -s RUNNING}
109
+ end
110
+
111
+ def stop_container(name)
112
+ %x{lxc-shutdown -n #{name} -d}
113
+ %x{lxc-wait -n #{name} -s STOPPED}
114
+ end
115
+
116
+ def create_lxc(lxc_name, template)
117
+ ensure_name_availability!(lxc_name)
118
+ clone_container(lxc_name, template)
119
+ address = update_container_ip(lxc_name)
120
+ # TODO: Update debian and fedora to use sudo user and remove root login
121
+ if(lxc_name == 'ubuntu')
122
+ sudoable_ubuntu(lxc_name)
123
+ end
124
+ start_container(lxc_name)
125
+ puts "LXC Node #{lxc_name} available at: #{address}"
126
+ end
127
+
128
+ def lxc_address(name)
129
+ if(File.exists?(int = File.join(LXC_HOME, name, 'rootfs', 'etc', 'network', 'interfaces')))
130
+ addr = File.readlines(int).detect{|l|l.include?('address')}.to_s.split(' ').last.to_s.strip
131
+ addr.empty? ? 'dynamic' : addr
132
+ else
133
+ 'dynamic'
134
+ end
135
+ end
136
+
137
+ def lxc_type(name)
138
+ base = File.join(LXC_HOME, name, 'rootfs', 'etc')
139
+ if(File.exists?(lsb = File.join(base, 'lsb-release')))
140
+ File.readlines(lsb).last.split('=').last.strip.gsub('"', '')
141
+ elsif(File.exists?(sys_rel = File.join(base, 'system-release')))
142
+ File.readlines(sys_rel).first.strip
143
+ elsif(File.exists?(deb_ver = File.join(base, 'debian_version')))
144
+ "Debain #{File.read(deb_ver).strip}"
145
+ else
146
+ 'UNKNOWN'
147
+ end
148
+ end
149
+
150
+ def list_lxcs
151
+ info = Hash[
152
+ *Dir.glob(File.join(LXC_HOME, '*')).map{|dir|
153
+ key = File.basename(dir)
154
+ [key, {:address => lxc_address(key), :type => lxc_type(key)}]
155
+ }.sort{|a,b|
156
+ a.first <=> b.first
157
+ }.flatten
158
+ ]
159
+ info.each do |name, info|
160
+ puts "#{name}"
161
+ puts " Type: #{info[:type]}"
162
+ puts " Address: #{info[:address]}"
163
+ end
164
+ end
165
+
166
+ def info_lxc(lxc_name)
167
+ puts "#{lxc_name}"
168
+ puts " Type: #{lxc_type(lxc_name)}"
169
+ puts " Address: #{lxc_address(lxc_name)}"
170
+ end
171
+
172
+ def delete_lxc(lxc_name)
173
+ %x{lxc-stop -n #{lxc_name}}
174
+ %x{lxc-wait -n #{lxc_name} -s STOPPED}
175
+ %x{lxc-destroy -n #{lxc_name}}
176
+ end
177
+
178
+ action = ARGV.first.to_s
179
+
180
+ case action
181
+ when 'create'
182
+ lxc_name = ARGV[1]
183
+ create_lxc(lxc_name, template)
184
+ when 'list'
185
+ list_lxcs
186
+ when 'info'
187
+ lxc_name = ARGV[1]
188
+ if(lxc_exists?(lxc_name))
189
+ info_lxc(lxc_name)
190
+ else
191
+ $stderr.puts "Requested container does not exist: #{lxc_name}"
192
+ exit 2
193
+ end
194
+ when 'start'
195
+ lxc_name = ARGV[1]
196
+ if(lxc_exists?(lxc_name))
197
+ print "Starting container #{lxc_name}... "
198
+ start_lxc(lxc_name)
199
+ puts 'started'
200
+ else
201
+ $stderr.puts "Requested container does not exist: #{lxc_name}"
202
+ exit 2
203
+ end
204
+ when 'stop'
205
+ lxc_name = ARGV[1]
206
+ if(lxc_exists?(lxc_name))
207
+ print "Stopping container #{lxc_name}... "
208
+ stop_lxc(lxc_name)
209
+ puts 'stopped'
210
+ else
211
+ $stderr.puts "Requested container does not exist: #{lxc_name}"
212
+ exit 2
213
+ end
214
+ when 'delete'
215
+ lxc_name = ARGV[1]
216
+ if(lxc_exists?(lxc_name))
217
+ print "Deleting LXC #{lxc_name}... "
218
+ destroy_lxc(lxc_name)
219
+ puts 'done'
220
+ else
221
+ $stderr.puts "Requested container does not exist: #{lxc_name}"
222
+ exit 2
223
+ end
224
+ else
225
+ $stderr.puts "ERROR: Unknown action: #{action}"
226
+ exit 1
227
+ end
228
+