Skip to content

Commit

Permalink
Merge pull request linode#146 from Charliekenney23/feat/firewall-devices
Browse files Browse the repository at this point in the history
add firewall device operations
  • Loading branch information
0xch4z committed May 6, 2020
2 parents ffcac8c + 413fc5b commit 8d20f69
Show file tree
Hide file tree
Showing 11 changed files with 1,486 additions and 7 deletions.
8 changes: 8 additions & 0 deletions account_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ const (
ActionDNSZoneCreate EventAction = "dns_zone_create"
ActionDNSZoneDelete EventAction = "dns_zone_delete"
ActionDNSZoneUpdate EventAction = "dns_zone_update"
ActionFirewallCreate EventAction = "firewall_create"
ActionFirewallDelete EventAction = "firewall_delete"
ActionFirewallDisable EventAction = "firewall_disable"
ActionFirewallEnable EventAction = "firewall_enable"
ActionFirewallUpdate EventAction = "firewall_update"
ActionFirewallDeviceAdd EventAction = "firewall_device_add"
ActionFirewallDeviceRemove EventAction = "firewall_device_remove"
ActionHostReboot EventAction = "host_reboot"
ActionImageDelete EventAction = "image_delete"
ActionImageUpdate EventAction = "image_update"
Expand Down Expand Up @@ -141,6 +148,7 @@ const (
EntityLinode EntityType = "linode"
EntityDisk EntityType = "disk"
EntityDomain EntityType = "domain"
EntityFirewall EntityType = "firewall"
EntityNodebalancer EntityType = "nodebalancer"
)

Expand Down
3 changes: 3 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type Client struct {
Domains *Resource
Events *Resource
Firewalls *Resource
FirewallDevices *Resource
IPAddresses *Resource
IPv6Pools *Resource
IPv6Ranges *Resource
Expand Down Expand Up @@ -261,6 +262,7 @@ func addResources(client *Client) {
domainsName: NewResource(client, domainsName, domainsEndpoint, false, Domain{}, DomainsPagedResponse{}),
eventsName: NewResource(client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}),
firewallsName: NewResource(client, firewallsName, firewallsEndpoint, false, Firewall{}, FirewallsPagedResponse{}),
firewallDevicesName: NewResource(client, firewallDevicesName, firewallDevicesEndpoint, true, FirewallDevice{}, FirewallDevicesPagedResponse{}),
imagesName: NewResource(client, imagesName, imagesEndpoint, false, Image{}, ImagesPagedResponse{}),
instanceConfigsName: NewResource(client, instanceConfigsName, instanceConfigsEndpoint, true, InstanceConfig{}, InstanceConfigsPagedResponse{}),
instanceDisksName: NewResource(client, instanceDisksName, instanceDisksEndpoint, true, InstanceDisk{}, InstanceDisksPagedResponse{}),
Expand Down Expand Up @@ -312,6 +314,7 @@ func addResources(client *Client) {
client.Domains = resources[domainsName]
client.Events = resources[eventsName]
client.Firewalls = resources[firewallsName]
client.FirewallDevices = resources[firewallDevicesName]
client.IPAddresses = resources[ipaddressesName]
client.IPv6Pools = resources[ipv6poolsName]
client.IPv6Ranges = resources[ipv6rangesName]
Expand Down
141 changes: 141 additions & 0 deletions firewall_devices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package linodego

import (
"context"
"encoding/json"
"fmt"
"time"

"github.com/linode/linodego/internal/parseabletime"
)

// FirewallDeviceType represents the different kinds of devices governable by a Firewall
type FirewallDeviceType string

// FirewallDeviceType constants start with FirewallDevice
const (
FirewallDeviceLinode FirewallDeviceType = "linode"
FirewallDeviceNodeBalancer FirewallDeviceType = "nodebalancer"
)

// FirewallDevice represents a device governed by a Firewall
type FirewallDevice struct {
ID int `json:"id"`
Entity FirewallDeviceEntity `json:"entity"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
}

// FirewallDeviceCreateOptions fields are those accepted by CreateFirewallDevice
type FirewallDeviceCreateOptions struct {
ID int `json:"id"`
Type FirewallDeviceType `json:"type"`
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (device *FirewallDevice) UnmarshalJSON(b []byte) error {
type Mask FirewallDevice

p := struct {
*Mask
Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(device),
}

if err := json.Unmarshal(b, &p); err != nil {
return err
}

device.Created = (*time.Time)(p.Created)
device.Updated = (*time.Time)(p.Updated)
return nil
}

// FirewallDeviceEntity contains information about a device associated with a Firewall
type FirewallDeviceEntity struct {
ID int `json:"id"`
Type FirewallDeviceType `json:"type"`
Label string `json:"label"`
URL string `json:"url"`
}

// FirewallDevicesPagedResponse represents a Linode API response for FirewallDevices
type FirewallDevicesPagedResponse struct {
*PageOptions
Data []FirewallDevice `json:"data"`
}

// endpointWithID gets the endpoint URL for FirewallDevices of a given Firewall
func (FirewallDevicesPagedResponse) endpointWithID(c *Client, id int) string {
endpoint, err := c.FirewallDevices.endpointWithID(id)
if err != nil {
panic(err)
}
return endpoint
}

func (resp *FirewallDevicesPagedResponse) appendData(r *FirewallDevicesPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}

// ListFirewallDevices get devices associated with a given Firewall
func (c *Client) ListFirewallDevices(ctx context.Context, firewallID int, opts *ListOptions) ([]FirewallDevice, error) {
response := FirewallDevicesPagedResponse{}
err := c.listHelperWithID(ctx, &response, firewallID, opts)

if err != nil {
return nil, err
}
return response.Data, nil
}

// GetFirewallDevice gets a FirewallDevice given an ID
func (c *Client) GetFirewallDevice(ctx context.Context, firewallID, deviceID int) (*FirewallDevice, error) {
e, err := c.FirewallDevices.endpointWithID(firewallID)
if err != nil {
return nil, err
}

e = fmt.Sprintf("%s/%d", e, deviceID)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&FirewallDevice{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*FirewallDevice), nil
}

// AddFirewallDevice associates a Device with a given Firewall
func (c *Client) CreateFirewallDevice(ctx context.Context, firewallID int, createOpts FirewallDeviceCreateOptions) (*FirewallDevice, error) {
var body string
e, err := c.FirewallDevices.endpointWithID(firewallID)
if err != nil {
return nil, err
}

req := c.R(ctx).SetResult(&FirewallDevice{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}

r, err := coupleAPIErrors(req.SetBody(body).Post(e))
if err != nil {
return nil, err
}
return r.Result().(*FirewallDevice), nil
}

// DeleteFirewallDevice disassociates a Device with a given Firewall
func (c *Client) DeleteFirewallDevice(ctx context.Context, firewallID, deviceID int) error {
e, err := c.FirewallDevices.endpointWithID(firewallID)
if err != nil {
return err
}

e = fmt.Sprintf("%s/%d", e, deviceID)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}
4 changes: 2 additions & 2 deletions firewalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ type Firewall struct {

// DevicesCreationOptions fields are used when adding devices during the Firewall creation process.
type DevicesCreationOptions struct {
Linodes []string `json:"linodes,omitempty"`
NodeBalancers []string `json:"nodebalancers,omitempty"`
Linodes []int `json:"linodes,omitempty"`
NodeBalancers []int `json:"nodebalancers,omitempty"`
}

// FirewallCreateOptions fields are those accepted by CreateFirewall
Expand Down
6 changes: 6 additions & 0 deletions pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw inte
results = response.Results
v.appendData(response)
}
case *FirewallDevicesPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(FirewallDevicesPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*FirewallDevicesPagedResponse).Pages
results = r.Result().(*FirewallDevicesPagedResponse).Results
v.appendData(r.Result().(*FirewallDevicesPagedResponse))
}
case *InstanceConfigsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(InstanceConfigsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
pages = r.Result().(*InstanceConfigsPagedResponse).Pages
Expand Down
2 changes: 2 additions & 0 deletions resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
domainsName = "domains"
eventsName = "events"
firewallsName = "firewalls"
firewallDevicesName = "firewalldevices"
imagesName = "images"
instanceConfigsName = "configs"
instanceDisksName = "disks"
Expand Down Expand Up @@ -65,6 +66,7 @@ const (
domainsEndpoint = "domains"
eventsEndpoint = "account/events"
firewallsEndpoint = "networking/firewalls"
firewallDevicesEndpoint = "networking/firewalls/{{ .ID }}/devices"
imagesEndpoint = "images"
instanceConfigsEndpoint = "linode/instances/{{ .ID }}/configs"
instanceDisksEndpoint = "linode/instances/{{ .ID }}/disks"
Expand Down
98 changes: 98 additions & 0 deletions test/integration/firewalls_devices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package integration

import (
"context"
"net/http"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/linode/linodego"
)

func TestListFirewallDevices(t *testing.T) {
client, instance, teardown, err := setupInstance(t, "fixtures/TestListFirewallDevices")
if err != nil {
t.Error(err)
}
defer teardown()

firewall, teardownFirewall, err := createFirewall(t, client, func(opts *linodego.FirewallCreateOptions) {
opts.Devices.Linodes = []int{instance.ID}
})
if err != nil {
t.Error(err)
}
defer teardownFirewall()

firewallDevices, err := client.ListFirewallDevices(context.Background(), firewall.ID, nil)
if err != nil {
t.Error(err)
}

if len(firewallDevices) != 1 {
t.Errorf("expected 1 firewall device but got %d", len(firewallDevices))
}
}

func TestGetFirewallDevice(t *testing.T) {
client, instance, teardown, err := setupInstance(t, "fixtures/TestGetFirewallDevice")
if err != nil {
t.Error(err)
}
defer teardown()

firewall, teardownFirewall, err := createFirewall(t, client)
if err != nil {
t.Error(err)
}
defer teardownFirewall()

firewallDevice, err := client.CreateFirewallDevice(context.Background(), firewall.ID, linodego.FirewallDeviceCreateOptions{
Type: linodego.FirewallDeviceLinode,
ID: instance.ID,
})
if err != nil {
t.Error(err)
}

if device, err := client.GetFirewallDevice(context.Background(), firewall.ID, firewallDevice.ID); err != nil {
t.Error(err)
} else if !cmp.Equal(device, firewallDevice) {
t.Errorf("expected device to match create result but got diffs: %s", cmp.Diff(device, firewallDevice))
}
}

func TestDeleteFirewallDevice(t *testing.T) {
client, instance, teardown, err := setupInstance(t, "fixtures/TestDeleteFirewallDevice")
if err != nil {
t.Error(err)
}
defer teardown()

firewall, teardownFirewall, err := createFirewall(t, client)
if err != nil {
t.Error(err)
}
defer teardownFirewall()

firewallDevice, err := client.CreateFirewallDevice(context.Background(), firewall.ID, linodego.FirewallDeviceCreateOptions{
Type: linodego.FirewallDeviceLinode,
ID: instance.ID,
})
if err != nil {
t.Error(err)
}

assertDateSet(t, firewallDevice.Created)
assertDateSet(t, firewallDevice.Updated)

if err := client.DeleteFirewallDevice(context.Background(), firewall.ID, firewallDevice.ID); err != nil {
t.Error(err)
}

if _, getErr := client.GetFirewallDevice(context.Background(), firewall.ID, firewallDevice.ID); err != nil {
t.Error("expected fetching firewall device to fail")
} else if apiError, ok := getErr.(*linodego.Error); !ok || apiError.Code != http.StatusNotFound {
t.Errorf("expected fetching firewall device to throw Not Found but got: %s", getErr)
}
}
19 changes: 14 additions & 5 deletions test/integration/firewalls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,8 @@ func TestGetFirewall(t *testing.T) {

type firewallModifier func(*linodego.FirewallCreateOptions)

func setupFirewall(t *testing.T, firewallModifiers []firewallModifier, fixturesYaml string) (*linodego.Client, *linodego.Firewall, func(), error) {
func createFirewall(t *testing.T, client *linodego.Client, firewallModifiers ...firewallModifier) (*linodego.Firewall, func(), error) {
t.Helper()
var fixtureTeardown func()
client, fixtureTeardown := createTestClient(t, fixturesYaml)

createOpts := testFirewallCreateOpts
for _, modifier := range firewallModifiers {
Expand All @@ -86,13 +84,24 @@ func setupFirewall(t *testing.T, firewallModifiers []firewallModifier, fixturesY

firewall, err := client.CreateFirewall(context.Background(), createOpts)
if err != nil {
t.Errorf("Error creating Firewall, expected struct, got error %v", err)
t.Errorf("failed to create firewall: %s", err)
}

teardown := func() {
if err := client.DeleteFirewall(context.Background(), firewall.ID); err != nil {
t.Errorf("Expected to delete a Firewall, but got %v", err)
t.Errorf("failed to delete firewall: %s", err)
}
}
return firewall, teardown, nil
}

func setupFirewall(t *testing.T, firewallModifiers []firewallModifier, fixturesYaml string) (*linodego.Client, *linodego.Firewall, func(), error) {
t.Helper()
client, fixtureTeardown := createTestClient(t, fixturesYaml)
firewall, firewallTeardown, err := createFirewall(t, client, firewallModifiers...)

teardown := func() {
firewallTeardown()
fixtureTeardown()
}
return client, firewall, teardown, err
Expand Down
Loading

0 comments on commit 8d20f69

Please sign in to comment.