Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ All notable changes to this project are documented here. The format is based on
`pcd_images_image_ids` data sources.
- Networking (Neutron v2): resources `pcd_networking_network`, `_subnet`, `_secgroup`,
`_secgroup_rule`, `_router`, `_router_interface`; data sources `pcd_networking_network`,
`_subnet`, `_secgroup`. (Floating IPs, ports, and remaining data sources to follow.)
`_subnet`, `_secgroup`.
- Networking extras: `pcd_networking_port` (fixed IPs, security groups, allowed-address
pairs, tags) and `pcd_networking_floatingip` (allocate from an external network by
`pool` name, associate/disassociate to a port); data sources `pcd_networking_port`,
`_port_ids`, `_router`, `_subnet_ids`, and `_floatingip`. Ports and floating IPs are
code-complete with acceptance tests; see DECISIONS.md for live-validation status.
- Compute (Nova v2): resources `pcd_compute_keypair`, `pcd_compute_flavor`,
`pcd_compute_servergroup` (acceptance-tested); `pcd_compute_instance` (code-complete —
boot verification is blocked on a lab image-library issue, see DECISIONS.md); data
Expand Down
4 changes: 4 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ yet passable on this lab (reason noted). Generated registry docs are not committ
| Images (DS) | `pcd_images_image`, `_image_ids` | **VALIDATED** |
| Networking | `pcd_networking_network`, `_subnet`, `_secgroup`, `_secgroup_rule`, `_router`, `_router_interface` | **VALIDATED** |
| Networking (DS) | `pcd_networking_network`, `_subnet`, `_secgroup` | **VALIDATED** |
| Networking | `pcd_networking_port` | **PENDING** — code-complete, build/vet/lint/docs clean; acceptance test written (create/update/import). Not yet run live: lab credentials were unavailable in this session. No lab-side blocker expected. |
| Networking | `pcd_networking_floatingip` | **PENDING** — code-complete. Needs an **external network** in the lab (allocation pool); acc test skips unless `PCD_ACC_EXTERNAL_NETWORK` names one. |
| Networking (DS) | `pcd_networking_port`, `_port_ids`, `_router`, `_subnet_ids` | **PENDING** — code-complete; acc test written. Not yet run live (credentials unavailable this session). |
| Networking (DS) | `pcd_networking_floatingip` | **PENDING** — depends on a floating IP existing (see external-network note above). |
| Compute | `pcd_compute_keypair`, `_flavor`, `_servergroup` | **VALIDATED** |
| Compute (DS) | `pcd_compute_flavor`, `_keypair`, `_availability_zones` | **VALIDATED** |
| Compute | `pcd_compute_instance` (boot) | **PENDING** — lab image-library gap: images don't reach the onboarded host's local library → nova returns HTTP 204 for image data. Create/schedule/wait/error-report verified; passes with a library-backed image. |
Expand Down
7 changes: 7 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func (p *pcdProvider) Resources(_ context.Context) []func() resource.Resource {
networking.NewSecgroupRuleResource,
networking.NewRouterResource,
networking.NewRouterInterfaceResource,
networking.NewPortResource,
networking.NewFloatingIPResource,
compute.NewKeypairResource,
compute.NewInstanceResource,
compute.NewFlavorResource,
Expand All @@ -71,7 +73,12 @@ func (p *pcdProvider) DataSources(_ context.Context) []func() datasource.DataSou
images.NewImageIDsDataSource,
networking.NewNetworkDataSource,
networking.NewSubnetDataSource,
networking.NewSubnetIDsDataSource,
networking.NewSecgroupDataSource,
networking.NewPortDataSource,
networking.NewPortIDsDataSource,
networking.NewRouterDataSource,
networking.NewFloatingIPDataSource,
compute.NewFlavorDataSource,
compute.NewKeypairDataSource,
compute.NewAvailabilityZonesDataSource,
Expand Down
87 changes: 87 additions & 0 deletions internal/services/networking/data_sources_extras_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Platform9 Systems, Inc.
// SPDX-License-Identifier: MPL-2.0

package networking_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"

"github.com/platform9/terraform-provider-pcd/internal/acctest"
)

// TestAccNetworkingExtraDataSources_basic creates a network, subnet, port, and
// router, then exercises the port, router, subnet_ids, and port_ids data
// sources against them.
func TestAccNetworkingExtraDataSources_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
CheckDestroy: resource.ComposeAggregateTestCheckFunc(
testAccCheckPortDestroy(t),
testAccCheckSubnetDestroy(t),
testAccCheckNetworkDestroy(t),
testAccCheckRouterDestroy(t),
),
Steps: []resource.TestStep{
{
Config: testAccNetworkingExtraDataSourcesConfig,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrPair("data.pcd_networking_port.by_name", "id", "pcd_networking_port.test", "id"),
resource.TestCheckResourceAttrPair("data.pcd_networking_port.by_name", "network_id", "pcd_networking_network.test", "id"),
resource.TestCheckResourceAttrPair("data.pcd_networking_router.by_name", "id", "pcd_networking_router.test", "id"),
resource.TestCheckResourceAttr("data.pcd_networking_subnet_ids.by_network", "ids.#", "1"),
resource.TestCheckResourceAttrPair("data.pcd_networking_subnet_ids.by_network", "ids.0", "pcd_networking_subnet.test", "id"),
resource.TestCheckResourceAttr("data.pcd_networking_port_ids.by_name", "ids.#", "1"),
resource.TestCheckResourceAttrPair("data.pcd_networking_port_ids.by_name", "ids.0", "pcd_networking_port.test", "id"),
),
},
},
})
}

const testAccNetworkingExtraDataSourcesConfig = `
resource "pcd_networking_network" "test" {
name = "tf-acc-dsx-net"
}

resource "pcd_networking_subnet" "test" {
name = "tf-acc-dsx-subnet"
network_id = pcd_networking_network.test.id
cidr = "10.103.0.0/24"
}

resource "pcd_networking_port" "test" {
name = "tf-acc-dsx-port"
network_id = pcd_networking_network.test.id

fixed_ip {
subnet_id = pcd_networking_subnet.test.id
}
}

resource "pcd_networking_router" "test" {
name = "tf-acc-dsx-router"
}

data "pcd_networking_port" "by_name" {
name = pcd_networking_port.test.name
}

data "pcd_networking_router" "by_name" {
name = pcd_networking_router.test.name
}

data "pcd_networking_subnet_ids" "by_network" {
network_id = pcd_networking_network.test.id

depends_on = [pcd_networking_subnet.test]
}

data "pcd_networking_port_ids" "by_name" {
name = pcd_networking_port.test.name

depends_on = [pcd_networking_port.test]
}
`
134 changes: 134 additions & 0 deletions internal/services/networking/floatingip_data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Platform9 Systems, Inc.
// SPDX-License-Identifier: MPL-2.0
//
// Ported from terraform-provider-openstack v3.4.0
// (openstack/data_source_openstack_networking_floatingip_v2.go), adapted for the
// terraform-plugin-framework and PCD.

package networking

import (
"context"
"fmt"

"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/platform9/terraform-provider-pcd/internal/clients"
)

var (
_ datasource.DataSource = (*floatingIPDataSource)(nil)
_ datasource.DataSourceWithConfigure = (*floatingIPDataSource)(nil)
)

// NewFloatingIPDataSource is the factory registered with the provider.
func NewFloatingIPDataSource() datasource.DataSource {
return &floatingIPDataSource{}
}

type floatingIPDataSource struct {
config *clients.Config
}

type floatingIPDataSourceModel struct {
ID types.String `tfsdk:"id"`
Address types.String `tfsdk:"address"`
Description types.String `tfsdk:"description"`
FloatingNetworkID types.String `tfsdk:"floating_network_id"`
PortID types.String `tfsdk:"port_id"`
FixedIP types.String `tfsdk:"fixed_ip"`
Status types.String `tfsdk:"status"`
RouterID types.String `tfsdk:"router_id"`
TenantID types.String `tfsdk:"tenant_id"`
Region types.String `tfsdk:"region"`
}

func (d *floatingIPDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_networking_floatingip"
}

func (d *floatingIPDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Look up a floating IP by address or filters. Exactly one floating IP must match.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{Computed: true, MarkdownDescription: "The floating IP ID."},
"address": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Filter by (and report) the floating IP address."},
"description": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Filter by (and report) the description."},
"floating_network_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Filter by (and report) the external network ID."},
"port_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Filter by (and report) the associated port."},
"fixed_ip": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Filter by (and report) the mapped fixed IP."},
"status": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Filter by (and report) the operational status."},
"router_id": schema.StringAttribute{Computed: true, MarkdownDescription: "The router through which the floating IP is routed."},
"tenant_id": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "Filter by (and report) the owning project."},
"region": schema.StringAttribute{Optional: true, Computed: true, MarkdownDescription: "The region. Defaults to the provider's region."},
},
}
}

func (d *floatingIPDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
d.config = configureClient(req.ProviderData, &resp.Diagnostics)
}

func (d *floatingIPDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data floatingIPDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

client, err := d.config.NetworkV2Client()
if err != nil {
resp.Diagnostics.AddError("networking: building v2 client", err.Error())
return
}

listOpts := floatingips.ListOpts{
FloatingIP: data.Address.ValueString(),
Description: data.Description.ValueString(),
FloatingNetworkID: data.FloatingNetworkID.ValueString(),
PortID: data.PortID.ValueString(),
FixedIP: data.FixedIP.ValueString(),
Status: data.Status.ValueString(),
TenantID: data.TenantID.ValueString(),
}
pages, err := floatingips.List(client, listOpts).AllPages(ctx)
if err != nil {
resp.Diagnostics.AddError("networking: listing floating IPs", err.Error())
return
}
all, err := floatingips.ExtractFloatingIPs(pages)
if err != nil {
resp.Diagnostics.AddError("networking: extracting floating IPs", err.Error())
return
}

var fip *floatingips.FloatingIP
switch len(all) {
case 0:
resp.Diagnostics.AddError("No floating IP found", "No floating IP matched the given criteria.")
return
case 1:
fip = &all[0]
default:
resp.Diagnostics.AddError("Multiple floating IPs found",
fmt.Sprintf("%d floating IPs matched; refine the filters.", len(all)))
return
}

data.ID = types.StringValue(fip.ID)
data.Address = types.StringValue(fip.FloatingIP)
data.Description = types.StringValue(fip.Description)
data.FloatingNetworkID = types.StringValue(fip.FloatingNetworkID)
data.PortID = types.StringValue(fip.PortID)
data.FixedIP = types.StringValue(fip.FixedIP)
data.Status = types.StringValue(fip.Status)
data.RouterID = types.StringValue(fip.RouterID)
data.TenantID = types.StringValue(fip.TenantID)
if data.Region.IsNull() || data.Region.IsUnknown() {
data.Region = types.StringValue(d.config.Region)
}
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Loading
Loading