terraspace_plugin_azurerm 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3845e196c53c10cdeb018eb26c60e29668f4cfd2de55296b8ccdd60373aa1582
4
- data.tar.gz: 49ba0fa4c80a3be68dfa90814e8dbad2ad4516591b447fd24c4bbe0a70c0e604
3
+ metadata.gz: 8704700ce76610490c5f29da3091ea5b9b14489911095418deb51561920f2062
4
+ data.tar.gz: e60b1a62a544c6cbc21838c13c41c93541806786600c85620a650e45a0aa7ae2
5
5
  SHA512:
6
- metadata.gz: c4299d6ce1c0e8d0f96187803d2d34fdeb1dc7a0b17c14ccf11777b2b26e54d031a9a166754932c6f1575e09eac722cf5d6c0073bd3f2e219b2c9365605df646
7
- data.tar.gz: f44e6b81bc3bf2a6b7e3932d5221d71e9d45582c1ad7c36cedbf49ed270e52bdc8ffab496284df5aecf6bbf01a6c3f8f8b405213b1f3c0cae98562862dc1c2f9
6
+ metadata.gz: f800a5c07d7ce75482b99b7ee459d8a87c9607e99086d149b83cc8cb81618a569906480df8c9b2457a81a4a4374eef0d2bcab7224998c894161f6e041e9444b3
7
+ data.tar.gz: 5ffcda4941b3b43d275d907861606b5c20f6cf76a570169c6369c2093e1a7dc4c0022180f7df6de33d1c135fdfcb73de61896f78ab186705f231fe1e73ac5a66
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/).
5
5
 
6
+ ## [0.3.0] - 2020-11-15
7
+ - [#5](https://github.com/boltops-tools/terraspace_plugin_azurerm/pull/5) helper and secrets support
8
+ - azure_secret helper
9
+
10
+ ## [0.2.3]
11
+ - #4 validate env vars and use older storage client
12
+
13
+ ## [0.2.2]
14
+ - #3 fix test template
15
+
16
+ ## [0.2.1]
17
+ - set prefix to @folder for performance improvement
18
+
19
+ ## [0.2.0]
20
+ - #1 include layer interface, update template to use expansion method, add region method
21
+
6
22
  ## [0.1.1]
7
23
  - update generator init terraform state path
8
24
 
data/README.md CHANGED
@@ -27,7 +27,7 @@ end
27
27
 
28
28
  By default, this plugin will automatically create the:
29
29
 
30
- * [resource group](Pluginazurerm)
30
+ * [resource group](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal)
31
31
  * [storage account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal)
32
32
  * [storage container](https://docs.microsoft.com/en-us/cli/azure/storage/container?view=azure-cli-latest#az-storage-container-create)
33
33
 
@@ -35,15 +35,17 @@ The settings generally only apply if the resource does not yet exist yet and is
35
35
 
36
36
  ## Environment Variables
37
37
 
38
- To create the Azure resources like [resource group](Pluginazurerm), [storage account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal), and [storage container](https://docs.microsoft.com/en-us/cli/azure/storage/container?view=azure-cli-latest#az-storage-container-create) these environment variables are required:
38
+ To create the Azure resources like [resource group](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal), [storage account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal), and [storage container](https://docs.microsoft.com/en-us/cli/azure/storage/container?view=azure-cli-latest#az-storage-container-create) these environment variables are required:
39
39
 
40
- AZURE_CLIENT_ID
41
- AZURE_CLIENT_SECRET
40
+ ARM_CLIENT_ID
41
+ ARM_CLIENT_SECRET
42
42
 
43
- There's other env variables can also be set, but are generally inferred.
43
+ Other env variables can be optionally set:
44
44
 
45
- AZURE_TENANT_ID
46
- AZURE_SUBSCRIPTION_ID
45
+ ARM_TENANT_ID
46
+ ARM_SUBSCRIPTION_ID
47
+
48
+ When not set, their values are inferred from the [az cli](https://docs.microsoft.com/en-us/cli/azure/) settings. For those interested, this is done with the [boltops-tools/azure_info](https://github.com/boltops-tools/azure_info) library.
47
49
 
48
50
  ## Contributing
49
51
 
@@ -1,10 +1,10 @@
1
1
  # SUBSCRIPTION_HASH is a short 4-char consistent hash of the longer subscription id.
2
- # This is useful because azure storage accounts not allowed special characters and can only be 24 chars long.
2
+ # This is useful because azure storage account names are not allowed special characters and are limited to 24 chars.
3
3
  terraform {
4
4
  backend "azurerm" {
5
- resource_group_name = "<%= backend_expand('azurerm', 'terraform-resources-:LOCATION') %>"
6
- storage_account_name = "<%= backend_expand('azurerm', 'ts:SUBSCRIPTION_HASH:LOCATION:ENV') %>"
5
+ resource_group_name = "<%= expansion('terraform-resources-:LOCATION') %>"
6
+ storage_account_name = "<%= expansion('ts:SUBSCRIPTION_HASH:LOCATION:ENV') %>"
7
7
  container_name = "terraform-state"
8
- key = "<%= backend_expand('azurerm', ':LOCATION/:ENV/:BUILD_DIR/terraform.tfstate') %>"
8
+ key = "<%= expansion(':LOCATION/:ENV/:BUILD_DIR/terraform.tfstate') %>"
9
9
  }
10
10
  }
@@ -1,5 +1,5 @@
1
1
  # SUBSCRIPTION_HASH is a short 4-char consistent hash of the longer subscription id.
2
- # This is useful because azure storage accounts not allowed special characters and can only be 24 chars long.
2
+ # This is useful because azure storage account names are not allowed special characters and are limited to 24 chars.
3
3
  backend("azurerm",
4
4
  resource_group_name: "terraform-resources-:LOCATION",
5
5
  storage_account_name: "ts:SUBSCRIPTION_HASH:LOCATION:ENV",
@@ -0,0 +1,4 @@
1
+ Terraspace.configure do |config|
2
+ config.logger.level = :info
3
+ config.test_framework = "rspec"
4
+ end
@@ -8,6 +8,7 @@ describe "main" do
8
8
  stacks: "app/stacks", # include all stacks in this folder
9
9
  # override demo stack tfvars for testing
10
10
  # copied over to test harness' app/stacks/demo/tfvars/test.tfvars
11
+ # need for azure
11
12
  tfvars: {demo: "spec/fixtures/tfvars/demo.tfvars"},
12
13
  config: "spec/fixtures/config",
13
14
  )
@@ -18,9 +19,6 @@ describe "main" do
18
19
  end
19
20
 
20
21
  it "successful deploy" do
21
- # Replace with your actual test
22
- expect(true).to be true
23
- # Example
24
22
  storage_account_id = terraspace.output("demo", "storage_account_id")
25
23
  expect(storage_account_id).to include("sa") # starts with sa
26
24
  end
@@ -23,12 +23,22 @@ module TerraspacePluginAzurerm
23
23
  Interfaces::Config.instance.config
24
24
  end
25
25
 
26
+ @@logger = nil
27
+ def logger
28
+ @@logger ||= Terraspace.logger
29
+ end
30
+
31
+ def logger=(v)
32
+ @@logger = v
33
+ end
34
+
26
35
  extend self
27
36
  end
28
37
 
29
38
  Terraspace::Plugin.register("azurerm",
30
39
  backend: "azurerm",
31
40
  config_class: TerraspacePluginAzurerm::Interfaces::Config,
41
+ helper_class: TerraspacePluginAzurerm::Interfaces::Helper,
32
42
  layer_class: TerraspacePluginAzurerm::Interfaces::Layer,
33
43
  root: File.dirname(__dir__),
34
44
  )
@@ -3,21 +3,49 @@ module TerraspacePluginAzurerm::Clients
3
3
  extend Memoist
4
4
 
5
5
  def client_options
6
- client_id = ENV['AZURE_CLIENT_ID']
7
- client_secret = ENV['AZURE_CLIENT_SECRET']
8
- subscription_id = ENV['AZURE_SUBSCRIPTION_ID'] || AzureInfo.subscription_id
9
- tenant_id = ENV['AZURE_TENANT_ID'] || AzureInfo.tenant_id
6
+ o = base_client_options
7
+ o[:credentials] = credentials
8
+ o
9
+ end
10
+
11
+ def credentials
12
+ o = base_client_options
13
+ provider = MsRestAzure::ApplicationTokenProvider.new(o[:tenant_id], o[:client_id], o[:client_secret])
14
+ MsRest::TokenCredentials.new(provider)
15
+ end
10
16
 
11
- provider = MsRestAzure::ApplicationTokenProvider.new(tenant_id, client_id, client_secret)
12
- credentials = MsRest::TokenCredentials.new(provider)
17
+ def base_client_options
18
+ # AZURE_* is used by ruby generally.
19
+ # ARM_* is used by Terraform azurerm provider: https://www.terraform.io/docs/providers/azurerm/index.html
20
+ # Favor ARM_ because this plugin is designed for Terraspace.
21
+ client_id = ENV['ARM_CLIENT_ID'] || ENV['AZURE_CLIENT_ID']
22
+ client_secret = ENV['ARM_CLIENT_SECRET'] || ENV['AZURE_CLIENT_SECRET']
23
+ subscription_id = ENV['ARM_SUBSCRIPTION_ID'] || ENV['AZURE_SUBSCRIPTION_ID'] || AzureInfo.subscription_id
24
+ tenant_id = ENV['ARM_TENANT_ID'] || ENV['AZURE_TENANT_ID'] || AzureInfo.tenant_id
13
25
 
14
- {
26
+ o = {
15
27
  tenant_id: tenant_id,
16
28
  client_id: client_id,
17
29
  client_secret: client_secret,
18
30
  subscription_id: subscription_id,
19
- credentials: credentials
20
31
  }
32
+ validate_base_options!(o)
33
+ o
34
+ end
35
+ memoize :base_client_options
36
+
37
+ def validate_base_options!(options)
38
+ vars = []
39
+ options.each do |k,v|
40
+ vars << "ARM_#{k}".upcase if v.nil?
41
+ end
42
+ return if vars.empty?
43
+
44
+ logger.error "ERROR: Required Azure env variables missing. Please set these env variables:".color(:red)
45
+ vars.each do |var|
46
+ logger.error " #{var}"
47
+ end
48
+ exit 1
21
49
  end
22
50
  end
23
51
  end
@@ -6,8 +6,8 @@ module TerraspacePluginAzurerm::Clients
6
6
  extend Memoist
7
7
 
8
8
  # Include SDK modules to ease access to Storage classes.
9
- include Azure::Storage::Profiles::Latest::Mgmt
10
- include Azure::Storage::Profiles::Latest::Mgmt::Models
9
+ include Azure::Storage::Mgmt::V2019_06_01
10
+ include Azure::Storage::Mgmt::V2019_06_01::Models
11
11
 
12
12
  def storage_accounts
13
13
  mgmt.storage_accounts
@@ -19,7 +19,9 @@ module TerraspacePluginAzurerm::Clients
19
19
  memoize :blob_containers
20
20
 
21
21
  def mgmt
22
- Client.new(client_options)
22
+ client = StorageManagementClient.new(credentials)
23
+ client.subscription_id = client_options[:subscription_id]
24
+ client
23
25
  end
24
26
  memoize :mgmt
25
27
  end
@@ -1,7 +1,6 @@
1
1
  class TerraspacePluginAzurerm::Interfaces::Backend
2
2
  class StorageContainer < Base
3
3
  include TerraspacePluginAzurerm::Clients::Storage
4
- extend Memoist
5
4
 
6
5
  def create
7
6
  if exist?
@@ -15,12 +15,12 @@ module TerraspacePluginAzurerm::Interfaces
15
15
  c = ActiveSupport::OrderedOptions.new
16
16
  c.auto_create = true
17
17
  c.location = nil # AzureInfo.location not assigned here so it can be lazily inferred
18
-
18
+ c.secrets = ActiveSupport::OrderedOptions.new
19
+ c.secrets.vault = nil
19
20
  c.storage_account = ActiveSupport::OrderedOptions.new
20
21
  c.storage_account.sku = ActiveSupport::OrderedOptions.new
21
22
  c.storage_account.sku.name = "Standard_LRS"
22
23
  c.storage_account.sku.tier = "Standard"
23
-
24
24
  c
25
25
  end
26
26
  end
@@ -16,13 +16,14 @@ module TerraspacePluginAzurerm::Interfaces
16
16
 
17
17
  delegate :subscription_id, :subscription, :tenant_id, :tenant_id, :group, :location, to: :azure_info
18
18
  alias_method :namespace, :subscription
19
+ alias_method :region, :location
19
20
 
20
21
  def azure_info
21
22
  AzureInfo
22
23
  end
23
24
 
24
25
  # subscription_hash is a short 4-char consistent hash of the longer subscription id.
25
- # This is useful because azure storage accounts not allowed special characters and can only be 24 chars long.
26
+ # This is useful because azure storage account names are not allowed special characters and are limited to 24 chars.
26
27
  # NOTE: be careful to not change this! or else state path will change
27
28
  def subscription_hash
28
29
  Digest::SHA1.hexdigest(subscription)[0..3]
@@ -0,0 +1,10 @@
1
+ module TerraspacePluginAzurerm::Interfaces
2
+ module Helper
3
+ include Terraspace::Plugin::Helper::Interface
4
+
5
+ def azure_secret(name, options={})
6
+ Secret.new(options).fetch(name, options)
7
+ end
8
+ cache_helper :azure_secret
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ require "base64"
2
+
3
+ module TerraspacePluginAzurerm::Interfaces::Helper
4
+ class Secret
5
+ extend Memoist
6
+ include TerraspacePluginAzurerm::Logging
7
+ include TerraspacePluginAzurerm::Clients::Options
8
+
9
+ def initialize(options={})
10
+ @options = options
11
+ @base64 = options[:base64]
12
+ end
13
+
14
+ # opts: version, vault
15
+ def fetch(name, opts={})
16
+ value = fetcher.fetch(name, opts)
17
+ value = Base64.strict_encode64(value).strip if @base64
18
+ value
19
+ end
20
+
21
+ def fetcher
22
+ Fetcher.new
23
+ end
24
+ memoize :fetcher
25
+ end
26
+ end
@@ -0,0 +1,112 @@
1
+ require "net/http"
2
+
3
+ class TerraspacePluginAzurerm::Interfaces::Helper::Secret
4
+ class Fetcher
5
+ class Error < StandardError; end
6
+ class VaultNotFoundError < Error; end
7
+
8
+ include TerraspacePluginAzurerm::Logging
9
+ include TerraspacePluginAzurerm::Clients::Options
10
+
11
+ def initialize
12
+ o = base_client_options
13
+ @client_id = o[:client_id]
14
+ @client_secret = o[:client_secret]
15
+ @tenant_id = o[:tenant_id]
16
+ end
17
+
18
+ def fetch(name, opts={})
19
+ opts[:vault] ||= TerraspacePluginAzurerm.config.secrets.vault
20
+ get_secret(name, opts)
21
+ end
22
+
23
+ def get_secret(name, vault: nil, version: nil)
24
+ unless token
25
+ return "ERROR: Unable to authorize and get the temporary token. Double check your ARM_ env variables."
26
+ end
27
+
28
+ version = "/#{version}" if version
29
+ vault_subdomain = vault.downcase
30
+ # Using Azure REST API since the old gem doesnt support secrets https://github.com/Azure/azure-sdk-for-ruby
31
+ # https://docs.microsoft.com/en-us/rest/api/keyvault/getsecret/getsecret
32
+ url = "https://#{vault_subdomain}.vault.azure.net/secrets/#{name}#{version}?api-version=7.1"
33
+ uri = URI(url)
34
+ req = Net::HTTP::Get.new(uri)
35
+ req["Authorization"] = token
36
+ req["Content-Type"] = "application/json"
37
+
38
+ resp = nil
39
+ begin
40
+ resp = send_request(uri, req)
41
+ rescue VaultNotFoundError
42
+ message = "WARN: Vault not found #{vault}"
43
+ logger.info message.color(:yellow)
44
+ return message
45
+ end
46
+
47
+ case resp.code.to_s
48
+ when /^2/
49
+ data = JSON.load(resp.body)
50
+ data['value']
51
+ else
52
+ message = standard_error_message(resp)
53
+ logger.info "WARN: #{message}".color(:yellow)
54
+ message
55
+ end
56
+ end
57
+
58
+ def send_request(uri, req)
59
+ http = Net::HTTP.new(uri.host, uri.port)
60
+ http.open_timeout = http.read_timeout = 30
61
+ http.use_ssl = true if uri.scheme == 'https'
62
+
63
+ begin
64
+ http.request(req) # response
65
+ rescue SocketError => e
66
+ # SocketError: Failed to open TCP connection to MISSING-VAULT.vault.azure.net:443 (getaddrinfo: Name or service not known)
67
+ if e.message.include?("vault.azure.net")
68
+ raise VaultNotFoundError.new(e)
69
+ else
70
+ raise
71
+ end
72
+ end
73
+ end
74
+
75
+ # Secret error handling: 1. network 2. json parse 3. missing secret
76
+ #
77
+ # Azure API responses with decent error message when
78
+ # 403 Forbidden - KeyVault Access Policy needs to be set up
79
+ # 404 Not Found - Secret name is incorrect
80
+ #
81
+ def standard_error_message(resp)
82
+ data = JSON.load(resp.body)
83
+ data['error']['message']
84
+ rescue JSON::ParserError
85
+ resp.body
86
+ end
87
+
88
+ @@token = nil
89
+ def token
90
+ return @@token unless @@token.nil?
91
+ url = "https://login.microsoftonline.com/#{@tenant_id}/oauth2/token"
92
+ uri = URI(url)
93
+ req = Net::HTTP::Get.new(uri)
94
+ req.set_form_data(
95
+ grant_type: "client_credentials",
96
+ client_id: @client_id,
97
+ client_secret: @client_secret,
98
+ resource: "https://vault.azure.net",
99
+ )
100
+ resp = send_request(uri, req)
101
+ data = JSON.load(resp.body)
102
+ if resp.code =~ /^2/
103
+ @@token = "Bearer #{data['access_token']}" if data
104
+ else
105
+ logger.info "WARN: #{data['error_description']}".color(:yellow)
106
+ # return false otherwise error message is used as the bearer toke and get this error:
107
+ # ArgumentError: header field value cannot include CR/LF
108
+ @@token = false
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,6 +1,7 @@
1
1
  module TerraspacePluginAzurerm::Interfaces
2
2
  class Layer
3
3
  extend Memoist
4
+ include Terraspace::Plugin::Layer::Interface
4
5
 
5
6
  # interface method
6
7
  def namespace
@@ -11,10 +12,5 @@ module TerraspacePluginAzurerm::Interfaces
11
12
  def region
12
13
  AzureInfo.location
13
14
  end
14
-
15
- # interface method
16
- def provider
17
- "azurerm"
18
- end
19
15
  end
20
16
  end
@@ -53,7 +53,7 @@ module TerraspacePluginAzurerm::Interfaces
53
53
 
54
54
  # Friendly error handling for user
55
55
  def list_blobs(container_name, marker:)
56
- blob_client.list_blobs(container_name, marker: marker)
56
+ blob_client.list_blobs(container_name, marker: marker, prefix: @folder)
57
57
  rescue Azure::Core::Http::HTTPError => e
58
58
  if e.message.include?("AuthenticationFailed")
59
59
  logger.error "e.class #{e.class}: #{e.message}"
@@ -0,0 +1,7 @@
1
+ module TerraspacePluginAzurerm
2
+ module Logging
3
+ def logger
4
+ Terraspace.logger
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module TerraspacePluginAzurerm
2
- VERSION = "0.1.1"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terraspace_plugin_azurerm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-24 00:00:00.000000000 Z
11
+ date: 2020-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: azure-storage-blob
@@ -134,6 +134,7 @@ files:
134
134
  - lib/templates/test/rspec/module/test/spec/fixtures/stack/variables.tf
135
135
  - lib/templates/test/rspec/module/test/spec/main_spec.rb.tt
136
136
  - lib/templates/test/rspec/module/test/spec/spec_helper.rb
137
+ - lib/templates/test/rspec/project/spec/fixtures/config/app.rb
137
138
  - lib/templates/test/rspec/project/spec/fixtures/config/terraform/provider.tf
138
139
  - lib/templates/test/rspec/project/spec/fixtures/tfvars/demo.tfvars
139
140
  - lib/templates/test/rspec/project/spec/spec_helper.rb
@@ -150,8 +151,12 @@ files:
150
151
  - lib/terraspace_plugin_azurerm/interfaces/backend/storage_container.rb
151
152
  - lib/terraspace_plugin_azurerm/interfaces/config.rb
152
153
  - lib/terraspace_plugin_azurerm/interfaces/expander.rb
154
+ - lib/terraspace_plugin_azurerm/interfaces/helper.rb
155
+ - lib/terraspace_plugin_azurerm/interfaces/helper/secret.rb
156
+ - lib/terraspace_plugin_azurerm/interfaces/helper/secret/fetcher.rb
153
157
  - lib/terraspace_plugin_azurerm/interfaces/layer.rb
154
158
  - lib/terraspace_plugin_azurerm/interfaces/summary.rb
159
+ - lib/terraspace_plugin_azurerm/logging.rb
155
160
  - lib/terraspace_plugin_azurerm/version.rb
156
161
  - terraspace_plugin_azurerm.gemspec
157
162
  homepage: https://github.com/boltops-tools/terraspace_plugin_azurerm
@@ -174,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
179
  - !ruby/object:Gem::Version
175
180
  version: '0'
176
181
  requirements: []
177
- rubygems_version: 3.1.2
182
+ rubygems_version: 3.1.4
178
183
  signing_key:
179
184
  specification_version: 4
180
185
  summary: Terraspace Azurerm Cloud Plugin