amigrind 0.1.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 +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +72 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +201 -0
- data/README.md +112 -0
- data/Rakefile +6 -0
- data/amigrind.gemspec +36 -0
- data/bin/amigrind +8 -0
- data/lib/amigrind.rb +29 -0
- data/lib/amigrind/blueprints/aws_config.rb +56 -0
- data/lib/amigrind/blueprints/base_ami_source.rb +15 -0
- data/lib/amigrind/blueprints/blueprint.rb +22 -0
- data/lib/amigrind/blueprints/evaluator.rb +269 -0
- data/lib/amigrind/blueprints/parent_blueprint_source.rb +10 -0
- data/lib/amigrind/blueprints/provisioner.rb +20 -0
- data/lib/amigrind/blueprints/provisioners/file_upload.rb +29 -0
- data/lib/amigrind/blueprints/provisioners/local_shell.rb +28 -0
- data/lib/amigrind/blueprints/provisioners/remote_shell.rb +57 -0
- data/lib/amigrind/build/packer_runner.rb +106 -0
- data/lib/amigrind/build/rackerizer.rb +134 -0
- data/lib/amigrind/builder.rb +46 -0
- data/lib/amigrind/cli.rb +12 -0
- data/lib/amigrind/cli/_helpers.rb +49 -0
- data/lib/amigrind/cli/_root.rb +43 -0
- data/lib/amigrind/cli/blueprints/_category.rb +15 -0
- data/lib/amigrind/cli/blueprints/list.rb +21 -0
- data/lib/amigrind/cli/blueprints/show.rb +22 -0
- data/lib/amigrind/cli/build/_category.rb +15 -0
- data/lib/amigrind/cli/build/execute.rb +32 -0
- data/lib/amigrind/cli/build/print_packer.rb +28 -0
- data/lib/amigrind/cli/environments/_category.rb +15 -0
- data/lib/amigrind/cli/environments/list.rb +23 -0
- data/lib/amigrind/cli/environments/show.rb +22 -0
- data/lib/amigrind/cli/inventory/_category.rb +15 -0
- data/lib/amigrind/cli/inventory/add_to_channel.rb +28 -0
- data/lib/amigrind/cli/inventory/get-image.rb +34 -0
- data/lib/amigrind/cli/inventory/list.rb +14 -0
- data/lib/amigrind/cli/inventory/remove_from_channel.rb +28 -0
- data/lib/amigrind/cli/repo/_category.rb +15 -0
- data/lib/amigrind/cli/repo/init.rb +22 -0
- data/lib/amigrind/config.rb +89 -0
- data/lib/amigrind/environments/channel.rb +9 -0
- data/lib/amigrind/environments/environment.rb +66 -0
- data/lib/amigrind/environments/rb_evaluator.rb +7 -0
- data/lib/amigrind/repo.rb +217 -0
- data/lib/amigrind/version.rb +3 -0
- data/sample_config/config.yaml +9 -0
- data/sample_repo/.amigrind_root +0 -0
- data/sample_repo/blueprints/dependent_ubuntu.rb +21 -0
- data/sample_repo/blueprints/simple_ubuntu.rb +37 -0
- data/sample_repo/environments/development.yaml.example +15 -0
- metadata +288 -0
data/Rakefile
ADDED
data/amigrind.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'amigrind/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "amigrind"
|
8
|
+
spec.version = Amigrind::VERSION
|
9
|
+
spec.authors = ["Ed Ropple"]
|
10
|
+
spec.email = ["ed@edropple.com"]
|
11
|
+
|
12
|
+
spec.summary = "An easy, convention-over-configuration builder for Packer images."
|
13
|
+
spec.homepage = "https://github.com/eropple/amigrind"
|
14
|
+
spec.license = "Apache 2.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "bin"
|
18
|
+
spec.executables = [ 'amigrind' ]
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
24
|
+
|
25
|
+
spec.add_runtime_dependency 'cri', '~> 2.7.0'
|
26
|
+
spec.add_runtime_dependency 'os', '~> 0.9.6'
|
27
|
+
spec.add_runtime_dependency 'racker', '~> 0.2.0'
|
28
|
+
spec.add_runtime_dependency 'virtus', '~> 1.0.5'
|
29
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.2.6'
|
30
|
+
spec.add_runtime_dependency 'activemodel', '~> 4.2.6'
|
31
|
+
spec.add_runtime_dependency 'erubis', '~> 2.7.0'
|
32
|
+
spec.add_runtime_dependency 'ptools', '~> 1.3', '>= 1.3.3'
|
33
|
+
spec.add_runtime_dependency 'settingslogic', '~> 2.0.9'
|
34
|
+
|
35
|
+
spec.add_runtime_dependency 'amigrind-core', '~> 0.1.0'
|
36
|
+
end
|
data/bin/amigrind
ADDED
data/lib/amigrind.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'amigrind/version'
|
2
|
+
require 'amigrind/core'
|
3
|
+
require 'amigrind/config'
|
4
|
+
require 'amigrind/repo'
|
5
|
+
|
6
|
+
require 'ptools'
|
7
|
+
require 'open3'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'aws-sdk'
|
10
|
+
require 'racker'
|
11
|
+
require 'json'
|
12
|
+
require 'yaml'
|
13
|
+
require 'ice_nine'
|
14
|
+
require 'set'
|
15
|
+
require 'erubis'
|
16
|
+
require 'virtus'
|
17
|
+
require 'active_support/core_ext/string/inflections'
|
18
|
+
require 'active_support/core_ext/hash'
|
19
|
+
require 'active_support/core_ext/numeric/time'
|
20
|
+
|
21
|
+
# Pre-includes
|
22
|
+
module Amigrind
|
23
|
+
end
|
24
|
+
|
25
|
+
Dir["#{__dir__}/**/*.rb"].reject { |f| f.include?('/cli') }.each { |f| require_relative f }
|
26
|
+
|
27
|
+
# Post-includes
|
28
|
+
module Amigrind
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Amigrind
|
2
|
+
module Blueprints
|
3
|
+
class AWSConfig
|
4
|
+
include Virtus.model(constructor: false, mass_assignment: false)
|
5
|
+
|
6
|
+
class BlockDeviceMapping
|
7
|
+
end
|
8
|
+
|
9
|
+
attribute :instance_type, String
|
10
|
+
# TODO: ssh_username is currently required for all builds, but should
|
11
|
+
# instead inherit from the parent blueprint if one exists. This
|
12
|
+
# is annoying and recursive right now, and a small pain point,
|
13
|
+
# so I've punted it.
|
14
|
+
attribute :ssh_username, String
|
15
|
+
|
16
|
+
attribute :region, String
|
17
|
+
attribute :copy_regions, Array[String]
|
18
|
+
attribute :associate_public_ip_address, Boolean
|
19
|
+
attribute :ebs_optimized, Boolean
|
20
|
+
attribute :enhanced_networking, Boolean
|
21
|
+
attribute :iam_instance_profile, String
|
22
|
+
attribute :ssh_keypair_name, String
|
23
|
+
attribute :ssh_private_ip, Boolean
|
24
|
+
attribute :user_data, String
|
25
|
+
attribute :windows_password_timeout, ActiveSupport::Duration
|
26
|
+
|
27
|
+
attribute :run_tags, Hash[String => String]
|
28
|
+
attribute :run_volume_tags, Hash[String => String]
|
29
|
+
attribute :security_group_ids, Array[String]
|
30
|
+
|
31
|
+
attribute :vpc_id, String
|
32
|
+
attribute :subnet_ids, Array[String]
|
33
|
+
|
34
|
+
# TODO: object-ize the ami block device mappings
|
35
|
+
attribute :ami_block_device_mappings, Array[BlockDeviceMapping]
|
36
|
+
attribute :launch_block_device_mappings, Array[BlockDeviceMapping]
|
37
|
+
|
38
|
+
attr_reader :custom
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@copy_regions = []
|
42
|
+
|
43
|
+
@run_tags = {}
|
44
|
+
@run_volume_tags = {}
|
45
|
+
@security_group_ids = []
|
46
|
+
|
47
|
+
@subnet_ids = []
|
48
|
+
|
49
|
+
@ami_block_device_mappings = []
|
50
|
+
@launch_block_device_mappings = []
|
51
|
+
|
52
|
+
@custom = {}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Amigrind
|
2
|
+
module Blueprints
|
3
|
+
class BaseAMISource
|
4
|
+
include Virtus.model(constructor: false, mass_assignment: false)
|
5
|
+
|
6
|
+
attribute :family, Symbol
|
7
|
+
attribute :version, String
|
8
|
+
attribute :ids, Hash[String => String]
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@ids = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'amigrind/blueprints/provisioner'
|
2
|
+
|
3
|
+
module Amigrind
|
4
|
+
module Blueprints
|
5
|
+
class Blueprint
|
6
|
+
include Virtus.model(constructor: false, mass_assignment: false)
|
7
|
+
|
8
|
+
attribute :name, String
|
9
|
+
attribute :build_channel, Symbol
|
10
|
+
attribute :description, String, default: ''
|
11
|
+
attr_accessor :source
|
12
|
+
attribute :aws, Amigrind::Blueprints::AWSConfig
|
13
|
+
|
14
|
+
attribute :provisioners, Array[Amigrind::Blueprints::Provisioner], default: []
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@aws = AWSConfig.new
|
18
|
+
@provisioners = []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
module Amigrind
|
2
|
+
module Blueprints
|
3
|
+
class Evaluator
|
4
|
+
include Amigrind::Core::Logging::Mixin
|
5
|
+
include Amigrind::Blueprints::Provisioners
|
6
|
+
|
7
|
+
attr_reader :blueprint
|
8
|
+
|
9
|
+
def initialize(filename, environment = nil)
|
10
|
+
@blueprint = Blueprint.new
|
11
|
+
@blueprint.name = File.basename(filename, ".rb")
|
12
|
+
|
13
|
+
regex = Amigrind::Core::BLUEPRINT_NAME_REGEX
|
14
|
+
raise "blueprint name (#{@blueprint.name}) must match #{regex.source}" \
|
15
|
+
unless regex.match(@blueprint.name)
|
16
|
+
|
17
|
+
@properties =
|
18
|
+
if environment.nil?
|
19
|
+
debug_log "no environment found to use with blueprint"
|
20
|
+
{}
|
21
|
+
else
|
22
|
+
debug_log "using environment '#{environment.name}' with blueprint"
|
23
|
+
environment.properties.merge(environment_name: environment.name)
|
24
|
+
end
|
25
|
+
|
26
|
+
unless environment.nil?
|
27
|
+
@blueprint.aws.vpc_id = environment.aws.vpc
|
28
|
+
@blueprint.aws.subnet_ids = environment.aws.subnets
|
29
|
+
|
30
|
+
@blueprint.aws.region = environment.aws.region
|
31
|
+
@blueprint.aws.copy_regions = environment.aws.copy_regions
|
32
|
+
@blueprint.aws.ssh_keypair_name = environment.aws.ssh_keypair_name
|
33
|
+
end
|
34
|
+
|
35
|
+
instance_eval(IO.read(filename), File.expand_path(filename), 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.evaluate(filename, environment = nil)
|
39
|
+
Evaluator.new(filename, environment).blueprint
|
40
|
+
end
|
41
|
+
|
42
|
+
def properties
|
43
|
+
@properties
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def build_channel(b)
|
49
|
+
raise "'build_channel' must be a String or Symbol." \
|
50
|
+
unless b.is_a?(String) || b.is_a?(Symbol)
|
51
|
+
|
52
|
+
@blueprint.build_channel = b
|
53
|
+
end
|
54
|
+
|
55
|
+
def description(d)
|
56
|
+
raise "'description' must be a String." unless d.is_a?(String)
|
57
|
+
@blueprint.description = d
|
58
|
+
end
|
59
|
+
|
60
|
+
def parent_blueprint(p)
|
61
|
+
raise "'parent_blueprint' must be a String." unless p.is_a?(String)
|
62
|
+
@blueprint.source = p
|
63
|
+
end
|
64
|
+
|
65
|
+
def source(type, &block)
|
66
|
+
case type
|
67
|
+
when :ami
|
68
|
+
BaseAMIEvaluator.new(@blueprint, self, &block)
|
69
|
+
when :parent
|
70
|
+
ParentBlueprintEvaluator.new(@blueprint, self, &block)
|
71
|
+
else
|
72
|
+
raise "Invalid source type: #{type} (must be :ami, :parent)"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def aws(&block)
|
77
|
+
AWSConfigEvaluator.new(@blueprint, self, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def provisioner(name, provisioner_class, weight: nil, &block)
|
81
|
+
weight ||= (@blueprint.provisioners.max_by(&:weight) || 0) + 1
|
82
|
+
|
83
|
+
raise "'name' must be a String or Symbol." \
|
84
|
+
unless name.is_a?(String) || name.is_a?(Symbol)
|
85
|
+
raise "'provisioner_class' must inherit from Amigrind::Blueprints::Provisioner" \
|
86
|
+
unless provisioner_class.ancestors.include?(Amigrind::Blueprints::Provisioner)
|
87
|
+
raise "'weight' must be a Fixnum." unless weight.is_a?(Fixnum)
|
88
|
+
|
89
|
+
@blueprint.provisioners <<
|
90
|
+
ProvisionerEvaluator.new(name, self, weight, provisioner_class, &block).provisioner
|
91
|
+
end
|
92
|
+
|
93
|
+
class BaseAMIEvaluator
|
94
|
+
def initialize(bp, evaluator, &block)
|
95
|
+
@bp = bp
|
96
|
+
@bp.source = Amigrind::Blueprints::BaseAMISource.new
|
97
|
+
|
98
|
+
@evaluator = evaluator
|
99
|
+
|
100
|
+
instance_eval(&block)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def properties
|
106
|
+
@evaluator.properties
|
107
|
+
end
|
108
|
+
|
109
|
+
def family(f)
|
110
|
+
raise "'family' must implement #to_sym." unless f.respond_to?(:to_sym)
|
111
|
+
|
112
|
+
@bp.source.family = f.to_sym.to_s.strip.to_sym
|
113
|
+
end
|
114
|
+
|
115
|
+
def version(v)
|
116
|
+
raise "'version' must be a String." unless v.is_a?(String)
|
117
|
+
|
118
|
+
@bp.source.version = v.strip
|
119
|
+
end
|
120
|
+
|
121
|
+
def id(region, image_id)
|
122
|
+
regex = Amigrind::Core::AMI_REGEX
|
123
|
+
|
124
|
+
raise "'region' must be stringable." unless region.respond_to?(:to_s)
|
125
|
+
raise "'image_id' must be in AMI format (/#{regex.source}/)" \
|
126
|
+
unless regex.match(image_id)
|
127
|
+
|
128
|
+
@bp.source.ids[region.to_s.strip] = image_id
|
129
|
+
end
|
130
|
+
|
131
|
+
def ids(ami_ids)
|
132
|
+
raise "'ami_ids' must be a Hash-alike." unless ami_ids.respond_to?(:each_pair)
|
133
|
+
ami_ids.each_pair { |region, image_id| id(region, image_id) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class ParentBlueprintEvaluator
|
138
|
+
def initialize(bp, evaluator, &block)
|
139
|
+
@bp = bp
|
140
|
+
@bp.source = Amigrind::Blueprints::ParentBlueprintSource.new
|
141
|
+
|
142
|
+
@evaluator = evaluator
|
143
|
+
|
144
|
+
instance_eval(&block)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def properties
|
150
|
+
@evaluator.properties
|
151
|
+
end
|
152
|
+
|
153
|
+
def name(n)
|
154
|
+
raise "'name' must be a String." unless n.is_a?(String)
|
155
|
+
|
156
|
+
@bp.source.name = n
|
157
|
+
end
|
158
|
+
|
159
|
+
def channel(c)
|
160
|
+
raise "'channel' must implement #to_sym." unless c.respond_to?(:to_sym)
|
161
|
+
|
162
|
+
@bp.source.channel = c
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class AWSConfigEvaluator
|
167
|
+
def initialize(bp, evaluator, &block)
|
168
|
+
@bp = bp
|
169
|
+
|
170
|
+
@evaluator = evaluator
|
171
|
+
|
172
|
+
instance_eval(&block)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def properties
|
178
|
+
@evaluator.properties
|
179
|
+
end
|
180
|
+
|
181
|
+
def run_tag(key, value)
|
182
|
+
raise "'key' must be a String." unless key.is_a?(String)
|
183
|
+
raise "'value' must be stringable." unless value.respond_to?(:to_s)
|
184
|
+
|
185
|
+
@bp.aws.run_tags[key] = value.to_s
|
186
|
+
end
|
187
|
+
|
188
|
+
def run_tags(tags)
|
189
|
+
raise "'tags' must be a Hash." unless tags.respond_to?(:each_pair)
|
190
|
+
tags.each_pair { |key, value| run_tag(key, value) }
|
191
|
+
end
|
192
|
+
|
193
|
+
def run_volume_tag(key, value)
|
194
|
+
raise "'key' must be a String." unless key.is_a?(String)
|
195
|
+
raise "'value' must be stringable." unless value.respond_to?(:to_s)
|
196
|
+
|
197
|
+
@bp.aws.run_volume_tags[key] = value.to_s
|
198
|
+
end
|
199
|
+
|
200
|
+
def run_volume_tags(tags)
|
201
|
+
raise "'tags' must be a Hash." unless tags.respond_to?(:each_pair)
|
202
|
+
tags.each_pair { |key, value| run_volume_tag(key, value) }
|
203
|
+
end
|
204
|
+
|
205
|
+
def vpc(vpc_id)
|
206
|
+
regex = Amigrind::Core::VPC_REGEX
|
207
|
+
raise "'vpc_id' must be a resource (#{regex.source})" unless regex.match(vpc_id)
|
208
|
+
|
209
|
+
@bp.aws.vpc_id = vpc_id
|
210
|
+
end
|
211
|
+
|
212
|
+
def subnet(subnet_id)
|
213
|
+
regex = Amigrind::Core::SUBNET_REGEX
|
214
|
+
raise "'subnet_id' must be a resource (#{regex.source})" unless regex.match(subnet_id)
|
215
|
+
|
216
|
+
@bp.aws.subnet_ids << subnet_id
|
217
|
+
end
|
218
|
+
|
219
|
+
def security_group(sg_id)
|
220
|
+
regex = Amigrind::Core::SG_REGEX
|
221
|
+
raise "'sg_id' must be a resource (#{regex.source})." unless regex.match(sg_id)
|
222
|
+
|
223
|
+
@bp.aws.security_group_ids << sg_id
|
224
|
+
end
|
225
|
+
|
226
|
+
def custom(key, value)
|
227
|
+
raise "custom 'key' must be a Symbol." unless key.is_a?(Symbol)
|
228
|
+
raise "custom 'value' must be non-nil." if value.nil?
|
229
|
+
|
230
|
+
@bp.aws.custom[key] = value
|
231
|
+
end
|
232
|
+
|
233
|
+
def method_missing(m, *args)
|
234
|
+
@bp.aws.send(:"#{m}=", args[0])
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class ProvisionerEvaluator
|
240
|
+
attr_reader :provisioner
|
241
|
+
|
242
|
+
def initialize(name, evaluator, weight, provisioner_class, &block)
|
243
|
+
@provisioner = provisioner_class.new
|
244
|
+
@provisioner.name = name.to_s
|
245
|
+
@provisioner.weight = weight
|
246
|
+
|
247
|
+
@evaluator = evaluator
|
248
|
+
|
249
|
+
instance_eval(&block)
|
250
|
+
end
|
251
|
+
|
252
|
+
def method_missing(m, *args)
|
253
|
+
eq_msg = :"#{m}="
|
254
|
+
|
255
|
+
if @provisioner.respond_to?(eq_msg)
|
256
|
+
@provisioner.send(eq_msg, args[0])
|
257
|
+
else
|
258
|
+
@provisioner.send(m, *args)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
def properties
|
265
|
+
@evaluator.properties
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|