From 69ac117b6f0d8c0842f0fe1c787735bfb9237bb0 Mon Sep 17 00:00:00 2001 From: Adam Weingarten <6517820+aweingarten@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:31:30 -0500 Subject: [PATCH 01/15] LKE Node Pools: add isolation and disk encryption options --- lke_node_pools.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lke_node_pools.go b/lke_node_pools.go index d6980451f..8162631b8 100644 --- a/lke_node_pools.go +++ b/lke_node_pools.go @@ -59,6 +59,12 @@ type LKENodePoolTaint struct { Effect LKENodePoolTaintEffect `json:"effect"` } +// LKENodePoolIsolation controls network isolation for nodes in the pool. +type LKENodePoolIsolation struct { + PublicIPv4 bool `json:"public_ipv4"` + PublicIPv6 bool `json:"public_ipv6"` +} + // LKENodePoolLabels represents Kubernetes labels to add to an LKENodePool type LKENodePoolLabels map[string]string @@ -80,6 +86,8 @@ type LKENodePool struct { // NOTE: Disk encryption may not currently be available to all users. DiskEncryption InstanceDiskEncryption `json:"disk_encryption,omitempty"` + Isolation *LKENodePoolIsolation `json:"isolation,omitempty"` + // K8sVersion and UpdateStrategy are only for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. K8sVersion *string `json:"k8s_version,omitempty"` @@ -99,6 +107,10 @@ type LKENodePoolCreateOptions struct { Autoscaler *LKENodePoolAutoscaler `json:"autoscaler,omitempty"` FirewallID *int `json:"firewall_id,omitempty"` + // NOTE: Disk encryption may not currently be available to all users. + DiskEncryption InstanceDiskEncryption `json:"disk_encryption,omitempty"` + Isolation *LKENodePoolIsolation `json:"isolation,omitempty"` + // K8sVersion and UpdateStrategy only works for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. K8sVersion *string `json:"k8s_version,omitempty"` @@ -120,6 +132,8 @@ type LKENodePoolUpdateOptions struct { // It may not currently be available to all users and is under v4beta. K8sVersion *string `json:"k8s_version,omitempty"` UpdateStrategy *LKENodePoolUpdateStrategy `json:"update_strategy,omitempty"` + + Isolation *LKENodePoolIsolation `json:"isolation,omitempty"` } // GetCreateOptions converts a LKENodePool to LKENodePoolCreateOptions for @@ -135,6 +149,8 @@ func (l LKENodePool) GetCreateOptions() (o LKENodePoolCreateOptions) { o.UpdateStrategy = l.UpdateStrategy o.Label = l.Label o.FirewallID = l.FirewallID + o.DiskEncryption = l.DiskEncryption + o.Isolation = l.Isolation return o } @@ -150,6 +166,7 @@ func (l LKENodePool) GetUpdateOptions() (o LKENodePoolUpdateOptions) { o.UpdateStrategy = l.UpdateStrategy o.Label = l.Label o.FirewallID = l.FirewallID + o.Isolation = l.Isolation return o } From 501426807071ca5dfc1d65505169070cf7ff81ff Mon Sep 17 00:00:00 2001 From: Adam Weingarten <6517820+aweingarten@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:31:30 -0500 Subject: [PATCH 02/15] LKE Cluster: add RuleSetIDs for enterprise cluster creation with tests --- lke_clusters.go | 10 ++++ .../lke_cluster_enterprise_create.json | 18 +++++++ test/unit/lke_clusters_test.go | 54 +++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 test/unit/fixtures/lke_cluster_enterprise_create.json diff --git a/lke_clusters.go b/lke_clusters.go index 786b6faaf..f46c00807 100644 --- a/lke_clusters.go +++ b/lke_clusters.go @@ -47,6 +47,16 @@ type LKECluster struct { SubnetID int `json:"subnet_id"` VpcID int `json:"vpc_id"` StackType LKEClusterStackType `json:"stack_type"` + + // RuleSetIDs contains the IDs of the service-managed firewall rulesets + // automatically created for LKE Enterprise clusters. + RuleSetIDs *LKEClusterRuleSetIDs `json:"ruleset_ids,omitempty"` +} + +// LKEClusterRuleSetIDs contains the inbound and outbound ruleset IDs for an LKE-E cluster. +type LKEClusterRuleSetIDs struct { + Inbound int `json:"inbound"` + Outbound int `json:"outbound"` } // LKEClusterCreateOptions fields are those accepted by CreateLKECluster diff --git a/test/unit/fixtures/lke_cluster_enterprise_create.json b/test/unit/fixtures/lke_cluster_enterprise_create.json new file mode 100644 index 000000000..8fe4783d0 --- /dev/null +++ b/test/unit/fixtures/lke_cluster_enterprise_create.json @@ -0,0 +1,18 @@ +{ + "id": 3010, + "label": "enterprise-cluster", + "region": "us-east", + "status": "ready", + "tier": "enterprise", + "subnet_id": 2010, + "vpc_id": 1010, + "stack_type": "ipv4-ipv6", + "control_plane": { + "high_availability": true, + "audit_logs_enabled": true + }, + "ruleset_ids": { + "inbound": 4010, + "outbound": 4011 + } +} diff --git a/test/unit/lke_clusters_test.go b/test/unit/lke_clusters_test.go index 0dfad0d51..39d279395 100644 --- a/test/unit/lke_clusters_test.go +++ b/test/unit/lke_clusters_test.go @@ -121,6 +121,60 @@ func TestLKECluster_Create(t *testing.T) { assert.Equal(t, false, cluster.ControlPlane.AuditLogsEnabled) } +func TestLKECluster_Create_Enterprise_RuleSetIDs(t *testing.T) { + fixtureData, err := fixtures.GetFixture("lke_cluster_enterprise_create") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + createOptions := linodego.LKEClusterCreateOptions{ + Label: "enterprise-cluster", + Region: "us-east", + K8sVersion: "1.31", + Tier: "enterprise", + SubnetID: linodego.Pointer(2010), + VpcID: linodego.Pointer(1010), + StackType: linodego.Pointer(linodego.LKEClusterDualStack), + ControlPlane: &linodego.LKEClusterControlPlaneOptions{ + HighAvailability: linodego.Pointer(true), + AuditLogsEnabled: linodego.Pointer(true), + }, + } + + base.MockPost("lke/clusters", fixtureData) + + cluster, err := base.Client.CreateLKECluster(context.Background(), createOptions) + assert.NoError(t, err) + assert.Equal(t, 3010, cluster.ID) + assert.Equal(t, "enterprise", cluster.Tier) + assert.Equal(t, 2010, cluster.SubnetID) + assert.Equal(t, 1010, cluster.VpcID) + assert.Equal(t, linodego.LKEClusterDualStack, cluster.StackType) + + // Validate ruleset_ids deserialization + assert.NotNil(t, cluster.RuleSetIDs, "RuleSetIDs should not be nil for enterprise clusters") + assert.Equal(t, 4010, cluster.RuleSetIDs.Inbound) + assert.Equal(t, 4011, cluster.RuleSetIDs.Outbound) +} + +func TestLKECluster_Get_NoRuleSetIDs(t *testing.T) { + // Standard clusters do not return ruleset_ids; the field should be nil + fixtureData, err := fixtures.GetFixture("lke_cluster_get") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("lke/clusters/123", fixtureData) + + cluster, err := base.Client.GetLKECluster(context.Background(), 123) + assert.NoError(t, err) + assert.Nil(t, cluster.RuleSetIDs, "RuleSetIDs should be nil for standard clusters") +} + func TestLKECluster_Update(t *testing.T) { fixtureData, err := fixtures.GetFixture("lke_cluster_update") assert.NoError(t, err) From c1931f629f4be94137cfc03d33553a537ee954a4 Mon Sep 17 00:00:00 2001 From: Adam Weingarten <6517820+aweingarten@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:36:25 -0500 Subject: [PATCH 03/15] Add Version field to Firewall and FirewallRuleSet structs with test coverage --- firewall_rules.go | 5 +++++ firewalls.go | 1 + test/unit/firewall_rules_test.go | 4 ++++ test/unit/firewalls_test.go | 8 ++++++++ test/unit/fixtures/firewall_create.json | 3 ++- test/unit/fixtures/firewall_get.json | 3 ++- test/unit/fixtures/firewall_list.json | 3 ++- test/unit/fixtures/firewall_update.json | 3 ++- 8 files changed, 26 insertions(+), 4 deletions(-) diff --git a/firewall_rules.go b/firewall_rules.go index a1e442959..7a13abc90 100644 --- a/firewall_rules.go +++ b/firewall_rules.go @@ -76,6 +76,11 @@ type FirewallRuleSet struct { InboundPolicy string `json:"inbound_policy"` Outbound []FirewallRule `json:"outbound"` OutboundPolicy string `json:"outbound_policy"` + + // Version is the firewall rules version, starting from 1. The version + // number increments when the rules change. Include the version when updating + // rules to avoid conflicts. + Version int `json:"version,omitempty"` } // GetFirewallRules gets the FirewallRuleSet for the given Firewall. diff --git a/firewalls.go b/firewalls.go index 4c3411ae7..2c54377e8 100644 --- a/firewalls.go +++ b/firewalls.go @@ -25,6 +25,7 @@ type Firewall struct { Status FirewallStatus `json:"status"` Tags []string `json:"tags,omitempty"` Rules FirewallRuleSet `json:"rules"` + Version int `json:"version"` Created *time.Time `json:"-"` Updated *time.Time `json:"-"` } diff --git a/test/unit/firewall_rules_test.go b/test/unit/firewall_rules_test.go index cb8b9dea1..410e8e773 100644 --- a/test/unit/firewall_rules_test.go +++ b/test/unit/firewall_rules_test.go @@ -24,6 +24,7 @@ func TestFirewallRule_Get(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, firewallRule) + assert.Equal(t, 1, firewallRule.Version) assert.Equal(t, "DROP", firewallRule.InboundPolicy) assert.Equal(t, 1, len(firewallRule.Inbound)) assert.Equal(t, "ACCEPT", firewallRule.Inbound[0].Action) @@ -119,6 +120,7 @@ func TestFirewallRule_Update(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, firewallRule) + assert.Equal(t, 1, firewallRule.Version) assert.Equal(t, "DROP", firewallRule.InboundPolicy) assert.Equal(t, 1, len(firewallRule.Inbound)) assert.Equal(t, "ACCEPT", firewallRule.Inbound[0].Action) @@ -147,6 +149,7 @@ func TestFirewallRule_GetExpansion(t *testing.T) { outboundIPv6 := []string{"pl::vpcs:"} mockResponse := linodego.FirewallRuleSet{ + Version: 2, Inbound: []linodego.FirewallRule{ { Action: "ACCEPT", @@ -187,6 +190,7 @@ func TestFirewallRule_GetExpansion(t *testing.T) { firewallRuleSet, err := base.Client.GetFirewallRulesExpansion(context.Background(), firewallID) assert.NoError(t, err) assert.NotNil(t, firewallRuleSet) + assert.Equal(t, 2, firewallRuleSet.Version) if assert.Len(t, firewallRuleSet.Inbound, 1) { assert.Equal(t, "ACCEPT", firewallRuleSet.Inbound[0].Action) diff --git a/test/unit/firewalls_test.go b/test/unit/firewalls_test.go index 9d7880a36..b5ee80007 100644 --- a/test/unit/firewalls_test.go +++ b/test/unit/firewalls_test.go @@ -30,8 +30,10 @@ func TestFirewall_List(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) + assert.Equal(t, 2, firewall.Version) assert.Equal(t, "DROP", firewall.Rules.InboundPolicy) + assert.Equal(t, 1, firewall.Rules.Version) assert.Len(t, firewall.Rules.Inbound, 1) inboundRule := firewall.Rules.Inbound[0] @@ -112,11 +114,13 @@ func TestFirewall_Create(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) + assert.Equal(t, 1, firewall.Version) assert.ElementsMatch(t, []string{"example tag", "another example"}, firewall.Tags) assert.NotNil(t, firewall.Rules) assert.Equal(t, "DROP", firewall.Rules.InboundPolicy) assert.Equal(t, "DROP", firewall.Rules.OutboundPolicy) + assert.Equal(t, 1, firewall.Rules.Version) assert.Len(t, firewall.Rules.Inbound, 1) inboundRule := firewall.Rules.Inbound[0] @@ -158,6 +162,7 @@ func TestFirewall_Get(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) + assert.Equal(t, 2, firewall.Version) assert.Equal(t, "2018-01-01T00:01:01Z", firewall.Created.Format(time.RFC3339)) assert.Equal(t, "2018-01-02T00:01:01Z", firewall.Updated.Format(time.RFC3339)) assert.ElementsMatch(t, []string{"example tag", "another example"}, firewall.Tags) @@ -165,6 +170,7 @@ func TestFirewall_Get(t *testing.T) { assert.NotNil(t, firewall.Rules) assert.Equal(t, "DROP", firewall.Rules.InboundPolicy) assert.Equal(t, "DROP", firewall.Rules.OutboundPolicy) + assert.Equal(t, 1, firewall.Rules.Version) assert.Len(t, firewall.Rules.Inbound, 1) inboundRule := firewall.Rules.Inbound[0] @@ -212,6 +218,7 @@ func TestFirewall_Update(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) + assert.Equal(t, 3, firewall.Version) assert.Equal(t, "2018-01-01T00:01:01Z", firewall.Created.Format(time.RFC3339)) assert.Equal(t, "2018-01-02T00:01:01Z", firewall.Updated.Format(time.RFC3339)) assert.ElementsMatch(t, []string{"updated tag", "another updated tag"}, firewall.Tags) @@ -219,6 +226,7 @@ func TestFirewall_Update(t *testing.T) { assert.NotNil(t, firewall.Rules) assert.Equal(t, "DROP", firewall.Rules.InboundPolicy) assert.Equal(t, "DROP", firewall.Rules.OutboundPolicy) + assert.Equal(t, 1, firewall.Rules.Version) assert.Len(t, firewall.Rules.Inbound, 1) inboundRule := firewall.Rules.Inbound[0] diff --git a/test/unit/fixtures/firewall_create.json b/test/unit/fixtures/firewall_create.json index b425fca39..f99a9589b 100644 --- a/test/unit/fixtures/firewall_create.json +++ b/test/unit/fixtures/firewall_create.json @@ -49,5 +49,6 @@ "example tag", "another example" ], - "updated": "2018-01-02T00:01:01" + "updated": "2018-01-02T00:01:01", + "version": 1 } \ No newline at end of file diff --git a/test/unit/fixtures/firewall_get.json b/test/unit/fixtures/firewall_get.json index b425fca39..117da619c 100644 --- a/test/unit/fixtures/firewall_get.json +++ b/test/unit/fixtures/firewall_get.json @@ -49,5 +49,6 @@ "example tag", "another example" ], - "updated": "2018-01-02T00:01:01" + "updated": "2018-01-02T00:01:01", + "version": 2 } \ No newline at end of file diff --git a/test/unit/fixtures/firewall_list.json b/test/unit/fixtures/firewall_list.json index 3417d9f9a..a9b441b23 100644 --- a/test/unit/fixtures/firewall_list.json +++ b/test/unit/fixtures/firewall_list.json @@ -51,7 +51,8 @@ "example tag", "another example" ], - "updated": "2018-01-02T00:01:01" + "updated": "2018-01-02T00:01:01", + "version": 2 } ], "page": 1, diff --git a/test/unit/fixtures/firewall_update.json b/test/unit/fixtures/firewall_update.json index b738a2677..05c7f2eca 100644 --- a/test/unit/fixtures/firewall_update.json +++ b/test/unit/fixtures/firewall_update.json @@ -49,5 +49,6 @@ "updated tag", "another updated tag" ], - "updated": "2018-01-02T00:01:01" + "updated": "2018-01-02T00:01:01", + "version": 3 } \ No newline at end of file From 271eb30b34ba925cdf6dbab2886a7058a9a5a2a3 Mon Sep 17 00:00:00 2001 From: Adam Weingarten <6517820+aweingarten@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:36:43 -0500 Subject: [PATCH 04/15] Add GetPrefixListByName helper with test coverage --- prefixlists.go | 26 ++++++++++++++ test/unit/prefixlists_test.go | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/prefixlists.go b/prefixlists.go index 37aede165..9118655d7 100644 --- a/prefixlists.go +++ b/prefixlists.go @@ -3,6 +3,7 @@ package linodego import ( "context" "encoding/json" + "fmt" "time" "github.com/linode/linodego/internal/parseabletime" @@ -59,3 +60,28 @@ func (c *Client) GetPrefixList(ctx context.Context, id int) (*PrefixList, error) endpoint := formatAPIPath("networking/prefixlists/%d", id) return doGETRequest[PrefixList](ctx, c, endpoint) } + +// GetPrefixListByName finds a Prefix List by its name (e.g., "pl:system:object-storage:us-iad"). +// Returns nil and an error if no matching prefix list is found. +func (c *Client) GetPrefixListByName(ctx context.Context, name string) (*PrefixList, error) { + f := Filter{} + f.AddField(Eq, "name", name) + + fJSON, err := f.MarshalJSON() + if err != nil { + return nil, err + } + + opts := ListOptions{Filter: string(fJSON)} + + lists, err := c.ListPrefixLists(ctx, &opts) + if err != nil { + return nil, err + } + + if len(lists) == 0 { + return nil, fmt.Errorf("prefix list with name %q not found", name) + } + + return &lists[0], nil +} diff --git a/test/unit/prefixlists_test.go b/test/unit/prefixlists_test.go index 69cf95744..0bffd2f2a 100644 --- a/test/unit/prefixlists_test.go +++ b/test/unit/prefixlists_test.go @@ -119,3 +119,68 @@ func TestPrefixList_UnmarshalJSON(t *testing.T) { } assert.Nil(t, prefixList.Deleted) } + +func TestPrefixLists_GetByName(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + response := map[string]any{ + "data": []map[string]any{ + { + "id": 999, + "name": "pl:system:resolvers:us-iad:staging", + "description": "Resolver ACL", + "visibility": "restricted", + "source_prefixlist_id": nil, + "ipv4": []string{"139.144.192.62"}, + "ipv6": []string{"2600:3c05:e001:bc::1"}, + "version": 7, + "created": "2021-01-01T00:00:00", + "updated": "2021-06-01T00:00:00", + "deleted": nil, + }, + }, + "page": 1, + "pages": 1, + "results": 1, + } + + base.MockGet("networking/prefixlists", response) + + pl, err := base.Client.GetPrefixListByName(context.Background(), "pl:system:resolvers:us-iad:staging") + assert.NoError(t, err) + assert.NotNil(t, pl) + + assert.Equal(t, 999, pl.ID) + assert.Equal(t, "pl:system:resolvers:us-iad:staging", pl.Name) + assert.Equal(t, "Resolver ACL", pl.Description) + assert.Equal(t, "restricted", pl.Visibility) + assert.Equal(t, 7, pl.Version) + if assert.NotNil(t, pl.IPv4) { + assert.Equal(t, []string{"139.144.192.62"}, *pl.IPv4) + } + if assert.NotNil(t, pl.IPv6) { + assert.Equal(t, []string{"2600:3c05:e001:bc::1"}, *pl.IPv6) + } +} + +func TestPrefixLists_GetByName_NotFound(t *testing.T) { + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + response := map[string]any{ + "data": []map[string]any{}, + "page": 1, + "pages": 1, + "results": 0, + } + + base.MockGet("networking/prefixlists", response) + + pl, err := base.Client.GetPrefixListByName(context.Background(), "pl:nonexistent") + assert.Error(t, err) + assert.Nil(t, pl) + assert.Contains(t, err.Error(), "not found") +} From 9ac25cceae263e8399d0c32f559db37d4539856f Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 3 Jun 2026 16:27:51 -0400 Subject: [PATCH 05/15] Build fix and format --- firewalls.go | 2 +- lke_node_pools.go | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/firewalls.go b/firewalls.go index feff57370..c95766180 100644 --- a/firewalls.go +++ b/firewalls.go @@ -26,7 +26,7 @@ type Firewall struct { Tags []string `json:"tags"` Rules FirewallRules `json:"rules"` Entities []FirewallDeviceEntity `json:"entities"` - Version int `json:"version"` + Version int `json:"version"` Created *time.Time `json:"-"` Updated *time.Time `json:"-"` } diff --git a/lke_node_pools.go b/lke_node_pools.go index 829eb00e1..6bc482184 100644 --- a/lke_node_pools.go +++ b/lke_node_pools.go @@ -85,7 +85,7 @@ type LKENodePool struct { DiskEncryption InstanceDiskEncryption `json:"disk_encryption,omitzero"` - Isolation *LKENodePoolIsolation `json:"isolation,omitempty"` + Isolation *LKENodePoolIsolation `json:"isolation,omitzero"` // K8sVersion and UpdateStrategy are only for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. @@ -108,10 +108,7 @@ type LKENodePoolCreateOptions struct { Autoscaler *LKENodePoolAutoscaler `json:"autoscaler,omitzero"` FirewallID *int `json:"firewall_id,omitzero"` - - // NOTE: Disk encryption may not currently be available to all users. - DiskEncryption InstanceDiskEncryption `json:"disk_encryption,omitempty"` - Isolation *LKENodePoolIsolation `json:"isolation,omitempty"` + Isolation *LKENodePoolIsolation `json:"isolation,omitzero"` // K8sVersion and UpdateStrategy only works for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. @@ -136,7 +133,7 @@ type LKENodePoolUpdateOptions struct { // It may not currently be available to all users and is under v4beta. K8sVersion *string `json:"k8s_version,omitzero"` UpdateStrategy *LKENodePoolUpdateStrategy `json:"update_strategy,omitzero"` - Isolation *LKENodePoolIsolation `json:"isolation,omitzero"` + Isolation *LKENodePoolIsolation `json:"isolation,omitzero"` } // GetCreateOptions converts a LKENodePool to LKENodePoolCreateOptions for From 3a330a2be1878f575f7df5db317c687898d0871b Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 3 Jun 2026 16:29:38 -0400 Subject: [PATCH 06/15] Fix JSON omitzero tag for LKENodePoolIsolation field --- lke_node_pools.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lke_node_pools.go b/lke_node_pools.go index 6bc482184..3bada4f7e 100644 --- a/lke_node_pools.go +++ b/lke_node_pools.go @@ -85,7 +85,7 @@ type LKENodePool struct { DiskEncryption InstanceDiskEncryption `json:"disk_encryption,omitzero"` - Isolation *LKENodePoolIsolation `json:"isolation,omitzero"` + Isolation *LKENodePoolIsolation `json:"isolation"` // K8sVersion and UpdateStrategy are only for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. From e0f5b370b05a6b983e09adaddf992bd1af6726c9 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 3 Jun 2026 17:26:23 -0400 Subject: [PATCH 07/15] Add LKENodePoolIsolationCreateOptions and LKENodePoolIsolationUpdateOptions for network isolation --- lke_node_pools.go | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/lke_node_pools.go b/lke_node_pools.go index 3bada4f7e..d61351e7b 100644 --- a/lke_node_pools.go +++ b/lke_node_pools.go @@ -65,6 +65,18 @@ type LKENodePoolIsolation struct { PublicIPv6 bool `json:"public_ipv6"` } +// LKENodePoolIsolationCreateOptions controls network isolation for node pool create requests. +type LKENodePoolIsolationCreateOptions struct { + PublicIPv4 *bool `json:"public_ipv4,omitzero"` + PublicIPv6 *bool `json:"public_ipv6,omitzero"` +} + +// LKENodePoolIsolationUpdateOptions controls network isolation for node pool update requests. +type LKENodePoolIsolationUpdateOptions struct { + PublicIPv4 *bool `json:"public_ipv4,omitzero"` + PublicIPv6 *bool `json:"public_ipv6,omitzero"` +} + // LKENodePoolLabels represents Kubernetes labels to add to an LKENodePool type LKENodePoolLabels map[string]string @@ -106,9 +118,9 @@ type LKENodePoolCreateOptions struct { Taints []LKENodePoolTaint `json:"taints"` Label *string `json:"label,omitzero"` - Autoscaler *LKENodePoolAutoscaler `json:"autoscaler,omitzero"` - FirewallID *int `json:"firewall_id,omitzero"` - Isolation *LKENodePoolIsolation `json:"isolation,omitzero"` + Autoscaler *LKENodePoolAutoscaler `json:"autoscaler,omitzero"` + FirewallID *int `json:"firewall_id,omitzero"` + Isolation *LKENodePoolIsolationCreateOptions `json:"isolation,omitzero"` // K8sVersion and UpdateStrategy only works for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. @@ -131,9 +143,9 @@ type LKENodePoolUpdateOptions struct { // K8sVersion and UpdateStrategy only works for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. - K8sVersion *string `json:"k8s_version,omitzero"` - UpdateStrategy *LKENodePoolUpdateStrategy `json:"update_strategy,omitzero"` - Isolation *LKENodePoolIsolation `json:"isolation,omitzero"` + K8sVersion *string `json:"k8s_version,omitzero"` + UpdateStrategy *LKENodePoolUpdateStrategy `json:"update_strategy,omitzero"` + Isolation *LKENodePoolIsolationUpdateOptions `json:"isolation,omitzero"` } // GetCreateOptions converts a LKENodePool to LKENodePoolCreateOptions for @@ -149,9 +161,15 @@ func (l LKENodePool) GetCreateOptions() (o LKENodePoolCreateOptions) { o.UpdateStrategy = l.UpdateStrategy o.Label = l.Label o.FirewallID = l.FirewallID - o.Isolation = l.Isolation o.DiskEncryption = &l.DiskEncryption + if l.Isolation != nil { + o.Isolation = &LKENodePoolIsolationCreateOptions{ + PublicIPv4: &l.Isolation.PublicIPv4, + PublicIPv6: &l.Isolation.PublicIPv6, + } + } + return o } @@ -166,7 +184,13 @@ func (l LKENodePool) GetUpdateOptions() (o LKENodePoolUpdateOptions) { o.UpdateStrategy = l.UpdateStrategy o.Label = l.Label o.FirewallID = l.FirewallID - o.Isolation = l.Isolation + + if l.Isolation != nil { + o.Isolation = &LKENodePoolIsolationUpdateOptions{ + PublicIPv4: &l.Isolation.PublicIPv4, + PublicIPv6: &l.Isolation.PublicIPv6, + } + } return o } From e7be7ca84adec3c8db5a4a5ceefd4ea0ad9f7fff Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 3 Jun 2026 17:26:34 -0400 Subject: [PATCH 08/15] Clarify JSON omitzero usage for optional fields in create and update options structs --- AGENTS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 285914055..6796f0c36 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,8 @@ - `NewClient` reads `LINODE_URL`, `LINODE_API_VERSION`, `LINODE_CA`, and `LINODE_DEBUG`; `NewClientFromEnv` prefers `LINODE_TOKEN` over config-file profiles. ## Conventions And Gotchas -- Many option structs use `json:",omitzero"`; use pointers for fields that must serialize explicit zero values. +- Optional fields in create or update options structs must use `json:",omitzero"`. +- Optional fields in create or update options structs must be pointer types so explicit zero values can be serialized when needed. - List APIs mutate the supplied `*ListOptions` with `Page`, `Pages`, and `Results`; do not reuse one `ListOptions` across list calls. - Use `formatAPIPath` for endpoint paths with user-provided string path segments so path escaping matches the client helpers. - CI enforces PR titles like `TPT-1234: Description` unless labels exempt the PR (`dependencies`, `hotfix`, `community-contribution`, `ignore-for-release`). From 1d69391967fb422332c291efd44a883d738f706b Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 3 Jun 2026 18:01:26 -0400 Subject: [PATCH 09/15] Remove LKECluster RuleSetIDs and Firewall Version fields --- firewalls.go | 1 - lke_clusters.go | 10 ------ test/unit/firewall_rules_test.go | 1 - test/unit/firewalls_test.go | 4 --- test/unit/lke_clusters_test.go | 54 -------------------------------- 5 files changed, 70 deletions(-) diff --git a/firewalls.go b/firewalls.go index c95766180..34d5b547d 100644 --- a/firewalls.go +++ b/firewalls.go @@ -26,7 +26,6 @@ type Firewall struct { Tags []string `json:"tags"` Rules FirewallRules `json:"rules"` Entities []FirewallDeviceEntity `json:"entities"` - Version int `json:"version"` Created *time.Time `json:"-"` Updated *time.Time `json:"-"` } diff --git a/lke_clusters.go b/lke_clusters.go index 392fd354c..2e7f21feb 100644 --- a/lke_clusters.go +++ b/lke_clusters.go @@ -50,16 +50,6 @@ type LKECluster struct { // NOTE: Locks can only be used with v4beta. Locks []LockType `json:"locks"` - - // RuleSetIDs contains the IDs of the service-managed firewall rulesets - // automatically created for LKE Enterprise clusters. - RuleSetIDs *LKEClusterRuleSetIDs `json:"ruleset_ids"` -} - -// LKEClusterRuleSetIDs contains the inbound and outbound ruleset IDs for an LKE-E cluster. -type LKEClusterRuleSetIDs struct { - Inbound int `json:"inbound"` - Outbound int `json:"outbound"` } // LKEClusterCreateOptions fields are those accepted by CreateLKECluster diff --git a/test/unit/firewall_rules_test.go b/test/unit/firewall_rules_test.go index f9525fbee..e723c07f1 100644 --- a/test/unit/firewall_rules_test.go +++ b/test/unit/firewall_rules_test.go @@ -148,7 +148,6 @@ func TestFirewallRule_GetExpansion(t *testing.T) { outboundIPv6 := []string{"pl::vpcs:"} mockResponse := linodego.FirewallRules{ - Version: 2, Inbound: []linodego.FirewallRuleInbound{ { Action: "ACCEPT", diff --git a/test/unit/firewalls_test.go b/test/unit/firewalls_test.go index 3c7f6406a..cce91273a 100644 --- a/test/unit/firewalls_test.go +++ b/test/unit/firewalls_test.go @@ -31,7 +31,6 @@ func TestFirewall_List(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) - assert.Equal(t, 2, firewall.Version) assert.Equal(t, "DROP", firewall.Rules.InboundPolicy) assert.Equal(t, 1, firewall.Rules.Version) @@ -115,7 +114,6 @@ func TestFirewall_Create(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) - assert.Equal(t, 1, firewall.Version) assert.ElementsMatch(t, []string{"example tag", "another example"}, firewall.Tags) assert.Equal(t, "2018-01-01T00:01:01Z", firewall.Created.Format(time.RFC3339)) assert.Equal(t, "2018-01-02T00:01:01Z", firewall.Updated.Format(time.RFC3339)) @@ -179,7 +177,6 @@ func TestFirewall_Get(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) - assert.Equal(t, 2, firewall.Version) assert.Equal(t, "2018-01-01T00:01:01Z", firewall.Created.Format(time.RFC3339)) assert.Equal(t, "2018-01-02T00:01:01Z", firewall.Updated.Format(time.RFC3339)) assert.ElementsMatch(t, []string{"example tag", "another example"}, firewall.Tags) @@ -235,7 +232,6 @@ func TestFirewall_Update(t *testing.T) { assert.Equal(t, 123, firewall.ID) assert.Equal(t, "firewall123", firewall.Label) assert.Equal(t, linodego.FirewallStatus("enabled"), firewall.Status) - assert.Equal(t, 3, firewall.Version) assert.Equal(t, "2018-01-01T00:01:01Z", firewall.Created.Format(time.RFC3339)) assert.Equal(t, "2018-01-02T00:01:01Z", firewall.Updated.Format(time.RFC3339)) assert.ElementsMatch(t, []string{"updated tag", "another updated tag"}, firewall.Tags) diff --git a/test/unit/lke_clusters_test.go b/test/unit/lke_clusters_test.go index 00fc562f5..88938da2e 100644 --- a/test/unit/lke_clusters_test.go +++ b/test/unit/lke_clusters_test.go @@ -124,60 +124,6 @@ func TestLKECluster_Create(t *testing.T) { assert.Equal(t, false, cluster.ControlPlane.AuditLogsEnabled) } -func TestLKECluster_Create_Enterprise_RuleSetIDs(t *testing.T) { - fixtureData, err := fixtures.GetFixture("lke_cluster_enterprise_create") - assert.NoError(t, err) - - var base ClientBaseCase - base.SetUp(t) - defer base.TearDown(t) - - createOptions := linodego.LKEClusterCreateOptions{ - Label: "enterprise-cluster", - Region: "us-east", - K8sVersion: "1.31", - Tier: "enterprise", - SubnetID: linodego.Pointer(2010), - VpcID: linodego.Pointer(1010), - StackType: linodego.Pointer(linodego.LKEClusterDualStack), - ControlPlane: &linodego.LKEClusterControlPlaneOptions{ - HighAvailability: linodego.Pointer(true), - AuditLogsEnabled: linodego.Pointer(true), - }, - } - - base.MockPost("lke/clusters", fixtureData) - - cluster, err := base.Client.CreateLKECluster(context.Background(), createOptions) - assert.NoError(t, err) - assert.Equal(t, 3010, cluster.ID) - assert.Equal(t, "enterprise", cluster.Tier) - assert.Equal(t, 2010, cluster.SubnetID) - assert.Equal(t, 1010, cluster.VpcID) - assert.Equal(t, linodego.LKEClusterDualStack, cluster.StackType) - - // Validate ruleset_ids deserialization - assert.NotNil(t, cluster.RuleSetIDs, "RuleSetIDs should not be nil for enterprise clusters") - assert.Equal(t, 4010, cluster.RuleSetIDs.Inbound) - assert.Equal(t, 4011, cluster.RuleSetIDs.Outbound) -} - -func TestLKECluster_Get_NoRuleSetIDs(t *testing.T) { - // Standard clusters do not return ruleset_ids; the field should be nil - fixtureData, err := fixtures.GetFixture("lke_cluster_get") - assert.NoError(t, err) - - var base ClientBaseCase - base.SetUp(t) - defer base.TearDown(t) - - base.MockGet("lke/clusters/123", fixtureData) - - cluster, err := base.Client.GetLKECluster(context.Background(), 123) - assert.NoError(t, err) - assert.Nil(t, cluster.RuleSetIDs, "RuleSetIDs should be nil for standard clusters") -} - func TestLKECluster_Update(t *testing.T) { fixtureData, err := fixtures.GetFixture("lke_cluster_update") assert.NoError(t, err) From a2ab3e9fdc90d3001e25014d571373d221113919 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 3 Jun 2026 18:07:03 -0400 Subject: [PATCH 10/15] Deep clean of firewall version and cluster rules set things --- prefixlists.go | 26 -------- test/unit/fixtures/firewall_create.json | 1 - test/unit/fixtures/firewall_get.json | 3 +- test/unit/fixtures/firewall_list.json | 3 +- test/unit/fixtures/firewall_update.json | 3 +- .../lke_cluster_enterprise_create.json | 18 ----- test/unit/prefixlists_test.go | 65 ------------------- 7 files changed, 3 insertions(+), 116 deletions(-) delete mode 100644 test/unit/fixtures/lke_cluster_enterprise_create.json diff --git a/prefixlists.go b/prefixlists.go index 77cc8fc87..2b661aa47 100644 --- a/prefixlists.go +++ b/prefixlists.go @@ -3,7 +3,6 @@ package linodego import ( "context" "encoding/json" - "fmt" "time" "github.com/linode/linodego/v2/internal/parseabletime" @@ -60,28 +59,3 @@ func (c *Client) GetPrefixList(ctx context.Context, id int) (*PrefixList, error) endpoint := formatAPIPath("networking/prefixlists/%d", id) return doGETRequest[PrefixList](ctx, c, endpoint) } - -// GetPrefixListByName finds a Prefix List by its name (e.g., "pl:system:object-storage:us-iad"). -// Returns nil and an error if no matching prefix list is found. -func (c *Client) GetPrefixListByName(ctx context.Context, name string) (*PrefixList, error) { - f := Filter{} - f.AddField(Eq, "name", name) - - fJSON, err := f.MarshalJSON() - if err != nil { - return nil, err - } - - opts := ListOptions{Filter: string(fJSON)} - - lists, err := c.ListPrefixLists(ctx, &opts) - if err != nil { - return nil, err - } - - if len(lists) == 0 { - return nil, fmt.Errorf("prefix list with name %q not found", name) - } - - return &lists[0], nil -} diff --git a/test/unit/fixtures/firewall_create.json b/test/unit/fixtures/firewall_create.json index 21e09742c..18e37afea 100644 --- a/test/unit/fixtures/firewall_create.json +++ b/test/unit/fixtures/firewall_create.json @@ -49,7 +49,6 @@ "example tag", "another example" ], - "version": 1 "entities": [ { "id": 189031, diff --git a/test/unit/fixtures/firewall_get.json b/test/unit/fixtures/firewall_get.json index 117da619c..b425fca39 100644 --- a/test/unit/fixtures/firewall_get.json +++ b/test/unit/fixtures/firewall_get.json @@ -49,6 +49,5 @@ "example tag", "another example" ], - "updated": "2018-01-02T00:01:01", - "version": 2 + "updated": "2018-01-02T00:01:01" } \ No newline at end of file diff --git a/test/unit/fixtures/firewall_list.json b/test/unit/fixtures/firewall_list.json index a9b441b23..3417d9f9a 100644 --- a/test/unit/fixtures/firewall_list.json +++ b/test/unit/fixtures/firewall_list.json @@ -51,8 +51,7 @@ "example tag", "another example" ], - "updated": "2018-01-02T00:01:01", - "version": 2 + "updated": "2018-01-02T00:01:01" } ], "page": 1, diff --git a/test/unit/fixtures/firewall_update.json b/test/unit/fixtures/firewall_update.json index 05c7f2eca..b738a2677 100644 --- a/test/unit/fixtures/firewall_update.json +++ b/test/unit/fixtures/firewall_update.json @@ -49,6 +49,5 @@ "updated tag", "another updated tag" ], - "updated": "2018-01-02T00:01:01", - "version": 3 + "updated": "2018-01-02T00:01:01" } \ No newline at end of file diff --git a/test/unit/fixtures/lke_cluster_enterprise_create.json b/test/unit/fixtures/lke_cluster_enterprise_create.json deleted file mode 100644 index 8fe4783d0..000000000 --- a/test/unit/fixtures/lke_cluster_enterprise_create.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": 3010, - "label": "enterprise-cluster", - "region": "us-east", - "status": "ready", - "tier": "enterprise", - "subnet_id": 2010, - "vpc_id": 1010, - "stack_type": "ipv4-ipv6", - "control_plane": { - "high_availability": true, - "audit_logs_enabled": true - }, - "ruleset_ids": { - "inbound": 4010, - "outbound": 4011 - } -} diff --git a/test/unit/prefixlists_test.go b/test/unit/prefixlists_test.go index cbed9739d..4f0ec285d 100644 --- a/test/unit/prefixlists_test.go +++ b/test/unit/prefixlists_test.go @@ -119,68 +119,3 @@ func TestPrefixList_UnmarshalJSON(t *testing.T) { } assert.Nil(t, prefixList.Deleted) } - -func TestPrefixLists_GetByName(t *testing.T) { - var base ClientBaseCase - base.SetUp(t) - defer base.TearDown(t) - - response := map[string]any{ - "data": []map[string]any{ - { - "id": 999, - "name": "pl:system:resolvers:us-iad:staging", - "description": "Resolver ACL", - "visibility": "restricted", - "source_prefixlist_id": nil, - "ipv4": []string{"139.144.192.62"}, - "ipv6": []string{"2600:3c05:e001:bc::1"}, - "version": 7, - "created": "2021-01-01T00:00:00", - "updated": "2021-06-01T00:00:00", - "deleted": nil, - }, - }, - "page": 1, - "pages": 1, - "results": 1, - } - - base.MockGet("networking/prefixlists", response) - - pl, err := base.Client.GetPrefixListByName(context.Background(), "pl:system:resolvers:us-iad:staging") - assert.NoError(t, err) - assert.NotNil(t, pl) - - assert.Equal(t, 999, pl.ID) - assert.Equal(t, "pl:system:resolvers:us-iad:staging", pl.Name) - assert.Equal(t, "Resolver ACL", pl.Description) - assert.Equal(t, "restricted", pl.Visibility) - assert.Equal(t, 7, pl.Version) - if assert.NotNil(t, pl.IPv4) { - assert.Equal(t, []string{"139.144.192.62"}, *pl.IPv4) - } - if assert.NotNil(t, pl.IPv6) { - assert.Equal(t, []string{"2600:3c05:e001:bc::1"}, *pl.IPv6) - } -} - -func TestPrefixLists_GetByName_NotFound(t *testing.T) { - var base ClientBaseCase - base.SetUp(t) - defer base.TearDown(t) - - response := map[string]any{ - "data": []map[string]any{}, - "page": 1, - "pages": 1, - "results": 0, - } - - base.MockGet("networking/prefixlists", response) - - pl, err := base.Client.GetPrefixListByName(context.Background(), "pl:nonexistent") - assert.Error(t, err) - assert.Nil(t, pl) - assert.Contains(t, err.Error(), "not found") -} From 8dccc6d3fa57d26497e6e8356fff6a420fade2cb Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:16:30 -0400 Subject: [PATCH 11/15] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- test/unit/firewall_rules_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/firewall_rules_test.go b/test/unit/firewall_rules_test.go index e723c07f1..d9f61ee55 100644 --- a/test/unit/firewall_rules_test.go +++ b/test/unit/firewall_rules_test.go @@ -119,7 +119,6 @@ func TestFirewallRule_Update(t *testing.T) { assert.Equal(t, 1, firewallRule.Version) assert.Equal(t, "997dd135", firewallRule.Fingerprint) - assert.Equal(t, 1, firewallRule.Version) assert.Equal(t, "DROP", firewallRule.InboundPolicy) assert.Equal(t, 1, len(firewallRule.Inbound)) assert.Equal(t, "ACCEPT", firewallRule.Inbound[0].Action) From d899620753aeffc41110b73fd29ad4c6ee2651d0 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Wed, 3 Jun 2026 20:37:16 -0400 Subject: [PATCH 12/15] Add isolation options to node pool create and update fixtures; update tests accordingly --- test/unit/fixtures/lke_node_pool_create.json | 6 +++++- test/unit/fixtures/lke_node_pool_get.json | 4 ++++ test/unit/fixtures/lke_node_pool_update.json | 6 +++++- test/unit/lke_node_pools_test.go | 20 ++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/test/unit/fixtures/lke_node_pool_create.json b/test/unit/fixtures/lke_node_pool_create.json index d710efd41..9218fbb82 100644 --- a/test/unit/fixtures/lke_node_pool_create.json +++ b/test/unit/fixtures/lke_node_pool_create.json @@ -16,5 +16,9 @@ "min": 1, "max": 5 }, - "label": "custom-label-create" + "label": "custom-label-create", + "isolation": { + "public_ipv4": true, + "public_ipv6": false + } } diff --git a/test/unit/fixtures/lke_node_pool_get.json b/test/unit/fixtures/lke_node_pool_get.json index 7a5592a4a..f07dc8458 100644 --- a/test/unit/fixtures/lke_node_pool_get.json +++ b/test/unit/fixtures/lke_node_pool_get.json @@ -9,6 +9,10 @@ "enabled": true, "min": 1, "max": 5 + }, + "isolation": { + "public_ipv4": true, + "public_ipv6": false } } \ No newline at end of file diff --git a/test/unit/fixtures/lke_node_pool_update.json b/test/unit/fixtures/lke_node_pool_update.json index 610e00d29..f6e86ce59 100644 --- a/test/unit/fixtures/lke_node_pool_update.json +++ b/test/unit/fixtures/lke_node_pool_update.json @@ -9,5 +9,9 @@ "min": 2, "max": 8 }, - "label": "custom-label-update" + "label": "custom-label-update", + "isolation": { + "public_ipv4": false, + "public_ipv6": true + } } diff --git a/test/unit/lke_node_pools_test.go b/test/unit/lke_node_pools_test.go index 9597ba6f5..52c054ea6 100644 --- a/test/unit/lke_node_pools_test.go +++ b/test/unit/lke_node_pools_test.go @@ -74,6 +74,10 @@ func TestLKENodePool_Get(t *testing.T) { assert.Equal(t, 3, nodePool.Count) assert.Equal(t, []string{"tag1", "tag2"}, nodePool.Tags) assert.Equal(t, []linodego.LockType{linodego.LockTypeCannotDelete}, nodePool.Locks) + if assert.NotNil(t, nodePool.Isolation) { + assert.True(t, nodePool.Isolation.PublicIPv4) + assert.False(t, nodePool.Isolation.PublicIPv6) + } } func TestLKENodePool_Create(t *testing.T) { @@ -100,6 +104,10 @@ func TestLKENodePool_Create(t *testing.T) { Max: 5, }, Label: &label, + Isolation: &linodego.LKENodePoolIsolationCreateOptions{ + PublicIPv4: Ptr(true), + PublicIPv6: Ptr(false), + }, } base.MockPost("lke/clusters/123/pools", fixtureData) @@ -112,6 +120,10 @@ func TestLKENodePool_Create(t *testing.T) { assert.Equal(t, 1, nodePool.Autoscaler.Min) assert.Equal(t, 5, nodePool.Autoscaler.Max) assert.Equal(t, &label, nodePool.Label) + if assert.NotNil(t, nodePool.Isolation) { + assert.True(t, nodePool.Isolation.PublicIPv4) + assert.False(t, nodePool.Isolation.PublicIPv6) + } } func TestLKENodePool_Update(t *testing.T) { @@ -134,6 +146,10 @@ func TestLKENodePool_Update(t *testing.T) { Max: 8, }, Label: &label, + Isolation: &linodego.LKENodePoolIsolationUpdateOptions{ + PublicIPv4: Ptr(false), + PublicIPv6: Ptr(true), + }, } base.MockPut("lke/clusters/123/pools/456", fixtureData) @@ -146,6 +162,10 @@ func TestLKENodePool_Update(t *testing.T) { assert.Equal(t, 2, nodePool.Autoscaler.Min) assert.Equal(t, 8, nodePool.Autoscaler.Max) assert.Equal(t, &label, nodePool.Label) + if assert.NotNil(t, nodePool.Isolation) { + assert.False(t, nodePool.Isolation.PublicIPv4) + assert.True(t, nodePool.Isolation.PublicIPv6) + } } func TestLKENodePool_Delete(t *testing.T) { From 27d6b847c0de6c6159b2bebb84f608cac504932f Mon Sep 17 00:00:00 2001 From: Zhiwei Liang <121905282+zliang-akamai@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:33:06 -0400 Subject: [PATCH 13/15] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lke_node_pools.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lke_node_pools.go b/lke_node_pools.go index d61351e7b..076b80fe8 100644 --- a/lke_node_pools.go +++ b/lke_node_pools.go @@ -165,8 +165,8 @@ func (l LKENodePool) GetCreateOptions() (o LKENodePoolCreateOptions) { if l.Isolation != nil { o.Isolation = &LKENodePoolIsolationCreateOptions{ - PublicIPv4: &l.Isolation.PublicIPv4, - PublicIPv6: &l.Isolation.PublicIPv6, + PublicIPv4: Pointer(l.Isolation.PublicIPv4), + PublicIPv6: Pointer(l.Isolation.PublicIPv6), } } @@ -187,8 +187,8 @@ func (l LKENodePool) GetUpdateOptions() (o LKENodePoolUpdateOptions) { if l.Isolation != nil { o.Isolation = &LKENodePoolIsolationUpdateOptions{ - PublicIPv4: &l.Isolation.PublicIPv4, - PublicIPv6: &l.Isolation.PublicIPv6, + PublicIPv4: Pointer(l.Isolation.PublicIPv4), + PublicIPv6: Pointer(l.Isolation.PublicIPv6), } } From 3dde26ced932a604838a58b58905daffe442ae13 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Thu, 4 Jun 2026 10:49:32 -0400 Subject: [PATCH 14/15] Add availability notes --- lke_node_pools.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lke_node_pools.go b/lke_node_pools.go index 076b80fe8..65ea0ea3f 100644 --- a/lke_node_pools.go +++ b/lke_node_pools.go @@ -97,6 +97,7 @@ type LKENodePool struct { DiskEncryption InstanceDiskEncryption `json:"disk_encryption,omitzero"` + // Isolation may not currently be available to all users. Isolation *LKENodePoolIsolation `json:"isolation"` // K8sVersion and UpdateStrategy are only for LKE Enterprise to support node pool upgrades. @@ -118,9 +119,11 @@ type LKENodePoolCreateOptions struct { Taints []LKENodePoolTaint `json:"taints"` Label *string `json:"label,omitzero"` - Autoscaler *LKENodePoolAutoscaler `json:"autoscaler,omitzero"` - FirewallID *int `json:"firewall_id,omitzero"` - Isolation *LKENodePoolIsolationCreateOptions `json:"isolation,omitzero"` + Autoscaler *LKENodePoolAutoscaler `json:"autoscaler,omitzero"` + FirewallID *int `json:"firewall_id,omitzero"` + + // Isolation may not currently be available to all users. + Isolation *LKENodePoolIsolationCreateOptions `json:"isolation,omitzero"` // K8sVersion and UpdateStrategy only works for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. @@ -143,9 +146,11 @@ type LKENodePoolUpdateOptions struct { // K8sVersion and UpdateStrategy only works for LKE Enterprise to support node pool upgrades. // It may not currently be available to all users and is under v4beta. - K8sVersion *string `json:"k8s_version,omitzero"` - UpdateStrategy *LKENodePoolUpdateStrategy `json:"update_strategy,omitzero"` - Isolation *LKENodePoolIsolationUpdateOptions `json:"isolation,omitzero"` + K8sVersion *string `json:"k8s_version,omitzero"` + UpdateStrategy *LKENodePoolUpdateStrategy `json:"update_strategy,omitzero"` + + // Isolation may not currently be available to all users. + Isolation *LKENodePoolIsolationUpdateOptions `json:"isolation,omitzero"` } // GetCreateOptions converts a LKENodePool to LKENodePoolCreateOptions for From c9b0ecc96abd1b3c63f8f5a4643734088b99c469 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Fri, 5 Jun 2026 13:14:43 -0400 Subject: [PATCH 15/15] Remove isolation options from LKENodePool update options and related tests --- lke_node_pools.go | 16 ---------------- test/unit/lke_node_pools_test.go | 8 -------- 2 files changed, 24 deletions(-) diff --git a/lke_node_pools.go b/lke_node_pools.go index 65ea0ea3f..85e6af2c7 100644 --- a/lke_node_pools.go +++ b/lke_node_pools.go @@ -71,12 +71,6 @@ type LKENodePoolIsolationCreateOptions struct { PublicIPv6 *bool `json:"public_ipv6,omitzero"` } -// LKENodePoolIsolationUpdateOptions controls network isolation for node pool update requests. -type LKENodePoolIsolationUpdateOptions struct { - PublicIPv4 *bool `json:"public_ipv4,omitzero"` - PublicIPv6 *bool `json:"public_ipv6,omitzero"` -} - // LKENodePoolLabels represents Kubernetes labels to add to an LKENodePool type LKENodePoolLabels map[string]string @@ -148,9 +142,6 @@ type LKENodePoolUpdateOptions struct { // It may not currently be available to all users and is under v4beta. K8sVersion *string `json:"k8s_version,omitzero"` UpdateStrategy *LKENodePoolUpdateStrategy `json:"update_strategy,omitzero"` - - // Isolation may not currently be available to all users. - Isolation *LKENodePoolIsolationUpdateOptions `json:"isolation,omitzero"` } // GetCreateOptions converts a LKENodePool to LKENodePoolCreateOptions for @@ -190,13 +181,6 @@ func (l LKENodePool) GetUpdateOptions() (o LKENodePoolUpdateOptions) { o.Label = l.Label o.FirewallID = l.FirewallID - if l.Isolation != nil { - o.Isolation = &LKENodePoolIsolationUpdateOptions{ - PublicIPv4: Pointer(l.Isolation.PublicIPv4), - PublicIPv6: Pointer(l.Isolation.PublicIPv6), - } - } - return o } diff --git a/test/unit/lke_node_pools_test.go b/test/unit/lke_node_pools_test.go index 52c054ea6..2dd26dac1 100644 --- a/test/unit/lke_node_pools_test.go +++ b/test/unit/lke_node_pools_test.go @@ -146,10 +146,6 @@ func TestLKENodePool_Update(t *testing.T) { Max: 8, }, Label: &label, - Isolation: &linodego.LKENodePoolIsolationUpdateOptions{ - PublicIPv4: Ptr(false), - PublicIPv6: Ptr(true), - }, } base.MockPut("lke/clusters/123/pools/456", fixtureData) @@ -162,10 +158,6 @@ func TestLKENodePool_Update(t *testing.T) { assert.Equal(t, 2, nodePool.Autoscaler.Min) assert.Equal(t, 8, nodePool.Autoscaler.Max) assert.Equal(t, &label, nodePool.Label) - if assert.NotNil(t, nodePool.Isolation) { - assert.False(t, nodePool.Isolation.PublicIPv4) - assert.True(t, nodePool.Isolation.PublicIPv6) - } } func TestLKENodePool_Delete(t *testing.T) {