terraform-wrapper 0.0.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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