diff --git a/Makefile b/Makefile
index e78a2f6..588de8d 100644
--- a/Makefile
+++ b/Makefile
@@ -74,4 +74,7 @@ endif
ifneq ("${METAL_HARDWARE_RESERVATION_PROJECT_ID}", "")
echo "metal_hardware_reservation_project_id: ${METAL_HARDWARE_RESERVATION_PROJECT_ID}" >> $(INTEGRATION_CONFIG)
endif
+ifneq ("${ANSIBLE_ACC_METAL_DEDICATED_CONNECTION_ID}", "")
+ echo "ansible_acc_metal_dedicated_connection_id: ${ANSIBLE_ACC_METAL_DEDICATED_CONNECTION_ID}" >> $(INTEGRATION_CONFIG)
+endif
diff --git a/README.md b/README.md
index 0a1d001..e1d0bf8 100755
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ Name | Description |
[equinix.cloud.metal_project_ssh_key](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_project_ssh_key.md)|Manage a project ssh key in Equinix Metal|
[equinix.cloud.metal_reserved_ip_block](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_reserved_ip_block.md)|Create/delete blocks of reserved IP addresses in a project.|
[equinix.cloud.metal_ssh_key](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_ssh_key.md)|Manage personal SSH keys in Equinix Metal|
+[equinix.cloud.metal_virtual_circuit](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_virtual_circuit.md)|Manage a Virtual Circuit in Equinix Metal|
[equinix.cloud.metal_vlan](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_vlan.md)|Manage a VLAN resource in Equinix Metal|
[equinix.cloud.metal_vrf](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_vrf.md)|Manage a VRF resource in Equinix Metal|
@@ -60,6 +61,7 @@ Name | Description |
[equinix.cloud.metal_project_ssh_key_info](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_project_ssh_key_info.md)|Gather project SSH keys.|
[equinix.cloud.metal_reserved_ip_block_info](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_reserved_ip_block_info.md)|Gather list of reserved IP blocks|
[equinix.cloud.metal_ssh_key_info](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_ssh_key_info.md)|Gather personal SSH keys|
+[equinix.cloud.metal_virtual_circuit_info](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_virtual_circuit_info.md)|Gather information about Equinix Metal Virtual Circuits|
[equinix.cloud.metal_vlan_info](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_vlan_info.md)|Gather VLANs.|
[equinix.cloud.metal_vrf_info](https://github.com/equinix-labs/ansible-collection-equinix/blob/v0.5.0/docs/modules/metal_vrf_info.md)|Gather VRFs|
diff --git a/docs/modules/metal_virtual_circuit.md b/docs/modules/metal_virtual_circuit.md
new file mode 100644
index 0000000..4016e5a
--- /dev/null
+++ b/docs/modules/metal_virtual_circuit.md
@@ -0,0 +1,100 @@
+# metal_virtual_circuit
+
+Manage a Virtual Circuit in Equinix Metal. You can use *id* or *name* to lookup the resource. If you want to create new resource, you must provide *name*.
+
+
+- [Examples](#examples)
+- [Parameters](#parameters)
+- [Return Values](#return-values)
+
+## Examples
+
+```yaml
+- name: create first VRF virtual circuit for test
+ hosts: localhost
+ tasks:
+ - equinix.cloud.metal_virtual_circuit:
+ connection_id: "52373d96-ac4e-496c-8721-f7ef18a01331"
+ port_id: "52373d96-ac4e-496c-8721-f7ef18a01331"
+ name: "test_virtual_circuit"
+ nni_vlan: 1056
+ peer_asn: 66000
+ project_id: "11e047e1-f51a-49c6-b5b2-1c7bfa4391e6"
+ subnet: "192.168.151.126/31"
+ vrf: "029c4219-04b7-4992-9fef-29ea7e2378a5"
+
+```
+
+
+
+
+
+
+
+
+
+
+## Parameters
+
+| Field | Type | Required | Description |
+|-----------|------|----------|------------------------------------------------------------------------------|
+| `id` |
`str` | Optional | UUID of the Virtual Circuit. |
+| `name` | `str` | Optional | Name of the Virtual Circuit resource. **(Updatable)** |
+| `connection_id` | `str` | Optional | UUID of Connection where the VC is scoped to. |
+| `project_id` | `str` | Optional | UUID of the Project where the VC is scoped to. |
+| `port_id` | `str` | Optional | UUID of the Connection Port where the VC is scoped to. |
+| `nni_vlan` | `int` | Optional | Equinix Metal network-to-network VLAN ID. |
+| `vlan_id` | `str` | Optional | UUID of the VLAN to associate. |
+| `vnid` | `str` | Optional | VNID VLAN parameter, see the documentation for Equinix Fabric. |
+| `description` | `str` | Optional | Description for the Virtual Circuit resource. |
+| `tags` | `str` | Optional | Tags for the Virtual Circuit resource. |
+| `speed` | `str` | Optional | Speed of the Virtual Circuit resource. |
+| `vrf` | `str` | Optional | UUID of the VRF to associate. |
+| `peer_asn` | `int` | Optional | The BGP ASN of the peer. The same ASN may be the used across several VCs, but it cannot be the same as the local_asn of the VRF. |
+| `subnet` | `str` | Optional | A subnet from one of the IP blocks associated with the VRF that we will help create an IP reservation for. Can only be either a /30 or /31. For a /31 block, it will only have two IP addresses, which will be used for the metal_ip and customer_ip. For a /30 block, it will have four IP addresses, but the first and last IP addresses are not usable. We will default to the first usable IP address for the metal_ip. |
+| `metal_ip` | `str` | Optional | The Metal IP address for the SVI (Switch Virtual Interface) of the VirtualCircuit. Will default to the first usable IP in the subnet. |
+| `customer_ip` | `str` | Optional | The Customer IP address which the CSR switch will peer with. Will default to the other usable IP in the subnet. |
+| `md5` | `str` | Optional | The password that can be set for the VRF BGP peer |
+| `timeout` | `int` | Optional | Timeout in seconds for Virtual Circuit to get to "ready" state **(Default: `15`)** |
+
+
+
+
+
+
+## Return Values
+
+
+
+### Sample Response for metal_virtual_circuit
+```json
+{
+ "changed": false,
+ "customer_ip": "192.168.151.127",
+ "id": "84f35a2f-1e0c-43ee-bd94-87aec0c5ffec",
+ "metal_ip": "192.168.151.126",
+ "name": "test_virtual_circuit",
+ "nni_vlan": 1056,
+ "peer_asn": 66000,
+ "port": {
+ "href": "/metal/v1/connections/52373d96-ac4e-496c-8721-f7ef18a01331/ports/52373d96-ac4e-496c-8721-f7ef18a01331",
+ "id": "4632fb7b-b1cf-48bc-8f20-a69b0a91d326"
+ },
+ "project": {
+ "href": "/metal/v1/projects/11e047e1-f51a-49c6-b5b2-1c7bfa4391e6",
+ "id": "11e047e1-f51a-49c6-b5b2-1c7bfa4391e6"
+ },
+ "project_id": "11e047e1-f51a-49c6-b5b2-1c7bfa4391e6",
+ "status": "active",
+ "subnet": "192.168.151.126/31",
+ "tags": [],
+ "type": "vrf",
+ "vrf": {
+ "bill": false,
+ "href": "/metal/v1/vrfs/029c4219-04b7-4992-9fef-29ea7e2378a5",
+ "id": "029c4219-04b7-4992-9fef-29ea7e2378a5"
+ }
+}
+```
+
+
diff --git a/docs/modules/metal_virtual_circuit_info.md b/docs/modules/metal_virtual_circuit_info.md
new file mode 100644
index 0000000..4bf467e
--- /dev/null
+++ b/docs/modules/metal_virtual_circuit_info.md
@@ -0,0 +1,59 @@
+# metal_virtual_circuit_info
+
+Gather information about Equinix Metal Virtual Circuits
+
+
+- [Examples](#examples)
+- [Parameters](#parameters)
+- [Return Values](#return-values)
+
+## Examples
+
+```yaml
+- name: Gather information about all projects in an organization
+ hosts: localhost
+ tasks:
+ - equinix.cloud.metal_virtual_circuit_info:
+ organization_id: 2a5122b9-c323-4d5c-b53c-9ad3f54273e7
+
+```
+
+
+
+
+
+
+
+
+
+
+## Parameters
+
+| Field | Type | Required | Description |
+|-----------|------|----------|------------------------------------------------------------------------------|
+| `connection_id` | `str` | Optional | ID of the virtual circuit resource |
+| `organization_id` | `str` | Optional | ID of the organisation to which the virtual circuit belongs |
+
+
+
+
+
+
+## Return Values
+
+
+
+### Sample Response for resources
+```json
+{
+ "backend_transfer_enabled": false,
+ "customdata": {},
+ "description": "",
+ "id": "31d3ae8b-bd5a-41f3-a420-055211345cc7",
+ "name": "ansible-integration-test-project-csle6t2y-project2",
+ "organization_id": "70c2f878-9f32-452e-8c69-ab15480e1d99",
+ "payment_method_id": "845b45a3-c565-47e5-b9b6-a86204a73d29"
+}
+```
+
+
diff --git a/plugins/module_utils/metal/api_routes.py b/plugins/module_utils/metal/api_routes.py
index e7525a7..6260fff 100644
--- a/plugins/module_utils/metal/api_routes.py
+++ b/plugins/module_utils/metal/api_routes.py
@@ -78,6 +78,12 @@ def get_routes(mpc):
("metal_plan", action.GET): spec_types.Specs(
equinix_metal.PlansApi(mpc).find_plans_by_project,
),
+ ('metal_virtual_circuit', action.GET): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).get_virtual_circuit,
+ ),
+ ('metal_virtual_circuit_vrf', action.GET): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).get_virtual_circuit,
+ ),
# LISTERS
('metal_project_device', action.LIST): spec_types.Specs(
@@ -161,6 +167,18 @@ def get_routes(mpc):
equinix_metal.PlansApi(mpc).find_plans,
{'category': 'category', 'type': 'type', 'slug': 'slug', 'include': 'include', 'exclude': 'exclude'},
),
+ ('metal_virtual_circuit', action.LIST): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).list_interconnection_virtual_circuits,
+ {'connection_id': 'connection_id'},
+ ),
+ ('metal_virtual_circuit_vrf', action.LIST): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).list_interconnection_virtual_circuits,
+ {'connection_id': 'connection_id'},
+ ),
+ ('metal_port_virtual_circuit', action.LIST): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).list_interconnection_port_virtual_circuits,
+ {'connection_id': 'connection_id', 'port_id': 'port_id'},
+ ),
# DELETERS
('metal_device', action.DELETE): spec_types.Specs(
@@ -194,7 +212,12 @@ def get_routes(mpc):
('metal_bgp_session', action.DELETE): spec_types.Specs(
equinix_metal.BGPApi(mpc).delete_bgp_session,
),
-
+ ('metal_virtual_circuit', action.DELETE): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).delete_virtual_circuit,
+ ),
+ ('metal_virtual_circuit_vrf', action.DELETE): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).delete_virtual_circuit,
+ ),
# CREATORS
('metal_device', action.CREATE): spec_types.Specs(
@@ -284,6 +307,19 @@ def get_routes(mpc):
{'id': 'project_id'},
equinix_metal.BgpConfigRequestInput,
),
+ ('metal_virtual_circuit', action.CREATE): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).create_interconnection_port_virtual_circuit,
+ {'connection_id': 'connection_id', 'port_id': 'port_id'},
+ equinix_metal.VlanVirtualCircuitCreateInput,
+ equinix_metal.VirtualCircuitCreateInput,
+ ),
+ ('metal_virtual_circuit_vrf', action.CREATE): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).create_interconnection_port_virtual_circuit,
+ {'connection_id': 'connection_id', 'port_id': 'port_id'},
+ equinix_metal.VrfVirtualCircuitCreateInput,
+ equinix_metal.VirtualCircuitCreateInput,
+ ),
+
# UPDATERS
('metal_device', action.UPDATE): spec_types.Specs(
@@ -326,4 +362,16 @@ def get_routes(mpc):
{},
equinix_metal.BGPSessionInput,
),
+ ('metal_virtual_circuit', action.UPDATE): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).update_virtual_circuit,
+ {},
+ equinix_metal.VlanVirtualCircuitUpdateInput,
+ equinix_metal.VirtualCircuitUpdateInput,
+ ),
+ ('metal_virtual_circuit_vrf', action.UPDATE): spec_types.Specs(
+ equinix_metal.InterconnectionsApi(mpc).update_virtual_circuit,
+ {},
+ equinix_metal.VrfVirtualCircuitUpdateInput,
+ equinix_metal.VirtualCircuitUpdateInput,
+ ),
}
diff --git a/plugins/module_utils/metal/metal_api.py b/plugins/module_utils/metal/metal_api.py
index bd5a2b4..8a82d23 100644
--- a/plugins/module_utils/metal/metal_api.py
+++ b/plugins/module_utils/metal/metal_api.py
@@ -131,7 +131,7 @@ def extract_ids_from_projects_hrefs(resource: dict):
'name': 'name',
'metro': 'metro',
'project_id': 'project.id',
- 'description': 'description',
+ 'description': optional_str('description'),
'local_asn': 'local_asn',
'ip_ranges': 'ip_ranges',
}
@@ -152,6 +152,7 @@ def extract_ids_from_projects_hrefs(resource: dict):
'bgp_sessions', # metal_bgp_session
'sessions', # metal_bgp_session_info
'plans',
+ 'virtual_circuits',
]
@@ -289,6 +290,23 @@ def private_ipv4_subnet_size(resource: dict):
'available_in_metros': 'available_in_metros',
}
+METAL_VIRTUAL_CIRCUIT_RESPONSE_ATTRIBUTE_MAP = {
+ 'id': 'id',
+ 'name': 'name',
+ 'customer_ip': 'customer_ip',
+ 'metal_ip': 'metal_ip',
+ 'nni_vlan': 'nni_vlan',
+ 'peer_asn': 'peer_asn',
+ 'port': 'port',
+ 'project': 'project',
+ 'status': 'status',
+ 'subnet': optional('subnet'),
+ 'tags': 'tags',
+ 'type': 'type',
+ 'vrf': 'vrf',
+ 'project_id': 'project.id',
+}
+
def get_attribute_mapper(resource_type):
"""
@@ -309,6 +327,7 @@ def get_attribute_mapper(resource_type):
bgp_resources = {'metal_bgp_session', 'metal_bgp_session_by_project'}
project_bgp_config_resources = {'metal_project_bgp_config'}
plan_resources = set(["metal_plan"])
+ virtual_circuit_resources = set(["metal_virtual_circuit", "metal_virtual_circuit_vrf"])
if resource_type in device_resources:
return METAL_DEVICE_RESPONSE_ATTRIBUTE_MAP
elif resource_type in project_resources:
@@ -341,6 +360,8 @@ def get_attribute_mapper(resource_type):
return METAL_PROJECT_BGP_CONFIG_RESPONSE_ATTRIBUTE_MAP
elif resource_type in plan_resources:
return METAL_PLAN_RESPONSE_ATTRIBUTE_MAP
+ elif resource_type in virtual_circuit_resources:
+ return METAL_VIRTUAL_CIRCUIT_RESPONSE_ATTRIBUTE_MAP
else:
raise NotImplementedError("No mapper for resource type %s" % resource_type)
diff --git a/plugins/modules/metal_virtual_circuit.py b/plugins/modules/metal_virtual_circuit.py
new file mode 100644
index 0000000..5c012fd
--- /dev/null
+++ b/plugins/modules/metal_virtual_circuit.py
@@ -0,0 +1,440 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from equinix_metal.exceptions import NotFoundException
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# DOCUMENTATION, EXAMPLES, and RETURN are generated by
+# ansible_specdoc. Do not edit them directly.
+
+DOCUMENTATION = '''
+author: Equinix DevRel Team (@equinix)
+description: Manage a Virtual Circuit in Equinix Metal. You can use *id* or *name*
+ to lookup the resource. If you want to create new resource, you must provide *name*.
+module: metal_virtual_circuit
+notes: []
+options:
+ connection_id:
+ description:
+ - UUID of Connection where the VC is scoped to.
+ required: false
+ type: str
+ customer_ip:
+ description:
+ - The Customer IP address which the CSR switch will peer with.
+ - Will default to the other usable IP in the subnet.
+ required: false
+ type: str
+ description:
+ description:
+ - Description for the Virtual Circuit resource.
+ required: false
+ type: str
+ id:
+ description:
+ - UUID of the Virtual Circuit.
+ required: false
+ type: str
+ md5:
+ description:
+ - The password that can be set for the VRF BGP peer
+ required: false
+ type: str
+ metal_ip:
+ description:
+ - The Metal IP address for the SVI (Switch Virtual Interface) of the VirtualCircuit.
+ - Will default to the first usable IP in the subnet.
+ required: false
+ type: str
+ name:
+ description:
+ - Name of the Virtual Circuit resource.
+ required: false
+ type: str
+ nni_vlan:
+ description:
+ - Equinix Metal network-to-network VLAN ID.
+ required: false
+ type: int
+ peer_asn:
+ description:
+ - The BGP ASN of the peer.
+ - The same ASN may be the used across several VCs, but it cannot be the same as
+ the local_asn of the VRF.
+ required: false
+ type: int
+ port_id:
+ description:
+ - UUID of the Connection Port where the VC is scoped to.
+ required: false
+ type: str
+ project_id:
+ description:
+ - UUID of the Project where the VC is scoped to.
+ required: false
+ type: str
+ speed:
+ description:
+ - Speed of the Virtual Circuit resource.
+ required: false
+ type: str
+ subnet:
+ description:
+ - A subnet from one of the IP blocks associated with the VRF that we will help
+ create an IP reservation for.
+ - Can only be either a /30 or /31.
+ - For a /31 block, it will only have two IP addresses, which will be used for
+ the metal_ip and customer_ip.
+ - For a /30 block, it will have four IP addresses, but the first and last IP addresses
+ are not usable.
+ - We will default to the first usable IP address for the metal_ip.
+ required: false
+ type: str
+ tags:
+ description:
+ - Tags for the Virtual Circuit resource.
+ required: false
+ type: str
+ timeout:
+ default: 15
+ description:
+ - Timeout in seconds for Virtual Circuit to get to "ready" state
+ required: false
+ type: int
+ vlan_id:
+ description:
+ - UUID of the VLAN to associate.
+ required: false
+ type: str
+ vnid:
+ description:
+ - VNID VLAN parameter, see the documentation for Equinix Fabric.
+ required: false
+ type: str
+ vrf:
+ description:
+ - UUID of the VRF to associate.
+ required: false
+ type: str
+requirements: null
+short_description: Manage a Virtual Circuit in Equinix Metal
+'''
+EXAMPLES = '''
+- name: create first VRF virtual circuit for test
+ hosts: localhost
+ tasks:
+ - equinix.cloud.metal_virtual_circuit:
+ connection_id: 52373d96-ac4e-496c-8721-f7ef18a01331
+ port_id: 52373d96-ac4e-496c-8721-f7ef18a01331
+ name: test_virtual_circuit
+ nni_vlan: 1056
+ peer_asn: 66000
+ project_id: 11e047e1-f51a-49c6-b5b2-1c7bfa4391e6
+ subnet: 192.168.151.126/31
+ vrf: 029c4219-04b7-4992-9fef-29ea7e2378a5
+'''
+RETURN = '''
+metal_virtual_circuit:
+ description: The module object
+ returned: always
+ sample:
+ - changed: false
+ customer_ip: 192.168.151.127
+ id: 84f35a2f-1e0c-43ee-bd94-87aec0c5ffec
+ metal_ip: 192.168.151.126
+ name: test_virtual_circuit
+ nni_vlan: 1056
+ peer_asn: 66000
+ port:
+ href: /metal/v1/connections/52373d96-ac4e-496c-8721-f7ef18a01331/ports/52373d96-ac4e-496c-8721-f7ef18a01331
+ id: 4632fb7b-b1cf-48bc-8f20-a69b0a91d326
+ project:
+ href: /metal/v1/projects/11e047e1-f51a-49c6-b5b2-1c7bfa4391e6
+ id: 11e047e1-f51a-49c6-b5b2-1c7bfa4391e6
+ project_id: 11e047e1-f51a-49c6-b5b2-1c7bfa4391e6
+ status: active
+ subnet: 192.168.151.126/31
+ tags: []
+ type: vrf
+ vrf:
+ bill: false
+ href: /metal/v1/vrfs/029c4219-04b7-4992-9fef-29ea7e2378a5
+ id: 029c4219-04b7-4992-9fef-29ea7e2378a5
+ type: dict
+'''
+
+# End of generated documentation
+
+# This is a template for a new module. It is not meant to be used as is.
+# It is meant to be copied and modified to create a new module.
+# Replace all occurrences of "metal_resource" with the name of the new
+# module, for example "metal_vlan".
+
+
+from ansible.module_utils._text import to_native
+from ansible_specdoc.objects import (
+ SpecField,
+ FieldType,
+ SpecReturnValue,
+)
+import traceback
+
+from ansible_collections.equinix.cloud.plugins.module_utils.equinix import (
+ EquinixModule,
+ get_diff,
+ getSpecDocMeta,
+)
+
+
+module_spec = dict(
+ id=SpecField(
+ type=FieldType.string,
+ description=['UUID of the Virtual Circuit.'],
+ ),
+ name=SpecField(
+ type=FieldType.string,
+ description=['Name of the Virtual Circuit resource.'],
+ editable=True,
+ ),
+ connection_id=SpecField(
+ type=FieldType.string,
+ description=[
+ 'UUID of Connection where the VC is scoped to.'
+ ],
+ ),
+ project_id=SpecField(
+ type=FieldType.string,
+ description=[
+ 'UUID of the Project where the VC is scoped to.'
+ ],
+ ),
+ port_id=SpecField(
+ type=FieldType.string,
+ description=[
+ 'UUID of the Connection Port where the VC is scoped to.'
+ ],
+ ),
+ nni_vlan=SpecField(
+ type=FieldType.integer,
+ description=[
+ 'Equinix Metal network-to-network VLAN ID.'
+ ],
+ ),
+ vlan_id=SpecField(
+ type=FieldType.string,
+ description=[
+ 'UUID of the VLAN to associate.'
+ ],
+ ),
+ vnid=SpecField(
+ type=FieldType.string,
+ description=[
+ 'VNID VLAN parameter, see the documentation for Equinix Fabric.'
+ ],
+ ),
+ description=SpecField(
+ type=FieldType.string,
+ description=[
+ 'Description for the Virtual Circuit resource.'
+ ],
+ ),
+ tags=SpecField(
+ type=FieldType.string,
+ description=[
+ 'Tags for the Virtual Circuit resource.'
+ ],
+ ),
+ speed=SpecField(
+ type=FieldType.string,
+ description=[
+ 'Speed of the Virtual Circuit resource.'
+ ],
+ ),
+ vrf=SpecField(
+ type=FieldType.string,
+ description=[
+ 'UUID of the VRF to associate.'
+ ],
+ ),
+ peer_asn=SpecField(
+ type=FieldType.integer,
+ description=[
+ 'The BGP ASN of the peer.',
+ 'The same ASN may be the used across several VCs, but it cannot be the same as the local_asn of the VRF.'
+ ],
+ ),
+ subnet=SpecField(
+ type=FieldType.string,
+ description=[
+ 'A subnet from one of the IP blocks associated with the VRF that we will help create an IP reservation for.',
+ 'Can only be either a /30 or /31.',
+ 'For a /31 block, it will only have two IP addresses, which will be used for the metal_ip and customer_ip.',
+ 'For a /30 block, it will have four IP addresses, but the first and last IP addresses are not usable.',
+ 'We will default to the first usable IP address for the metal_ip.',
+ ],
+ ),
+ metal_ip=SpecField(
+ type=FieldType.string,
+ description=[
+ 'The Metal IP address for the SVI (Switch Virtual Interface) of the VirtualCircuit.',
+ 'Will default to the first usable IP in the subnet.'
+ ],
+ ),
+ customer_ip=SpecField(
+ type=FieldType.string,
+ description=[
+ 'The Customer IP address which the CSR switch will peer with.',
+ 'Will default to the other usable IP in the subnet.'
+ ],
+ ),
+ md5=SpecField(
+ type=FieldType.string,
+ description=[
+ 'The password that can be set for the VRF BGP peer'
+ ],
+ ),
+ timeout=SpecField(
+ type=FieldType.integer,
+ description=[
+ 'Timeout in seconds for Virtual Circuit to get to "ready" state'
+ ],
+ default=15,
+ ),
+)
+
+specdoc_examples = [
+ '''
+- name: create first VRF virtual circuit for test
+ hosts: localhost
+ tasks:
+ - equinix.cloud.metal_virtual_circuit:
+ connection_id: "52373d96-ac4e-496c-8721-f7ef18a01331"
+ port_id: "52373d96-ac4e-496c-8721-f7ef18a01331"
+ name: "test_virtual_circuit"
+ nni_vlan: 1056
+ peer_asn: 66000
+ project_id: "11e047e1-f51a-49c6-b5b2-1c7bfa4391e6"
+ subnet: "192.168.151.126/31"
+ vrf: "029c4219-04b7-4992-9fef-29ea7e2378a5"
+''',
+]
+
+return_values = [
+ {
+ "changed": False,
+ "customer_ip": "192.168.151.127",
+ "id": "84f35a2f-1e0c-43ee-bd94-87aec0c5ffec",
+ "metal_ip": "192.168.151.126",
+ "name": "test_virtual_circuit",
+ "nni_vlan": 1056,
+ "peer_asn": 66000,
+ "port": {
+ "href": "/metal/v1/connections/52373d96-ac4e-496c-8721-f7ef18a01331/ports/52373d96-ac4e-496c-8721-f7ef18a01331",
+ "id": "4632fb7b-b1cf-48bc-8f20-a69b0a91d326"
+ },
+ "project": {
+ "href": "/metal/v1/projects/11e047e1-f51a-49c6-b5b2-1c7bfa4391e6",
+ "id": "11e047e1-f51a-49c6-b5b2-1c7bfa4391e6"
+ },
+ "project_id": "11e047e1-f51a-49c6-b5b2-1c7bfa4391e6",
+ "status": "active",
+ "subnet": "192.168.151.126/31",
+ "tags": [],
+ "type": "vrf",
+ "vrf": {
+ "bill": False,
+ "href": "/metal/v1/vrfs/029c4219-04b7-4992-9fef-29ea7e2378a5",
+ "id": "029c4219-04b7-4992-9fef-29ea7e2378a5"
+ }
+ }
+]
+
+MUTABLE_ATTRIBUTES = [
+ k for k, v in module_spec.items() if v.editable
+]
+
+SPECDOC_META = getSpecDocMeta(
+ short_description='Manage a Virtual Circuit in Equinix Metal',
+ description=(
+ 'Manage a Virtual Circuit in Equinix Metal. '
+ 'You can use *id* or *name* to lookup the resource. '
+ 'If you want to create new resource, you must provide *name*.'
+ ),
+ examples=specdoc_examples,
+ options=module_spec,
+ return_values={
+ "metal_virtual_circuit": SpecReturnValue(
+ description='The module object',
+ type=FieldType.dict,
+ sample=return_values,
+ ),
+ },
+)
+
+
+def main():
+ module = EquinixModule(
+ argument_spec=SPECDOC_META.ansible_spec,
+ )
+
+ state = module.params.get("state")
+ changed = False
+ module_route = 'metal_virtual_circuit_vrf' if module.params.get('vrf', False) else 'metal_virtual_circuit'
+
+ try:
+ module.params_syntax_check()
+ fetched = None
+ if module.params.get("id"):
+ tolerate_not_found = state == "absent"
+ fetched = module.get_by_id(module_route, tolerate_not_found)
+ else:
+ fetched = module.get_one_from_list(
+ module_route,
+ ["name"],
+ )
+
+ except NotFoundException as e:
+ pass
+
+ except Exception as e:
+ tb = traceback.format_exc()
+ module.fail_json(msg="Error in metal_virtual_circuit: {0}".format(to_native(e)),
+ exception=tb)
+
+ try:
+ if fetched:
+ module.params['id'] = fetched['id']
+ if state == "present":
+ diff = get_diff(module.params, fetched, MUTABLE_ATTRIBUTES)
+ if diff:
+ fetched = module.update_by_id(diff, module_route)
+ changed = True
+
+ else:
+ module.delete_by_id(module_route)
+
+ module.wait_for_resource_removal(
+ module_route,
+ timeout=module.params.get("timeout"),
+ )
+ changed = True
+ else:
+ if state == "present":
+ fetched = module.create(module_route)
+ if 'id' not in fetched:
+ module.fail_json(msg="UUID not found in resource creation response")
+ changed = True
+ else:
+ fetched = {}
+
+ except Exception as e:
+ tb = traceback.format_exc()
+ module.fail_json(msg="Error in metal_virtual_circuit: {0}".format(to_native(e)),
+ exception=tb)
+
+ fetched.update({'changed': changed})
+ module.exit_json(**fetched)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/plugins/modules/metal_virtual_circuit_info.py b/plugins/modules/metal_virtual_circuit_info.py
new file mode 100644
index 0000000..90e5cab
--- /dev/null
+++ b/plugins/modules/metal_virtual_circuit_info.py
@@ -0,0 +1,128 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# DOCUMENTATION, EXAMPLES, and METAL_PROJECT_ARGS are generated by
+# ansible_specdoc. Do not edit them directly.
+
+DOCUMENTATION = '''
+author: Equinix DevRel Team (@equinix)
+description: Gather information about Equinix Metal Virtual Circuits
+module: metal_virtual_circuit_info
+notes: []
+options:
+ connection_id:
+ description:
+ - ID of the virtual circuit resource
+ required: false
+ type: str
+ organization_id:
+ description:
+ - ID of the organisation to which the virtual circuit belongs
+ required: false
+ type: str
+requirements: null
+short_description: Gather information about Equinix Metal Virtual Circuits
+'''
+EXAMPLES = '''
+- name: Gather information about all projects in an organization
+ hosts: localhost
+ tasks:
+ - equinix.cloud.metal_virtual_circuit_info:
+ organization_id: 2a5122b9-c323-4d5c-b53c-9ad3f54273e7
+'''
+RETURN = '''
+resources:
+ description: Found resources
+ returned: always
+ sample:
+ - backend_transfer_enabled: false
+ customdata: {}
+ description: ''
+ id: 31d3ae8b-bd5a-41f3-a420-055211345cc7
+ name: ansible-integration-test-project-csle6t2y-project2
+ organization_id: 70c2f878-9f32-452e-8c69-ab15480e1d99
+ payment_method_id: 845b45a3-c565-47e5-b9b6-a86204a73d29
+ type: dict
+'''
+
+# End
+
+from ansible.module_utils._text import to_native
+from ansible_specdoc.objects import SpecField, FieldType, SpecReturnValue
+import traceback
+
+from ansible_collections.equinix.cloud.plugins.module_utils.equinix import (
+ EquinixModule,
+ getSpecDocMeta,
+)
+
+module_spec = dict(
+ connection_id=SpecField(
+ type=FieldType.string,
+ description=['ID of the virtual circuit resource'],
+ ),
+ organization_id=SpecField(
+ type=FieldType.string,
+ description=["ID of the organisation to which the virtual circuit belongs"],
+ ),
+)
+
+specdoc_examples = ['''
+- name: Gather information about all projects in an organization
+ hosts: localhost
+ tasks:
+ - equinix.cloud.metal_virtual_circuit_info:
+ organization_id: 2a5122b9-c323-4d5c-b53c-9ad3f54273e7
+''']
+
+return_values = [
+ {
+ "backend_transfer_enabled": False,
+ "customdata": {},
+ "description": "",
+ "id": "31d3ae8b-bd5a-41f3-a420-055211345cc7",
+ "name": "ansible-integration-test-project-csle6t2y-project2",
+ "organization_id": "70c2f878-9f32-452e-8c69-ab15480e1d99",
+ "payment_method_id": "845b45a3-c565-47e5-b9b6-a86204a73d29"
+ }
+]
+
+SPECDOC_META = getSpecDocMeta(
+ short_description="Gather information about Equinix Metal Virtual Circuits",
+ description=(
+ 'Gather information about Equinix Metal Virtual Circuits'
+ ),
+ examples=specdoc_examples,
+ options=module_spec,
+ return_values={
+ "resources": SpecReturnValue(
+ description='Found resources',
+ type=FieldType.dict,
+ sample=return_values,
+ ),
+ },
+)
+
+
+def main():
+ module = EquinixModule(
+ argument_spec=SPECDOC_META.ansible_spec,
+ is_info=True,
+ )
+ try:
+ module.params_syntax_check()
+ if module.params.get('connection_id'):
+ return_value = {'resources': module.get_list("metal_virtual_circuit")}
+ else:
+ module.fail_json(msg="missing connection_id parameter")
+
+ except Exception as e:
+ tr = traceback.format_exc()
+ module.fail_json(msg=to_native(e), exception=tr)
+ module.exit_json(**return_value)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/integration/targets/metal_virtual_circuit/tasks/main.yml b/tests/integration/targets/metal_virtual_circuit/tasks/main.yml
new file mode 100644
index 0000000..add809f
--- /dev/null
+++ b/tests/integration/targets/metal_virtual_circuit/tasks/main.yml
@@ -0,0 +1,199 @@
+# https://registry.terraform.io/providers/equinix/equinix/latest/docs/resources/equinix_metal_virtual_circuit
+- name: metal_virtual_circuit
+ module_defaults:
+ equinix.cloud.metal_virtual_circuit:
+ metal_api_token: '{{ metal_api_token }}'
+ equinix.cloud.metal_virtual_circuit_info:
+ metal_api_token: '{{ metal_api_token }}'
+ equinix.cloud.metal_project:
+ metal_api_token: '{{ metal_api_token }}'
+ equinix.cloud.metal_connection:
+ metal_api_token: '{{ metal_api_token }}'
+ equinix.cloud.metal_project_info:
+ metal_api_token: '{{ metal_api_token }}'
+ equinix.cloud.metal_vrf:
+ metal_api_token: '{{ metal_api_token }}'
+ equinix.cloud.metal_vrf_info:
+ metal_api_token: '{{ metal_api_token }}'
+ equinix.cloud.metal_organization_info:
+ metal_api_token: '{{ metal_api_token }}'
+ block:
+ - set_fact:
+ test_resource_name_prefix: 'ansible-integration-test-virtualcircuit'
+ - set_fact:
+ unique_id: "{{ lookup('password', '/dev/null chars=ascii_lowercase,digits length=8') }}"
+ - set_fact:
+ test_prefix: "{{ test_resource_name_prefix }}-{{ unique_id }}"
+ - set_fact:
+ test_circuit_name: "test_virtual_circuit"
+ test_nni_vlan: 1056
+ test_peer_asn: 66000
+ test_subnet: "192.168.151.126/31"
+
+
+ - name: create project for test
+ equinix.cloud.metal_project:
+ name: "{{ test_prefix }}-project"
+ backend_transfer_enabled: true
+ register: test_project
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: get connection
+ equinix.cloud.metal_connection:
+ id: "{{ ansible_acc_metal_dedicated_connection_id }}"
+ organization_id: "{{ test_project.organization_id }}"
+ register: test_connection
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - debug:
+ var: test_connection
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: create test VRF
+ equinix.cloud.metal_vrf:
+ name: "{{ test_prefix }}"
+ project_id: "{{ test_project.id }}"
+ metro: "{{ test_connection.metro.code }}"
+ description: "Test VRF with ASN 65000"
+ local_asn: 65000
+ ip_ranges:
+ - "192.168.151.0/25"
+ - "192.168.222.0/25"
+ register: test_vrf
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - debug:
+ var: test_vrf
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: Extract the port ID with status 'active' from test_connection
+ set_fact:
+ test_active_ports: "{{ test_connection.ports | selectattr('status', 'equalto', 'active') }}"
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - debug:
+ var: test_active_ports
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - fail:
+ msg: "There is no port with status=active"
+ when:
+ - ansible_acc_metal_dedicated_connection_id is defined
+ - test_active_ports | length == 0
+
+ - name: create first VRF virtual circuit for test
+ equinix.cloud.metal_virtual_circuit:
+ connection_id: "{{ ansible_acc_metal_dedicated_connection_id }}"
+ port_id: "{{ test_active_ports[0].id }}"
+ name: "{{ test_circuit_name }}"
+ nni_vlan: "{{ test_nni_vlan }}"
+ peer_asn: "{{ test_peer_asn }}"
+ project_id: "{{ test_project.id }}"
+ subnet: "{{ test_subnet }}"
+ vrf: "{{ test_vrf.id }}"
+ register: first_virtual_circuit
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: create first VRF virtual circuit for test (idempotence test)
+ equinix.cloud.metal_virtual_circuit:
+ connection_id: "{{ ansible_acc_metal_dedicated_connection_id }}"
+ port_id: "{{ test_active_ports[0].id }}"
+ name: "{{ test_circuit_name }}"
+ nni_vlan: "{{ test_nni_vlan }}"
+ peer_asn: "{{ test_peer_asn }}"
+ project_id: "{{ test_project.id }}"
+ subnet: "{{ test_subnet }}"
+ vrf: "{{ test_vrf.id }}"
+ register: first_virtual_circuit_idempotence
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: create first VRF virtual circuit for test (edit)
+ equinix.cloud.metal_virtual_circuit:
+ id: "{{ first_virtual_circuit.id }}"
+ name: "{{ test_circuit_name }}-changed"
+ register: first_virtual_circuit_changed
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - set_fact:
+ expected_change_name: "{{ test_circuit_name }}-changed"
+
+ - name: assert virtual circuits
+ assert:
+ that:
+ - "first_virtual_circuit.changed == true"
+ - "first_virtual_circuit_idempotence.changed == false"
+ - "first_virtual_circuit_changed.changed == true"
+ - "first_virtual_circuit_changed.name == expected_change_name"
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: fetch virtual circuit
+ equinix.cloud.metal_virtual_circuit:
+ id: "{{ first_virtual_circuit.id }}"
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: fetch virtual circuit by name
+ equinix.cloud.metal_virtual_circuit:
+ name: "{{ expected_change_name }}"
+ connection_id: "{{ ansible_acc_metal_dedicated_connection_id }}"
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - debug:
+ var: first_virtual_circuit
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: list test VCs
+ equinix.cloud.metal_virtual_circuit_info:
+ connection_id: "{{ ansible_acc_metal_dedicated_connection_id }}"
+ register: test_circuits_listed
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - debug:
+ var: test_circuits_listed
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ always:
+ - name: Announce teardown start
+ debug:
+ msg: "***** TESTING COMPLETE. COMMENCE TEARDOWN *****"
+
+ - name: list test VCs
+ equinix.cloud.metal_virtual_circuit_info:
+ connection_id: "{{ ansible_acc_metal_dedicated_connection_id }}"
+ register: test_circuits_listed
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: delete test VCs
+ equinix.cloud.metal_virtual_circuit:
+ id: "{{ item.id }}"
+ state: absent
+ loop: "{{ test_circuits_listed.resources }}"
+ ignore_errors: yes
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: get VRF info
+ equinix.cloud.metal_vrf_info:
+ project_id: "{{ test_project.id }}"
+ register: vrf_list
+
+ - debug:
+ var: vrf_list
+
+ - name: delete test VRF
+ equinix.cloud.metal_vrf:
+ id: "{{ item.id }}"
+ state: absent
+ loop: "{{ vrf_list.resources }}"
+
+ - name: list test projects
+ equinix.cloud.metal_project_info:
+ name: "{{ test_prefix }}"
+ register: test_projects_listed
+ when: ansible_acc_metal_dedicated_connection_id is defined
+
+ - name: delete test projects
+ equinix.cloud.metal_project:
+ id: "{{ item.id }}"
+ state: absent
+ loop: "{{ test_projects_listed.resources }}"
+ ignore_errors: yes
+ when: ansible_acc_metal_dedicated_connection_id is defined