Skip to content

Commit e5aa346

Browse files
committed
Rename and refactor bootstrap, implement basic provisioning functions
This renames `DeviceSpec.Bootstrap` to `Provisioning` in order to have a unified naming. We also implement a ProvisioningProvider interface and state transitionings in the device controller.
1 parent 7f1a105 commit e5aa346

File tree

11 files changed

+893
-233
lines changed

11 files changed

+893
-233
lines changed

api/core/v1alpha1/device_types.go

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
package v1alpha1
55

66
import (
7+
"crypto/rand"
8+
"encoding/hex"
9+
"fmt"
10+
711
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
812
)
913

@@ -16,7 +20,7 @@ type DeviceSpec struct {
1620
// Bootstrap is an optional configuration for the device bootstrap process.
1721
// It can be used to provide initial configuration templates or scripts that are applied during the device provisioning.
1822
// +optional
19-
Bootstrap *Bootstrap `json:"bootstrap,omitempty"`
23+
Provisioning *Provisioning `json:"provisioning,omitempty"`
2024
}
2125

2226
// Endpoint contains the connection information for the device.
@@ -48,11 +52,40 @@ type TLS struct {
4852
Certificate *CertificateSource `json:"certificate,omitempty"`
4953
}
5054

51-
// Bootstrap defines the configuration for device bootstrap.
52-
type Bootstrap struct {
53-
// Template defines the multiline string template that contains the initial configuration for the device.
55+
// Provisioning defines the configuration for device bootstrap.
56+
type Provisioning struct {
57+
// Image defines the image to be used for provisioning the device.
5458
// +required
55-
Template TemplateSource `json:"template"`
59+
Image Image `json:"image"`
60+
61+
// BootScript defines the script delivered by a TFTP server to the device during bootstrapping.
62+
// +optional
63+
BootScript TemplateSource `json:"bootScript"`
64+
}
65+
66+
// ChecksumType defines the type of checksum used for image verification.
67+
// +kubebuilder:validation:Enum=SHA256;MD5
68+
type ChecksumType string
69+
70+
const (
71+
ChecksumTypeSHA256 ChecksumType = "SHA256"
72+
ChecksumTypeMD5 ChecksumType = "MD5" //nolint: usestdlibvars
73+
)
74+
75+
type Image struct {
76+
// URL is the location of the image to be used for provisioning.
77+
// +required
78+
URL string `json:"url"`
79+
80+
// Checksum is the checksum of the image for verification.
81+
// +required
82+
// kubebuilder:validation:MinLength=1
83+
Checksum string `json:"checksum"`
84+
85+
// ChecksumType is the type of the checksum (e.g., sha256, md5).
86+
// +required
87+
// +kubebuilder:default=MD5
88+
ChecksumType ChecksumType `json:"checksumType"`
5689
}
5790

5891
// TemplateSource defines a source for template content.
@@ -105,6 +138,14 @@ type DeviceStatus struct {
105138
// +optional
106139
FirmwareVersion string `json:"firmwareVersion,omitempty"`
107140

141+
// Provisioning is the list of provisioning attempts for the Device.
142+
//+listType=map
143+
//+listMapKey=startTime
144+
//+patchStrategy=merge
145+
//+patchMergeKey=startTime
146+
//+optional
147+
Provisioning []ProvisioningInfo `json:"provisioning,omitempty"`
148+
108149
// Ports is the list of ports on the Device.
109150
// +optional
110151
Ports []DevicePort `json:"ports,omitempty"`
@@ -122,6 +163,47 @@ type DeviceStatus struct {
122163
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
123164
}
124165

166+
type ProvisioningInfo struct {
167+
StartTime metav1.Time `json:"startTime"`
168+
Token string `json:"token"`
169+
//+optional
170+
EndTime metav1.Time `json:"endTime,omitzero"`
171+
//+optional
172+
RebootTime metav1.Time `json:"reboot,omitzero"`
173+
//+optional
174+
Error string `json:"error,omitempty"`
175+
}
176+
177+
func (d *Device) GetActiveProvisioning() *ProvisioningInfo {
178+
for i := range d.Status.Provisioning {
179+
if d.Status.Provisioning[i].EndTime.IsZero() {
180+
return &d.Status.Provisioning[i]
181+
}
182+
}
183+
return nil
184+
}
185+
186+
func (d *Device) CreateProvisioningEntry() (*ProvisioningInfo, error) {
187+
if d.Status.Phase != DevicePhaseProvisioning {
188+
return nil, fmt.Errorf("device is in phase %s, expected %s", d.Status.Phase, DevicePhaseProvisioning)
189+
}
190+
active := d.GetActiveProvisioning()
191+
if active != nil {
192+
return nil, fmt.Errorf("device has an active provisioning with StartTime %s", active.StartTime.String())
193+
}
194+
token := make([]byte, 32)
195+
_, err := rand.Read(token)
196+
if err != nil {
197+
return nil, err
198+
}
199+
entry := ProvisioningInfo{
200+
StartTime: metav1.Now(),
201+
Token: hex.EncodeToString(token),
202+
}
203+
d.Status.Provisioning = append(d.Status.Provisioning, entry)
204+
return &entry, nil
205+
}
206+
125207
type DevicePort struct {
126208
// Name is the name of the port.
127209
// +required
@@ -137,7 +219,7 @@ type DevicePort struct {
137219

138220
// Transceiver is the type of transceiver plugged into the port, if any.
139221
// +optional
140-
Trasceiver string `json:"transceiver,omitempty"`
222+
Transceiver string `json:"transceiver,omitempty"`
141223

142224
// InterfaceRef is the reference to the corresponding Interface resource
143225
// configuring this port, if any.
@@ -146,14 +228,16 @@ type DevicePort struct {
146228
}
147229

148230
// DevicePhase represents the current phase of the Device as it's being provisioned and managed by the operator.
149-
// +kubebuilder:validation:Enum=Pending;Provisioning;Active;Failed
231+
// +kubebuilder:validation:Enum=Pending;Provisioning;Active;Failed;ProvisioningCompleted
150232
type DevicePhase string
151233

152234
const (
153235
// DevicePhasePending indicates that the device is pending and has not yet been provisioned.
154236
DevicePhasePending DevicePhase = "Pending"
155237
// DevicePhaseProvisioning indicates that the device is being provisioned.
156238
DevicePhaseProvisioning DevicePhase = "Provisioning"
239+
// DevicePhaseProvisioningCompleted indicates that the device provisioning has completed and the operator is performing post-provisioning tasks.
240+
DevicePhaseProvisioningCompleted DevicePhase = "ProvisioningCompleted"
157241
// DevicePhaseActive indicates that the device has been successfully provisioned and is now ready for use.
158242
DevicePhaseActive DevicePhase = "Active"
159243
// DevicePhaseFailed indicates that the device provisioning has failed.
@@ -211,11 +295,6 @@ func (d *Device) GetSecretRefs() []SecretReference {
211295
refs = append(refs, d.Spec.Endpoint.TLS.Certificate.SecretRef)
212296
}
213297
}
214-
if d.Spec.Bootstrap != nil {
215-
if d.Spec.Bootstrap.Template.SecretRef != nil {
216-
refs = append(refs, d.Spec.Bootstrap.Template.SecretRef.SecretReference)
217-
}
218-
}
219298
for i := range refs {
220299
if refs[i].Namespace == "" {
221300
refs[i].Namespace = d.Namespace
@@ -227,11 +306,6 @@ func (d *Device) GetSecretRefs() []SecretReference {
227306
// GetConfigMapRefs returns the list of configmaps referenced in the [Device] resource.
228307
func (d *Device) GetConfigMapRefs() []ConfigMapReference {
229308
refs := []ConfigMapReference{}
230-
if d.Spec.Bootstrap != nil {
231-
if d.Spec.Bootstrap.Template.ConfigMapRef != nil {
232-
refs = append(refs, d.Spec.Bootstrap.Template.ConfigMapRef.ConfigMapReference)
233-
}
234-
}
235309
for i := range refs {
236310
if refs[i].Namespace == "" {
237311
refs[i].Namespace = d.Namespace

0 commit comments

Comments
 (0)