image_builder 0.0.1
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/.gitignore +22 -0
- data/.rubocop.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +29 -0
- data/bin/image_builder +234 -0
- data/image_builder.gemspec +30 -0
- data/lib/image_builder.rb +38 -0
- data/lib/image_builder/backends/backend_base.rb +10 -0
- data/lib/image_builder/backends/packer.rb +109 -0
- data/lib/image_builder/builders/aws_base.rb +101 -0
- data/lib/image_builder/builders/aws_chroot.rb +33 -0
- data/lib/image_builder/builders/aws_ebs.rb +15 -0
- data/lib/image_builder/builders/aws_instance.rb +45 -0
- data/lib/image_builder/builders/builder_base.rb +12 -0
- data/lib/image_builder/builders/null.rb +39 -0
- data/lib/image_builder/post_processors/compress.rb +24 -0
- data/lib/image_builder/post_processors/post_processors_base.rb +10 -0
- data/lib/image_builder/post_processors/vagrant.rb +41 -0
- data/lib/image_builder/provisioners/chef_base.rb +96 -0
- data/lib/image_builder/provisioners/chef_client.rb +115 -0
- data/lib/image_builder/provisioners/chef_solo.rb +33 -0
- data/lib/image_builder/provisioners/file.rb +26 -0
- data/lib/image_builder/provisioners/provisioner_base.rb +10 -0
- data/lib/image_builder/provisioners/shell.rb +47 -0
- data/lib/image_builder/version.rb +4 -0
- data/spec/aws_chroot_builder_spec.rb +30 -0
- data/spec/aws_ebs_builder_spec.rb +30 -0
- data/spec/aws_instance_builder_spec.rb +30 -0
- data/spec/chef_client_provisioner_spec.rb +32 -0
- data/spec/chef_solo_provisioner_spec.rb +28 -0
- data/spec/compress_postprocessor_spec.rb +15 -0
- data/spec/fixtures/test-knife.rb +40 -0
- data/spec/null_builder_spec.rb +21 -0
- data/spec/packer_backend_spec.rb +145 -0
- data/spec/vagrant_postprocessor_spec.rb +20 -0
- metadata +236 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 96fdbf490de1409c12b7a35690ec161016076e46
|
4
|
+
data.tar.gz: 4cf8e63dd9fc03672579739c72ee8e6a95571718
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4a69a23cb0a72a8cb509511c4848f79b8534222e67252384d5d43ec5a4eac99cab99001d99ddf5d80655b5ffa06a4501aefd47a58c44a0b8ee3d76713e6b0766
|
7
|
+
data.tar.gz: 8bc8a87b2541b9e78c51bed13ddad2f0227e267ebefd53ba41aca9cd4157cdb492ee1acacc1143c8022cd0be5537c24da79c1038ebe684bbbea18ae359f199f5
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Mike Morris
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# ImageBuilder
|
2
|
+
|
3
|
+
A gem to build operating system images for various platforms. At initial release, this gem supports
|
4
|
+
building images using packer to build images for the AWS platform
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'image_builder'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install image_builder
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
This is how you use the gem, should probably write something useful here.
|
23
|
+
But since it's just a library gem that basically just wraps the packer utility,
|
24
|
+
read this code, and the packer documentation to figure out what to do
|
25
|
+
|
26
|
+
## Contributing
|
27
|
+
|
28
|
+
1. Fork it ( https://github.com/[my-github-username]/image_builder/fork )
|
29
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
30
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
31
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
32
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
gem_dir = ::File.dirname(__FILE__)
|
5
|
+
|
6
|
+
desc 'Rubocop'
|
7
|
+
task rc: [:rubocop]
|
8
|
+
task :rubocop do
|
9
|
+
sh "bundle exec rubocop -D #{gem_dir}"
|
10
|
+
end
|
11
|
+
|
12
|
+
task default: [:rc] do
|
13
|
+
Rake::Task[:spec].invoke('progress')
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Run rspec'
|
17
|
+
RSpec::Core::RakeTask.new(:spec, [:format, :tags]) do |t, args|
|
18
|
+
format = args[:format] || 'documentation'
|
19
|
+
tags = args[:tags] || []
|
20
|
+
t.verbose = false
|
21
|
+
t.fail_on_error = true
|
22
|
+
t.rspec_opts = "--format=#{format}"
|
23
|
+
|
24
|
+
tags.flatten.compact.each do |tag|
|
25
|
+
t.rspec_opts += " --tag #{tag}"
|
26
|
+
end
|
27
|
+
|
28
|
+
t.ruby_opts = '-W0'
|
29
|
+
end
|
data/bin/image_builder
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'commander'
|
4
|
+
require 'cloudutil/aws/ec2'
|
5
|
+
require 'chef/node'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
# All sorts of funkiness happens if I try to put these in the
|
9
|
+
# top-level image_builder module, and try to require just that
|
10
|
+
require 'image_builder/version'
|
11
|
+
require 'image_builder/backends/packer'
|
12
|
+
require 'image_builder/builders/aws_ebs'
|
13
|
+
require 'image_builder/provisioners/chef_client'
|
14
|
+
require 'image_builder/provisioners/chef_solo'
|
15
|
+
require 'image_builder/provisioners/shell'
|
16
|
+
require 'image_builder/post_processors/vagrant'
|
17
|
+
|
18
|
+
def prog_name
|
19
|
+
::File.basename(__FILE__)
|
20
|
+
end
|
21
|
+
|
22
|
+
def attr_missing?(attr_value)
|
23
|
+
attr_value.nil? || attr_value.strip.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_attrs(attr_file)
|
27
|
+
# I didn't want to have to dip into the chef gem to do this,
|
28
|
+
# but it appears to be the best way to get attributes
|
29
|
+
node = Chef::Node.build('image-builder')
|
30
|
+
node.from_file(::File.expand_path(attr_file))
|
31
|
+
node.attributes.merged_attributes
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_chef_client_provisioner(knife_file, chef_attrs, options)
|
35
|
+
chef_client_prov = ImageBuilder::Provisioners::Chef::Client.from_file(knife_file)
|
36
|
+
chef_client_prov.chef_environment = chef_attrs['amibakery']['build']['chef_env']
|
37
|
+
chef_client_prov.node_name = "#{prog_name}_{{uuid}}"
|
38
|
+
chef_client_prov.run_list = options.r_run_list
|
39
|
+
chef_client_prov.json = JSON.parse(options.j_json) # Placeholder for custom json used to pass in build-specific data
|
40
|
+
chef_client_prov
|
41
|
+
end
|
42
|
+
|
43
|
+
def resolve_sec_grps(grp_ary, util_obj)
|
44
|
+
grps = []
|
45
|
+
|
46
|
+
grp_ary.each do |g|
|
47
|
+
grps << util_obj.resolve_security_group_id(g)
|
48
|
+
end
|
49
|
+
|
50
|
+
grps.flatten.compact.uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
def format_string(str, subs = {})
|
54
|
+
str % subs unless str.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_tags(hash, subs = {})
|
58
|
+
new_hash = hash
|
59
|
+
|
60
|
+
unless hash.nil?
|
61
|
+
hash.each_pair do |k, v|
|
62
|
+
new_hash[k] = format_string(v, subs)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
new_hash
|
67
|
+
end
|
68
|
+
|
69
|
+
# rubocop:disable Metrics/MethodLength
|
70
|
+
def create_builders(attrs, options)
|
71
|
+
# Create builder objects, resolve AWS names/tags to ids along the way
|
72
|
+
builders = []
|
73
|
+
cu = Cloudutil::AWS::EC2.new
|
74
|
+
|
75
|
+
build_attrs = attrs['amibakery']['build']
|
76
|
+
%w(centos ubuntu).each do |p|
|
77
|
+
build_attrs[p].keys.each do |v|
|
78
|
+
plat_attrs = build_attrs[p][v]
|
79
|
+
root_type = plat_attrs['root_dev']
|
80
|
+
virt_type = plat_attrs['virt_type']
|
81
|
+
sub_hash = { p: p, v: v, root_type: root_type, virt_type: virt_type }
|
82
|
+
|
83
|
+
b = ImageBuilder::Builders::AWS::EBS.new
|
84
|
+
b.name = "#{p}#{v}-#{root_type}"
|
85
|
+
b.ami_name = format_string(options.n_ami_name, sub_hash)
|
86
|
+
b.instance_type = build_attrs['inst_type']
|
87
|
+
b.source_ami = cu.resolve_ami_id(plat_attrs['ami_id'])
|
88
|
+
b.ssh_username = plat_attrs['build_user']
|
89
|
+
b.ami_description = format_string(options.ami_description, sub_hash)
|
90
|
+
b.ami_regions = options.c_copy_regions
|
91
|
+
b.ami_users = options.u_users
|
92
|
+
b.ami_virtualization_type = virt_type
|
93
|
+
b.security_group_ids = resolve_sec_grps([build_attrs['sec_grp_id']].flatten, cu)
|
94
|
+
b.ssh_private_ip = true
|
95
|
+
b.subnet_id = cu.resolve_subnet_id(build_attrs['subnet_id'])
|
96
|
+
b.tags = { Name: b.ami_name }.merge(format_tags(options.tags.clone, sub_hash))
|
97
|
+
b.launch_block_device_mappings = b.class.default_launch_block_device_mappings
|
98
|
+
b.ami_block_device_mappings = b.class.default_block_device_mappings
|
99
|
+
|
100
|
+
builders << b
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
builders
|
105
|
+
end
|
106
|
+
# rubocop:enable Metrics/MethodLength
|
107
|
+
|
108
|
+
def run_packer(builders, provisioners, postprocessors, path = nil)
|
109
|
+
ImageBuilder::Backends::Packer.packer_path(path) unless path.nil? || path.strip.empty?
|
110
|
+
packer = ImageBuilder::Backends::Packer.new
|
111
|
+
|
112
|
+
builders.each do |b|
|
113
|
+
packer.add_builder(b)
|
114
|
+
end
|
115
|
+
|
116
|
+
provisioners.each do |p|
|
117
|
+
packer.add_provisioner(p)
|
118
|
+
end
|
119
|
+
|
120
|
+
postprocessors.each do |p|
|
121
|
+
packer.add_post_processor(p)
|
122
|
+
end
|
123
|
+
|
124
|
+
packer.build(['-machine-readable'])
|
125
|
+
end
|
126
|
+
|
127
|
+
def convert_tags(tag_opt)
|
128
|
+
# Should only ever be an empty Hash (option default),
|
129
|
+
# or an Array from the command-line
|
130
|
+
hash = {}
|
131
|
+
|
132
|
+
if tag_opt.is_a? Array
|
133
|
+
tag_opt.each do |t|
|
134
|
+
(tag, value) = t.split('=', 2)
|
135
|
+
hash[tag] = value
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
hash
|
140
|
+
end
|
141
|
+
|
142
|
+
# NOTE: Array options are in format '--opt arg1,arg2' (comma-seperated, no space!)
|
143
|
+
Commander.configure do
|
144
|
+
program :name, prog_name
|
145
|
+
program :version, ImageBuilder::VERSION
|
146
|
+
program :description, 'A program to generate AWS AMIs with Chef and Packer.'
|
147
|
+
.concat(' It is very opinionated about how it works, so use it as a reference, and write your own')
|
148
|
+
|
149
|
+
default_command :build
|
150
|
+
global_option('-d', '--debug', 'Provide debugging output')
|
151
|
+
|
152
|
+
command :build do |c|
|
153
|
+
c.syntax = "#{prog_name} [build] [OPTIONS]"
|
154
|
+
c.description = 'A program to build AWS AMIs using Chef and Packer. This is the default action to run, if not supplied'
|
155
|
+
|
156
|
+
c.option '-r, --run-list RUN_LIST', Array, 'The run-list for Chef to execute to provision the node'
|
157
|
+
c.option '-c, --copy-regions REGIONS', Array, 'A list of regions to copy the generated AMI to'
|
158
|
+
c.option '-u, --users USERS', Array, 'A list of users to grant access to the generated AMI'
|
159
|
+
c.option '-s, --serverspec-path SS_PATH', String, 'The path to the serverspec directory to use to validate the build'
|
160
|
+
c.option '-k, --knife-file KNIFE_FILE', String, 'The path to a knife config file to use to configure a Chef provisioner'
|
161
|
+
c.option '-a, --attrib-file ATTRIB_FILE', String, 'The path to a Chef recipes attribute file to use to configure an AWS builder'
|
162
|
+
c.option '-p, --packer-path PACKER_PATH', String, 'The path to the packer executable, if it\'s in a non-standard location'
|
163
|
+
c.option '-j, --json JSON', String, 'A string of JSON text to pass along to the chef-client provisioner'
|
164
|
+
c.option '-n, --ami-name NAME', String, 'The name of the ami to create'
|
165
|
+
c.option '--tags TAGS', Array, 'Tags to apply to the AMI (Name tag is auto-generated as --ami-name value)'
|
166
|
+
c.option '--ami-description DESC', String, 'A description of the ami'
|
167
|
+
c.option '--cleanup-run-list RUN_LIST', Array, 'A run-list used to clean up the provisioned node before AMI creation'
|
168
|
+
c.option '--[no-]vagrant-box', 'Create a Vagrant box defintion after the build completes'
|
169
|
+
|
170
|
+
c.action do |_args, options|
|
171
|
+
options.default vagrant_box: false
|
172
|
+
options.default tags: {}
|
173
|
+
|
174
|
+
knife_file = options.k_knife_file
|
175
|
+
attrib_file = options.a_attrib_file
|
176
|
+
|
177
|
+
# Do a fixup for --tags arg to convert from Array to Hash
|
178
|
+
options.tags = convert_tags(options.tags)
|
179
|
+
|
180
|
+
# Check to make sure -k, -a, & -n args are given, since it's missing from doing in Commander
|
181
|
+
err_msgs = []
|
182
|
+
|
183
|
+
err_msgs << 'Missing required -k option' if attr_missing? knife_file
|
184
|
+
err_msgs << 'Missing required -a option' if attr_missing? attrib_file
|
185
|
+
err_msgs << 'Missing required -n option' if attr_missing? options.n_ami_name
|
186
|
+
|
187
|
+
fail ArgumentError, err_msgs.join("\n") unless err_msgs.empty?
|
188
|
+
|
189
|
+
# create AWS builder info via data from -a option
|
190
|
+
chef_attrs = read_attrs(attrib_file)
|
191
|
+
builders = create_builders(chef_attrs, options)
|
192
|
+
|
193
|
+
# create chef-client provisioner from knife.rb and chef attributes
|
194
|
+
chef_client_prov = create_chef_client_provisioner(knife_file, chef_attrs, options)
|
195
|
+
|
196
|
+
dir_prov = ImageBuilder::Provisioners::Shell.new
|
197
|
+
dir_prov.inline = ['sudo mkdir -p -m 777 /etc/chef /tmp/packer-chef-client']
|
198
|
+
|
199
|
+
# Do 'require' here, to avoid collisions with potential unqualified references to the File class
|
200
|
+
require 'image_builder/provisioners/file'
|
201
|
+
|
202
|
+
unless attr_missing? chef_client_prov.encrypted_data_bag_secret_path
|
203
|
+
sec_prov = ImageBuilder::Provisioners::File.new
|
204
|
+
sec_prov.source = chef_client_prov.encrypted_data_bag_secret_path
|
205
|
+
sec_prov.destination = '/etc/chef/encrypted_data_bag_secret'
|
206
|
+
end
|
207
|
+
|
208
|
+
unless attr_missing? options.s_serverspec_path
|
209
|
+
ss_file_prov = ImageBuilder::Provisioners::File.new
|
210
|
+
ss_file_prov.source = options.s_serverspec_path
|
211
|
+
ss_file_prov.destination = '/var/tmp'
|
212
|
+
|
213
|
+
ss_shell_prov = ImageBuilder::Provisioners::Shell.new
|
214
|
+
ss_shell_prov.inline = ['/var/tmp/serverspec/serverspec.sh']
|
215
|
+
end
|
216
|
+
|
217
|
+
unless options.cleanup_run_list.nil?
|
218
|
+
cleanup_prov = ImageBuilder::Provisioners::Chef::Solo.new
|
219
|
+
cleanup_prov.run_list = [options.cleanup_run_list].flatten
|
220
|
+
cleanup_prov.remote_cookbook_paths = ['/var/chef/cache/cookbooks']
|
221
|
+
cleanup_prov.skip_install = true
|
222
|
+
end
|
223
|
+
|
224
|
+
# add vagrant post-processor, if requested
|
225
|
+
vagrant = ImageBuilder::PostProcessors::Vagrant.new if options.vagrant_box
|
226
|
+
|
227
|
+
# order matters!
|
228
|
+
provisioners = [dir_prov, sec_prov, chef_client_prov, ss_file_prov, ss_shell_prov, cleanup_prov].compact
|
229
|
+
|
230
|
+
# run packer
|
231
|
+
run_packer(builders, provisioners, [vagrant].compact, options.p_packer_path)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'image_builder/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'image_builder'
|
8
|
+
spec.version = ImageBuilder::VERSION
|
9
|
+
spec.authors = ['Mike Morris']
|
10
|
+
spec.email = ['michael.m.morris@pearson.com']
|
11
|
+
spec.summary = 'A gem to create operating system images using various methods'
|
12
|
+
spec.description = IO.read(File.join(File.dirname(__FILE__), 'README.md'))
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'rspec'
|
24
|
+
spec.add_development_dependency 'rubocop'
|
25
|
+
|
26
|
+
spec.add_dependency 'mixlib-shellout'
|
27
|
+
spec.add_dependency 'commander'
|
28
|
+
spec.add_dependency 'cloudutil'
|
29
|
+
spec.add_dependency 'chef', '~> 11.12'
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'image_builder/version'
|
2
|
+
|
3
|
+
# Generic top-level module comment
|
4
|
+
module ImageBuilder
|
5
|
+
protected
|
6
|
+
|
7
|
+
def attr_to_hash(src_hash, attr_sym, required = false)
|
8
|
+
val = send(attr_sym)
|
9
|
+
|
10
|
+
if required
|
11
|
+
src_hash[attr_sym] = val
|
12
|
+
else
|
13
|
+
unless val.nil?
|
14
|
+
if val.respond_to? :empty?
|
15
|
+
v = check_empty(val)
|
16
|
+
src_hash[attr_sym] = v unless v.nil? # rubocop:disable Metrics/BlockNesting
|
17
|
+
else
|
18
|
+
# Not nil, and doesn't support empty?, so assign
|
19
|
+
src_hash[attr_sym] = val
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def check_empty(val)
|
28
|
+
if val.is_a? String
|
29
|
+
# Strip whitespace surrounding strings before testing for empty
|
30
|
+
return val unless val.strip.empty?
|
31
|
+
else
|
32
|
+
# Array, Hash, etc...
|
33
|
+
return val unless val.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|