terrafying 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []