chef 0.7.10

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of chef might be problematic. Click here for more details.

Files changed (120) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +135 -0
  3. data/bin/chef-client +26 -0
  4. data/bin/chef-solo +26 -0
  5. data/lib/chef.rb +49 -0
  6. data/lib/chef/application.rb +98 -0
  7. data/lib/chef/application/agent.rb +18 -0
  8. data/lib/chef/application/client.rb +209 -0
  9. data/lib/chef/application/indexer.rb +141 -0
  10. data/lib/chef/application/server.rb +18 -0
  11. data/lib/chef/application/solo.rb +214 -0
  12. data/lib/chef/client.rb +396 -0
  13. data/lib/chef/compile.rb +138 -0
  14. data/lib/chef/config.rb +141 -0
  15. data/lib/chef/cookbook.rb +144 -0
  16. data/lib/chef/cookbook/metadata.rb +407 -0
  17. data/lib/chef/cookbook/metadata/version.rb +87 -0
  18. data/lib/chef/cookbook_loader.rb +168 -0
  19. data/lib/chef/couchdb.rb +172 -0
  20. data/lib/chef/daemon.rb +170 -0
  21. data/lib/chef/exceptions.rb +36 -0
  22. data/lib/chef/file_cache.rb +205 -0
  23. data/lib/chef/log.rb +39 -0
  24. data/lib/chef/mixin/check_helper.rb +31 -0
  25. data/lib/chef/mixin/checksum.rb +37 -0
  26. data/lib/chef/mixin/command.rb +351 -0
  27. data/lib/chef/mixin/create_path.rb +56 -0
  28. data/lib/chef/mixin/deep_merge.rb +36 -0
  29. data/lib/chef/mixin/find_preferred_file.rb +99 -0
  30. data/lib/chef/mixin/from_file.rb +36 -0
  31. data/lib/chef/mixin/generate_url.rb +48 -0
  32. data/lib/chef/mixin/language.rb +79 -0
  33. data/lib/chef/mixin/params_validate.rb +197 -0
  34. data/lib/chef/mixin/template.rb +84 -0
  35. data/lib/chef/node.rb +406 -0
  36. data/lib/chef/node/attribute.rb +412 -0
  37. data/lib/chef/openid_registration.rb +181 -0
  38. data/lib/chef/platform.rb +253 -0
  39. data/lib/chef/provider.rb +40 -0
  40. data/lib/chef/provider/cron.rb +137 -0
  41. data/lib/chef/provider/directory.rb +72 -0
  42. data/lib/chef/provider/execute.rb +58 -0
  43. data/lib/chef/provider/file.rb +191 -0
  44. data/lib/chef/provider/group.rb +120 -0
  45. data/lib/chef/provider/group/groupadd.rb +92 -0
  46. data/lib/chef/provider/group/pw.rb +88 -0
  47. data/lib/chef/provider/http_request.rb +102 -0
  48. data/lib/chef/provider/ifconfig.rb +131 -0
  49. data/lib/chef/provider/link.rb +157 -0
  50. data/lib/chef/provider/mount.rb +121 -0
  51. data/lib/chef/provider/mount/mount.rb +208 -0
  52. data/lib/chef/provider/package.rb +160 -0
  53. data/lib/chef/provider/package/apt.rb +110 -0
  54. data/lib/chef/provider/package/dpkg.rb +113 -0
  55. data/lib/chef/provider/package/freebsd.rb +153 -0
  56. data/lib/chef/provider/package/macports.rb +105 -0
  57. data/lib/chef/provider/package/portage.rb +124 -0
  58. data/lib/chef/provider/package/rpm.rb +99 -0
  59. data/lib/chef/provider/package/rubygems.rb +130 -0
  60. data/lib/chef/provider/package/yum-dump.py +104 -0
  61. data/lib/chef/provider/package/yum.rb +175 -0
  62. data/lib/chef/provider/remote_directory.rb +126 -0
  63. data/lib/chef/provider/remote_file.rb +134 -0
  64. data/lib/chef/provider/route.rb +118 -0
  65. data/lib/chef/provider/ruby_block.rb +15 -0
  66. data/lib/chef/provider/script.rb +42 -0
  67. data/lib/chef/provider/service.rb +129 -0
  68. data/lib/chef/provider/service/debian.rb +64 -0
  69. data/lib/chef/provider/service/freebsd.rb +157 -0
  70. data/lib/chef/provider/service/gentoo.rb +54 -0
  71. data/lib/chef/provider/service/init.rb +126 -0
  72. data/lib/chef/provider/service/redhat.rb +62 -0
  73. data/lib/chef/provider/template.rb +141 -0
  74. data/lib/chef/provider/user.rb +170 -0
  75. data/lib/chef/provider/user/pw.rb +113 -0
  76. data/lib/chef/provider/user/useradd.rb +107 -0
  77. data/lib/chef/queue.rb +145 -0
  78. data/lib/chef/recipe.rb +210 -0
  79. data/lib/chef/resource.rb +256 -0
  80. data/lib/chef/resource/apt_package.rb +34 -0
  81. data/lib/chef/resource/bash.rb +33 -0
  82. data/lib/chef/resource/cron.rb +143 -0
  83. data/lib/chef/resource/csh.rb +33 -0
  84. data/lib/chef/resource/directory.rb +76 -0
  85. data/lib/chef/resource/dpkg_package.rb +34 -0
  86. data/lib/chef/resource/execute.rb +127 -0
  87. data/lib/chef/resource/file.rb +84 -0
  88. data/lib/chef/resource/gem_package.rb +41 -0
  89. data/lib/chef/resource/group.rb +68 -0
  90. data/lib/chef/resource/http_request.rb +52 -0
  91. data/lib/chef/resource/ifconfig.rb +134 -0
  92. data/lib/chef/resource/link.rb +78 -0
  93. data/lib/chef/resource/macports_package.rb +29 -0
  94. data/lib/chef/resource/mount.rb +135 -0
  95. data/lib/chef/resource/package.rb +80 -0
  96. data/lib/chef/resource/perl.rb +33 -0
  97. data/lib/chef/resource/portage_package.rb +33 -0
  98. data/lib/chef/resource/python.rb +33 -0
  99. data/lib/chef/resource/remote_directory.rb +91 -0
  100. data/lib/chef/resource/remote_file.rb +60 -0
  101. data/lib/chef/resource/route.rb +135 -0
  102. data/lib/chef/resource/ruby.rb +33 -0
  103. data/lib/chef/resource/ruby_block.rb +20 -0
  104. data/lib/chef/resource/script.rb +51 -0
  105. data/lib/chef/resource/service.rb +134 -0
  106. data/lib/chef/resource/template.rb +60 -0
  107. data/lib/chef/resource/user.rb +98 -0
  108. data/lib/chef/resource_collection.rb +176 -0
  109. data/lib/chef/resource_definition.rb +67 -0
  110. data/lib/chef/rest.rb +238 -0
  111. data/lib/chef/role.rb +231 -0
  112. data/lib/chef/run_list.rb +156 -0
  113. data/lib/chef/runner.rb +123 -0
  114. data/lib/chef/search.rb +88 -0
  115. data/lib/chef/search/result.rb +64 -0
  116. data/lib/chef/search_index.rb +77 -0
  117. data/lib/chef/tasks/chef_repo.rake +345 -0
  118. data/lib/chef/util/file_edit.rb +125 -0
  119. data/lib/chef/util/fileedit.rb +121 -0
  120. metadata +262 -0
@@ -0,0 +1,110 @@
1
+ #
2
+ # Author:: Adam Jacob (<adam@opscode.com>)
3
+ # Copyright:: Copyright (c) 2008 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/provider/package'
20
+ require 'chef/mixin/command'
21
+ require 'chef/resource/package'
22
+
23
+ class Chef
24
+ class Provider
25
+ class Package
26
+ class Apt < Chef::Provider::Package
27
+
28
+ def load_current_resource
29
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
30
+ @current_resource.package_name(@new_resource.package_name)
31
+
32
+ Chef::Log.debug("Checking apt-cache policy for #{@new_resource.package_name}")
33
+ status = popen4("apt-cache policy #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
34
+ stdout.each do |line|
35
+ case line
36
+ when /^\s{2}Installed: (.+)$/
37
+ installed_version = $1
38
+ if installed_version == '(none)'
39
+ Chef::Log.debug("Current version is nil")
40
+ @current_resource.version(nil)
41
+ else
42
+ Chef::Log.debug("Current version is #{installed_version}")
43
+ @current_resource.version(installed_version)
44
+ end
45
+ when /^\s{2}Candidate: (.+)$/
46
+ Chef::Log.debug("Current version is #{$1}")
47
+ @candidate_version = $1
48
+ end
49
+ end
50
+ end
51
+
52
+ unless status.exitstatus == 0
53
+ raise Chef::Exceptions::Package, "apt-cache failed - #{status.inspect}!"
54
+ end
55
+
56
+ if @candidate_version == "(none)"
57
+ raise Chef::Exceptions::Package, "apt does not have a version of package #{@new_resource.package_name}"
58
+ end
59
+
60
+ @current_resource
61
+ end
62
+
63
+ def install_package(name, version)
64
+ run_command(
65
+ :command => "apt-get -q -y#{expand_options(@new_resource.options)} install #{name}=#{version}",
66
+ :environment => {
67
+ "DEBIAN_FRONTEND" => "noninteractive"
68
+ }
69
+ )
70
+ end
71
+
72
+ def upgrade_package(name, version)
73
+ install_package(name, version)
74
+ end
75
+
76
+ def remove_package(name, version)
77
+ run_command(
78
+ :command => "apt-get -q -y#{expand_options(@new_resource.options)} remove #{@new_resource.package_name}",
79
+ :environment => {
80
+ "DEBIAN_FRONTEND" => "noninteractive"
81
+ }
82
+ )
83
+ end
84
+
85
+ def purge_package(name, version)
86
+ run_command(
87
+ :command => "apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}",
88
+ :environment => {
89
+ "DEBIAN_FRONTEND" => "noninteractive"
90
+ }
91
+ )
92
+ end
93
+
94
+ def preseed_package(name, version)
95
+ preseed_file = get_preseed_file(name, version)
96
+ if preseed_file
97
+ Chef::Log.info("Pre-seeding #{@new_resource} with package installation instructions.")
98
+ run_command(
99
+ :command => "debconf-set-selections #{preseed_file}",
100
+ :environment => {
101
+ "DEBIAN_FRONTEND" => "noninteractive"
102
+ }
103
+ )
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,113 @@
1
+ #
2
+ # Author:: Bryan McLellan (btm@loftninjas.org)
3
+ # Copyright:: Copyright (c) 2009 Bryan McLellan
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/provider/package'
20
+ require 'chef/mixin/command'
21
+ require 'chef/resource/package'
22
+
23
+ class Chef
24
+ class Provider
25
+ class Package
26
+ class Dpkg < Chef::Provider::Package::Apt
27
+
28
+ def load_current_resource
29
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
30
+ @current_resource.package_name(@new_resource.package_name)
31
+ @new_resource.version(nil)
32
+
33
+ # We only -need- source for action install
34
+ if @new_resource.source
35
+ unless ::File.exists?(@new_resource.source)
36
+ raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
37
+ end
38
+
39
+ # Get information from the package if supplied
40
+ Chef::Log.debug("Checking dpkg status for #{@new_resource.package_name}")
41
+ status = popen4("dpkg-deb -W #{@new_resource.source}") do |pid, stdin, stdout, stderr|
42
+ stdout.each do |line|
43
+ case line
44
+ when /([\w\d]+)\t([\w\d.-]+)/
45
+ @current_resource.package_name($1)
46
+ @new_resource.version($2)
47
+ end
48
+ end
49
+ end
50
+ else
51
+ # if the source was not set, and we're installing, fail
52
+ if @new_resource.action.include?(:install)
53
+ raise Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
54
+ end
55
+ end
56
+
57
+ # Check to see if it is installed
58
+ package_installed = nil
59
+ Chef::Log.debug("Checking install state for #{@current_resource.package_name}")
60
+ status = popen4("dpkg -s #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
61
+ stdout.each do |line|
62
+ case line
63
+ when /^Status: install ok installed/
64
+ package_installed = true
65
+ when /^Version: (.+)$/
66
+ if package_installed
67
+ Chef::Log.debug("Current version is #{$1}")
68
+ @current_resource.version($1)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ unless status.exitstatus == 0 || status.exitstatus == 1
75
+ raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!"
76
+ end
77
+
78
+ @current_resource
79
+ end
80
+
81
+ def install_package(name, version)
82
+ run_command(
83
+ :command => "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}",
84
+ :environment => {
85
+ "DEBIAN_FRONTEND" => "noninteractive",
86
+ "LANG" => "en_US"
87
+ }
88
+ )
89
+ end
90
+
91
+ def remove_package(name, version)
92
+ run_command(
93
+ :command => "dpkg -r#{expand_options(@new_resource.options)} #{@new_resource.package_name}",
94
+ :environment => {
95
+ "DEBIAN_FRONTEND" => "noninteractive",
96
+ "LANG" => "en_US"
97
+ }
98
+ )
99
+ end
100
+
101
+ def purge_package(name, version)
102
+ run_command(
103
+ :command => "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}",
104
+ :environment => {
105
+ "DEBIAN_FRONTEND" => "noninteractive",
106
+ "LANG" => "en_US"
107
+ }
108
+ )
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,153 @@
1
+ #
2
+ # Authors:: Bryan McLellan (btm@loftninjas.org)
3
+ # Matthew Landauer (matthew@openaustralia.org)
4
+ # Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'chef/provider/package'
21
+ require 'chef/mixin/command'
22
+ require 'chef/resource/package'
23
+
24
+ class Chef
25
+ class Provider
26
+ class Package
27
+ class Freebsd < Chef::Provider::Package
28
+
29
+ def current_installed_version
30
+ command = "pkg_info -E \"#{package_name}*\""
31
+ status = popen4(command) do |pid, stdin, stdout, stderr|
32
+ stdout.each do |line|
33
+ case line
34
+ when /^#{package_name}-(.+)/
35
+ return $1
36
+ end
37
+ end
38
+ end
39
+ unless status.exitstatus == 0 || status.exitstatus == 1
40
+ raise Chef::Exceptions::Package, "#{command} failed - #{status.inspect}!"
41
+ end
42
+ nil
43
+ end
44
+
45
+ def port_path
46
+ case @new_resource.package_name
47
+ # When the package name starts with a '/' treat it as the full path to the ports directory
48
+ when /^\//
49
+ @new_resource.package_name
50
+ # Otherwise if the package name contains a '/' not at the start (like 'www/wordpress') treat as a relative
51
+ # path from /usr/ports
52
+ when /\//
53
+ "/usr/ports/#{@new_resource.package_name}"
54
+ # Otherwise look up the path to the ports directory using 'whereis'
55
+ else
56
+ popen4("whereis -s #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
57
+ stdout.each do |line|
58
+ case line
59
+ when /^#{@new_resource.package_name}:\s+(.+)$/
60
+ return $1
61
+ end
62
+ end
63
+ end
64
+ raise Chef::Exception::Package, "Could not find port with the name #{@new_resource.package_name}"
65
+ end
66
+ end
67
+
68
+ def ports_makefile_variable_value(variable)
69
+ command = "cd #{port_path}; make -V #{variable}"
70
+ status = popen4(command) do |pid, stdin, stdout, stderr|
71
+ return stdout.readline.strip
72
+ end
73
+ unless status.exitstatus == 0 || status.exitstatus == 1
74
+ raise Chef::Exceptions::Package, "#{command} failed - #{status.inspect}!"
75
+ end
76
+ nil
77
+ end
78
+
79
+ def ports_candidate_version
80
+ ports_makefile_variable_value("PORTVERSION")
81
+ end
82
+
83
+ def load_current_resource
84
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
85
+ @current_resource.package_name(@new_resource.package_name)
86
+
87
+ @current_resource.version(current_installed_version)
88
+ Chef::Log.debug("Current version is #{@current_resource.version}") if @current_resource.version
89
+
90
+ @candidate_version = ports_candidate_version
91
+ Chef::Log.debug("Ports candidate version is #{@candidate_version}") if @candidate_version
92
+
93
+ @current_resource
94
+ end
95
+
96
+ def latest_link_name
97
+ ports_makefile_variable_value("LATEST_LINK")
98
+ end
99
+
100
+ # The name of the package (without the version number) as understood by pkg_add and pkg_info
101
+ def package_name
102
+ if ports_makefile_variable_value("PKGNAME") =~ /^(.+)-[^-]+$/
103
+ $1
104
+ else
105
+ raise Chef::Exception::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile"
106
+ end
107
+ end
108
+
109
+ def install_package(name, version)
110
+ unless @current_resource.version
111
+ case @new_resource.source
112
+ when /^ports$/
113
+ run_command(
114
+ :command => "make -DBATCH install",
115
+ :cwd => "#{port_path}"
116
+ )
117
+ when /^http/, /^ftp/
118
+ run_command(
119
+ :command => "pkg_add -r #{package_name}",
120
+ :environment => { "PACKAGESITE" => @new_resource.source }
121
+ )
122
+ Chef::Log.info("Installed package #{package_name} from: #{@new_resource.source}")
123
+ when /^\//
124
+ run_command(
125
+ :command => "pkg_add #{@new_resource.name}",
126
+ :environment => { "PKG_PATH" => @new_resource.source }
127
+ )
128
+ Chef::Log.info("Installed package #{@new_resource.name} from: #{@new_resource.source}")
129
+ else
130
+ run_command(
131
+ :command => "pkg_add -r #{latest_link_name}"
132
+ )
133
+ Chef::Log.info("Installed package #{package_name}")
134
+ end
135
+ end
136
+ end
137
+
138
+ def remove_package(name, version)
139
+ # a version is mandatory
140
+ if version
141
+ run_command(
142
+ :command => "pkg_delete #{package_name}-#{version}"
143
+ )
144
+ else
145
+ run_command(
146
+ :command => "pkg_delete #{package_name}-#{@current_resource.version}"
147
+ )
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,105 @@
1
+ class Chef
2
+ class Provider
3
+ class Package
4
+ class Macports < Chef::Provider::Package
5
+ def load_current_resource
6
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
7
+ @current_resource.package_name(@new_resource.package_name)
8
+
9
+ @current_resource.version(current_installed_version)
10
+ Chef::Log.debug("Current version is #{@current_resource.version}") if @current_resource.version
11
+
12
+ @candidate_version = macports_candidate_version
13
+
14
+ if !@new_resource.version and !@candidate_version
15
+ raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{@new_resource.name} does not seem to be a valid package!"
16
+ end
17
+
18
+ Chef::Log.debug("MacPorts candidate version is #{@candidate_version}") if @candidate_version
19
+
20
+ @current_resource
21
+ end
22
+
23
+ def current_installed_version
24
+ command = "port installed #{@new_resource.package_name}"
25
+ output = get_response_from_command(command)
26
+
27
+ response = nil
28
+ output.each_line do |line|
29
+ match = line.match(/^.+ @([^\s]+) \(active\)$/)
30
+ response = match[1] if match
31
+ end
32
+ response
33
+ end
34
+
35
+ def macports_candidate_version
36
+ command = "port info --version #{@new_resource.package_name}"
37
+ output = get_response_from_command(command)
38
+
39
+ match = output.match(/^version: (.+)$/)
40
+
41
+ match ? match[1] : nil
42
+ end
43
+
44
+ def install_package(name, version)
45
+ unless @current_resource.version == version
46
+ command = "port install #{name}"
47
+ command << " @#{version}" if version and !version.empty?
48
+ run_command(
49
+ :command => command
50
+ )
51
+ end
52
+ end
53
+
54
+ def purge_package(name, version)
55
+ command = "port uninstall #{name}"
56
+ command << " @#{version}" if version and !version.empty?
57
+ run_command(
58
+ :command => command
59
+ )
60
+ end
61
+
62
+ def remove_package(name, version)
63
+ command = "port deactivate #{name}"
64
+ command << " @#{version}" if version and !version.empty?
65
+
66
+ run_command(
67
+ :command => command
68
+ )
69
+ end
70
+
71
+ def upgrade_package(name, version)
72
+ # Saving this to a variable -- weird rSpec behavior
73
+ # happens otherwise...
74
+ current_version = @current_resource.version
75
+
76
+ if current_version.nil? or current_version.empty?
77
+ # Macports doesn't like when you upgrade a package
78
+ # that hasn't been installed.
79
+ install_package(name, version)
80
+ elsif current_version != version
81
+ run_command(
82
+ :command => "port upgrade #{name} @#{version}"
83
+ )
84
+ end
85
+ end
86
+
87
+ private
88
+ def get_response_from_command(command)
89
+ output = nil
90
+ status = popen4(command) do |pid, stdin, stdout, stderr|
91
+ begin
92
+ output = stdout.read
93
+ rescue Exception
94
+ raise Chef::Exceptions::Package, "Could not read from STDOUT on command: #{command}"
95
+ end
96
+ end
97
+ unless status.exitstatus == 0 || status.exitstatus == 1
98
+ raise Chef::Exceptions::Package, "#{command} failed - #{status.insect}!"
99
+ end
100
+ output
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end