terrafying 0.0.0
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/bin/terrafying +6 -0
- data/lib/hash/deep_merge.rb +6 -0
- data/lib/terrafying/aws.rb +542 -0
- data/lib/terrafying/cli.rb +73 -0
- data/lib/terrafying/dynamodb/config.rb +17 -0
- data/lib/terrafying/dynamodb/named_lock.rb +126 -0
- data/lib/terrafying/dynamodb/state.rb +92 -0
- data/lib/terrafying/dynamodb.rb +31 -0
- data/lib/terrafying/generator.rb +166 -0
- data/lib/terrafying/lock.rb +25 -0
- data/lib/terrafying/state.rb +51 -0
- data/lib/terrafying/util.rb +32 -0
- data/lib/terrafying/version.rb +4 -0
- data/lib/terrafying.rb +270 -0
- metadata +185 -0
data/lib/terrafying.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'logger'
|
3
|
+
require 'pathname'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
require 'hash/deep_merge'
|
8
|
+
|
9
|
+
require 'terrafying/aws'
|
10
|
+
require 'terrafying/cli'
|
11
|
+
require 'terrafying/generator'
|
12
|
+
require 'terrafying/lock'
|
13
|
+
require 'terrafying/version'
|
14
|
+
require 'terrafying/state'
|
15
|
+
|
16
|
+
module Terrafying
|
17
|
+
|
18
|
+
class Config
|
19
|
+
|
20
|
+
attr_reader :path, :scope
|
21
|
+
|
22
|
+
def initialize(path, options)
|
23
|
+
@path = File.expand_path(path)
|
24
|
+
@options = options
|
25
|
+
@scope = options[:scope] || scope_for_path(@path)
|
26
|
+
|
27
|
+
$stderr.puts "Scope: #{@scope}"
|
28
|
+
|
29
|
+
load(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def list
|
33
|
+
Terrafying::Generator.resource_names
|
34
|
+
end
|
35
|
+
|
36
|
+
def json
|
37
|
+
Terrafying::Generator.pretty_generate
|
38
|
+
end
|
39
|
+
|
40
|
+
def plan
|
41
|
+
exit_code = 1
|
42
|
+
with_config do
|
43
|
+
with_state(mode: :read) do
|
44
|
+
exit_code = exec_with_optional_target 'plan'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
exit_code
|
48
|
+
end
|
49
|
+
|
50
|
+
def graph
|
51
|
+
exit_code = 1
|
52
|
+
with_config do
|
53
|
+
with_state(mode: :read) do
|
54
|
+
exit_code = exec_with_optional_target 'graph'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
exit_code
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate
|
61
|
+
exit_code = 1
|
62
|
+
with_config do
|
63
|
+
with_state(mode: :read) do
|
64
|
+
exit_code = exec_with_optional_target 'validate'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
exit_code
|
68
|
+
end
|
69
|
+
|
70
|
+
def apply
|
71
|
+
exit_code = 1
|
72
|
+
with_config do
|
73
|
+
with_lock do
|
74
|
+
with_state(mode: :update) do
|
75
|
+
exit_code = exec_with_optional_target "apply -auto-approve -backup=- #{@dir}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
exit_code
|
80
|
+
end
|
81
|
+
|
82
|
+
def destroy
|
83
|
+
exit_code = 1
|
84
|
+
with_config do
|
85
|
+
with_lock do
|
86
|
+
with_state(mode: :update) do
|
87
|
+
exit_code = stream_command("terraform destroy -backup=- #{@dir}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
exit_code
|
92
|
+
end
|
93
|
+
|
94
|
+
def show_state
|
95
|
+
puts(State.store(self).get)
|
96
|
+
end
|
97
|
+
|
98
|
+
def use_remote_state
|
99
|
+
with_lock do
|
100
|
+
local = State.local(self)
|
101
|
+
state = local.get
|
102
|
+
if state
|
103
|
+
State.remote(self).put(state)
|
104
|
+
end
|
105
|
+
local.delete
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def use_local_state
|
110
|
+
with_lock do
|
111
|
+
remote = State.remote(self)
|
112
|
+
state = remote.get
|
113
|
+
if state
|
114
|
+
State.local(self).put(state)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def import(addr, id)
|
120
|
+
exit_code = 1
|
121
|
+
with_config do
|
122
|
+
with_lock do
|
123
|
+
with_state(mode: :update) do
|
124
|
+
exit_code = exec_with_optional_target "import -backup=- #{@dir} #{addr} #{id}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
exit_code
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
def lock_timeout
|
133
|
+
"-lock-timeout=#{@options[:lock_timeout]}" if @options[:lock_timeout]
|
134
|
+
end
|
135
|
+
|
136
|
+
def targets
|
137
|
+
@options[:target].split(',').map { |target| "-target=#{target}" }.join(' ') if @options[:target]
|
138
|
+
end
|
139
|
+
|
140
|
+
def exec_with_optional_target(command, *args)
|
141
|
+
exec_with_args(command, targets, lock_timeout, *args)
|
142
|
+
end
|
143
|
+
|
144
|
+
def exec_with_args(command, *args)
|
145
|
+
stream_command("terraform #{command} #{args.join(' ')}")
|
146
|
+
end
|
147
|
+
|
148
|
+
def with_config(&block)
|
149
|
+
abort("***** ERROR: You must have terraform installed to run this gem *****") unless terraform_installed?
|
150
|
+
check_version
|
151
|
+
name = File.basename(@path, ".*")
|
152
|
+
dir = File.join(git_toplevel, 'tmp', SecureRandom.uuid)
|
153
|
+
terraform_files = File.join(git_toplevel, ".terraform/")
|
154
|
+
unless Dir.exists?(terraform_files)
|
155
|
+
abort("***** ERROR: No .terraform directory found. Please run 'terraform init' to install plugins *****")
|
156
|
+
end
|
157
|
+
FileUtils.mkdir_p(dir)
|
158
|
+
output_path = File.join(dir, name + ".tf.json")
|
159
|
+
FileUtils.cp_r(terraform_files, dir)
|
160
|
+
Dir.chdir(dir) do
|
161
|
+
begin
|
162
|
+
File.write(output_path, Terrafying::Generator.pretty_generate)
|
163
|
+
yield block
|
164
|
+
ensure
|
165
|
+
FileUtils.rm_rf(dir) unless @options[:keep]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def with_lock(&block)
|
171
|
+
lock_id = nil
|
172
|
+
begin
|
173
|
+
lock = if @options[:no_lock]
|
174
|
+
Locks.noop
|
175
|
+
else
|
176
|
+
Locks.dynamodb(scope)
|
177
|
+
end
|
178
|
+
|
179
|
+
lock_id = if @options[:force]
|
180
|
+
lock.steal
|
181
|
+
else
|
182
|
+
lock.acquire
|
183
|
+
end
|
184
|
+
yield block
|
185
|
+
|
186
|
+
# If block raises any exception we will still hold on to lock
|
187
|
+
# after process exits. This is actually what we want as
|
188
|
+
# terraform may have succeeded in updating some resources, but
|
189
|
+
# not others so we need to manually get into a consistent
|
190
|
+
# state and then re-run.
|
191
|
+
lock.release(lock_id)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def with_state(opts, &block)
|
196
|
+
if !@options[:dynamodb]
|
197
|
+
return yield(block)
|
198
|
+
end
|
199
|
+
|
200
|
+
store = State.store(self)
|
201
|
+
|
202
|
+
begin
|
203
|
+
state = store.get
|
204
|
+
File.write(State::STATE_FILENAME, state) if state
|
205
|
+
rescue => e
|
206
|
+
raise "Error retrieving state for config #{self}: #{e}"
|
207
|
+
end
|
208
|
+
|
209
|
+
yield block
|
210
|
+
|
211
|
+
begin
|
212
|
+
if opts[:mode] == :update
|
213
|
+
store.put(IO.read(State::STATE_FILENAME))
|
214
|
+
end
|
215
|
+
rescue => e
|
216
|
+
raise "Error updating state for config #{self}: #{e}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def scope_for_path(path)
|
221
|
+
top_level_path = Pathname.new(git_toplevel)
|
222
|
+
Pathname.new(@path).relative_path_from(top_level_path).to_s
|
223
|
+
end
|
224
|
+
|
225
|
+
def git_toplevel
|
226
|
+
@top_level ||= begin
|
227
|
+
top_level = `git rev-parse --show-toplevel`
|
228
|
+
raise "Unable to find .git directory top level for '#{@path}'" if top_level.empty?
|
229
|
+
File.expand_path(top_level.chomp)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def check_version
|
234
|
+
if terraform_version != Terrafying::CLI_VERSION
|
235
|
+
abort("***** ERROR: You must have v#{Terrafying::CLI_VERSION} of terraform installed to run any command (you are running v#{terraform_version}) *****")
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def terraform_installed?
|
240
|
+
which('terraform')
|
241
|
+
end
|
242
|
+
|
243
|
+
def terraform_version
|
244
|
+
`terraform -v`.split("\n").first.split("v").last
|
245
|
+
end
|
246
|
+
|
247
|
+
def stream_command(cmd)
|
248
|
+
IO.popen(cmd) do |io|
|
249
|
+
while (line = io.gets) do
|
250
|
+
puts line.gsub('\n', "\n").gsub('\\"', "\"")
|
251
|
+
end
|
252
|
+
end
|
253
|
+
return $?.exitstatus
|
254
|
+
end
|
255
|
+
|
256
|
+
# Cross-platform way of finding an executable in the $PATH.
|
257
|
+
#
|
258
|
+
# which('ruby') #=> /usr/bin/ruby
|
259
|
+
def which(cmd)
|
260
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
261
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
262
|
+
exts.each { |ext|
|
263
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
264
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
265
|
+
}
|
266
|
+
end
|
267
|
+
return nil
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: terrafying
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- uSwitch Limited
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-mocks
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: aws-sdk
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: thor
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.19.1
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.19.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: deep_merge
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.1.1
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.1.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: netaddr
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.5'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.5'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: xxhash
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.4.0
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.4.0
|
139
|
+
description: No.
|
140
|
+
email:
|
141
|
+
- developers@uswitch.com
|
142
|
+
executables:
|
143
|
+
- terrafying
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- bin/terrafying
|
148
|
+
- lib/hash/deep_merge.rb
|
149
|
+
- lib/terrafying.rb
|
150
|
+
- lib/terrafying/aws.rb
|
151
|
+
- lib/terrafying/cli.rb
|
152
|
+
- lib/terrafying/dynamodb.rb
|
153
|
+
- lib/terrafying/dynamodb/config.rb
|
154
|
+
- lib/terrafying/dynamodb/named_lock.rb
|
155
|
+
- lib/terrafying/dynamodb/state.rb
|
156
|
+
- lib/terrafying/generator.rb
|
157
|
+
- lib/terrafying/lock.rb
|
158
|
+
- lib/terrafying/state.rb
|
159
|
+
- lib/terrafying/util.rb
|
160
|
+
- lib/terrafying/version.rb
|
161
|
+
homepage: https://github.com/uswitch/terrafying
|
162
|
+
licenses:
|
163
|
+
- Apache-2.0
|
164
|
+
metadata: {}
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
requirements: []
|
180
|
+
rubyforge_project:
|
181
|
+
rubygems_version: 2.7.6
|
182
|
+
signing_key:
|
183
|
+
specification_version: 4
|
184
|
+
summary: No.
|
185
|
+
test_files: []
|