mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-08-27 13:55:41 +00:00
cleanup
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
bf19edb12d
commit
12cc3298e9
@ -44,6 +44,8 @@ linters:
|
|||||||
gosec:
|
gosec:
|
||||||
excludes:
|
excludes:
|
||||||
- G115
|
- G115
|
||||||
|
lll:
|
||||||
|
line-length: 150
|
||||||
revive:
|
revive:
|
||||||
rules:
|
rules:
|
||||||
- name: "package-comments"
|
- name: "package-comments"
|
||||||
|
@ -8,15 +8,19 @@ package zone
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"regexp"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
|
||||||
|
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,168 +28,131 @@ var (
|
|||||||
_ resource.ResourceWithImportState = &EVPNResource{}
|
_ resource.ResourceWithImportState = &EVPNResource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type evpnModel struct {
|
||||||
|
genericModel
|
||||||
|
|
||||||
|
AdvertiseSubnets types.Bool `tfsdk:"advertise_subnets"`
|
||||||
|
Controller types.String `tfsdk:"controller"`
|
||||||
|
DisableARPNDSuppression types.Bool `tfsdk:"disable_arp_nd_suppression"`
|
||||||
|
ExitNodes stringset.Value `tfsdk:"exit_nodes"`
|
||||||
|
ExitNodesLocalRouting types.Bool `tfsdk:"exit_nodes_local_routing"`
|
||||||
|
PrimaryExitNode types.String `tfsdk:"primary_exit_node"`
|
||||||
|
RouteTargetImport types.String `tfsdk:"rt_import"`
|
||||||
|
VRFVXLANID types.Int64 `tfsdk:"vrf_vxlan"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *evpnModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
||||||
|
m.genericModel.importFromAPI(name, data, diags)
|
||||||
|
|
||||||
|
m.AdvertiseSubnets = types.BoolPointerValue(data.AdvertiseSubnets.PointerBool())
|
||||||
|
m.Controller = types.StringPointerValue(data.Controller)
|
||||||
|
m.DisableARPNDSuppression = types.BoolPointerValue(data.DisableARPNDSuppression.PointerBool())
|
||||||
|
m.ExitNodes = stringset.NewValueString(data.ExitNodes, diags, stringset.WithSeparator(","))
|
||||||
|
m.ExitNodesLocalRouting = types.BoolPointerValue(data.ExitNodesLocalRouting.PointerBool())
|
||||||
|
m.PrimaryExitNode = types.StringPointerValue(data.ExitNodesPrimary)
|
||||||
|
m.RouteTargetImport = types.StringPointerValue(data.RouteTargetImport)
|
||||||
|
m.VRFVXLANID = types.Int64PointerValue(data.VRFVXLANID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *evpnModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
||||||
|
data := m.genericModel.toAPIRequestBody(ctx, diags)
|
||||||
|
|
||||||
|
data.AdvertiseSubnets = proxmoxtypes.CustomBoolPtr(m.AdvertiseSubnets.ValueBoolPointer())
|
||||||
|
data.Controller = m.Controller.ValueStringPointer()
|
||||||
|
data.DisableARPNDSuppression = proxmoxtypes.CustomBoolPtr(m.DisableARPNDSuppression.ValueBoolPointer())
|
||||||
|
data.ExitNodes = m.ExitNodes.ValueStringPointer(ctx, diags, stringset.WithSeparator(","))
|
||||||
|
data.ExitNodesLocalRouting = proxmoxtypes.CustomBoolPtr(m.ExitNodesLocalRouting.ValueBoolPointer())
|
||||||
|
data.ExitNodesPrimary = m.PrimaryExitNode.ValueStringPointer()
|
||||||
|
data.RouteTargetImport = m.RouteTargetImport.ValueStringPointer()
|
||||||
|
data.VRFVXLANID = m.VRFVXLANID.ValueInt64Pointer()
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
type EVPNResource struct {
|
type EVPNResource struct {
|
||||||
client *zones.Client
|
generic *genericZoneResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEVPNResource() resource.Resource {
|
func NewEVPNResource() resource.Resource {
|
||||||
return &EVPNResource{}
|
return &EVPNResource{
|
||||||
}
|
generic: newGenericZoneResource(zoneResourceConfig{
|
||||||
|
typeNameSuffix: "_sdn_zone_evpn",
|
||||||
func (r *EVPNResource) Metadata(
|
zoneType: zones.TypeEVPN,
|
||||||
_ context.Context,
|
modelFunc: func() zoneModel { return &evpnModel{} },
|
||||||
req resource.MetadataRequest,
|
}).(*genericZoneResource),
|
||||||
resp *resource.MetadataResponse,
|
|
||||||
) {
|
|
||||||
resp.TypeName = req.ProviderTypeName + "_sdn_zone_evpn"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EVPNResource) Configure(
|
|
||||||
_ context.Context,
|
|
||||||
req resource.ConfigureRequest,
|
|
||||||
resp *resource.ConfigureResponse,
|
|
||||||
) {
|
|
||||||
if req.ProviderData == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, ok := req.ProviderData.(config.Resource)
|
|
||||||
if !ok {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unexpected Resource Configure Type",
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Expected config.Resource, got: %T",
|
|
||||||
req.ProviderData,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.client = cfg.Client.Cluster().SDNZones()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EVPNResource) Create(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.CreateRequest,
|
|
||||||
resp *resource.CreateResponse,
|
|
||||||
) {
|
|
||||||
var plan evpnModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
reqData.Type = ptr.Ptr(zones.TypeEVPN)
|
|
||||||
|
|
||||||
if err := r.client.CreateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Create SDN EVPN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EVPNResource) Read(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.ReadRequest,
|
|
||||||
resp *resource.ReadResponse,
|
|
||||||
) {
|
|
||||||
var state evpnModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zone, err := r.client.GetZone(ctx, state.ID.ValueString())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.State.RemoveResource(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Read SDN EVPN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
readModel := &evpnModel{}
|
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EVPNResource) Update(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.UpdateRequest,
|
|
||||||
resp *resource.UpdateResponse,
|
|
||||||
) {
|
|
||||||
var plan evpnModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
|
|
||||||
if err := r.client.UpdateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Update SDN EVPN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EVPNResource) Delete(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.DeleteRequest,
|
|
||||||
resp *resource.DeleteResponse,
|
|
||||||
) {
|
|
||||||
var state evpnModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.client.DeleteZone(ctx, state.ID.ValueString()); err != nil &&
|
|
||||||
!errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Delete SDN EVPN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *EVPNResource) ImportState(
|
func (r *EVPNResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
ctx context.Context,
|
resp.Schema = schema.Schema{
|
||||||
req resource.ImportStateRequest,
|
Description: "EVPN Zone in Proxmox SDN.",
|
||||||
resp *resource.ImportStateResponse,
|
MarkdownDescription: "EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of " +
|
||||||
) {
|
"spanning across multiple clusters.",
|
||||||
zone, err := r.client.GetZone(ctx, req.ID)
|
Attributes: genericAttributesWith(map[string]schema.Attribute{
|
||||||
if err != nil {
|
"advertise_subnets": schema.BoolAttribute{
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
Optional: true,
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Zone %s does not exist", req.ID), err.Error())
|
Description: "Enable subnet advertisement for EVPN.",
|
||||||
return
|
},
|
||||||
}
|
"controller": schema.StringAttribute{
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Unable to Import SDN EVPN Zone %s", req.ID), err.Error())
|
Optional: true,
|
||||||
return
|
Description: "EVPN controller address.",
|
||||||
|
},
|
||||||
|
"disable_arp_nd_suppression": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Disable ARP/ND suppression for EVPN.",
|
||||||
|
},
|
||||||
|
"exit_nodes": stringset.ResourceAttribute("List of exit nodes for EVPN.", ""),
|
||||||
|
"exit_nodes_local_routing": schema.BoolAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Enable local routing for EVPN exit nodes.",
|
||||||
|
},
|
||||||
|
"primary_exit_node": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Primary exit node for EVPN.",
|
||||||
|
},
|
||||||
|
"rt_import": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Route target import for EVPN.",
|
||||||
|
Validators: []validator.String{
|
||||||
|
stringvalidator.RegexMatches(
|
||||||
|
regexp.MustCompile(`^(\d+):(\d+)$`),
|
||||||
|
"must be in the format '<ASN>:<number>' (e.g., '65000:65000')",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"vrf_vxlan": schema.Int64Attribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "VRF VXLAN-ID used for dedicated routing interconnect between VNets. It must be different " +
|
||||||
|
"than the VXLAN-ID of the VNets.",
|
||||||
|
},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
readModel := &evpnModel{}
|
}
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
func (r *EVPNResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
|
r.generic.Metadata(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EVPNResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
|
r.generic.Configure(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EVPNResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
|
r.generic.Create(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EVPNResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
|
r.generic.Read(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EVPNResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
r.generic.Update(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EVPNResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
r.generic.Delete(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EVPNResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
r.generic.ImportState(ctx, req, resp)
|
||||||
}
|
}
|
||||||
|
294
fwprovider/cluster/sdn/zone/resource_generic.go
Normal file
294
fwprovider/cluster/sdn/zone/resource_generic.go
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package zone
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type genericModel struct {
|
||||||
|
ID types.String `tfsdk:"id"`
|
||||||
|
IPAM types.String `tfsdk:"ipam"`
|
||||||
|
DNS types.String `tfsdk:"dns"`
|
||||||
|
ReverseDNS types.String `tfsdk:"reverse_dns"`
|
||||||
|
DNSZone types.String `tfsdk:"dns_zone"`
|
||||||
|
Nodes stringset.Value `tfsdk:"nodes"`
|
||||||
|
MTU types.Int64 `tfsdk:"mtu"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *genericModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
||||||
|
m.ID = types.StringValue(name)
|
||||||
|
|
||||||
|
m.DNS = types.StringPointerValue(data.DNS)
|
||||||
|
m.DNSZone = types.StringPointerValue(data.DNSZone)
|
||||||
|
m.IPAM = types.StringPointerValue(data.IPAM)
|
||||||
|
m.MTU = types.Int64PointerValue(data.MTU)
|
||||||
|
m.Nodes = stringset.NewValueString(data.Nodes, diags, stringset.WithSeparator(","))
|
||||||
|
m.ReverseDNS = types.StringPointerValue(data.ReverseDNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *genericModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
||||||
|
data := &zones.ZoneRequestData{}
|
||||||
|
|
||||||
|
data.ID = m.ID.ValueString()
|
||||||
|
|
||||||
|
data.IPAM = m.IPAM.ValueStringPointer()
|
||||||
|
data.DNS = m.DNS.ValueStringPointer()
|
||||||
|
data.ReverseDNS = m.ReverseDNS.ValueStringPointer()
|
||||||
|
data.DNSZone = m.DNSZone.ValueStringPointer()
|
||||||
|
data.Nodes = m.Nodes.ValueStringPointer(ctx, diags, stringset.WithSeparator(","))
|
||||||
|
data.MTU = m.MTU.ValueInt64Pointer()
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *genericModel) getID() string {
|
||||||
|
return m.ID.ValueString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func genericAttributesWith(extraAttributes ...map[string]schema.Attribute) map[string]schema.Attribute {
|
||||||
|
if len(extraAttributes) > 1 {
|
||||||
|
panic("genericAttributesWith expects at most one extraAttributes map")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(extraAttributes) == 0 {
|
||||||
|
extraAttributes = append(extraAttributes, make(map[string]schema.Attribute))
|
||||||
|
}
|
||||||
|
|
||||||
|
maps.Copy(extraAttributes[0], map[string]schema.Attribute{
|
||||||
|
"dns": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "DNS API server address.",
|
||||||
|
},
|
||||||
|
"dns_zone": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "DNS domain name. The DNS zone must already exist on the DNS server.",
|
||||||
|
MarkdownDescription: "DNS domain name. Used to register hostnames, such as `<hostname>.<domain>`. " +
|
||||||
|
"The DNS zone must already exist on the DNS server.",
|
||||||
|
},
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
Description: "The unique identifier of the SDN zone.",
|
||||||
|
Required: true,
|
||||||
|
PlanModifiers: []planmodifier.String{
|
||||||
|
stringplanmodifier.RequiresReplace(),
|
||||||
|
},
|
||||||
|
Validators: []validator.String{
|
||||||
|
// https://github.com/proxmox/pve-network/blob/faaf96a8378a3e41065018562c09c3de0aa434f5/src/PVE/Network/SDN/Zones/Plugin.pm#L34
|
||||||
|
stringvalidator.RegexMatches(
|
||||||
|
regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]*[A-Za-z0-9]$`),
|
||||||
|
"must be a valid zone identifier",
|
||||||
|
),
|
||||||
|
stringvalidator.LengthAtMost(8),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ipam": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "IP Address Management system.",
|
||||||
|
},
|
||||||
|
"mtu": schema.Int64Attribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "MTU value for the zone.",
|
||||||
|
},
|
||||||
|
"nodes": stringset.ResourceAttribute("Proxmox node names.", ""),
|
||||||
|
"reverse_dns": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Reverse DNS API server address.",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return extraAttributes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
type zoneModel interface {
|
||||||
|
importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics)
|
||||||
|
toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData
|
||||||
|
getID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type zoneResourceConfig struct {
|
||||||
|
typeNameSuffix string
|
||||||
|
zoneType string
|
||||||
|
modelFunc func() zoneModel
|
||||||
|
}
|
||||||
|
|
||||||
|
type genericZoneResource struct {
|
||||||
|
client *zones.Client
|
||||||
|
config zoneResourceConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenericZoneResource(cfg zoneResourceConfig) resource.Resource {
|
||||||
|
return &genericZoneResource{config: cfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *genericZoneResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + r.config.typeNameSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *genericZoneResource) Configure(
|
||||||
|
_ context.Context,
|
||||||
|
req resource.ConfigureRequest,
|
||||||
|
resp *resource.ConfigureResponse,
|
||||||
|
) {
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, ok := req.ProviderData.(config.Resource)
|
||||||
|
if !ok {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unexpected Resource Configure Type",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Expected config.Resource, got: %T",
|
||||||
|
req.ProviderData,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.client = cfg.Client.Cluster().SDNZones()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *genericZoneResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
|
plan := r.config.modelFunc()
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diags := &diag.Diagnostics{}
|
||||||
|
reqData := plan.toAPIRequestBody(ctx, diags)
|
||||||
|
resp.Diagnostics.Append(*diags...)
|
||||||
|
|
||||||
|
reqData.Type = ptr.Ptr(r.config.zoneType)
|
||||||
|
|
||||||
|
if err := r.client.CreateZone(ctx, reqData); err != nil {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unable to Create SDN Zone",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *genericZoneResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
|
state := r.config.modelFunc()
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zone, err := r.client.GetZone(ctx, state.getID())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unable to Read SDN Zone",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := r.config.modelFunc()
|
||||||
|
diags := &diag.Diagnostics{}
|
||||||
|
readModel.importFromAPI(zone.ID, zone, diags)
|
||||||
|
resp.Diagnostics.Append(*diags...)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *genericZoneResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
plan := r.config.modelFunc()
|
||||||
|
resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diags := &diag.Diagnostics{}
|
||||||
|
reqData := plan.toAPIRequestBody(ctx, diags)
|
||||||
|
resp.Diagnostics.Append(*diags...)
|
||||||
|
|
||||||
|
if err := r.client.UpdateZone(ctx, reqData); err != nil {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unable to Update SDN Zone",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *genericZoneResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
state := r.config.modelFunc()
|
||||||
|
resp.Diagnostics.Append(req.State.Get(ctx, state)...)
|
||||||
|
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.client.DeleteZone(ctx, state.getID()); err != nil &&
|
||||||
|
!errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError(
|
||||||
|
"Unable to Delete SDN Zone",
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *genericZoneResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
zone, err := r.client.GetZone(ctx, req.ID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
||||||
|
resp.Diagnostics.AddError(fmt.Sprintf("Zone %s does not exist", req.ID), err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.AddError(fmt.Sprintf("Unable to Import SDN Zone %s", req.ID), err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
readModel := r.config.modelFunc()
|
||||||
|
diags := &diag.Diagnostics{}
|
||||||
|
readModel.importFromAPI(zone.ID, zone, diags)
|
||||||
|
resp.Diagnostics.Append(*diags...)
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schema is required to satisfy the resource.Resource interface. It should be implemented by the specific resource.
|
||||||
|
func (r *genericZoneResource) Schema(_ context.Context, _ resource.SchemaRequest, _ *resource.SchemaResponse) {
|
||||||
|
// Intentionally left blank. Should be set by the specific resource.
|
||||||
|
}
|
@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package zone
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
|
||||||
|
|
||||||
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type baseModel struct {
|
|
||||||
ID types.String `tfsdk:"id"`
|
|
||||||
IPAM types.String `tfsdk:"ipam"`
|
|
||||||
DNS types.String `tfsdk:"dns"`
|
|
||||||
ReverseDNS types.String `tfsdk:"reverse_dns"`
|
|
||||||
DNSZone types.String `tfsdk:"dns_zone"`
|
|
||||||
Nodes stringset.Value `tfsdk:"nodes"`
|
|
||||||
MTU types.Int64 `tfsdk:"mtu"`
|
|
||||||
// // VLAN.
|
|
||||||
// Bridge types.String `tfsdk:"bridge"`
|
|
||||||
// // QinQ.
|
|
||||||
// ServiceVLAN types.Int64 `tfsdk:"service_vlan"`
|
|
||||||
// ServiceVLANProtocol types.String `tfsdk:"service_vlan_protocol"`
|
|
||||||
// // VXLAN.
|
|
||||||
// Peers stringset.Value `tfsdk:"peers"`
|
|
||||||
// // EVPN.
|
|
||||||
// Controller types.String `tfsdk:"controller"`
|
|
||||||
// ExitNodes stringset.Value `tfsdk:"exit_nodes"`
|
|
||||||
// PrimaryExitNode types.String `tfsdk:"primary_exit_node"`
|
|
||||||
// RouteTargetImport types.String `tfsdk:"rt_import"`
|
|
||||||
// VRFVXLANID types.Int64 `tfsdk:"vrf_vxlan"`
|
|
||||||
// ExitNodesLocalRouting types.Bool `tfsdk:"exit_nodes_local_routing"`
|
|
||||||
// AdvertiseSubnets types.Bool `tfsdk:"advertise_subnets"`
|
|
||||||
// DisableARPNDSuppression types.Bool `tfsdk:"disable_arp_nd_suppression"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *baseModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
|
||||||
m.ID = types.StringValue(name)
|
|
||||||
|
|
||||||
m.DNS = types.StringPointerValue(data.DNS)
|
|
||||||
m.DNSZone = types.StringPointerValue(data.DNSZone)
|
|
||||||
m.IPAM = types.StringPointerValue(data.IPAM)
|
|
||||||
m.MTU = types.Int64PointerValue(data.MTU)
|
|
||||||
m.Nodes = stringset.NewValueString(data.Nodes, diags, stringset.WithSeparator(","))
|
|
||||||
m.ReverseDNS = types.StringPointerValue(data.ReverseDNS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *baseModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
|
||||||
data := &zones.ZoneRequestData{}
|
|
||||||
|
|
||||||
data.ID = m.ID.ValueString()
|
|
||||||
|
|
||||||
data.IPAM = m.IPAM.ValueStringPointer()
|
|
||||||
data.DNS = m.DNS.ValueStringPointer()
|
|
||||||
data.ReverseDNS = m.ReverseDNS.ValueStringPointer()
|
|
||||||
data.DNSZone = m.DNSZone.ValueStringPointer()
|
|
||||||
data.Nodes = m.Nodes.ValueStringPointer(ctx, diags, stringset.WithSeparator(","))
|
|
||||||
data.MTU = m.MTU.ValueInt64Pointer()
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleModel struct {
|
|
||||||
baseModel
|
|
||||||
}
|
|
||||||
|
|
||||||
type vlanModel struct {
|
|
||||||
baseModel
|
|
||||||
|
|
||||||
Bridge types.String `tfsdk:"bridge"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *vlanModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
|
||||||
m.baseModel.importFromAPI(name, data, diags)
|
|
||||||
|
|
||||||
m.Bridge = types.StringPointerValue(data.Bridge)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *vlanModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
|
||||||
data := m.baseModel.toAPIRequestBody(ctx, diags)
|
|
||||||
|
|
||||||
data.Bridge = m.Bridge.ValueStringPointer()
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
type qinqModel struct {
|
|
||||||
vlanModel
|
|
||||||
|
|
||||||
ServiceVLAN types.Int64 `tfsdk:"service_vlan"`
|
|
||||||
ServiceVLANProtocol types.String `tfsdk:"service_vlan_protocol"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *qinqModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
|
||||||
m.vlanModel.importFromAPI(name, data, diags)
|
|
||||||
|
|
||||||
m.ServiceVLAN = types.Int64PointerValue(data.ServiceVLAN)
|
|
||||||
m.ServiceVLANProtocol = types.StringPointerValue(data.ServiceVLANProtocol)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *qinqModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
|
||||||
data := m.vlanModel.toAPIRequestBody(ctx, diags)
|
|
||||||
|
|
||||||
data.ServiceVLAN = m.ServiceVLAN.ValueInt64Pointer()
|
|
||||||
data.ServiceVLANProtocol = m.ServiceVLANProtocol.ValueStringPointer()
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
type vxlanModel struct {
|
|
||||||
baseModel
|
|
||||||
|
|
||||||
Peers stringset.Value `tfsdk:"peers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *vxlanModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
|
||||||
m.baseModel.importFromAPI(name, data, diags)
|
|
||||||
m.Peers = stringset.NewValueString(data.Peers, diags, stringset.WithSeparator(","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *vxlanModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
|
||||||
data := m.baseModel.toAPIRequestBody(ctx, diags)
|
|
||||||
|
|
||||||
data.Peers = m.Peers.ValueStringPointer(ctx, diags, stringset.WithSeparator(","))
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
type evpnModel struct {
|
|
||||||
baseModel
|
|
||||||
|
|
||||||
AdvertiseSubnets types.Bool `tfsdk:"advertise_subnets"`
|
|
||||||
Controller types.String `tfsdk:"controller"`
|
|
||||||
DisableARPNDSuppression types.Bool `tfsdk:"disable_arp_nd_suppression"`
|
|
||||||
ExitNodes stringset.Value `tfsdk:"exit_nodes"`
|
|
||||||
ExitNodesLocalRouting types.Bool `tfsdk:"exit_nodes_local_routing"`
|
|
||||||
PrimaryExitNode types.String `tfsdk:"primary_exit_node"`
|
|
||||||
RouteTargetImport types.String `tfsdk:"rt_import"`
|
|
||||||
VRFVXLANID types.Int64 `tfsdk:"vrf_vxlan"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *evpnModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
|
||||||
m.baseModel.importFromAPI(name, data, diags)
|
|
||||||
|
|
||||||
m.AdvertiseSubnets = types.BoolPointerValue(data.AdvertiseSubnets.PointerBool())
|
|
||||||
m.Controller = types.StringPointerValue(data.Controller)
|
|
||||||
m.DisableARPNDSuppression = types.BoolPointerValue(data.DisableARPNDSuppression.PointerBool())
|
|
||||||
m.ExitNodes = stringset.NewValueString(data.ExitNodes, diags, stringset.WithSeparator(","))
|
|
||||||
m.ExitNodesLocalRouting = types.BoolPointerValue(data.ExitNodesLocalRouting.PointerBool())
|
|
||||||
m.PrimaryExitNode = types.StringPointerValue(data.ExitNodesPrimary)
|
|
||||||
m.RouteTargetImport = types.StringPointerValue(data.RouteTargetImport)
|
|
||||||
m.VRFVXLANID = types.Int64PointerValue(data.VRFVXLANID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *evpnModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
|
||||||
data := m.baseModel.toAPIRequestBody(ctx, diags)
|
|
||||||
|
|
||||||
data.AdvertiseSubnets = proxmoxtypes.CustomBoolPtr(m.AdvertiseSubnets.ValueBoolPointer())
|
|
||||||
data.Controller = m.Controller.ValueStringPointer()
|
|
||||||
data.DisableARPNDSuppression = proxmoxtypes.CustomBoolPtr(m.DisableARPNDSuppression.ValueBoolPointer())
|
|
||||||
data.ExitNodes = m.ExitNodes.ValueStringPointer(ctx, diags, stringset.WithSeparator(","))
|
|
||||||
data.ExitNodesLocalRouting = proxmoxtypes.CustomBoolPtr(m.ExitNodesLocalRouting.ValueBoolPointer())
|
|
||||||
data.ExitNodesPrimary = m.PrimaryExitNode.ValueStringPointer()
|
|
||||||
data.RouteTargetImport = m.RouteTargetImport.ValueStringPointer()
|
|
||||||
data.VRFVXLANID = m.VRFVXLANID.ValueInt64Pointer()
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
@ -8,15 +8,16 @@ package zone
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,168 +25,98 @@ var (
|
|||||||
_ resource.ResourceWithImportState = &QinQResource{}
|
_ resource.ResourceWithImportState = &QinQResource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type qinqModel struct {
|
||||||
|
vlanModel
|
||||||
|
|
||||||
|
ServiceVLAN types.Int64 `tfsdk:"service_vlan"`
|
||||||
|
ServiceVLANProtocol types.String `tfsdk:"service_vlan_protocol"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *qinqModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
||||||
|
m.vlanModel.importFromAPI(name, data, diags)
|
||||||
|
|
||||||
|
m.ServiceVLAN = types.Int64PointerValue(data.ServiceVLAN)
|
||||||
|
m.ServiceVLANProtocol = types.StringPointerValue(data.ServiceVLANProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *qinqModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
||||||
|
data := m.vlanModel.toAPIRequestBody(ctx, diags)
|
||||||
|
|
||||||
|
data.ServiceVLAN = m.ServiceVLAN.ValueInt64Pointer()
|
||||||
|
data.ServiceVLANProtocol = m.ServiceVLANProtocol.ValueStringPointer()
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
type QinQResource struct {
|
type QinQResource struct {
|
||||||
client *zones.Client
|
generic *genericZoneResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQinQResource() resource.Resource {
|
func NewQinQResource() resource.Resource {
|
||||||
return &QinQResource{}
|
return &QinQResource{
|
||||||
}
|
generic: newGenericZoneResource(zoneResourceConfig{
|
||||||
|
typeNameSuffix: "_sdn_zone_qinq",
|
||||||
func (r *QinQResource) Metadata(
|
zoneType: zones.TypeQinQ,
|
||||||
_ context.Context,
|
modelFunc: func() zoneModel { return &qinqModel{} },
|
||||||
req resource.MetadataRequest,
|
}).(*genericZoneResource),
|
||||||
resp *resource.MetadataResponse,
|
|
||||||
) {
|
|
||||||
resp.TypeName = req.ProviderTypeName + "_sdn_zone_qinq"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QinQResource) Configure(
|
|
||||||
_ context.Context,
|
|
||||||
req resource.ConfigureRequest,
|
|
||||||
resp *resource.ConfigureResponse,
|
|
||||||
) {
|
|
||||||
if req.ProviderData == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, ok := req.ProviderData.(config.Resource)
|
|
||||||
if !ok {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unexpected Resource Configure Type",
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Expected config.Resource, got: %T",
|
|
||||||
req.ProviderData,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.client = cfg.Client.Cluster().SDNZones()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QinQResource) Create(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.CreateRequest,
|
|
||||||
resp *resource.CreateResponse,
|
|
||||||
) {
|
|
||||||
var plan qinqModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
reqData.Type = ptr.Ptr(zones.TypeQinQ)
|
|
||||||
|
|
||||||
if err := r.client.CreateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Create SDN QinQ Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QinQResource) Read(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.ReadRequest,
|
|
||||||
resp *resource.ReadResponse,
|
|
||||||
) {
|
|
||||||
var state qinqModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zone, err := r.client.GetZone(ctx, state.ID.ValueString())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.State.RemoveResource(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Read SDN QinQ Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
readModel := &qinqModel{}
|
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QinQResource) Update(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.UpdateRequest,
|
|
||||||
resp *resource.UpdateResponse,
|
|
||||||
) {
|
|
||||||
var plan qinqModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
|
|
||||||
if err := r.client.UpdateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Update SDN QinQ Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QinQResource) Delete(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.DeleteRequest,
|
|
||||||
resp *resource.DeleteResponse,
|
|
||||||
) {
|
|
||||||
var state qinqModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.client.DeleteZone(ctx, state.ID.ValueString()); err != nil &&
|
|
||||||
!errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Delete SDN QinQ Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *QinQResource) ImportState(
|
func (r *QinQResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
ctx context.Context,
|
resp.Schema = schema.Schema{
|
||||||
req resource.ImportStateRequest,
|
Description: "QinQ Zone in Proxmox SDN.",
|
||||||
resp *resource.ImportStateResponse,
|
MarkdownDescription: "QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of " +
|
||||||
) {
|
"VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner " +
|
||||||
zone, err := r.client.GetZone(ctx, req.ID)
|
"VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this " +
|
||||||
if err != nil {
|
"configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. " +
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
"For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500.",
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Zone %s does not exist", req.ID), err.Error())
|
Attributes: genericAttributesWith(map[string]schema.Attribute{
|
||||||
return
|
"bridge": schema.StringAttribute{
|
||||||
}
|
Description: "A local, VLAN-aware bridge that is already configured on each local node",
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Unable to Import SDN QinQ Zone %s", req.ID), err.Error())
|
Optional: true,
|
||||||
return
|
},
|
||||||
|
"service_vlan": schema.Int64Attribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Service VLAN tag for QinQ.",
|
||||||
|
Validators: []validator.Int64{
|
||||||
|
int64validator.Between(int64(1), int64(4094)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"service_vlan_protocol": schema.StringAttribute{
|
||||||
|
Optional: true,
|
||||||
|
Description: "Service VLAN protocol for QinQ.",
|
||||||
|
Validators: []validator.String{
|
||||||
|
stringvalidator.OneOf("802.1ad", "802.1q"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
readModel := &qinqModel{}
|
}
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
func (r *QinQResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
|
r.generic.Metadata(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QinQResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
|
r.generic.Configure(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QinQResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
|
r.generic.Create(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QinQResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
|
r.generic.Read(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QinQResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
r.generic.Update(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QinQResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
r.generic.Delete(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QinQResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
r.generic.ImportState(ctx, req, resp)
|
||||||
}
|
}
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
//go:build acceptance || all
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package zone_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAccResourceSDNZoneQinQ(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
te := test.InitEnvironment(t)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
steps []resource.TestStep
|
|
||||||
}{
|
|
||||||
{"create and update QinQ zone", []resource.TestStep{{
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_qinq" "zone_qinq" {
|
|
||||||
id = "zoneQ"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1496
|
|
||||||
bridge = "vmbr0"
|
|
||||||
service_vlan = 100
|
|
||||||
service_vlan_protocol = "802.1ad"
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
}, {
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_qinq" "zone_qinq" {
|
|
||||||
id = "zoneQ"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1495
|
|
||||||
bridge = "vmbr0"
|
|
||||||
service_vlan = 200
|
|
||||||
service_vlan_protocol = "802.1q"
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
ResourceName: "proxmox_virtual_environment_sdn_zone_qinq.zone_qinq",
|
|
||||||
ImportStateId: "zoneQ",
|
|
||||||
ImportState: true,
|
|
||||||
ImportStateVerify: true,
|
|
||||||
}}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
resource.ParallelTest(t, resource.TestCase{
|
|
||||||
ProtoV6ProviderFactories: te.AccProviders,
|
|
||||||
Steps: tt.steps,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package zone
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"maps"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
|
||||||
)
|
|
||||||
|
|
||||||
func baseAttributesWith(extraAttributes ...map[string]schema.Attribute) map[string]schema.Attribute {
|
|
||||||
if len(extraAttributes) > 1 {
|
|
||||||
panic("baseAttributesWith expects at most one extraAttributes map")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(extraAttributes) == 0 {
|
|
||||||
extraAttributes = append(extraAttributes, make(map[string]schema.Attribute))
|
|
||||||
}
|
|
||||||
|
|
||||||
maps.Copy(extraAttributes[0], map[string]schema.Attribute{
|
|
||||||
"dns": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "DNS API server address.",
|
|
||||||
},
|
|
||||||
"dns_zone": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "DNS domain name. The DNS zone must already exist on the DNS server.",
|
|
||||||
MarkdownDescription: "DNS domain name. Used to register hostnames, such as `<hostname>.<domain>`. " +
|
|
||||||
"The DNS zone must already exist on the DNS server.",
|
|
||||||
},
|
|
||||||
"id": schema.StringAttribute{
|
|
||||||
Description: "The unique identifier of the SDN zone.",
|
|
||||||
Required: true,
|
|
||||||
PlanModifiers: []planmodifier.String{
|
|
||||||
stringplanmodifier.RequiresReplace(),
|
|
||||||
},
|
|
||||||
Validators: []validator.String{
|
|
||||||
// https://github.com/proxmox/pve-network/blob/faaf96a8378a3e41065018562c09c3de0aa434f5/src/PVE/Network/SDN/Zones/Plugin.pm#L34
|
|
||||||
stringvalidator.RegexMatches(
|
|
||||||
regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]*[A-Za-z0-9]$`),
|
|
||||||
"must be a valid zone identifier",
|
|
||||||
),
|
|
||||||
stringvalidator.LengthAtMost(8),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ipam": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "IP Address Management system.",
|
|
||||||
},
|
|
||||||
"mtu": schema.Int64Attribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "MTU value for the zone.",
|
|
||||||
},
|
|
||||||
"nodes": stringset.ResourceAttribute("Proxmox node names.", ""),
|
|
||||||
"reverse_dns": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Reverse DNS API server address.",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return extraAttributes[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SimpleResource) Schema(
|
|
||||||
_ context.Context,
|
|
||||||
_ resource.SchemaRequest,
|
|
||||||
resp *resource.SchemaResponse,
|
|
||||||
) {
|
|
||||||
resp.Schema = schema.Schema{
|
|
||||||
Description: "Simple Zone in Proxmox SDN.",
|
|
||||||
MarkdownDescription: "Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. " +
|
|
||||||
"This bridge is not linked to a physical interface, and VM traffic is only local on each the node. " +
|
|
||||||
"It can be used in NAT or routed setups.",
|
|
||||||
Attributes: baseAttributesWith(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VLANResource) Schema(
|
|
||||||
_ context.Context,
|
|
||||||
_ resource.SchemaRequest,
|
|
||||||
resp *resource.SchemaResponse,
|
|
||||||
) {
|
|
||||||
resp.Schema = schema.Schema{
|
|
||||||
Description: "VLAN Zone in Proxmox SDN.",
|
|
||||||
MarkdownDescription: "VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the " +
|
|
||||||
"node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. " +
|
|
||||||
"This allows connectivity of VMs between different nodes.",
|
|
||||||
Attributes: baseAttributesWith(map[string]schema.Attribute{
|
|
||||||
"bridge": schema.StringAttribute{
|
|
||||||
Description: "Bridge interface for VLAN.",
|
|
||||||
MarkdownDescription: "The local bridge or OVS switch, already configured on _each_ node that allows " +
|
|
||||||
"node-to-node connection.",
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QinQResource) Schema(
|
|
||||||
_ context.Context,
|
|
||||||
_ resource.SchemaRequest,
|
|
||||||
resp *resource.SchemaResponse,
|
|
||||||
) {
|
|
||||||
resp.Schema = schema.Schema{
|
|
||||||
Description: "QinQ Zone in Proxmox SDN.",
|
|
||||||
MarkdownDescription: "QinQ Zone in Proxmox SDN. QinQ also known as VLAN stacking, that uses multiple layers of " +
|
|
||||||
"VLAN tags for isolation. The QinQ zone defines the outer VLAN tag (the Service VLAN) whereas the inner " +
|
|
||||||
"VLAN tag is defined by the VNet. Your physical network switches must support stacked VLANs for this " +
|
|
||||||
"configuration. Due to the double stacking of tags, you need 4 more bytes for QinQ VLANs. " +
|
|
||||||
"For example, you must reduce the MTU to 1496 if you physical interface MTU is 1500.",
|
|
||||||
Attributes: baseAttributesWith(map[string]schema.Attribute{
|
|
||||||
"bridge": schema.StringAttribute{
|
|
||||||
Description: "A local, VLAN-aware bridge that is already configured on each local node",
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
"service_vlan": schema.Int64Attribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Service VLAN tag for QinQ.",
|
|
||||||
Validators: []validator.Int64{
|
|
||||||
int64validator.Between(int64(1), int64(4094)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"service_vlan_protocol": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Service VLAN protocol for QinQ.",
|
|
||||||
Validators: []validator.String{
|
|
||||||
stringvalidator.OneOf("802.1ad", "802.1q"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VXLANResource) Schema(
|
|
||||||
_ context.Context,
|
|
||||||
_ resource.SchemaRequest,
|
|
||||||
resp *resource.SchemaResponse,
|
|
||||||
) {
|
|
||||||
resp.Schema = schema.Schema{
|
|
||||||
Description: "VXLAN Zone in Proxmox SDN.",
|
|
||||||
MarkdownDescription: "VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network " +
|
|
||||||
"(underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default " +
|
|
||||||
"destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity " +
|
|
||||||
"between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the " +
|
|
||||||
"outgoing physical interface.",
|
|
||||||
Attributes: baseAttributesWith(map[string]schema.Attribute{
|
|
||||||
"peers": stringset.ResourceAttribute(
|
|
||||||
"A list of IP addresses of each node in the VXLAN zone.",
|
|
||||||
"A list of IP addresses of each node in the VXLAN zone. "+
|
|
||||||
"This can be external nodes reachable at this IP address. All nodes in the cluster need to be "+
|
|
||||||
"mentioned here",
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EVPNResource) Schema(
|
|
||||||
_ context.Context,
|
|
||||||
_ resource.SchemaRequest,
|
|
||||||
resp *resource.SchemaResponse,
|
|
||||||
) {
|
|
||||||
resp.Schema = schema.Schema{
|
|
||||||
Description: "EVPN Zone in Proxmox SDN.",
|
|
||||||
MarkdownDescription: "EVPN Zone in Proxmox SDN. The EVPN zone creates a routable Layer 3 network, capable of " +
|
|
||||||
"spanning across multiple clusters.",
|
|
||||||
Attributes: baseAttributesWith(map[string]schema.Attribute{
|
|
||||||
"advertise_subnets": schema.BoolAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Enable subnet advertisement for EVPN.",
|
|
||||||
},
|
|
||||||
"controller": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "EVPN controller address.",
|
|
||||||
},
|
|
||||||
"disable_arp_nd_suppression": schema.BoolAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Disable ARP/ND suppression for EVPN.",
|
|
||||||
},
|
|
||||||
"exit_nodes": stringset.ResourceAttribute("List of exit nodes for EVPN.", ""),
|
|
||||||
"exit_nodes_local_routing": schema.BoolAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Enable local routing for EVPN exit nodes.",
|
|
||||||
},
|
|
||||||
"primary_exit_node": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Primary exit node for EVPN.",
|
|
||||||
},
|
|
||||||
"rt_import": schema.StringAttribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "Route target import for EVPN.",
|
|
||||||
Validators: []validator.String{
|
|
||||||
stringvalidator.RegexMatches(
|
|
||||||
regexp.MustCompile(`^(\d+):(\d+)$`),
|
|
||||||
"must be in the format '<ASN>:<number>' (e.g., '65000:65000')",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"vrf_vxlan": schema.Int64Attribute{
|
|
||||||
Optional: true,
|
|
||||||
Description: "VRF VXLAN-ID used for dedicated routing interconnect between VNets. It must be different " +
|
|
||||||
"than the VXLAN-ID of the VNets.",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,15 +8,11 @@ package zone
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,176 +20,58 @@ var (
|
|||||||
_ resource.ResourceWithImportState = &SimpleResource{}
|
_ resource.ResourceWithImportState = &SimpleResource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type simpleModel struct {
|
||||||
|
genericModel
|
||||||
|
}
|
||||||
|
|
||||||
type SimpleResource struct {
|
type SimpleResource struct {
|
||||||
client *zones.Client
|
generic *genericZoneResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSimpleResource() resource.Resource {
|
func NewSimpleResource() resource.Resource {
|
||||||
return &SimpleResource{}
|
return &SimpleResource{
|
||||||
}
|
generic: newGenericZoneResource(zoneResourceConfig{
|
||||||
|
typeNameSuffix: "_sdn_zone_simple",
|
||||||
func (r *SimpleResource) Metadata(
|
zoneType: zones.TypeSimple,
|
||||||
_ context.Context,
|
modelFunc: func() zoneModel { return &simpleModel{} },
|
||||||
req resource.MetadataRequest,
|
}).(*genericZoneResource),
|
||||||
resp *resource.MetadataResponse,
|
|
||||||
) {
|
|
||||||
resp.TypeName = req.ProviderTypeName + "_sdn_zone_simple"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SimpleResource) Configure(
|
|
||||||
_ context.Context,
|
|
||||||
req resource.ConfigureRequest,
|
|
||||||
resp *resource.ConfigureResponse,
|
|
||||||
) {
|
|
||||||
if req.ProviderData == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, ok := req.ProviderData.(config.Resource)
|
|
||||||
if !ok {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unexpected Resource Configure Type",
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Expected config.Resource, got: %T",
|
|
||||||
req.ProviderData,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.client = cfg.Client.Cluster().SDNZones()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SimpleResource) Create(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.CreateRequest,
|
|
||||||
resp *resource.CreateResponse,
|
|
||||||
) {
|
|
||||||
var plan simpleModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
reqData.Type = ptr.Ptr(zones.TypeSimple)
|
|
||||||
|
|
||||||
if err := r.client.CreateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Create SDN SimpleZone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SimpleResource) Read(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.ReadRequest,
|
|
||||||
resp *resource.ReadResponse,
|
|
||||||
) {
|
|
||||||
var state simpleModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zone, err := r.client.GetZone(ctx, state.ID.ValueString())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.State.RemoveResource(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Read SDN SimpleZone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
readModel := &baseModel{}
|
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SimpleResource) Update(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.UpdateRequest,
|
|
||||||
resp *resource.UpdateResponse,
|
|
||||||
) {
|
|
||||||
var plan simpleModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
|
|
||||||
if err := r.client.UpdateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Update SDN Simple Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *SimpleResource) Delete(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.DeleteRequest,
|
|
||||||
resp *resource.DeleteResponse,
|
|
||||||
) {
|
|
||||||
var state simpleModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.client.DeleteZone(ctx, state.ID.ValueString()); err != nil &&
|
|
||||||
!errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Delete SDN Simple Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SimpleResource) ImportState(
|
func (r *SimpleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
ctx context.Context,
|
resp.Schema = schema.Schema{
|
||||||
req resource.ImportStateRequest,
|
Description: "Simple Zone in Proxmox SDN.",
|
||||||
resp *resource.ImportStateResponse,
|
MarkdownDescription: "Simple Zone in Proxmox SDN. It will create an isolated VNet bridge. " +
|
||||||
) {
|
"This bridge is not linked to a physical interface, and VM traffic is only local on each the node. " +
|
||||||
zone, err := r.client.GetZone(ctx, req.ID)
|
"It can be used in NAT or routed setups.",
|
||||||
if err != nil {
|
Attributes: genericAttributesWith(),
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Zone %s does not exist", req.ID), err.Error())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Unable to Import SDN Simple Zone %s", req.ID), err.Error())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
readModel := &simpleModel{}
|
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
func (r *SimpleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
r.generic.Metadata(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SimpleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
|
r.generic.Configure(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SimpleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
|
r.generic.Create(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SimpleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
|
r.generic.Read(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SimpleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
r.generic.Update(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SimpleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
r.generic.Delete(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SimpleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
r.generic.ImportState(ctx, req, resp)
|
||||||
}
|
}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
//go:build acceptance || all
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package zone_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAccResourceSDNZoneSimple(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
te := test.InitEnvironment(t)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
steps []resource.TestStep
|
|
||||||
}{
|
|
||||||
{"create and update zones", []resource.TestStep{{
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_simple" "zone_simple" {
|
|
||||||
id = "zoneS"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1496
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
}, {
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_simple" "zone_simple" {
|
|
||||||
id = "zoneS"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1495
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
ResourceName: "proxmox_virtual_environment_sdn_zone_simple.zone_simple",
|
|
||||||
ImportStateId: "zoneS",
|
|
||||||
ImportState: true,
|
|
||||||
ImportStateVerify: true,
|
|
||||||
}}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
resource.ParallelTest(t, resource.TestCase{
|
|
||||||
ProtoV6ProviderFactories: te.AccProviders,
|
|
||||||
Steps: tt.steps,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,15 +8,13 @@ package zone
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,168 +22,81 @@ var (
|
|||||||
_ resource.ResourceWithImportState = &VLANResource{}
|
_ resource.ResourceWithImportState = &VLANResource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type vlanModel struct {
|
||||||
|
genericModel
|
||||||
|
|
||||||
|
Bridge types.String `tfsdk:"bridge"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *vlanModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
||||||
|
m.genericModel.importFromAPI(name, data, diags)
|
||||||
|
|
||||||
|
m.Bridge = types.StringPointerValue(data.Bridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *vlanModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
||||||
|
data := m.genericModel.toAPIRequestBody(ctx, diags)
|
||||||
|
|
||||||
|
data.Bridge = m.Bridge.ValueStringPointer()
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
type VLANResource struct {
|
type VLANResource struct {
|
||||||
client *zones.Client
|
generic *genericZoneResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVLANResource() resource.Resource {
|
func NewVLANResource() resource.Resource {
|
||||||
return &VLANResource{}
|
return &VLANResource{
|
||||||
}
|
generic: newGenericZoneResource(zoneResourceConfig{
|
||||||
|
typeNameSuffix: "_sdn_zone_vlan",
|
||||||
func (r *VLANResource) Metadata(
|
zoneType: zones.TypeVLAN,
|
||||||
_ context.Context,
|
modelFunc: func() zoneModel { return &vlanModel{} },
|
||||||
req resource.MetadataRequest,
|
}).(*genericZoneResource),
|
||||||
resp *resource.MetadataResponse,
|
|
||||||
) {
|
|
||||||
resp.TypeName = req.ProviderTypeName + "_sdn_zone_vlan"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VLANResource) Configure(
|
|
||||||
_ context.Context,
|
|
||||||
req resource.ConfigureRequest,
|
|
||||||
resp *resource.ConfigureResponse,
|
|
||||||
) {
|
|
||||||
if req.ProviderData == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, ok := req.ProviderData.(config.Resource)
|
|
||||||
if !ok {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unexpected Resource Configure Type",
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Expected config.Resource, got: %T",
|
|
||||||
req.ProviderData,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.client = cfg.Client.Cluster().SDNZones()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VLANResource) Create(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.CreateRequest,
|
|
||||||
resp *resource.CreateResponse,
|
|
||||||
) {
|
|
||||||
var plan vlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
reqData.Type = ptr.Ptr(zones.TypeVLAN)
|
|
||||||
|
|
||||||
if err := r.client.CreateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Create SDN VLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VLANResource) Read(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.ReadRequest,
|
|
||||||
resp *resource.ReadResponse,
|
|
||||||
) {
|
|
||||||
var state vlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zone, err := r.client.GetZone(ctx, state.ID.ValueString())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.State.RemoveResource(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Read SDN VLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
readModel := &vlanModel{}
|
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VLANResource) Update(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.UpdateRequest,
|
|
||||||
resp *resource.UpdateResponse,
|
|
||||||
) {
|
|
||||||
var plan vlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
|
|
||||||
if err := r.client.UpdateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Update SDN VLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VLANResource) Delete(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.DeleteRequest,
|
|
||||||
resp *resource.DeleteResponse,
|
|
||||||
) {
|
|
||||||
var state vlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.client.DeleteZone(ctx, state.ID.ValueString()); err != nil &&
|
|
||||||
!errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Delete SDN VLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VLANResource) ImportState(
|
func (r *VLANResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
ctx context.Context,
|
resp.Schema = schema.Schema{
|
||||||
req resource.ImportStateRequest,
|
Description: "VLAN Zone in Proxmox SDN.",
|
||||||
resp *resource.ImportStateResponse,
|
MarkdownDescription: "VLAN Zone in Proxmox SDN. It uses an existing local Linux or OVS bridge to connect to the " +
|
||||||
) {
|
"node's physical interface. It uses VLAN tagging defined in the VNet to isolate the network segments. " +
|
||||||
zone, err := r.client.GetZone(ctx, req.ID)
|
"This allows connectivity of VMs between different nodes.",
|
||||||
if err != nil {
|
Attributes: genericAttributesWith(map[string]schema.Attribute{
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
"bridge": schema.StringAttribute{
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Zone %s does not exist", req.ID), err.Error())
|
Description: "Bridge interface for VLAN.",
|
||||||
return
|
MarkdownDescription: "The local bridge or OVS switch, already configured on _each_ node that allows " +
|
||||||
}
|
"node-to-node connection.",
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Unable to Import SDN VLAN Zone %s", req.ID), err.Error())
|
Optional: true,
|
||||||
return
|
},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
readModel := &vlanModel{}
|
}
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
func (r *VLANResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
|
r.generic.Metadata(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VLANResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
|
r.generic.Configure(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VLANResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
|
r.generic.Create(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VLANResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
|
r.generic.Read(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VLANResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
r.generic.Update(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VLANResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
r.generic.Delete(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VLANResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
r.generic.ImportState(ctx, req, resp)
|
||||||
}
|
}
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
//go:build acceptance || all
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package zone_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAccResourceSDNZoneVLAN(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
te := test.InitEnvironment(t)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
steps []resource.TestStep
|
|
||||||
}{
|
|
||||||
{"create and update VLAN zone", []resource.TestStep{{
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_vlan" "zone_vlan" {
|
|
||||||
id = "zoneV"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1496
|
|
||||||
bridge = "vmbr0"
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
}, {
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_vlan" "zone_vlan" {
|
|
||||||
id = "zoneV"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1495
|
|
||||||
bridge = "vmbr0"
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
ResourceName: "proxmox_virtual_environment_sdn_zone_vlan.zone_vlan",
|
|
||||||
ImportStateId: "zoneV",
|
|
||||||
ImportState: true,
|
|
||||||
ImportStateVerify: true,
|
|
||||||
}}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
resource.ParallelTest(t, resource.TestCase{
|
|
||||||
ProtoV6ProviderFactories: te.AccProviders,
|
|
||||||
Steps: tt.steps,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,15 +8,13 @@ package zone
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/config"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/types/stringset"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster/sdn/zones"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -24,168 +22,82 @@ var (
|
|||||||
_ resource.ResourceWithImportState = &VXLANResource{}
|
_ resource.ResourceWithImportState = &VXLANResource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type vxlanModel struct {
|
||||||
|
genericModel
|
||||||
|
|
||||||
|
Peers stringset.Value `tfsdk:"peers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *vxlanModel) importFromAPI(name string, data *zones.ZoneData, diags *diag.Diagnostics) {
|
||||||
|
m.genericModel.importFromAPI(name, data, diags)
|
||||||
|
m.Peers = stringset.NewValueString(data.Peers, diags, stringset.WithSeparator(","))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *vxlanModel) toAPIRequestBody(ctx context.Context, diags *diag.Diagnostics) *zones.ZoneRequestData {
|
||||||
|
data := m.genericModel.toAPIRequestBody(ctx, diags)
|
||||||
|
|
||||||
|
data.Peers = m.Peers.ValueStringPointer(ctx, diags, stringset.WithSeparator(","))
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
type VXLANResource struct {
|
type VXLANResource struct {
|
||||||
client *zones.Client
|
generic *genericZoneResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVXLANResource() resource.Resource {
|
func NewVXLANResource() resource.Resource {
|
||||||
return &VXLANResource{}
|
return &VXLANResource{
|
||||||
}
|
generic: newGenericZoneResource(zoneResourceConfig{
|
||||||
|
typeNameSuffix: "_sdn_zone_vxlan",
|
||||||
func (r *VXLANResource) Metadata(
|
zoneType: zones.TypeVXLAN,
|
||||||
_ context.Context,
|
modelFunc: func() zoneModel { return &vxlanModel{} },
|
||||||
req resource.MetadataRequest,
|
}).(*genericZoneResource),
|
||||||
resp *resource.MetadataResponse,
|
|
||||||
) {
|
|
||||||
resp.TypeName = req.ProviderTypeName + "_sdn_zone_vxlan"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VXLANResource) Configure(
|
|
||||||
_ context.Context,
|
|
||||||
req resource.ConfigureRequest,
|
|
||||||
resp *resource.ConfigureResponse,
|
|
||||||
) {
|
|
||||||
if req.ProviderData == nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cfg, ok := req.ProviderData.(config.Resource)
|
func (r *VXLANResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
if !ok {
|
resp.Schema = schema.Schema{
|
||||||
resp.Diagnostics.AddError(
|
Description: "VXLAN Zone in Proxmox SDN.",
|
||||||
"Unexpected Resource Configure Type",
|
MarkdownDescription: "VXLAN Zone in Proxmox SDN. It establishes a tunnel (overlay) on top of an existing network " +
|
||||||
fmt.Sprintf(
|
"(underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams using the default " +
|
||||||
"Expected config.Resource, got: %T",
|
"destination port 4789. You have to configure the underlay network yourself to enable UDP connectivity " +
|
||||||
req.ProviderData,
|
"between all peers. Because VXLAN encapsulation uses 50 bytes, the MTU needs to be 50 bytes lower than the " +
|
||||||
|
"outgoing physical interface.",
|
||||||
|
Attributes: genericAttributesWith(map[string]schema.Attribute{
|
||||||
|
"peers": stringset.ResourceAttribute(
|
||||||
|
"A list of IP addresses of each node in the VXLAN zone.",
|
||||||
|
"A list of IP addresses of each node in the VXLAN zone. "+
|
||||||
|
"This can be external nodes reachable at this IP address. All nodes in the cluster need to be "+
|
||||||
|
"mentioned here",
|
||||||
),
|
),
|
||||||
)
|
}),
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.client = cfg.Client.Cluster().SDNZones()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VXLANResource) Create(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.CreateRequest,
|
|
||||||
resp *resource.CreateResponse,
|
|
||||||
) {
|
|
||||||
var plan vxlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
reqData.Type = ptr.Ptr(zones.TypeVXLAN)
|
|
||||||
|
|
||||||
if err := r.client.CreateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Create SDN VXLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VXLANResource) Read(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.ReadRequest,
|
|
||||||
resp *resource.ReadResponse,
|
|
||||||
) {
|
|
||||||
var state vxlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zone, err := r.client.GetZone(ctx, state.ID.ValueString())
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.State.RemoveResource(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Read SDN VXLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
readModel := &vxlanModel{}
|
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VXLANResource) Update(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.UpdateRequest,
|
|
||||||
resp *resource.UpdateResponse,
|
|
||||||
) {
|
|
||||||
var plan vxlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reqData := plan.toAPIRequestBody(ctx, &resp.Diagnostics)
|
|
||||||
|
|
||||||
if err := r.client.UpdateZone(ctx, reqData); err != nil {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Update SDN VXLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *VXLANResource) Delete(
|
|
||||||
ctx context.Context,
|
|
||||||
req resource.DeleteRequest,
|
|
||||||
resp *resource.DeleteResponse,
|
|
||||||
) {
|
|
||||||
var state vxlanModel
|
|
||||||
|
|
||||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.client.DeleteZone(ctx, state.ID.ValueString()); err != nil &&
|
|
||||||
!errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unable to Delete SDN VXLAN Zone",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VXLANResource) ImportState(
|
func (r *VXLANResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||||
ctx context.Context,
|
r.generic.Metadata(ctx, req, resp)
|
||||||
req resource.ImportStateRequest,
|
}
|
||||||
resp *resource.ImportStateResponse,
|
|
||||||
) {
|
func (r *VXLANResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
zone, err := r.client.GetZone(ctx, req.ID)
|
r.generic.Configure(ctx, req, resp)
|
||||||
if err != nil {
|
}
|
||||||
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Zone %s does not exist", req.ID), err.Error())
|
func (r *VXLANResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||||
return
|
r.generic.Create(ctx, req, resp)
|
||||||
}
|
}
|
||||||
resp.Diagnostics.AddError(fmt.Sprintf("Unable to Import SDN VXLAN Zone %s", req.ID), err.Error())
|
|
||||||
return
|
func (r *VXLANResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||||
}
|
r.generic.Read(ctx, req, resp)
|
||||||
readModel := &vxlanModel{}
|
}
|
||||||
readModel.importFromAPI(zone.ID, zone, &resp.Diagnostics)
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, readModel)...)
|
func (r *VXLANResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||||
|
r.generic.Update(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VXLANResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||||
|
r.generic.Delete(ctx, req, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VXLANResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||||
|
r.generic.ImportState(ctx, req, resp)
|
||||||
}
|
}
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
//go:build acceptance || all
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package zone_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAccResourceSDNZoneVXLAN(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
te := test.InitEnvironment(t)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
steps []resource.TestStep
|
|
||||||
}{
|
|
||||||
{"create and update VXLAN zone", []resource.TestStep{{
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_vxlan" "zone_vxlan" {
|
|
||||||
id = "zoneX"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1450
|
|
||||||
peers = ["10.0.0.1", "10.0.0.2"]
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
}, {
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_sdn_zone_vxlan" "zone_vxlan" {
|
|
||||||
id = "zoneX"
|
|
||||||
nodes = ["pve"]
|
|
||||||
mtu = 1440
|
|
||||||
peers = ["10.0.0.3", "10.0.0.4"]
|
|
||||||
}
|
|
||||||
`),
|
|
||||||
ResourceName: "proxmox_virtual_environment_sdn_zone_vxlan.zone_vxlan",
|
|
||||||
ImportStateId: "zoneX",
|
|
||||||
ImportState: true,
|
|
||||||
ImportStateVerify: true,
|
|
||||||
}}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
resource.ParallelTest(t, resource.TestCase{
|
|
||||||
ProtoV6ProviderFactories: te.AccProviders,
|
|
||||||
Steps: tt.steps,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
195
fwprovider/cluster/sdn/zone/resource_zones_test.go
Normal file
195
fwprovider/cluster/sdn/zone/resource_zones_test.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
//go:build acceptance || all
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package zone_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccResourceSDNZoneSimple(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
te := test.InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{"create and update zones", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_simple" "zone_simple" {
|
||||||
|
id = "zoneS"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1496
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
}, {
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_simple" "zone_simple" {
|
||||||
|
id = "zoneS"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1495
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
ResourceName: "proxmox_virtual_environment_sdn_zone_simple.zone_simple",
|
||||||
|
ImportStateId: "zoneS",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccResourceSDNZoneVLAN(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
te := test.InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{"create and update VLAN zone", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_vlan" "zone_vlan" {
|
||||||
|
id = "zoneV"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1496
|
||||||
|
bridge = "vmbr0"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
}, {
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_vlan" "zone_vlan" {
|
||||||
|
id = "zoneV"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1495
|
||||||
|
bridge = "vmbr0"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
ResourceName: "proxmox_virtual_environment_sdn_zone_vlan.zone_vlan",
|
||||||
|
ImportStateId: "zoneV",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccResourceSDNZoneQinQ(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
te := test.InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{"create and update QinQ zone", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_qinq" "zone_qinq" {
|
||||||
|
id = "zoneQ"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1496
|
||||||
|
bridge = "vmbr0"
|
||||||
|
service_vlan = 100
|
||||||
|
service_vlan_protocol = "802.1ad"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
}, {
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_qinq" "zone_qinq" {
|
||||||
|
id = "zoneQ"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1495
|
||||||
|
bridge = "vmbr0"
|
||||||
|
service_vlan = 200
|
||||||
|
service_vlan_protocol = "802.1q"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
ResourceName: "proxmox_virtual_environment_sdn_zone_qinq.zone_qinq",
|
||||||
|
ImportStateId: "zoneQ",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccResourceSDNZoneVXLAN(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
te := test.InitEnvironment(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
steps []resource.TestStep
|
||||||
|
}{
|
||||||
|
{"create and update VXLAN zone", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_vxlan" "zone_vxlan" {
|
||||||
|
id = "zoneX"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1450
|
||||||
|
peers = ["10.0.0.1", "10.0.0.2"]
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
}, {
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_sdn_zone_vxlan" "zone_vxlan" {
|
||||||
|
id = "zoneX"
|
||||||
|
nodes = ["pve"]
|
||||||
|
mtu = 1440
|
||||||
|
peers = ["10.0.0.3", "10.0.0.4"]
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
ResourceName: "proxmox_virtual_environment_sdn_zone_vxlan.zone_vxlan",
|
||||||
|
ImportStateId: "zoneX",
|
||||||
|
ImportState: true,
|
||||||
|
ImportStateVerify: true,
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
resource.ParallelTest(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.steps,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,6 @@ type linuxBridgeResourceModel struct {
|
|||||||
VLANAware types.Bool `tfsdk:"vlan_aware"`
|
VLANAware types.Bool `tfsdk:"vlan_aware"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:lll
|
|
||||||
func (m *linuxBridgeResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nodes.NetworkInterfaceCreateUpdateRequestBody {
|
func (m *linuxBridgeResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nodes.NetworkInterfaceCreateUpdateRequestBody {
|
||||||
body := &nodes.NetworkInterfaceCreateUpdateRequestBody{
|
body := &nodes.NetworkInterfaceCreateUpdateRequestBody{
|
||||||
Iface: m.Name.ValueString(),
|
Iface: m.Name.ValueString(),
|
||||||
|
@ -54,7 +54,6 @@ type linuxVLANResourceModel struct {
|
|||||||
VLAN types.Int64 `tfsdk:"vlan"`
|
VLAN types.Int64 `tfsdk:"vlan"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:lll
|
|
||||||
func (m *linuxVLANResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nodes.NetworkInterfaceCreateUpdateRequestBody {
|
func (m *linuxVLANResourceModel) exportToNetworkInterfaceCreateUpdateBody() *nodes.NetworkInterfaceCreateUpdateRequestBody {
|
||||||
body := &nodes.NetworkInterfaceCreateUpdateRequestBody{
|
body := &nodes.NetworkInterfaceCreateUpdateRequestBody{
|
||||||
Iface: m.Name.ValueString(),
|
Iface: m.Name.ValueString(),
|
||||||
|
@ -529,10 +529,13 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
|
|||||||
options.NewClusterOptionsResource,
|
options.NewClusterOptionsResource,
|
||||||
vm.NewResource,
|
vm.NewResource,
|
||||||
sdnzone.NewSimpleResource,
|
sdnzone.NewSimpleResource,
|
||||||
|
sdnzone.NewVLANResource,
|
||||||
|
sdnzone.NewQinQResource,
|
||||||
|
sdnzone.NewVXLANResource,
|
||||||
|
sdnzone.NewEVPNResource,
|
||||||
//
|
//
|
||||||
// sdn.NewSDNZoneResource,
|
|
||||||
// sdn.NewSDNVnetResource,
|
// sdn.NewSDNVnetResource,
|
||||||
//sdn.NewSDNSubnetResource,
|
// sdn.NewSDNSubnetResource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
proxmox/cluster/sdn/zones/api.go
Normal file
19
proxmox/cluster/sdn/zones/api.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package zones
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API interface {
|
||||||
|
GetZones(ctx context.Context) ([]ZoneData, error)
|
||||||
|
GetZone(ctx context.Context, id string) (*ZoneData, error)
|
||||||
|
CreateZone(ctx context.Context, req *ZoneRequestData) error
|
||||||
|
UpdateZone(ctx context.Context, req *ZoneRequestData) error
|
||||||
|
DeleteZone(ctx context.Context, id string) error
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user