terrafying 1.7.4 → 1.7.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/terrafying +1 -0
- data/lib/terrafying/aws.rb +170 -168
- data/lib/terrafying/cli.rb +22 -20
- data/lib/terrafying/dynamodb/config.rb +7 -5
- data/lib/terrafying/dynamodb/named_lock.rb +55 -58
- data/lib/terrafying/dynamodb/state.rb +32 -32
- data/lib/terrafying/dynamodb.rb +7 -5
- data/lib/terrafying/generator.rb +70 -80
- data/lib/terrafying/lock.rb +7 -5
- data/lib/terrafying/state.rb +7 -5
- data/lib/terrafying/util.rb +12 -15
- data/lib/terrafying/version.rb +4 -2
- data/lib/terrafying.rb +28 -36
- metadata +30 -16
data/lib/terrafying/cli.rb
CHANGED
@@ -1,70 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thor'
|
2
4
|
|
3
5
|
module Terrafying
|
4
6
|
class Cli < Thor
|
5
|
-
class_option :lock_timeout, :
|
6
|
-
class_option :no_lock, :
|
7
|
-
class_option :keep, :
|
8
|
-
class_option :target, :
|
9
|
-
class_option :scope, :
|
10
|
-
class_option :dynamodb, :
|
7
|
+
class_option :lock_timeout, type: :string, default: nil
|
8
|
+
class_option :no_lock, type: :boolean, default: false
|
9
|
+
class_option :keep, type: :boolean, default: false
|
10
|
+
class_option :target, type: :string, default: nil
|
11
|
+
class_option :scope, type: :string, default: nil
|
12
|
+
class_option :dynamodb, type: :boolean, default: true
|
11
13
|
|
12
|
-
desc
|
14
|
+
desc 'list PATH', 'List resources defined'
|
13
15
|
def list(path)
|
14
16
|
puts "Defined resources:\n\n"
|
15
17
|
Config.new(path, options).list.each do |name|
|
16
|
-
puts
|
18
|
+
puts name.to_s
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
desc
|
22
|
+
desc 'plan PATH', 'Show execution plan'
|
21
23
|
def plan(path)
|
22
24
|
exit Config.new(path, options).plan
|
23
25
|
end
|
24
26
|
|
25
|
-
desc
|
27
|
+
desc 'graph PATH', 'Show execution graph'
|
26
28
|
def graph(path)
|
27
29
|
exit Config.new(path, options).graph
|
28
30
|
end
|
29
31
|
|
30
|
-
desc
|
32
|
+
desc 'validate PATH', 'Validate the generated Terraform'
|
31
33
|
def validate(path)
|
32
34
|
exit Config.new(path, options).validate
|
33
35
|
end
|
34
36
|
|
35
|
-
desc
|
36
|
-
option :force, :
|
37
|
+
desc 'apply PATH', 'Apply changes to resources'
|
38
|
+
option :force, aliases: ['f'], type: :boolean, desc: 'Forcefully remove any pending locks'
|
37
39
|
def apply(path)
|
38
40
|
exit Config.new(path, options).apply
|
39
41
|
end
|
40
42
|
|
41
|
-
desc
|
42
|
-
option :force, :
|
43
|
+
desc 'destroy PATH', 'Destroy resources'
|
44
|
+
option :force, aliases: ['f'], type: :boolean, desc: 'Forcefully remove any pending locks'
|
43
45
|
def destroy(path)
|
44
46
|
exit Config.new(path, options).destroy
|
45
47
|
end
|
46
48
|
|
47
|
-
desc
|
49
|
+
desc 'json PATH', 'Show terraform JSON'
|
48
50
|
def json(path)
|
49
51
|
puts(Config.new(path, options).json)
|
50
52
|
end
|
51
53
|
|
52
|
-
desc
|
54
|
+
desc 'show-state PATH', 'Show state'
|
53
55
|
def show_state(path)
|
54
56
|
puts(Config.new(path, options).show_state)
|
55
57
|
end
|
56
58
|
|
57
|
-
desc
|
59
|
+
desc 'use-remote-state PATH', 'Migrate to using remote state storage'
|
58
60
|
def use_remote_state(path)
|
59
61
|
puts(Config.new(path, options).use_remote_state)
|
60
62
|
end
|
61
63
|
|
62
|
-
desc
|
64
|
+
desc 'use-local-state PATH', 'Migrate to using local state storage'
|
63
65
|
def use_local_state(path)
|
64
66
|
puts(Config.new(path, options).use_local_state)
|
65
67
|
end
|
66
68
|
|
67
|
-
desc
|
69
|
+
desc 'import PATH ADDR ID', 'Import existing infrastructure into your Terraform state'
|
68
70
|
def import(path, addr, id)
|
69
71
|
exit Config.new(path, options).import(addr, id)
|
70
72
|
end
|
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Terrafying
|
2
4
|
module DynamoDb
|
3
5
|
class Config
|
4
6
|
attr_accessor :state_table, :lock_table
|
5
|
-
|
7
|
+
|
6
8
|
def initialize
|
7
|
-
@state_table =
|
8
|
-
@lock_table =
|
9
|
+
@state_table = 'terrafying-state'
|
10
|
+
@lock_table = 'terrafying-state-lock'
|
9
11
|
end
|
10
12
|
end
|
11
|
-
|
13
|
+
|
12
14
|
def config
|
13
15
|
@config ||= Config.new
|
14
16
|
end
|
15
17
|
module_function :config
|
16
18
|
end
|
17
|
-
end
|
19
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'terrafying/dynamodb'
|
2
4
|
require 'terrafying/dynamodb/config'
|
3
5
|
|
@@ -12,18 +14,18 @@ module Terrafying
|
|
12
14
|
|
13
15
|
def status
|
14
16
|
@client.ensure_table(table) do
|
15
|
-
resp = @client.get_item(
|
17
|
+
resp = @client.get_item(
|
16
18
|
table_name: @table_name,
|
17
19
|
key: {
|
18
|
-
|
20
|
+
'name' => @name
|
19
21
|
},
|
20
|
-
consistent_read: true
|
21
|
-
|
22
|
+
consistent_read: true
|
23
|
+
)
|
22
24
|
if resp.item
|
23
25
|
return {
|
24
26
|
status: :locked,
|
25
|
-
locked_at: resp.item[
|
26
|
-
metadata: resp.item[
|
27
|
+
locked_at: resp.item['locked_at'],
|
28
|
+
metadata: resp.item['metadata']
|
27
29
|
}
|
28
30
|
else
|
29
31
|
return {
|
@@ -32,92 +34,87 @@ module Terrafying
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|
35
|
-
|
37
|
+
|
36
38
|
def acquire
|
37
39
|
@client.ensure_table(table) do
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
raise "Unable to acquire lock: #{status.inspect}" # TODO
|
44
|
-
end
|
40
|
+
lock_id = SecureRandom.uuid
|
41
|
+
@client.update_item(acquire_request(lock_id))
|
42
|
+
return lock_id
|
43
|
+
rescue ::Aws::DynamoDB::Errors::ConditionalCheckFailedException
|
44
|
+
raise "Unable to acquire lock: #{status.inspect}" # TODO
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
def steal
|
49
49
|
@client.ensure_table(table) do
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
raise "Unable to steal lock: #{status.inspect}" # TODO
|
58
|
-
end
|
50
|
+
lock_id = SecureRandom.uuid
|
51
|
+
req = acquire_request(lock_id)
|
52
|
+
req.delete(:condition_expression)
|
53
|
+
@client.update_item(req)
|
54
|
+
return lock_id
|
55
|
+
rescue ::Aws::DynamoDB::Errors::ConditionalCheckFailedException
|
56
|
+
raise "Unable to steal lock: #{status.inspect}" # TODO
|
59
57
|
end
|
60
58
|
end
|
61
|
-
|
59
|
+
|
62
60
|
def release(lock_id)
|
63
61
|
@client.ensure_table(table) do
|
64
|
-
|
65
|
-
@
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
raise "Unable to release lock: #{status.inspect}" # TODO
|
79
|
-
end
|
62
|
+
@client.delete_item(
|
63
|
+
table_name: @table_name,
|
64
|
+
key: {
|
65
|
+
'name' => @name
|
66
|
+
},
|
67
|
+
return_values: 'NONE',
|
68
|
+
condition_expression: 'lock_id = :lock_id',
|
69
|
+
expression_attribute_values: {
|
70
|
+
':lock_id' => lock_id
|
71
|
+
}
|
72
|
+
)
|
73
|
+
nil
|
74
|
+
rescue ::Aws::DynamoDB::Errors::ConditionalCheckFailedException
|
75
|
+
raise "Unable to release lock: #{status.inspect}" # TODO
|
80
76
|
end
|
81
77
|
end
|
82
78
|
|
83
79
|
private
|
80
|
+
|
84
81
|
def acquire_request(lock_id)
|
85
82
|
{
|
86
83
|
table_name: @table_name,
|
87
84
|
key: {
|
88
|
-
|
85
|
+
'name' => @name
|
89
86
|
},
|
90
|
-
return_values:
|
91
|
-
update_expression:
|
92
|
-
condition_expression:
|
87
|
+
return_values: 'NONE',
|
88
|
+
update_expression: 'SET lock_id = :lock_id, locked_at = :locked_at, metadata = :metadata',
|
89
|
+
condition_expression: 'attribute_not_exists(lock_id)',
|
93
90
|
expression_attribute_values: {
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
}
|
99
|
-
}
|
91
|
+
':lock_id' => lock_id,
|
92
|
+
':locked_at' => Time.now.to_s,
|
93
|
+
':metadata' => {
|
94
|
+
'owner' => "#{`git config user.name`.chomp} (#{`git config user.email`.chomp})"
|
95
|
+
}
|
96
|
+
}
|
100
97
|
}
|
101
98
|
end
|
102
|
-
|
99
|
+
|
103
100
|
def table
|
104
101
|
{
|
105
102
|
table_name: @table_name,
|
106
103
|
attribute_definitions: [
|
107
104
|
{
|
108
|
-
attribute_name:
|
109
|
-
attribute_type:
|
110
|
-
}
|
105
|
+
attribute_name: 'name',
|
106
|
+
attribute_type: 'S'
|
107
|
+
}
|
111
108
|
],
|
112
109
|
key_schema: [
|
113
110
|
{
|
114
|
-
attribute_name:
|
115
|
-
key_type:
|
116
|
-
}
|
111
|
+
attribute_name: 'name',
|
112
|
+
key_type: 'HASH'
|
113
|
+
}
|
117
114
|
],
|
118
115
|
provisioned_throughput: {
|
119
116
|
read_capacity_units: 1,
|
120
|
-
write_capacity_units: 1
|
117
|
+
write_capacity_units: 1
|
121
118
|
}
|
122
119
|
}
|
123
120
|
end
|
@@ -1,31 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'digest'
|
2
4
|
require 'terrafying/dynamodb/config'
|
3
5
|
|
4
6
|
module Terrafying
|
5
7
|
module DynamoDb
|
6
8
|
class StateStore
|
7
|
-
def initialize(scope,
|
9
|
+
def initialize(scope, _opts = {})
|
8
10
|
@scope = scope
|
9
11
|
@client = Terrafying::DynamoDb.client
|
10
12
|
@table_name = Terrafying::DynamoDb.config.state_table
|
11
13
|
end
|
12
|
-
|
14
|
+
|
13
15
|
def get
|
14
16
|
@client.ensure_table(table) do
|
15
|
-
resp = @client.query(
|
17
|
+
resp = @client.query(
|
16
18
|
table_name: @table_name,
|
17
19
|
limit: 1,
|
18
20
|
key_conditions: {
|
19
|
-
|
21
|
+
'scope' => {
|
20
22
|
attribute_value_list: [@scope],
|
21
|
-
comparison_operator:
|
23
|
+
comparison_operator: 'EQ'
|
22
24
|
}
|
23
25
|
},
|
24
|
-
scan_index_forward: false
|
25
|
-
|
26
|
+
scan_index_forward: false
|
27
|
+
)
|
26
28
|
case resp.items.count
|
27
29
|
when 0 then return nil
|
28
|
-
when 1 then return resp.items.first[
|
30
|
+
when 1 then return resp.items.first['state']
|
29
31
|
else raise 'More than one item found when retrieving state. This is a bug and should never happen.' if resp.items.count != 1
|
30
32
|
end
|
31
33
|
end
|
@@ -35,58 +37,56 @@ module Terrafying
|
|
35
37
|
@client.ensure_table(table) do
|
36
38
|
sha256 = Digest::SHA256.hexdigest(state)
|
37
39
|
json = JSON.parse(state)
|
38
|
-
@client.update_item(
|
40
|
+
@client.update_item(
|
39
41
|
table_name: @table_name,
|
40
42
|
key: {
|
41
|
-
|
42
|
-
|
43
|
+
'scope' => @scope,
|
44
|
+
'serial' => json['serial'].to_i
|
43
45
|
},
|
44
|
-
return_values:
|
45
|
-
update_expression:
|
46
|
-
condition_expression:
|
46
|
+
return_values: 'NONE',
|
47
|
+
update_expression: 'SET sha256 = :sha256, #state = :state',
|
48
|
+
condition_expression: 'attribute_not_exists(serial) OR sha256 = :sha256',
|
47
49
|
expression_attribute_names: {
|
48
|
-
|
50
|
+
'#state' => 'state'
|
49
51
|
},
|
50
52
|
expression_attribute_values: {
|
51
|
-
|
52
|
-
|
53
|
+
':sha256' => sha256,
|
54
|
+
':state' => state
|
53
55
|
}
|
54
|
-
|
56
|
+
)
|
55
57
|
end
|
56
58
|
end
|
57
|
-
|
59
|
+
|
58
60
|
def table
|
59
61
|
{
|
60
62
|
table_name: @table_name,
|
61
63
|
attribute_definitions: [
|
62
64
|
{
|
63
|
-
attribute_name:
|
64
|
-
attribute_type:
|
65
|
+
attribute_name: 'scope',
|
66
|
+
attribute_type: 'S'
|
65
67
|
},
|
66
68
|
{
|
67
|
-
attribute_name:
|
68
|
-
attribute_type:
|
69
|
+
attribute_name: 'serial',
|
70
|
+
attribute_type: 'N'
|
69
71
|
}
|
70
72
|
],
|
71
73
|
key_schema: [
|
72
74
|
{
|
73
|
-
attribute_name:
|
74
|
-
key_type:
|
75
|
+
attribute_name: 'scope',
|
76
|
+
key_type: 'HASH'
|
75
77
|
},
|
76
78
|
{
|
77
|
-
attribute_name:
|
78
|
-
key_type:
|
79
|
-
}
|
80
|
-
|
79
|
+
attribute_name: 'serial',
|
80
|
+
key_type: 'RANGE'
|
81
|
+
}
|
82
|
+
|
81
83
|
],
|
82
84
|
provisioned_throughput: {
|
83
85
|
read_capacity_units: 1,
|
84
|
-
write_capacity_units: 1
|
86
|
+
write_capacity_units: 1
|
85
87
|
}
|
86
88
|
}
|
87
89
|
end
|
88
90
|
end
|
89
91
|
end
|
90
92
|
end
|
91
|
-
|
92
|
-
|
data/lib/terrafying/dynamodb.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'aws-sdk-dynamodb'
|
2
4
|
require 'json'
|
3
5
|
require 'securerandom'
|
@@ -9,7 +11,7 @@ class ::Aws::DynamoDB::Client
|
|
9
11
|
begin
|
10
12
|
yield block
|
11
13
|
rescue ::Aws::DynamoDB::Errors::ResourceNotFoundException => e
|
12
|
-
if
|
14
|
+
if !retried
|
13
15
|
create_table(table_spec)
|
14
16
|
retry
|
15
17
|
else
|
@@ -22,10 +24,10 @@ end
|
|
22
24
|
module Terrafying
|
23
25
|
module DynamoDb
|
24
26
|
def self.client
|
25
|
-
@@client ||= ::Aws::DynamoDB::Client.new(
|
26
|
-
region: Terrafying::Context::REGION
|
27
|
-
#endpoint: 'http://localhost:8000',
|
28
|
-
|
27
|
+
@@client ||= ::Aws::DynamoDB::Client.new(
|
28
|
+
region: Terrafying::Context::REGION
|
29
|
+
# endpoint: 'http://localhost:8000',
|
30
|
+
)
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|