@@ -22,6 +22,88 @@ const (
2222 DefaultKonnectivityLBPort = 8132
2323)
2424
25+ // FindSubnet selects a subnet from the provided subnets based on the subnet name
26+ // It handles both direct VPC subnets and VPCRef subnets
27+ // If subnet name is provided, it looks for a matching subnet; otherwise, it uses the first subnet
28+ // Returns the subnet ID and any error encountered
29+ func FindSubnet (subnetName string , isDirectVPC bool , subnets interface {}) (int , error ) {
30+ var subnetID int
31+ var err error
32+
33+ // Different handling based on whether we're dealing with a direct VPC or VPCRef
34+ if isDirectVPC {
35+ subnetID , err = findDirectVPCSubnet (subnetName , subnets )
36+ } else {
37+ subnetID , err = findVPCRefSubnet (subnetName , subnets )
38+ }
39+
40+ if err != nil {
41+ return 0 , err
42+ }
43+
44+ // Validate the selected subnet ID
45+ if subnetID == 0 {
46+ return 0 , errors .New ("invalid subnet ID: selected subnet ID is 0" )
47+ }
48+
49+ return subnetID , nil
50+ }
51+
52+ // findDirectVPCSubnet finds a subnet in direct VPC subnets
53+ func findDirectVPCSubnet (subnetName string , subnets interface {}) (int , error ) {
54+ vpcSubnets , ok := subnets .([]linodego.VPCSubnet )
55+ if ! ok {
56+ return 0 , fmt .Errorf ("invalid subnet data type for direct VPC: expected []linodego.VPCSubnet" )
57+ }
58+
59+ if len (vpcSubnets ) == 0 {
60+ return 0 , errors .New ("no subnets found in VPC" )
61+ }
62+
63+ return selectSubnet (subnetName , vpcSubnets , func (subnet linodego.VPCSubnet ) (string , int ) {
64+ return subnet .Label , subnet .ID
65+ })
66+ }
67+
68+ // findVPCRefSubnet finds a subnet in VPCRef subnets
69+ func findVPCRefSubnet (subnetName string , subnets interface {}) (int , error ) {
70+ vpcRefSubnets , ok := subnets .([]v1alpha2.VPCSubnetCreateOptions )
71+ if ! ok {
72+ return 0 , fmt .Errorf ("invalid subnet data type for VPC reference: expected []v1alpha2.VPCSubnetCreateOptions" )
73+ }
74+
75+ if len (vpcRefSubnets ) == 0 {
76+ return 0 , errors .New ("no subnets found in LinodeVPC" )
77+ }
78+
79+ return selectSubnet (subnetName , vpcRefSubnets , func (subnet v1alpha2.VPCSubnetCreateOptions ) (string , int ) {
80+ return subnet .Label , subnet .SubnetID
81+ })
82+ }
83+
84+ // selectSubnet is a generic helper to select a subnet by name or use the first one
85+ func selectSubnet [T any ](subnetName string , subnets []T , getProps func (T ) (string , int )) (int , error ) {
86+ if len (subnets ) == 0 {
87+ return 0 , errors .New ("no subnets available in the VPC" )
88+ }
89+
90+ // If subnet name specified, find matching subnet
91+ if subnetName != "" {
92+ for _ , subnet := range subnets {
93+ label , id := getProps (subnet )
94+ if label == subnetName {
95+ return id , nil
96+ }
97+ }
98+ // Keep the original error message format for compatibility with tests
99+ return 0 , fmt .Errorf ("subnet with label %s not found in VPC" , subnetName )
100+ }
101+
102+ // Use the first subnet when no specific name is provided
103+ _ , id := getProps (subnets [0 ])
104+ return id , nil
105+ }
106+
25107// EnsureNodeBalancer creates a new NodeBalancer if one doesn't exist or returns the existing NodeBalancer
26108func EnsureNodeBalancer (ctx context.Context , clusterScope * scope.ClusterScope , logger logr.Logger ) (* linodego.NodeBalancer , error ) {
27109 nbID := clusterScope .LinodeCluster .Spec .Network .NodeBalancerID
@@ -44,7 +126,7 @@ func EnsureNodeBalancer(ctx context.Context, clusterScope *scope.ClusterScope, l
44126 }
45127
46128 // if NodeBalancerBackendIPv4Range is set, create the NodeBalancer in the specified VPC
47- if clusterScope .LinodeCluster .Spec .Network .NodeBalancerBackendIPv4Range != "" && clusterScope .LinodeCluster .Spec .VPCRef != nil {
129+ if clusterScope .LinodeCluster .Spec .Network .NodeBalancerBackendIPv4Range != "" && ( clusterScope .LinodeCluster .Spec .VPCRef != nil || clusterScope . LinodeCluster . Spec . VPCID != nil ) {
48130 logger .Info ("Creating NodeBalancer in VPC" , "NodeBalancerBackendIPv4Range" , clusterScope .LinodeCluster .Spec .Network .NodeBalancerBackendIPv4Range )
49131 subnetID , err := getSubnetID (ctx , clusterScope , logger )
50132 if err != nil {
@@ -88,13 +170,43 @@ func EnsureNodeBalancer(ctx context.Context, clusterScope *scope.ClusterScope, l
88170// getSubnetID returns the subnetID of the first subnet in the LinodeVPC.
89171// If no subnets or subnetID is found, it returns an error.
90172func getSubnetID (ctx context.Context , clusterScope * scope.ClusterScope , logger logr.Logger ) (int , error ) {
173+ subnetName := clusterScope .LinodeCluster .Spec .Network .SubnetName
174+
175+ // If direct VPCID is specified, get the VPC and subnets directly from Linode API
176+ if clusterScope .LinodeCluster .Spec .VPCID != nil {
177+ vpcID := * clusterScope .LinodeCluster .Spec .VPCID
178+ vpc , err := clusterScope .LinodeClient .GetVPC (ctx , vpcID )
179+ if err != nil {
180+ logger .Error (err , "Failed to fetch VPC from Linode API" , "vpcID" , vpcID )
181+ return 0 , err
182+ }
183+
184+ if len (vpc .Subnets ) == 0 {
185+ return 0 , errors .New ("no subnets found in VPC" )
186+ }
187+
188+ subnetID , err := FindSubnet (subnetName , true , vpc .Subnets )
189+ if err != nil {
190+ logger .Error (err , "Failed to find subnet in VPC" , "vpcID" , vpcID , "subnetName" , subnetName )
191+ return 0 , err
192+ }
193+ return subnetID , nil
194+ }
195+
196+ // Otherwise, use the VPCRef
197+ if clusterScope .LinodeCluster .Spec .VPCRef == nil {
198+ return 0 , errors .New ("neither VPCID nor VPCRef is specified in LinodeCluster" )
199+ }
200+
91201 name := clusterScope .LinodeCluster .Spec .VPCRef .Name
92202 namespace := clusterScope .LinodeCluster .Spec .VPCRef .Namespace
93203 if namespace == "" {
94204 namespace = clusterScope .LinodeCluster .Namespace
95205 }
96206
97- logger = logger .WithValues ("vpcName" , name , "vpcNamespace" , namespace )
207+ if name == "" {
208+ return 0 , errors .New ("VPCRef name is not specified in LinodeCluster" )
209+ }
98210
99211 linodeVPC := & v1alpha2.LinodeVPC {
100212 ObjectMeta : metav1.ObjectMeta {
@@ -104,41 +216,16 @@ func getSubnetID(ctx context.Context, clusterScope *scope.ClusterScope, logger l
104216 }
105217
106218 objectKey := client .ObjectKeyFromObject (linodeVPC )
107- err := clusterScope .Client .Get (ctx , objectKey , linodeVPC )
108- if err != nil {
109- logger .Error (err , "Failed to fetch LinodeVPC" )
110- return 0 , err
219+ if err := clusterScope .Client .Get (ctx , objectKey , linodeVPC ); err != nil {
220+ logger .Error (err , "Failed to fetch LinodeVPC" , "name" , name , "namespace" , namespace )
221+ return 0 , fmt .Errorf ("failed to fetch LinodeVPC %s/%s: %w" , namespace , name , err )
111222 }
112- if len (linodeVPC .Spec .Subnets ) == 0 {
113- err = errors .New ("No subnets found in LinodeVPC" )
114- logger .Error (err , "Failed to fetch LinodeVPC" )
115- return 0 , err
116- }
117-
118- subnetID := 0
119- subnetName := clusterScope .LinodeCluster .Spec .Network .SubnetName
120223
121- // If subnet name specified, find matching subnet; otherwise use first subnet
122- if subnetName != "" {
123- for _ , subnet := range linodeVPC .Spec .Subnets {
124- if subnet .Label == subnetName {
125- subnetID = subnet .SubnetID
126- break
127- }
128- }
129- if subnetID == 0 {
130- return 0 , fmt .Errorf ("subnet with label %s not found in VPC" , subnetName )
131- }
132- } else {
133- subnetID = linodeVPC .Spec .Subnets [0 ].SubnetID
134- }
135-
136- // Validate the selected subnet ID
137- if subnetID == 0 {
138- return 0 , errors .New ("selected subnet ID is 0" )
224+ if len (linodeVPC .Spec .Subnets ) == 0 {
225+ return 0 , errors .New ("no subnets found in LinodeVPC" )
139226 }
140227
141- return subnetID , nil
228+ return FindSubnet ( subnetName , false , linodeVPC . Spec . Subnets )
142229}
143230
144231func getFirewallID (ctx context.Context , clusterScope * scope.ClusterScope , logger logr.Logger ) (int , error ) {
0 commit comments