terraform-wrapper 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +2 -0
  3. data/Gemfile +0 -4
  4. data/lib/terraform-wrapper.rb +36 -16
  5. data/lib/terraform-wrapper/common.rb +12 -23
  6. data/lib/terraform-wrapper/shared.rb +7 -1
  7. data/lib/terraform-wrapper/shared/auths.rb +9 -0
  8. data/lib/terraform-wrapper/shared/auths/azure.rb +179 -0
  9. data/lib/terraform-wrapper/shared/auths/common.rb +95 -0
  10. data/lib/terraform-wrapper/shared/backends/aws.rb +71 -33
  11. data/lib/terraform-wrapper/shared/backends/azure.rb +38 -39
  12. data/lib/terraform-wrapper/shared/backends/common.rb +18 -14
  13. data/lib/terraform-wrapper/shared/backends/local.rb +20 -18
  14. data/lib/terraform-wrapper/shared/binary.rb +16 -5
  15. data/lib/terraform-wrapper/shared/code.rb +15 -4
  16. data/lib/terraform-wrapper/shared/config.rb +61 -31
  17. data/lib/terraform-wrapper/shared/latest.rb +11 -7
  18. data/lib/terraform-wrapper/shared/logger.rb +80 -0
  19. data/lib/terraform-wrapper/shared/logging.rb +77 -0
  20. data/lib/terraform-wrapper/shared/runner.rb +79 -21
  21. data/lib/terraform-wrapper/shared/variables.rb +66 -0
  22. data/lib/terraform-wrapper/tasks.rb +6 -0
  23. data/lib/terraform-wrapper/tasks/apply.rb +16 -18
  24. data/lib/terraform-wrapper/tasks/binary.rb +26 -23
  25. data/lib/terraform-wrapper/tasks/clean.rb +15 -15
  26. data/lib/terraform-wrapper/tasks/destroy.rb +16 -18
  27. data/lib/terraform-wrapper/tasks/import.rb +66 -0
  28. data/lib/terraform-wrapper/tasks/init.rb +16 -18
  29. data/lib/terraform-wrapper/tasks/plan.rb +16 -18
  30. data/lib/terraform-wrapper/tasks/plandestroy.rb +16 -18
  31. data/lib/terraform-wrapper/tasks/upgrade.rb +58 -0
  32. data/lib/terraform-wrapper/tasks/validate.rb +7 -7
  33. data/lib/terraform-wrapper/version.rb +1 -1
  34. data/terraform-wrapper.gemspec +3 -0
  35. metadata +39 -4
  36. data/lib/terraform-wrapper/shared/identifiers.rb +0 -70
@@ -14,6 +14,10 @@ module TerraformWrapper
14
14
 
15
15
  class Config
16
16
 
17
+ ###############################################################################
18
+
19
+ include TerraformWrapper::Shared::Logging
20
+
17
21
  ###############################################################################
18
22
 
19
23
  @@config_exts = [ "", ".yaml", ".yml" ]
@@ -25,53 +29,79 @@ module TerraformWrapper
25
29
 
26
30
  ###############################################################################
27
31
 
32
+ attr_reader :auths
28
33
  attr_reader :backend
29
34
  attr_reader :base
30
35
  attr_reader :code
31
36
  attr_reader :name
32
37
  attr_reader :path
33
- attr_reader :overrides
34
38
  attr_reader :service
35
39
  attr_reader :variable_files
40
+ attr_reader :variables
36
41
 
37
42
  ###############################################################################
38
43
 
39
- def initialize(backend:, base:, code:, name:, overrides:, service:)
40
- if not name.nil? and name.kind_of?(String) and not name.strip.empty? then
41
- @name = name
42
- else
43
- raise("Configuration name not provided!")
44
- end
44
+ def initialize(code:, options:)
45
+ logger.fatal("Configuration base path must be a String!") unless options["base"].kind_of?(String)
46
+ logger.fatal("Configuration base path must not be blank!") if options["base"].strip.empty?
47
+
48
+ @base = options["base"]
49
+
50
+ logger.fatal("Configuration service name must be a String!") unless options["service"].kind_of?(String)
51
+ logger.fatal("Configuration service name must not be blank!") if options["service"].strip.empty?
52
+
53
+ @service = options["service"]
54
+
55
+ logger.fatal("Configuration name must be a String!") unless options["name"].kind_of?(String)
56
+ logger.fatal("Configuration name must not be blank!") if options["name"].strip.empty?
57
+
58
+ @name = options["name"]
59
+
60
+ logger.fatal("Configuration authenticator for Azure enabled must be a Boolean!") unless [ true, false ].include?(options["auth-azure"])
45
61
 
46
- @base = base
47
- @code = code
48
- @name = name
49
- @overrides = overrides
50
- @service = service
62
+ auth_azure = options["auth-azure"]
51
63
 
64
+ logger.fatal("Configuration authenticator for Azure options must be a Hash!") unless options["auth-azure-options"].kind_of?(Hash)
65
+
66
+ auth_azure_options = options["auth-azure-options"]
67
+
68
+ logger.fatal("Configuration backend name must be a String!") unless options["backend"].kind_of?(String)
69
+ logger.fatal("Configuration backend name must not be blank!") if options["backend"].strip.empty?
70
+
71
+ backend = options["backend"]
72
+
73
+ logger.fatal("Configuration backend options must be a Hash!") unless options["backend-options"].kind_of?(Hash)
74
+
75
+ backend_options = options["backend-options"]
76
+
77
+ @code = code
52
78
  @path = find
53
79
 
54
80
  yaml = YAML.load(File.read(@path))
55
81
 
56
- raise "Invalid YAML in configuration file: #{@path}" unless yaml.kind_of?(Hash)
82
+ logger.fatal("Invalid YAML in configuration file: #{@path}") unless yaml.kind_of?(Hash)
57
83
 
58
- raise "Mandatory key 'identifiers' missing from configuration file: #{@path}" unless yaml.key?("identifiers")
59
- raise "Mandatory key 'identifiers' is not a hash in configuration file: #{@path}" unless yaml["identifiers"].kind_of?(Hash)
60
- raise "Mandatory key 'identifiers' is empty in provided configuration file: #{@path}" if yaml["identifiers"].empty?
84
+ if yaml.key?("variables") then
85
+ logger.fatal("Key 'variables' is not a hash in configuration file: #{@path}") unless yaml["variables"].kind_of?(Hash)
86
+ @variables = TerraformWrapper::Shared::Variables.new(values: yaml["variables"])
87
+ else
88
+ @variables = TerraformWrapper::Shared::Variables.new()
89
+ end
61
90
 
62
- identifiers = TerraformWrapper::Shared::Identifiers.new(identifiers: yaml["identifiers"])
91
+ @variable_files = yaml.key?("terraform") ? validate(variable_files: yaml["terraform"]) : Array.new
92
+
93
+ @auths = Array.new
94
+ @auths.append(TerraformWrapper::Shared::Auths::Azure.new(code: @code, config: @name, options: auth_azure_options, service: @service, variables: @variables)) if auth_azure
63
95
 
64
96
  if backend == "local" then
65
- @backend = TerraformWrapper::Shared::Backends::Local.new(service: @service, code: @code, identifiers: identifiers, overrides: @overrides)
97
+ @backend = TerraformWrapper::Shared::Backends::Local.new(code: @code, config: @name, options: backend_options, service: @service, variables: @variables)
66
98
  elsif backend == "aws" then
67
- @backend = TerraformWrapper::Shared::Backends::AWS.new(service: @service, code: @code, identifiers: identifiers, overrides: @overrides)
99
+ @backend = TerraformWrapper::Shared::Backends::AWS.new(code: @code, config: @name, options: backend_options, service: @service, variables: @variables)
68
100
  elsif backend == "azure" then
69
- @backend = TerraformWrapper::Shared::Backends::Azure.new(service: @service, code: @code, identifiers: identifiers, overrides: @overrides)
101
+ @backend = TerraformWrapper::Shared::Backends::Azure.new(code: @code, config: @name, options: backend_options, service: @service, variables: @variables)
70
102
  else
71
- raise "Backend: #{backend} is not valid!"
103
+ logger.fatal("Backend: #{backend} is not valid!")
72
104
  end
73
-
74
- @variable_files = yaml.key?("tfvars") ? tfvars(tfvars: yaml["tfvars"]) : Array.new
75
105
  end
76
106
 
77
107
  ###############################################################################
@@ -86,24 +116,24 @@ module TerraformWrapper
86
116
  return path if File.file?(path)
87
117
  end
88
118
 
89
- raise "Terraform configuration name: #{@name} not found in location: #{@base}!"
119
+ logger.fatal("Terraform configuration name: #{@name} not found in location: #{@base}!")
90
120
  end
91
121
 
92
122
  ###############################################################################
93
123
 
94
- def tfvars(tfvars:)
95
- raise("Optional key 'tfvars' must be a list of strings!") unless tfvars.kind_of?(Array)
124
+ def validate(variable_files:)
125
+ logger.fatal("Optional key 'variable_files' must be a list of strings!") unless variable_files.kind_of?(Array)
96
126
 
97
127
  result = Array.new
98
128
 
99
- tfvars.each do |tfvar|
100
- raise("All elements of 'tfvars' must be strings!") unless tfvar.kind_of?(String)
101
- raise("All elements of 'tfvars' must not be blank!") if tfvar.strip.empty?
102
- path = File.join(@base, @@variable_files_name, tfvar.strip + @@variable_files_ext)
129
+ variable_files.each do |variable_file|
130
+ logger.fatal("All elements of 'variable_files' must be strings!") unless variable_file.kind_of?(String)
131
+ logger.fatal("All elements of 'variable_files' must not be blank!") if variable_file.strip.empty?
132
+ path = File.join(@base, @@variable_files_name, variable_file.strip + @@variable_files_ext)
103
133
  if File.file?(path) then
104
134
  result.append(path)
105
135
  else
106
- raise("Terraform variables file: #{tfvar}, path: #{path} does not exist!")
136
+ logger.fatal("Terraform variables file: #{variable_file}, path: #{path} does not exist!")
107
137
  end
108
138
  end
109
139
 
@@ -21,6 +21,10 @@ module TerraformWrapper
21
21
 
22
22
  include Singleton
23
23
 
24
+ ###############################################################################
25
+
26
+ include TerraformWrapper::Shared::Logging
27
+
24
28
  ###############################################################################
25
29
 
26
30
  @version
@@ -40,22 +44,22 @@ module TerraformWrapper
40
44
  ###############################################################################
41
45
 
42
46
  def refresh
43
- $logger.info("Finding latest available Terraform release...")
47
+ logger.info("Finding latest available Terraform release...")
44
48
 
45
49
  response = Net::HTTP.get_response(URI("https://checkpoint-api.hashicorp.com/v1/check/terraform"))
46
50
 
47
- raise "Hashicorp Checkpoint did not return status 200 for latest version check!" if response.code != "200"
48
- raise "Response body from Hashicorp Checkpoint is not permitted!" if not response.class.body_permitted?
49
- raise "Response body from Hashicorp Checkpoint is empty!" if response.body.nil?
51
+ logger.fatal("Hashicorp Checkpoint did not return status 200 for latest version check!") if response.code != "200"
52
+ logger.fatal("Response body from Hashicorp Checkpoint is not permitted!") if not response.class.body_permitted?
53
+ logger.fatal("Response body from Hashicorp Checkpoint is empty!") if response.body.nil?
50
54
 
51
55
  body = JSON.parse(response.body)
52
56
 
53
- raise "Hashicorp Checkpoint JSON response did not include latest available Terraform version!" if not body.key?("current_version")
54
- raise "Hashicorp Checkpoint indicated latest available version of Terraform is blank!" if body["current_version"].empty?
57
+ logger.fatal("Hashicorp Checkpoint JSON response did not include latest available Terraform version!") if not body.key?("current_version")
58
+ logger.fatal("Hashicorp Checkpoint indicated latest available version of Terraform is blank!") if body["current_version"].empty?
55
59
 
56
60
  version = body["current_version"]
57
61
 
58
- $logger.info("Latest available Terraform release found: #{version}")
62
+ logger.success("Latest available Terraform release found: #{version}")
59
63
 
60
64
  return version
61
65
  end
@@ -0,0 +1,80 @@
1
+ ###############################################################################
2
+
3
+ require 'logger'
4
+
5
+ ###############################################################################
6
+
7
+ module TerraformWrapper
8
+
9
+ ###############################################################################
10
+
11
+ module Shared
12
+
13
+ ###############################################################################
14
+
15
+ class Logger < ::Logger
16
+
17
+ ###############################################################################
18
+
19
+ @colour = false
20
+
21
+ ###############################################################################
22
+
23
+ def colour()
24
+ @colour
25
+ end
26
+
27
+ ###############################################################################
28
+
29
+ def colour=(enabled)
30
+ @colour = [ true, false ].include?(enabled) ? enabled : false
31
+ end
32
+
33
+ ###############################################################################
34
+
35
+ def success(message)
36
+ info(format(colour: 32, message: message))
37
+ end
38
+
39
+ ###############################################################################
40
+
41
+ def warn(message)
42
+ super(format(colour: 33, message: message))
43
+ end
44
+
45
+ ###############################################################################
46
+
47
+ def error(message)
48
+ super(format(colour: 31, message: message))
49
+ end
50
+
51
+ ###############################################################################
52
+
53
+ def fatal(message)
54
+ super(format(colour: 31, message: message))
55
+ exit(1)
56
+ end
57
+
58
+ ###############################################################################
59
+
60
+ private
61
+
62
+ ###############################################################################
63
+
64
+ def format(colour: 32, message:)
65
+ return @colour ? "\e[" + colour.to_s + "m" + message + "\e[0m" : message
66
+ end
67
+
68
+ ###############################################################################
69
+
70
+ end
71
+
72
+ ###############################################################################
73
+
74
+ end
75
+
76
+ ###############################################################################
77
+
78
+ end
79
+
80
+ ###############################################################################
@@ -0,0 +1,77 @@
1
+ ###############################################################################
2
+
3
+ require 'logger'
4
+
5
+ ###############################################################################
6
+
7
+ module TerraformWrapper
8
+
9
+ ###############################################################################
10
+
11
+ module Shared
12
+
13
+ ###############################################################################
14
+
15
+ module Logging
16
+
17
+ ###############################################################################
18
+
19
+ private
20
+
21
+ ###############################################################################
22
+
23
+ def logger
24
+ @logger ||= Logging.logger_for(self.class.name)
25
+ end
26
+
27
+ ###############################################################################
28
+
29
+ @loggers = {}
30
+
31
+ ###############################################################################
32
+
33
+ class << self
34
+
35
+ ###############################################################################
36
+
37
+ def logger_for(classname)
38
+ @loggers[classname] ||= configure_logger_for(classname)
39
+ end
40
+
41
+ ###############################################################################
42
+
43
+ def configure_logger_for(classname)
44
+ colour = ENV["TERRAFORM_WRAPPER_LOG_COLOUR"] || "true"
45
+ level = ENV["TERRAFORM_WRAPPER_LOG_LEVEL"] || "INFO"
46
+
47
+ logger = ::TerraformWrapper::Shared::Logger.new(STDOUT)
48
+
49
+ logger.colour = colour.downcase == "true"
50
+ logger.level = level.upcase
51
+ logger.progname = classname
52
+
53
+ logger.formatter = proc do |severity, datetime, progname, msg|
54
+ sevId = severity.chars.first.upcase
55
+ "[#{sevId}] [#{progname}] #{msg}\n"
56
+ end
57
+
58
+ logger
59
+ end
60
+
61
+ ###############################################################################
62
+
63
+ end
64
+
65
+ ###############################################################################
66
+
67
+ end
68
+
69
+ ###############################################################################
70
+
71
+ end
72
+
73
+ ###############################################################################
74
+
75
+ end
76
+
77
+ ###############################################################################
@@ -10,6 +10,10 @@ module TerraformWrapper
10
10
 
11
11
  class Runner
12
12
 
13
+ ###############################################################################
14
+
15
+ include TerraformWrapper::Shared::Logging
16
+
13
17
  ###############################################################################
14
18
 
15
19
  attr_reader :binary
@@ -30,75 +34,111 @@ module TerraformWrapper
30
34
 
31
35
  ###############################################################################
32
36
 
33
- def download
34
- parameters = [ "-backend=false" ]
37
+ def download(upgrade: false)
38
+ parameters = Array.new
39
+ parameters.append("-backend=false")
40
+ parameters.append("-upgrade") if upgrade
41
+
35
42
  @downloaded = run(action: "init", parameters: parameters)
36
- raise("Failed to download Terraform modules.") unless @downloaded
43
+ logger.fatal("Failed to download Terraform modules.") unless @downloaded
44
+ end
45
+
46
+ ###############################################################################
47
+
48
+ def import(address: nil, id: nil)
49
+ logger.fatal("Cannot Terraform import before initialising backend!") unless initialised
50
+
51
+ logger.fatal("Terraform state address for import must be a String!") unless address.kind_of?(String)
52
+ logger.fatal("Terraform state address for import must be a String!") unless address.kind_of?(String)
53
+ logger.fatal("Terraform state address for import must not be blank!") if address.strip.empty?
54
+
55
+ logger.fatal("Identification for infrastructure to import must be a String!") unless id.kind_of?(String)
56
+ logger.fatal("Identification for infrastructure to import must not be blank!") if id.strip.empty?
57
+
58
+ parameters = Array.new
59
+ parameters.concat(variable_files)
60
+ parameters.concat(variable_strings)
61
+
62
+ parameters.append("'#{address}'")
63
+ parameters.append("'#{id}'")
64
+
65
+ logger.fatal("Terraform import failed!") unless run(action: "import", parameters: parameters)
37
66
  end
38
67
 
39
68
  ###############################################################################
40
69
 
41
70
  def init(config:)
42
- parameters = [ "-reconfigure" ]
71
+ parameters = Array.new
72
+ parameters.append("-reconfigure")
73
+
43
74
  config.backend.hash.each do |key, value|
44
75
  parameters.append("-backend-config=\"#{key}=#{value}\"")
45
76
  end
46
77
 
78
+ config.auths.map(&:auth)
79
+
47
80
  @config = config
48
81
  @initialised = run(action: "init", parameters: parameters)
49
- raise("Failed to initialise Terraform with backend.") unless @initialised
82
+ logger.fatal("Failed to initialise Terraform with backend.") unless @initialised
50
83
  end
51
84
 
52
85
  ###############################################################################
53
86
 
54
87
  def plan(destroy: false, file: nil)
55
- raise("Cannot Terraform plan before initialising backend!") unless initialised
88
+ logger.fatal("Cannot Terraform plan before initialising backend!") unless initialised
56
89
 
57
- parameters = variable_files
90
+ parameters = Array.new
91
+ parameters.concat(variable_files)
92
+ parameters.concat(variable_strings)
58
93
 
59
94
  if not file.nil? and file.kind_of?(String) and not file.strip.empty? then
60
- raise "Failed to create plan directory: #{directory}" unless create_directory(directory: File.dirname(file), purpose: "plan")
95
+ logger.fatal("Failed to create plan directory: #{directory}") unless ::TerraformWrapper.create_directory(directory: File.dirname(file), purpose: "plan")
61
96
  parameters.append("-out=\"#{file}\"")
62
97
  end
63
98
 
64
99
  parameters.append("-destroy") if destroy
65
100
 
66
- raise("Terraform plan failed!") unless run(action: "plan", parameters: parameters)
101
+ logger.fatal("Terraform plan failed!") unless run(action: "plan", parameters: parameters)
67
102
  end
68
103
 
69
104
  ###############################################################################
70
105
 
71
106
  def apply(file: nil)
72
- raise("Cannot Terraform apply before initialising backend!") unless initialised
107
+ logger.fatal("Cannot Terraform apply before initialising backend!") unless initialised
73
108
 
74
- parameters = [ "-auto-approve" ]
109
+ parameters = Array.new
110
+ parameters.concat(variable_files)
111
+ parameters.concat(variable_strings)
112
+ parameters.append("-auto-approve")
75
113
 
76
114
  if not file.nil? and file.kind_of?(String) and not file.strip.empty? then
77
- raise "Plan file: #{file} does not exist!" unless File.file?(file)
115
+ logger.fatal("Plan file: #{file} does not exist!") unless File.file?(file)
78
116
  parameters.append("\"#{file}\"")
79
117
  else
80
118
  parameters.concat(variable_files)
81
119
  end
82
120
 
83
- raise("Terraform apply failed!") unless run(action: "apply", parameters: parameters)
121
+ logger.fatal("Terraform apply failed!") unless run(action: "apply", parameters: parameters)
84
122
  end
85
123
 
86
124
  ###############################################################################
87
125
 
88
126
  def destroy
89
- raise("Cannot Terraform destroy before initialising backend!") unless initialised
127
+ logger.fatal("Cannot Terraform destroy before initialising backend!") unless initialised
90
128
 
91
- parameters = [ "-auto-approve" ]
129
+ parameters = Array.new
92
130
  parameters.concat(variable_files)
131
+ parameters.concat(variable_strings)
132
+ parameters.append("-auto-approve")
93
133
 
94
- raise("Terraform destroy failed!") unless run(action: "destroy", parameters: parameters)
134
+ logger.fatal("Terraform destroy failed!") unless run(action: "destroy", parameters: parameters)
95
135
  end
96
136
 
97
137
  ###############################################################################
98
138
 
99
139
  def validate
100
- raise("Cannot Terraform validate before downloading modules!") unless downloaded
101
- raise("Terraform validation failed!") unless run(action: "validate")
140
+ logger.fatal("Cannot Terraform validate before downloading modules!") unless downloaded
141
+ logger.fatal("Terraform validation failed!") unless run(action: "validate")
102
142
  end
103
143
 
104
144
  ###############################################################################
@@ -108,7 +148,7 @@ module TerraformWrapper
108
148
  ###############################################################################
109
149
 
110
150
  def variable_files
111
- raise("Cannot generate variable files until Terraform has been initialised!") unless @initialised
151
+ logger.fatal("Cannot generate variable files until Terraform has been initialised!") unless @initialised
112
152
 
113
153
  result = Array.new
114
154
 
@@ -119,6 +159,24 @@ module TerraformWrapper
119
159
  return result
120
160
  end
121
161
 
162
+ ###############################################################################
163
+
164
+ def variable_strings
165
+ logger.fatal("Cannot generate variable strings until Terraform has been initialised!") unless @initialised
166
+
167
+ result = Array.new
168
+
169
+ result.append("-var=\"component=#{@code.name}\"")
170
+ result.append("-var=\"config=#{@config.name}\"")
171
+ result.append("-var=\"service=#{@config.service}\"")
172
+
173
+ @config.variables.values.each do |key, value|
174
+ result.append("-var=\"#{key.to_s}=#{value}\"")
175
+ end
176
+
177
+ return result
178
+ end
179
+
122
180
  ###############################################################################
123
181
 
124
182
  def run(action:, parameters: Array.new)
@@ -128,14 +186,14 @@ module TerraformWrapper
128
186
 
129
187
  cmdline = [ "\"#{@binary.path}\"", action ].concat(parameters).join(" ")
130
188
 
131
- $logger.info("Starting Terraform, action: #{action}")
189
+ logger.info("Starting Terraform, action: #{action}")
132
190
 
133
191
  puts("\n" + ('#' * 80) + "\n\n")
134
192
 
135
193
  Dir.chdir(@code.path)
136
194
  result = system(cmdline) || false
137
195
 
138
- puts("\n")
196
+ puts("\n" + ('#' * 80) + "\n\n")
139
197
 
140
198
  return result
141
199
  end