Class: Kitchen::Driver::Pulumi

Overview

Driver class implementing the CLI equivalency between Kitchen and Pulumi

Author:

  • Jacob Learned

Instance Method Summary collapse

Methods included from Pulumi::ConfigAttribute::PreserveConfig

#config_preserve_config_default_value, to_sym

Methods included from Pulumi::ConfigAttributeCacher

#define_cache, extended

Methods included from Pulumi::ConfigAttribute::SecretsProvider

#config_secrets_provider_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::RefreshConfig

#config_refresh_config_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::StackEvolution

#config_stack_evolution_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::TestStackName

#config_test_stack_name_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::Secrets

#config_secrets_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::Backend

#config_backend_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::Plugins

#config_plugins_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::Directory

#config_directory_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::ConfigFile

#config_config_file_default_value, to_sym

Methods included from Pulumi::ConfigAttribute::Config

#config_config_default_value, to_sym

Methods included from Pulumi::Configurable

#finalize_config!

Instance Method Details

#config_file(conf_file = '', flag: false) ⇒ String

Get the value of the config file to use, if set on instance or provided as param, optionally as a command line flag --config-file

Parameters:

  • conf_file (String) (defaults to: '')

    path to a stack config file to use for configuration

  • flag (Boolean) (defaults to: false)

    specify true to prepend '--config-file ' to the config file

Returns:

  • (String)

    the path to the config file or its corresponding CLI flag


210
211
212
213
214
215
216
217
# File 'lib/kitchen/driver/pulumi.rb', line 210

def config_file(conf_file = '', flag: false)
  file = conf_file.empty? ? config_config_file : conf_file
  return '' if File.directory?(file) || file.empty?

  return "--config-file #{file}" if flag

  file
end

#configure(stack_confs, stack, conf_file, dir = '', is_secret: false) ⇒ void

This method returns an undefined value.

Configures a stack in the current directory unless another is provided

Parameters:

  • stack_confs (::Hash)

    hash specifying the stack config for the instance

  • stack (String)

    name of the stack to configure

  • conf_file (String)

    path to a stack config file to use for configuration

  • dir (String) (defaults to: '')

    path to the directory to run Pulumi commands in

  • is_secret (Boolean) (defaults to: false)

    specify true to set the given stack config as secrets


174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/kitchen/driver/pulumi.rb', line 174

def configure(stack_confs, stack, conf_file, dir = '', is_secret: false)
  secret = is_secret ? '--secret' : ''
  config_flag = config_file(conf_file, flag: true)
  base_cmd = "config set #{secret} -s #{stack} #{dir} #{config_flag}"

  stack_confs.each do |namespace, stack_settings|
    stack_settings.each do |key, val|
      ::Kitchen::Pulumi::ShellOut.run(
        cmd: "#{base_cmd} #{namespace}:#{key} \"#{val}\"",
        logger: logger,
      )
    end
  end
end

#create(_state) ⇒ void

This method returns an undefined value.

Initializes a stack via pulumi stack init

Parameters:

  • _state (::Hash)

    the current kitchen state


54
55
56
57
58
# File 'lib/kitchen/driver/pulumi.rb', line 54

def create(_state)
  dir = "-C #{config_directory}"
  
  initialize_stack(stack, dir)
end

#destroy(_state) ⇒ void

This method returns an undefined value.

Destroys a stack via pulumi destroy

Parameters:

  • _state (::Hash)

    the current kitchen state


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/kitchen/driver/pulumi.rb', line 92

def destroy(_state)
  dir = "-C #{config_directory}"

  cmds = [
    "destroy -y -r --show-config -s #{stack} #{dir}",
    "stack rm #{preserve_config} -y -s #{stack} #{dir}",
  ]

  
  ::Kitchen::Pulumi::ShellOut.run(cmd: cmds, logger: logger)
rescue ::Kitchen::Pulumi::Error => e
  if e.message.match?(/no stack named '#{stack}' found/) || (
    e.message.match?(/failed to load checkpoint/) && config_backend == 'local'
  )
    puts "Stack '#{stack}' does not exist, continuing..."
  end
end

#evolve_stack(stack, conf_file, dir = '', config_only: false) ⇒ void

This method returns an undefined value.

Evolves a stack via successive calls to pulumi config set and pulumi up according to the stack_evolution instance attribute, if set. This permits testing stack config changes over time.

Parameters:

  • config_only (Boolean) (defaults to: false)

    specify true to prevent running pulumi up

  • stack (String)

    name of the stack being refreshed

  • conf_file (String)

    path to a stack config file to use for configuration

  • dir (String) (defaults to: '')

    path to the directory to run Pulumi commands in


238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/kitchen/driver/pulumi.rb', line 238

def evolve_stack(stack, conf_file, dir = '', config_only: false)
  config_stack_evolution.each do |evolution|
    new_conf_file = config_file(evolution.fetch(:config_file, ''))
    new_stack_confs = evolution.fetch(:config, {})
    new_stack_secrets = evolution.fetch(:secrets, {})

    rewrite_config_file(new_conf_file, conf_file)

    configure(new_stack_confs, stack, conf_file, dir)
    configure(new_stack_secrets, stack, conf_file, dir, is_secret: true)
    update_stack(stack, conf_file, dir) unless config_only
  end
end

#initialize_stack(stack, dir = '') ⇒ Object

Initializes a stack in the current directory unless another is provided

Parameters:

  • stack (String)

    name of the stack to initialize

  • dir (String) (defaults to: '')

    path to the directory to run Pulumi commands in


157
158
159
160
161
162
163
164
# File 'lib/kitchen/driver/pulumi.rb', line 157

def initialize_stack(stack, dir = '')
  ::Kitchen::Pulumi::ShellOut.run(
    cmd: "stack init #{stack} #{dir} #{secrets_provider(flag: true)}",
    logger: logger,
  )
rescue ::Kitchen::Pulumi::Error => e
  puts 'Continuing...' if e.message.match?(/stack '#{stack}' already exists/)
end

#loginvoid

This method returns an undefined value.

Logs in to the Pulumi backend set for the instance via pulumi login


145
146
147
148
149
150
151
# File 'lib/kitchen/driver/pulumi.rb', line 145

def 
  backend = config_backend == 'local' ? '--local' : config_backend
  ::Kitchen::Pulumi::ShellOut.run(
    cmd: "login #{backend}",
    logger: logger,
  )
end

#preserve_configString

Returns --preserve-config if the preserve_config instance attribute is set

Returns:

  • (String)

    either '' or --preserve-config


113
114
115
116
117
# File 'lib/kitchen/driver/pulumi.rb', line 113

def preserve_config
  return '' unless config_preserve_config

  '--preserve-config'
end

#refresh_config(stack, conf_file, dir = '') ⇒ void

This method returns an undefined value.

Refreshes a stack's config on the specified config file

Parameters:

  • stack (String)

    name of the stack being refreshed

  • conf_file (String)

    path to a stack config file to use for configuration

  • dir (String) (defaults to: '')

    path to the directory to run Pulumi commands in


195
196
197
198
199
200
201
202
# File 'lib/kitchen/driver/pulumi.rb', line 195

def refresh_config(stack, conf_file, dir = '')
  ::Kitchen::Pulumi::ShellOut.run(
    cmd: "config refresh -s #{stack} #{dir} #{config_file(conf_file, flag: true)}",
    logger: logger,
  )
rescue ::Kitchen::Pulumi::Error => e
  puts 'Continuing...' if e.message.match?(/no previous deployment/)
end

#rewrite_config_file(new_conf_file, old_conf_file) ⇒ void

This method returns an undefined value.

Rewrites a temporary config file by merging the contents of the new config file into the old config file. This is used during stack evolution to ensure that stack config changes for each evolution step are implemented correctly if the user has provided a new config file to use for a step.

Parameters:

  • new_conf_file (String)

    the path to the new config file to use

  • old_conf_file (String)

    the path to the config file to overwrite


260
261
262
263
264
265
266
267
268
269
# File 'lib/kitchen/driver/pulumi.rb', line 260

def rewrite_config_file(new_conf_file, old_conf_file)
  return if new_conf_file.empty?

  old_conf = YAML.load_file(old_conf_file)
  new_conf_file = File.join(config_directory, new_conf_file)
  return unless File.exist?(new_conf_file)

  new_conf = old_conf.deep_merge(YAML.load_file(new_conf_file))
  File.write(old_conf_file, new_conf.to_yaml)
end

#secrets_provider(flag: false) ⇒ String

Returns the name of the secrets provider, if set, optionally as a Pulumi CLI flag

Parameters:

  • flag (Boolean) (defaults to: false)

    specify true to prepend --secrets-provider=` to the name

Returns:

  • (String)

    value to use for the secrets provider


134
135
136
137
138
139
140
# File 'lib/kitchen/driver/pulumi.rb', line 134

def secrets_provider(flag: false)
  return '' if config_secrets_provider.empty?

  return "--secrets-provider=\"#{config_secrets_provider}\"" if flag

  config_secrets_provider
end

#stackString

Returns the name of the current stack to use. If the test_stack_name driver attribute is set, then it uses that one, otherwise it will be <suite name>-<platform name>

Returns:

  • (String)

    either the empty string or '--preserve-config'


124
125
126
127
128
# File 'lib/kitchen/driver/pulumi.rb', line 124

def stack
  return config_test_stack_name unless config_test_stack_name.empty?

  "#{instance.suite.name}-#{instance.platform.name}"
end

#stack_inputs(&block) {|stack_inputs| ... } ⇒ self

Retrieves the fully resolved stack inputs based on the current configuration of the stack via pulumi config

for block {|stack_inputs| ... }

Parameters:

  • block (Block)

    block to run with stack inputs yielded to it

Yields:

Returns:

  • (self)

Raises:

  • (Kitchen::ActionFailed)

    if an error occurs retrieving stack inputs


281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/kitchen/driver/pulumi.rb', line 281

def stack_inputs(&block)
  update({}, config_only: true) do |temp_conf_file|
    ::Kitchen::Pulumi::Command::Input.run(
      directory: config_directory,
      stack: stack,
      conf_file: config_file(temp_conf_file, flag: true),
      logger: logger,
      &block
    )
  end

  self
rescue ::Kitchen::Pulumi::Error => e
  raise ::Kitchen::ActionFailed, e.message
end

#stack_outputs(&block) {|stack_outputs| ... } ⇒ self

Retrieves stack outputs via pulumi stack output

for block {|stack_outputs| ... }

Parameters:

  • block (Block)

    block to run with stack outputs yielded to it

Yields:

Returns:

  • (self)

Raises:

  • (Kitchen::ActionFailed)

    if an error occurs retrieving stack outputs


306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/kitchen/driver/pulumi.rb', line 306

def stack_outputs(&block)
  ::Kitchen::Pulumi::Command::Output.run(
    directory: config_directory,
    stack: stack,
    logger: logger,
    &block
  )

  self
rescue ::Kitchen::Pulumi::Error => e
  raise ::Kitchen::ActionFailed, e.message
end

#update(_state, config_only: false) {|temp_conf_file| ... } ⇒ void

This method returns an undefined value.

Sets stack config values via pulumi config and updates the stack via pulumi up

for block {|temp_conf_file| ...}

Parameters:

  • _state (::Hash)

    the current kitchen state

  • config_only (Boolean) (defaults to: false)

    specify true to update the stack config without applying changes to the stack via pulumi up

Yields:

  • (temp_conf_file)

    provides the path to the temporary config file used


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/kitchen/driver/pulumi.rb', line 70

def update(_state, config_only: false)
  dir = "-C #{config_directory}"

  ::Kitchen::Pulumi.with_temp_conf(config_file) do |temp_conf_file|
    
    refresh_config(stack, temp_conf_file, dir) if config_refresh_config
    configure(config_config, stack, temp_conf_file, dir)
    configure(config_secrets, stack, temp_conf_file, dir, is_secret: true)
    update_stack(stack, temp_conf_file, dir) unless config_only

    unless config_stack_evolution.empty?
      evolve_stack(stack, temp_conf_file, dir, config_only: config_only)
    end

    yield temp_conf_file if block_given?
  end
end

#update_stack(stack, conf_file, dir = '') ⇒ void

This method returns an undefined value.

Updates a stack via pulumi up according to instance attributes

Parameters:

  • stack (String)

    name of the stack being refreshed

  • conf_file (String)

    path to a stack config file to use for configuration

  • dir (String) (defaults to: '')

    path to the directory to run Pulumi commands in


223
224
225
226
227
228
229
# File 'lib/kitchen/driver/pulumi.rb', line 223

def update_stack(stack, conf_file, dir = '')
  base_cmd = "up -y -r --show-config -s #{stack} #{dir}"
  ::Kitchen::Pulumi::ShellOut.run(
    cmd: "#{base_cmd} #{config_file(conf_file, flag: true)}",
    logger: logger,
  )
end