bolt 1.34.0 → 1.35.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -1
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +16 -3
- data/lib/bolt/config.rb +1 -1
- data/lib/bolt/inventory.rb +1 -1
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/inventory/group2.rb +232 -216
- data/lib/bolt/inventory/inventory2.rb +64 -219
- data/lib/bolt/inventory/target.rb +227 -0
- data/lib/bolt/module.rb +4 -4
- data/lib/bolt/plugin.rb +0 -3
- data/lib/bolt/plugin/module.rb +52 -5
- data/lib/bolt/plugin/task.rb +5 -1
- data/lib/bolt/target.rb +24 -46
- data/lib/bolt/util.rb +18 -0
- data/lib/bolt/version.rb +1 -1
- metadata +3 -5
- data/lib/bolt/plugin/aws_inventory.rb +0 -102
- data/lib/bolt/plugin/terraform.rb +0 -175
- data/lib/bolt/plugin/vault.rb +0 -206
data/lib/bolt/module.rb
CHANGED
@@ -8,10 +8,10 @@ module Bolt
|
|
8
8
|
def self.discover(modulepath)
|
9
9
|
modulepath.each_with_object({}) do |path, mods|
|
10
10
|
next unless File.exist?(path) && File.directory?(path)
|
11
|
-
Dir.
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
(Dir.entries(path) - %w[. ..])
|
12
|
+
.map { |dir| File.join(path, dir) }
|
13
|
+
.select { |dir| File.directory?(dir) }
|
14
|
+
.each do |dir|
|
15
15
|
module_name = File.basename(dir)
|
16
16
|
if module_name =~ MODULE_NAME_REGEX
|
17
17
|
# Puppet will load some objects from shadowed modules but this won't
|
data/lib/bolt/plugin.rb
CHANGED
@@ -123,13 +123,10 @@ module Bolt
|
|
123
123
|
# PDB is special do we want to expose the default client to the context?
|
124
124
|
plugins.add_plugin(Bolt::Plugin::Puppetdb.new(pdb_client))
|
125
125
|
|
126
|
-
plugins.add_ruby_plugin('Bolt::Plugin::AwsInventory')
|
127
126
|
plugins.add_ruby_plugin('Bolt::Plugin::InstallAgent')
|
128
127
|
plugins.add_ruby_plugin('Bolt::Plugin::Task')
|
129
|
-
plugins.add_ruby_plugin('Bolt::Plugin::Terraform')
|
130
128
|
plugins.add_ruby_plugin('Bolt::Plugin::Pkcs7')
|
131
129
|
plugins.add_ruby_plugin('Bolt::Plugin::Prompt')
|
132
|
-
plugins.add_ruby_plugin('Bolt::Plugin::Vault')
|
133
130
|
|
134
131
|
plugins
|
135
132
|
end
|
data/lib/bolt/plugin/module.rb
CHANGED
@@ -35,9 +35,18 @@ module Bolt
|
|
35
35
|
# This method interacts with the module on disk so it's separate from initialize
|
36
36
|
def setup
|
37
37
|
@data = load_data
|
38
|
-
@config_schema = process_schema(@data['config'] || {})
|
39
|
-
|
40
38
|
@hook_map = find_hooks(@data['hooks'] || {})
|
39
|
+
# If there is a config section in bolt_plugin.json, validate against that and send
|
40
|
+
# validated values nested under `_config` key. Otherwise validate againsts the intersection
|
41
|
+
# of all task schemas.
|
42
|
+
# TODO: remove @send_config when deprecated
|
43
|
+
schema = if @data['config']
|
44
|
+
@send_config = true
|
45
|
+
@data['config']
|
46
|
+
else
|
47
|
+
extract_task_parameter_schema
|
48
|
+
end
|
49
|
+
@config_schema = process_schema(schema)
|
41
50
|
|
42
51
|
validate_config(@config, @config_schema)
|
43
52
|
end
|
@@ -150,15 +159,45 @@ module Bolt
|
|
150
159
|
# handled previously. That may not always be the case so filter them
|
151
160
|
# out now.
|
152
161
|
_meta, params = opts.partition { |key, _val| key.start_with?('_') }.map(&:to_h)
|
153
|
-
|
154
162
|
metaparams = {}
|
155
|
-
|
163
|
+
# Send config with `_config` when config is defined in bolt_plugin.json
|
164
|
+
# Otherwise, merge config with params
|
165
|
+
# TODO: remove @send_config when deprecated
|
166
|
+
if @send_config
|
167
|
+
metaparams['_config'] = config if config?
|
168
|
+
else
|
169
|
+
params = @config ? config.merge(params) : params
|
170
|
+
end
|
156
171
|
metaparams['_boltdir'] = @context.boltdir
|
157
172
|
|
158
173
|
validate_params(task, params)
|
159
174
|
[params, metaparams]
|
160
175
|
end
|
161
176
|
|
177
|
+
def extract_task_parameter_schema
|
178
|
+
# Get the intersection of expected types (using Set)
|
179
|
+
type_set = @hook_map.each_with_object({}) do |(_hook, task), acc|
|
180
|
+
next unless (schema = task['task'].metadata['parameters'])
|
181
|
+
schema.each do |param, scheme|
|
182
|
+
next unless scheme['type'].is_a?(String)
|
183
|
+
scheme['type'] = Set.new([scheme['type']])
|
184
|
+
if acc.dig(param, 'type').is_a?(Set)
|
185
|
+
scheme['type'].merge(acc[param]['type'])
|
186
|
+
end
|
187
|
+
end
|
188
|
+
acc.merge!(schema)
|
189
|
+
end
|
190
|
+
# Convert Set to string
|
191
|
+
type_set.each do |_param, schema|
|
192
|
+
next unless schema['type']
|
193
|
+
schema['type'] = if schema['type'].size > 1
|
194
|
+
"Optional[Variant[#{schema['type'].to_a.join(', ')}]]"
|
195
|
+
else
|
196
|
+
"Optional[#{schema['type'].to_a.first}]"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
162
201
|
def run_task(task, opts)
|
163
202
|
params, metaparams = process_params(task, opts)
|
164
203
|
params = params.merge(metaparams)
|
@@ -191,7 +230,15 @@ module Bolt
|
|
191
230
|
end
|
192
231
|
|
193
232
|
def validate_resolve_reference(opts)
|
194
|
-
|
233
|
+
# Send config with `_config` when config is defined in bolt_plugin.json
|
234
|
+
# Otherwise, merge config with params
|
235
|
+
# TODO: remove @send_config when deprecated
|
236
|
+
if @send_config
|
237
|
+
params = opts.reject { |k, _v| k.start_with?('_') }
|
238
|
+
else
|
239
|
+
merged = @config.merge(opts)
|
240
|
+
params = merged.reject { |k, _v| k.start_with?('_') }
|
241
|
+
end
|
195
242
|
sig = @hook_map[:resolve_reference]['task']
|
196
243
|
if sig
|
197
244
|
validate_params(sig, params)
|
data/lib/bolt/plugin/task.rb
CHANGED
@@ -48,7 +48,11 @@ module Bolt
|
|
48
48
|
|
49
49
|
def puppet_library(opts, target, apply_prep)
|
50
50
|
params = opts['parameters'] || {}
|
51
|
-
|
51
|
+
begin
|
52
|
+
task = apply_prep.get_task(opts['task'], params)
|
53
|
+
rescue Bolt::Error => e
|
54
|
+
raise Bolt::Plugin::PluginError::ExecutionError.new(e.message, name, 'puppet_library')
|
55
|
+
end
|
52
56
|
proc do
|
53
57
|
apply_prep.run_task([target], task, params).first
|
54
58
|
end
|
data/lib/bolt/target.rb
CHANGED
@@ -10,10 +10,12 @@ module Bolt
|
|
10
10
|
# Target.new from a plan initialized with a hash
|
11
11
|
def self.from_asserted_hash(hash)
|
12
12
|
inventory = Puppet.lookup(:bolt_inventory)
|
13
|
-
inventory.create_target_from_plan(hash)
|
13
|
+
target = inventory.create_target_from_plan(hash)
|
14
|
+
new(target.name, inventory)
|
14
15
|
end
|
15
16
|
|
16
|
-
# Target.new from a plan with just a uri
|
17
|
+
# Target.new from a plan with just a uri. Puppet requires the arguments to
|
18
|
+
# this method to match (by name) the attributes defined on the datatype.
|
17
19
|
# rubocop:disable Lint/UnusedMethodArgument
|
18
20
|
def self.from_asserted_args(uri = nil,
|
19
21
|
name = nil,
|
@@ -23,37 +25,15 @@ module Bolt
|
|
23
25
|
vars = nil,
|
24
26
|
features = nil,
|
25
27
|
plugin_hooks = nil)
|
26
|
-
|
27
|
-
inventory.create_target_from_plan('uri' => uri)
|
28
|
+
from_asserted_hash('uri' => uri)
|
28
29
|
end
|
29
30
|
|
30
|
-
|
31
|
-
def initialize(uri = nil,
|
32
|
-
name = nil,
|
33
|
-
target_alias = nil,
|
34
|
-
config = nil,
|
35
|
-
facts = nil,
|
36
|
-
vars = nil,
|
37
|
-
features = nil,
|
38
|
-
plugin_hooks = nil)
|
31
|
+
def initialize(name, inventory = nil)
|
39
32
|
@name = name
|
33
|
+
@inventory = inventory
|
40
34
|
end
|
41
35
|
# rubocop:enable Lint/UnusedMethodArgument
|
42
36
|
|
43
|
-
# Used for munging target + group data
|
44
|
-
def target_data_hash
|
45
|
-
{
|
46
|
-
'config' => @inventory.targets[@name]['config'],
|
47
|
-
'vars' => @inventory.targets[@name]['vars'],
|
48
|
-
'facts' => @inventory.targets[@name]['facts'],
|
49
|
-
'features' => @inventory.targets[@name]['features'].to_a,
|
50
|
-
'plugin_hooks' => @inventory.targets[@name]['plugin_hooks'],
|
51
|
-
'name' => @inventory.targets[@name]['name'],
|
52
|
-
'uri' => @inventory.targets[@name]['uri'],
|
53
|
-
'alias' => @inventory.targets[@name]['target_alias']
|
54
|
-
}
|
55
|
-
end
|
56
|
-
|
57
37
|
# features returns an array to be compatible with plans
|
58
38
|
def features
|
59
39
|
@inventory.features(self).to_a
|
@@ -77,15 +57,15 @@ module Bolt
|
|
77
57
|
end
|
78
58
|
|
79
59
|
def config
|
80
|
-
|
60
|
+
inventory_target.config
|
81
61
|
end
|
82
62
|
|
83
63
|
def safe_name
|
84
|
-
|
64
|
+
inventory_target.safe_name
|
85
65
|
end
|
86
66
|
|
87
67
|
def target_alias
|
88
|
-
|
68
|
+
inventory_target.target_alias
|
89
69
|
end
|
90
70
|
|
91
71
|
def to_h
|
@@ -100,53 +80,51 @@ module Bolt
|
|
100
80
|
)
|
101
81
|
end
|
102
82
|
|
83
|
+
def inventory_target
|
84
|
+
@inventory.targets[@name]
|
85
|
+
end
|
86
|
+
|
103
87
|
def host
|
104
|
-
|
88
|
+
inventory_target.host
|
105
89
|
end
|
106
90
|
|
107
91
|
attr_reader :name
|
108
92
|
|
109
93
|
def uri
|
110
|
-
|
94
|
+
inventory_target.uri
|
111
95
|
end
|
112
96
|
|
113
97
|
def remote?
|
114
|
-
|
98
|
+
protocol == 'remote'
|
115
99
|
end
|
116
100
|
|
117
101
|
def port
|
118
|
-
|
102
|
+
inventory_target.port
|
119
103
|
end
|
120
104
|
|
121
|
-
# transport is separate from protocol for remote targets.
|
122
105
|
def transport
|
123
|
-
|
106
|
+
inventory_target.protocol
|
124
107
|
end
|
125
108
|
|
126
109
|
def protocol
|
127
|
-
|
110
|
+
inventory_target.protocol
|
128
111
|
end
|
129
112
|
|
130
113
|
def user
|
131
|
-
|
114
|
+
inventory_target.user
|
132
115
|
end
|
133
116
|
|
134
117
|
def password
|
135
|
-
|
118
|
+
inventory_target.password
|
136
119
|
end
|
137
120
|
|
138
121
|
def options
|
139
|
-
|
122
|
+
inventory_target.options
|
140
123
|
end
|
141
124
|
|
142
125
|
def plugin_hooks
|
143
|
-
|
144
|
-
end
|
145
|
-
|
146
|
-
def unencode(component)
|
147
|
-
Addressable::URI.unencode_component(component)
|
126
|
+
inventory_target.plugin_hooks
|
148
127
|
end
|
149
|
-
private :unencode
|
150
128
|
|
151
129
|
def eql?(other)
|
152
130
|
self.class.equal?(other.class) && @name == other.name
|
data/lib/bolt/util.rb
CHANGED
@@ -147,6 +147,24 @@ module Bolt
|
|
147
147
|
end
|
148
148
|
end
|
149
149
|
|
150
|
+
# Accepts a Data object and returns a copy with all hash and array values
|
151
|
+
# modified by the given block. Descendants are modified before their
|
152
|
+
# parents.
|
153
|
+
def postwalk_vals(data, skip_top = false, &block)
|
154
|
+
new_data = if data.is_a? Hash
|
155
|
+
map_vals(data) { |v| postwalk_vals(v, &block) }
|
156
|
+
elsif data.is_a? Array
|
157
|
+
data.map { |v| postwalk_vals(v, &block) }
|
158
|
+
else
|
159
|
+
data
|
160
|
+
end
|
161
|
+
if skip_top
|
162
|
+
new_data
|
163
|
+
else
|
164
|
+
yield(new_data)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
150
168
|
# Performs a deep_clone, using an identical copy if the cloned structure contains multiple
|
151
169
|
# references to the same object and prevents endless recursion.
|
152
170
|
# Credit to Jan Molic via https://github.com/rubyworks/facets/blob/master/LICENSE.txt
|
data/lib/bolt/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bolt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.35.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -373,6 +373,7 @@ files:
|
|
373
373
|
- lib/bolt/inventory/group.rb
|
374
374
|
- lib/bolt/inventory/group2.rb
|
375
375
|
- lib/bolt/inventory/inventory2.rb
|
376
|
+
- lib/bolt/inventory/target.rb
|
376
377
|
- lib/bolt/logger.rb
|
377
378
|
- lib/bolt/module.rb
|
378
379
|
- lib/bolt/node/errors.rb
|
@@ -399,15 +400,12 @@ files:
|
|
399
400
|
- lib/bolt/pal/yaml_plan/transpiler.rb
|
400
401
|
- lib/bolt/plan_result.rb
|
401
402
|
- lib/bolt/plugin.rb
|
402
|
-
- lib/bolt/plugin/aws_inventory.rb
|
403
403
|
- lib/bolt/plugin/install_agent.rb
|
404
404
|
- lib/bolt/plugin/module.rb
|
405
405
|
- lib/bolt/plugin/pkcs7.rb
|
406
406
|
- lib/bolt/plugin/prompt.rb
|
407
407
|
- lib/bolt/plugin/puppetdb.rb
|
408
408
|
- lib/bolt/plugin/task.rb
|
409
|
-
- lib/bolt/plugin/terraform.rb
|
410
|
-
- lib/bolt/plugin/vault.rb
|
411
409
|
- lib/bolt/puppetdb.rb
|
412
410
|
- lib/bolt/puppetdb/client.rb
|
413
411
|
- lib/bolt/puppetdb/config.rb
|
@@ -1,102 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Bolt
|
6
|
-
class Plugin
|
7
|
-
class AwsInventory
|
8
|
-
attr_accessor :client
|
9
|
-
attr_reader :config
|
10
|
-
|
11
|
-
def initialize(config:, **_kwargs)
|
12
|
-
require 'aws-sdk-ec2'
|
13
|
-
@config = config
|
14
|
-
@logger = Logging.logger[self]
|
15
|
-
end
|
16
|
-
|
17
|
-
def name
|
18
|
-
'aws_inventory'
|
19
|
-
end
|
20
|
-
|
21
|
-
def hooks
|
22
|
-
[:resolve_reference]
|
23
|
-
end
|
24
|
-
|
25
|
-
def config_client(opts)
|
26
|
-
return client if client
|
27
|
-
|
28
|
-
options = {}
|
29
|
-
|
30
|
-
if opts.key?('region')
|
31
|
-
options[:region] = opts['region']
|
32
|
-
end
|
33
|
-
if opts.key?('profile')
|
34
|
-
options[:profile] = opts['profile']
|
35
|
-
end
|
36
|
-
|
37
|
-
if config['credentials']
|
38
|
-
creds = File.expand_path(config['credentials'])
|
39
|
-
if File.exist?(creds)
|
40
|
-
options[:credentials] = ::Aws::SharedCredentials.new(path: creds)
|
41
|
-
else
|
42
|
-
raise Bolt::ValidationError, "Cannot load credentials file #{config['credentials']}"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
::Aws::EC2::Client.new(options)
|
47
|
-
end
|
48
|
-
|
49
|
-
def resolve_reference(opts)
|
50
|
-
client = config_client(opts)
|
51
|
-
resource = ::Aws::EC2::Resource.new(client: client)
|
52
|
-
|
53
|
-
# Retrieve a list of EC2 instances and create a list of targets
|
54
|
-
# Note: It doesn't seem possible to filter stubbed responses...
|
55
|
-
resource.instances(filters: opts['filters']).map do |instance|
|
56
|
-
next unless instance.state.name == 'running'
|
57
|
-
target = {}
|
58
|
-
|
59
|
-
if opts.key?('uri')
|
60
|
-
uri = lookup(instance, opts['uri'])
|
61
|
-
target['uri'] = uri if uri
|
62
|
-
end
|
63
|
-
if opts.key?('name')
|
64
|
-
real_name = lookup(instance, opts['name'])
|
65
|
-
target['name'] = real_name if real_name
|
66
|
-
end
|
67
|
-
if opts.key?('config')
|
68
|
-
target['config'] = resolve_config(instance, opts['config'])
|
69
|
-
end
|
70
|
-
|
71
|
-
target if target['uri'] || target['name']
|
72
|
-
end.compact
|
73
|
-
end
|
74
|
-
|
75
|
-
# Look for an instance attribute specified in the inventory file
|
76
|
-
def lookup(instance, attribute)
|
77
|
-
value = instance.data.respond_to?(attribute) ? instance.data[attribute] : nil
|
78
|
-
unless value
|
79
|
-
warn_missing_attribute(instance, attribute)
|
80
|
-
end
|
81
|
-
value
|
82
|
-
end
|
83
|
-
|
84
|
-
def warn_missing_attribute(instance, attribute)
|
85
|
-
@logger.warn("Could not find attribute #{attribute} of instance #{instance.instance_id}")
|
86
|
-
end
|
87
|
-
|
88
|
-
# Walk the "template" config mapping provided in the plugin config and
|
89
|
-
# replace all values with the corresponding value from the resource
|
90
|
-
# parameters.
|
91
|
-
def resolve_config(name, config_template)
|
92
|
-
Bolt::Util.walk_vals(config_template) do |value|
|
93
|
-
if value.is_a?(String)
|
94
|
-
lookup(name, value)
|
95
|
-
else
|
96
|
-
value
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
@@ -1,175 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Bolt
|
6
|
-
class Plugin
|
7
|
-
class Terraform
|
8
|
-
KNOWN_KEYS = Set['_plugin', 'dir', 'resource_type', 'uri', 'name', 'statefile',
|
9
|
-
'config', 'backend']
|
10
|
-
REQ_KEYS = Set['dir', 'resource_type']
|
11
|
-
|
12
|
-
def initialize(*_args)
|
13
|
-
@logger = Logging.logger[self]
|
14
|
-
end
|
15
|
-
|
16
|
-
def name
|
17
|
-
'terraform'
|
18
|
-
end
|
19
|
-
|
20
|
-
def hooks
|
21
|
-
[:resolve_reference]
|
22
|
-
end
|
23
|
-
|
24
|
-
def warn_missing_property(name, property)
|
25
|
-
@logger.warn("Could not find property #{property} of terraform resource #{name}")
|
26
|
-
end
|
27
|
-
|
28
|
-
# Make sure no unexpected keys are in the inventory config and
|
29
|
-
# that required keys are present
|
30
|
-
def validate_options(opts)
|
31
|
-
opt_keys = opts.keys.to_set
|
32
|
-
|
33
|
-
unless KNOWN_KEYS.superset?(opt_keys)
|
34
|
-
keys = opt_keys - KNOWN_KEYS
|
35
|
-
raise Bolt::ValidationError, "Unexpected key(s) in inventory config: #{keys.to_a.inspect}"
|
36
|
-
end
|
37
|
-
|
38
|
-
unless opt_keys.superset?(REQ_KEYS)
|
39
|
-
keys = REQ_KEYS - opt_keys
|
40
|
-
raise Bolt::ValidationError, "Expected key(s) in inventory config: #{keys.to_a.inspect}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def resolve_reference(opts)
|
45
|
-
validate_options(opts)
|
46
|
-
|
47
|
-
state = load_statefile(opts)
|
48
|
-
|
49
|
-
resources = extract_resources(state)
|
50
|
-
|
51
|
-
regex = Regexp.new(opts['resource_type'])
|
52
|
-
|
53
|
-
resources.select do |name, _resource|
|
54
|
-
name.match?(regex)
|
55
|
-
end.map do |name, resource|
|
56
|
-
target = {}
|
57
|
-
|
58
|
-
if opts.key?('uri')
|
59
|
-
uri = lookup(name, resource, opts['uri'])
|
60
|
-
target['uri'] = uri if uri
|
61
|
-
end
|
62
|
-
if opts.key?('name')
|
63
|
-
real_name = lookup(name, resource, opts['name'])
|
64
|
-
target['name'] = real_name if real_name
|
65
|
-
end
|
66
|
-
if opts.key?('config')
|
67
|
-
target['config'] = resolve_config(name, resource, opts['config'])
|
68
|
-
end
|
69
|
-
target
|
70
|
-
end.compact
|
71
|
-
end
|
72
|
-
|
73
|
-
def load_statefile(opts)
|
74
|
-
statefile = if opts['backend'] == 'remote'
|
75
|
-
load_remote_statefile(opts)
|
76
|
-
else
|
77
|
-
load_local_statefile(opts)
|
78
|
-
end
|
79
|
-
|
80
|
-
JSON.parse(statefile)
|
81
|
-
end
|
82
|
-
|
83
|
-
# Uses the Terraform CLI to pull remote state files
|
84
|
-
def load_remote_statefile(opts)
|
85
|
-
dir = File.expand_path(opts['dir'])
|
86
|
-
|
87
|
-
begin
|
88
|
-
stdout_str, stderr_str, status = Open3.capture3('terraform state pull', chdir: dir)
|
89
|
-
rescue Errno::ENOENT
|
90
|
-
reason = if File.directory?(dir)
|
91
|
-
"Could not find executable 'terraform'"
|
92
|
-
else
|
93
|
-
"Could not find directory '#{dir}'"
|
94
|
-
end
|
95
|
-
raise Bolt::Error.new(reason, 'FILE_ERROR')
|
96
|
-
end
|
97
|
-
|
98
|
-
unless status.success?
|
99
|
-
err = stdout_str + stderr_str
|
100
|
-
msg = "Could not pull Terraform remote state file for #{opts['dir']}:\n#{err}"
|
101
|
-
raise Bolt::Error.new(msg, 'bolt/terraform-state-error')
|
102
|
-
end
|
103
|
-
|
104
|
-
stdout_str
|
105
|
-
end
|
106
|
-
|
107
|
-
def load_local_statefile(opts)
|
108
|
-
dir = opts['dir']
|
109
|
-
filename = opts.fetch('statefile', 'terraform.tfstate')
|
110
|
-
File.read(File.expand_path(File.join(dir, filename)))
|
111
|
-
rescue StandardError => e
|
112
|
-
raise Bolt::FileError.new("Could not load Terraform state file #{filename}: #{e}", filename)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Format the list of resources into a list of [name, attribute map]
|
116
|
-
# pairs. This method handles both version 4 and earlier statefiles, doing
|
117
|
-
# the appropriate munging based on the shape of the data.
|
118
|
-
def extract_resources(state)
|
119
|
-
if state['version'] >= 4
|
120
|
-
state.fetch('resources', []).flat_map do |resource_set|
|
121
|
-
prefix = "#{resource_set['type']}.#{resource_set['name']}"
|
122
|
-
resource_set['instances'].map do |resource|
|
123
|
-
instance_name = prefix
|
124
|
-
instance_name += ".#{resource['index_key']}" if resource['index_key']
|
125
|
-
# When using `terraform state pull` with terraform >= 0.12 version 3 statefiles
|
126
|
-
# Will be converted to version 4. When converted attributes is converted to attributes_flat
|
127
|
-
attributes = resource['attributes'] || resource['attributes_flat']
|
128
|
-
[instance_name, attributes]
|
129
|
-
end
|
130
|
-
end
|
131
|
-
else
|
132
|
-
state.fetch('modules', {}).flat_map do |mod|
|
133
|
-
mod.fetch('resources', {}).map do |name, resource|
|
134
|
-
[name, resource.dig('primary', 'attributes')]
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Look up a nested value from the resource attributes. The key is of the
|
141
|
-
# form `foo.bar.0.baz`. For terraform statefile version 3, this will
|
142
|
-
# exactly correspond to a key in the resource. In version 4, it will
|
143
|
-
# correspond to a nested hash entry at {foo: {bar: [{baz: <value>}]}}
|
144
|
-
# For simplicity's sake, we just check both.
|
145
|
-
def lookup(name, resource, path)
|
146
|
-
segments = path.split('.').map do |segment|
|
147
|
-
begin
|
148
|
-
Integer(segment)
|
149
|
-
rescue ArgumentError
|
150
|
-
segment
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
value = resource[path] || resource.dig(*segments)
|
155
|
-
unless value
|
156
|
-
warn_missing_property(name, path)
|
157
|
-
end
|
158
|
-
value
|
159
|
-
end
|
160
|
-
|
161
|
-
# Walk the "template" config mapping provided in the plugin config and
|
162
|
-
# replace all values with the corresponding value from the resource
|
163
|
-
# parameters.
|
164
|
-
def resolve_config(name, resource, config_template)
|
165
|
-
Bolt::Util.walk_vals(config_template) do |value|
|
166
|
-
if value.is_a?(String)
|
167
|
-
lookup(name, resource, value)
|
168
|
-
else
|
169
|
-
value
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|