diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3e56e5..204873f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,4 +46,4 @@ jobs: args: release --clean env: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - GITHUB_TOKEN: ${{ secrets.GH_PAT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 88f9b4d..8bda3b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,6 +44,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v4 + - uses: opentofu/setup-opentofu@v1 with: go-version-file: "go.mod" cache: true diff --git a/.gitignore b/.gitignore index 83a87b3..175effe 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ website/node_modules test/.terraform.lock.hcl website/vendor test/*.tfplan +test/*tfplan # Test exclusions !command/test-fixtures/**/*.tfstate diff --git a/docs/data-sources/available_ip_address.md b/docs/data-sources/available_ip_address.md new file mode 100644 index 0000000..712e81c --- /dev/null +++ b/docs/data-sources/available_ip_address.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_available_ip_address Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + This data source retrieves an available IP address from a given prefix in Nautobot +--- + +# nautobot_available_ip_address (Data Source) + +This data source retrieves an available IP address from a given prefix in Nautobot + + + + +## Schema + +### Required + +- `prefix_id` (String) The ID of the prefix from which to retrieve an available IP. + +### Read-Only + +- `address` (String) The available IP address. +- `id` (String) The ID of this resource. +- `ip_version` (Number) The version of the IP address (4 or 6). + + diff --git a/docs/data-sources/cluster.md b/docs/data-sources/cluster.md new file mode 100644 index 0000000..8eb3e6e --- /dev/null +++ b/docs/data-sources/cluster.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_cluster Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about a specific cluster in Nautobot. +--- + +# nautobot_cluster (Data Source) + +Retrieves information about a specific cluster in Nautobot. + + + + +## Schema + +### Required + +- `name` (String) The name of the cluster. + +### Read-Only + +- `cluster_group_id` (String) The ID of the cluster group. +- `cluster_type_id` (String) The ID of the cluster type. +- `comments` (String) Comments or notes about the cluster. +- `created` (String) The creation date of the cluster. +- `id` (String) The UUID of the cluster. +- `last_updated` (String) The last update date of the cluster. +- `location_id` (String) The ID of the location associated with the cluster. +- `tags_ids` (List of String) The IDs of the tags associated with the cluster. +- `tenant_id` (String) The ID of the tenant associated with the cluster. + + diff --git a/docs/data-sources/cluster_type.md b/docs/data-sources/cluster_type.md new file mode 100644 index 0000000..8b12080 --- /dev/null +++ b/docs/data-sources/cluster_type.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_cluster_type Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about a specific cluster type in Nautobot. +--- + +# nautobot_cluster_type (Data Source) + +Retrieves information about a specific cluster type in Nautobot. + + + + +## Schema + +### Required + +- `name` (String) The name of the cluster type to retrieve. + +### Read-Only + +- `created` (String) The date the cluster type was created. +- `description` (String) The description of the cluster type. +- `display` (String) Human-friendly display value for the cluster type. +- `id` (String) The UUID of the cluster type. +- `last_updated` (String) The date the cluster type was last updated. +- `natural_slug` (String) Natural slug for the cluster type. +- `notes_url` (String) Notes URL for the cluster type. +- `object_type` (String) Object type of the cluster type. +- `url` (String) URL of the cluster type. + + diff --git a/docs/data-sources/cluster_types.md b/docs/data-sources/cluster_types.md new file mode 100644 index 0000000..c2efe7e --- /dev/null +++ b/docs/data-sources/cluster_types.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_cluster_types Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about cluster types in Nautobot. +--- + +# nautobot_cluster_types (Data Source) + +Retrieves information about cluster types in Nautobot. + + + + +## Schema + +### Read-Only + +- `cluster_types` (List of Object) (see [below for nested schema](#nestedatt--cluster_types)) +- `id` (String) The ID of this resource. + + +### Nested Schema for `cluster_types` + +Read-Only: + +- `created` (String) +- `description` (String) +- `display` (String) +- `id` (String) +- `last_updated` (String) +- `name` (String) +- `natural_slug` (String) +- `notes_url` (String) +- `object_type` (String) +- `url` (String) + + diff --git a/docs/data-sources/clusters.md b/docs/data-sources/clusters.md new file mode 100644 index 0000000..65eef22 --- /dev/null +++ b/docs/data-sources/clusters.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_clusters Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about clusters in Nautobot. +--- + +# nautobot_clusters (Data Source) + +Retrieves information about clusters in Nautobot. + + + + +## Schema + +### Read-Only + +- `clusters` (List of Object) (see [below for nested schema](#nestedatt--clusters)) +- `id` (String) The ID of this resource. + + +### Nested Schema for `clusters` + +Read-Only: + +- `cluster_group_id` (String) +- `cluster_type_id` (String) +- `comments` (String) +- `created` (String) +- `id` (String) +- `last_updated` (String) +- `location_id` (String) +- `name` (String) +- `tags_ids` (List of String) +- `tenant_id` (String) + + diff --git a/docs/data-sources/manufacturer.md b/docs/data-sources/manufacturer.md new file mode 100644 index 0000000..18fee26 --- /dev/null +++ b/docs/data-sources/manufacturer.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_manufacturer Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about a specific manufacturer in Nautobot. +--- + +# nautobot_manufacturer (Data Source) + +Retrieves information about a specific manufacturer in Nautobot. + + + + +## Schema + +### Required + +- `name` (String) The name of the manufacturer to retrieve. + +### Read-Only + +- `created` (String) Manufacturer's creation date. +- `description` (String) Manufacturer's description. +- `display` (String) Human friendly display value for the manufacturer. +- `id` (String) Manufacturer's UUID. +- `last_updated` (String) Manufacturer's last update. +- `natural_slug` (String) Natural slug for the manufacturer. +- `notes_url` (String) Notes URL for the manufacturer. +- `object_type` (String) Object type of the manufacturer. +- `url` (String) URL of the manufacturer. + + diff --git a/docs/data-sources/manufacturers.md b/docs/data-sources/manufacturers.md index 499a2ca..f8830e6 100644 --- a/docs/data-sources/manufacturers.md +++ b/docs/data-sources/manufacturers.md @@ -26,17 +26,14 @@ Manufacturer data source in the Terraform provider Nautobot. Read-Only: - `created` (String) -- `custom_fields` (Map of String) - `description` (String) -- `devicetype_count` (Number) - `display` (String) - `id` (String) -- `inventoryitem_count` (Number) - `last_updated` (String) - `name` (String) +- `natural_slug` (String) - `notes_url` (String) -- `platform_count` (Number) -- `slug` (String) +- `object_type` (String) - `url` (String) diff --git a/docs/data-sources/prefix.md b/docs/data-sources/prefix.md new file mode 100644 index 0000000..570cc82 --- /dev/null +++ b/docs/data-sources/prefix.md @@ -0,0 +1,35 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_prefix Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about a Prefix in Nautobot by its associated VLAN ID. +--- + +# nautobot_prefix (Data Source) + +Retrieves information about a Prefix in Nautobot by its associated VLAN ID. + + + + +## Schema + +### Required + +- `vlan_id` (String) The UUID of the VLAN to retrieve the prefix for. + +### Read-Only + +- `created` (String) The creation date of the prefix. +- `description` (String) Description of the prefix. +- `id` (String) The UUID of the prefix. +- `last_updated` (String) The last update date of the prefix. +- `namespace_id` (String) The ID of the namespace associated with the prefix. +- `prefix` (String) The prefix. +- `rir_id` (String) The ID of the RIR associated with the prefix. +- `role_id` (String) The ID of the role associated with the prefix. +- `status` (String) The status of the prefix. +- `tenant_id` (String) The ID of the tenant associated with the prefix. + + diff --git a/docs/data-sources/prefixes.md b/docs/data-sources/prefixes.md new file mode 100644 index 0000000..e096489 --- /dev/null +++ b/docs/data-sources/prefixes.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_prefixes Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about all prefixes in Nautobot. +--- + +# nautobot_prefixes (Data Source) + +Retrieves information about all prefixes in Nautobot. + + + + +## Schema + +### Read-Only + +- `id` (String) The ID of this resource. +- `prefixes` (List of Object) (see [below for nested schema](#nestedatt--prefixes)) + + +### Nested Schema for `prefixes` + +Read-Only: + +- `created` (String) +- `description` (String) +- `id` (String) +- `last_updated` (String) +- `namespace_id` (String) +- `prefix` (String) +- `rir_id` (String) +- `role_id` (String) +- `status` (String) +- `tenant_id` (String) + + diff --git a/docs/data-sources/virtual_machine.md b/docs/data-sources/virtual_machine.md new file mode 100644 index 0000000..3ffce5c --- /dev/null +++ b/docs/data-sources/virtual_machine.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_virtual_machine Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about a specific virtual machine in Nautobot. +--- + +# nautobot_virtual_machine (Data Source) + +Retrieves information about a specific virtual machine in Nautobot. + + + + +## Schema + +### Required + +- `name` (String) The name of the virtual machine to retrieve. + +### Read-Only + +- `cluster_id` (String) The ID of the cluster associated with the virtual machine. +- `comments` (String) Comments or notes about the virtual machine. +- `created` (String) The creation date of the virtual machine. +- `disk` (Number) The disk size in GB. +- `id` (String) The UUID of the virtual machine. +- `last_updated` (String) The last update date of the virtual machine. +- `memory` (Number) The amount of memory in MB. +- `platform_id` (String) The ID of the platform associated with the virtual machine. +- `primary_ip4_id` (String) The ID of the primary IPv4 address. +- `primary_ip6_id` (String) The ID of the primary IPv6 address. +- `role_id` (String) The ID of the role associated with the virtual machine. +- `status` (String) The name of the status of the virtual machine. +- `tags_ids` (List of String) The IDs of the tags associated with the virtual machine. +- `tenant_id` (String) The ID of the tenant associated with the virtual machine. +- `vcpus` (Number) The number of virtual CPUs. + + diff --git a/docs/data-sources/virtual_machines.md b/docs/data-sources/virtual_machines.md new file mode 100644 index 0000000..d4e4efb --- /dev/null +++ b/docs/data-sources/virtual_machines.md @@ -0,0 +1,45 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_virtual_machines Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about virtual machines in Nautobot. +--- + +# nautobot_virtual_machines (Data Source) + +Retrieves information about virtual machines in Nautobot. + + + + +## Schema + +### Read-Only + +- `id` (String) The ID of this resource. +- `virtual_machines` (List of Object) (see [below for nested schema](#nestedatt--virtual_machines)) + + +### Nested Schema for `virtual_machines` + +Read-Only: + +- `cluster_id` (String) +- `comments` (String) +- `created` (String) +- `disk` (Number) +- `id` (String) +- `last_updated` (String) +- `memory` (Number) +- `name` (String) +- `platform_id` (String) +- `primary_ip4_id` (String) +- `primary_ip6_id` (String) +- `role_id` (String) +- `status` (String) +- `tags_ids` (List of String) +- `tenant_id` (String) +- `vcpus` (Number) + + diff --git a/docs/data-sources/vlan.md b/docs/data-sources/vlan.md new file mode 100644 index 0000000..a60b8bc --- /dev/null +++ b/docs/data-sources/vlan.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_vlan Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about a specific VLAN in Nautobot. +--- + +# nautobot_vlan (Data Source) + +Retrieves information about a specific VLAN in Nautobot. + + + + +## Schema + +### Required + +- `name` (String) The name of the VLAN to retrieve. + +### Read-Only + +- `created` (String) The creation date of the VLAN. +- `description` (String) Description of the VLAN. +- `id` (String) The UUID of the VLAN. +- `last_updated` (String) The last update date of the VLAN. +- `locations` (List of String) The IDs of the locations associated with the VLAN. +- `role_id` (String) The ID of the role associated with the VLAN. +- `status` (String) The status of the VLAN. +- `tags_ids` (List of String) The IDs of the tags associated with the VLAN. +- `tenant_id` (String) The ID of the tenant associated with the VLAN. +- `vid` (Number) The ID (VID) of the VLAN. +- `vlan_group_id` (String) The ID of the VLAN group. + + diff --git a/docs/data-sources/vlans.md b/docs/data-sources/vlans.md new file mode 100644 index 0000000..f372336 --- /dev/null +++ b/docs/data-sources/vlans.md @@ -0,0 +1,41 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_vlans Data Source - terraform-provider-nautobot" +subcategory: "" +description: |- + Retrieves information about all VLANs in Nautobot. +--- + +# nautobot_vlans (Data Source) + +Retrieves information about all VLANs in Nautobot. + + + + +## Schema + +### Read-Only + +- `id` (String) The ID of this resource. +- `vlans` (List of Object) (see [below for nested schema](#nestedatt--vlans)) + + +### Nested Schema for `vlans` + +Read-Only: + +- `created` (String) +- `description` (String) +- `id` (String) +- `last_updated` (String) +- `locations` (List of String) +- `name` (String) +- `role_id` (String) +- `status` (String) +- `tags_ids` (List of String) +- `tenant_id` (String) +- `vid` (Number) +- `vlan_group_id` (String) + + diff --git a/docs/resources/available_ip_address.md b/docs/resources/available_ip_address.md new file mode 100644 index 0000000..ea72f07 --- /dev/null +++ b/docs/resources/available_ip_address.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_available_ip_address Resource - terraform-provider-nautobot" +subcategory: "" +description: |- + This object allocates and manages an available IP address in Nautobot +--- + +# nautobot_available_ip_address (Resource) + +This object allocates and manages an available IP address in Nautobot + + + + +## Schema + +### Required + +- `prefix_id` (String) ID of the prefix to allocate the IP address from. +- `status` (String) Status of the allocated IP address. + +### Optional + +- `dns_name` (String) DNS name associated with the IP address. + +### Read-Only + +- `address` (String) Allocated IP address. +- `id` (String) The ID of this resource. +- `ip_version` (Number) IP version of the allocated IP address (4 or 6). + + diff --git a/docs/resources/cluster.md b/docs/resources/cluster.md new file mode 100644 index 0000000..e4827c4 --- /dev/null +++ b/docs/resources/cluster.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_cluster Resource - terraform-provider-nautobot" +subcategory: "" +description: |- + This object manages a cluster in Nautobot +--- + +# nautobot_cluster (Resource) + +This object manages a cluster in Nautobot + + + + +## Schema + +### Required + +- `cluster_type_id` (String) ID of the Cluster's type. This can be sourced from the cluster_type resource or data source. +- `name` (String) Cluster's name. + +### Optional + +- `cluster_group_id` (String) ID of the Cluster's group. +- `comments` (String) Comments or notes about the cluster. +- `location_id` (String) ID of the Location of the cluster. +- `tags_ids` (List of String) IDs of the Tags associated with the cluster. +- `tenant_id` (String) ID of the Tenant associated with the cluster. + +### Read-Only + +- `created` (String) Creation date of the cluster. +- `id` (String) The ID of this resource. +- `last_updated` (String) Last update date of the cluster. + + diff --git a/docs/resources/cluster_type.md b/docs/resources/cluster_type.md new file mode 100644 index 0000000..2e8df33 --- /dev/null +++ b/docs/resources/cluster_type.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_cluster_type Resource - terraform-provider-nautobot" +subcategory: "" +description: |- + This object manages a cluster type in Nautobot. +--- + +# nautobot_cluster_type (Resource) + +This object manages a cluster type in Nautobot. + + + + +## Schema + +### Required + +- `name` (String) Cluster type's name. + +### Optional + +- `description` (String) Description for the cluster type. + +### Read-Only + +- `created` (String) Creation date of the cluster type. +- `display` (String) Human-friendly display value for the cluster type. +- `id` (String) Cluster type's UUID. +- `last_updated` (String) Last update date of the cluster type. +- `natural_slug` (String) Natural slug for the cluster type. +- `notes_url` (String) Notes URL for the cluster type. +- `object_type` (String) Object type of the cluster type. +- `url` (String) URL of the cluster type. + + diff --git a/docs/resources/manufacturer.md b/docs/resources/manufacturer.md index a5db2fd..dffa9fd 100644 --- a/docs/resources/manufacturer.md +++ b/docs/resources/manufacturer.md @@ -21,20 +21,17 @@ This object manages a manufacturer in Nautobot ### Optional -- `custom_fields` (Map of String) Manufacturer custom fields. - `description` (String) Manufacturer's description. -- `display` (String) Manufacturer's display name. -- `notes_url` (String) Notes for manufacturer. -- `slug` (String) Manufacturer's slug. -- `url` (String) Manufacturer's URL. ### Read-Only - `created` (String) Manufacturer's creation date. -- `devicetype_count` (Number) Manufacturer's device count. +- `display` (String) Manufacturer's display name. - `id` (String) Manufacturer's UUID. -- `inventoryitem_count` (Number) Manufacturer's inventory item count. -- `last_updated` (String) Manufacturer's last update. -- `platform_count` (Number) Manufacturer's platform count. +- `last_updated` (String) Manufacturer's last update date. +- `natural_slug` (String) Natural slug for the manufacturer. +- `notes_url` (String) Notes URL for the manufacturer. +- `object_type` (String) Object type of the manufacturer. +- `url` (String) Manufacturer's URL. diff --git a/docs/resources/virtual_machine.md b/docs/resources/virtual_machine.md new file mode 100644 index 0000000..2ef7ee2 --- /dev/null +++ b/docs/resources/virtual_machine.md @@ -0,0 +1,52 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_virtual_machine Resource - terraform-provider-nautobot" +subcategory: "" +description: |- + This object manages a virtual machine in Nautobot +--- + +# nautobot_virtual_machine (Resource) + +This object manages a virtual machine in Nautobot + + + + +## Schema + +### Required + +- `cluster_id` (String) Cluster where the virtual machine belongs. +- `name` (String) Virtual Machine's name. +- `status` (String) Status of the virtual machine. + +### Optional + +- `comments` (String) Comments or notes about the virtual machine. +- `disk` (Number) Disk size in GB. +- `memory` (Number) Amount of memory in MB. +- `platform_id` (String) Platform or OS installed on the virtual machine. +- `primary_ip4_id` (String) Primary IPv4 address. +- `primary_ip6_id` (String) Primary IPv6 address. +- `role_id` (String) Role of the virtual machine. +- `software_image_files` (Block List) Software image files associated with the software version. (see [below for nested schema](#nestedblock--software_image_files)) +- `software_version_id` (String) Software version installed on the virtual machine. +- `tags_ids` (List of String) Tags associated with the virtual machine. +- `tenant_id` (String) Tenant associated with the virtual machine. +- `vcpus` (Number) Number of virtual CPUs. + +### Read-Only + +- `created` (String) Creation date of the virtual machine. +- `id` (String) The ID of this resource. +- `last_updated` (String) Last update date of the virtual machine. + + +### Nested Schema for `software_image_files` + +Read-Only: + +- `id` (String) The ID of this resource. + + diff --git a/docs/resources/vm_interface.md b/docs/resources/vm_interface.md new file mode 100644 index 0000000..3c8cef0 --- /dev/null +++ b/docs/resources/vm_interface.md @@ -0,0 +1,41 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_vm_interface Resource - terraform-provider-nautobot" +subcategory: "" +description: |- + This object manages a VM Interface in Nautobot +--- + +# nautobot_vm_interface (Resource) + +This object manages a VM Interface in Nautobot + + + + +## Schema + +### Required + +- `name` (String) Name of the VM interface. +- `status` (String) Status of the VM interface. +- `virtual_machine_id` (String) ID of the virtual machine to which the interface belongs. + +### Optional + +- `description` (String) Description of the interface. +- `enabled` (Boolean) Whether the interface is enabled. +- `ip_addresses` (List of String) List of IP addresses to assign to the VM interface. +- `mac_address` (String) MAC address of the interface. +- `mode` (String) Mode of the interface. +- `mtu` (Number) MTU size of the interface. +- `tags_ids` (List of String) Tags associated with the interface. +- `untagged_vlan_id` (String) Untagged VLAN ID associated with the interface. + +### Read-Only + +- `created` (String) Creation date of the interface. +- `id` (String) The ID of this resource. +- `last_updated` (String) Last updated date of the interface. + + diff --git a/docs/resources/vm_primary_ip.md b/docs/resources/vm_primary_ip.md new file mode 100644 index 0000000..a095212 --- /dev/null +++ b/docs/resources/vm_primary_ip.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nautobot_vm_primary_ip Resource - terraform-provider-nautobot" +subcategory: "" +description: |- + This resource sets an IP address as the primary IPv4 or IPv6 for a virtual machine in Nautobot +--- + +# nautobot_vm_primary_ip (Resource) + +This resource sets an IP address as the primary IPv4 or IPv6 for a virtual machine in Nautobot + + + + +## Schema + +### Required + +- `virtual_machine_id` (String) ID of the virtual machine. + +### Optional + +- `primary_ip4_id` (String) ID of the primary IPv4 address. +- `primary_ip6_id` (String) ID of the primary IPv6 address. + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/go.mod b/go.mod index 221dc8f..32f275c 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,12 @@ module github.com/nautobot/terraform-provider-nautobot -go 1.21 - -toolchain go1.21.13 +go 1.23 require ( - github.com/deepmap/oapi-codegen v1.12.4 - github.com/google/uuid v1.3.0 github.com/hashicorp/terraform-plugin-docs v0.13.0 github.com/hashicorp/terraform-plugin-log v0.8.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 - github.com/nautobot/go-nautobot v1.5.8-beta + github.com/nautobot/go-nautobot/v2 v2.3.2-beta github.com/tidwall/gjson v1.14.4 ) @@ -19,13 +15,13 @@ require ( github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/agext/levenshtein v1.2.3 // indirect - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/fatih/color v1.15.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -47,7 +43,7 @@ require ( github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/cli v1.1.5 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -65,13 +61,14 @@ require ( github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/zclconf/go-cty v1.13.1 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230323212658-478b75c54725 // indirect google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/validator.v2 v2.0.1 // indirect ) diff --git a/go.sum b/go.sum index 21b772c..5c4af6e 100644 --- a/go.sum +++ b/go.sum @@ -10,14 +10,11 @@ github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= @@ -27,13 +24,10 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= -github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -67,8 +61,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -127,7 +121,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -151,8 +144,8 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -169,8 +162,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/nautobot/go-nautobot v1.5.8-beta h1:bBLMfg1GA4ms2tRTFLcWz5Lhz+r1KTCGL+ohBPy6u6c= -github.com/nautobot/go-nautobot v1.5.8-beta/go.mod h1:XeWVogQH4iHksB8xkL6x2r3FCAZglKtqRZatt3yJPGk= +github.com/nautobot/go-nautobot/v2 v2.3.2-beta h1:w3sDcyt5CDSSWkKWUow08loxo6o72WjoDyclwkllotQ= +github.com/nautobot/go-nautobot/v2 v2.3.2-beta/go.mod h1:T0emSo3w4TabGb4Wwb1o3NOEgbaVL1MLMDI9rFWaxtg= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= @@ -195,18 +188,16 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -235,12 +226,12 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -249,8 +240,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -274,8 +265,9 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= @@ -284,8 +276,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -301,14 +293,16 @@ google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY= +gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/provider/data_source_available_ip_address.go b/internal/provider/data_source_available_ip_address.go new file mode 100644 index 0000000..1a8f821 --- /dev/null +++ b/internal/provider/data_source_available_ip_address.go @@ -0,0 +1,78 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceAvailableIP() *schema.Resource { + return &schema.Resource{ + Description: "This data source retrieves an available IP address from a given prefix in Nautobot", + + ReadContext: dataSourceAvailableIPRead, + + Schema: map[string]*schema.Schema{ + "prefix_id": { + Description: "The ID of the prefix from which to retrieve an available IP.", + Type: schema.TypeString, + Required: true, + }, + "ip_version": { + Description: "The version of the IP address (4 or 6).", + Type: schema.TypeInt, + Computed: true, + }, + "address": { + Description: "The available IP address.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAvailableIPRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + prefixID := d.Get("prefix_id").(string) + + // Fetch the available IPs from the given prefix + availableIPs, _, err := c.IpamAPI.IpamPrefixesAvailableIpsList(auth, prefixID).Execute() + if err != nil { + return diag.Errorf("failed to retrieve available IPs from prefix %s: %s", prefixID, err.Error()) + } + + // Check if there are available IPs + if len(availableIPs) == 0 { + return diag.Errorf("no available IP addresses found for prefix %s", prefixID) + } + + // Use the first available IP from the list + availableIP := availableIPs[0] + + // Set values in Terraform state + d.Set("ip_version", availableIP.IpVersion) + d.Set("address", availableIP.Address) + + // Set resource ID to the available IP address + d.SetId(fmt.Sprintf("%s-%d", availableIP.Address, availableIP.IpVersion)) + + return nil +} diff --git a/internal/provider/data_source_cluster.go b/internal/provider/data_source_cluster.go new file mode 100644 index 0000000..ecde8d8 --- /dev/null +++ b/internal/provider/data_source_cluster.go @@ -0,0 +1,166 @@ +package provider + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceCluster() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about a specific cluster in Nautobot.", + + ReadContext: dataSourceClusterRead, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "The name of the cluster.", + Type: schema.TypeString, + Required: true, + }, + "id": { + Description: "The UUID of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_type_id": { + Description: "The ID of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_group_id": { + Description: "The ID of the cluster group.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "location_id": { + Description: "The ID of the location associated with the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "tags_ids": { + Description: "The IDs of the tags associated with the cluster.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "comments": { + Description: "Comments or notes about the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "created": { + Description: "The creation date of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch the cluster name from Terraform configuration + clusterName := d.Get("name").(string) + + // Fetch clusters by name + rsp, _, err := c.VirtualizationAPI.VirtualizationClustersList(auth).Name([]string{clusterName}).Execute() + if err != nil { + return diag.Errorf("failed to get cluster with name %s: %s", clusterName, err.Error()) + } + + // Ensure at least one result is returned + if len(rsp.Results) == 0 { + return diag.Errorf("no cluster found with name %s", clusterName) + } + + cluster := rsp.Results[0] + + d.SetId(cluster.Id) + + // Set basic fields + d.Set("id", cluster.Id) + d.Set("name", cluster.Name) + d.Set("comments", cluster.Comments) + + // Convert created and last updated fields to strings + createdStr := "" + if cluster.Created.IsSet() && cluster.Created.Get() != nil { + createdStr = cluster.Created.Get().Format(time.RFC3339) + } + d.Set("created", createdStr) + + lastUpdatedStr := "" + if cluster.LastUpdated.IsSet() && cluster.LastUpdated.Get() != nil { + lastUpdatedStr = cluster.LastUpdated.Get().Format(time.RFC3339) + } + d.Set("last_updated", lastUpdatedStr) + + // Handle cluster_type_id + if cluster.ClusterType.Id != nil && cluster.ClusterType.Id.String != nil { + d.Set("cluster_type_id", *cluster.ClusterType.Id.String) + } + + // Handle cluster_group_id + if cluster.ClusterGroup.IsSet() { + if clusterGroup := cluster.ClusterGroup.Get(); clusterGroup != nil && clusterGroup.Id != nil { + d.Set("cluster_group_id", *clusterGroup.Id.String) + } + } + + // Handle tenant_id + if cluster.Tenant.IsSet() { + if tenant := cluster.Tenant.Get(); tenant != nil && tenant.Id != nil { + d.Set("tenant_id", *tenant.Id.String) + } + } + + // Handle location_id + if cluster.Location.IsSet() { + if location := cluster.Location.Get(); location != nil && location.Id != nil { + d.Set("location_id", *location.Id.String) + } + } + + // Handle tags + var tags []string + for _, tag := range cluster.Tags { + if tag.Id != nil && tag.Id.String != nil { + tags = append(tags, *tag.Id.String) + } + } + d.Set("tags_ids", tags) + + return diags +} diff --git a/internal/provider/data_source_cluster_type.go b/internal/provider/data_source_cluster_type.go new file mode 100644 index 0000000..241bc25 --- /dev/null +++ b/internal/provider/data_source_cluster_type.go @@ -0,0 +1,131 @@ +package provider + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceClusterType() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about a specific cluster type in Nautobot.", + + ReadContext: dataSourceClusterTypeRead, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "The name of the cluster type to retrieve.", + Type: schema.TypeString, + Required: true, + }, + "id": { + Description: "The UUID of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "object_type": { + Description: "Object type of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "display": { + Description: "Human-friendly display value for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "url": { + Description: "URL of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "natural_slug": { + Description: "Natural slug for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "The description of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "created": { + Description: "The date the cluster type was created.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The date the cluster type was last updated.", + Type: schema.TypeString, + Computed: true, + }, + "notes_url": { + Description: "Notes URL for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceClusterTypeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Get the cluster type name from the Terraform configuration + clusterTypeName := d.Get("name").(string) + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch cluster types by name + rsp, _, err := c.VirtualizationAPI.VirtualizationClusterTypesList(auth).Name([]string{clusterTypeName}).Execute() + if err != nil { + return diag.Errorf("failed to get cluster types with name %s: %s", clusterTypeName, err.Error()) + } + + if len(rsp.Results) == 0 { + return diag.Errorf("no cluster type found with name %s", clusterTypeName) + } + + clusterType := rsp.Results[0] + + d.SetId(clusterType.Id) + + createdStr := "" + if clusterType.Created.IsSet() && clusterType.Created.Get() != nil { + createdStr = clusterType.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if clusterType.LastUpdated.IsSet() && clusterType.LastUpdated.Get() != nil { + lastUpdatedStr = clusterType.LastUpdated.Get().Format(time.RFC3339) + } + + // Set the fields directly in the resource data + d.Set("id", clusterType.Id) + d.Set("object_type", clusterType.ObjectType) + d.Set("display", clusterType.Display) + d.Set("url", clusterType.Url) + d.Set("natural_slug", clusterType.NaturalSlug) + d.Set("name", clusterType.Name) + d.Set("description", clusterType.Description) + d.Set("created", createdStr) + d.Set("last_updated", lastUpdatedStr) + d.Set("notes_url", clusterType.NotesUrl) + + return diags +} diff --git a/internal/provider/data_source_cluster_types.go b/internal/provider/data_source_cluster_types.go new file mode 100644 index 0000000..54eb4a7 --- /dev/null +++ b/internal/provider/data_source_cluster_types.go @@ -0,0 +1,144 @@ +package provider + +import ( + "context" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceClusterTypes() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about cluster types in Nautobot.", + + ReadContext: dataSourceClusterTypesRead, + + Schema: map[string]*schema.Schema{ + "cluster_types": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The UUID of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "object_type": { + Description: "Object type of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "display": { + Description: "Human-friendly display value for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "url": { + Description: "URL of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "natural_slug": { + Description: "Natural slug for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "The name of the cluster type.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "The description of the cluster type.", + Type: schema.TypeString, + Optional: true, + }, + "created": { + Description: "The date the cluster type was created.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The date the cluster type was last updated.", + Type: schema.TypeString, + Computed: true, + }, + "notes_url": { + Description: "Notes URL for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceClusterTypesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + s := meta.(*apiClient).Server + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + rsp, _, err := c.VirtualizationAPI.VirtualizationClusterTypesList(auth).Execute() + if err != nil { + return diag.Errorf("failed to get cluster types list from %s: %s", s, err.Error()) + } + + results := rsp.Results + list := make([]map[string]interface{}, 0) + + // Iterate over the results and map each cluster type to the format expected by Terraform + for _, clusterType := range results { + createdStr := "" + if clusterType.Created.IsSet() && clusterType.Created.Get() != nil { + createdStr = clusterType.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if clusterType.LastUpdated.IsSet() && clusterType.LastUpdated.Get() != nil { + lastUpdatedStr = clusterType.LastUpdated.Get().Format(time.RFC3339) + } + + itemMap := map[string]interface{}{ + "id": clusterType.Id, + "object_type": clusterType.ObjectType, + "display": clusterType.Display, + "url": clusterType.Url, + "natural_slug": clusterType.NaturalSlug, + "name": clusterType.Name, + "description": clusterType.Description, + "created": createdStr, + "last_updated": lastUpdatedStr, + "notes_url": clusterType.NotesUrl, + } + list = append(list, itemMap) + } + + if err := d.Set("cluster_types", list); err != nil { + return diag.FromErr(err) + } + + // Always run + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/internal/provider/data_source_clusters.go b/internal/provider/data_source_clusters.go new file mode 100644 index 0000000..7260800 --- /dev/null +++ b/internal/provider/data_source_clusters.go @@ -0,0 +1,182 @@ +package provider + +import ( + "context" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceClusters() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about clusters in Nautobot.", + + ReadContext: dataSourceClustersRead, + + Schema: map[string]*schema.Schema{ + "clusters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The UUID of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "The name of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_type_id": { + Description: "The ID of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_group_id": { + Description: "The ID of the cluster group.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "location_id": { + Description: "The ID of the location associated with the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "tags_ids": { + Description: "The IDs of the tags associated with the cluster.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "comments": { + Description: "Comments or notes about the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "created": { + Description: "The creation date of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceClustersRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + s := meta.(*apiClient).Server + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch clusters list + rsp, _, err := c.VirtualizationAPI.VirtualizationClustersList(auth).Execute() + if err != nil { + return diag.Errorf("failed to get clusters list from %s: %s", s, err.Error()) + } + + results := rsp.Results + list := make([]map[string]interface{}, 0) + + // Iterate over the results and map each cluster to the format expected by Terraform + for _, cluster := range results { + createdStr := "" + if cluster.Created.IsSet() && cluster.Created.Get() != nil { + createdStr = cluster.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if cluster.LastUpdated.IsSet() && cluster.LastUpdated.Get() != nil { + lastUpdatedStr = cluster.LastUpdated.Get().Format(time.RFC3339) + } + + // Prepare itemMap with mandatory fields + itemMap := map[string]interface{}{ + "id": cluster.Id, + "name": cluster.Name, + "comments": cluster.Comments, + "created": createdStr, + "last_updated": lastUpdatedStr, + } + + // Extract cluster_type_id safely + if cluster.ClusterType.Id != nil && cluster.ClusterType.Id.String != nil { + itemMap["cluster_type_id"] = *cluster.ClusterType.Id.String + } + + // Handle nullable ClusterGroup + if cluster.ClusterGroup.IsSet() { + if clusterGroup := cluster.ClusterGroup.Get(); clusterGroup != nil && clusterGroup.Id != nil { + itemMap["cluster_group_id"] = *clusterGroup.Id.String + } + } + + // Handle nullable Tenant + if cluster.Tenant.IsSet() { + if tenant := cluster.Tenant.Get(); tenant != nil && tenant.Id != nil { + itemMap["tenant_id"] = *tenant.Id.String + } + } + + // Handle nullable Location + if cluster.Location.IsSet() { + if location := cluster.Location.Get(); location != nil && location.Id != nil { + itemMap["location_id"] = *location.Id.String + } + } + + // Handle Tags + var tags []string + for _, tag := range cluster.Tags { + if tag.Id != nil && tag.Id.String != nil { + tags = append(tags, *tag.Id.String) + } + } + itemMap["tags_ids"] = tags + + // Add the cluster to the list + list = append(list, itemMap) + } + + // Set the clusters list in the resource data + if err := d.Set("clusters", list); err != nil { + return diag.FromErr(err) + } + + // Always run + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/internal/provider/data_source_graphql.go b/internal/provider/data_source_graphql.go index b65d068..0e69a18 100644 --- a/internal/provider/data_source_graphql.go +++ b/internal/provider/data_source_graphql.go @@ -44,8 +44,8 @@ type reqBody struct { func dataSourceGraphQLRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - c := meta.(*apiClient).BaseClient - s := fmt.Sprintf("%sgraphql/", meta.(*apiClient).Server) + c := meta.(*apiClient).Client + s := fmt.Sprintf("%s/graphql/", meta.(*apiClient).Server) t := meta.(*apiClient).Token query := d.Get("query").(string) @@ -58,7 +58,7 @@ func dataSourceGraphQLRead(ctx context.Context, d *schema.ResourceData, meta int // Add the authorization header to our request. t.Intercept(ctx, req) - rsp, err := c.Client.Do(req) + rsp, err := c.GetConfig().HTTPClient.Do(req) if err != nil { return diag.Errorf("failed to successfully call %s: %s", s, err.Error()) } diff --git a/internal/provider/data_source_manufacturer.go b/internal/provider/data_source_manufacturer.go new file mode 100644 index 0000000..cd68eaa --- /dev/null +++ b/internal/provider/data_source_manufacturer.go @@ -0,0 +1,131 @@ +package provider + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceManufacturer() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about a specific manufacturer in Nautobot.", + + ReadContext: dataSourceManufacturerRead, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "The name of the manufacturer to retrieve.", + Type: schema.TypeString, + Required: true, + }, + "id": { + Description: "Manufacturer's UUID.", + Type: schema.TypeString, + Computed: true, + }, + "object_type": { + Description: "Object type of the manufacturer.", + Type: schema.TypeString, + Computed: true, + }, + "display": { + Description: "Human friendly display value for the manufacturer.", + Type: schema.TypeString, + Computed: true, + }, + "url": { + Description: "URL of the manufacturer.", + Type: schema.TypeString, + Computed: true, + }, + "natural_slug": { + Description: "Natural slug for the manufacturer.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Manufacturer's description.", + Type: schema.TypeString, + Computed: true, + }, + "created": { + Description: "Manufacturer's creation date.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "Manufacturer's last update.", + Type: schema.TypeString, + Computed: true, + }, + "notes_url": { + Description: "Notes URL for the manufacturer.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceManufacturerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Get the manufacturer name from the Terraform configuration + manufacturerName := d.Get("name").(string) + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch manufacturer by name + rsp, _, err := c.DcimAPI.DcimManufacturersList(auth).Name([]string{manufacturerName}).Execute() + if err != nil { + return diag.Errorf("failed to get manufacturer with name %s: %s", manufacturerName, err.Error()) + } + + if len(rsp.Results) == 0 { + return diag.Errorf("no manufacturer found with name %s", manufacturerName) + } + + manufacturer := rsp.Results[0] + + d.SetId(manufacturer.Id) + + createdStr := "" + if manufacturer.Created.IsSet() && manufacturer.Created.Get() != nil { + createdStr = manufacturer.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if manufacturer.LastUpdated.IsSet() && manufacturer.LastUpdated.Get() != nil { + lastUpdatedStr = manufacturer.LastUpdated.Get().Format(time.RFC3339) + } + + // Set the fields directly in the resource data + d.Set("id", manufacturer.Id) + d.Set("object_type", manufacturer.ObjectType) + d.Set("display", manufacturer.Display) + d.Set("url", manufacturer.Url) + d.Set("natural_slug", manufacturer.NaturalSlug) + d.Set("name", manufacturer.Name) + d.Set("description", manufacturer.Description) + d.Set("created", createdStr) + d.Set("last_updated", lastUpdatedStr) + d.Set("notes_url", manufacturer.NotesUrl) + + return diags +} diff --git a/internal/provider/data_source_manufacturers.go b/internal/provider/data_source_manufacturers.go index cd0e9a1..42e2799 100644 --- a/internal/provider/data_source_manufacturers.go +++ b/internal/provider/data_source_manufacturers.go @@ -2,16 +2,12 @@ package provider import ( "context" - "encoding/json" "strconv" - "strings" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/tidwall/gjson" - - nb "github.com/nautobot/go-nautobot/pkg/nautobot" + nb "github.com/nautobot/go-nautobot/v2" ) func dataSourceManufacturers() *schema.Resource { @@ -26,44 +22,28 @@ func dataSourceManufacturers() *schema.Resource { Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "created": { - Description: "Manufacturer's creation date.", + "id": { + Description: "Manufacturer's UUID.", Type: schema.TypeString, Computed: true, }, - "description": { - Description: "Manufacturer's description.", + "object_type": { + Description: "Object type of the Manufacturer.", Type: schema.TypeString, - Optional: true, - }, - "custom_fields": { - Description: "Manufacturer custom fields.", - Type: schema.TypeMap, - Optional: true, - }, - "devicetype_count": { - Description: "Manufacturer's device count.", - Type: schema.TypeInt, Computed: true, }, "display": { - Description: "Manufacturer's display name.", + Description: "Human friendly display value for the Manufacturer.", Type: schema.TypeString, - Optional: true, Computed: true, }, - "id": { - Description: "Manufacturer's UUID.", + "url": { + Description: "URL of the Manufacturer.", Type: schema.TypeString, Computed: true, }, - "inventoryitem_count": { - Description: "Manufacturer's inventory item count.", - Type: schema.TypeInt, - Computed: true, - }, - "last_updated": { - Description: "Manufacturer's last update.", + "natural_slug": { + Description: "Natural slug for the Manufacturer.", Type: schema.TypeString, Computed: true, }, @@ -72,25 +52,23 @@ func dataSourceManufacturers() *schema.Resource { Type: schema.TypeString, Required: true, }, - "notes_url": { - Description: "Notes for manufacturer.", + "description": { + Description: "Manufacturer's description.", Type: schema.TypeString, Optional: true, - Computed: true, }, - "platform_count": { - Description: "Manufacturer's platform count.", - Type: schema.TypeInt, + "created": { + Description: "Manufacturer's creation date.", + Type: schema.TypeString, Computed: true, }, - "slug": { - Description: "Manufacturer's slug.", + "last_updated": { + Description: "Manufacturer's last update.", Type: schema.TypeString, - Optional: true, Computed: true, }, - "url": { - Description: "Manufacturer's URL.", + "notes_url": { + Description: "Notes URL for the Manufacturer.", Type: schema.TypeString, Optional: true, Computed: true, @@ -108,23 +86,51 @@ func dataSourceManufacturersRead(ctx context.Context, d *schema.ResourceData, me c := meta.(*apiClient).Client s := meta.(*apiClient).Server - - rsp, err := c.DcimManufacturersListWithResponse( + t := meta.(*apiClient).Token.token + auth := context.WithValue( ctx, - &nb.DcimManufacturersListParams{}) + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + rsp, _, err := c.DcimAPI.DcimManufacturersList(auth).Execute() if err != nil { return diag.Errorf("failed to get manufacturers list from %s: %s", s, err.Error()) } - results := gjson.Get(string(rsp.Body), "results") - resultsReader := strings.NewReader(results.String()) + results := rsp.Results list := make([]map[string]interface{}, 0) - err = json.NewDecoder(resultsReader).Decode(&list) - if err != nil { - return diag.Errorf("failed to decode manufacturers list from %s: %s", s, err.Error()) + // Iterate over the results and map each manufacturer to the format expected by Terraform + for _, manufacturer := range results { + createdStr := "" + if manufacturer.Created.IsSet() && manufacturer.Created.Get() != nil { + createdStr = manufacturer.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if manufacturer.LastUpdated.IsSet() && manufacturer.LastUpdated.Get() != nil { + lastUpdatedStr = manufacturer.LastUpdated.Get().Format(time.RFC3339) + } + itemMap := map[string]interface{}{ + "id": manufacturer.Id, + "object_type": manufacturer.ObjectType, + "display": manufacturer.Display, + "url": manufacturer.Url, + "natural_slug": manufacturer.NaturalSlug, + "name": manufacturer.Name, + "description": manufacturer.Description, + "created": createdStr, + "last_updated": lastUpdatedStr, + "notes_url": manufacturer.NotesUrl, + } + list = append(list, itemMap) } if err := d.Set("manufacturers", list); err != nil { diff --git a/internal/provider/data_source_prefix.go b/internal/provider/data_source_prefix.go new file mode 100644 index 0000000..1470651 --- /dev/null +++ b/internal/provider/data_source_prefix.go @@ -0,0 +1,170 @@ +package provider + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourcePrefix() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about a Prefix in Nautobot by its associated VLAN ID.", + + ReadContext: dataSourcePrefixRead, + + Schema: map[string]*schema.Schema{ + "vlan_id": { + Description: "The UUID of the VLAN to retrieve the prefix for.", + Type: schema.TypeString, + Required: true, + }, + "id": { + Description: "The UUID of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "prefix": { + Description: "The prefix.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Description of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "status": { + Description: "The status of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "role_id": { + Description: "The ID of the role associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "rir_id": { + Description: "The ID of the RIR associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "namespace_id": { + Description: "The ID of the namespace associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "created": { + Description: "The creation date of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourcePrefixRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Get the VLAN ID from the Terraform configuration + vlanID := d.Get("vlan_id").(string) + + // Prepare the VLAN ID as a []*string slice + vlanIDList := []*string{&vlanID} + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch prefixes by VLAN ID + rsp, _, err := c.IpamAPI.IpamPrefixesList(auth).VlanId(vlanIDList).Execute() + if err != nil { + return diag.Errorf("failed to get prefix for VLAN ID %s: %s", vlanID, err.Error()) + } + + if len(rsp.Results) == 0 { + return diag.Errorf("no prefix found for VLAN ID %s", vlanID) + } + + prefix := rsp.Results[0] + + d.SetId(prefix.Id) + + createdStr := "" + if prefix.Created.IsSet() && prefix.Created.Get() != nil { + createdStr = prefix.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if prefix.LastUpdated.IsSet() && prefix.LastUpdated.Get() != nil { + lastUpdatedStr = prefix.LastUpdated.Get().Format(time.RFC3339) + } + + // Set the fields directly in the resource data + d.Set("id", prefix.Id) + d.Set("prefix", prefix.Prefix) + d.Set("description", prefix.Description) + d.Set("created", createdStr) + d.Set("last_updated", lastUpdatedStr) + + // Handle nullable status + if prefix.Status.Id != nil && prefix.Status.Id.String != nil { + statusID := *prefix.Status.Id.String + statusName, err := getStatusName(ctx, c, t, statusID) + if err != nil { + return diag.Errorf("failed to get status name for ID %s: %s", statusID, err.Error()) + } + d.Set("status", statusName) + } + + // Handle nullable Tenant + if prefix.Tenant.IsSet() { + if tenant := prefix.Tenant.Get(); tenant != nil && tenant.Id != nil && tenant.Id.String != nil { + d.Set("tenant_id", *tenant.Id.String) + } + } + + // Handle nullable Role + if prefix.Role.IsSet() { + if role := prefix.Role.Get(); role != nil && role.Id != nil && role.Id.String != nil { + d.Set("role_id", *role.Id.String) + } + } + + // Handle nullable RIR + if prefix.Rir.IsSet() { + if rir := prefix.Rir.Get(); rir != nil && rir.Id != nil && rir.Id.String != nil { + d.Set("rir_id", *rir.Id.String) + } + } + + // Handle nullable Namespace (without using IsSet) + if prefix.Namespace != nil && prefix.Namespace.Id != nil && prefix.Namespace.Id.String != nil { + d.Set("namespace_id", *prefix.Namespace.Id.String) + } + + return diags +} diff --git a/internal/provider/data_source_prefixes.go b/internal/provider/data_source_prefixes.go new file mode 100644 index 0000000..4b719f0 --- /dev/null +++ b/internal/provider/data_source_prefixes.go @@ -0,0 +1,169 @@ +package provider + +import ( + "context" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourcePrefixes() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about all prefixes in Nautobot.", + + ReadContext: dataSourcePrefixesRead, + + Schema: map[string]*schema.Schema{ + "prefixes": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The UUID of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "prefix": { + Description: "The prefix.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Description of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "status": { + Description: "The status of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "role_id": { + Description: "The ID of the role associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "rir_id": { + Description: "The ID of the RIR associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "namespace_id": { + Description: "The ID of the namespace associated with the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "created": { + Description: "The creation date of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the prefix.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourcePrefixesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + rsp, _, err := c.IpamAPI.IpamPrefixesList(auth).Execute() + if err != nil { + return diag.Errorf("failed to get prefixes: %s", err.Error()) + } + + results := rsp.Results + list := make([]map[string]interface{}, 0) + + for _, prefix := range results { + createdStr := "" + if prefix.Created.IsSet() && prefix.Created.Get() != nil { + createdStr = prefix.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if prefix.LastUpdated.IsSet() && prefix.LastUpdated.Get() != nil { + lastUpdatedStr = prefix.LastUpdated.Get().Format(time.RFC3339) + } + + itemMap := map[string]interface{}{ + "id": prefix.Id, + "prefix": prefix.Prefix, + "description": prefix.Description, + "created": createdStr, + "last_updated": lastUpdatedStr, + } + + if prefix.Status.Id != nil && prefix.Status.Id.String != nil { + statusID := *prefix.Status.Id.String + statusName, err := getStatusName(ctx, c, t, statusID) + if err != nil { + return diag.Errorf("failed to get status name for ID %s: %s", statusID, err.Error()) + } + itemMap["status"] = statusName + } + + if prefix.Tenant.IsSet() { + if tenant := prefix.Tenant.Get(); tenant != nil && tenant.Id != nil && tenant.Id.String != nil { + itemMap["tenant_id"] = *tenant.Id.String + } + } + + if prefix.Role.IsSet() { + if role := prefix.Role.Get(); role != nil && role.Id != nil && role.Id.String != nil { + itemMap["role_id"] = *role.Id.String + } + } + + if prefix.Rir.IsSet() { + if rir := prefix.Rir.Get(); rir != nil && rir.Id != nil && rir.Id.String != nil { + itemMap["rir_id"] = *rir.Id.String + } + } + + if prefix.Namespace != nil && prefix.Namespace.Id != nil && prefix.Namespace.Id.String != nil { + itemMap["namespace_id"] = *prefix.Namespace.Id.String + } + + list = append(list, itemMap) + } + + if err := d.Set("prefixes", list); err != nil { + return diag.FromErr(err) + } + + // Set ID for the data source + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/internal/provider/data_source_virtual_machine.go b/internal/provider/data_source_virtual_machine.go new file mode 100644 index 0000000..29692a8 --- /dev/null +++ b/internal/provider/data_source_virtual_machine.go @@ -0,0 +1,212 @@ +package provider + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceVirtualMachine() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about a specific virtual machine in Nautobot.", + + ReadContext: dataSourceVirtualMachineRead, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "The name of the virtual machine to retrieve.", + Type: schema.TypeString, + Required: true, + }, + "id": { + Description: "The UUID of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_id": { + Description: "The ID of the cluster associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "status": { + Description: "The name of the status of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "platform_id": { + Description: "The ID of the platform associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "role_id": { + Description: "The ID of the role associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "primary_ip4_id": { + Description: "The ID of the primary IPv4 address.", + Type: schema.TypeString, + Computed: true, + }, + "primary_ip6_id": { + Description: "The ID of the primary IPv6 address.", + Type: schema.TypeString, + Computed: true, + }, + "vcpus": { + Description: "The number of virtual CPUs.", + Type: schema.TypeInt, + Computed: true, + }, + "memory": { + Description: "The amount of memory in MB.", + Type: schema.TypeInt, + Computed: true, + }, + "disk": { + Description: "The disk size in GB.", + Type: schema.TypeInt, + Computed: true, + }, + "comments": { + Description: "Comments or notes about the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "tags_ids": { + Description: "The IDs of the tags associated with the virtual machine.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "created": { + Description: "The creation date of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceVirtualMachineRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Get the virtual machine name from the Terraform configuration + vmName := d.Get("name").(string) + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch virtual machine by name + rsp, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesList(auth).Name([]string{vmName}).Execute() + if err != nil { + return diag.Errorf("failed to get virtual machine with name %s: %s", vmName, err.Error()) + } + + if len(rsp.Results) == 0 { + return diag.Errorf("no virtual machine found with name %s", vmName) + } + + vm := rsp.Results[0] + + d.SetId(vm.Id) + + createdStr := "" + if vm.Created.IsSet() && vm.Created.Get() != nil { + createdStr = vm.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if vm.LastUpdated.IsSet() && vm.LastUpdated.Get() != nil { + lastUpdatedStr = vm.LastUpdated.Get().Format(time.RFC3339) + } + + // Set the fields directly in the resource data + d.Set("id", vm.Id) + d.Set("name", vm.Name) + d.Set("vcpus", vm.Vcpus.Get()) + d.Set("memory", vm.Memory.Get()) + d.Set("disk", vm.Disk.Get()) + d.Set("comments", vm.Comments) + d.Set("created", createdStr) + d.Set("last_updated", lastUpdatedStr) + d.Set("tags_ids", vm.Tags) + + // Extract additional fields + if vm.Cluster.Id != nil && vm.Cluster.Id.String != nil { + d.Set("cluster_id", *vm.Cluster.Id.String) + } + + if vm.Status.Id != nil && vm.Status.Id.String != nil { + statusID := *vm.Status.Id.String + statusName, err := getStatusName(ctx, c, t, statusID) + if err != nil { + return diag.Errorf("failed to get status name for ID %s: %s", statusID, err.Error()) + } + d.Set("status", statusName) + } + + if vm.Tenant.IsSet() { + tenant := vm.Tenant.Get() + if tenant != nil && tenant.Id != nil { + d.Set("tenant_id", *tenant.Id.String) + } + } + + if vm.Platform.IsSet() { + platform := vm.Platform.Get() + if platform != nil && platform.Id != nil { + d.Set("platform_id", *platform.Id.String) + } + } + + if vm.Role.IsSet() { + role := vm.Role.Get() + if role != nil && role.Id != nil { + d.Set("role_id", *role.Id.String) + } + } + + if vm.PrimaryIp4.IsSet() { + primaryIp4 := vm.PrimaryIp4.Get() + if primaryIp4 != nil && primaryIp4.Id != nil { + d.Set("primary_ip4_id", *primaryIp4.Id.String) + } + } + + if vm.PrimaryIp6.IsSet() { + primaryIp6 := vm.PrimaryIp6.Get() + if primaryIp6 != nil && primaryIp6.Id != nil { + d.Set("primary_ip6_id", *primaryIp6.Id.String) + } + } + + return diags +} diff --git a/internal/provider/data_source_virtual_machines.go b/internal/provider/data_source_virtual_machines.go new file mode 100644 index 0000000..ebbbb1c --- /dev/null +++ b/internal/provider/data_source_virtual_machines.go @@ -0,0 +1,227 @@ +package provider + +import ( + "context" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceVirtualMachines() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about virtual machines in Nautobot.", + + ReadContext: dataSourceVirtualMachinesRead, + + Schema: map[string]*schema.Schema{ + "virtual_machines": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The UUID of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "The name of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "cluster_id": { + Description: "The ID of the cluster associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "status": { + Description: "The name of the status of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "platform_id": { + Description: "The ID of the platform associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "role_id": { + Description: "The ID of the role associated with the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "primary_ip4_id": { + Description: "The ID of the primary IPv4 address.", + Type: schema.TypeString, + Computed: true, + }, + "primary_ip6_id": { + Description: "The ID of the primary IPv6 address.", + Type: schema.TypeString, + Computed: true, + }, + "vcpus": { + Description: "The number of virtual CPUs.", + Type: schema.TypeInt, + Computed: true, + }, + "memory": { + Description: "The amount of memory in MB.", + Type: schema.TypeInt, + Computed: true, + }, + "disk": { + Description: "The disk size in GB.", + Type: schema.TypeInt, + Computed: true, + }, + "comments": { + Description: "Comments or notes about the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "tags_ids": { + Description: "The IDs of the tags associated with the virtual machine.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "created": { + Description: "The creation date of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceVirtualMachinesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + s := meta.(*apiClient).Server + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch virtual machines list + rsp, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesList(auth).Execute() + if err != nil { + return diag.Errorf("failed to get virtual machines list from %s: %s", s, err.Error()) + } + + results := rsp.Results + list := make([]map[string]interface{}, 0) + + for _, vm := range results { + createdStr := "" + if vm.Created.IsSet() && vm.Created.Get() != nil { + createdStr = vm.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if vm.LastUpdated.IsSet() && vm.LastUpdated.Get() != nil { + lastUpdatedStr = vm.LastUpdated.Get().Format(time.RFC3339) + } + + itemMap := map[string]interface{}{ + "id": vm.Id, + "name": vm.Name, + "vcpus": vm.Vcpus.Get(), + "memory": vm.Memory.Get(), + "disk": vm.Disk.Get(), + "comments": vm.Comments, + "created": createdStr, + "last_updated": lastUpdatedStr, + "tags_ids": vm.Tags, + } + + // Extract cluster_id, status, and other fields + if vm.Cluster.Id != nil && vm.Cluster.Id.String != nil { + itemMap["cluster_id"] = *vm.Cluster.Id.String + } + + if vm.Status.Id != nil && vm.Status.Id.String != nil { + statusID := *vm.Status.Id.String + statusName, err := getStatusName(ctx, c, t, statusID) + if err != nil { + return diag.Errorf("failed to get status name for ID %s: %s", statusID, err.Error()) + } + itemMap["status"] = statusName + } + + // Handle nullable fields (tenant, platform, role, etc.) + if vm.Tenant.IsSet() { + tenant := vm.Tenant.Get() + if tenant != nil && tenant.Id != nil { + itemMap["tenant_id"] = *tenant.Id.String + } + } + + if vm.Platform.IsSet() { + platform := vm.Platform.Get() + if platform != nil && platform.Id != nil { + itemMap["platform_id"] = *platform.Id.String + } + } + + if vm.Role.IsSet() { + role := vm.Role.Get() + if role != nil && role.Id != nil { + itemMap["role_id"] = *role.Id.String + } + } + + if vm.PrimaryIp4.IsSet() { + primaryIp4 := vm.PrimaryIp4.Get() + if primaryIp4 != nil && primaryIp4.Id != nil { + itemMap["primary_ip4_id"] = *primaryIp4.Id.String + } + } + + if vm.PrimaryIp6.IsSet() { + primaryIp6 := vm.PrimaryIp6.Get() + if primaryIp6 != nil && primaryIp6.Id != nil { + itemMap["primary_ip6_id"] = *primaryIp6.Id.String + } + } + + list = append(list, itemMap) + } + + if err := d.Set("virtual_machines", list); err != nil { + return diag.FromErr(err) + } + + // Set ID for the data source + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/internal/provider/data_source_vlan.go b/internal/provider/data_source_vlan.go new file mode 100644 index 0000000..faf850b --- /dev/null +++ b/internal/provider/data_source_vlan.go @@ -0,0 +1,191 @@ +package provider + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceVLAN() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about a specific VLAN in Nautobot.", + + ReadContext: dataSourceVLANRead, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "The name of the VLAN to retrieve.", + Type: schema.TypeString, + Required: true, + }, + "id": { + Description: "The UUID of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "vid": { + Description: "The ID (VID) of the VLAN.", + Type: schema.TypeInt, + Computed: true, + }, + "description": { + Description: "Description of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "vlan_group_id": { + Description: "The ID of the VLAN group.", + Type: schema.TypeString, + Computed: true, + }, + "status": { + Description: "The status of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "role_id": { + Description: "The ID of the role associated with the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "locations": { + Description: "The IDs of the locations associated with the VLAN.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "tags_ids": { + Description: "The IDs of the tags associated with the VLAN.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "created": { + Description: "The creation date of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceVLANRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Get the VLAN name from the Terraform configuration + vlanName := d.Get("name").(string) + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch VLAN by name + rsp, _, err := c.IpamAPI.IpamVlansList(auth).Name([]string{vlanName}).Execute() + if err != nil { + return diag.Errorf("failed to get VLAN with name %s: %s", vlanName, err.Error()) + } + + if len(rsp.Results) == 0 { + return diag.Errorf("no VLAN found with name %s", vlanName) + } + + vlan := rsp.Results[0] + + d.SetId(vlan.Id) + + createdStr := "" + if vlan.Created.IsSet() && vlan.Created.Get() != nil { + createdStr = vlan.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if vlan.LastUpdated.IsSet() && vlan.LastUpdated.Get() != nil { + lastUpdatedStr = vlan.LastUpdated.Get().Format(time.RFC3339) + } + + // Set the fields directly in the resource data + d.Set("id", vlan.Id) + d.Set("vid", vlan.Vid) + d.Set("name", vlan.Name) + d.Set("description", vlan.Description) + d.Set("created", createdStr) + d.Set("last_updated", lastUpdatedStr) + + // Handle nullable VlanGroup + if vlan.VlanGroup.IsSet() { + if vlanGroup := vlan.VlanGroup.Get(); vlanGroup != nil && vlanGroup.Id != nil { + d.Set("vlan_group_id", *vlanGroup.Id) + } + } + + if vlan.Status.Id != nil && vlan.Status.Id.String != nil { + statusID := *vlan.Status.Id.String + statusName, err := getStatusName(ctx, c, t, statusID) + if err != nil { + return diag.Errorf("failed to get status name for ID %s: %s", statusID, err.Error()) + } + d.Set("status", statusName) + } + + // Handle nullable Tenant + if vlan.Tenant.IsSet() { + if tenant := vlan.Tenant.Get(); tenant != nil && tenant.Id != nil && tenant.Id.String != nil { + d.Set("tenant_id", *tenant.Id.String) + } + } + + // Handle nullable Role + if vlan.Role.IsSet() { + if role := vlan.Role.Get(); role != nil && role.Id != nil && role.Id.String != nil { + d.Set("role_id", *role.Id.String) + } + } + + // Handle locations + var locations []string + for _, location := range vlan.Locations { + if location.Id != nil && location.Id.String != nil { + locations = append(locations, *location.Id.String) + } + } + d.Set("locations", locations) + + // Handle Tags + var tags []string + for _, tag := range vlan.Tags { + if tag.Id != nil && tag.Id.String != nil { + tags = append(tags, *tag.Id.String) + } + } + d.Set("tags_ids", tags) + + return diags +} diff --git a/internal/provider/data_source_vlans.go b/internal/provider/data_source_vlans.go new file mode 100644 index 0000000..1bd2fa3 --- /dev/null +++ b/internal/provider/data_source_vlans.go @@ -0,0 +1,209 @@ +package provider + +import ( + "context" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func dataSourceVLANs() *schema.Resource { + return &schema.Resource{ + Description: "Retrieves information about all VLANs in Nautobot.", + + ReadContext: dataSourceVLANsRead, + + Schema: map[string]*schema.Schema{ + "vlans": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Description: "The UUID of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "vid": { + Description: "The ID (VID) of the VLAN.", + Type: schema.TypeInt, + Computed: true, + }, + "name": { + Description: "The name of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Description of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "vlan_group_id": { + Description: "The ID of the VLAN group.", + Type: schema.TypeString, + Computed: true, + }, + "status": { + Description: "The status of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Description: "The ID of the tenant associated with the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "role_id": { + Description: "The ID of the role associated with the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "locations": { + Description: "The IDs of the locations associated with the VLAN.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "tags_ids": { + Description: "The IDs of the tags associated with the VLAN.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "created": { + Description: "The creation date of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "The last update date of the VLAN.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceVLANsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch VLANs list + rsp, _, err := c.IpamAPI.IpamVlansList(auth).Execute() + if err != nil { + return diag.Errorf("failed to get VLANs list: %s", err.Error()) + } + + results := rsp.Results + list := make([]map[string]interface{}, 0) + + // Iterate over the results and map each VLAN to the format expected by Terraform + for _, vlan := range results { + createdStr := "" + if vlan.Created.IsSet() && vlan.Created.Get() != nil { + createdStr = vlan.Created.Get().Format(time.RFC3339) + } + + lastUpdatedStr := "" + if vlan.LastUpdated.IsSet() && vlan.LastUpdated.Get() != nil { + lastUpdatedStr = vlan.LastUpdated.Get().Format(time.RFC3339) + } + + // Prepare itemMap with mandatory fields + itemMap := map[string]interface{}{ + "id": vlan.Id, + "vid": vlan.Vid, + "name": vlan.Name, + "description": vlan.Description, + "created": createdStr, + "last_updated": lastUpdatedStr, + } + + // Handle nullable VlanGroup + if vlan.VlanGroup.IsSet() { + if vlanGroup := vlan.VlanGroup.Get(); vlanGroup != nil && vlanGroup.Id != nil { + itemMap["vlan_group_id"] = *vlanGroup.Id + } + } + + // Fetch status name from the status ID + if vlan.Status.Id != nil && vlan.Status.Id.String != nil { + statusID := *vlan.Status.Id.String + statusName, err := getStatusName(ctx, c, t, statusID) + if err != nil { + return diag.Errorf("failed to get status name for ID %s: %s", statusID, err.Error()) + } + itemMap["status"] = statusName + } + + // Handle nullable Tenant + if vlan.Tenant.IsSet() { + if tenant := vlan.Tenant.Get(); tenant != nil && tenant.Id != nil && tenant.Id.String != nil { + itemMap["tenant_id"] = *tenant.Id.String + } + } + + // Handle nullable Role + if vlan.Role.IsSet() { + if role := vlan.Role.Get(); role != nil && role.Id != nil && role.Id.String != nil { + itemMap["role_id"] = *role.Id.String + } + } + + // Handle locations + var locations []string + for _, location := range vlan.Locations { + if location.Id != nil && location.Id.String != nil { + locations = append(locations, *location.Id.String) + } + } + itemMap["locations"] = locations + + // Handle Tags + var tags []string + for _, tag := range vlan.Tags { + if tag.Id != nil && tag.Id.String != nil { + tags = append(tags, *tag.Id.String) + } + } + itemMap["tags_ids"] = tags + + // Add the VLAN to the list + list = append(list, itemMap) + } + + // Set the VLANs list in the resource data + if err := d.Set("vlans", list); err != nil { + return diag.FromErr(err) + } + + // Always run + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} diff --git a/internal/provider/patch.go b/internal/provider/patch.go index 4225732..64586d2 100644 --- a/internal/provider/patch.go +++ b/internal/provider/patch.go @@ -4,10 +4,8 @@ import ( "context" "fmt" "net/http" - "time" - "github.com/deepmap/oapi-codegen/pkg/types" - nb "github.com/nautobot/go-nautobot/pkg/nautobot" + nb "github.com/nautobot/go-nautobot/v2" ) func NewSecurityProviderNautobotToken(t string) (*SecurityProviderNautobotToken, error) { @@ -25,62 +23,61 @@ func (s *SecurityProviderNautobotToken) Intercept(ctx context.Context, req *http return nil } -// PaginatedSiteList defines model for PaginatedSiteList. -type PaginatedSiteList struct { - Count *int `json:"count,omitempty"` - Next *string `json:"next"` - Previous *string `json:"previous"` - Results *[]Site `json:"results,omitempty"` +func stringPtr(s string) *string { + return &s } -// Mixin to add `status` choice field to model serializers. -type Site struct { - // 32-bit autonomous system number - Asn *int64 `json:"asn"` - CircuitCount *int `json:"circuit_count,omitempty"` - Comments *string `json:"comments,omitempty"` - ContactEmail *string `json:"contact_email,omitempty"` - ContactName *string `json:"contact_name,omitempty"` - ContactPhone *string `json:"contact_phone,omitempty"` - Created *types.Date `json:"created,omitempty"` - CustomFields *nb.CustomFieldChoice `json:"custom_fields,omitempty"` - Description *string `json:"description,omitempty"` - DeviceCount *int `json:"device_count,omitempty"` +func int32Ptr(i int) *int32 { + val := int32(i) + return &val +} + +func getStatusName(ctx context.Context, c *nb.APIClient, token string, statusID string) (string, error) { + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: token, + Prefix: "Token", + }, + }, + ) + + // Fetch the status using the status ID + status, _, err := c.ExtrasAPI.ExtrasStatusesRetrieve(auth, statusID).Execute() + if err != nil { + return "", err + } + + // No need to dereference, just check if the string is empty + if status.Name != "" { + return status.Name, nil + } + + return "", fmt.Errorf("status name not found for ID %s", statusID) +} - // Human friendly display value - Display *string `json:"display,omitempty"` +func getStatusID(ctx context.Context, c *nb.APIClient, token string, statusName string) (string, error) { + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: token, + Prefix: "Token", + }, + }, + ) - // Local facility ID or description - Facility *string `json:"facility,omitempty"` - Id *types.UUID `json:"id,omitempty"` - LastUpdated *time.Time `json:"last_updated,omitempty"` + statuses, _, err := c.ExtrasAPI.ExtrasStatusesList(auth).Name([]string{statusName}).Execute() + if err != nil { + return "", err + } - // GPS coordinate (latitude) - Latitude *string `json:"latitude"` + if len(statuses.Results) == 0 { + return "", fmt.Errorf("status %s not found", statusName) + } - // GPS coordinate (longitude) - Longitude *string `json:"longitude"` - Name string `json:"name"` - PhysicalAddress *string `json:"physical_address,omitempty"` - PrefixCount *int `json:"prefix_count,omitempty"` - RackCount *int `json:"rack_count,omitempty"` - Region *struct { - // Embedded struct due to allOf(#/components/schemas/NestedRegion) - nb.NestedRegion `yaml:",inline"` - } `json:"region"` - ShippingAddress *string `json:"shipping_address,omitempty"` - Slug *string `json:"slug,omitempty"` - Status struct { - Label *nb.SiteStatusLabel `json:"label,omitempty"` - Value *nb.SiteStatusValue `json:"value,omitempty"` - } `json:"status"` - Tags *[]nb.TagSerializerField `json:"tags,omitempty"` - Tenant *struct { - // Embedded struct due to allOf(#/components/schemas/NestedTenant) - nb.NestedTenant `yaml:",inline"` - } `json:"tenant"` - TimeZone *string `json:"time_zone"` - Url *string `json:"url,omitempty"` - VirtualmachineCount *int `json:"virtualmachine_count,omitempty"` - VlanCount *int `json:"vlan_count,omitempty"` + return statuses.Results[0].Id, nil } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9f3212c..9ce7667 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - nb "github.com/nautobot/go-nautobot/pkg/nautobot" + nb "github.com/nautobot/go-nautobot/v2" ) func init() { @@ -52,11 +52,29 @@ func New(version string) func() *schema.Provider { }, }, DataSourcesMap: map[string]*schema.Resource{ - "nautobot_manufacturers": dataSourceManufacturers(), - "nautobot_graphql": dataSourceGraphQL(), + "nautobot_available_ip_address": dataSourceAvailableIP(), + "nautobot_cluster": dataSourceCluster(), + "nautobot_clusters": dataSourceClusters(), + "nautobot_cluster_type": dataSourceClusterType(), + "nautobot_cluster_types": dataSourceClusterTypes(), + "nautobot_manufacturer": dataSourceManufacturer(), + "nautobot_manufacturers": dataSourceManufacturers(), + "nautobot_graphql": dataSourceGraphQL(), + "nautobot_prefix": dataSourcePrefix(), + "nautobot_prefixes": dataSourcePrefixes(), + "nautobot_virtual_machine": dataSourceVirtualMachine(), + "nautobot_virtual_machines": dataSourceVirtualMachines(), + "nautobot_vlan": dataSourceVLAN(), + "nautobot_vlans": dataSourceVLANs(), }, ResourcesMap: map[string]*schema.Resource{ - "nautobot_manufacturer": resourceManufacturer(), + "nautobot_available_ip_address": resourceAvailableIPAddress(), + "nautobot_cluster": resourceCluster(), + "nautobot_cluster_type": resourceClusterType(), + "nautobot_manufacturer": resourceManufacturer(), + "nautobot_virtual_machine": resourceVirtualMachine(), + "nautobot_vm_interface": resourceVMInterface(), + "nautobot_vm_primary_ip": resourcePrimaryIPAddressForVM(), }, } @@ -70,10 +88,9 @@ func New(version string) func() *schema.Provider { // you would need to setup to communicate with the upstream // API. type apiClient struct { - Client *nb.ClientWithResponses - Server string - Token *SecurityProviderNautobotToken - BaseClient *nb.Client + Client *nb.APIClient + Server string + Token *SecurityProviderNautobotToken } func configure( @@ -82,6 +99,8 @@ func configure( ) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) { return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { serverURL := d.Get("url").(string) + config := nb.NewConfiguration() + config.Servers[0].URL = serverURL _, hasToken := d.GetOk("token") var diags diag.Diagnostics = nil @@ -96,30 +115,12 @@ func configure( d.Get("token").(string), ) - c, err := nb.NewClientWithResponses( - serverURL, - nb.WithRequestEditorFn(token.Intercept), - ) - if err != nil { - diags = diag.FromErr(err) - diags[0].Severity = diag.Error - return &apiClient{Server: serverURL}, diags - } - bc, err := nb.NewClient( - serverURL, - nb.WithRequestEditorFn(token.Intercept), - ) - if err != nil { - diags = diag.FromErr(err) - diags[0].Severity = diag.Error - return &apiClient{Server: serverURL}, diags - } + c := nb.NewAPIClient(config) return &apiClient{ - Client: c, - Server: serverURL, - Token: token, - BaseClient: bc, + Client: c, + Server: serverURL, + Token: token, }, diags } } diff --git a/internal/provider/resource_available_ip_address.go b/internal/provider/resource_available_ip_address.go new file mode 100644 index 0000000..d54bca4 --- /dev/null +++ b/internal/provider/resource_available_ip_address.go @@ -0,0 +1,211 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func resourceAvailableIPAddress() *schema.Resource { + return &schema.Resource{ + Description: "This object allocates and manages an available IP address in Nautobot", + + CreateContext: resourceAvailableIPAddressCreate, + ReadContext: resourceAvailableIPAddressRead, + UpdateContext: resourceAvailableIPAddressUpdate, + DeleteContext: resourceAvailableIPAddressDelete, + + Schema: map[string]*schema.Schema{ + "prefix_id": { + Description: "ID of the prefix to allocate the IP address from.", + Type: schema.TypeString, + Required: true, + }, + "address": { + Description: "Allocated IP address.", + Type: schema.TypeString, + Computed: true, + }, + "ip_version": { + Description: "IP version of the allocated IP address (4 or 6).", + Type: schema.TypeInt, + Computed: true, + }, + "dns_name": { + Description: "DNS name associated with the IP address.", + Type: schema.TypeString, + Optional: true, + }, + "status": { + Description: "Status of the allocated IP address.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceAvailableIPAddressCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + prefixID := d.Get("prefix_id").(string) + + // Convert status name to ID + statusName := d.Get("status").(string) + statusID, err := getStatusID(ctx, c, t, statusName) + if err != nil { + return diag.Errorf("failed to get status ID for %s: %s", statusName, err.Error()) + } + + // Prepare the IP allocation request + ipRequest := nb.IPAllocationRequest{ + Status: nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &statusID, + }, + }, + } + + if v, ok := d.GetOk("dns_name"); ok { + dns_name := v.(string) + ipRequest.DnsName = &dns_name + } + + // Allocate the IP (this automatically chooses the first available IP from the prefix) + rsp, _, err := c.IpamAPI.IpamPrefixesAvailableIpsCreate(auth, prefixID).IPAllocationRequest([]nb.IPAllocationRequest{ipRequest}).Execute() + if err != nil { + return diag.Errorf("failed to allocate IP address: %s", err.Error()) + } + + // Set resource data (assuming a single result, adjust if needed) + d.SetId(rsp[0].Id) + d.Set("address", rsp[0].Address) + d.Set("ip_version", rsp[0].IpVersion) + d.Set("dns_name", rsp[0].DnsName) + + return resourceAvailableIPAddressRead(ctx, d, meta) +} + +func resourceAvailableIPAddressRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch the allocated IP by ID + ipID := d.Id() + ipAddress, _, err := c.IpamAPI.IpamIpAddressesRetrieve(auth, ipID).Execute() + if err != nil { + d.SetId("") + return diag.Errorf("failed to read IP address %s: %s", ipID, err.Error()) + } + + // Map the retrieved data back to Terraform state + d.Set("address", ipAddress.Address) + d.Set("ip_version", ipAddress.IpVersion) + d.Set("dns_name", ipAddress.DnsName) + + return nil +} + +func resourceAvailableIPAddressUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + ipID := d.Id() + + var ipAddress nb.PatchedIPAddressRequest + + if d.HasChange("status") { + statusName := d.Get("status").(string) + statusID, err := getStatusID(ctx, c, t, statusName) + if err != nil { + return diag.Errorf("failed to get status ID for %s: %s", statusName, err.Error()) + } + + ipAddress.Status = &nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &statusID, + }, + } + } + + if d.HasChange("dns_name") { + dnsName := d.Get("dns_name").(string) + ipAddress.DnsName = &dnsName + } + + // Call the API to update the allocated IP address + _, _, err := c.IpamAPI.IpamIpAddressesPartialUpdate(auth, ipID).PatchedIPAddressRequest(ipAddress).Execute() + if err != nil { + return diag.Errorf("failed to update IP address %s: %s", ipID, err.Error()) + } + + return resourceAvailableIPAddressRead(ctx, d, meta) +} + +func resourceAvailableIPAddressDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch the IP address ID and delete it + ipID := d.Id() + _, err := c.IpamAPI.IpamIpAddressesDestroy(auth, ipID).Execute() + if err != nil { + return diag.Errorf("failed to delete IP address %s: %s", ipID, err.Error()) + } + + // Clear the ID from the state + d.SetId("") + + return nil +} diff --git a/internal/provider/resource_cluster.go b/internal/provider/resource_cluster.go new file mode 100644 index 0000000..4799f31 --- /dev/null +++ b/internal/provider/resource_cluster.go @@ -0,0 +1,357 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func resourceCluster() *schema.Resource { + return &schema.Resource{ + Description: "This object manages a cluster in Nautobot", + + CreateContext: resourceClusterCreate, + ReadContext: resourceClusterRead, + UpdateContext: resourceClusterUpdate, + DeleteContext: resourceClusterDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "Cluster's name.", + Type: schema.TypeString, + Required: true, + }, + "comments": { + Description: "Comments or notes about the cluster.", + Type: schema.TypeString, + Optional: true, + }, + "cluster_type_id": { + Description: "ID of the Cluster's type. This can be sourced from the cluster_type resource or data source.", + Type: schema.TypeString, + Required: true, + }, + "cluster_group_id": { + Description: "ID of the Cluster's group.", + Type: schema.TypeString, + Optional: true, + }, + "tenant_id": { + Description: "ID of the Tenant associated with the cluster.", + Type: schema.TypeString, + Optional: true, + }, + "location_id": { + Description: "ID of the Location of the cluster.", + Type: schema.TypeString, + Optional: true, + }, + "tags_ids": { + Description: "IDs of the Tags associated with the cluster.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "created": { + Description: "Creation date of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "Last update date of the cluster.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + clusterName := d.Get("name").(string) + existingClusters, _, err := c.VirtualizationAPI.VirtualizationClustersList(auth).Name([]string{clusterName}).Execute() + if err != nil { + return diag.Errorf("failed to list clusters: %s", err.Error()) + } + + // If a cluster with the same name exists, use its ID and skip creation + if len(existingClusters.Results) > 0 { + d.SetId(existingClusters.Results[0].Id) + return resourceClusterRead(ctx, d, meta) + } + + // Prepare ClusterRequest + var cluster nb.ClusterRequest + cluster.Name = clusterName + cluster.ClusterType = nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(d.Get("cluster_type_id").(string)), + }, + } + + // Optional fields + if v, ok := d.GetOk("comments"); ok { + comments := v.(string) + cluster.Comments = &comments + } + + if v, ok := d.GetOk("cluster_group_id"); ok { + var clusterGroup nb.NullableBulkWritableCircuitRequestTenant + clusterGroup.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(v.(string)), + }, + }) + cluster.ClusterGroup = clusterGroup + } + + if v, ok := d.GetOk("tenant_id"); ok { + var tenant nb.NullableBulkWritableCircuitRequestTenant + tenant.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(v.(string)), + }, + }) + cluster.Tenant = tenant + } + + if v, ok := d.GetOk("location_id"); ok { + var location nb.NullableBulkWritableCircuitRequestTenant + location.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(v.(string)), + }, + }) + cluster.Location = location + } + + if v, ok := d.GetOk("tags_ids"); ok { + var tags []nb.BulkWritableCableRequestStatus + for _, tag := range v.([]interface{}) { + tags = append(tags, nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(tag.(string)), + }, + }) + } + cluster.Tags = tags + } + + // Create the cluster + rsp, _, err := c.VirtualizationAPI.VirtualizationClustersCreate(auth).ClusterRequest(cluster).Execute() + if err != nil { + return diag.Errorf("failed to create cluster: %s", err.Error()) + } + + // Set resource ID (Cluster ID) + d.SetId(rsp.Id) + + return resourceClusterRead(ctx, d, meta) +} + +func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch cluster by ID + clusterId := d.Id() + cluster, _, err := c.VirtualizationAPI.VirtualizationClustersRetrieve(auth, clusterId).Execute() + if err != nil { + return diag.Errorf("failed to read cluster: %s", err.Error()) + } + + // Map the retrieved data back to Terraform state + d.Set("name", cluster.Name) + + // Extract cluster_type_id safely + if cluster.ClusterType.Id != nil && cluster.ClusterType.Id.String != nil { + d.Set("cluster_type_id", *cluster.ClusterType.Id.String) + } + + // Check if comments exist before setting + if cluster.Comments != nil { + d.Set("comments", *cluster.Comments) + } + + // Handle nullable cluster group + if cluster.ClusterGroup.IsSet() { + if clusterGroup := cluster.ClusterGroup.Get(); clusterGroup != nil && clusterGroup.Id != nil { + d.Set("cluster_group_id", *clusterGroup.Id.String) + } + } + + // Handle nullable tenant + if cluster.Tenant.IsSet() { + if tenant := cluster.Tenant.Get(); tenant != nil && tenant.Id != nil { + d.Set("tenant_id", *tenant.Id.String) + } + } + + // Handle nullable location + if cluster.Location.IsSet() { + if location := cluster.Location.Get(); location != nil && location.Id != nil { + d.Set("location_id", *location.Id.String) + } + } + + // Set tags + if len(cluster.Tags) > 0 { + var tags []string + for _, tag := range cluster.Tags { + if tag.Id != nil && tag.Id.String != nil { + tags = append(tags, *tag.Id.String) + } + } + d.Set("tags_ids", tags) + } + + d.Set("created", cluster.Created) + d.Set("last_updated", cluster.LastUpdated) + + return nil +} + +func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + clusterId := d.Id() + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + var cluster nb.PatchedClusterRequest + + // Update the fields that have changed + if d.HasChange("name") { + name := d.Get("name").(string) + cluster.Name = &name // Set the pointer for the name + } + if d.HasChange("comments") { + comments := d.Get("comments").(string) + cluster.Comments = &comments + } + if d.HasChange("cluster_type_id") { + clusterTypeID := d.Get("cluster_type_id").(string) + cluster.ClusterType = &nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &clusterTypeID, // Pass pointer for the string value + }, + } + } + if d.HasChange("cluster_group_id") { + clusterGroupID := d.Get("cluster_group_id").(string) + clusterGroup := &nb.BulkWritableCircuitRequestTenant{ // Create the cluster group as a pointer + Id: &nb.BulkWritableCableRequestStatusId{ + String: &clusterGroupID, // Pass pointer for the string value + }, + } + cluster.ClusterGroup.Set(clusterGroup) // Pass the pointer to Set() + } + if d.HasChange("tenant_id") { + tenantID := d.Get("tenant_id").(string) + tenant := &nb.BulkWritableCircuitRequestTenant{ // Create the tenant as a pointer + Id: &nb.BulkWritableCableRequestStatusId{ + String: &tenantID, // Pass pointer for the string value + }, + } + cluster.Tenant.Set(tenant) // Pass the pointer to Set() + } + if d.HasChange("location_id") { + locationID := d.Get("location_id").(string) + location := &nb.BulkWritableCircuitRequestTenant{ // Create the location as a pointer + Id: &nb.BulkWritableCableRequestStatusId{ + String: &locationID, // Pass pointer for the string value + }, + } + cluster.Location.Set(location) // Pass the pointer to Set() + } + if d.HasChange("tags_ids") { + var tags []nb.BulkWritableCableRequestStatus + for _, tag := range d.Get("tags_ids").([]interface{}) { + tagID := tag.(string) + tags = append(tags, nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &tagID, // Pass pointer for the string value + }, + }) + } + cluster.Tags = tags + } + + // Call the API to update the cluster + _, _, err := c.VirtualizationAPI.VirtualizationClustersPartialUpdate(auth, clusterId).PatchedClusterRequest(cluster).Execute() + if err != nil { + return diag.Errorf("failed to update cluster: %s", err.Error()) + } + + return resourceClusterRead(ctx, d, meta) +} + +func resourceClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Delete the cluster by ID + clusterId := d.Id() + _, err := c.VirtualizationAPI.VirtualizationClustersDestroy(auth, clusterId).Execute() + if err != nil { + return diag.Errorf("failed to delete cluster: %s", err.Error()) + } + + // Clear the ID + d.SetId("") + + return nil +} diff --git a/internal/provider/resource_cluster_type.go b/internal/provider/resource_cluster_type.go new file mode 100644 index 0000000..306fafd --- /dev/null +++ b/internal/provider/resource_cluster_type.go @@ -0,0 +1,229 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func resourceClusterType() *schema.Resource { + return &schema.Resource{ + Description: "This object manages a cluster type in Nautobot.", + + CreateContext: resourceClusterTypeCreate, + ReadContext: resourceClusterTypeRead, + UpdateContext: resourceClusterTypeUpdate, + DeleteContext: resourceClusterTypeDelete, + + Schema: map[string]*schema.Schema{ + "id": { + Description: "Cluster type's UUID.", + Type: schema.TypeString, + Computed: true, + }, + "object_type": { + Description: "Object type of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "display": { + Description: "Human-friendly display value for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "url": { + Description: "URL of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "natural_slug": { + Description: "Natural slug for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "Cluster type's name.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Description for the cluster type.", + Type: schema.TypeString, + Optional: true, + }, + "created": { + Description: "Creation date of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "Last update date of the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + "notes_url": { + Description: "Notes URL for the cluster type.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceClusterTypeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + clusterTypeName := d.Get("name").(string) + existingClusterTypes, _, err := c.VirtualizationAPI.VirtualizationClusterTypesList(auth).Name([]string{clusterTypeName}).Execute() + if err != nil { + return diag.Errorf("failed to list cluster types: %s", err.Error()) + } + + // If a cluster type with the same name exists, use its ID and skip creation + if len(existingClusterTypes.Results) > 0 { + d.SetId(existingClusterTypes.Results[0].Id) + return resourceClusterTypeRead(ctx, d, meta) + } + + // Prepare ClusterTypeRequest + var clusterType nb.ClusterTypeRequest + clusterType.Name = clusterTypeName + + if v, ok := d.GetOk("description"); ok { + description := v.(string) + clusterType.Description = &description + } + + // Create the cluster type using VirtualizationAPI + rsp, _, err := c.VirtualizationAPI.VirtualizationClusterTypesCreate(auth).ClusterTypeRequest(clusterType).Execute() + if err != nil { + return diag.Errorf("failed to create cluster type: %s", err.Error()) + } + + // Set resource ID (Cluster Type ID) + d.SetId(rsp.Id) + + return resourceClusterTypeRead(ctx, d, meta) +} + +func resourceClusterTypeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch cluster type by ID using VirtualizationAPI + clusterTypeId := d.Id() + clusterType, _, err := c.VirtualizationAPI.VirtualizationClusterTypesRetrieve(auth, clusterTypeId).Execute() + if err != nil { + return diag.Errorf("failed to read cluster type: %s", err.Error()) + } + + // Map the retrieved data back to Terraform state + d.Set("name", clusterType.Name) + d.Set("object_type", clusterType.ObjectType) + d.Set("display", clusterType.Display) + d.Set("url", clusterType.Url) + d.Set("natural_slug", clusterType.NaturalSlug) + if clusterType.Description != nil { + d.Set("description", *clusterType.Description) + } + d.Set("created", clusterType.Created) + d.Set("last_updated", clusterType.LastUpdated) + d.Set("notes_url", clusterType.NotesUrl) + + return nil +} + +func resourceClusterTypeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + clusterTypeId := d.Id() + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + var clusterType nb.PatchedClusterTypeRequest + + // Update the fields that have changed + if d.HasChange("name") { + name := d.Get("name").(string) + clusterType.Name = &name + } + if d.HasChange("description") { + description := d.Get("description").(string) + clusterType.Description = &description + } + + // Call the API to update the cluster type + _, _, err := c.VirtualizationAPI.VirtualizationClusterTypesPartialUpdate(auth, clusterTypeId).PatchedClusterTypeRequest(clusterType).Execute() + if err != nil { + return diag.Errorf("failed to update cluster type: %s", err.Error()) + } + + return resourceClusterTypeRead(ctx, d, meta) +} + +func resourceClusterTypeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Delete the cluster type by ID using VirtualizationAPI + clusterTypeId := d.Id() + _, err := c.VirtualizationAPI.VirtualizationClusterTypesDestroy(auth, clusterTypeId).Execute() + if err != nil { + return diag.Errorf("failed to delete cluster type: %s", err.Error()) + } + + // Clear the ID + d.SetId("") + + return nil +} diff --git a/internal/provider/resource_manufacturer.go b/internal/provider/resource_manufacturer.go index 378443e..ad39b1a 100644 --- a/internal/provider/resource_manufacturer.go +++ b/internal/provider/resource_manufacturer.go @@ -2,17 +2,13 @@ package provider import ( "context" - "encoding/json" - "strings" + "time" - "github.com/deepmap/oapi-codegen/pkg/types" - "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/tidwall/gjson" - nb "github.com/nautobot/go-nautobot/pkg/nautobot" + nb "github.com/nautobot/go-nautobot/v2" ) func resourceManufacturer() *schema.Resource { @@ -35,20 +31,9 @@ func resourceManufacturer() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "custom_fields": { - Description: "Manufacturer custom fields.", - Type: schema.TypeMap, - Optional: true, - }, - "devicetype_count": { - Description: "Manufacturer's device count.", - Type: schema.TypeInt, - Computed: true, - }, "display": { Description: "Manufacturer's display name.", Type: schema.TypeString, - Optional: true, Computed: true, }, "id": { @@ -56,13 +41,8 @@ func resourceManufacturer() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "inventoryitem_count": { - Description: "Manufacturer's inventory item count.", - Type: schema.TypeInt, - Computed: true, - }, "last_updated": { - Description: "Manufacturer's last update.", + Description: "Manufacturer's last update date.", Type: schema.TypeString, Computed: true, }, @@ -72,26 +52,23 @@ func resourceManufacturer() *schema.Resource { Required: true, }, "notes_url": { - Description: "Notes for manufacturer.", + Description: "Notes URL for the manufacturer.", Type: schema.TypeString, - Optional: true, Computed: true, }, - "platform_count": { - Description: "Manufacturer's platform count.", - Type: schema.TypeInt, + "url": { + Description: "Manufacturer's URL.", + Type: schema.TypeString, Computed: true, }, - "slug": { - Description: "Manufacturer's slug.", + "object_type": { + Description: "Object type of the manufacturer.", Type: schema.TypeString, - Optional: true, Computed: true, }, - "url": { - Description: "Manufacturer's URL.", + "natural_slug": { + Description: "Natural slug for the manufacturer.", Type: schema.TypeString, - Optional: true, Computed: true, }, }, @@ -101,175 +78,136 @@ func resourceManufacturer() *schema.Resource { func resourceManufacturerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c := meta.(*apiClient).Client s := meta.(*apiClient).Server + t := meta.(*apiClient).Token.token - var m nb.ManufacturerRequest - - name, ok := d.GetOk("name") - n := name.(string) - if ok { - m.Name = n - } - - m.Description = &n - description, ok := d.GetOk("description") - if ok { - t := description.(string) - m.Description = &t - } + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) - sl := strings.ReplaceAll(strings.ToLower(n), " ", "-") - m.Slug = &sl - slug, ok := d.GetOk("slug") - if ok { - t := slug.(string) - m.Slug = &t - } + // Check if a manufacturer with the same name already exists + name := d.Get("name").(string) - rsp, err := c.DcimManufacturersCreateWithResponse( - ctx, - nb.DcimManufacturersCreateJSONRequestBody(m)) + existingManufacturersResp, _, err := c.DcimAPI.DcimManufacturersList(auth).Execute() if err != nil { - return diag.Errorf("failed to create manufacturer %s on %s: %s", name.(string), s, err.Error()) + return diag.Errorf("failed to check existing manufacturers on %s : %s", s, err.Error()) } - data := string(rsp.Body) - - dataName := gjson.Get(data, "name.0") - - if dataName.String() == "manufacturer with this name already exists." { - rsp, err := c.DcimManufacturersListWithResponse( - ctx, - &nb.DcimManufacturersListParams{ - NameIe: &[]string{n}, - }) - if err != nil { - return diag.Errorf("failed to get manufacturer %s from %s: %s", n, s, err.Error()) + // Search through the results for a manufacturer with the given name + for _, manufacturer := range existingManufacturersResp.Results { + if manufacturer.Name == name { + // Manufacturer already exists, set the ID and exit + d.SetId(manufacturer.Id) + return resourceManufacturerRead(ctx, d, meta) } - id := gjson.Get(string(rsp.Body), "results.0.id") + } - d.SetId(id.String()) + // Create a new manufacturer + var m nb.ManufacturerRequest + m.Name = name - return resourceManufacturerRead(ctx, d, meta) + if v, ok := d.GetOk("description"); ok { + desc := v.(string) + m.Description = &desc + } + rsp, _, err := c.DcimAPI.DcimManufacturersCreate(auth).ManufacturerRequest(m).Execute() + if err != nil { + return diag.Errorf("failed to create manufacturer %s on %s: %s", m.Name, s, err.Error()) } tflog.Trace(ctx, "manufacturer created", map[string]interface{}{ - "name": name.(string), - "data": []interface{}{description, slug}, + "name": m.Name, }) - id := gjson.Get(data, "id") - - d.SetId(id.String()) + d.SetId(rsp.Id) return resourceManufacturerRead(ctx, d, meta) } func resourceManufacturerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token id := d.Get("id").(string) - rsp, err := c.DcimManufacturersListWithResponse( - ctx, - &nb.DcimManufacturersListParams{ - IdIe: &[]types.UUID{uuid.MustParse(id)}, - }) - - var diags diag.Diagnostics - name := d.Get("name").(string) - s := meta.(*apiClient).Server - if err != nil { - return diag.Errorf("failed to get manufacturer %s from %s: %s", name, s, err.Error()) - } - - // If the Manufacturer is in the state file, but it is not in the Nautobot platform - // the response we get from DcimManufacturersListWithResponse is: {"count":0,"next":null,"previous":null,"results":[]} - // When you create something in Terraform but delete it manually, Terraform should gracefully handle it. - // We should set the ID to an empty string so Terraform "destroys" the resource in state. - count := gjson.Get(string(rsp.Body), "count") - if count.String() == "0" { - d.SetId("") - return diags - } - results := gjson.Get(string(rsp.Body), "results.0") - - resultsReader := strings.NewReader(results.String()) - - item := make(map[string]interface{}) + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) - err = json.NewDecoder(resultsReader).Decode(&item) + // Fetch manufacturer by ID + manufacturer, _, err := c.DcimAPI.DcimManufacturersRetrieve(auth, id).Execute() if err != nil { - return diag.Errorf("failed to decode manufacturer %s from %s: %s", name, s, err.Error()) + return diag.Errorf("failed to get manufacturer %s: %s", id, err.Error()) } - d.Set("name", item["name"].(string)) - d.Set("created", item["created"].(string)) - d.Set("description", item["description"].(string)) - d.Set("display", item["display"].(string)) - d.Set("id", item["id"].(string)) - d.Set("notes_url", item["notes_url"].(string)) - d.Set("slug", item["slug"].(string)) - d.Set("url", item["url"].(string)) - d.Set("last_updated", item["last_updated"].(string)) - - switch v := item["devicetype_count"].(type) { - case int: - d.Set("devicetype_count", v) - case float64: - d.Set("devicetype_count", int(v)) - default: - } - switch v := item["inventoryitem_count"].(type) { - case int: - d.Set("inventoryitem_count", v) - case float64: - d.Set("inventoryitem_count", int(v)) - default: + // Set the Terraform state from the retrieved manufacturer data + d.Set("name", manufacturer.Name) + if manufacturer.Created.IsSet() && manufacturer.Created.Get() != nil { + d.Set("created", manufacturer.Created.Get().Format(time.RFC3339)) } - switch v := item["platform_count"].(type) { - case int: - d.Set("platform_count", v) - case float64: - d.Set("platform_count", int(v)) - default: + if manufacturer.LastUpdated.IsSet() && manufacturer.LastUpdated.Get() != nil { + d.Set("last_updated", manufacturer.LastUpdated.Get().Format(time.RFC3339)) } + d.Set("description", manufacturer.Description) + d.Set("display", manufacturer.Display) + d.Set("id", manufacturer.Id) + d.Set("notes_url", manufacturer.NotesUrl) + d.Set("url", manufacturer.Url) + d.Set("object_type", manufacturer.ObjectType) + d.Set("natural_slug", manufacturer.NaturalSlug) - return diags + return nil } func resourceManufacturerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { c := meta.(*apiClient).Client s := meta.(*apiClient).Server + t := meta.(*apiClient).Token.token - name := d.Get("name").(string) id := d.Get("id").(string) var m nb.PatchedManufacturerRequest + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + if d.HasChange("name") { + name := d.Get("name").(string) m.Name = &name } - desc := d.Get("description").(string) if d.HasChange("description") { + desc := d.Get("description").(string) m.Description = &desc } - slug := d.Get("slug").(string) - if d.HasChange("slug") { - m.Description = &slug - } - - _, err := c.DcimManufacturersPartialUpdateWithResponse( - ctx, - uuid.MustParse(id), - nb.DcimManufacturersPartialUpdateJSONRequestBody(m)) + _, _, err := c.DcimAPI.DcimManufacturersPartialUpdate(auth, id).PatchedManufacturerRequest(m).Execute() if err != nil { - return diag.Errorf("failed to update manufacturer %s on %s: %s", name, s, err.Error()) + return diag.Errorf("failed to update manufacturer %s on %s: %s", id, s, err.Error()) } tflog.Trace(ctx, "manufacturer updated", map[string]interface{}{ - "name": name, - "data": []string{desc, slug}, + "id": id, }) return resourceManufacturerRead(ctx, d, meta) @@ -280,15 +218,24 @@ func resourceManufacturerDelete(ctx context.Context, d *schema.ResourceData, met c := meta.(*apiClient).Client s := meta.(*apiClient).Server + t := meta.(*apiClient).Token.token id := d.Get("id").(string) - name := d.Get("name").(string) - _, err := c.DcimManufacturersDestroy( + auth := context.WithValue( ctx, - uuid.MustParse(id)) + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + _, err := c.DcimAPI.DcimManufacturersDestroy(auth, id).Execute() if err != nil { - return diag.Errorf("failed to delete manufacturer %s on %s: %s", name, s, err.Error()) + return diag.Errorf("failed to delete manufacturer %s on %s: %s", id, s, err.Error()) } // d.SetId("") is automatically called assuming delete returns no errors, but diff --git a/internal/provider/resource_virtual_machine.go b/internal/provider/resource_virtual_machine.go new file mode 100644 index 0000000..892ac60 --- /dev/null +++ b/internal/provider/resource_virtual_machine.go @@ -0,0 +1,583 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func resourceVirtualMachine() *schema.Resource { + return &schema.Resource{ + Description: "This object manages a virtual machine in Nautobot", + + CreateContext: resourceVirtualMachineCreate, + ReadContext: resourceVirtualMachineRead, + UpdateContext: resourceVirtualMachineUpdate, + DeleteContext: resourceVirtualMachineDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "Virtual Machine's name.", + Type: schema.TypeString, + Required: true, + }, + "cluster_id": { + Description: "Cluster where the virtual machine belongs.", + Type: schema.TypeString, + Required: true, + }, + "status": { + Description: "Status of the virtual machine.", + Type: schema.TypeString, + Required: true, + }, + "vcpus": { + Description: "Number of virtual CPUs.", + Type: schema.TypeInt, + Optional: true, + }, + "memory": { + Description: "Amount of memory in MB.", + Type: schema.TypeInt, + Optional: true, + }, + "disk": { + Description: "Disk size in GB.", + Type: schema.TypeInt, + Optional: true, + }, + "comments": { + Description: "Comments or notes about the virtual machine.", + Type: schema.TypeString, + Optional: true, + }, + "tenant_id": { + Description: "Tenant associated with the virtual machine.", + Type: schema.TypeString, + Optional: true, + }, + "platform_id": { + Description: "Platform or OS installed on the virtual machine.", + Type: schema.TypeString, + Optional: true, + }, + "role_id": { + Description: "Role of the virtual machine.", + Type: schema.TypeString, + Optional: true, + }, + "primary_ip4_id": { + Description: "Primary IPv4 address.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "primary_ip6_id": { + Description: "Primary IPv6 address.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "software_version_id": { + Description: "Software version installed on the virtual machine.", + Type: schema.TypeString, + Optional: true, + }, + "software_image_files": { + Description: "Software image files associated with the software version.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "tags_ids": { + Description: "Tags associated with the virtual machine.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "created": { + Description: "Creation date of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "Last update date of the virtual machine.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceVirtualMachineCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Check if the VM with the same name exists + vmName := d.Get("name").(string) + existingVMs, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesList(auth).Name([]string{vmName}).Execute() + if err != nil { + return diag.Errorf("failed to list virtual machines: %s", err.Error()) + } + + // If a VM with the same name exists, use its ID and skip creation + if len(existingVMs.Results) > 0 { + d.SetId(existingVMs.Results[0].Id) + return resourceVirtualMachineRead(ctx, d, meta) + } + + // Convert status name to ID + statusName := d.Get("status").(string) + statusID, err := getStatusID(ctx, c, t, statusName) + if err != nil { + return diag.Errorf("failed to get status ID for %s: %s", statusName, err.Error()) + } + + // Prepare the VirtualMachineRequest + var vm nb.VirtualMachineRequest + vm.Name = d.Get("name").(string) + vm.Cluster = nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(d.Get("cluster_id").(string)), + }, + } + vm.Status = nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(statusID), + }, + } + + // Optional fields + if v, ok := d.GetOk("vcpus"); ok { + vm.Vcpus.Set(int32Ptr(v.(int))) + } + if v, ok := d.GetOk("memory"); ok { + vm.Memory.Set(int32Ptr(v.(int))) + } + if v, ok := d.GetOk("disk"); ok { + vm.Disk.Set(int32Ptr(v.(int))) + } + if v, ok := d.GetOk("comments"); ok { + comments := v.(string) + vm.Comments = &comments + } + if v, ok := d.GetOk("tenant_id"); ok { + tenant := v.(string) + var nullableTenant nb.NullableBulkWritableCircuitRequestTenant + nullableTenant.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(tenant), + }, + }) + vm.Tenant = nullableTenant + } + if v, ok := d.GetOk("platform_id"); ok { + platform := v.(string) + var nullablePlatform nb.NullableBulkWritableCircuitRequestTenant + nullablePlatform.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(platform), + }, + }) + vm.Platform = nullablePlatform + } + + if v, ok := d.GetOk("role_id"); ok { + role := v.(string) + var nullableRole nb.NullableBulkWritableCircuitRequestTenant + nullableRole.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(role), + }, + }) + vm.Role = nullableRole + } + + if v, ok := d.GetOk("primary_ip4_id"); ok { + ip4 := v.(string) + var nullableIP4 nb.NullablePrimaryIPv4 + primaryIPv4 := &nb.PrimaryIPv4{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(ip4), + }, + } + nullableIP4.Set(primaryIPv4) + vm.PrimaryIp4 = nullableIP4 + } + + if v, ok := d.GetOk("primary_ip6_id"); ok { + ip6 := v.(string) + var nullableIP6 nb.NullablePrimaryIPv6 + primaryIPv6 := &nb.PrimaryIPv6{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(ip6), + }, + } + nullableIP6.Set(primaryIPv6) + vm.PrimaryIp6 = nullableIP6 + } + + if v, ok := d.GetOk("software_version_id"); ok { + softwareVersion := v.(string) + var nullableSoftwareVersion nb.NullableBulkWritableVirtualMachineRequestSoftwareVersion + softwareVersionStruct := &nb.BulkWritableVirtualMachineRequestSoftwareVersion{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(softwareVersion), + }, + } + nullableSoftwareVersion.Set(softwareVersionStruct) + vm.SoftwareVersion = nullableSoftwareVersion + } + + if v, ok := d.GetOk("software_image_files"); ok { + var files []nb.SoftwareImageFiles + for _, file := range v.([]interface{}) { + fileData := file.(map[string]interface{}) + files = append(files, nb.SoftwareImageFiles{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(fileData["id"].(string)), + }, + }) + } + vm.SoftwareImageFiles = files + } + if v, ok := d.GetOk("tags_ids"); ok { + var tags []nb.BulkWritableCableRequestStatus + for _, tag := range v.([]interface{}) { + tags = append(tags, nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(tag.(string)), + }, + }) + } + vm.Tags = tags + } + + // Create the virtual machine + rsp, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesCreate(auth).VirtualMachineRequest(vm).Execute() + if err != nil { + return diag.Errorf("failed to create virtual machine: %s", err.Error()) + } + + // Set resource ID + d.SetId(rsp.Id) + + return resourceVirtualMachineRead(ctx, d, meta) +} + +func resourceVirtualMachineRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch virtual machine by ID + vmId := d.Id() + vm, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesRetrieve(auth, vmId).Execute() + if err != nil { + return diag.Errorf("failed to read virtual machine: %s", err.Error()) + } + + // Map the retrieved data back to Terraform state + d.Set("name", vm.Name) + d.Set("cluster_id", vm.Cluster.Id) + d.Set("status", vm.Status.Id) + d.Set("vcpus", vm.Vcpus.Get()) + d.Set("memory", vm.Memory.Get()) + d.Set("disk", vm.Disk.Get()) + d.Set("comments", vm.Comments) + + // Handle nullable fields using IsSet and Get methods + if vm.Tenant.IsSet() { + tenant := vm.Tenant.Get() + if tenant != nil && tenant.Id != nil { + d.Set("tenant_id", *tenant.Id.String) + } + } + + if vm.Platform.IsSet() { + platform := vm.Platform.Get() + if platform != nil && platform.Id != nil { + d.Set("platform_id", *platform.Id.String) + } + } + + if vm.Role.IsSet() { + role := vm.Role.Get() + if role != nil && role.Id != nil { + d.Set("role_id", *role.Id.String) + } + } + + if vm.PrimaryIp4.IsSet() { + primaryIp4 := vm.PrimaryIp4.Get() + if primaryIp4 != nil && primaryIp4.Id != nil { + d.Set("primary_ip4_id", *primaryIp4.Id.String) + } + } + + if vm.PrimaryIp6.IsSet() { + primaryIp6 := vm.PrimaryIp6.Get() + if primaryIp6 != nil && primaryIp6.Id != nil { + d.Set("primary_ip6_id", *primaryIp6.Id.String) + } + } + + if vm.SoftwareVersion.IsSet() { + softwareVersion := vm.SoftwareVersion.Get() + if softwareVersion != nil && softwareVersion.Id != nil { + d.Set("software_version_id", *softwareVersion.Id.String) + } + } + + var imageFiles []map[string]string + for _, file := range vm.SoftwareImageFiles { + if file.Id != nil && file.Id.String != nil { + imageFiles = append(imageFiles, map[string]string{ + "id": *file.Id.String, + }) + } + } + + d.Set("software_image_files", imageFiles) + + var tags []string + for _, tag := range vm.Tags { + if tag.Id != nil { + tags = append(tags, *tag.Id.String) + } + } + d.Set("tags_ids", tags) + + d.Set("created", vm.Created) + d.Set("last_updated", vm.LastUpdated) + + return nil +} + +func resourceVirtualMachineUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + vmId := d.Id() + + var vm nb.PatchedVirtualMachineRequest + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Update the fields that have changed + if d.HasChange("name") { + name := d.Get("name").(string) + vm.Name = &name + } + + if d.HasChange("cluster_id") { + clusterID := d.Get("cluster_id").(string) + vm.Cluster = &nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &clusterID, + }, + } + } + + if d.HasChange("status") { + statusName := d.Get("status").(string) + statusID, err := getStatusID(ctx, c, t, statusName) + if err != nil { + return diag.Errorf("failed to get status ID for %s: %s", statusName, err.Error()) + } + vm.Status = &nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(statusID), + }, + } + } + + // Optional fields + if d.HasChange("vcpus") { + vm.Vcpus.Set(int32Ptr(d.Get("vcpus").(int))) + } + if d.HasChange("memory") { + vm.Memory.Set(int32Ptr(d.Get("memory").(int))) + } + if d.HasChange("disk") { + vm.Disk.Set(int32Ptr(d.Get("disk").(int))) + } + if d.HasChange("comments") { + comments := d.Get("comments").(string) + vm.Comments = &comments + } + if d.HasChange("tenant_id") { + tenant := d.Get("tenant_id").(string) + var nullableTenant nb.NullableBulkWritableCircuitRequestTenant + nullableTenant.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(tenant), + }, + }) + vm.Tenant = nullableTenant + } + if d.HasChange("platform_id") { + platform := d.Get("platform_id").(string) + var nullablePlatform nb.NullableBulkWritableCircuitRequestTenant + nullablePlatform.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(platform), + }, + }) + vm.Platform = nullablePlatform + } + + if d.HasChange("role_id") { + role := d.Get("role_id").(string) + var nullableRole nb.NullableBulkWritableCircuitRequestTenant + nullableRole.Set(&nb.BulkWritableCircuitRequestTenant{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(role), + }, + }) + vm.Role = nullableRole + } + + if d.HasChange("primary_ip4_id") { + ip4 := d.Get("primary_ip4_id").(string) + var nullableIP4 nb.NullablePrimaryIPv4 + primaryIPv4 := &nb.PrimaryIPv4{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(ip4), + }, + } + nullableIP4.Set(primaryIPv4) + vm.PrimaryIp4 = nullableIP4 + } + + if d.HasChange("primary_ip6_id") { + ip6 := d.Get("primary_ip6_id").(string) + var nullableIP6 nb.NullablePrimaryIPv6 + primaryIPv6 := &nb.PrimaryIPv6{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(ip6), + }, + } + nullableIP6.Set(primaryIPv6) + vm.PrimaryIp6 = nullableIP6 + } + + if d.HasChange("software_version_id") { + softwareVersion := d.Get("software_version_id").(string) + var nullableSoftwareVersion nb.NullableBulkWritableVirtualMachineRequestSoftwareVersion + softwareVersionStruct := &nb.BulkWritableVirtualMachineRequestSoftwareVersion{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(softwareVersion), + }, + } + nullableSoftwareVersion.Set(softwareVersionStruct) + vm.SoftwareVersion = nullableSoftwareVersion + } + + if d.HasChange("software_image_files") { + var files []nb.SoftwareImageFiles + for _, file := range d.Get("software_image_files").([]interface{}) { + fileData := file.(map[string]interface{}) + files = append(files, nb.SoftwareImageFiles{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(fileData["id"].(string)), + }, + }) + } + vm.SoftwareImageFiles = files + } + + if d.HasChange("tags_ids") { + var tags []nb.BulkWritableCableRequestStatus + for _, tag := range d.Get("tags_ids").([]interface{}) { + tagID := tag.(string) + tags = append(tags, nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &tagID, + }, + }) + } + vm.Tags = tags + } + + // Call the API to update the virtual machine + _, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesPartialUpdate(auth, vmId).PatchedVirtualMachineRequest(vm).Execute() + if err != nil { + return diag.Errorf("failed to update virtual machine: %s", err.Error()) + } + + return resourceVirtualMachineRead(ctx, d, meta) +} + +func resourceVirtualMachineDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Delete the virtual machine by ID + vmId := d.Id() + _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesDestroy(auth, vmId).Execute() + if err != nil { + return diag.Errorf("failed to delete virtual machine: %s", err.Error()) + } + + // Clear the ID + d.SetId("") + + return nil +} diff --git a/internal/provider/resource_virtual_machine_interface.go b/internal/provider/resource_virtual_machine_interface.go new file mode 100644 index 0000000..f2d2565 --- /dev/null +++ b/internal/provider/resource_virtual_machine_interface.go @@ -0,0 +1,450 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func resourceVMInterface() *schema.Resource { + return &schema.Resource{ + Description: "This object manages a VM Interface in Nautobot", + + CreateContext: resourceVMInterfaceCreate, + ReadContext: resourceVMInterfaceRead, + UpdateContext: resourceVMInterfaceUpdate, + DeleteContext: resourceVMInterfaceDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Description: "Name of the VM interface.", + Type: schema.TypeString, + Required: true, + }, + "mac_address": { + Description: "MAC address of the interface.", + Type: schema.TypeString, + Optional: true, + }, + "enabled": { + Description: "Whether the interface is enabled.", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "mtu": { + Description: "MTU size of the interface.", + Type: schema.TypeInt, + Optional: true, + }, + "mode": { + Description: "Mode of the interface.", + Type: schema.TypeString, + Optional: true, + }, + "description": { + Description: "Description of the interface.", + Type: schema.TypeString, + Optional: true, + }, + "status": { + Description: "Status of the VM interface.", + Type: schema.TypeString, + Required: true, + }, + "virtual_machine_id": { + Description: "ID of the virtual machine to which the interface belongs.", + Type: schema.TypeString, + Required: true, + }, + "untagged_vlan_id": { + Description: "Untagged VLAN ID associated with the interface.", + Type: schema.TypeString, + Optional: true, + }, + "tags_ids": { + Description: "Tags associated with the interface.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "ip_addresses": { + Description: "List of IP addresses to assign to the VM interface.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "created": { + Description: "Creation date of the interface.", + Type: schema.TypeString, + Computed: true, + }, + "last_updated": { + Description: "Last updated date of the interface.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceVMInterfaceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Check if the interface with the same name and virtual machine ID exists + interfaceName := d.Get("name").(string) + virtualMachineID := d.Get("virtual_machine_id").(string) + + existingInterfaces, _, err := c.VirtualizationAPI.VirtualizationInterfacesList(auth). + Name([]string{interfaceName}). + VirtualMachine([]string{virtualMachineID}). + Execute() + if err != nil { + return diag.Errorf("failed to list VM interfaces: %s", err.Error()) + } + + if len(existingInterfaces.Results) > 0 { + // Interface already exists, use its ID and skip creation + d.SetId(existingInterfaces.Results[0].Id) + return resourceVMInterfaceRead(ctx, d, meta) + } + + // Convert status name to ID + statusName := d.Get("status").(string) + statusID, err := getStatusID(ctx, c, t, statusName) + if err != nil { + return diag.Errorf("failed to get status ID for %s: %s", statusName, err.Error()) + } + + // Prepare the VMInterface request + var vmInterface nb.WritableVMInterfaceRequest + vmInterface.Name = interfaceName + vmInterface.Status = nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &statusID, + }, + } + + // Set optional fields + if v, ok := d.GetOk("mac_address"); ok { + vmInterface.MacAddress.Set(stringPtr(v.(string))) + } + if v, ok := d.GetOk("enabled"); ok { + enabled := v.(bool) + vmInterface.Enabled = &enabled + } + if v, ok := d.GetOk("mtu"); ok { + mtu := int32(v.(int)) + vmInterface.Mtu.Set(&mtu) + } + if v, ok := d.GetOk("description"); ok { + desc := v.(string) + vmInterface.Description = &desc + } + + // Handle virtual machine ID + vmInterface.VirtualMachine.Id = &nb.BulkWritableCableRequestStatusId{ + String: &virtualMachineID, + } + + // Create the interface + rsp, _, err := c.VirtualizationAPI.VirtualizationInterfacesCreate(auth).WritableVMInterfaceRequest(vmInterface).Execute() + if err != nil { + return diag.Errorf("failed to create VM interface: %s", err.Error()) + } + + // Set resource ID + d.SetId(rsp.Id) + + // Assign IP addresses to the VM interface + if v, ok := d.GetOk("ip_addresses"); ok { + ipAddresses := v.([]interface{}) + for _, ip := range ipAddresses { + err := assignIPAddressToVMInterface(ctx, c, t, ip.(string), rsp.Id) + if err != nil { + return diag.Errorf("failed to assign IP address to VM interface: %s", err.Error()) + } + } + } + + return resourceVMInterfaceRead(ctx, d, meta) +} + +func resourceVMInterfaceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Fetch interface by ID + vmInterfaceId := d.Id() + vmInterface, _, err := c.VirtualizationAPI.VirtualizationInterfacesRetrieve(auth, vmInterfaceId).Execute() + if err != nil { + return diag.Errorf("failed to read VM interface: %s", err.Error()) + } + + // Map the retrieved data back to Terraform state + d.Set("name", vmInterface.Name) + d.Set("mac_address", vmInterface.MacAddress) + d.Set("enabled", vmInterface.Enabled) + d.Set("mtu", vmInterface.Mtu) + d.Set("description", vmInterface.Description) + d.Set("status", vmInterface.Status.Id) + d.Set("virtual_machine_id", vmInterface.VirtualMachine.Id) + d.Set("untagged_vlan_id", vmInterface.UntaggedVlan) + d.Set("created", vmInterface.Created) + d.Set("last_updated", vmInterface.LastUpdated) + d.Set("tags_ids", vmInterface.Tags) + + // Fetch assigned IP addresses + assignedIPs := []string{} + for _, ip := range vmInterface.IpAddresses { + assignedIPs = append(assignedIPs, *ip.Id.String) + } + d.Set("ip_addresses", assignedIPs) + + if vmInterface.Mode != nil { + d.Set("mode", vmInterface.Mode.Label) + } + + return nil +} + +func resourceVMInterfaceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + vmInterfaceId := d.Id() + + var vmInterface nb.PatchedWritableVMInterfaceRequest + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Update the fields that have changed + if d.HasChange("name") { + name := d.Get("name").(string) + vmInterface.Name = &name + } + if d.HasChange("mac_address") { + mac := d.Get("mac_address").(string) + vmInterface.MacAddress.Set(&mac) + } + if d.HasChange("enabled") { + enabled := d.Get("enabled").(bool) + vmInterface.Enabled = &enabled + } + if d.HasChange("mtu") { + mtu := int32(d.Get("mtu").(int)) + vmInterface.Mtu.Set(&mtu) + } + if d.HasChange("description") { + description := d.Get("description").(string) + vmInterface.Description = &description + } + if d.HasChange("status") { + statusName := d.Get("status").(string) + statusID, err := getStatusID(ctx, c, t, statusName) + if err != nil { + return diag.Errorf("failed to get status ID for %s: %s", statusName, err.Error()) + } + vmInterface.Status = &nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &statusID, + }, + } + } + if d.HasChange("virtual_machine_id") { + vmID := d.Get("virtual_machine_id").(string) + vmInterface.VirtualMachine = &nb.BulkWritableCableRequestStatus{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: &vmID, + }, + } + } + + // Call the API to update the VM interface + _, _, err := c.VirtualizationAPI.VirtualizationInterfacesPartialUpdate(auth, vmInterfaceId).PatchedWritableVMInterfaceRequest(vmInterface).Execute() + if err != nil { + return diag.Errorf("failed to update VM interface: %s", err.Error()) + } + + // Update IP addresses if they have changed + if d.HasChange("ip_addresses") { + oldIPs, newIPs := d.GetChange("ip_addresses") + + // Remove old IP addresses + for _, oldIP := range oldIPs.([]interface{}) { + err := removeIPAddressFromVMInterface(ctx, c, t, oldIP.(string), vmInterfaceId) // Pass vmInterfaceId here + if err != nil { + return diag.Errorf("failed to remove IP address from VM interface: %s", err.Error()) + } + } + + // Assign new IP addresses + for _, newIP := range newIPs.([]interface{}) { + err := assignIPAddressToVMInterface(ctx, c, t, newIP.(string), vmInterfaceId) + if err != nil { + return diag.Errorf("failed to assign IP address to VM interface: %s", err.Error()) + } + } + } + + return resourceVMInterfaceRead(ctx, d, meta) +} + +func resourceVMInterfaceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + // Delete the interface by ID + vmInterfaceId := d.Id() + _, err := c.VirtualizationAPI.VirtualizationInterfacesDestroy(auth, vmInterfaceId).Execute() + if err != nil { + return diag.Errorf("failed to delete VM interface: %s", err.Error()) + } + + // Clear the ID + d.SetId("") + + return nil +} + +// Helper function to assign an IP address to a VM interface +func assignIPAddressToVMInterface(ctx context.Context, c *nb.APIClient, token, ipAddressID, vmInterfaceID string) error { + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: token, + Prefix: "Token", + }, + }, + ) + + // Wrap the ipAddressID and vmInterfaceID in BulkWritableCableRequestStatusId + ipAddressStatusId := nb.BulkWritableCableRequestStatusId{String: &ipAddressID} + vmInterfaceStatusId := nb.BulkWritableCableRequestStatusId{String: &vmInterfaceID} + + // Create BulkWritableCircuitRequestTenant for the VM Interface + vmInterfaceTenant := nb.BulkWritableCircuitRequestTenant{ + Id: &vmInterfaceStatusId, + } + + // Create the NullableBulkWritableCircuitRequestTenant as a value (not a pointer) + vmInterfaceNullableTenant := nb.NullableBulkWritableCircuitRequestTenant{} + vmInterfaceNullableTenant.Set(&vmInterfaceTenant) + + // Prepare the request to assign the IP address to the VM interface + ipToInterfaceRequest := nb.IPAddressToInterfaceRequest{ + IpAddress: nb.BulkWritableCableRequestStatus{ + Id: &ipAddressStatusId, // Assign the IP address ID + }, + // Properly set VmInterface using NullableBulkWritableCircuitRequestTenant + VmInterface: vmInterfaceNullableTenant, // Use the value, not a pointer + } + + // Call the API to link the IP address with the VM interface + _, _, err := c.IpamAPI.IpamIpAddressToInterfaceCreate(auth).IPAddressToInterfaceRequest(ipToInterfaceRequest).Execute() + if err != nil { + return fmt.Errorf("failed to assign IP address to VM interface: %v", err) + } + + return nil +} + +// Helper function to remove an IP address from a VM interface +func removeIPAddressFromVMInterface(ctx context.Context, c *nb.APIClient, token, ipAddressID, vmInterfaceID string) error { + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: token, + Prefix: "Token", + }, + }, + ) + + // Retrieve the IP address object to find the related VM interface assignment + ipAddress, _, err := c.IpamAPI.IpamIpAddressesRetrieve(auth, ipAddressID).Execute() + if err != nil { + return fmt.Errorf("failed to retrieve IP address: %v", err) + } + + // Look for the specific VM interface assignment in the IP address object + var assignmentID string + for _, vmInterface := range ipAddress.VmInterfaces { + if vmInterface.Id.String != nil && *vmInterface.Id.String == vmInterfaceID { + assignmentID = *vmInterface.Id.String + break + } + } + + // If no assignment is found, return an error + if assignmentID == "" { + return fmt.Errorf("no assignment found for IP address %s and VM interface %s", ipAddressID, vmInterfaceID) + } + + // Call IpamIpAddressToInterfaceDestroy to remove the assignment + _, err = c.IpamAPI.IpamIpAddressToInterfaceDestroy(auth, assignmentID).Execute() + if err != nil { + return fmt.Errorf("failed to remove IP address assignment: %v", err) + } + + return nil +} diff --git a/internal/provider/resource_virtual_machine_primary_ip.go b/internal/provider/resource_virtual_machine_primary_ip.go new file mode 100644 index 0000000..6151cdb --- /dev/null +++ b/internal/provider/resource_virtual_machine_primary_ip.go @@ -0,0 +1,187 @@ +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + nb "github.com/nautobot/go-nautobot/v2" +) + +func resourcePrimaryIPAddressForVM() *schema.Resource { + return &schema.Resource{ + Description: "This resource sets an IP address as the primary IPv4 or IPv6 for a virtual machine in Nautobot", + + CreateContext: resourcePrimaryIPAddressForVMCreate, + ReadContext: resourcePrimaryIPAddressForVMRead, + UpdateContext: resourcePrimaryIPAddressForVMUpdate, + DeleteContext: resourcePrimaryIPAddressForVMDelete, + + Schema: map[string]*schema.Schema{ + "virtual_machine_id": { + Description: "ID of the virtual machine.", + Type: schema.TypeString, + Required: true, + }, + "primary_ip4_id": { + Description: "ID of the primary IPv4 address.", + Type: schema.TypeString, + Optional: true, + }, + "primary_ip6_id": { + Description: "ID of the primary IPv6 address.", + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourcePrimaryIPAddressForVMCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + vmID := d.Get("virtual_machine_id").(string) + + // Prepare the VirtualMachineRequest to set the primary IP address + var vm nb.PatchedVirtualMachineRequest + + if v, ok := d.GetOk("primary_ip4_id"); ok { + ip4 := v.(string) + var nullableIP4 nb.NullablePrimaryIPv4 + primaryIPv4 := &nb.PrimaryIPv4{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(ip4), + }, + } + nullableIP4.Set(primaryIPv4) + vm.PrimaryIp4 = nullableIP4 + } + + if v, ok := d.GetOk("primary_ip6_id"); ok { + ip6 := v.(string) + var nullableIP6 nb.NullablePrimaryIPv6 + primaryIPv6 := &nb.PrimaryIPv6{ + Id: &nb.BulkWritableCableRequestStatusId{ + String: stringPtr(ip6), + }, + } + nullableIP6.Set(primaryIPv6) + vm.PrimaryIp6 = nullableIP6 + } + + // Update the virtual machine with the primary IP addresses + _, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesPartialUpdate(auth, vmID).PatchedVirtualMachineRequest(vm).Execute() + if err != nil { + return diag.Errorf("failed to set primary IP address for virtual machine: %s", err.Error()) + } + + // Use VM ID as the resource ID + d.SetId(vmID) + + return resourcePrimaryIPAddressForVMRead(ctx, d, meta) +} + +func resourcePrimaryIPAddressForVMRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + vmID := d.Id() + + // Fetch the virtual machine by ID + vm, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesRetrieve(auth, vmID).Execute() + if err != nil { + d.SetId("") + return diag.Errorf("failed to read virtual machine: %s", err.Error()) + } + + // Set primary IPs if they exist + if vm.PrimaryIp4.IsSet() { + primaryIp4 := vm.PrimaryIp4.Get() + if primaryIp4 != nil && primaryIp4.Id != nil { + d.Set("primary_ip4_id", *primaryIp4.Id.String) + } + } + + if vm.PrimaryIp6.IsSet() { + primaryIp6 := vm.PrimaryIp6.Get() + if primaryIp6 != nil && primaryIp6.Id != nil { + d.Set("primary_ip6_id", *primaryIp6.Id.String) + } + } + + d.Set("virtual_machine_id", vmID) + + return nil +} + +func resourcePrimaryIPAddressForVMUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + // The update function is identical to the create function since the action is the same + return resourcePrimaryIPAddressForVMCreate(ctx, d, meta) +} + +func resourcePrimaryIPAddressForVMDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*apiClient).Client + t := meta.(*apiClient).Token.token + + // Auth context + auth := context.WithValue( + ctx, + nb.ContextAPIKeys, + map[string]nb.APIKey{ + "tokenAuth": { + Key: t, + Prefix: "Token", + }, + }, + ) + + vmID := d.Id() + + // Prepare the VirtualMachineRequest to remove the primary IP addresses + var vm nb.PatchedVirtualMachineRequest + var nullableIP4 nb.NullablePrimaryIPv4 + var nullableIP6 nb.NullablePrimaryIPv6 + + // Set both IPs to null + nullableIP4.Unset() + nullableIP6.Unset() + + vm.PrimaryIp4 = nullableIP4 + vm.PrimaryIp6 = nullableIP6 + + // Update the virtual machine to unset the primary IPs + _, _, err := c.VirtualizationAPI.VirtualizationVirtualMachinesPartialUpdate(auth, vmID).PatchedVirtualMachineRequest(vm).Execute() + if err != nil { + return diag.Errorf("failed to remove primary IP address for virtual machine: %s", err.Error()) + } + + // Clear the ID from the state + d.SetId("") + + return nil +} diff --git a/main.go b/main.go index 0334111..3d93362 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( // If you do not have terraform installed, you can remove the formatting command, but its suggested to // ensure the documentation is formatted properly. -//go:generate terraform fmt -recursive ./examples/ +//go:generate tofu fmt -recursive ./examples/ // Run the docs generation tool, check its repository for more information on how it works and how docs // can be customized. diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json index 1931b0e..fec2a56 100644 --- a/terraform-registry-manifest.json +++ b/terraform-registry-manifest.json @@ -1,6 +1,6 @@ { "version": 1, "metadata": { - "protocol_versions": ["5.0"] + "protocol_versions": ["6.0"] } } diff --git a/test/main.tf b/test/main.tf index 9716e1c..8b9d715 100644 --- a/test/main.tf +++ b/test/main.tf @@ -8,7 +8,7 @@ terraform { } provider "nautobot" { - url = "https://demo.nautobot.com/api/" + url = "https://demo.nautobot.com/api" token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } @@ -16,3 +16,69 @@ resource "nautobot_manufacturer" "new" { description = "Created with Terraform" name = "New Vendor" } + +resource "nautobot_cluster_type" "new" { + name = "Example Cluster Type" + description = "This is a cluster type created via Terraform" +} + +resource "nautobot_cluster" "new" { + name = "My New Cluster" + comments = "This cluster was created using Terraform." + cluster_type_id = nautobot_cluster_type.new.id + + # Optionally add cluster group, tenant, location, etc. +# cluster_group_id = "your-cluster-group-id" +# tenant_id = data.nautobot_tenant.example.id # Referencing tenant data source +# location_id = "your-location-id" +# tags_id = ["tag1", "tag2"] +} + +data "nautobot_vlan" "example" { + name = "pek02-106-mgmt" +} + +data "nautobot_prefix" "example" { + depends_on = [data.nautobot_vlan.example] + vlan_id = data.nautobot_vlan.example.id +} + +resource "nautobot_available_ip_address" "example" { + prefix_id = data.nautobot_prefix.example.id + status = "Active" + dns_name = "test-vm.test.com" +} + +# Example virtual machine resource +resource "nautobot_virtual_machine" "new" { + name = "Example VM" + cluster_id = nautobot_cluster.new.id + status = "Active" + vcpus = 4 + memory = 8192 # Memory in MB (8GB) + disk = 100 # Disk size in GB + comments = "This virtual machine was created using Terraform." +# tenant_id = "some-tenant-id" # Optional +# platform_id = "Linux" # Optional +# role_id = "Web Server" # Optional +# primary_ip4_id = nautobot_available_ip_address.example.id +# primary_ip6_id = "2001:db8::100" # Optional +# software_version_id = "v1.0" # Optional + +# tags_ids = ["tag1", "tag2"] # Optional tags +} + +resource "nautobot_vm_interface" "new" { + name = "eth0" + virtual_machine_id = nautobot_virtual_machine.new.id + status = "Active" + ip_addresses = [ + nautobot_available_ip_address.example.id + ] +} + +resource "nautobot_vm_primary_ip" "new" { + depends_on = [nautobot_vm_interface.new] + virtual_machine_id = nautobot_virtual_machine.new.id + primary_ip4_id = nautobot_available_ip_address.example.id +} \ No newline at end of file diff --git a/test/output.tf b/test/output.tf index cd6c8d5..999d0c1 100644 --- a/test/output.tf +++ b/test/output.tf @@ -1,3 +1,69 @@ +data "nautobot_cluster" "example" { + depends_on = [nautobot_cluster.new] + name = nautobot_cluster.new.name +} + +output "cluster_details" { + value = data.nautobot_cluster.example +} + +output "cluster_id" { + value = data.nautobot_cluster.example.id +} + +data "nautobot_clusters" "example" { + depends_on = [nautobot_cluster.new] +} + +output "clusters_details" { + value = data.nautobot_clusters.example.clusters[0] +} + +output "clusters_id" { + value = data.nautobot_clusters.example.clusters[0].id +} + + +data "nautobot_cluster_type" "example" { + depends_on = [nautobot_cluster_type.new] + name = nautobot_cluster_type.new.name +} + +output "cluster_type_details" { + value = data.nautobot_cluster_type.example +} + +output "cluster_type_id" { + value = data.nautobot_cluster_type.example.id +} + +data "nautobot_cluster_types" "example" { + depends_on = [nautobot_cluster_type.new] +} + +output "cluster_types_details" { + value = data.nautobot_cluster_types.example.cluster_types[0] +} + +output "cluster_types_id" { + value = data.nautobot_cluster_types.example.cluster_types[0].id +} + + +data "nautobot_manufacturer" "example" { + depends_on = [nautobot_manufacturer.new] + name = nautobot_manufacturer.new.name +} + +output "manufacturer_details" { + value = data.nautobot_manufacturer.example +} + +output "manufacturer_id" { + value = data.nautobot_manufacturer.example.id +} + + data "nautobot_manufacturers" "all" { depends_on = [nautobot_manufacturer.new] } @@ -15,7 +81,45 @@ output "data_source_example" { if manufacturer.name == var.manufacturer_name } } + +output "prefix_details" { + value = data.nautobot_prefix.example +} + +output "prefix_id" { + value = data.nautobot_prefix.example.id +} + +data "nautobot_prefixes" "example" { +} + +output "prefixes_details" { + value = data.nautobot_prefixes.example.prefixes[0] +} + +output "prefixes_id" { + value = data.nautobot_prefixes.example.prefixes[0].id +} + +data "nautobot_available_ip_address" "example" { + prefix_id = data.nautobot_prefix.example.id +} + +output "available_ip_address" { + value = data.nautobot_available_ip_address.example.address +} + +output "available_ip_version" { + value = data.nautobot_available_ip_address.example.ip_version +} + +output "allocated_ip" { + value = nautobot_available_ip_address.example.address +} + + data "nautobot_graphql" "nodes" { + depends_on = [nautobot_virtual_machine.new] query = <