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.
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: []