environments-list-builder 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,203 @@
1
+ Copyright (c) 2014 Christo De Lange
2
+
3
+ Apache License
4
+ Version 2.0, January 2004
5
+ http://www.apache.org/licenses/
6
+
7
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
+
9
+ 1. Definitions.
10
+
11
+ "License" shall mean the terms and conditions for use, reproduction,
12
+ and distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ "Licensor" shall mean the copyright owner or entity authorized by
15
+ the copyright owner that is granting the License.
16
+
17
+ "Legal Entity" shall mean the union of the acting entity and all
18
+ other entities that control, are controlled by, or are under common
19
+ control with that entity. For the purposes of this definition,
20
+ "control" means (i) the power, direct or indirect, to cause the
21
+ direction or management of such entity, whether by contract or
22
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
23
+ outstanding shares, or (iii) beneficial ownership of such entity.
24
+
25
+ "You" (or "Your") shall mean an individual or Legal Entity
26
+ exercising permissions granted by this License.
27
+
28
+ "Source" form shall mean the preferred form for making modifications,
29
+ including but not limited to software source code, documentation
30
+ source, and configuration files.
31
+
32
+ "Object" form shall mean any form resulting from mechanical
33
+ transformation or translation of a Source form, including but
34
+ not limited to compiled object code, generated documentation,
35
+ and conversions to other media types.
36
+
37
+ "Work" shall mean the work of authorship, whether in Source or
38
+ Object form, made available under the License, as indicated by a
39
+ copyright notice that is included in or attached to the work
40
+ (an example is provided in the Appendix below).
41
+
42
+ "Derivative Works" shall mean any work, whether in Source or Object
43
+ form, that is based on (or derived from) the Work and for which the
44
+ editorial revisions, annotations, elaborations, or other modifications
45
+ represent, as a whole, an original work of authorship. For the purposes
46
+ of this License, Derivative Works shall not include works that remain
47
+ separable from, or merely link (or bind by name) to the interfaces of,
48
+ the Work and Derivative Works thereof.
49
+
50
+ "Contribution" shall mean any work of authorship, including
51
+ the original version of the Work and any modifications or additions
52
+ to that Work or Derivative Works thereof, that is intentionally
53
+ submitted to Licensor for inclusion in the Work by the copyright owner
54
+ or by an individual or Legal Entity authorized to submit on behalf of
55
+ the copyright owner. For the purposes of this definition, "submitted"
56
+ means any form of electronic, verbal, or written communication sent
57
+ to the Licensor or its representatives, including but not limited to
58
+ communication on electronic mailing lists, source code control systems,
59
+ and issue tracking systems that are managed by, or on behalf of, the
60
+ Licensor for the purpose of discussing and improving the Work, but
61
+ excluding communication that is conspicuously marked or otherwise
62
+ designated in writing by the copyright owner as "Not a Contribution."
63
+
64
+ "Contributor" shall mean Licensor and any individual or Legal Entity
65
+ on behalf of whom a Contribution has been received by Licensor and
66
+ subsequently incorporated within the Work.
67
+
68
+ 2. Grant of Copyright License. Subject to the terms and conditions of
69
+ this License, each Contributor hereby grants to You a perpetual,
70
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71
+ copyright license to reproduce, prepare Derivative Works of,
72
+ publicly display, publicly perform, sublicense, and distribute the
73
+ Work and such Derivative Works in Source or Object form.
74
+
75
+ 3. Grant of Patent License. Subject to the terms and conditions of
76
+ this License, each Contributor hereby grants to You a perpetual,
77
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78
+ (except as stated in this section) patent license to make, have made,
79
+ use, offer to sell, sell, import, and otherwise transfer the Work,
80
+ where such license applies only to those patent claims licensable
81
+ by such Contributor that are necessarily infringed by their
82
+ Contribution(s) alone or by combination of their Contribution(s)
83
+ with the Work to which such Contribution(s) was submitted. If You
84
+ institute patent litigation against any entity (including a
85
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
86
+ or a Contribution incorporated within the Work constitutes direct
87
+ or contributory patent infringement, then any patent licenses
88
+ granted to You under this License for that Work shall terminate
89
+ as of the date such litigation is filed.
90
+
91
+ 4. Redistribution. You may reproduce and distribute copies of the
92
+ Work or Derivative Works thereof in any medium, with or without
93
+ modifications, and in Source or Object form, provided that You
94
+ meet the following conditions:
95
+
96
+ (a) You must give any other recipients of the Work or
97
+ Derivative Works a copy of this License; and
98
+
99
+ (b) You must cause any modified files to carry prominent notices
100
+ stating that You changed the files; and
101
+
102
+ (c) You must retain, in the Source form of any Derivative Works
103
+ that You distribute, all copyright, patent, trademark, and
104
+ attribution notices from the Source form of the Work,
105
+ excluding those notices that do not pertain to any part of
106
+ the Derivative Works; and
107
+
108
+ (d) If the Work includes a "NOTICE" text file as part of its
109
+ distribution, then any Derivative Works that You distribute must
110
+ include a readable copy of the attribution notices contained
111
+ within such NOTICE file, excluding those notices that do not
112
+ pertain to any part of the Derivative Works, in at least one
113
+ of the following places: within a NOTICE text file distributed
114
+ as part of the Derivative Works; within the Source form or
115
+ documentation, if provided along with the Derivative Works; or,
116
+ within a display generated by the Derivative Works, if and
117
+ wherever such third-party notices normally appear. The contents
118
+ of the NOTICE file are for informational purposes only and
119
+ do not modify the License. You may add Your own attribution
120
+ notices within Derivative Works that You distribute, alongside
121
+ or as an addendum to the NOTICE text from the Work, provided
122
+ that such additional attribution notices cannot be construed
123
+ as modifying the License.
124
+
125
+ You may add Your own copyright statement to Your modifications and
126
+ may provide additional or different license terms and conditions
127
+ for use, reproduction, or distribution of Your modifications, or
128
+ for any such Derivative Works as a whole, provided Your use,
129
+ reproduction, and distribution of the Work otherwise complies with
130
+ the conditions stated in this License.
131
+
132
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
133
+ any Contribution intentionally submitted for inclusion in the Work
134
+ by You to the Licensor shall be under the terms and conditions of
135
+ this License, without any additional terms or conditions.
136
+ Notwithstanding the above, nothing herein shall supersede or modify
137
+ the terms of any separate license agreement you may have executed
138
+ with Licensor regarding such Contributions.
139
+
140
+ 6. Trademarks. This License does not grant permission to use the trade
141
+ names, trademarks, service marks, or product names of the Licensor,
142
+ except as required for reasonable and customary use in describing the
143
+ origin of the Work and reproducing the content of the NOTICE file.
144
+
145
+ 7. Disclaimer of Warranty. Unless required by applicable law or
146
+ agreed to in writing, Licensor provides the Work (and each
147
+ Contributor provides its Contributions) on an "AS IS" BASIS,
148
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149
+ implied, including, without limitation, any warranties or conditions
150
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151
+ PARTICULAR PURPOSE. You are solely responsible for determining the
152
+ appropriateness of using or redistributing the Work and assume any
153
+ risks associated with Your exercise of permissions under this License.
154
+
155
+ 8. Limitation of Liability. In no event and under no legal theory,
156
+ whether in tort (including negligence), contract, or otherwise,
157
+ unless required by applicable law (such as deliberate and grossly
158
+ negligent acts) or agreed to in writing, shall any Contributor be
159
+ liable to You for damages, including any direct, indirect, special,
160
+ incidental, or consequential damages of any character arising as a
161
+ result of this License or out of the use or inability to use the
162
+ Work (including but not limited to damages for loss of goodwill,
163
+ work stoppage, computer failure or malfunction, or any and all
164
+ other commercial damages or losses), even if such Contributor
165
+ has been advised of the possibility of such damages.
166
+
167
+ 9. Accepting Warranty or Additional Liability. While redistributing
168
+ the Work or Derivative Works thereof, You may choose to offer,
169
+ and charge a fee for, acceptance of support, warranty, indemnity,
170
+ or other liability obligations and/or rights consistent with this
171
+ License. However, in accepting such obligations, You may act only
172
+ on Your own behalf and on Your sole responsibility, not on behalf
173
+ of any other Contributor, and only if You agree to indemnify,
174
+ defend, and hold each Contributor harmless for any liability
175
+ incurred by, or claims asserted against, such Contributor by reason
176
+ of your accepting any such warranty or additional liability.
177
+
178
+ END OF TERMS AND CONDITIONS
179
+
180
+ APPENDIX: How to apply the Apache License to your work.
181
+
182
+ To apply the Apache License to your work, attach the following
183
+ boilerplate notice, with the fields enclosed by brackets "{}"
184
+ replaced with your own identifying information. (Don't include
185
+ the brackets!) The text should be enclosed in the appropriate
186
+ comment syntax for the file format. We also recommend that a
187
+ file or class name and description of purpose be included on the
188
+ same "printed page" as the copyright notice for easier
189
+ identification within third-party archives.
190
+
191
+ Copyright {yyyy} {name of copyright owner}
192
+
193
+ Licensed under the Apache License, Version 2.0 (the "License");
194
+ you may not use this file except in compliance with the License.
195
+ You may obtain a copy of the License at
196
+
197
+ http://www.apache.org/licenses/LICENSE-2.0
198
+
199
+ Unless required by applicable law or agreed to in writing, software
200
+ distributed under the License is distributed on an "AS IS" BASIS,
201
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202
+ See the License for the specific language governing permissions and
203
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ ore-apachev2
2
+ ============
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler'
7
+ rescue LoadError => e
8
+ warn e.message
9
+ warn "Run `gem install bundler` to install Bundler."
10
+ exit -1
11
+ end
12
+
13
+ begin
14
+ Bundler.setup(:development)
15
+ rescue Bundler::BundlerError => e
16
+ warn e.message
17
+ warn "Run `bundle install` to install missing gems."
18
+ exit e.status_code
19
+ end
20
+
21
+ require 'rake'
22
+
23
+ require 'rubygems/tasks'
24
+ Gem::Tasks.new
25
+
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new
28
+
29
+ task :test => :spec
30
+ task :default => :spec
31
+
32
+ require 'cucumber/rake/task'
33
+
34
+ Cucumber::Rake::Task.new do |t|
35
+ t.cucumber_opts = %w[--format pretty]
36
+ end
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ path = File.dirname(__FILE__)
6
+ if File.exists?("#{path}/../.gitignore")
7
+ %w(chefrepo-builder cicd-builder).each do |mod|
8
+ add_path = File.expand_path(File.join(path, "../../#{mod}", "lib"))
9
+ $:.unshift(add_path)
10
+ end
11
+ else
12
+ # Borrowing from "whiches" gem ...
13
+ cmd = File.basename(__FILE__, '.rb')
14
+ exes = []
15
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
16
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |pth|
17
+ exts.each { |ext|
18
+ exe = File.join(pth, "#{cmd}#{ext}")
19
+ exes << exe if File.executable? exe
20
+ }
21
+ end
22
+ if exes.size > 0
23
+ path = File.dirname(exes[0])
24
+ end
25
+
26
+ end
27
+ add_path = File.expand_path(File.join(path, "..", "lib"))
28
+ $:.unshift(add_path)
29
+
30
+ require 'dldinternet/mixlib/logging'
31
+ require 'cicd/builder/environments-list'
32
+
33
+ # =====================================================================================================================
34
+ exit CiCd::Builder::EnvironmentsList::Runner.new().run()
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/cicd/builder/environments-list/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'environments-list-builder'
7
+ gem.version = CiCd::Builder::EnvironmentsList::VERSION
8
+ gem.summary = %q{Jenkins builder task for CI/CD}
9
+ gem.description = %q{Jenkins builder of the environments manifest for Continuous Integration/Continuous Delivery artifact promotion style deployments}
10
+ gem.license = "Apachev2"
11
+ gem.authors = ["Christo De Lange"]
12
+ gem.email = "rubygems@dldinternet.com"
13
+ gem.homepage = "https://rubygems.org/gems/environments-list-builder"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency 'manifest-builder', '>= 0.6.9', '< 1.1'
21
+ gem.add_dependency 'json', '>= 1.8.1', '< 1.9'
22
+ gem.add_dependency 's3etag', '>= 0.0.1', '< 0.1.0'
23
+ gem.add_dependency 'archive-tar-minitar', '= 0.5.2'
24
+ gem.add_dependency 'hashie', '>= 2.1.2', '< 3.5'
25
+ gem.add_dependency 'awesome_print'
26
+ gem.add_dependency 'colorize'
27
+ gem.add_dependency 'inifile'
28
+ gem.add_dependency 'thor'
29
+ gem.add_dependency 'aws-sdk', '>= 2.0', '< 2.1'
30
+ gem.add_dependency 'dldinternet-mixlib-logging'
31
+
32
+ gem.add_development_dependency 'bundler', '>= 1.7', '< 2.0'
33
+ gem.add_development_dependency 'rake', '>= 10.3', '< 11'
34
+ gem.add_development_dependency 'rubygems-tasks', '>= 0.2', '< 1.1'
35
+ gem.add_development_dependency 'cucumber', '>= 0.10.7', '< 0.11'
36
+ gem.add_development_dependency 'rspec', '>= 2.99', '< 3.0'
37
+ end
data/features/.gitkeep ADDED
File without changes
@@ -0,0 +1 @@
1
+ Feature: Blah blah blah
File without changes
@@ -0,0 +1,50 @@
1
+ require 'cicd/builder/manifest'
2
+
3
+ module CiCd
4
+ module Builder
5
+ _lib=File.dirname(__FILE__)
6
+ $:.unshift(_lib) unless $:.include?(_lib)
7
+
8
+ require 'cicd/builder/environments-list/version'
9
+
10
+ module EnvironmentsList
11
+ class Runner < Manifest::Runner
12
+ require 'cicd/builder/environments-list/mixlib/build'
13
+ include CiCd::Builder::EnvironmentsList::Build
14
+ require 'cicd/builder/environments-list/mixlib/repo'
15
+ include CiCd::Builder::EnvironmentsList::Repo
16
+
17
+ # ---------------------------------------------------------------------------------------------------------------
18
+ def initialize()
19
+ super
20
+ @klass = 'CiCd::Builder::EnvironmentsList'
21
+ @default_options[:builder] = VERSION
22
+ end
23
+
24
+ # ---------------------------------------------------------------------------------------------------------------
25
+ def getBuilderVersion
26
+ {
27
+ version: VERSION,
28
+ major: MAJOR,
29
+ minor: MINOR,
30
+ patch: PATCH,
31
+ }
32
+ end
33
+
34
+ # ---------------------------------------------------------------------------------------------------------------
35
+ def setup()
36
+ $stdout.write("EnvironmentsListBuilder v#{CiCd::Builder::EnvironmentsList::VERSION}\n")
37
+ @default_options[:env_keys] << %w(
38
+ REPO_PRODUCTS
39
+ ARTIFACTORY_RELEASE_REPO
40
+ ARTIFACTORY_ENVIRONMENTS_MODULE
41
+ )
42
+ @default_options[:env_keys] = @default_options[:env_keys].select{|key| key !~ /^CLASSES/}
43
+ super
44
+ end
45
+
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,163 @@
1
+ module CiCd
2
+ module Builder
3
+ # noinspection RubySuperCallWithoutSuperclassInspection
4
+ module EnvironmentsList
5
+ CLASS = 'CiCd::Builder::EnvironmentsList'
6
+ module Build
7
+
8
+ alias_method :super_prepareBuild, :prepareBuild
9
+ # ---------------------------------------------------------------------------------------------------------------
10
+ # noinspection RubyHashKeysTypesInspection
11
+ def prepareBuild()
12
+ @logger.step CLASS+'::'+__method__.to_s
13
+ # self.class.superclass.instance_method(:prepareBuild).bind(self).call()
14
+ super_prepareBuild
15
+ if 0 == @vars[:return_code]
16
+ @vars[:components] = {}
17
+ @vars[:artifacts] = []
18
+ # Now we need to pull the current environments list
19
+ # Get the repo a little earlier than the typical build which uses it as a write target iso a read source
20
+ getRepoInstance('Artifactory')
21
+ if 0 == @vars[:return_code]
22
+ getLatestEnvironments()
23
+ end
24
+ end
25
+
26
+ @vars[:return_code]
27
+ end
28
+
29
+ # ---------------------------------------------------------------------------------------------------------------
30
+ def getLatestEnvironments(oldver=nil)
31
+ version = @repo.latestArtifactoryVersion(ENV['ARTIFACTORY_ENVIRONMENTS_MODULE'], ENV['ARTIFACTORY_RELEASE_REPO'])
32
+ if version
33
+ if version != oldver
34
+ objects = @repo.maybeArtifactoryObject(ENV['ARTIFACTORY_ENVIRONMENTS_MODULE'], version, false, ENV['ARTIFACTORY_RELEASE_REPO'])
35
+ if objects and objects.size > 0
36
+ if objects.size > 1
37
+ @logger.error "Too many versions found for #{ENV['ARTIFACTORY_RELEASE_REPO']}/#{ENV['ARTIFACTORY_ENVIRONMENTS_MODULE']}-#{version} during preparation?"
38
+ @vars[:return_code] = Errors::ARTIFACT_MULTI_MATCH
39
+ else
40
+ require 'hashie'
41
+ filename = objects[0].download()
42
+ @vars[:environments] = {
43
+ file: filename,
44
+ # artifact: objects[0],
45
+ version: version,
46
+ changed: false,
47
+ }
48
+ data = IO.read(filename)
49
+ json = begin
50
+ JSON.parse(data)
51
+ rescue
52
+ eval(data)
53
+ end
54
+ IO.write(filename, JSON.pretty_generate(json, {indent: "\t", space: ' '})) # Just to be tidy :)
55
+ @vars[:environments][:data] = Hashie::Mash.new(json)
56
+ @logger.info "#{ENV['ARTIFACTORY_ENVIRONMENTS_MODULE']}-#{version}"
57
+ end
58
+ else
59
+ @logger.error "Version not found for #{ENV['ARTIFACTORY_RELEASE_REPO']}/#{ENV['ARTIFACTORY_ENVIRONMENTS_MODULE']}-#{version} during preparation?"
60
+ @vars[:return_code] = Errors::ARTIFACT_NOT_FOUND
61
+ end
62
+ else
63
+ @logger.info "Alread have the latest version (#{oldver})"
64
+ end
65
+ else
66
+ @logger.error "No version found for #{ENV['ARTIFACTORY_RELEASE_REPO']}/#{ENV['ARTIFACTORY_ENVIRONMENTS_MODULE']} during preparation?"
67
+ @vars[:return_code] = Errors::ARTIFACT_NOT_FOUND
68
+ end
69
+ @vars[:return_code]
70
+ end
71
+
72
+ alias_method :super_cleanupBuild, :cleanupBuild
73
+ # ---------------------------------------------------------------------------------------------------------------
74
+ def cleanupAfterUpload()
75
+ @logger.step CLASS+'::'+__method__.to_s
76
+ if @vars[:environments] and @vars[:environments][:file]
77
+ FileUtils.rm_rf(File.dirname(@vars[:environments][:file]))
78
+ end
79
+ 0
80
+ end
81
+
82
+ # ---------------------------------------------------------------------------------------------------------------
83
+ def packageBuild()
84
+ @logger.info CLASS+'::'+__method__.to_s
85
+ if @vars.has_key?(:environments) and not @vars[:environments].empty?
86
+ @vars[:return_code] = 0
87
+ # Now comes the fun part ... get the list of profiles/credentials and use AWS SDK to discover environments
88
+ # Let's reuse some code we already have and wrap it in a class
89
+ begin
90
+ require_relative 'lib/update_bucket_policy'
91
+ helper = UpdateBucketPolicy.new
92
+ helper.logger = @logger
93
+ helper.options = Hashie::Mash.new(helper.options.to_hash)
94
+ helper.options[:iniglob] = ENV['AWS_INI_GLOB'] if ENV['AWS_INI_GLOB']
95
+ helper.options[:inifile] = ENV['AWS_INI_FILE'] if ENV['AWS_INI_FILE']
96
+ helper.options[:profile] = ENV['AWS_PROFILE'] if ENV['AWS_PROFILE']
97
+ helper.options[:profiles] = ENV['AWS_PROFILES'] if ENV['AWS_PROFILES']
98
+ helper.prepare_accounts
99
+ if helper.accounts.size > 0
100
+ helper.get_environments()
101
+ if helper.environments.size > 0
102
+ getLatestEnvironments(@vars[:environments][:version])
103
+ if 0 == @vars[:return_code]
104
+ environments = Hashie::Mash.new(@vars[:environments][:data])
105
+ exclude_regex = ENV['ENVS_EXCLUDE_REGEX'] || '-repo'
106
+ helper.environments.select{|e,_| e !~ /#{exclude_regex}/ }.each do |envnam,_|
107
+ environments[envnam] ||= {}
108
+ end
109
+ if environments.size != @vars[:environments][:data].size
110
+ IO.write(@vars[:environments][:file], JSON.pretty_generate(environments.to_hash, {indent: "\t", space: ' '}))
111
+ @vars[:environments][:changed] = true
112
+ end
113
+ end
114
+ end
115
+ else
116
+ @logger.error 'No AWS accounts found during preparation?'
117
+ @vars[:return_code] = Errors::NO_ACCOUNTS
118
+ end
119
+ rescue => e
120
+ @logger.fatal "#{e.message}\n#{e.backtrace}"
121
+ @vars[:return_code] = Errors::NO_ACCOUNTS
122
+ end
123
+ else
124
+ @logger.error 'No components found during preparation?'
125
+ @vars[:return_code] = Errors::NO_COMPONENTS
126
+ end
127
+ @vars[:return_code]
128
+ end
129
+
130
+ protected
131
+
132
+ def prepare_accounts
133
+ @accounts = []
134
+ if @options[:iniglob]
135
+ iniglob = File.expand_path(@options[:iniglob])
136
+ Dir.glob( iniglob) {| filename |
137
+ @accounts << IniFileCredentials.new(filename)
138
+ }
139
+ elsif @options[:inifile]
140
+ @accounts << IniFileCredentials.new(@options[:inifile]) unless (@options[:inifile].nil? or @options[:inifile].empty?)
141
+ end
142
+
143
+ if @options[:profiles]
144
+ @options[:profiles].each {| profile |
145
+ @accounts << ProfileCredentials.new(profile)
146
+ }
147
+ end
148
+ unless @accounts.size > 0
149
+ parse_options
150
+ logger.fatal 'No options allow for account identification'
151
+ end
152
+ # Make the options mutable
153
+ opts = @options.dup
154
+ @options = Hash.new()
155
+ opts.each do |k,v|
156
+ @options[k.to_sym] = v
157
+ end
158
+ end
159
+
160
+ end
161
+ end
162
+ end
163
+ end