Skip to content

Commit 9c131f9

Browse files
authored
Merge pull request #3 from miscord-dev/fix-bugs
Fix bugs
2 parents 7d7d36b + fdc16ea commit 9c131f9

File tree

7 files changed

+129
-17
lines changed

7 files changed

+129
-17
lines changed

config/crd/kustomization.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ resources:
88
- bases/infrastructure.cluster.x-k8s.io_incusmachinetemplates.yaml
99
# +kubebuilder:scaffold:crdkustomizeresource
1010

11+
commonLabels:
12+
cluster.x-k8s.io/v1beta1: v1alpha1
13+
1114
patches:
1215
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
1316
# patches here are for enabling the conversion webhook for each CRD

config/default/kustomization.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Adds namespace to all resources.
2-
namespace: cluster-api-provider-incus-system
2+
namespace: capincus-system
33

44
# Value of this field is prepended to the
55
# names of all resources, e.g. a deployment named

config/rbac/role.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,20 @@ kind: ClusterRole
44
metadata:
55
name: manager-role
66
rules:
7+
- apiGroups:
8+
- ""
9+
resources:
10+
- secrets
11+
verbs:
12+
- get
13+
- list
14+
- watch
715
- apiGroups:
816
- cluster.x-k8s.io
917
resources:
1018
- clusters
1119
- machines
20+
- machinesets
1221
verbs:
1322
- get
1423
- list

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/miscord-dev/cluster-api-provider-incus
33
go 1.22.0
44

55
require (
6+
github.com/google/uuid v1.6.0
67
github.com/lxc/incus v0.7.0
78
github.com/onsi/ginkgo/v2 v2.19.1
89
github.com/onsi/gomega v1.34.0
@@ -43,7 +44,6 @@ require (
4344
github.com/google/go-cmp v0.6.0 // indirect
4445
github.com/google/gofuzz v1.2.0 // indirect
4546
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect
46-
github.com/google/uuid v1.6.0 // indirect
4747
github.com/gorilla/schema v1.2.1 // indirect
4848
github.com/gorilla/securecookie v1.1.2 // indirect
4949
github.com/gorilla/websocket v1.5.1 // indirect

internal/controller/incusmachine_controller.go

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"time"
2324

25+
"github.com/google/uuid"
2426
"github.com/lxc/incus/shared/api"
2527
infrav1alpha1 "github.com/miscord-dev/cluster-api-provider-incus/api/v1alpha1"
2628
"github.com/miscord-dev/cluster-api-provider-incus/pkg/incus"
@@ -55,6 +57,8 @@ type IncusMachineReconciler struct {
5557
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=incusmachines/finalizers,verbs=update
5658
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;list;watch
5759
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machines,verbs=get;list;watch
60+
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinesets,verbs=get;list;watch
61+
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch
5862

5963
// Reconcile is part of the main kubernetes reconciliation loop which aims to
6064
// move the current state of the cluster closer to the desired state.
@@ -139,6 +143,13 @@ func (r *IncusMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request
139143
if err != nil {
140144
return ctrl.Result{}, err
141145
}
146+
deleted := !incusMachine.ObjectMeta.DeletionTimestamp.IsZero()
147+
containsFinalizer := controllerutil.ContainsFinalizer(incusMachine, infrav1alpha1.MachineFinalizer)
148+
149+
if deleted && !containsFinalizer {
150+
// Return early since the object is being deleted and doesn't have the finalizer.
151+
return ctrl.Result{}, nil
152+
}
142153
// Always attempt to Patch the IncusMachine object and status after each reconciliation.
143154
defer func() {
144155
if err := patchIncusMachine(ctx, patchHelper, incusMachine); err != nil {
@@ -149,18 +160,24 @@ func (r *IncusMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request
149160
}
150161
}()
151162

152-
// Add finalizer first if not set to avoid the race condition between init and delete.
153-
// Note: Finalizers in general can only be added when the deletionTimestamp is not set.
154-
if incusMachine.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(incusMachine, infrav1alpha1.MachineFinalizer) {
155-
controllerutil.AddFinalizer(incusMachine, infrav1alpha1.MachineFinalizer)
156-
return ctrl.Result{}, nil
157-
}
163+
log.Info("Reconciling IncusMachine")
158164

159165
// Handle deleted machines
160-
if !incusMachine.ObjectMeta.DeletionTimestamp.IsZero() {
166+
if deleted {
161167
return r.reconcileDelete(ctx, incusCluster, machine, incusMachine)
162168
}
163169

170+
// Add finalizer first if not set to avoid the race condition between init and delete.
171+
// Note: Finalizers in general can only be added when the deletionTimestamp is not set.
172+
if !containsFinalizer {
173+
log.Info("Adding finalizer for IncusMachine")
174+
175+
controllerutil.AddFinalizer(incusMachine, infrav1alpha1.MachineFinalizer)
176+
return ctrl.Result{
177+
Requeue: true,
178+
}, nil
179+
}
180+
164181
// Handle non-deleted machines
165182
return r.reconcileNormal(ctx, cluster, incusCluster, machine, incusMachine)
166183
}
@@ -198,25 +215,43 @@ func (r *IncusMachineReconciler) reconcileDelete(ctx context.Context, _ *infrav1
198215
// return err
199216
// }
200217

201-
output, err := r.IncusClient.GetInstance(ctx, incusMachine.Name)
202-
if errors.Is(err, incus.ErrorInstanceNotFound) {
218+
exists, err := r.IncusClient.InstanceExists(ctx, incusMachine.Name)
219+
if err != nil {
220+
return ctrl.Result{}, fmt.Errorf("failed to check if instance exists: %w", err)
221+
}
222+
if !exists {
203223
// Instance is already deleted so remove the finalizer.
224+
log.Info("Deleting finalizer from IncusMachine")
204225
controllerutil.RemoveFinalizer(incusMachine, infrav1alpha1.MachineFinalizer)
205226
return ctrl.Result{}, nil
206227
}
228+
229+
output, err := r.IncusClient.GetInstance(ctx, incusMachine.Name)
207230
if err != nil {
208231
return ctrl.Result{}, fmt.Errorf("failed to get instance: %w", err)
209232
}
210233

211234
if output.StatusCode != api.Stopped &&
212235
output.StatusCode != api.Stopping {
236+
log.Info("Stopping instance")
237+
213238
if err := r.IncusClient.StopInstance(ctx, incusMachine.Name); err != nil {
214239
log.Info("Failed to stop instance", "error", err)
215240
}
241+
242+
return ctrl.Result{
243+
RequeueAfter: 5 * time.Second,
244+
}, nil
216245
} else if output.StatusCode != api.OperationCreated {
246+
log.Info("Deleting instance")
247+
217248
if err := r.IncusClient.DeleteInstance(ctx, incusMachine.Name); err != nil {
218249
return ctrl.Result{}, fmt.Errorf("failed to delete instance: %w", err)
219250
}
251+
252+
return ctrl.Result{
253+
RequeueAfter: 5 * time.Second,
254+
}, nil
220255
}
221256

222257
return ctrl.Result{
@@ -240,9 +275,20 @@ func (r *IncusMachineReconciler) reconcileNormal(ctx context.Context, cluster *c
240275
}
241276
dataSecretName := *machine.Spec.Bootstrap.DataSecretName
242277

243-
_, err := r.IncusClient.GetInstance(ctx, incusMachine.Name)
278+
output, err := r.IncusClient.GetInstance(ctx, incusMachine.Name)
244279
if err == nil {
245-
return ctrl.Result{}, nil
280+
if r.isMachineReady(output) {
281+
log.Info("IncusMachine instance is ready")
282+
283+
incusMachine.Spec.ProviderID = &output.ProviderID
284+
incusMachine.Status.Ready = true
285+
286+
return ctrl.Result{}, nil
287+
}
288+
289+
return ctrl.Result{
290+
RequeueAfter: 10 * time.Second,
291+
}, nil
246292
}
247293
if !errors.Is(err, incus.ErrorInstanceNotFound) {
248294
return ctrl.Result{}, fmt.Errorf("failed to get instance: %w", err)
@@ -253,20 +299,32 @@ func (r *IncusMachineReconciler) reconcileNormal(ctx context.Context, cluster *c
253299
return ctrl.Result{}, err
254300
}
255301

302+
log.Info("Creating IncusMachine instance")
303+
304+
uuid, err := uuid.NewV7()
305+
if err != nil {
306+
return ctrl.Result{}, fmt.Errorf("failed to generate UUID: %w", err)
307+
}
308+
309+
providerID := fmt.Sprintf("incus://%s", uuid.String())
310+
256311
// Create the instance
257312
err = r.IncusClient.CreateInstance(ctx, incus.CreateInstanceInput{
258313
Name: incusMachine.Name,
259314
BootstrapData: incus.BootstrapData{
260315
Data: bootstrapData,
261316
Format: string(bootstrapFormat),
262317
},
318+
ProviderID: providerID,
263319
InstanceSpec: incusMachine.Spec.InstanceSpec,
264320
})
265321
if err != nil {
266322
return ctrl.Result{}, fmt.Errorf("failed to create instance: %w", err)
267323
}
268324

269-
return ctrl.Result{}, nil
325+
return ctrl.Result{
326+
RequeueAfter: 10 * time.Second,
327+
}, nil
270328
}
271329

272330
func (r *IncusMachineReconciler) getBootstrapData(ctx context.Context, namespace string, dataSecretName string) (string, bootstrapv1.Format, error) {
@@ -289,6 +347,10 @@ func (r *IncusMachineReconciler) getBootstrapData(ctx context.Context, namespace
289347
return string(value), bootstrapv1.Format(format), nil
290348
}
291349

350+
func (r *IncusMachineReconciler) isMachineReady(output *incus.GetInstanceOutput) bool {
351+
return output.StatusCode == api.Running
352+
}
353+
292354
// SetupWithManager sets up the controller with the Manager.
293355
func (r *IncusMachineReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
294356
clusterToIncusMachines, err := util.ClusterToTypedObjectsMapper(mgr.GetClient(), &infrav1alpha1.IncusMachineList{}, mgr.GetScheme())

pkg/incus/incus.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ var (
1515
ErrorInstanceNotFound = fmt.Errorf("instance not found")
1616
)
1717

18+
const (
19+
configUserDataKey = "cloud-init.user-data"
20+
configProviderIDKey = "user.provider-id"
21+
configMetaDataKey = "user.meta-data"
22+
)
23+
1824
type BootstrapData struct {
1925
Format string
2026
Data string
@@ -23,6 +29,7 @@ type BootstrapData struct {
2329
type CreateInstanceInput struct {
2430
Name string
2531
BootstrapData BootstrapData
32+
ProviderID string
2633

2734
infrav1alpha1.InstanceSpec
2835
}
@@ -31,12 +38,14 @@ type GetInstanceOutput struct {
3138
Name string
3239
infrav1alpha1.InstanceSpec
3340

41+
ProviderID string
3442
// TODO: Add status
3543
StatusCode api.StatusCode
3644
}
3745

3846
type Client interface {
3947
CreateInstance(ctx context.Context, spec CreateInstanceInput) error
48+
InstanceExists(ctx context.Context, name string) (bool, error)
4049
GetInstance(ctx context.Context, name string) (*GetInstanceOutput, error)
4150
DeleteInstance(ctx context.Context, name string) error
4251
StopInstance(ctx context.Context, name string) error
@@ -54,21 +63,27 @@ func NewClient(instanceServer incusclient.InstanceServer) Client {
5463

5564
func (c *client) CreateInstance(ctx context.Context, spec CreateInstanceInput) error {
5665
config := maps.Clone(spec.Config)
66+
if config == nil {
67+
config = make(map[string]string)
68+
}
5769

5870
switch spec.BootstrapData.Format {
5971
case "cloud-config":
60-
config["cloud-init.user-data"] = spec.BootstrapData.Data
72+
config[configUserDataKey] = spec.BootstrapData.Data
6173
case "":
6274
// Do nothing
6375
default:
6476
return fmt.Errorf("unsupported bootstrap data format: %s", spec.BootstrapData.Format)
6577
}
6678

79+
config[configProviderIDKey] = spec.ProviderID
80+
config[configMetaDataKey] = fmt.Sprintf("provider-id: %s", spec.ProviderID)
81+
6782
req := api.InstancesPost{
6883
Name: spec.Name,
6984
InstancePut: api.InstancePut{
7085
Architecture: spec.Architecture,
71-
Config: spec.Config,
86+
Config: config,
7287
Devices: spec.Devices,
7388
Ephemeral: spec.Ephemeral,
7489
Profiles: spec.Profiles,
@@ -108,6 +123,19 @@ func (c *client) CreateInstance(ctx context.Context, spec CreateInstanceInput) e
108123
return nil
109124
}
110125

126+
func (c *client) InstanceExists(ctx context.Context, name string) (bool, error) {
127+
_, _, err := c.client.GetInstance(name)
128+
if err != nil {
129+
if api.StatusErrorCheck(err, http.StatusNotFound) {
130+
return false, nil
131+
}
132+
133+
return false, fmt.Errorf("failed to get instance: %w", err)
134+
}
135+
136+
return true, nil
137+
}
138+
111139
func (c *client) GetInstance(ctx context.Context, name string) (*GetInstanceOutput, error) {
112140
resp, _, err := c.client.GetInstanceFull(name)
113141
if err != nil {
@@ -131,6 +159,7 @@ func (c *client) GetInstance(ctx context.Context, name string) (*GetInstanceOutp
131159
Description: resp.Description,
132160
Type: infrav1alpha1.InstanceType(resp.Type),
133161
},
162+
ProviderID: resp.Config[configProviderIDKey],
134163
StatusCode: resp.StatusCode,
135164
}, nil
136165
}

test/e2e/e2e_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
)
3232

3333
// namespace where the project is deployed in
34-
const namespace = "cluster-api-provider-incus-system"
34+
const namespace = "capincus-system"
3535

3636
// serviceAccountName created for the project
3737
const serviceAccountName = "cluster-api-provider-incus-controller-manager"
@@ -291,6 +291,15 @@ var _ = Describe("Manager", Ordered, func() {
291291
// fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`,
292292
// strings.ToLower(<Kind>),
293293
// ))
294+
295+
It("create an IncusCluster", func() {
296+
By("creating a ClusterClass/Cluster/IncusCluster/etc for the IncusCluster")
297+
cmd := exec.Command("kubectl", "apply", "-n", namespace,
298+
"-f", ".github/cluster.yml",
299+
)
300+
_, err := utils.Run(cmd)
301+
Expect(err).NotTo(HaveOccurred(), "Failed to create Cluster")
302+
})
294303
})
295304
})
296305

0 commit comments

Comments
 (0)