yutani 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bed76670778f66c3a40d8d86e5de3cef50b718cb
4
+ data.tar.gz: 9f061e4858b478a843e4a0d786f050c1b8436e15
5
+ SHA512:
6
+ metadata.gz: d5209d59b656258ce292a7f0217996f807fc1fae43aab035bda4c56d4a556038a7603aced3f44c3e6ab9336f6d4f814f2fbaeb60b89c07cdb77c5fc923edb76a
7
+ data.tar.gz: 7cb439bef4a54df1e25239a075ffb694743286925ede586d1049deaf888eef8f2e16c1c744a5d6326e9c79a828b76759da342a05e92f0ca2ed417ad445742463
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+
3
+ https://releases.hashicorp.com/terraform/0.7.13/terraform_0.7.13_linux_amd64.zip
data/bin/yutani ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'yutani'
7
+
8
+ Yutani::Cli::main(ARGV)
data/lib/yutani/cli.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+ require 'guard'
4
+ require 'guard/commander'
5
+
6
+ module Yutani
7
+ class Cli < Thor
8
+ map '-v' => :version, '--version' => :version
9
+ map '--hiera-config-file' => :hiera_config_file
10
+
11
+ def self.main(args)
12
+ begin
13
+ Cli.start(args)
14
+ rescue StandardError => e
15
+ Yutani.logger.fatal "#{e.class.name} #{e.message}"
16
+ Yutani.logger.fatal Yutani::Cli.format_backtrace(e.backtrace) unless e.backtrace.empty?
17
+
18
+ exit 1
19
+ end
20
+ end
21
+
22
+ desc 'build', 'Evaluates the given script and creates terraform files'
23
+ def build(script)
24
+ Yutani.build_from_file(script)
25
+ end
26
+
27
+ # we need to know these things:
28
+ # * the directory to restrict to watching for changes
29
+ # * the script that build should evaluate
30
+ # * the glob - this is hardcoded to *.rb
31
+ desc 'watch', 'Run build upon changes to files/directories'
32
+ def watch(script, script_dir)
33
+ guardfile = <<-EOF
34
+ run_build = proc do
35
+ system("yutani build #{script}")
36
+ end
37
+
38
+ guard :yield, { :run_on_modifications => run_build } do
39
+ watch(%r|^.*\.rb$|)
40
+ end
41
+ EOF
42
+ Guard.start(guardfile_contents: guardfile, watchdir: script_dir, debug: true)
43
+ end
44
+
45
+ desc 'version', 'Prints the current version of Yutani'
46
+ def version
47
+ puts Yutani::VERSION
48
+ end
49
+
50
+ desc 'init', 'Initialize with a basic setup'
51
+ def init
52
+ if File.exists? '.yutani.yml'
53
+ puts ".yutani.yml already exists, skipping initialization"
54
+ else
55
+ File.open('.yutani.yml', 'w+') do |f|
56
+ f.write Yutani::Config::DEFAULTS.to_yaml(indent: 2)
57
+ puts ".yutani.yml created"
58
+ end
59
+
60
+ hiera_dir = Yutani::Config::DEFAULTS['hiera_config'][:yaml][:datadir]
61
+ FileUtils.mkdir hiera_dir unless Dir.exists? hiera_dir
62
+
63
+ common_yml = File.join(hiera_dir, 'common.yaml')
64
+ unless File.exists? common_yml
65
+ File.new(common_yml, 'w+')
66
+ puts "#{common_yml} created"
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def self.format_backtrace(bt)
74
+ "Backtrace: #{bt.join("\n from ")}"
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,34 @@
1
+ module Yutani
2
+ class Config < Hash
3
+ CONFIG_FILE = '.yutani.yml'
4
+
5
+ # Strings rather than symbols are used for compatibility with YAML.
6
+ DEFAULTS = Config[{
7
+ "terraform_dir" => "terraform",
8
+ "hiera_config" => {
9
+ :backends => ["yaml"],
10
+ :hierarchy => ["common"],
11
+ :yaml => {
12
+ :datadir=>"hiera"
13
+ },
14
+ :logger => "noop"
15
+ }
16
+ }]
17
+
18
+ class << self
19
+ # Returns a Configuration filled with defaults and fixed for common
20
+ # problems and backwards-compatibility.
21
+ def from(user_config)
22
+ DEFAULTS.merge Config[user_config]
23
+ end
24
+ end
25
+
26
+ def read_config_file
27
+ if File.exists? CONFIG_FILE
28
+ YAML.load_file(CONFIG_FILE)
29
+ else
30
+ {}
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ require 'rubygems/package'
2
+ require 'fileutils'
3
+
4
+ module Yutani
5
+ # An abstraction of a real directory tree on disk
6
+ # Permits us to decide later whether this will be written to disk
7
+ # or embedded in a tarball, or sent over scp, etc.
8
+ class DirectoryTree
9
+ File = Struct.new(:path, :permissions, :content)
10
+
11
+ attr_reader :files, :prefix
12
+
13
+ def initialize(prefix = './')
14
+ @prefix = prefix
15
+ @files = []
16
+ end
17
+
18
+ def add_file(path, permissions, content)
19
+ @files << File.new(::File.join(@prefix, path), permissions.to_i, content)
20
+ end
21
+
22
+ def to_fs
23
+ @files.each do |f|
24
+ FileUtils.mkdir_p(::File.dirname(f.path))
25
+ ::File.open(f.path, 'w+', f.permissions) do |new_f|
26
+ new_f.write f.content
27
+ end
28
+ end
29
+ end
30
+
31
+ def to_tar(io = STDOUT)
32
+ Gem::Package::TarWriter.new(io) do |tar|
33
+ @files.each do |f|
34
+ tar.mkdir(::File.dirname(f.path), '0755')
35
+
36
+ tar.add_file_simple(f.path, f.permissions, f.content.bytes.size) do |tar_file|
37
+ tar_file.write f.content
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ module Yutani
2
+ class DSLEntity
3
+ attr_accessor :scope
4
+
5
+ def hiera(k)
6
+ Yutani::Hiera.lookup(k, @scope)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ module Yutani
2
+ module Hiera
3
+ class << self
4
+ def hiera(config_override={})
5
+ @hiera ||= init_hiera(config_override)
6
+ end
7
+
8
+ def init_hiera(override={})
9
+ conf = Yutani.config(override)
10
+
11
+ # hiera_config_file trumps hiera_config
12
+ ::Hiera.new(config:
13
+ conf.fetch('hiera_config_file', conf['hiera_config'])
14
+ )
15
+ end
16
+
17
+ def lookup(k, scope)
18
+ # hiera expects strings, not symbols
19
+ hiera_scope = scope.inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
20
+ Yutani.logger.debug "hiera scope: %s" % hiera_scope
21
+
22
+ v = Yutani::Hiera.hiera.lookup(k.to_s, nil, hiera_scope)
23
+ Yutani.logger.warn "hiera couldn't find value for key #{k}" if v.nil?
24
+
25
+ # let us use symbols for hash keys
26
+ Yutani::Utils.convert_nested_hash_to_indifferent_access(v)
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/yutani/mod.rb ADDED
@@ -0,0 +1,321 @@
1
+ require 'json'
2
+ require 'hashie'
3
+ require 'pp'
4
+
5
+ module Yutani
6
+ # Maps to a terraform module. Named 'mod' to avoid confusion with
7
+ # ruby 'module'.
8
+ # It can contain :
9
+ # * other modules
10
+ # * resources
11
+ # * other resources (provider, data, etc)
12
+ # * variables
13
+ # * outputs
14
+ # Its block is evaluated depending upon whether it is enclosed within
15
+ # another module
16
+ # It has the following properties
17
+ # * mandatory name of type symbol
18
+ # * optional scope of type hash
19
+ # * ability to output a tar of its contents
20
+ class Mod < DSLEntity
21
+ attr_accessor :name, :resources, :providers, :block, :mods, :params, :outputs, :variables
22
+
23
+ def initialize(name, parent, local_scope, parent_scope, &block)
24
+ @name = name.to_sym
25
+
26
+ @scope = parent_scope.merge(local_scope)
27
+ @scope[:module_name] = name
28
+ @local_scope = local_scope
29
+ @parent = parent
30
+
31
+ @mods = []
32
+ @resources = []
33
+ @providers = []
34
+ @outputs = {}
35
+ @params = {}
36
+ @variables = {}
37
+
38
+ instance_eval &block
39
+ end
40
+
41
+ def mod(name, **scope, &block)
42
+
43
+ @mods << Mod.new(name, self, scope, @scope, &block)
44
+ end
45
+
46
+ def source(path)
47
+ absolute_path = File.expand_path(path, File.dirname(Yutani.entry_path))
48
+ contents = File.read absolute_path
49
+
50
+ instance_eval contents, path
51
+ end
52
+
53
+ def resources_hash
54
+ @resources.inject({}) do |r_hash, r|
55
+ r_hash[r.resource_type] ||= {}
56
+ r_hash[r.resource_type][r.resource_name] = r
57
+ r_hash
58
+ end
59
+ end
60
+
61
+ def debug
62
+ resolve_references!(self)
63
+ #pp @mods.unshift(self).map{|m| m.to_h }
64
+ end
65
+
66
+ class MyHash < Hash
67
+ include Hashie::Extensions::DeepMerge
68
+ end
69
+
70
+ # this generates the contents of *.tf.main
71
+ def to_h
72
+ h = {
73
+ module: @mods.inject({}) {|modules,m|
74
+ modules[m.tf_name] = {}
75
+ modules[m.tf_name][:source] = m.dir_path
76
+ modules[m.tf_name].merge! m.params
77
+ modules
78
+ },
79
+ resource: @resources.inject(MyHash.new){|resources,r|
80
+ resources.deep_merge(r.to_h)
81
+ },
82
+ provider: @providers.inject(MyHash.new){|providers,r|
83
+ providers.deep_merge(r.to_h)
84
+ },
85
+ output: @outputs.inject({}){|outputs,(k,v)|
86
+ outputs[k] = { value: v }
87
+ outputs
88
+ },
89
+ variable: @variables.inject({}){|variables,(k,v)|
90
+ variables[k] = {}
91
+ variables
92
+ }
93
+ }
94
+
95
+ # terraform doesn't like empty output and variable collections
96
+ h.delete_if {|_,v| v.empty? }
97
+ end
98
+
99
+ def tf_name
100
+ dirs = @local_scope.values.map{|v| v.to_s.gsub('-', '_') }
101
+ dirs.unshift(name)
102
+ dirs.join('_')
103
+ end
104
+
105
+ def dir_path
106
+ tf_name
107
+ end
108
+
109
+ def resource(resource_type, identifiers, **scope, &block)
110
+ merged_scope = @scope.merge(scope)
111
+ @resources <<
112
+ Resource.new(resource_type, identifiers, merged_scope, &block)
113
+ end
114
+
115
+ def provider(provider_name, **scope, &block)
116
+ merged_scope = @scope.merge(scope)
117
+ @providers <<
118
+ Provider.new(provider_name, merged_scope, &block)
119
+ end
120
+
121
+ def pretty_json
122
+ JSON.pretty_generate(to_h)
123
+ end
124
+
125
+ def children
126
+ @mods
127
+ end
128
+
129
+ def descendents
130
+ children + children.map{|c| c.descendents}.flatten
131
+ end
132
+
133
+ def parent?(mod)
134
+ @parent.name == mod.name
135
+ end
136
+
137
+ # given name of mod, return bool
138
+ def child?(mod)
139
+ children.map{|m| m.name }.include? mod.name
140
+ end
141
+
142
+ def child_by_name(name)
143
+ children.find{|child| child.name == name.to_sym}
144
+ end
145
+
146
+ def path
147
+ File.join(@parent.path, name.to_s)
148
+ end
149
+
150
+ class InvalidReferencePathException < StandardError; end
151
+
152
+ # rel_path: relative path to a target mod
153
+ # ret an array of mods tracing that path
154
+ # sorted from target -> source
155
+ # mods: array of module objects, which after being built is returned
156
+ # path: array of path strings: i.e. [.. .. .. a b c]
157
+
158
+ def generate_pathway(mods, path)
159
+ curr = path.shift
160
+
161
+ case curr
162
+ when /[a-z]+/
163
+ child = child_by_name(curr)
164
+ if child.nil?
165
+ raise InvalidReferencePathException, "no such module #{curr}"
166
+ else
167
+ child.generate_pathway(mods.unshift(self), path)
168
+ end
169
+ when '..'
170
+ if @parent.nil?
171
+ raise InvalidReferencePathException, "no such module #{curr}"
172
+ else
173
+ @parent.generate_pathway(mods.unshift(self), path)
174
+ end
175
+ when nil
176
+ return mods.unshift(self)
177
+ else
178
+ raise InvalidReferencePathException, "invalid path component: #{curr}"
179
+ end
180
+ end
181
+
182
+ # recursive linked-list function, propagating a variable
183
+ # from a target module to a source module
184
+ # seed var with array of [type,name,attr]
185
+ def propagate(prev, nxt, var)
186
+ if prev.empty?
187
+ # we are the source module
188
+ if nxt.empty?
189
+ # src and target in same mod
190
+ "${%s}" % var.join('.')
191
+ else
192
+ if self.child? nxt.first
193
+ # there is no 'composition' mod,
194
+ new_var = [self.name, var].flatten
195
+ nxt.first.params[new_var.join('_')] = "${%s}" % new_var.join('.')
196
+ nxt.first.variables[new_var] = ''
197
+
198
+ nxt.shift.propagate(prev.push(self), nxt, var)
199
+ elsif self.parent?(nxt.first)
200
+ # we are propagating upward the variable
201
+ self.outputs[var.join('_')] = "${%s}" % var.join('.')
202
+ nxt.shift.propagate(prev.push(self), nxt, var.join('_'))
203
+ else
204
+ raise "Propagation error!"
205
+ end
206
+ end
207
+ else
208
+ if nxt.empty?
209
+ # we're the source module
210
+ if self.child? prev.last
211
+ # it's been propagated 'up' to us
212
+ "${module.%s.%s}" % [prev.last.name, var]
213
+ elsif self.parent? prev.last
214
+ # it's been propagated 'down' to us
215
+ "${var.%s}" % var
216
+ else
217
+ raise "Propagation error!"
218
+ end
219
+ else
220
+ if self.child? prev.last and self.child? nxt.first
221
+ # we're a 'composition' module; the common ancestor
222
+ # to source and target modules
223
+ new_var = [prev.last.name, var]
224
+ nxt.first.params[new_var.join('_')] = "${module.%s.%s}" % new_var
225
+ nxt.first.variables[new_var.join('_')] = ""
226
+
227
+ nxt.shift.propagate(prev.push(self), nxt, new_var.join('_'))
228
+ elsif self.child? prev.last and self.parent? nxt.first
229
+ # we're propagating 'upward' the variable
230
+ # towards the common ancestor
231
+
232
+ new_var = [prev.last.name, var]
233
+ self.outputs[new_var.join('_')] = "${module.%s.%s}" % new_var
234
+
235
+ nxt.shift.propagate(prev.push(self), nxt, new_var.join('_'))
236
+ elsif self.parent? prev.last and self.parent? nxt.first
237
+ # we cannot be a child to two parents in a tree!
238
+ raise "Progation error!"
239
+ elsif self.parent? prev.last and self.child? nxt.first
240
+ nxt.first.params[var] = "${var.%s}" % var
241
+ nxt.first.variables[var] = ""
242
+
243
+ nxt.shift.propagate(prev.push(self), nxt, var)
244
+ else
245
+ raise "Propagation error!"
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ def resolve_references!
252
+ @resources.each do |r|
253
+ r.resolve_references! do |ref|
254
+
255
+ matching_resources = []
256
+
257
+ path_components = ref.relative_path(self).split('/')
258
+ mod_path = generate_pathway([], path_components)
259
+ target_mod = mod_path.shift
260
+
261
+ # lookup matching resources in mod_path.first
262
+ matches = ref.find_matching_resources_in_module!(target_mod)
263
+
264
+ if matches.empty?
265
+ raise ReferenceException,
266
+ "no matching resources found in mod #{target_mod.name}"
267
+ end
268
+
269
+ interpolation_strings = matches.map do |res|
270
+ ra = ResourceAttribute.new(res.resource_type, res.resource_name, ref.attr)
271
+ # clone mod_path, because propagate() will alter it
272
+ target_mod.propagate([], mod_path.clone, [ra.type, ra.name, ra.attr])
273
+ end
274
+ interpolation_strings.length == 1 ? interpolation_strings[0] :
275
+ interpolation_strings
276
+ end
277
+ end
278
+
279
+ children.each do |m|
280
+ m.resolve_references!
281
+ end
282
+ end
283
+
284
+ def tar(filename)
285
+ # ideally, this needs to be done automatically as part of to_h
286
+ resolve_references!
287
+
288
+ File.open(filename, 'w+') do |tarball|
289
+ create_dir_tree('./').to_tar(tarball)
290
+ end
291
+ end
292
+
293
+ def to_fs(prefix='./terraform')
294
+ # ideally, this needs to be done automatically as part of to_h
295
+ resolve_references!
296
+
297
+ create_dir_tree(prefix).to_fs
298
+ end
299
+
300
+ def create_dir_tree(prefix)
301
+ dir_tree(DirectoryTree.new(prefix), '')
302
+ end
303
+
304
+ def dir_tree(dt, prefix)
305
+ full_dir_path = File.join(prefix, self.dir_path)
306
+ main_tf_path = File.join(full_dir_path, 'main.tf.json')
307
+
308
+ dt.add_file(
309
+ main_tf_path,
310
+ 0644,
311
+ self.pretty_json
312
+ )
313
+
314
+ mods.each do |m|
315
+ m.dir_tree(dt, full_dir_path)
316
+ end
317
+
318
+ dt
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module Yutani
4
+ class Provider < DSLEntity
5
+ attr_accessor :provider_name, :fields
6
+
7
+ def initialize(provider_name, **scope, &block)
8
+ @provider_name = provider_name
9
+ @scope = HashWithIndifferentAccess.new(scope)
10
+ @fields = {}
11
+
12
+ instance_eval &block if block_given?
13
+ end
14
+
15
+ def []=(k,v)
16
+ @fields[k] = v
17
+ end
18
+
19
+ def to_h
20
+ {
21
+ @provider_name => @fields
22
+ }
23
+ end
24
+
25
+ def method_missing(name, *args, &block)
26
+ if block_given?
27
+ raise StandardError,
28
+ "provider properties do not accept blocks as parameters"
29
+ else
30
+ @fields[name] = args.first
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ require 'pathname'
2
+
3
+ module Yutani
4
+ class Reference
5
+
6
+ attr_reader :path
7
+
8
+ def initialize(path='.', t, n, a)
9
+ @path = path
10
+ @t = t
11
+ @n = n
12
+ @a = a
13
+ end
14
+
15
+ def relative_path(source_mod)
16
+ source_path = Pathname.new(source_mod.path)
17
+ target_path = Pathname.new(@path)
18
+ target_path.relative_path_from(source_path).to_s
19
+ end
20
+
21
+ def resource_type
22
+ @t
23
+ end
24
+
25
+ def resource_name
26
+ @n
27
+ end
28
+
29
+ def attr
30
+ @a
31
+ end
32
+
33
+ def find_matching_resources_in_module!(mod)
34
+ resolutions = []
35
+ # we currently support strings for t n and a, and regex
36
+ # for n only
37
+ mod.resources.each do |resource|
38
+ if resource.resource_type == self.resource_type.to_sym
39
+ case self.resource_name
40
+ when Regexp
41
+ if resource.resource_name =~ self.resource_name
42
+ resolutions.push(resource)
43
+ end
44
+ when Symbol
45
+ if resource.resource_name == self.resource_name.to_sym
46
+ resolutions.push(resource)
47
+ end
48
+ else
49
+ raise "unsupported class #{self.resource_name.class}"
50
+ end
51
+ end
52
+ end
53
+
54
+ resolutions
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ module Yutani
2
+ class ReferencePath
3
+ def initialize(mods, resource_type, resource_name, attr)
4
+ @mods = mods
5
+ @resource_type = resource_type
6
+ @resource_name = resource_name
7
+ @attr = attr
8
+ end
9
+
10
+ def interpolation_string(prefix=nil, mods=[])
11
+ resource_params = [@resource_type, @resource_name, @attr]
12
+ if prefix.nil?
13
+ # we're referencing a resource in the local module
14
+ resource_params.join('.')
15
+ else
16
+ resource_params.join('_')
17
+ end
18
+ end
19
+
20
+ def shift(prev, curr, nxt)
21
+ traverse(prev.push(curr), nxt.shift, nxt)
22
+ end
23
+
24
+ def traverse(prev,
25
+ shift(prev, curr, nxt)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,72 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module Yutani
4
+ class Resource < DSLEntity
5
+ attr_accessor :resource_type, :resources, :fields, :mods, :resource_name
6
+
7
+ def initialize(resource_type, resource_name, **scope, &block)
8
+ @resource_type = resource_type
9
+ @resource_name = resource_name
10
+ @scope = HashWithIndifferentAccess.new(scope)
11
+ @fields = {}
12
+
13
+ instance_eval &block if block_given?
14
+ end
15
+
16
+ def []=(k,v)
17
+ @fields[k] = v
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ @resource_type => {
23
+ @resource_name => @fields
24
+ }
25
+ }
26
+ end
27
+
28
+ def ref(m='.', t, n, a)
29
+ Reference.new(m, t, n, a)
30
+ end
31
+
32
+ def resolve_references!(&block)
33
+ @fields.each do |k,v|
34
+ case v
35
+ when Reference
36
+ @fields[k] = yield v
37
+ when SubResource
38
+ v.fields.each do |k,v|
39
+ if v.is_a? Reference
40
+ v.fields[k] = yield v
41
+ end
42
+ end
43
+ else
44
+ next
45
+ end
46
+ end
47
+ end
48
+
49
+ def method_missing(name, *args, &block)
50
+ if block_given?
51
+ sub = SubResource.new(scope)
52
+ sub.instance_exec(&block)
53
+ @fields[name] = sub.fields
54
+ else
55
+ # remove leading '_' if present.
56
+ # DSL users have to do prefix with underscore when they want to use
57
+ # a resource property that has same name as an existing ruby method
58
+ # i.e. 'timeout'
59
+ @fields[name] = args.first.to_s.sub(/^_/, '')
60
+ end
61
+ end
62
+ end
63
+
64
+ class SubResource < Resource
65
+ def initialize(scope)
66
+ @scope = scope
67
+ @fields = {}
68
+ end
69
+ end
70
+
71
+ ResourceAttribute = Struct.new(:type, :name, :attr)
72
+ end
@@ -0,0 +1,22 @@
1
+ module Yutani
2
+ # a stack is a terraform module with
3
+ # additional properties:
4
+ # * module name is hardcoded to 'root'
5
+ # * can only be found at top-level (it's an error if found within another stack/module)
6
+ # * because it's the top-level module, it's immediately evaluated
7
+ # * ability to configure remote state
8
+ class Stack < Mod
9
+
10
+ def initialize(name, **scope, &block)
11
+ super(name, nil, scope, {stack_name: name}, &block)
12
+ end
13
+
14
+ def path
15
+ "/root"
16
+ end
17
+
18
+ def [](name)
19
+ descendents.find{|d| d.name == name}
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module Yutani
4
+ module Utils
5
+ class << self
6
+ def convert_nested_hash_to_indifferent_access(v)
7
+ case v
8
+ when Array
9
+ v.map{|i| convert_nested_hash_to_indifferent_access(i) }
10
+ when Hash
11
+ HashWithIndifferentAccess.new(v)
12
+ else
13
+ v
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Yutani
2
+ VERSION = '0.0.2'
3
+ end
data/lib/yutani.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'hiera'
2
+ require 'hashie'
3
+ require 'logger'
4
+ require 'yutani/version'
5
+ require 'yutani/config'
6
+ require 'yutani/hiera'
7
+ require 'yutani/cli'
8
+ require 'yutani/dsl_entity'
9
+ require 'yutani/reference'
10
+ require 'yutani/directory_tree'
11
+ require 'yutani/mod'
12
+ require 'yutani/stack'
13
+ require 'yutani/resource'
14
+ require 'yutani/provider'
15
+ require 'yutani/utils'
16
+
17
+ module Yutani
18
+
19
+ @stacks = []
20
+
21
+ class << self
22
+ attr_accessor :hiera, :stacks, :logger, :entry_path
23
+ end
24
+
25
+ class << self
26
+ def logger
27
+ @logger ||= (
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger.const_get(ENV.fetch('LOG_LEVEL', 'INFO'))
30
+ logger
31
+ )
32
+ end
33
+
34
+ def stack(name, **scope, &block)
35
+ s = Stack.new(name, **scope, &block)
36
+ @stacks << s
37
+ s
38
+ end
39
+
40
+ def config(override = {})
41
+ config = Config.new
42
+ override = Config[override]
43
+
44
+ config = config.read_config_file
45
+
46
+ # Merge DEFAULTS < .yutani.yml < override
47
+ Config.from(config.merge(override))
48
+ end
49
+
50
+ def build_from_file(file)
51
+ Yutani.entry_path = file
52
+
53
+ instance_eval(File.read(file), file)
54
+
55
+ unless stacks.empty?
56
+ stacks.each {|s| s.to_fs}
57
+ end
58
+ end
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,187 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yutani
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Louis Garman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hashie
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.4.3
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 3.4.3
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: 3.4.3
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.4.3
47
+ - !ruby/object:Gem::Dependency
48
+ name: hiera
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.2.1
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '3.2'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.2.1
67
+ - !ruby/object:Gem::Dependency
68
+ name: thor
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: 0.19.1
74
+ type: :runtime
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: 0.19.1
81
+ - !ruby/object:Gem::Dependency
82
+ name: guard
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: 2.14.0
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: 2.14.0
95
+ - !ruby/object:Gem::Dependency
96
+ name: guard-yield
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: rake
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: aruba
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: 0.14.2
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: 0.14.2
137
+ description: Generates JSON for Terraform
138
+ email: louisgarman+yutani@gmail.com
139
+ executables:
140
+ - yutani
141
+ extensions: []
142
+ extra_rdoc_files: []
143
+ files:
144
+ - bin/install-terraform.sh
145
+ - bin/yutani
146
+ - lib/yutani.rb
147
+ - lib/yutani/cli.rb
148
+ - lib/yutani/config.rb
149
+ - lib/yutani/directory_tree.rb
150
+ - lib/yutani/dsl_entity.rb
151
+ - lib/yutani/hiera.rb
152
+ - lib/yutani/mod.rb
153
+ - lib/yutani/provider.rb
154
+ - lib/yutani/reference.rb
155
+ - lib/yutani/reference_path.rb
156
+ - lib/yutani/resource.rb
157
+ - lib/yutani/stack.rb
158
+ - lib/yutani/utils.rb
159
+ - lib/yutani/version.rb
160
+ homepage: https://github.com/leg100/yutani
161
+ licenses:
162
+ - MIT
163
+ metadata: {}
164
+ post_install_message:
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '2.3'
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: 2.3.1
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubyforge_project:
183
+ rubygems_version: 2.5.1
184
+ signing_key:
185
+ specification_version: 4
186
+ summary: Terraform DSL
187
+ test_files: []