terraform_dsl 0.0.4

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: d5ba050edd5f77df1afad2a171fe81b140f1ad09
4
+ data.tar.gz: eb7d97ed6e27a68abac7a3c63607f67c6c12da0f
5
+ SHA512:
6
+ metadata.gz: 2b9e2eda30ddc8943012088c07bb53718daba7ac23a3aaebe3233c528ee39e732dc0fa8ca681d5b3386209a3d3c53e70605cad60eb53c267963dfe688274830f
7
+ data.tar.gz: 8b1eda39d07c1921b212ea16e197d42ef2c8d9e87451b0ebb4f1ada4dc38c252d936862769ce3f18f2603c6c52e7b79b193440caaf8b05cb037b10f4d46aa110
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+
3
+ module Terraform
4
+ # Loads all rake tasks when terraform_dsl is included by a rake script
5
+ class Tasks
6
+ def self.loadall
7
+ Dir.glob("#{File.join(File.dirname(__dir__), 'tasks')}/*.rake").each { |r| load r }
8
+ template_path = ENV['TERRAFORM_TEMPLATE_PATH'] || 'templates'
9
+ Dir.glob("#{File.join(Dir.pwd,template_path)}/*.rb").each { |t| require_relative t }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,126 @@
1
+ require_relative '../terraform_dsl.rb'
2
+ namespace 'terraform' do
3
+ desc 'Loads available stack modules'
4
+ task :load do
5
+ Terraform::Stacks.load
6
+ end
7
+
8
+ desc 'Initialize stacks.yml if it does not exist'
9
+ task :init do
10
+ Terraform::Stacks.init
11
+ end
12
+
13
+ desc 'Fetch modules for a stack'
14
+ task :get, [:name] => :load do |_t, args|
15
+ stack = Terraform::Stacks.stacks[args[:name]]
16
+ stack.get
17
+ end
18
+
19
+ desc 'Fetch modules for all stacks'
20
+ task get_all: :load do
21
+ Terraform::Stacks.stacks.each do |_name, stack|
22
+ stack.get
23
+ end
24
+ end
25
+
26
+ desc 'List all available stacks'
27
+ task list: :load do
28
+ Terraform::Stacks.stacks.each do |name, _stack|
29
+ puts name
30
+ end
31
+ end
32
+
33
+ desc 'Show details about a stack by name'
34
+ task :show, [:name] => :load do |_t, args|
35
+ fail 'Specify a name' unless args[:name]
36
+ stack = Terraform::Stacks.stacks[args[:name]]
37
+ puts stack
38
+ stack.plan
39
+ end
40
+
41
+ desc 'Show all pending stack changes'
42
+ task show_all: [:load, :get_all] do
43
+ Terraform::Stacks.stacks.each do |_name, stack|
44
+ puts stack
45
+ stack.plan
46
+ end
47
+ end
48
+
49
+ desc 'Show pending changes for a stack'
50
+ task :plan, [:name] => :load do |_t, args|
51
+ fail 'Specify a name' unless args[:name]
52
+ stack = Terraform::Stacks.stacks[args[:name]]
53
+ stack.plan
54
+ end
55
+
56
+ desc 'Apply pending changes for a stack'
57
+ task :apply, [:name] => :load do |_t, args|
58
+ fail 'Specify a name' unless args[:name]
59
+ stack = Terraform::Stacks.stacks[args[:name]]
60
+ stack.apply
61
+ end
62
+
63
+ desc 'Apply all pending stack changes'
64
+ task apply_all: :load do
65
+ Terraform::Stacks.stacks.each do |_name, stack|
66
+ puts stack
67
+ stack.apply
68
+ end
69
+ end
70
+
71
+ desc 'Destroy a stack'
72
+ task :destroy, [:name] => :load do |_t, args|
73
+ fail 'Specify a name' unless args[:name]
74
+ stack = Terraform::Stacks.stacks[args[:name]]
75
+ stack.destroy
76
+ end
77
+
78
+ desc 'Push stack state to remote location'
79
+ task :push, [:name] => [:load, :remote_config] do |_t, args|
80
+ stack = Terraform::Stacks.stacks[args[:name]]
81
+ stack.push
82
+ end
83
+
84
+ desc 'Push stack states to remote location'
85
+ task push_all: [:load, :remote_config_all] do
86
+ Terraform::Stacks.stacks.each do |_name, stack|
87
+ puts stack
88
+ stack.push
89
+ end
90
+ end
91
+
92
+ desc 'Pulls stack state from remote location'
93
+ task :pull, [:name] => [:load, :remote_config] do |_t, args|
94
+ stack = Terraform::Stacks.stacks[args[:name]]
95
+ stack.pull
96
+ end
97
+
98
+ desc 'Pulls stack states from remote location'
99
+ task pull_all: [:load, :remote_config_all] do
100
+ Terraform::Stacks.stacks.each do |_name, stack|
101
+ puts stack
102
+ stack.pull
103
+ end
104
+ end
105
+
106
+ desc 'Sets up remote config for a stack'
107
+ task :remote_config, [:name] => [:load] do |_t, args|
108
+ stack = Terraform::Stacks.stacks[args[:name]]
109
+ stack.remote_config
110
+ end
111
+
112
+ desc 'Sets up remote config for all stacks that support it'
113
+ task remote_config_all: :load do
114
+ Terraform::Stacks.stacks.each do |_name, stack|
115
+ puts stack
116
+ stack.remote_config
117
+ end
118
+ end
119
+
120
+ desc 'Generate a UUID for a stack, so stacks do not clobber each other'
121
+ task :uuid do
122
+ uuid = Terraform::Stacks.uuid
123
+ puts "uuid: #{uuid}"
124
+ puts 'Add this as a field to a new stack to prevent clobbering stacks with the same name'
125
+ end
126
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'terraform_dsl/stack.rb'
2
+ require_relative 'terraform_dsl/stacks.rb'
3
+ require_relative 'terraform_dsl/stack_module.rb'
4
+ require_relative 'tasks/tasks.rb' if defined? Rake
@@ -0,0 +1,20 @@
1
+ module Terraform
2
+ module Aws
3
+ # Support AWS S3 remote state backend
4
+ module Remote_S3
5
+ private
6
+
7
+ def options_for_s3(command)
8
+ options = ''
9
+ options = "-backend=s3 #{config_opts_s3}" if command == :config
10
+ "terraform remote #{command} #{options}"
11
+ end
12
+
13
+ def config_opts_s3
14
+ config = "-backend-config='key=#{@stack.stack_id}' "
15
+ config += @stack.remote['s3'].map { |k, v| "-backend-config='#{k}=#{v}'" }.join(' ')
16
+ config
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module Terraform
2
+ # Aws provider-specific functionality, to be mixed in to stack elements
3
+ module Aws
4
+ def self.taggable?(resource_type)
5
+ supports_tagging = [:aws_autoscaling_group, :aws_customer_gateway,
6
+ :aws_db_instance, :aws_elasticache_cluster,
7
+ :aws_elb, :aws_instance, :aws_internet_gateway,
8
+ :aws_network_acl, :aws_network_interface, :aws_route53_zone,
9
+ :aws_route_table, :aws_s3_bucket, :aws_security_group,
10
+ :aws_subnet, :aws_vpc, :aws_vpc_dhcp_options,
11
+ :aws_vpc_peering_connection, :aws_vpn_connection,
12
+ :aws_vpn_gateway]
13
+ supports_tagging.include?(resource_type.to_sym)
14
+ end
15
+
16
+ # Tag all resources (that support tagging) that we created with this stack id
17
+ def post_processing_aws
18
+ return unless Aws.taggable?(@resource_type)
19
+ @fields[:tags] ||= {}
20
+ @fields[:tags][:terraform_stack_id] = var(:terraform_stack_id)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,58 @@
1
+ module Terraform
2
+ # Wraps terraform command execution
3
+ class Command
4
+ attr_accessor :command
5
+
6
+ def initialize(stack, command)
7
+ @stack = stack
8
+ @command = options_for(command)
9
+ prepare
10
+ end
11
+
12
+ def env
13
+ vars = {}
14
+ @stack.variables.each { |k, v| vars["TF_VAR_#{k}"] = v[:default] }
15
+ @stack.module.secrets.each do |_provider, secrets|
16
+ secrets.each do |k, v|
17
+ vars[k.to_s] = v
18
+ end
19
+ end
20
+ vars
21
+ end
22
+
23
+ def execute
24
+ Process.waitpid(spawn(env, @command, chdir: @stack.stack_dir))
25
+ fail 'Command failed' unless $CHILD_STATUS.to_i == 0
26
+ end
27
+
28
+ protected
29
+
30
+ def prepare
31
+ FileUtils.mkdir_p(@stack.stack_dir)
32
+ File.open(File.join(@stack.stack_dir, 'terraform.tf.json'), 'w') { |f| f.write(generate) }
33
+ end
34
+
35
+ def options_for(cmd)
36
+ options = begin
37
+ case cmd
38
+ when :apply
39
+ '-input=false'
40
+ when :destroy
41
+ '-input=false -force'
42
+ when :plan
43
+ '-input=false -module-depth=-1'
44
+ when :graph
45
+ '-draw-cycles'
46
+ else
47
+ ''
48
+ end
49
+ end
50
+
51
+ "terraform #{cmd} #{options}"
52
+ end
53
+
54
+ def generate
55
+ @stack.module.generate
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'stack_element'
2
+ require_relative 'stack_modules'
3
+ require_relative 'stack_module'
4
+ require_relative 'stacks'
5
+
6
+ module Terraform
7
+ class Module < StackElement
8
+ def initialize(parent_module, module_name, &block)
9
+ super(parent_module, &block)
10
+
11
+ if @fields[:source].match(/^module_/)
12
+ build_submodule(parent_module, module_name)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def build_submodule(parent_module, module_name)
19
+ generated = generate_child_module(parent_module)
20
+ module_path = File.join(Stacks.dir, parent_module.id, module_name.to_s)
21
+ FileUtils.mkdir_p(module_path)
22
+ File.open(File.join(module_path, 'stack_module.tf.json'),'w') { |f| f.write(generated) }
23
+ @fields[:source] = File.expand_path(module_path)
24
+ end
25
+
26
+ def generate_child_module(parent_module)
27
+ variables = @fields.clone
28
+ variables.delete(:source)
29
+ variables[:terraform_stack_id] = parent_module.id
30
+ child_name = @fields[:source].gsub(/^module_/,'')
31
+ child_module = StackModules.get(child_name)
32
+ child_module.build(variables)
33
+ child_module.generate
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ require_relative 'stack_element.rb'
2
+
3
+ module Terraform
4
+ class Output < StackElement
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'secrets.rb'
2
+ require_relative 'stack_element.rb'
3
+
4
+ module Terraform
5
+ # Implements DSL for a terraform provider, and a means of loading secrets.
6
+ class Provider < StackElement
7
+ def load_secrets(name)
8
+ @secrets ||= {}
9
+ @secrets[name.to_sym] = SECRETS[name.to_sym]
10
+ @secrets
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'aws/remote_s3.rb'
2
+
3
+ module Terraform
4
+ class Remote < Command
5
+ class RemoteNotSupported < Exception; end
6
+ include Aws::Remote_S3
7
+ def initialize(stack, command)
8
+ super
9
+ unless @stack.remote.first
10
+ puts "\tWARNING: #{@stack.name} is not configured with a remote backend"
11
+ return
12
+ end
13
+
14
+ backend = @stack.remote.keys.first
15
+ sym = "options_for_#{backend}"
16
+
17
+ if self.respond_to?(sym, include_private: true)
18
+ @command = send(sym, command)
19
+ else
20
+ fail RemoteNotSupported, "Remote backend #{backend} is not supported yet"
21
+ end
22
+ end
23
+
24
+ def execute
25
+ return unless @stack.remote.first
26
+ super
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'stack_element.rb'
2
+
3
+ module Terraform
4
+ # Defines a terraform resource
5
+ class Resource < StackElement
6
+ attr_reader :resource_name
7
+
8
+ def initialize(parent_module, resource_name, resource_type, &block)
9
+ @resource_name = resource_name
10
+ @resource_type = resource_type
11
+ super(parent_module, &block)
12
+
13
+ # Allow provider specific post processing
14
+ sym = "post_processing_#{resource_type.split('_').first}"
15
+ send(sym) if self.respond_to?(sym, include_private: true)
16
+ end
17
+
18
+ # Allow provisioner blocks to be nested within resources
19
+ def provisioner(provisioner_type, &block)
20
+ provisioner_type = provisioner_type.to_sym
21
+
22
+ @fields[:provisioner] = @fields[:provisioner] || []
23
+
24
+ provisioner_set = Provisioner.new(@module, &block)
25
+ @fields[:provisioner] << { cleanup_provisioner_type(provisioner_type) => provisioner_set.fields }
26
+ end
27
+
28
+ private
29
+
30
+ def cleanup_provisioner_type(provisioner_type)
31
+ case provisioner_type.to_sym
32
+ when :remote_exec
33
+ 'remote-exec'
34
+ when :local_exec
35
+ 'local-exec'
36
+ else
37
+ provisioner_type
38
+ end
39
+ end
40
+ end
41
+ # Defines a terraform resource provisioner
42
+ class Provisioner < StackElement
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+ require 'open3'
3
+
4
+ # Load and provide access to secrets required by terraform providers
5
+ class SecretHash < Hash
6
+ class SecretNotFound < Exception; end
7
+
8
+ def initialize
9
+ super { |_secrets, key| fail SecretNotFound, "Secret `#{key}` not found" }
10
+ end
11
+ end
12
+
13
+ if ENV['TERRAFORM_ENV'] == 'test'
14
+ SECRETS ||= {
15
+ aws: {
16
+ access_key_id: 'foo',
17
+ secret_access_key: 'bar'
18
+ }
19
+ }
20
+ else
21
+ SECRETS ||= begin
22
+ secrets_file = File.expand_path(ENV['CONFIG_PATH'] || 'config/secrets.json')
23
+ if File.exist?(secrets_file)
24
+ JSON.parse(File.read(secrets_file), symbolize_names: true, object_class: SecretHash)
25
+ else
26
+ {}
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,79 @@
1
+ require_relative 'stacks'
2
+ require_relative 'stack_modules'
3
+ require_relative 'command'
4
+ require_relative 'remote'
5
+
6
+ module Terraform
7
+ # Wrapper to instantiate a stack from a yaml definition
8
+ class Stack
9
+ class MalformedConfig < Exception; end
10
+ class << self
11
+ def load(config)
12
+ fail MalformedConfig, "Configuration malformed at #{config}" unless config.is_a?(Hash)
13
+ fail MalformedConfig, "A name must be specified for the stack #{config}" unless config.key?('name')
14
+ fail MalformedConfig, 'You must specify a uuid. Get one from rake uuid and add it to the config' unless config.key?('uuid')
15
+ new(config)
16
+ end
17
+ end
18
+
19
+ attr_reader :name, :description, :module,
20
+ :stack_dir, :stack_id, :remote
21
+
22
+ def initialize(config)
23
+ @name = config['name']
24
+ @uuid = config['uuid']
25
+ @description = config['description'] || ''
26
+ @variables = config['variables'] || {}
27
+ @remote = config['remote'] || {}
28
+ @stack_id = "terraform_#{@name}_#{@uuid}"
29
+ @module = StackModules.get(config['root'])
30
+ @variables['terraform_stack_id'] = @stack_id
31
+ @stack_dir = File.join(Stacks.dir, @stack_id)
32
+ @module.build(@variables.map { |k, v| [k.to_sym, v] }.to_h)
33
+ end
34
+
35
+ def apply
36
+ Command.new(self, :apply).execute
37
+ end
38
+
39
+ def destroy
40
+ Command.new(self, :destroy).execute
41
+ end
42
+
43
+ def plan
44
+ Command.new(self, :plan).execute
45
+ end
46
+
47
+ def get
48
+ Command.new(self, :get).execute
49
+ end
50
+
51
+ def show
52
+ Command.new(self, :show).execute
53
+ end
54
+
55
+ def pull
56
+ Remote.new(self, :pull).execute
57
+ end
58
+
59
+ def push
60
+ Remote.new(self, :pull).execute
61
+ end
62
+
63
+ def remote_config
64
+ Remote.new(self, :config).execute
65
+ end
66
+
67
+ def variables
68
+ @module.variables
69
+ end
70
+
71
+ def to_s
72
+ <<-eos
73
+ Name: #{@name}
74
+ Description: #{@description}
75
+ Stack Directory: #{@stack_dir}
76
+ eos
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,62 @@
1
+ require_relative 'aws/tagging.rb'
2
+
3
+ module Terraform
4
+ # Defines a single terraform stack element, subclass for any element defined in terraform DSL
5
+ class StackElement
6
+ include Aws
7
+ attr_reader :fields
8
+
9
+ def initialize(stack_module, &block)
10
+ @module = stack_module
11
+ @fields = {}
12
+ instance_eval(&block)
13
+ end
14
+
15
+ private
16
+
17
+ # Allows resource attributes to be specified with a nice syntax
18
+ # If the method is implemented, it will be treated as a nested resource
19
+ def method_missing(method_name, *args, &block)
20
+ symbol = method_name.to_sym
21
+ if args.length == 1
22
+ if args[0].nil?
23
+ fail "Passed nil to '#{method_name}'. Generally disallowed, subclass StackElement if you need this."
24
+ end
25
+ add_field(symbol, args[0])
26
+ else
27
+ add_field(symbol, Terraform::StackElement.new(@module, &block).fields)
28
+ end
29
+ end
30
+
31
+ # Get the runtime value of a variable
32
+ def get(variable_name)
33
+ @module.get(variable_name)
34
+ end
35
+
36
+ # Reference a variable
37
+ def var(variable_name)
38
+ "${var.#{variable_name}}"
39
+ end
40
+
41
+ # Syntax to handle interpolation of resource variables
42
+ def value_of(resource_type, resource_name, value_type)
43
+ "${#{resource_type}.#{resource_name}.#{value_type}}"
44
+ end
45
+
46
+ # Shorthand to interpolate the ID of another resource
47
+ def id_of(resource_type, resource_name)
48
+ value_of(resource_type, resource_name, :id)
49
+ end
50
+
51
+ def add_field(symbol, value)
52
+ existing = @fields[symbol]
53
+ if existing
54
+ # If it's already an array, just push to it
55
+ @fields[symbol] = [existing] unless existing.is_a?(Array)
56
+ @fields[symbol] << value
57
+ else
58
+ @fields[symbol] = value
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,123 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+
4
+ require_relative 'stack_modules.rb'
5
+ require_relative 'resource.rb'
6
+ require_relative 'provider.rb'
7
+ require_relative 'variable.rb'
8
+ require_relative 'module.rb'
9
+ require_relative 'output.rb'
10
+
11
+ module Terraform
12
+
13
+ class StackModule
14
+
15
+ class << self
16
+ def define(name, &block)
17
+ template = self.new(name, &block)
18
+ StackModules.register(name, template)
19
+ end
20
+
21
+ def flatten_variable_arrays(variables)
22
+ variables.map do |k,v|
23
+ if v.is_a?(Hash) && v.key?(:default) && v[:default].is_a?(Array)
24
+ v[:default] = v[:default].join(',')
25
+ elsif v.is_a?(Array)
26
+ v = v.join(',')
27
+ end
28
+ [k, v]
29
+ end.to_h
30
+ end
31
+
32
+ end
33
+
34
+ attr_accessor :secrets
35
+
36
+ def initialize(name, &block)
37
+ @stack_elements = { resource: {}, provider: {}, variable: {}, output: {}, module: {} }
38
+ @secrets = {}
39
+ @block = block
40
+ variable(:terraform_stack_id) {}
41
+ end
42
+
43
+ def clone
44
+ b = @block
45
+ StackModule.new(@name, &b)
46
+ end
47
+
48
+ def build(**kwargs)
49
+ vars = kwargs.map{ |k, v| [k, {default: v}] }.to_h
50
+ @stack_elements[:variable].merge!(vars)
51
+ b = @block
52
+ instance_eval(&b)
53
+ end
54
+
55
+ def generate
56
+ JSON.pretty_generate(elements)
57
+ end
58
+
59
+ def variables
60
+ elements[:variable]
61
+ end
62
+
63
+ def outputs
64
+ @stack_elements[:output]
65
+ end
66
+
67
+ def get(variable)
68
+ @stack_elements[:variable].fetch(variable)[:default]
69
+ end
70
+
71
+ def elements
72
+ elements = @stack_elements.clone
73
+ variables = StackModule.flatten_variable_arrays(@stack_elements[:variable])
74
+ @stack_elements[:module].each do |mod, data|
75
+ elements[:module][mod] = StackModule.flatten_variable_arrays(data)
76
+ end
77
+ elements[:variable] = variables
78
+ elements
79
+ end
80
+
81
+ def id
82
+ get(:terraform_stack_id)
83
+ end
84
+
85
+ private
86
+
87
+ def register_resource(resource_type, name, &block)
88
+ @stack_elements[:resource] ||= {}
89
+ @stack_elements[:resource][resource_type.to_sym] ||= {}
90
+ @stack_elements[:resource][resource_type.to_sym][name.to_sym] = Terraform::Resource.new(self, name, resource_type, &block).fields
91
+ end
92
+
93
+ def register_variable(name, &block)
94
+ new_variable = Terraform::Variable.new(self, &block).fields
95
+ unless @stack_elements[:variable].key?(name.to_sym)
96
+ @stack_elements[:variable][name.to_sym] = { default: new_variable[:default] || '' }
97
+ end
98
+ end
99
+
100
+ def register_output(name, &block)
101
+ new_output = Terraform::Output.new(self, &block).fields
102
+ @stack_elements[:output][name.to_sym] = new_output
103
+ end
104
+
105
+ def register_module(name, &block)
106
+ new_module = Terraform::Module.new(self, name, &block).fields
107
+ @stack_elements[:module][name.to_sym] = new_module
108
+ end
109
+
110
+ def register_provider(name, &block)
111
+ provider = Terraform::Provider.new(self, &block)
112
+ @secrets.merge!(provider.load_secrets(name))
113
+ @stack_elements[:provider][name.to_sym] = provider.fields
114
+ end
115
+
116
+ alias_method :resource, :register_resource
117
+ alias_method :variable, :register_variable
118
+ alias_method :provider, :register_provider
119
+ alias_method :output, :register_output
120
+ alias_method :submodule, :register_module
121
+
122
+ end
123
+ end
@@ -0,0 +1,24 @@
1
+ module Terraform
2
+ # Stack module factory, register a module and provide clones of it
3
+ class StackModules
4
+ class ModuleNotFound < StandardError; end
5
+ class ModuleAlreadyRegistered < StandardError; end
6
+
7
+ class << self
8
+ def register(name, stack_module)
9
+ fail ModuleAlreadyRegistered, "#{name} is already a registered stack_module" if @stack_modules.key?(name.downcase)
10
+ @stack_modules[name.downcase] = stack_module
11
+ end
12
+
13
+ def get(stack_module_name)
14
+ fail ModuleNotFound, "#{stack_module_name} module module not found" unless @stack_modules.key?(stack_module_name.downcase)
15
+ @stack_modules[stack_module_name.downcase].clone
16
+ end
17
+
18
+ def reset!
19
+ @stack_modules ||= {}
20
+ end
21
+ end
22
+ reset!
23
+ end
24
+ end
@@ -0,0 +1,64 @@
1
+ require 'yaml'
2
+ require 'securerandom'
3
+
4
+ require_relative 'stack.rb'
5
+
6
+ module Terraform
7
+ # Singleton to keep track of stack templates
8
+ class Stacks
9
+ class MalformedConfig < StandardError; end
10
+ class << self
11
+ attr_reader :stacks
12
+
13
+ def load
14
+ config = ENV['STACK_CONFIG'] || 'stacks.yml'
15
+ fail 'stacks.yml must exist in root directory, or specify STACK_CONFIG pointing to stacks.yml' unless File.exist?(config)
16
+ stack_specs = YAML.load(File.read(config))
17
+ fail MalformedConfig, 'Stacks must be an array' unless stack_specs.key?('stacks') && stack_specs['stacks'].is_a?(Array)
18
+ common = stack_specs['common'] || {}
19
+ stack_specs['stacks'].each do |stack_spec|
20
+ stack = Stack.load(stack_spec.merge(common))
21
+ @stacks[stack.name] = stack
22
+ end
23
+ end
24
+
25
+ def init
26
+ config = ENV['STACK_CONFIG'] || 'stacks.yml'
27
+ fail "stacks.yaml already exists at #{File.expand_path(config)}" if File.exist?(config)
28
+ File.open(config,'w') do |f|
29
+ f.write(YAML.dump(base_config))
30
+ end
31
+ end
32
+
33
+ def uuid
34
+ "#{Time.now.utc.to_i}_#{SecureRandom.urlsafe_base64(6)}"
35
+ end
36
+
37
+ def base_config
38
+ {
39
+ 'common' => {},
40
+ 'stacks' => [ base_stack_config ],
41
+ }
42
+ end
43
+
44
+ def base_stack_config
45
+ {
46
+ 'name' => 'SET_NAME',
47
+ 'uuid' => uuid,
48
+ 'description' => 'SET_A_DESCRIPTION',
49
+ 'root' => 'SET_A_TEMPLATE',
50
+ 'variables' => {},
51
+ }
52
+ end
53
+
54
+ def reset!
55
+ @stacks ||= {}
56
+ end
57
+
58
+ def dir
59
+ File.expand_path(File.join(ENV['TERRAFORM_DATA_DIR'] || 'data', 'stacks'))
60
+ end
61
+ end
62
+ reset!
63
+ end
64
+ end
@@ -0,0 +1,6 @@
1
+ require_relative 'stack_element.rb'
2
+
3
+ module Terraform
4
+ class Variable < StackElement
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: terraform_dsl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Dale Hamel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 10.4.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 10.4.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.2.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.2.0
41
+ description: Terraform DSL provides a syntax for managing terraform infrastructure
42
+ entirely in git
43
+ email: dale.hamel@srvthe.net
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/tasks/tasks.rb
49
+ - lib/tasks/terraform.rake
50
+ - lib/terraform_dsl.rb
51
+ - lib/terraform_dsl/aws/remote_s3.rb
52
+ - lib/terraform_dsl/aws/tagging.rb
53
+ - lib/terraform_dsl/command.rb
54
+ - lib/terraform_dsl/module.rb
55
+ - lib/terraform_dsl/output.rb
56
+ - lib/terraform_dsl/provider.rb
57
+ - lib/terraform_dsl/remote.rb
58
+ - lib/terraform_dsl/resource.rb
59
+ - lib/terraform_dsl/secrets.rb
60
+ - lib/terraform_dsl/stack.rb
61
+ - lib/terraform_dsl/stack_element.rb
62
+ - lib/terraform_dsl/stack_module.rb
63
+ - lib/terraform_dsl/stack_modules.rb
64
+ - lib/terraform_dsl/stacks.rb
65
+ - lib/terraform_dsl/variable.rb
66
+ homepage: https://rubygems.org/gems/terraform_dsl
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.4.6
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Wrap hashicorps "terraform" in a ruby DSL for managing stack templates
90
+ test_files: []
91
+ has_rdoc: