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 AWS < Common
16
16
 
17
+ ###############################################################################
18
+
19
+ include TerraformWrapper::Shared::Logging
20
+
17
21
  ###############################################################################
18
22
 
19
23
  @@default_class = "terraform-state"
@@ -25,24 +29,30 @@ module TerraformWrapper
25
29
  ###############################################################################
26
30
 
27
31
  attr_reader :bucket
28
- attr_reader :class
32
+ attr_reader :encrypt
29
33
  attr_reader :key
30
34
  attr_reader :region
31
35
 
32
36
  ###############################################################################
33
37
 
34
- def initialize(service:, code:, identifiers:, overrides: Hash.new)
35
- construct(service: service, code: code, identifiers: identifiers, overrides: overrides)
38
+ def initialize(code:, config:, options:, service:, variables:)
39
+ construct(code: code, config: config, options: options, service: service, variables: variables)
36
40
  end
37
41
 
38
42
  ###############################################################################
39
43
 
40
44
  def hash()
41
- return {
42
- "bucket" => @bucket,
43
- "key" => @key,
44
- "region" => @region
45
- }
45
+ result = Hash.new
46
+
47
+ result["bucket"] = @bucket
48
+ result["region"] = @region
49
+ result["key"] = @key
50
+ result["encrypt"] = @encrypt.to_s
51
+
52
+ result["kms_key_id"] = @kms unless @kms.nil?
53
+ result["role_arn"] = @role unless @role.nil?
54
+
55
+ return result
46
56
  end
47
57
 
48
58
  ###############################################################################
@@ -52,39 +62,67 @@ module TerraformWrapper
52
62
  ###############################################################################
53
63
 
54
64
  def specific()
55
- if @overrides.key?("class") and @overrides["class"].kind_of?(String) and not @overrides["class"].strip.empty? then
56
- @class = @overrides["class"]
57
- else
58
- @class = @@default_class
59
- end
65
+ kms = nil
66
+ role = nil
60
67
 
61
- if @overrides.key?("region") and @overrides["region"].kind_of?(String) and not @overrides["region"].strip.empty? then
62
- @region = @overrides["region"]
63
- else
64
- raise "Mandatory identifier 'region' or override 'region' must be set in provided configuration to backend of type: #{@@type}" unless @identifier.key?("region")
65
- raise "Mandatory identifier 'region' is not a string in provided configuration to backend of type: #{@@type}" unless @identifier["region"].kind_of?(String)
66
- raise "Mandatory identifier 'region' is empty in provided configuration to backend of type: #{@@type}" if @identifier["region"].strip.empty?
68
+ logger.fatal("AWS backend mandatory option 'bucket' has not been set!") unless @options.key?("bucket")
69
+ logger.fatal("AWS backend mandatory option 'region' has not been set!") unless @options.key?("region")
67
70
 
68
- @region = @identifiers["account"].strip.downcase
69
- end
71
+ bucket = @options["bucket"]
72
+
73
+ logger.fatal("AWS backend S3 bucket name must be a String!") unless bucket.kind_of?(String)
74
+ logger.fatal("AWS backend S3 bucket name must not be blank!") if bucket.strip.empty?
75
+
76
+ region = @options["region"]
70
77
 
71
- if @overrides.key?("bucket") and @overrides["bucket"].kind_of?(String) and not @overrides["bucket"].strip.empty? then
72
- @bucket = @overrides["bucket"]
73
- else
74
- raise "Mandatory identifier 'account' or override 'bucket' must be set in provided configuration to backend of type: #{@@type}" unless @identifier.key?("account")
75
- raise "Mandatory identifier 'account' is not a string in provided configuration to backend of type: #{@@type}" unless @identifier["account"].kind_of?(String)
76
- raise "Mandatory identifier 'account' is empty in provided configuration to backend of type: #{@@type}" if @identifier["account"].strip.empty?
78
+ logger.fatal("AWS backend S3 bucket region must be a String!") unless region.kind_of?(String)
79
+ logger.fatal("AWS backend S3 bucket region must not be blank!") if region.strip.empty?
77
80
 
78
- @bucket = @class + "-" + @region + "-" + @identifiers["account"].strip.downcase
81
+ key = @options.key?("key") ? @options["key"] : File.join("%{service}", "%{config}", "%{component}" + @@ext)
82
+
83
+ logger.fatal("AWS backend S3 bucket key must be a String!") unless key.kind_of?(String)
84
+ logger.fatal("AWS backend S3 bucket key must not be blank!") if key.strip.empty?
85
+
86
+ encrypt = @options.key?("encrypt") ? @options["encrypt"] : true
87
+
88
+ logger.fatal("AWS backend S3 bucket encryption enabled must be a Boolean!") unless [ true, false ].include?(encrypt)
89
+
90
+ if @options.key?("kms") then
91
+ kms = @options["kms"]
92
+
93
+ logger.fatal("AWS backend S3 bucket encryption KMS key ARN must be a String if specified!") unless kms.kind_of?(String)
94
+ logger.fatal("AWS backend S3 bucket encryption KMS key ARN must not be blank if specified!") if kms.strip.empty?
79
95
  end
80
96
 
81
- key = File.join(@identifiers.path, @service, @component + @@ext)
97
+ if @options.key?("role") then
98
+ role = @options["role"]
82
99
 
83
- if key.length > 1024 then
84
- raise "Key: #{key} is too long for backend of type: #{@@type}"
85
- else
86
- @key = key
100
+ logger.fatal("AWS backend role to assume ARN must be a String if specified!") unless role.kind_of?(String)
101
+ logger.fatal("AWS backend role to assume ARN must not be blank if specified!") if role.strip.empty?
87
102
  end
103
+
104
+ logger.fatal("AWS backend S3 bucket name or key must include %{service}.") unless (bucket.include?("%{service}") or key.include?("%{service}"))
105
+ logger.fatal("AWS backend S3 bucket name or key must include %{config}.") unless (bucket.include?("%{config}") or key.include?("%{config}"))
106
+ logger.fatal("AWS backend S3 bucket name or key must include %{component}.") unless (bucket.include?("%{component}") or key.include?("%{component}"))
107
+
108
+ begin
109
+ bucket = bucket % @variables
110
+ region = region % @variables
111
+ key = key % @variables
112
+ kms = kms % @variables unless kms.nil?
113
+ role = role % @variables unless role.nil?
114
+ rescue
115
+ logger.fatal("AWS backend options contain variables that are not included in the configuration file!")
116
+ end
117
+
118
+ logger.fatal("Key: #{key} is too long for backend of type: #{@@type}") if key.length > 1024
119
+
120
+ @bucket = bucket
121
+ @region = region
122
+ @key = key
123
+ @encrypt = encrypt
124
+ @kms = kms
125
+ @role = role
88
126
  end
89
127
 
90
128
  ###############################################################################
@@ -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
  ###############################################################################