terraform-wrapper 0.0.2 → 0.1.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/terraform-wrapper.rb +34 -16
  3. data/lib/terraform-wrapper/common.rb +12 -23
  4. data/lib/terraform-wrapper/shared.rb +7 -1
  5. data/lib/terraform-wrapper/shared/auths.rb +9 -0
  6. data/lib/terraform-wrapper/shared/auths/azure.rb +179 -0
  7. data/lib/terraform-wrapper/shared/auths/common.rb +95 -0
  8. data/lib/terraform-wrapper/shared/backends/aws.rb +34 -29
  9. data/lib/terraform-wrapper/shared/backends/azure.rb +38 -39
  10. data/lib/terraform-wrapper/shared/backends/common.rb +18 -14
  11. data/lib/terraform-wrapper/shared/backends/local.rb +20 -18
  12. data/lib/terraform-wrapper/shared/binary.rb +16 -5
  13. data/lib/terraform-wrapper/shared/code.rb +15 -4
  14. data/lib/terraform-wrapper/shared/config.rb +61 -31
  15. data/lib/terraform-wrapper/shared/latest.rb +11 -7
  16. data/lib/terraform-wrapper/shared/logger.rb +80 -0
  17. data/lib/terraform-wrapper/shared/logging.rb +77 -0
  18. data/lib/terraform-wrapper/shared/runner.rb +59 -22
  19. data/lib/terraform-wrapper/shared/variables.rb +66 -0
  20. data/lib/terraform-wrapper/tasks/apply.rb +16 -14
  21. data/lib/terraform-wrapper/tasks/binary.rb +25 -21
  22. data/lib/terraform-wrapper/tasks/clean.rb +15 -11
  23. data/lib/terraform-wrapper/tasks/destroy.rb +16 -14
  24. data/lib/terraform-wrapper/tasks/init.rb +16 -14
  25. data/lib/terraform-wrapper/tasks/plan.rb +16 -14
  26. data/lib/terraform-wrapper/tasks/plandestroy.rb +16 -14
  27. data/lib/terraform-wrapper/tasks/validate.rb +7 -3
  28. data/lib/terraform-wrapper/version.rb +1 -1
  29. metadata +8 -3
  30. data/lib/terraform-wrapper/shared/identifiers.rb +0 -70
@@ -16,9 +16,7 @@ module TerraformWrapper
16
16
 
17
17
  ###############################################################################
18
18
 
19
- @@default_class = "state"
20
- @@default_container = "default"
21
- @@default_suffix = "tf"
19
+ include TerraformWrapper::Shared::Logging
22
20
 
23
21
  ###############################################################################
24
22
 
@@ -27,16 +25,14 @@ module TerraformWrapper
27
25
  ###############################################################################
28
26
 
29
27
  attr_reader :account
30
- attr_reader :class
31
28
  attr_reader :container
32
29
  attr_reader :group
33
30
  attr_reader :key
34
- attr_reader :suffix
35
31
 
36
32
  ###############################################################################
37
33
 
38
- def initialize(service:, code:, identifiers:, overrides: Hash.new)
39
- construct(service: service, code: code, identifiers: identifiers, overrides: overrides)
34
+ def initialize(code:, config:, options:, service:, variables:)
35
+ construct(code: code, config: config, options: options, service: service, variables: variables)
40
36
  end
41
37
 
42
38
  ###############################################################################
@@ -57,48 +53,51 @@ module TerraformWrapper
57
53
  ###############################################################################
58
54
 
59
55
  def specific()
60
- if @overrides.key?("class") and @overrides["class"].kind_of?(String) and not @overrides["class"].strip.empty? then
61
- @class = @overrides["class"]
62
- else
63
- @class = @@default_class
64
- end
56
+ logger.fatal("Azure backend mandatory option 'group' has not been set!") unless @options.key?("group")
65
57
 
66
- if @overrides.key?("container") and @overrides["container"].kind_of?(String) and not @overrides["container"].strip.empty? then
67
- @container = @overrides["container"]
68
- else
69
- @container = @@default_container
70
- end
58
+ group = @options["group"]
71
59
 
72
- if @overrides.key?("suffix") and @overrides["suffix"].kind_of?(String) and not @overrides["suffix"].strip.empty? then
73
- @suffix = @overrides["suffix"]
74
- else
75
- @suffix = @@default_suffix
76
- end
60
+ logger.fatal("Azure backend group must be a String!") unless group.kind_of?(String)
61
+ logger.fatal("Azure backend group must not be blank!") if group.strip.empty?
77
62
 
78
- if @overrides.key?("group") and @overrides["group"].kind_of?(String) and not @overrides["group"].strip.empty? then
79
- @group = @overrides["group"]
80
- else
81
- raise "Mandatory identifier 'account' or override 'group' must be set in provided configuration to backend of type: #{@@type}" unless @identifier.key?("account")
82
- raise "Mandatory identifier 'account' is not a string in provided configuration to backend of type: #{@@type}" unless @identifier["account"].kind_of?(String)
83
- raise "Mandatory identifier 'account' is empty in provided configuration to backend of type: #{@@type}" if @identifier["account"].strip.empty?
63
+ account = @options.key?("account") ? @options["account"] : group + "tf"
84
64
 
85
- @group = @identifiers["subscription"].strip.downcase + @suffix
86
- end
65
+ logger.fatal("Azure backend storage account must be a String!") unless account.kind_of?(String)
66
+ logger.fatal("Azure backend storage account must not be blank!") if account.strip.empty?
87
67
 
88
- if @overrides.key?("account") and @overrides["account"].kind_of?(String) and not @overrides["account"].strip.empty? then
89
- @account = @overrides["account"]
90
- else
91
- @account = @group + "-" + @class
92
- end
68
+ container = @options.key?("container") ? @options["container"] : "default"
69
+
70
+ logger.fatal("Azure backend storage account container must be a String!") unless container.kind_of?(String)
71
+ logger.fatal("Azure backend storage account container must not be blank!") if container.strip.empty?
72
+
73
+ key = @options.key?("key") ? @options["key"] : File.join("%{service}", "%{config}", "%{component}" + @@ext)
93
74
 
94
- key = File.join(@identifiers.path, @service, @component + @@ext)
75
+ logger.fatal("Azure backend storage account key must be a String!") unless key.kind_of?(String)
76
+ logger.fatal("Azure backend storage account key must not be blank!") if key.strip.empty?
77
+
78
+ logger.fatal("Azure backend container or key must include %{service}.") unless (container.include?("%{service}") or key.include?("%{service}"))
79
+ logger.fatal("Azure backend container or key must include %{config}.") unless (container.include?("%{config}") or key.include?("%{config}"))
80
+ logger.fatal("Azure backend container or key must include %{component}.") unless (container.include?("%{component}") or key.include?("%{component}"))
81
+
82
+ begin
83
+ group = group % @variables
84
+ account = account % @variables
85
+ container = container % @variables
86
+ key = key % @variables
87
+ rescue
88
+ logger.fatal("Azure backend options contain variables that are not included in the configuration file!")
89
+ end
95
90
 
96
91
  if key.length > 1024 then
97
- raise "Key: #{key} is too long for backend of type: #{@@type}"
92
+ logger.fatal("Key: #{key} is too long for backend of type: #{@@type}")
98
93
  else
99
- $logger.warning("Key for backend of type: #{@@type} exceeds 256 characters. This will not work with the Azure Storage Emulator. If key is not being overriden, consider using less identifiers.") if key.length > 256
100
- @key = key
94
+ logger.warn("Key for backend of type: #{@@type} exceeds 256 characters. This will not work with the Azure Storage Emulator. If key is not being overriden, consider using less identifiers.") if key.length > 256
101
95
  end
96
+
97
+ @group = group
98
+ @account = account
99
+ @container = container
100
+ @key = key
102
101
  end
103
102
 
104
103
  ###############################################################################
@@ -14,6 +14,10 @@ module TerraformWrapper
14
14
 
15
15
  class Common
16
16
 
17
+ ###############################################################################
18
+
19
+ include TerraformWrapper::Shared::Logging
20
+
17
21
  ###############################################################################
18
22
 
19
23
  @@ext = ".tfstate"
@@ -21,27 +25,25 @@ module TerraformWrapper
21
25
 
22
26
  ###############################################################################
23
27
 
24
- @component
25
- @identifiers
26
- @overrides
27
- @service
28
+ @options
29
+ @variables
28
30
 
29
31
  ###############################################################################
30
32
 
31
- def initialize(service:, code:, identifiers:, overrides: Hash.new)
32
- raise("This class should not be used directly! Please create a backend-specific class instead!")
33
+ def initialize(code:, config:, options:, service:, variables:)
34
+ logger.fatal("This class should not be used directly! Please create a backend-specific class instead!")
33
35
  end
34
36
 
35
37
  ###############################################################################
36
38
 
37
39
  def hash()
38
- raise("The backend specific class should override the 'hash' method to return a hash of parameters for Terraform to set!")
40
+ logger.fatal("The backend specific class should override the 'hash' method to return a hash of parameters for Terraform to set!")
39
41
  end
40
42
 
41
43
  ###############################################################################
42
44
 
43
45
  def type()
44
- raise("The backend specific class should set the 'type' class variable to a string!") unless @@type.kind_of?(String)
46
+ logger.fatal("The backend specific class should set the 'type' class variable to a string!") unless @@type.kind_of?(String)
45
47
 
46
48
  return @@type
47
49
  end
@@ -52,11 +54,13 @@ module TerraformWrapper
52
54
 
53
55
  ###############################################################################
54
56
 
55
- def construct(service:, code:, identifiers:, overrides:)
56
- @component = code.name
57
- @identifiers = identifiers
58
- @overrides = overrides
59
- @service = service
57
+ def construct(code:, config:, options:, service:, variables:)
58
+ @options = options
59
+ @variables = variables.values.merge({
60
+ component: code.name,
61
+ config: config,
62
+ service: service
63
+ })
60
64
 
61
65
  specific
62
66
  end
@@ -64,7 +68,7 @@ module TerraformWrapper
64
68
  ###############################################################################
65
69
 
66
70
  def specific()
67
- raise("The backend specific class should override the 'specific' method to include backend specific validation and setup, or simply return 'true' if it is not required.")
71
+ logger.fatal("The backend specific class should override the 'specific' method to include backend specific validation and setup, or simply return 'true' if it is not required.")
68
72
  end
69
73
 
70
74
  ###############################################################################
@@ -16,7 +16,7 @@ module TerraformWrapper
16
16
 
17
17
  ###############################################################################
18
18
 
19
- @@default_base = File.join(Dir.pwd, "state", "terraform")
19
+ include TerraformWrapper::Shared::Logging
20
20
 
21
21
  ###############################################################################
22
22
 
@@ -24,23 +24,19 @@ module TerraformWrapper
24
24
 
25
25
  ###############################################################################
26
26
 
27
- attr_reader :base
28
- attr_reader :directory
29
- attr_reader :key
30
- attr_reader :name
31
27
  attr_reader :path
32
28
 
33
29
  ###############################################################################
34
30
 
35
- def initialize(service:, code:, identifiers:, overrides: Hash.new)
36
- construct(service: service, code: code, identifiers: identifiers, overrides: overrides)
31
+ def initialize(code:, config:, options:, service:, variables:)
32
+ construct(code: code, config: config, options: options, service: service, variables: variables)
37
33
  end
38
34
 
39
35
  ###############################################################################
40
36
 
41
37
  def hash()
42
38
  return {
43
- "path" => @path,
39
+ "path" => @path
44
40
  }
45
41
  end
46
42
 
@@ -51,20 +47,26 @@ module TerraformWrapper
51
47
  ###############################################################################
52
48
 
53
49
  def specific()
54
- if @overrides.key?("base") and @overrides["base"].kind_of?(String) and not @overrides["base"].strip.empty? then
55
- @base = @overrides["base"]
56
- else
57
- @base = @@default_base
50
+ path = @options.key?("path") ? @options["path"] : File.join(Dir.pwd, "state", "terraform", "%{config}", "%{component}" + @@ext)
51
+
52
+ logger.fatal("Local backend path must be a String!") unless path.kind_of?(String)
53
+ logger.fatal("Local backend path must not be blank!") if path.strip.empty?
54
+
55
+ logger.fatal("Local backend path must include %{service} or the path to this repository.") unless (path.include?("%{service}") or path.include?(Dir.pwd))
56
+ logger.fatal("Local backend path must include %{config}") unless path.include?("%{config}")
57
+ logger.fatal("Local backend path must include %{component}") unless path.include?("%{component}")
58
+
59
+ begin
60
+ path = path % @variables
61
+ rescue
62
+ logger.fatal("Local backend options contain variables that are not included in the configuration file!")
58
63
  end
59
64
 
60
- @name = @component + @@ext
65
+ directory = File.dirname(path)
61
66
 
62
- directory = File.join(@base, @identifiers.path)
63
- raise "Failed to create state directory: #{directory}" unless create_directory(directory: directory, purpose: "state")
67
+ logger.fatal("Failed to create state directory: #{directory}") unless ::TerraformWrapper.create_directory(directory: directory, purpose: "state")
64
68
 
65
- @directory = directory
66
- @key = File.join(@identifiers.path, @name)
67
- @path = File.join(@directory, @name)
69
+ @path = path
68
70
  end
69
71
 
70
72
  ###############################################################################
@@ -10,6 +10,10 @@ module TerraformWrapper
10
10
 
11
11
  class Binary
12
12
 
13
+ ###############################################################################
14
+
15
+ include TerraformWrapper::Shared::Logging
16
+
13
17
  ###############################################################################
14
18
 
15
19
  attr_accessor :name
@@ -23,11 +27,18 @@ module TerraformWrapper
23
27
 
24
28
  ###############################################################################
25
29
 
26
- def initialize(base:, version:)
27
- @platform = platform_detect
30
+ def initialize(options:)
31
+ logger.fatal("Binary base path must be a String!") unless options["base"].kind_of?(String)
32
+ logger.fatal("Binary base path must not be blank!") if options["base"].strip.empty?
28
33
 
29
- @base = base
30
- @version = version
34
+ @base = options["base"]
35
+
36
+ logger.fatal("Binary version must be a String!") unless options["version"].kind_of?(String)
37
+ logger.fatal("Binary version must not be blank!") if options["version"].strip.empty?
38
+
39
+ @version = options["version"]
40
+
41
+ @platform = platform_detect
31
42
 
32
43
  @directory = File.join(@base, @version, @platform)
33
44
  @name = "terraform"
@@ -72,7 +83,7 @@ module TerraformWrapper
72
83
  def platform_detect
73
84
  return "darwin" if platform_is_mac
74
85
  return "linux" if platform_is_linux
75
- raise "Platform is NOT supported: #{RUBY_PLATFORM}"
86
+ logger.fatal("Platform is NOT supported: #{RUBY_PLATFORM}")
76
87
  end
77
88
 
78
89
  ###############################################################################
@@ -10,6 +10,10 @@ module TerraformWrapper
10
10
 
11
11
  class Code
12
12
 
13
+ ###############################################################################
14
+
15
+ include TerraformWrapper::Shared::Logging
16
+
13
17
  ###############################################################################
14
18
 
15
19
  attr_reader :base
@@ -18,13 +22,20 @@ module TerraformWrapper
18
22
 
19
23
  ###############################################################################
20
24
 
21
- def initialize(base:, name:)
22
- @base = base
23
- @name = name
25
+ def initialize(options:)
26
+ logger.fatal("Code base path must be a String!") unless options["base"].kind_of?(String)
27
+ logger.fatal("Code base path must not be blank!") if options["base"].strip.empty?
28
+
29
+ @base = options["base"]
30
+
31
+ logger.fatal("Code component name must be a String!") unless options["name"].kind_of?(String)
32
+ logger.fatal("Code component name must not be blank!") if options["name"].strip.empty?
33
+
34
+ @name = options["name"]
24
35
 
25
36
  @path = File.join(@base, @name)
26
37
 
27
- raise "Terraform code location: #{@path} does not exist!" unless exists
38
+ logger.fatal("Terraform code location: #{@path} does not exist!") unless exists
28
39
  end
29
40
 
30
41
  ###############################################################################
@@ -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