diff --git a/client/interface_peer.go b/client/interface_peer.go deleted file mode 100644 index 550deaff..00000000 --- a/client/interface_peer.go +++ /dev/null @@ -1,96 +0,0 @@ -package client - -import ( - "github.com/go-routeros/routeros" -) - -type InterfacePeer struct { - Id string `mikrotik:".id"` - AllowedAddress string `mikrotik:"allowed-address"` - Comment string `mikrotik:"comment"` - Disabled bool `mikrotik:"disabled"` - EndpointAddress string `mikrotik:"endpoint-address"` - EndpointPort int `mikrotik:"endpoint-port"` - Interface string `mikrotik:"interface"` - PersistentKeepalive int `mikrotik:"persistent-keepalive"` - PresharedKey string `mikrotik:"preshared-key"` - PublicKey string `mikrotik:"public-key"` - CurrentEndpointAddress string `mikrotik:"current-endpoint-address,readonly"` - CurrentEndpointPort string `mikrotik:"current-endpoint-port,readonly"` - LastHandshake string `mikrotik:"last-handshake,readonly"` - Rx string `mikrotik:"rx,readonly"` - Tx string `mikrotik:"tx,readonly"` -} - -func (i *InterfacePeer) ActionToCommand(action Action) string { - return map[Action]string{ - Add: "/interface/wireguard/peers/add", //is this correct? - Find: "/interface/wireguard/peers/print", - List: "/interface/wireguard/peers/print", - Update: "/interface/wireguard/peers/set", - Delete: "/interface/wireguard/peers/remove", - }[action] -} - -func (i *InterfacePeer) IDField() string { - return ".id" -} - -func (i *InterfacePeer) ID() string { - return i.Id -} - -func (i *InterfacePeer) SetID(id string) { - i.Id = id -} - -func (i *InterfacePeer) AfterAddHook(r *routeros.Reply) { - i.Id = r.Done.Map["ret"] -} - -func (i *InterfacePeer) FindField() string { - return ".id" -} - -func (i *InterfacePeer) FindFieldValue() string { - return i.Id -} - -func (i *InterfacePeer) DeleteField() string { - return "numbers" -} - -func (i *InterfacePeer) DeleteFieldValue() string { - return i.Id -} - -func (client Mikrotik) AddInterfacePeer(i *InterfacePeer) (*InterfacePeer, error) { - res, err := client.Add(i) - if err != nil { - return nil, err - } - - return res.(*InterfacePeer), nil -} - -func (client Mikrotik) FindInterfacePeer(id string) (*InterfacePeer, error) { - res, err := client.Find(&InterfacePeer{Id: id}) - if err != nil { - return nil, err - } - - return res.(*InterfacePeer), nil -} - -func (client Mikrotik) UpdateInterfacePeer(i *InterfacePeer) (*InterfacePeer, error) { - res, err := client.Update(i) - if err != nil { - return nil, err - } - - return res.(*InterfacePeer), nil -} - -func (client Mikrotik) DeleteInterfacePeer(id string) error { - return client.Delete(&InterfacePeer{Id: id}) -} diff --git a/client/interface_peer_test.go b/client/interface_peer_test.go deleted file mode 100644 index 010699da..00000000 --- a/client/interface_peer_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package client - -import ( - "errors" - "reflect" - "testing" -) - -func TestFindInterfacePeer_onNonExistantInterfacePeer(t *testing.T) { - SkipInterfaceWireguardIfUnsupported(t) - c := NewClient(GetConfigFromEnv()) - - id := "Interface peer does not exist" - _, err := c.FindInterfacePeer(id) - - if _, ok := err.(*NotFound); !ok { - t.Errorf("Expecting to receive NotFound error for Interface peer `%s`, instead error was nil.", id) - } -} - -func TestAddFindDeleteInterfacePeer(t *testing.T) { - SkipInterfaceWireguardIfUnsupported(t) - c := NewClient(GetConfigFromEnv()) - - id := "new_interface_peer" - interfacePeer := &InterfacePeer{ - Id: id, - Disabled: false, - Comment: "new interface from test", - } - - created, err := c.Add(interfacePeer) - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - defer func() { - err = c.Delete(interfacePeer) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - expected := &NotFound{} - if _, err := c.Find(interfacePeer); err == nil || !errors.As(err, &expected) { - t.Error(err) - } - }() - findInterface := &InterfacePeer{} - findInterface.Id = id - found, err := c.Find(findInterface) - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - - if _, ok := found.(Resource); !ok { - t.Error("expected found resource to implement Resource interface, but it doesn't") - return - } - if !reflect.DeepEqual(created, found) { - t.Error("expected created and found resources to be equal, but they don't") - } -} diff --git a/client/interface_wireguard_peer.go b/client/interface_wireguard_peer.go new file mode 100644 index 00000000..b5c797aa --- /dev/null +++ b/client/interface_wireguard_peer.go @@ -0,0 +1,93 @@ +package client + +import ( + "github.com/go-routeros/routeros" +) + +type InterfaceWireguardPeer struct { + Id string `mikrotik:".id"` + AllowedAddress string `mikrotik:"allowed-address"` + Comment string `mikrotik:"comment"` + Disabled bool `mikrotik:"disabled"` + EndpointAddress string `mikrotik:"endpoint-address"` + EndpointPort int `mikrotik:"endpoint-port"` + Interface string `mikrotik:"interface"` + PersistentKeepalive int `mikrotik:"persistent-keepalive"` + PresharedKey string `mikrotik:"preshared-key"` + PublicKey string `mikrotik:"public-key"` + CurrentEndpointAddress string `mikrotik:"current-endpoint-address,readonly"` + CurrentEndpointPort int `mikrotik:"current-endpoint-port,readonly"` +} + +func (i *InterfaceWireguardPeer) ActionToCommand(action Action) string { + return map[Action]string{ + Add: "/interface/wireguard/peers/add", + Find: "/interface/wireguard/peers/print", + List: "/interface/wireguard/peers/print", + Update: "/interface/wireguard/peers/set", + Delete: "/interface/wireguard/peers/remove", + }[action] +} + +func (i *InterfaceWireguardPeer) IDField() string { + return ".id" +} + +func (i *InterfaceWireguardPeer) ID() string { + return i.Id +} + +func (i *InterfaceWireguardPeer) SetID(id string) { + i.Id = id +} + +func (i *InterfaceWireguardPeer) AfterAddHook(r *routeros.Reply) { + i.Id = r.Done.Map["ret"] +} + +func (i *InterfaceWireguardPeer) FindField() string { + return "interface" +} + +func (i *InterfaceWireguardPeer) FindFieldValue() string { + return i.Interface +} + +func (i *InterfaceWireguardPeer) DeleteField() string { + return "numbers" +} + +func (i *InterfaceWireguardPeer) DeleteFieldValue() string { + return i.Id +} + +func (client Mikrotik) AddInterfaceWireguardPeer(i *InterfaceWireguardPeer) (*InterfaceWireguardPeer, error) { + res, err := client.Add(i) + if err != nil { + return nil, err + } + + return res.(*InterfaceWireguardPeer), nil +} + +func (client Mikrotik) FindInterfaceWireguardPeer(interfaceName string) (*InterfaceWireguardPeer, error) { + res, err := client.Find(&InterfaceWireguardPeer{Interface: interfaceName}) + if err != nil { + return nil, err + } + + return res.(*InterfaceWireguardPeer), nil +} + +func (client Mikrotik) UpdateInterfaceWireguardPeer(i *InterfaceWireguardPeer) (*InterfaceWireguardPeer, error) { + res, err := client.Update(i) + if err != nil { + return nil, err + } + + return res.(*InterfaceWireguardPeer), nil +} + +func (client Mikrotik) DeleteInterfaceWireguardPeer(id string) error { + return client.Delete(&InterfaceWireguardPeer{Id: id}) +} diff --git a/client/interface_wireguard_peer_test.go b/client/interface_wireguard_peer_test.go new file mode 100644 index 00000000..28c946d2 --- /dev/null +++ b/client/interface_wireguard_peer_test.go @@ -0,0 +1,79 @@ +package client + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFindInterfaceWireguardPeer_onNonExistantInterfacePeer(t *testing.T) { + SkipInterfaceWireguardIfUnsupported(t) + c := NewClient(GetConfigFromEnv()) + + interfaceName := "Interface peer does not exist" + _, err := c.FindInterfaceWireguardPeer(interfaceName) + + require.Truef(t, IsNotFoundError(err), + "Expecting to receive NotFound error for Interface peer `%q`, instead error was nil.", interfaceName) +} + +func TestInterfaceWireguardPeer_Crud(t *testing.T) { + SkipInterfaceWireguardIfUnsupported(t) + c := NewClient(GetConfigFromEnv()) + + name := "new_interface_wireguard" + interfaceWireguard := &InterfaceWireguard{ + Name: name, + Disabled: false, + ListenPort: 10000, + Mtu: 10001, + PrivateKey: "YOi0P0lTTiN8hAQvuRET23Srb+U7C52iOZokj0CCSkM=", + Comment: "new interface from test", + } + + createdInterface, err := c.Add(interfaceWireguard) + if err != nil { + t.Errorf("expected no error, got %v", err) + return + } + defer func() { + err = c.Delete(interfaceWireguard) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + }() + + interfaceWireguardPeer := &InterfaceWireguardPeer{ + Interface: createdInterface.(*InterfaceWireguard).Name, + Disabled: false, + Comment: "new interface from test", + } + + created, err := c.Add(interfaceWireguardPeer) + if err != nil { + t.Errorf("expected no error, got %v", err) + return + } + defer func() { + err = c.Delete(interfaceWireguardPeer) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + }() + findInterface := &InterfaceWireguardPeer{} + findInterface.Interface = createdInterface.(*InterfaceWireguard).Name + found, err := c.Find(findInterface) + if err != nil { + t.Errorf("expected no error, got %v", err) + return + } + + if _, ok := found.(Resource); !ok { + t.Error("expected found resource to implement Resource interface, but it doesn't") + return + } + if !reflect.DeepEqual(created, found) { + t.Error("expected created and found resources to be equal, but they aren't") + } +} diff --git a/docs/resources/interface_wireguard_peer.md b/docs/resources/interface_wireguard_peer.md new file mode 100644 index 00000000..06b9fe3f --- /dev/null +++ b/docs/resources/interface_wireguard_peer.md @@ -0,0 +1,30 @@ +# mikrotik_interface_wireguard_peer (Resource) +Creates a Mikrotik Interface Wireguard Peer only supported by RouterOS v7+. + + + + +## Schema + +### Required + +- `interface` (String) Name of the WireGuard interface the peer belongs to. + +### Optional + +- `allowed_address` (String) List of IP (v4 or v6) addresses with CIDR masks from which incoming traffic for this peer is allowed and to which outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may be specified for matching all IPv4 addresses, and ::/0 may be specified for matching all IPv6 addresses. +- `comment` (String) Short description of the peer. +- `disabled` (Boolean) Boolean for whether or not the interface peer is disabled. +- `endpoint_address` (String) An endpoint IP or hostname can be left blank to allow remote connection from any address. +- `endpoint_port` (Number) An endpoint port can be left blank to allow remote connection from any port. +- `persistent_keepalive` (Number) A seconds interval, between 1 and 65535 inclusive, of how often to send an authenticated empty packet to the peer for the purpose of keeping a stateful firewall or NAT mapping valid persistently. For example, if the interface very rarely sends traffic, but it might at anytime receive traffic from a peer, and it is behind NAT, the interface might benefit from having a persistent keepalive interval of 25 seconds. +- `preshared_key` (String) A base64 preshared key. Optional, and may be omitted. This option adds an additional layer of symmetric-key cryptography to be mixed into the already existing public-key cryptography, for post-quantum resistance. +- `public_key` (String) The remote peer's calculated public key. + +### Read-Only + +- `current_endpoint_address` (String) The most recent source IP address of correctly authenticated packets from the peer. +- `current_endpoint_port` (Number) The most recent source IP port of correctly authenticated packets from the peer. +- `id` (String) Identifier of this resource assigned by RouterOS + + diff --git a/mikrotik/provider_framework.go b/mikrotik/provider_framework.go index 0d0f88ac..fbbcd93f 100644 --- a/mikrotik/provider_framework.go +++ b/mikrotik/provider_framework.go @@ -184,6 +184,7 @@ func (p *ProviderFramework) Resources(ctx context.Context) []func() resource.Res return []func() resource.Resource{ NewSchedulerResource, NewInterfaceWireguardResource, + NewInterfaceWireguardPeerResource, } } diff --git a/mikrotik/resource_interface_wireguard_peer.go b/mikrotik/resource_interface_wireguard_peer.go new file mode 100644 index 00000000..aea95ecd --- /dev/null +++ b/mikrotik/resource_interface_wireguard_peer.go @@ -0,0 +1,273 @@ +package mikrotik + +import ( + "context" + "fmt" + + "github.com/ddelnano/terraform-provider-mikrotik/client" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "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" + tftypes "github.com/hashicorp/terraform-plugin-framework/types" +) + +type interfaceWireguardPeer struct { + client *client.Mikrotik +} + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &interfaceWireguardPeer{} + _ resource.ResourceWithConfigure = &interfaceWireguardPeer{} + _ resource.ResourceWithImportState = &interfaceWireguardPeer{} +) + +// NewInterfaceWireguardPeerResource is a helper function to simplify the provider implementation. +func NewInterfaceWireguardPeerResource() resource.Resource { + return &interfaceWireguardPeer{} + +} + +func (i *interfaceWireguardPeer) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + i.client = req.ProviderData.(*client.Mikrotik) +} + +// Metadata returns the resource type name. +func (i *interfaceWireguardPeer) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_interface_wireguard_peer" +} + +// Schema defines the schema for the resource. +func (i *interfaceWireguardPeer) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Creates a Mikrotik Interface Wireguard Peer only supported by RouterOS v7+.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Description: "Identifier of this resource assigned by RouterOS", + }, + "allowed_address": schema.StringAttribute{ + Optional: true, + Description: "List of IP (v4 or v6) addresses with CIDR masks from which incoming traffic for this peer is allowed and to which outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may be specified for matching all IPv4 addresses, and ::/0 may be specified for matching all IPv6 addresses.", + }, + "comment": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Short description of the peer.", + }, + "disabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + Description: "Boolean for whether or not the interface peer is disabled.", + }, + "endpoint_address": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "An endpoint IP or hostname can be left blank to allow remote connection from any address.", + }, + "endpoint_port": schema.Int64Attribute{ + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(0, 65535), + }, + Description: "An endpoint port can be left blank to allow remote connection from any port.", + }, + "interface": schema.StringAttribute{ + Required: true, + Description: "Name of the WireGuard interface the peer belongs to.", + }, + "persistent_keepalive": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + Validators: []validator.Int64{ + int64validator.Between(0, 65535), + }, + Description: "A seconds interval, between 1 and 65535 inclusive, of how often to send an authenticated empty packet to the peer for the purpose of keeping a stateful firewall or NAT mapping valid persistently. For example, if the interface very rarely sends traffic, but it might at anytime receive traffic from a peer, and it is behind NAT, the interface might benefit from having a persistent keepalive interval of 25 seconds.", + }, + "preshared_key": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "A base64 preshared key. Optional, and may be omitted. This option adds an additional layer of symmetric-key cryptography to be mixed into the already existing public-key cryptography, for post-quantum resistance.", + }, + "public_key": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The remote peer's calculated public key.", + }, + "current_endpoint_address": schema.StringAttribute{ + Optional: false, + Computed: true, + Description: "The most recent source IP address of correctly authenticated packets from the peer.", + }, + "current_endpoint_port": schema.Int64Attribute{ + Optional: false, + Computed: true, + Description: "The most recent source IP port of correctly authenticated packets from the peer.", + }, + }, + } +} + +// Create creates the resource and sets the initial Terraform state. +func (i *interfaceWireguardPeer) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan interfaceWireguardPeerModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + created, err := i.client.AddInterfaceWireguardPeer(modelToInterfaceWireguardPeer(&plan)) + if err != nil { + resp.Diagnostics.AddError("creation failed", err.Error()) + return + } + + resp.Diagnostics.Append(interfaceWireguardPeerToModel(created, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (i *interfaceWireguardPeer) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state interfaceWireguardPeerModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resource, err := i.client.FindInterfaceWireguardPeer(state.Interface.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error reading remote resource", + fmt.Sprintf("Could not read interfaceWireguardPeer with interface name %q", state.Interface.ValueString()), + ) + return + } + + resp.Diagnostics.Append(interfaceWireguardPeerToModel(resource, &state)...) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update updates the resource and sets the updated Terraform state on success. +func (i *interfaceWireguardPeer) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan interfaceWireguardPeerModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + updated, err := i.client.UpdateInterfaceWireguardPeer(modelToInterfaceWireguardPeer(&plan)) + if err != nil { + resp.Diagnostics.AddError("update failed", err.Error()) + return + } + + resp.Diagnostics.Append(interfaceWireguardPeerToModel(updated, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes the resource and removes the Terraform state on success. +func (i *interfaceWireguardPeer) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state interfaceWireguardPeerModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if err := i.client.DeleteInterfaceWireguardPeer(state.ID.ValueString()); err != nil { + resp.Diagnostics.AddError("Could not delete interfaceWireguardPeer", err.Error()) + return + } +} + +func (i *interfaceWireguardPeer) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Retrieve import ID and save to id attribute + resource.ImportStatePassthroughID(ctx, path.Root("interface"), req, resp) +} + +type interfaceWireguardPeerModel struct { + ID tftypes.String `tfsdk:"id"` + AllowedAddress tftypes.String `tfsdk:"allowed_address"` + Comment tftypes.String `tfsdk:"comment"` + Disabled tftypes.Bool `tfsdk:"disabled"` + EndpointAddress tftypes.String `tfsdk:"endpoint_address"` + EndpointPort tftypes.Int64 `tfsdk:"endpoint_port"` + Interface tftypes.String `tfsdk:"interface"` + PersistentKeepalive tftypes.Int64 `tfsdk:"persistent_keepalive"` + PresharedKey tftypes.String `tfsdk:"preshared_key"` + PublicKey tftypes.String `tfsdk:"public_key"` + CurrentEndpointAddress tftypes.String `tfsdk:"current_endpoint_address"` + CurrentEndpointPort tftypes.Int64 `tfsdk:"current_endpoint_port"` +} + +func interfaceWireguardPeerToModel(i *client.InterfaceWireguardPeer, m *interfaceWireguardPeerModel) diag.Diagnostics { + var diags diag.Diagnostics + if i == nil { + diags.AddError("Interface Wireguard Peer cannot be nil", "Cannot build model from nil object") + return diags + } + m.ID = tftypes.StringValue(i.Id) + m.AllowedAddress = tftypes.StringValue(i.AllowedAddress) + m.Comment = tftypes.StringValue(i.Comment) + m.Disabled = tftypes.BoolValue(i.Disabled) + m.EndpointAddress = tftypes.StringValue(i.EndpointAddress) + m.EndpointPort = tftypes.Int64Value(int64(i.EndpointPort)) + m.Interface = tftypes.StringValue(i.Interface) + m.PersistentKeepalive = tftypes.Int64Value(int64(i.PersistentKeepalive)) + m.PresharedKey = tftypes.StringValue(i.PresharedKey) + m.PublicKey = tftypes.StringValue(i.PublicKey) + m.CurrentEndpointAddress = tftypes.StringValue(i.CurrentEndpointAddress) + m.CurrentEndpointPort = tftypes.Int64Value(int64(i.CurrentEndpointPort)) + + return diags +} + +func modelToInterfaceWireguardPeer(m *interfaceWireguardPeerModel) *client.InterfaceWireguardPeer { + return &client.InterfaceWireguardPeer{ + Id: m.ID.ValueString(), + AllowedAddress: m.AllowedAddress.ValueString(), + Comment: m.Comment.ValueString(), + Disabled: m.Disabled.ValueBool(), + EndpointAddress: m.EndpointAddress.ValueString(), + EndpointPort: int(m.EndpointPort.ValueInt64()), + Interface: m.Interface.ValueString(), + PersistentKeepalive: int(m.PersistentKeepalive.ValueInt64()), + PresharedKey: m.PresharedKey.ValueString(), + PublicKey: m.PublicKey.ValueString(), + CurrentEndpointAddress: m.CurrentEndpointAddress.ValueString(), + CurrentEndpointPort: int(m.EndpointPort.ValueInt64()), + } +} diff --git a/mikrotik/resource_interface_wireguard_peer_test.go b/mikrotik/resource_interface_wireguard_peer_test.go new file mode 100644 index 00000000..d4848557 --- /dev/null +++ b/mikrotik/resource_interface_wireguard_peer_test.go @@ -0,0 +1,189 @@ +package mikrotik + +import ( + "fmt" + "strconv" + "testing" + + "github.com/ddelnano/terraform-provider-mikrotik/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var origCommentPeer string = "testing" +var origAllowedAddress string = "192.168.8.1/32" +var origEndpointPort int = 13231 +var updatedCommentPeer string = "new_comment" + +func TestAccMikrotikInterfaceWireguardPeer_create(t *testing.T) { + client.SkipInterfaceWireguardIfUnsupported(t) + + interfaceName := "tf-acc-interface-wireguard" + publicKey := "/yZWgiYAgNNSy7AIcxuEewYwOVPqJJRKG90s9ypwfiM=" + resourceName := "mikrotik_interface_wireguard_peer.bar" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + CheckDestroy: testAccCheckMikrotikInterfaceWireguardPeerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInterfaceWireguardPeer(interfaceName, publicKey), + Check: resource.ComposeAggregateTestCheckFunc( + testAccInterfaceWireguardPeerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "allowed_address", origAllowedAddress), + resource.TestCheckResourceAttr(resourceName, "public_key", publicKey), + resource.TestCheckResourceAttr(resourceName, "endpoint_port", strconv.Itoa(origEndpointPort)), + resource.TestCheckResourceAttr(resourceName, "interface", interfaceName)), + }, + }, + }) +} + +func TestAccMikrotikInterfaceWireguardPeer_updatedComment(t *testing.T) { + client.SkipInterfaceWireguardIfUnsupported(t) + + interfaceName := "tf-acc-interface-wireguard-updated" + publicKey := "/bTmUihbgNsSy2AIcxuEcwYwOVdqJJRKG51s4ypwfiM=" + resourceName := "mikrotik_interface_wireguard_peer.bar" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + CheckDestroy: testAccCheckMikrotikInterfaceWireguardPeerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInterfaceWireguardPeer(interfaceName, publicKey), + Check: resource.ComposeAggregateTestCheckFunc( + testAccInterfaceWireguardPeerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "interface", interfaceName), + resource.TestCheckResourceAttr(resourceName, "public_key", publicKey), + resource.TestCheckResourceAttr(resourceName, "comment", origCommentPeer)), + }, + { + Config: testAccInterfaceWireguardPeerUpdatedComment(interfaceName, publicKey), + Check: resource.ComposeAggregateTestCheckFunc( + testAccInterfaceWireguardPeerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "interface", interfaceName), + resource.TestCheckResourceAttr(resourceName, "comment", updatedCommentPeer)), + }, + }, + }) +} + +func TestAccMikrotikInterfaceWireguardPeer_import(t *testing.T) { + client.SkipInterfaceWireguardIfUnsupported(t) + + interfaceName := "tf-acc-interface-wireguard-import" + publicKey := "/zYaGiYbgNsSy8AIcxuEcwYwOVdqJJRKG91s9ypwfiM=" + resourceName := "mikrotik_interface_wireguard_peer.bar" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + CheckDestroy: testAccCheckMikrotikInterfaceWireguardPeerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInterfaceWireguardPeer(interfaceName, publicKey), + Check: resource.ComposeAggregateTestCheckFunc( + testAccInterfaceWireguardPeerExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "interface"), + resource.TestCheckResourceAttr(resourceName, "public_key", publicKey), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + return interfaceName, nil + }, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccInterfaceWireguardPeer(interfaceName string, publicKey string) string { + return fmt.Sprintf(` + resource "mikrotik_interface_wireguard" "bar" { + name = "%s" + comment = "test interface" + listen_port = "12321" + mtu = "1420" + } + resource "mikrotik_interface_wireguard_peer" "bar" { + interface = mikrotik_interface_wireguard.bar.name + public_key = "%s" + comment = "%s" + allowed_address = "%s" + endpoint_port = "%d" + } + `, interfaceName, publicKey, origCommentPeer, origAllowedAddress, origEndpointPort) +} + +func testAccInterfaceWireguardPeerUpdatedComment(interfaceName string, publicKey string) string { + return fmt.Sprintf(` + resource "mikrotik_interface_wireguard" "bar" { + name = "%s" + comment = "test interface" + listen_port = "12321" + mtu = "1420" + } + resource "mikrotik_interface_wireguard_peer" "bar" { + interface = mikrotik_interface_wireguard.bar.name + public_key = "%s" + comment = "%s" + allowed_address = "%s" + endpoint_port = "%d" + } + `, interfaceName, publicKey, updatedCommentPeer, origAllowedAddress, origEndpointPort) +} + +func testAccCheckMikrotikInterfaceWireguardPeerDestroy(s *terraform.State) error { + c := client.NewClient(client.GetConfigFromEnv()) + for _, rs := range s.RootModule().Resources { + if rs.Type != "mikrotik_interface_wireguard_peer" { + continue + } + + interfaceWireguardPeer, err := c.FindInterfaceWireguardPeer(rs.Primary.Attributes["interface"]) + + if !client.IsNotFoundError(err) && err != nil { + return err + } + + if interfaceWireguardPeer != nil { + return fmt.Errorf("interface wireguard peer (%s) still exists", interfaceWireguardPeer.Interface) + } + } + return nil +} + +func testAccInterfaceWireguardPeerExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("mikrotik_interface_wireguard_peer does not exist in the statefile") + } + + c := client.NewClient(client.GetConfigFromEnv()) + + interfaceWireguardPeer, err := c.FindInterfaceWireguardPeer(rs.Primary.Attributes["interface"]) + + _, ok = err.(*client.NotFound) + if !ok && err != nil { + return fmt.Errorf("Unable to get the interface wireguard peer with error: %v", err) + } + + if interfaceWireguardPeer == nil { + return fmt.Errorf("Unable to get the interface wireguard peer with interface: %s", rs.Primary.Attributes["interface"]) + } + + if interfaceWireguardPeer.Id == rs.Primary.Attributes[".id"] { + return nil + } + return nil + } +}