Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add firewall device operations #146

Merged
merged 3 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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