Skip to content

Project: Linode Interfaces #735

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

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6fd4b61
Implement changes for account APIs (#715)
zliang-akamai Apr 22, 2025
245b2f0
Added support for `networking/` and `vpcs/` endpoint changs for VPC L…
ezilber-akamai Apr 23, 2025
724d21a
Add support for firewall template (#726)
zliang-akamai Apr 26, 2025
db8dfbb
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai Apr 30, 2025
134c0a4
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai May 5, 2025
75f0190
Adding VPC Enhanced Interfaces (#724)
jriddle-linode May 16, 2025
bc899d5
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai May 16, 2025
e7ea104
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai May 19, 2025
c1db12c
Linode instance changes for Linode Interfaces (#723)
zliang-akamai May 20, 2025
70290fa
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai May 20, 2025
0d06dc8
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai May 21, 2025
d4d1ba1
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai May 30, 2025
625aa9a
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai Jun 2, 2025
e0764ae
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai Jun 5, 2025
85b88c3
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai Jun 5, 2025
c7cde18
Make default firewall IDs pointers (#765)
zliang-akamai Jun 7, 2025
8b04c5d
Firewall Related integration tests (#760)
vshanthe Jun 10, 2025
1cab31f
Linode Interfaces: Consolidate InterfaceGeneration and LinodeInterfac…
lgarber-akamai Jun 11, 2025
517572e
Fix firewall settings tests (#772)
zliang-akamai Jun 24, 2025
b46e5cb
Make interface optional fields pointers (#773)
zliang-akamai Jun 24, 2025
d3eda89
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai Jun 24, 2025
bf3104f
Rename Label field to VLANLabel to align with API attribute naming
zliang-akamai Jul 1, 2025
2f2f21d
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai Jul 2, 2025
4954b32
golangci-lint run --fix
zliang-akamai Jul 2, 2025
5811bfe
fix a comment
zliang-akamai Jul 2, 2025
8909b3b
Merge branch 'main' into proj/vpc-linodes-enhanced-interfaces
zliang-akamai Jul 8, 2025
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
21 changes: 17 additions & 4 deletions account_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import (
"context"
)

type InterfacesForNewLinodes string

const (
LegacyConfigOnly InterfacesForNewLinodes = "legacy_config_only"
LegacyConfigDefaultButLinodeAllowed InterfacesForNewLinodes = "legacy_config_default_but_linode_allowed"
LinodeDefaultButLegacyConfigAllowed InterfacesForNewLinodes = "linode_default_but_legacy_config_allowed"
LinodeOnly InterfacesForNewLinodes = "linode_only"
)

// AccountSettings are the account wide flags or plans that effect new resources
type AccountSettings struct {
// The default backups enrollment status for all new Linodes for all users on the account. When enabled, backups are mandatory per instance.
Expand All @@ -20,19 +29,23 @@ type AccountSettings struct {

// A string like "disabled", "suspended", or "active" describing the status of this account’s Object Storage service enrollment.
ObjectStorage *string `json:"object_storage"`

// NOTE: Interfaces for new linode setting may not currently be available to all users.
// A new configuration flag defines whether new Linodes can use Linode and/or legacy config interfaces.
InterfacesForNewLinodes InterfacesForNewLinodes `json:"interfaces_for_new_linodes"`
}

// AccountSettingsUpdateOptions are the updateable account wide flags or plans that effect new resources.
type AccountSettingsUpdateOptions struct {
// The default backups enrollment status for all new Linodes for all users on the account. When enabled, backups are mandatory per instance.
BackupsEnabled *bool `json:"backups_enabled,omitempty"`

// A plan name like "longview-3"..."longview-100", or a nil value for to cancel any existing subscription plan.
// Deprecated: Use PUT /longview/plan instead to update the LongviewSubscription
LongviewSubscription *string `json:"longview_subscription,omitempty"`

// The default network helper setting for all new Linodes and Linode Configs for all users on the account.
NetworkHelper *bool `json:"network_helper,omitempty"`

// NOTE: Interfaces for new linode setting may not currently be available to all users.
// A new configuration flag defines whether new Linodes can use Linode and/or legacy config interfaces.
InterfacesForNewLinodes *InterfacesForNewLinodes `json:"interfaces_for_new_linodes"`
}

// GetAccountSettings gets the account wide flags or plans that effect new resources
Expand Down
1 change: 1 addition & 0 deletions firewall_devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type FirewallDeviceType string
const (
FirewallDeviceLinode FirewallDeviceType = "linode"
FirewallDeviceNodeBalancer FirewallDeviceType = "nodebalancer"
FirewallDeviceInterface FirewallDeviceType = "interface"
)

// FirewallDevice represents a device governed by a Firewall
Expand Down
23 changes: 23 additions & 0 deletions firewall_templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package linodego

import (
"context"
)

type FirewallTemplate struct {
Slug string `json:"slug"`
Rules FirewallRuleSet `json:"rules"`
}

// NOTE: This feature may not currently be available to all users.
// GetFirewallTemplate gets a FirewallTemplate given a slug.
func (c *Client) GetFirewallTemplate(ctx context.Context, slug string) (*FirewallTemplate, error) {
e := formatAPIPath("networking/firewalls/templates/%s", slug)
return doGETRequest[FirewallTemplate](ctx, c, e)
}

// NOTE: This feature may not currently be available to all users.
// ListFirewallTemplates gets all available firewall templates for the account.
func (c *Client) ListFirewallTemplates(ctx context.Context, opts *ListOptions) ([]FirewallTemplate, error) {
return getPaginatedResults[FirewallTemplate](ctx, c, "networking/firewalls/templates", opts)
}
36 changes: 36 additions & 0 deletions firewalls.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Firewall struct {
type DevicesCreationOptions struct {
Linodes []int `json:"linodes,omitempty"`
NodeBalancers []int `json:"nodebalancers,omitempty"`
Interfaces []int `json:"interfaces,omitempty"`
}

// FirewallCreateOptions fields are those accepted by CreateFirewall
Expand All @@ -50,6 +51,31 @@ type FirewallUpdateOptions struct {
Tags *[]string `json:"tags,omitempty"`
}

// FirewallSettings represents the default firewalls for Linodes,
// Linode VPC and public interfaces, and NodeBalancers.
type FirewallSettings struct {
DefaultFirewallIDs DefaultFirewallIDs `json:"default_firewall_ids"`
}

type DefaultFirewallIDs struct {
Linode *int `json:"linode"`
NodeBalancer *int `json:"nodebalancer"`
PublicInterface *int `json:"public_interface"`
VPCInterface *int `json:"vpc_interface"`
}

// FirewallSettingsUpdateOptions is an options struct used when Updating FirewallSettings
type FirewallSettingsUpdateOptions struct {
DefaultFirewallIDs DefaultFirewallIDsOptions `json:"default_firewall_ids"`
}

type DefaultFirewallIDsOptions struct {
Linode **int `json:"linode,omitempty"`
NodeBalancer **int `json:"nodebalancer,omitempty"`
PublicInterface **int `json:"public_interface,omitempty"`
VPCInterface **int `json:"vpc_interface,omitempty"`
}

// GetUpdateOptions converts a Firewall to FirewallUpdateOptions for use in Client.UpdateFirewall.
func (f *Firewall) GetUpdateOptions() FirewallUpdateOptions {
return FirewallUpdateOptions{
Expand Down Expand Up @@ -109,3 +135,13 @@ func (c *Client) DeleteFirewall(ctx context.Context, firewallID int) error {
e := formatAPIPath("networking/firewalls/%d", firewallID)
return doDELETERequest(ctx, c, e)
}

// GetFirewallSettings returns default firewalls for Linodes, Linode VPC and public interfaces, and NodeBalancers.
func (c *Client) GetFirewallSettings(ctx context.Context) (*FirewallSettings, error) {
return doGETRequest[FirewallSettings](ctx, c, "networking/firewalls/settings")
}

// UpdateFirewallSettings updates the default firewalls for Linodes, Linode VPC and public interfaces, and NodeBalancers.
func (c *Client) UpdateFirewallSettings(ctx context.Context, opts FirewallSettingsUpdateOptions) (*FirewallSettings, error) {
return doPUTRequest[FirewallSettings](ctx, c, "networking/firewalls/settings", opts)
}
4 changes: 2 additions & 2 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
Expand All @@ -80,19 +81,18 @@ golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
Expand Down
23 changes: 12 additions & 11 deletions instance_ips.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@ type InstanceIPv4Response struct {

// InstanceIP represents an Instance IP with additional DNS and networking details
type InstanceIP struct {
Address string `json:"address"`
Gateway string `json:"gateway"`
SubnetMask string `json:"subnet_mask"`
Prefix int `json:"prefix"`
Type InstanceIPType `json:"type"`
Public bool `json:"public"`
RDNS string `json:"rdns"`
LinodeID int `json:"linode_id"`
Region string `json:"region"`
VPCNAT1To1 *InstanceIPNAT1To1 `json:"vpc_nat_1_1"`
Reserved bool `json:"reserved"`
Address string `json:"address"`
Gateway string `json:"gateway"`
SubnetMask string `json:"subnet_mask"`
Prefix int `json:"prefix"`
Type InstanceIPType `json:"type"`
Public bool `json:"public"`
RDNS string `json:"rdns"`
LinodeID int `json:"linode_id"`
InterfaceID *int `json:"interface_id"`
Region string `json:"region"`
VPCNAT1To1 *InstanceIPNAT1To1 `json:"vpc_nat_1_1"`
Reserved bool `json:"reserved"`
}

// VPCIP represents a private IP address in a VPC subnet with additional networking details
Expand Down
121 changes: 104 additions & 17 deletions instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package linodego
import (
"context"
"encoding/json"
"fmt"
"net"
"time"

Expand Down Expand Up @@ -177,23 +178,32 @@ type InstancePasswordResetOptions struct {

// InstanceCreateOptions require only Region and Type
type InstanceCreateOptions struct {
Region string `json:"region"`
Type string `json:"type"`
Label string `json:"label,omitempty"`
RootPass string `json:"root_pass,omitempty"`
AuthorizedKeys []string `json:"authorized_keys,omitempty"`
AuthorizedUsers []string `json:"authorized_users,omitempty"`
StackScriptID int `json:"stackscript_id,omitempty"`
StackScriptData map[string]string `json:"stackscript_data,omitempty"`
BackupID int `json:"backup_id,omitempty"`
Image string `json:"image,omitempty"`
Interfaces []InstanceConfigInterfaceCreateOptions `json:"interfaces,omitempty"`
BackupsEnabled bool `json:"backups_enabled,omitempty"`
PrivateIP bool `json:"private_ip,omitempty"`
Tags []string `json:"tags,omitempty"`
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
FirewallID int `json:"firewall_id,omitempty"`
InterfaceGeneration InterfaceGeneration `json:"interface_generation,omitempty"`
Region string `json:"region"`
Type string `json:"type"`
Label string `json:"label,omitempty"`
RootPass string `json:"root_pass,omitempty"`
AuthorizedKeys []string `json:"authorized_keys,omitempty"`
AuthorizedUsers []string `json:"authorized_users,omitempty"`
StackScriptID int `json:"stackscript_id,omitempty"`
StackScriptData map[string]string `json:"stackscript_data,omitempty"`
BackupID int `json:"backup_id,omitempty"`
Image string `json:"image,omitempty"`
BackupsEnabled bool `json:"backups_enabled,omitempty"`
PrivateIP bool `json:"private_ip,omitempty"`
NetworkHelper *bool `json:"network_helper,omitempty"`
Tags []string `json:"tags,omitempty"`
Metadata *InstanceMetadataOptions `json:"metadata,omitempty"`
FirewallID int `json:"firewall_id,omitempty"`
InterfaceGeneration InterfaceGeneration `json:"interface_generation,omitempty"`

// Linode Interfaces to create the new instance with.
// Conflicts with Interfaces.
// NOTE: Linode Interfaces may not currently be available to all users.
LinodeInterfaces []LinodeInterfaceCreateOptions `json:"-"`

// Legacy (config) Interfaces to create the new instance with.
// Conflicts with LinodeInterfaces.
Interfaces []InstanceConfigInterfaceCreateOptions `json:"-"`

// NOTE: Disk encryption may not currently be available to all users.
DiskEncryption InstanceDiskEncryption `json:"disk_encryption,omitempty"`
Expand Down Expand Up @@ -229,6 +239,83 @@ type InstanceUpdateOptions struct {
Group *string `json:"group,omitempty"`
}

// MarshalJSON contains logic necessary to populate the `interfaces` field of
// InstanceCreateOptions depending on whether Interfaces or LinodeInterfaces
// is specified.
func (i InstanceCreateOptions) MarshalJSON() ([]byte, error) {
type Mask InstanceCreateOptions

resultData := struct {
*Mask

Interfaces any `json:"interfaces,omitempty"`
}{
Mask: (*Mask)(&i),
Interfaces: nil,
}

if i.Interfaces != nil && i.LinodeInterfaces != nil {
return nil, fmt.Errorf("fields Interfaces and LinodeInterfaces cannot be specified together")
}

if i.Interfaces != nil {
resultData.Interfaces = i.Interfaces
}

if i.LinodeInterfaces != nil {
resultData.Interfaces = i.LinodeInterfaces
}

return json.Marshal(resultData)
}

// UnmarshalJSON contains logic necessary to populate the Interfaces field
// depending on the value of interface_generation.
func (i *InstanceCreateOptions) UnmarshalJSON(b []byte) error {
type Mask InstanceCreateOptions

p := struct {
*Mask

GenericInterfaces any `json:"interfaces,omitempty"`
}{
Mask: (*Mask)(i),
}

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

if p.GenericInterfaces == nil {
// No interfaces were given - nothing to do here.
return nil
}

if i.InterfaceGeneration == GenerationLinode {
data := struct {
Interfaces []LinodeInterfaceCreateOptions `json:"interfaces"`
}{}

err := json.Unmarshal(b, &data)
i.LinodeInterfaces = data.Interfaces

return err
}

if i.InterfaceGeneration == GenerationLegacyConfig {
data := struct {
Interfaces []InstanceConfigInterfaceCreateOptions `json:"interfaces"`
}{}

err := json.Unmarshal(b, &data)
i.Interfaces = data.Interfaces

return err
}

return fmt.Errorf("cannot unmarshal interfaces without valid value for interface_generation")
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (i *Instance) UnmarshalJSON(b []byte) error {
type Mask Instance
Expand Down
Loading