Skip to content

Commit 78b0488

Browse files
committed
feat: Add VPC and Subnet retention and adoption
This commit introduces a retention policy for LinodeVPC resources, allowing users to prevent the deletion of VPCs and their subnets when the corresponding Kubernetes object is deleted. Key features: - A `retain` boolean field has been added to both `LinodeVPCSpec` and `VPCSubnetCreateOptions`. - A new `--enable-subnet-deletion` controller flag allows for the deletion of unretained subnets within a retained VPC. This is disabled by default to prevent accidental data loss. - The VPC reconciler now supports adopting existing VPCs and their subnets by matching labels. If a subnet from the spec is not found in the existing VPC, it will be created. Comprehensive unit tests have been added in isolated test suites to cover these new behaviors.
1 parent fd878a5 commit 78b0488

File tree

11 files changed

+412
-10
lines changed

11 files changed

+412
-10
lines changed

api/v1alpha2/linodevpc_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ type LinodeVPCSpec struct {
3838
// +optional
3939
Subnets []VPCSubnetCreateOptions `json:"subnets,omitempty"`
4040

41+
// Retain allows you to keep the VPC after the LinodeVPC object is deleted.
42+
// This is useful if you want to use an existing VPC that was not created by this controller.
43+
// If set to true, the controller will not delete the VPC resource in Linode.
44+
// Defaults to false.
45+
// +optional
46+
Retain *bool `json:"retain,omitempty"`
47+
4148
// CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this VPC. If not
4249
// supplied then the credentials of the controller will be used.
4350
// +optional
@@ -55,6 +62,11 @@ type VPCSubnetCreateOptions struct {
5562
// SubnetID is subnet id for the subnet
5663
// +optional
5764
SubnetID int `json:"subnetID,omitempty"`
65+
// Retain allows you to keep the Subnet after the LinodeVPC object is deleted.
66+
// This is only applicable when the parent VPC has RetainVPC set to true and the
67+
// --enable-subnet-deletion flag is enabled on the controller.
68+
// +optional
69+
Retain *bool `json:"retain,omitempty"`
5870
}
5971

6072
// LinodeVPCStatus defines the observed state of LinodeVPC

api/v1alpha2/zz_generated.deepcopy.go

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/clients.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ type LinodeVPCClient interface {
6363
ListVPCs(ctx context.Context, opts *linodego.ListOptions) ([]linodego.VPC, error)
6464
CreateVPC(ctx context.Context, opts linodego.VPCCreateOptions) (*linodego.VPC, error)
6565
DeleteVPC(ctx context.Context, vpcID int) error
66+
CreateVPCSubnet(ctx context.Context, opts linodego.VPCSubnetCreateOptions, vpcID int) (*linodego.VPCSubnet, error)
67+
DeleteVPCSubnet(ctx context.Context, vpcID, subnetID int) error
6668
}
6769

6870
// LinodeNodeBalancerClient defines the methods that interact with Linode's Node Balancer service.

cmd/main.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ type flagVars struct {
8888
linodeVPCConcurrency int
8989
linodePlacementGroupConcurrency int
9090
linodeFirewallConcurrency int
91+
enableSubnetDeletion bool
9192
}
9293

9394
func init() {
@@ -152,6 +153,7 @@ func parseFlags() (flags flagVars, opts zap.Options) {
152153
flag.IntVar(&flags.linodeVPCConcurrency, "linodevpc-concurrency", concurrencyDefault, "Number of LinodeVPCs to process simultaneously")
153154
flag.IntVar(&flags.linodePlacementGroupConcurrency, "linodeplacementgroup-concurrency", concurrencyDefault, "Number of Linode Placement Groups to process simultaneously")
154155
flag.IntVar(&flags.linodeFirewallConcurrency, "linodefirewall-concurrency", concurrencyDefault, "Number of Linode Firewall to process simultaneously")
156+
flag.BoolVar(&flags.enableSubnetDeletion, "enable-subnet-deletion", false, "Enable the deletion of subnets in a retained VPC.")
155157

156158
opts = zap.Options{Development: true}
157159
opts.BindFlags(flag.CommandLine)
@@ -293,10 +295,11 @@ func setupControllers(mgr manager.Manager, flags flagVars, linodeClientConfig, d
293295

294296
// LinodeVPC Controller
295297
if err := (&controller.LinodeVPCReconciler{
296-
Client: mgr.GetClient(),
297-
Recorder: mgr.GetEventRecorderFor("LinodeVPCReconciler"),
298-
WatchFilterValue: flags.clusterWatchFilter,
299-
LinodeClientConfig: linodeClientConfig,
298+
Client: mgr.GetClient(),
299+
Recorder: mgr.GetEventRecorderFor("LinodeVPCReconciler"),
300+
WatchFilterValue: flags.clusterWatchFilter,
301+
LinodeClientConfig: linodeClientConfig,
302+
EnableSubnetDeletion: flags.enableSubnetDeletion,
300303
}).SetupWithManager(mgr, crcontroller.Options{MaxConcurrentReconciles: flags.linodeVPCConcurrency}); err != nil {
301304
setupLog.Error(err, "unable to create controller", "controller", "LinodeVPC")
302305
os.Exit(1)

config/crd/bases/infrastructure.cluster.x-k8s.io_linodevpcs.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ spec:
7272
x-kubernetes-validations:
7373
- message: Value is immutable
7474
rule: self == oldSelf
75+
retain:
76+
description: |-
77+
Retain allows you to keep the VPC after the LinodeVPC object is deleted.
78+
This is useful if you want to use an existing VPC that was not created by this controller.
79+
If set to true, the controller will not delete the VPC resource in Linode.
80+
Defaults to false.
81+
type: boolean
7582
subnets:
7683
items:
7784
description: VPCSubnetCreateOptions defines subnet options
@@ -82,6 +89,12 @@ spec:
8289
maxLength: 63
8390
minLength: 3
8491
type: string
92+
retain:
93+
description: |-
94+
Retain allows you to keep the Subnet after the LinodeVPC object is deleted.
95+
This is only applicable when the parent VPC has RetainVPC set to true and the
96+
--enable-subnet-deletion flag is enabled on the controller.
97+
type: boolean
8598
subnetID:
8699
description: SubnetID is subnet id for the subnet
87100
type: integer

docs/src/reference/out.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,7 @@ _Appears in:_
10591059
| `description` _string_ | | | |
10601060
| `region` _string_ | | | |
10611061
| `subnets` _[VPCSubnetCreateOptions](#vpcsubnetcreateoptions) array_ | | | |
1062+
| `retain` _boolean_ | Retain allows you to keep the VPC after the LinodeVPC object is deleted.<br />This is useful if you want to use an existing VPC that was not created by this controller.<br />If set to true, the controller will not delete the VPC resource in Linode.<br />Defaults to false. | | |
10621063
| `credentialsRef` _[SecretReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#secretreference-v1-core)_ | CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this VPC. If not<br />supplied then the credentials of the controller will be used. | | |
10631064

10641065

@@ -1217,5 +1218,6 @@ _Appears in:_
12171218
| `label` _string_ | | | MaxLength: 63 <br />MinLength: 3 <br /> |
12181219
| `ipv4` _string_ | | | |
12191220
| `subnetID` _integer_ | SubnetID is subnet id for the subnet | | |
1221+
| `retain` _boolean_ | Retain allows you to keep the Subnet after the LinodeVPC object is deleted.<br />This is only applicable when the parent VPC has RetainVPC set to true and the<br />--enable-subnet-deletion flag is enabled on the controller. | | |
12201222

12211223

internal/controller/linodevpc_controller.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,11 @@ import (
5454
// LinodeVPCReconciler reconciles a LinodeVPC object
5555
type LinodeVPCReconciler struct {
5656
client.Client
57-
Recorder record.EventRecorder
58-
LinodeClientConfig scope.ClientConfig
59-
WatchFilterValue string
60-
ReconcileTimeout time.Duration
57+
Recorder record.EventRecorder
58+
LinodeClientConfig scope.ClientConfig
59+
WatchFilterValue string
60+
EnableSubnetDeletion bool
61+
ReconcileTimeout time.Duration
6162
}
6263

6364
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodevpcs,verbs=get;list;watch;create;update;patch;delete
@@ -293,6 +294,35 @@ func (r *LinodeVPCReconciler) reconcileUpdate(ctx context.Context, logger logr.L
293294
func (r *LinodeVPCReconciler) reconcileDelete(ctx context.Context, logger logr.Logger, vpcScope *scope.VPCScope) (ctrl.Result, error) {
294295
logger.Info("deleting VPC")
295296

297+
if vpcScope.LinodeVPC.Spec.Retain != nil && *vpcScope.LinodeVPC.Spec.Retain {
298+
logger.Info("VPC has retain flag, skipping VPC deletion")
299+
300+
if r.EnableSubnetDeletion && vpcScope.LinodeVPC.Spec.VPCID != nil {
301+
logger.Info("subnet deletion enabled, checking subnets")
302+
vpc, err := vpcScope.LinodeClient.GetVPC(ctx, *vpcScope.LinodeVPC.Spec.VPCID)
303+
if err != nil {
304+
if util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil {
305+
return ctrl.Result{}, err
306+
}
307+
}
308+
if vpc != nil {
309+
for _, subnet := range vpcScope.LinodeVPC.Spec.Subnets {
310+
if subnet.Retain != nil && *subnet.Retain {
311+
continue
312+
}
313+
314+
logger.Info("deleting subnet", "subnetID", subnet.SubnetID)
315+
if err := vpcScope.LinodeClient.DeleteVPCSubnet(ctx, *vpcScope.LinodeVPC.Spec.VPCID, subnet.SubnetID); util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil {
316+
return ctrl.Result{}, err
317+
}
318+
}
319+
}
320+
}
321+
322+
controllerutil.RemoveFinalizer(vpcScope.LinodeVPC, infrav1alpha2.VPCFinalizer)
323+
return ctrl.Result{}, nil
324+
}
325+
296326
if vpcScope.LinodeVPC.Spec.VPCID != nil {
297327
vpc, err := vpcScope.LinodeClient.GetVPC(ctx, *vpcScope.LinodeVPC.Spec.VPCID)
298328
if util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil {

internal/controller/linodevpc_controller_helpers.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,32 @@ func reconcileVPC(ctx context.Context, vpcScope *scope.VPCScope, logger logr.Log
5555
} else if len(vpcs) != 0 {
5656
// Labels are unique
5757
vpcScope.LinodeVPC.Spec.VPCID = &vpcs[0].ID
58-
updateVPCSpecSubnets(vpcScope, &vpcs[0])
58+
59+
// build a map of existing subnets to easily check for existence
60+
existingSubnets := make(map[string]int)
61+
for _, subnet := range vpcs[0].Subnets {
62+
existingSubnets[subnet.Label] = subnet.ID
63+
}
64+
65+
// adopt or create subnets
66+
for i, subnet := range vpcScope.LinodeVPC.Spec.Subnets {
67+
if subnet.SubnetID != 0 {
68+
continue
69+
}
70+
if id, ok := existingSubnets[subnet.Label]; ok {
71+
vpcScope.LinodeVPC.Spec.Subnets[i].SubnetID = id
72+
} else {
73+
createSubnetConfig := linodego.VPCSubnetCreateOptions{
74+
Label: subnet.Label,
75+
IPv4: subnet.IPv4,
76+
}
77+
newSubnet, err := vpcScope.LinodeClient.CreateVPCSubnet(ctx, createSubnetConfig, *vpcScope.LinodeVPC.Spec.VPCID)
78+
if err != nil {
79+
return err
80+
}
81+
vpcScope.LinodeVPC.Spec.Subnets[i].SubnetID = newSubnet.ID
82+
}
83+
}
5984

6085
return nil
6186
}

0 commit comments

Comments
 (0)