Class: TerraformDSL::Deployment

Inherits:
Object
  • Object
show all
Defined in:
lib/scout/terraform_dsl/deployment.rb

Overview

Manage Terraform deployments

Defined Under Namespace

Classes: TerraformException

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_dir) ⇒ Deployment

Create a new deployment on a given directory. Templates and modules will reside on the directory and can be used by terraform

Parameters:

  • config_dir (String)

    path to the deployment directory



87
88
89
90
91
# File 'lib/scout/terraform_dsl/deployment.rb', line 87

def initialize(config_dir)
  @directory = (Path === config_dir) ? config_dir.find : config_dir

  @init = false
end

Instance Attribute Details

#directoryObject

Returns the value of attribute directory.



80
81
82
# File 'lib/scout/terraform_dsl/deployment.rb', line 80

def directory
  @directory
end

Class Method Details

.load(file, directory = nil) ⇒ Object



272
273
274
275
276
277
278
279
280
281
# File 'lib/scout/terraform_dsl/deployment.rb', line 272

def self.load(file, directory = nil)
  directory ||= WORK_DIR[TerraformDSL.obj2digest(file)]
  TerraformDSL.log "Load #{file} bundle into #{directory}", "TerraformDSL::Deployment"
  Misc.in_dir directory do
    `tar xvfz #{file}`
  end
  deployment = TerraformDSL::Deployment.new directory
  deployment.refresh
  deployment
end

.run(cmd) ⇒ String

Run a terraform command returning the STDOUT as a String. Forwards STDERR of the process

Parameters:

  • cmd (String)

    terraform command to run (not including terraform command name)

Returns:

  • (String)

    STDOUT of the process



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/scout/terraform_dsl/deployment.rb', line 18

def self.run(cmd)
  Open3.popen3("terraform #{cmd}") do |stdin, stdout, stderr, wait_thr|
    TerraformDSL.log "Running: terraform #{cmd}", wait_thr.pid
    stdin.close
    stderr_thr = Thread.new do
      while (line = stderr.gets)
        TerraformDSL.log line, wait_thr.pid
      end
    end
    out = stdout.read
    exit_status = wait_thr.value
    raise TerraformException, out.split(/Error:\s*/m).last if exit_status != 0

    stderr_thr.join
    out
  end
end

.run_log(cmd, log_file = nil) ⇒ Object

Run a terraform command loging STDERR and STDOUT of the process to STDERR and to a log file.

Parameters:

  • cmd (String)

    terraform command to run (not including terraform command name)

  • log_file (String) (defaults to: nil)

    path to the log file (optional)



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/scout/terraform_dsl/deployment.rb', line 42

def self.run_log(cmd, log_file = nil)
  log_io = Open.open(log_file, mode: 'a') if log_file
  log_io.sync = true if log_io
  Open3.popen3("terraform #{cmd}") do |stdin, stdout, stderr, wait_thr|
    TerraformDSL.log "Running: terraform #{cmd}", wait_thr.pid
    stdin.close
    wait_thr.pid
    stdin.close
    stderr_thr = Thread.new do
      while (line = stderr.gets)
        TerraformDSL.log line, [wait_thr.pid, :STDERR] * " - "
        log_io.puts "[#{Time.now} - STDERR]: " + line if log_io
      end
    end
    stdout_thr = Thread.new do
      while (line = stdout.gets)
        TerraformDSL.log line, [wait_thr.pid, :STDOUT] * " - "
        log_io.puts "[#{Time.now} - STDOUT]: " + line if log_io
      end
    end
    exit_status = wait_thr.value

    stderr_thr.join
    stdout_thr.join
    log_io.close if log_io

    if exit_status != 0
      log_io.close if log_io
      log_txt = Open.read(log_file, :encoding => "UTF-8")
      error_msg = log_txt.split(/Error:/).last
      error_msg = error_msg.split("\n").collect{|e| e.sub(/.*? STD...\]:\s*/,'') } * "\n"
      raise TerraformException, error_msg
    end

  end
  nil
end

Instance Method Details

#applyObject

Applies a terraform deployment by running the plan_file.



141
142
143
144
145
146
# File 'lib/scout/terraform_dsl/deployment.rb', line 141

def apply
  plan unless @planned
  Misc.in_dir @directory do
    Deployment.run_log("apply -auto-approve #{plan_file}", log_file)
  end
end

#bundle(file) ⇒ Object

Raises:



253
254
255
256
257
258
259
260
261
262
# File 'lib/scout/terraform_dsl/deployment.rb', line 253

def bundle(file)
  raise TerraformException, "Target bundle file is nil" if file.nil?
  TerraformDSL.log "Bundle #{@directory} in #{file}", "TerraformDSL::Deployment"
  Misc.in_dir @directory do
    cmd = "tar cvfz '#{file}' *"
    cmd += ' *.lock.hcl' if Dir.glob('*.lock.hcl').any?
    cmd += ' > /dev/null'
    system(cmd)
  end
end

#delete(element) ⇒ Object

Delete an element of a deployment. Removes the definition file and the output file

Parameters:

  • element (String)

    name of the element to destroy



246
247
248
249
250
251
# File 'lib/scout/terraform_dsl/deployment.rb', line 246

def delete(element)
  [element + '.tf', element + '.output.tf'].each do |file|
    path = @directory[file]
    Open.rm path
  end
end

#destroyObject

Destroys a provision



193
194
195
196
197
# File 'lib/scout/terraform_dsl/deployment.rb', line 193

def destroy
  Misc.in_dir @directory do
    Deployment.run_log('destroy -auto-approve', log_file)
  end
end

#element_state(element) ⇒ String

Return the state of a provisioned element

Returns:

  • (String)

    state of the element in the original terraform format



186
187
188
189
190
# File 'lib/scout/terraform_dsl/deployment.rb', line 186

def element_state(element)
  Misc.in_dir @directory do
    Deployment.run("state show '#{element}'")
  end
end

#initObject

Initialize deployment @directory with all the templates and modules. Sets @init to true and @planned to false. Removes plan_file if present



105
106
107
108
109
110
111
112
113
# File 'lib/scout/terraform_dsl/deployment.rb', line 105

def init
  Misc.in_dir @directory.find do
    Deployment.run_log 'init', log_file
  end
  Open.rm plan_file if Open.exist?(plan_file)
  @init = true
  @planned = false
  nil
end

#log_fileString

Returns File where the logs will be stored.

Returns:

  • (String)

    File where the logs will be stored



99
100
101
# File 'lib/scout/terraform_dsl/deployment.rb', line 99

def log_file
  @directory.log
end

#output(name, output) ⇒ Hash

Returns the value of an output for a given module in the current deployment

Parameters:

  • name (String)

    name of the module

  • output (String)

    name of the module output variable

Returns:

  • (Hash)

    containg the output names and their values



223
224
225
226
227
# File 'lib/scout/terraform_dsl/deployment.rb', line 223

def output(name, output)
  name = name.name if defined?(TerraformDSL::Module) && name.is_a?(TerraformDSL::Module)

  outputs[[name, output].join('_')]
end

#outputsHash

Returns the outputs available for a current deployment

Returns:

  • (Hash)

    containg the output names (module.variable_name) and their values



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/scout/terraform_dsl/deployment.rb', line 202

def outputs
  outputs = {}

  Misc.in_dir @directory do
    output_info = JSON.parse(Deployment.run('output -json'))

    output_info.each do |output, info|
      outputs[output] = info['value']
    end
  end

  outputs
end

#planObject

Plan a terraform deployment and save it in #plan_file. Runs init if required. Saves the time in @planned



132
133
134
135
136
137
138
# File 'lib/scout/terraform_dsl/deployment.rb', line 132

def plan
  init unless @init
  Misc.in_dir @directory.find do
    Deployment.run_log("plan -out #{plan_file}", log_file)
  end
  @planned = Time.now
end

#plan_fileString

Returns File where the terraform plan will be stored.

Returns:

  • (String)

    File where the terraform plan will be stored



94
95
96
# File 'lib/scout/terraform_dsl/deployment.rb', line 94

def plan_file
  @directory['main.plan']
end

#provisioned_elementsArray

Lists all provisioned elements

Returns:

  • (Array)

    with names of provisioned elements



158
159
160
161
162
163
164
165
166
# File 'lib/scout/terraform_dsl/deployment.rb', line 158

def provisioned_elements
  Misc.in_dir @directory do
    begin
      Deployment.run('state list').split("\n")
    rescue StandardError
      []
    end
  end
end

#refreshObject



148
149
150
151
152
153
# File 'lib/scout/terraform_dsl/deployment.rb', line 148

def refresh
  plan unless @planned
  Misc.in_dir @directory do
    Deployment.run_log('refresh', log_file)
  end
end

#templatesHash

Lists all provisioned elements

Returns:

  • (Hash)

    with templates organized by module type



171
172
173
174
175
176
177
178
179
180
# File 'lib/scout/terraform_dsl/deployment.rb', line 171

def templates
  elements = {}
  @directory.glob("*.tf").each do |file|
    if m = File.basename(file).match(/^([^.]+)\.([^.]+)\.tf/)
      elements[m[1]] ||= []
      elements[m[1]] << m[2]
    end
  end
  elements
end

#updateObject

Update changes on a terraform deployment by running init, plan, and apply



116
117
118
119
120
# File 'lib/scout/terraform_dsl/deployment.rb', line 116

def update
  init
  plan
  apply
end

#validateObject

Validate a terraform deployment. Runs init if required



123
124
125
126
127
128
# File 'lib/scout/terraform_dsl/deployment.rb', line 123

def validate
  init unless @init
  Misc.in_dir @directory do
    Deployment.run('validate')
  end
end

#with_bundle(&block) ⇒ Object



264
265
266
267
268
269
270
# File 'lib/scout/terraform_dsl/deployment.rb', line 264

def with_bundle(&block)
  name = 'deployment-bundle-tmp_' + rand(100000).to_s + '.tar.gz'
  TmpFile.with_file nil, extension: 'deployment_bundle' do |tmpfile|
    bundle(file)
    yield file
  end
end

#with_deploymentObject

Apply a deployment, run a block of code, and destroy the deployment afterwards

Returns:

  • whatever the block returns



233
234
235
236
237
238
239
240
# File 'lib/scout/terraform_dsl/deployment.rb', line 233

def with_deployment
  begin
    apply
    yield
  ensure
    destroy
  end
end