diff --git a/README.md b/README.md
index bf03f3d9b..d0c801edd 100644
--- a/README.md
+++ b/README.md
@@ -163,6 +163,8 @@ The static resources derived from the generic resources prepended with `azure_`
- [azure_network_interfaces](docs/resources/azure_network_interfaces.md)
- [azure_network_security_group](docs/resources/azure_network_security_group.md)
- [azure_network_security_groups](docs/resources/azure_network_security_groups.md)
+- [azure_network_watcher](docs/resources/azure_network_watcher.md)
+- [azure_network_watchers](docs/resources/azure_network_watchers.md)
- [azure_policy_definition](docs/resources/azure_policy_definition.md)
- [azure_policy_definitions](docs/resources/azure_policy_definitions.md)
- [azure_postgresql_database](docs/resources/azure_postgresql_database.md)
diff --git a/docs/resources/azure_network_watcher.md b/docs/resources/azure_network_watcher.md
new file mode 100644
index 000000000..be98f3a81
--- /dev/null
+++ b/docs/resources/azure_network_watcher.md
@@ -0,0 +1,113 @@
+---
+title: About the azure_network_watcher Resource
+platform: azure
+---
+
+# azure_network_watcher
+
+Use the `azure_network_watcher` InSpec audit resource to test properties of an Azure network watcher.
+
+## Azure REST API version, endpoint and http client parameters
+
+This resource interacts with api versions supported by the resource provider.
+The `api_version` can be defined as a resource parameter.
+If not provided, the latest version will be used.
+For more information, refer to [`azure_generic_resource`](azure_generic_resource.md).
+
+Unless defined, `azure_cloud` global endpoint, and default values for the http client will be used.
+For more information, refer to the resource pack [README](../../README.md).
+
+## Availability
+
+### Installation
+
+This resource is available in the [InSpec Azure resource pack](https://github.com/inspec/inspec-azure).
+For an example `inspec.yml` file and how to set up your Azure credentials, refer to resource pack [README](../../README.md#Service-Principal).
+
+## Syntax
+
+An `azure_network_watcher` resource block identifies a network watcher by `name` and `resource_group` or the `resource_id`.
+```ruby
+describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do
+ it { should exist }
+end
+```
+```ruby
+describe azure_network_watcher(resource_id: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkWatchers/{networkWatcherName}') do
+ it { should exist }
+end
+```
+## Parameters
+
+| Name | Description |
+|--------------------------------|-----------------------------------------------------------------------------------|
+| resource_group | Azure resource group that the network watcher resides in. `resourceGroupName` |
+| name | Name of the network watcher to test. `networkWatcherName` |
+| resource_id | The unique resource ID. `/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkWatchers/{networkWatcherName}` |
+| flow_logs_api_version | The flow log status endpoint api version used for creating `flow_logs` property. The latest version will be used unless provided. A network security group within the same region can be targeted for getting the flow log statuses. For more, see [here](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/getflowlogstatus).|
+| nsg_resource_id | The unique resource ID of the network security group being targeted to get the flow log statuses. `/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/networkSecurityGroups/{networkSecurityGroupName}` |
+| nsg_resource_group | The resource group of the network security group being targeted to get the flow log statuses. This requires `nsg_name` to be provided. |
+| nsg_name | The name of the network security group being targeted to get the flow log statuses. This requires `nsg_resource_group` to be provided.|
+
+
+Either one of the parameter sets can be provided for a valid query:
+- `resource_id`
+- `resource_group` and `name`
+
+## Properties
+
+| Property | Description |
+|-----------------------|-------------|
+| provisioning_state | The provisioning state of the network watcher resource. For the valid values, see [here](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/get#provisioningstate). |
+| flow_logs | Information on the configuration of flow log and traffic analytics (optional) in [this format](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/getflowlogstatus#flowloginformation). All properties can be accessed via dot notation, e.g.: `flow_logs.properties.enabled`. This resource supports targeting network security groups defined at resource creation only. |
+
+For properties applicable to all resources, such as `type`, `name`, `id`, `properties`, refer to [`azure_generic_resource`](azure_generic_resource.md#properties).
+
+Also, refer to [Azure documentation](https://docs.microsoft.com/en-us/rest/api/network-watcher/networkwatchers/get#networkwatcher) for other properties available.
+Any attribute in the response may be accessed with the key names separated by dots (`.`).
+
+## Examples
+
+### Test the Location of a Network Watcher
+```ruby
+describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do
+ its('location') { should cmp 'eastus' }
+end
+```
+### Test the Flow Log Status of a Network Security Group
+```ruby
+describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName', nsg_resource_group: 'nsg_rg', nsg_name: 'nsg_eastus') do
+ its('flow_logs.properties.enabled') { should be true }
+ its('flow_logs.properties.retentionPolicy.days') { should be >= 90 }
+end
+```
+### Loop through Network Security Groups with the Resource ID
+```ruby
+azure_network_security_groups.where(location: 'eastus').ids.each do |nsg_id|
+ describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName', nsg_resource_id: nsg_id) do
+ its('flow_logs.properties.enabled') { should be true }
+ its('flow_logs.properties.retentionPolicy.days') { should be >= 90 }
+ end
+end
+```
+See [integration tests](../../test/integration/verify/controls/azurerm_network_watcher.rb) for more examples.
+
+## Matchers
+
+This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [Universal Matchers page](https://docs.chef.io/inspec/matchers/).
+
+### exists
+```ruby
+# If we expect 'networkWatcherName' to always exist
+describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do
+ it { should exist }
+end
+
+# If we expect 'networkWatcherName' to never exist
+describe azure_network_watcher(resource_group: 'resourceGroupName', name: 'networkWatcherName') do
+ it { should_not exist }
+end
+```
+## Azure Permissions
+
+Your [Service Principal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal) must be setup with a `contributor` role on the subscription you wish to test.
diff --git a/docs/resources/azure_network_watchers.md b/docs/resources/azure_network_watchers.md
new file mode 100644
index 000000000..51918afe5
--- /dev/null
+++ b/docs/resources/azure_network_watchers.md
@@ -0,0 +1,84 @@
+---
+title: About the azure_network_watchers Resource
+platform: azure
+---
+
+# azure_network_watchers
+
+Use the `azure_network_watchers` InSpec audit resource to test properties and configuration of multiple Azure network watchers.
+
+## Azure REST API version, endpoint and http client parameters
+
+This resource interacts with api versions supported by the resource provider.
+The `api_version` can be defined as a resource parameter.
+If not provided, the latest version will be used.
+For more information, refer to [`azure_generic_resource`](azure_generic_resource.md).
+
+Unless defined, `azure_cloud` global endpoint, and default values for the http client will be used.
+For more information, refer to the resource pack [README](../../README.md).
+
+## Availability
+
+### Installation
+
+This resource is available in the [InSpec Azure resource pack](https://github.com/inspec/inspec-azure).
+For an example `inspec.yml` file and how to set up your Azure credentials, refer to resource pack [README](../../README.md#Service-Principal).
+
+## Syntax
+
+An `azure_network_watchers` resource block returns all network watchers, either within a Resource Group (if provided), or within an entire Subscription.
+```ruby
+describe azure_network_watchers do
+ #...
+end
+```
+or
+```ruby
+describe azure_network_watchers(resource_group: 'my-rg') do
+ #...
+end
+```
+## Parameters
+
+- `resource_group` (Optional)
+
+## Properties
+
+|Property | Description | Filter Criteria* |
+|---------------|--------------------------------------------------------------------------------------|-----------------|
+| ids | A list of the unique resource ids. | `id` |
+| locations | A list of locations for all the resources being interrogated. | `location` |
+| names | A list of names of all the resources being interrogated. | `name` |
+| tags | A list of `tag:value` pairs defined on the resources being interrogated. | `tags` |
+
+* For information on how to use filter criteria on plural resources refer to [FilterTable usage](https://github.com/inspec/inspec/blob/master/dev-docs/filtertable-usage.md).
+
+## Examples
+
+### Test that an Example Resource Group has the Named Network Watcher
+```ruby
+describe azure_network_watchers(resource_group: 'ExampleGroup') do
+ its('names') { should include('NetworkWatcherName') }
+end
+```
+## Matchers
+
+This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [Universal Matchers page](https://www.inspec.io/docs/reference/matchers/).
+
+### exists
+
+The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
+```ruby
+# If we expect 'ExampleGroup' Resource Group to have Network Watchers
+describe azure_network_watchers(resource_group: 'ExampleGroup') do
+ it { should exist }
+end
+
+# If we expect 'EmptyExampleGroup' Resource Group to not have Network Watchers
+describe azure_network_watchers(resource_group: 'EmptyExampleGroup') do
+ it { should_not exist }
+end
+```
+## Azure Permissions
+
+Your [Service Principal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal) must be setup with a `contributor` role on the subscription you wish to test.
diff --git a/docs/resources/azurerm_network_watcher.md b/docs/resources/azurerm_network_watcher.md
index ec97663d9..48e6f338e 100644
--- a/docs/resources/azurerm_network_watcher.md
+++ b/docs/resources/azurerm_network_watcher.md
@@ -3,6 +3,8 @@ title: About the azurerm_network_watcher Resource
platform: azure
---
+> WARNING This resource will be deprecated in InSpec Azure Resource Pack version **2**. Please start using fully backward compatible [`azure_network_watcher`](azure_network_watcher.md) InSpec audit resource.
+
# azurerm\_network\_watcher
Use the `azurerm_network_watcher` InSpec audit resource to test properties of an Azure
diff --git a/docs/resources/azurerm_network_watchers.md b/docs/resources/azurerm_network_watchers.md
index 88f0c1923..2a4e4efb1 100644
--- a/docs/resources/azurerm_network_watchers.md
+++ b/docs/resources/azurerm_network_watchers.md
@@ -3,6 +3,8 @@ title: About the azurerm_network_watchers Resource
platform: azure
---
+> WARNING This resource will be deprecated in InSpec Azure Resource Pack version **2**. Please start using fully backward compatible [`azure_networke_watchers`](azure_network_watchers.md) InSpec audit resource.
+
# azurerm\_network\_watchers
Use the `azurerm_network_watchers` InSpec audit resource to verify that a Network Watcher
diff --git a/libraries/azure_network_watcher.rb b/libraries/azure_network_watcher.rb
new file mode 100644
index 000000000..aec2040bc
--- /dev/null
+++ b/libraries/azure_network_watcher.rb
@@ -0,0 +1,104 @@
+require 'azure_generic_resource'
+
+class AzureNetworkWatcher < AzureGenericResource
+ name 'azure_network_watcher'
+ desc 'Verifies settings for Network Watchers'
+ example <<-EXAMPLE
+ describe azure_network_watcher(resource_group: 'example', name: 'name') do
+ its(name) { should eq 'name'}
+ end
+ EXAMPLE
+
+ # This is for backward compatibility.
+ attr_accessor :nsg
+
+ def initialize(opts = {})
+ # Options should be Hash type. Otherwise Ruby will raise an error when we try to access the keys.
+ raise ArgumentError, 'Parameters must be provided in an Hash object.' unless opts.is_a?(Hash)
+
+ opts[:resource_provider] = specific_resource_constraint('Microsoft.Network/networkWatchers', opts)
+ opts[:allowed_parameters] = %i(flow_logs_api_version nsg_resource_id nsg_resource_group nsg_name)
+ opts[:flow_logs_api_version] ||= 'latest'
+
+ # static_resource parameter must be true for setting the resource_provider in the backend.
+ super(opts, true)
+ end
+
+ def to_s
+ super(AzureNetworkWatcher)
+ end
+
+ def provisioning_state
+ return unless exists?
+ properties&.provisioningState
+ end
+
+ def flow_logs
+ return unless exists?
+ # This is for backward compatibility. !!! NOT RECOMMENDED !!!
+ # nsg is the name of the network security group.
+ # It can be defined on the instance of this resource.
+ # nw = azure_network_watcher(resource_group: resource_group, name: nw_name)
+ # nw.nsg = nsg
+ # It must be within the same resource group with the network watcher.
+ if nsg
+ target_resource_id = validate_resource_uri(
+ {
+ resource_uri:
+ "/resourceGroups/#{@opts[:resource_group]}/providers/Microsoft.Network/networkSecurityGroups/#{nsg}",
+ add_subscription_id: true,
+ },
+ )
+ elsif @opts[:nsg_resource_group] && @opts[:nsg_name]
+ # network security group can be provided with its resource group and name.
+ target_resource_id = validate_resource_uri(
+ {
+ resource_uri:
+ "/resourceGroups/#{@opts[:nsg_resource_group]}/providers/Microsoft.Network/networkSecurityGroups/"\
+ "#{@opts[:nsg_name]}",
+ add_subscription_id: true,
+ },
+ )
+ elsif @opts[:nsg_resource_id]
+ # network security group can be provided with its resource id.
+ target_resource_id = @opts[:nsg_resource_id]
+ else
+ raise ArgumentError,
+ 'The resource group (nsg_resource_group) and the name (nsg_name) of the network security group or '\
+ 'the resource id (nsg_resource_id) must be provided at resource initiation.'
+ end
+ req_body_hash = {
+ targetResourceId: target_resource_id,
+ }
+ additional_resource_properties(
+ {
+ property_name: 'flow_logs',
+ property_endpoint: id + '/queryFlowLogStatus',
+ api_version: @opts[:flow_logs_api_version],
+ method: 'post',
+ req_body: JSON.generate(req_body_hash),
+ headers: { 'Content-Type' => 'application/json' },
+ },
+ )
+ end
+end
+
+# Provide the same functionality under the old resource name.
+# This is for backward compatibility.
+class AzurermNetworkWatcher < AzureNetworkWatcher
+ name 'azurerm_network_watcher'
+ desc 'Verifies settings for Network Watchers'
+ example <<-EXAMPLE
+ describe azurerm_network_watcher(resource_group: 'example', name: 'name') do
+ its(name) { should eq 'name'}
+ end
+ EXAMPLE
+
+ def initialize(opts = {})
+ Inspec::Log.warn Helpers.resource_deprecation_message(@__resource_name__, AzureNetworkWatcher.name)
+ # For backward compatibility.
+ opts[:api_version] ||= '2018-02-01'
+ opts[:flow_logs_api_version] ||= '2019-04-01'
+ super
+ end
+end
diff --git a/libraries/azure_network_watchers.rb b/libraries/azure_network_watchers.rb
new file mode 100644
index 000000000..e67d364f2
--- /dev/null
+++ b/libraries/azure_network_watchers.rb
@@ -0,0 +1,64 @@
+require 'azure_generic_resources'
+
+class AzureNetworkWatchers < AzureGenericResources
+ name 'azure_network_watchers'
+ desc 'Verifies settings for Network Watchers'
+ example <<-EXAMPLE
+ azure_network_watchers(resource_group: 'example') do
+ it{ should exist }
+ end
+ EXAMPLE
+
+ attr_reader :table
+
+ def initialize(opts = {})
+ # Options should be Hash type. Otherwise Ruby will raise an error when we try to access the keys.
+ raise ArgumentError, 'Parameters must be provided in an Hash object.' unless opts.is_a?(Hash)
+
+ opts[:resource_provider] = specific_resource_constraint('Microsoft.Network/networkWatchers', opts)
+ opts[:allowed_parameters] = %i(resource_group)
+
+ # static_resource parameter must be true for setting the resource_provider in the backend.
+ super(opts, true)
+
+ # Check if the resource is failed.
+ # It is recommended to check that after every usage of inherited methods or making API calls.
+ return if failed_resource?
+
+ # Define the column and field names for FilterTable.
+ # In most cases, the `column` should be the pluralized form of the `field`.
+ # @see https://github.com/inspec/inspec/blob/master/docs/dev/filtertable-usage.md
+ table_schema = [
+ { column: :names, field: :name },
+ { column: :ids, field: :id },
+ { column: :tags, field: :tags },
+ { column: :locations, field: :location },
+ ]
+
+ # FilterTable is populated at the very end due to being an expensive operation.
+ AzureGenericResources.populate_filter_table(:table, table_schema)
+ end
+
+ def to_s
+ super(AzureNetworkWatchers)
+ end
+end
+
+# Provide the same functionality under the old resource name.
+# This is for backward compatibility.
+class AzurermNetworkWatchers < AzureNetworkWatchers
+ name 'azurerm_network_watchers'
+ desc 'Verifies settings for Network Watchers'
+ example <<-EXAMPLE
+ azurerm_network_watchers(resource_group: 'example') do
+ it{ should exist }
+ end
+ EXAMPLE
+
+ def initialize(opts = {})
+ Inspec::Log.warn Helpers.resource_deprecation_message(@__resource_name__, AzureNetworkWatchers.name)
+ # For backward compatibility.
+ opts[:api_version] ||= '2018-02-01'
+ super
+ end
+end
diff --git a/libraries/azurerm_network_watcher.rb b/libraries/azurerm_network_watcher.rb
deleted file mode 100644
index b40beaf74..000000000
--- a/libraries/azurerm_network_watcher.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-require 'azurerm_resource'
-
-class AzurermNetworkWatcher < AzurermSingularResource
- name 'azurerm_network_watcher'
- desc 'Verifies settings for Network Watchers'
- example <<-EXAMPLE
- describe azurerm_network_watcher(resource_group: 'example', name: 'name') do
- its(name) { should eq 'name'}
- end
- EXAMPLE
-
- ATTRS = %i(
- name
- id
- etag
- type
- location
- tags
- properties
- ).freeze
-
- attr_reader(*ATTRS)
- attr_accessor(:nsg)
-
- def initialize(resource_group: nil, name: nil)
- resp = management.network_watcher(resource_group, name)
- return if has_error?(resp)
-
- assign_fields(ATTRS, resp)
-
- @resource_group = resource_group
- @exists = true
- end
-
- def to_s
- "'#{name}' Network Watcher"
- end
-
- def flow_logs
- return nil if @nsg.nil?
- @flow_logs ||= management.network_watcher_flow_log_status(@resource_group, name, nsg)
- end
-
- def provisioning_state
- @provisioning_state ||= properties.provisioningState
- end
-end
diff --git a/libraries/azurerm_network_watchers.rb b/libraries/azurerm_network_watchers.rb
deleted file mode 100644
index 49653fe4e..000000000
--- a/libraries/azurerm_network_watchers.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'azurerm_resource'
-
-class AzurermNetworkWatchers < AzurermPluralResource
- name 'azurerm_network_watchers'
- desc 'Verifies settings for Network Watchers'
- example <<-EXAMPLE
- azurerm_network_watchers(resource_group: 'example') do
- it{ should exist }
- end
- EXAMPLE
-
- attr_reader :table
-
- FilterTable.create
- .register_column(:names, field: :name)
- .install_filter_methods_on_resource(self, :table)
-
- def initialize(resource_group = nil)
- resource_group = resource_group[:resource_group] if resource_group.is_a?(Hash)
- resp = management.network_watchers(resource_group)
- return if has_error?(resp)
-
- @table = resp
- end
-
- include Azure::Deprecations::StringsInWhereClause
-
- def to_s
- 'Network Watchers'
- end
-end
diff --git a/test/integration/verify/controls/azurerm_network_watcher.rb b/test/integration/verify/controls/azurerm_network_watcher.rb
index 0f6fbf7e8..675e02731 100644
--- a/test/integration/verify/controls/azurerm_network_watcher.rb
+++ b/test/integration/verify/controls/azurerm_network_watcher.rb
@@ -3,7 +3,7 @@
nw_id = input('network_watcher_id', value: []).first
control 'azurerm_network_watcher' do
- only_if { ENV['NETWORK_WATCHER'] }
+ only_if { !nw.nil? }
describe azurerm_network_watcher(resource_group: resource_group, name: nw) do
it { should exist }
diff --git a/test/integration/verify/controls/azurerm_network_watchers.rb b/test/integration/verify/controls/azurerm_network_watchers.rb
index 7eebdef84..c64be9308 100644
--- a/test/integration/verify/controls/azurerm_network_watchers.rb
+++ b/test/integration/verify/controls/azurerm_network_watchers.rb
@@ -1,10 +1,21 @@
resource_group = input('resource_group', value: nil)
+nw = input('network_watcher_name', value: []).first
control 'azurerm_network_watchers' do
- only_if { ENV['NETWORK_WATCHER'] }
+ only_if { !nw.nil? }
describe azurerm_network_watchers(resource_group: resource_group) do
it { should exist }
its('names') { should be_an(Array) }
end
end
+
+control 'azure_network_watchers' do
+ only_if { !nw.nil? }
+
+ azurerm_network_watchers.ids.each do |id|
+ describe azure_network_watcher(resource_id: id) do
+ it { should exist }
+ end
+ end
+end
diff --git a/test/unit/resources/azure_network_watchers_test.rb b/test/unit/resources/azure_network_watchers_test.rb
new file mode 100644
index 000000000..eb3e25e3f
--- /dev/null
+++ b/test/unit/resources/azure_network_watchers_test.rb
@@ -0,0 +1,25 @@
+require_relative 'helper'
+require 'azure_network_watchers'
+
+class AzureNetworkWatchersConstructorTest < Minitest::Test
+ # resource_type should not be allowed.
+ def test_resource_type_not_ok
+ assert_raises(ArgumentError) { AzureNetworkWatchers.new(resource_provider: 'some_type') }
+ end
+
+ def tag_value_not_ok
+ assert_raises(ArgumentError) { AzureNetworkWatchers.new(tag_value: 'some_tag_value') }
+ end
+
+ def tag_name_not_ok
+ assert_raises(ArgumentError) { AzureNetworkWatchers.new(tag_name: 'some_tag_name') }
+ end
+
+ def test_resource_id_not_ok
+ assert_raises(ArgumentError) { AzureNetworkWatchers.new(resource_id: 'some_id') }
+ end
+
+ def test_name_not_ok
+ assert_raises(ArgumentError) { AzureNetworkWatchers.new(name: 'some_name') }
+ end
+end
diff --git a/test/unit/resources/azure_networke_watcher_test.rb b/test/unit/resources/azure_networke_watcher_test.rb
new file mode 100644
index 000000000..60cb0fa59
--- /dev/null
+++ b/test/unit/resources/azure_networke_watcher_test.rb
@@ -0,0 +1,17 @@
+require_relative 'helper'
+require 'azure_network_watcher'
+
+class AzureNetworkWatcherConstructorTest < Minitest::Test
+ def test_empty_param_not_ok
+ assert_raises(ArgumentError) { AzureNetworkWatcher.new }
+ end
+
+ # resource_provider should not be allowed.
+ def test_resource_provider_not_ok
+ assert_raises(ArgumentError) { AzureNetworkWatcher.new(resource_provider: 'some_type') }
+ end
+
+ def test_resource_group
+ assert_raises(ArgumentError) { AzureNetworkWatcher.new(name: 'my-name') }
+ end
+end