terraform_dsl 0.0.4

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 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: