Skip to content

Commit a36de6f

Browse files
feat: add conversion methods for device resource across API versions
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
1 parent dd7e870 commit a36de6f

File tree

4 files changed

+187
-42
lines changed

4 files changed

+187
-42
lines changed

cmd/fabrica/add.go

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func runAddResource(resourceName string, opts *addOptions) error {
180180
resourceFile = filepath.Join(targetDir, opts.packageName+".go")
181181
}
182182

183-
if err := generateResourceFile(resourceFile, resourceName, isVersioned, opts); err != nil {
183+
if err := generateResourceFile(resourceFile, resourceName, isVersioned, opts, config); err != nil {
184184
return err
185185
}
186186

@@ -220,7 +220,7 @@ func runAddResource(resourceName string, opts *addOptions) error {
220220
return nil
221221
}
222222

223-
func generateResourceFile(filePath, resourceName string, isVersioned bool, opts *addOptions) error {
223+
func generateResourceFile(filePath, resourceName string, isVersioned bool, opts *addOptions, config *FabricaConfig) error {
224224
var packageName string
225225
if isVersioned {
226226
// Use version as package name (e.g., v1alpha1)
@@ -229,6 +229,12 @@ func generateResourceFile(filePath, resourceName string, isVersioned bool, opts
229229
packageName = opts.packageName
230230
}
231231

232+
// Determine if this is the hub version (storage version)
233+
isHub := false
234+
if isVersioned && config != nil {
235+
isHub = (opts.version == config.Features.Versioning.StorageVersion)
236+
}
237+
232238
content := fmt.Sprintf(`// Copyright © 2025 OpenCHAMI a Series of LF Projects, LLC
233239
//
234240
// SPDX-License-Identifier: MIT
@@ -241,7 +247,20 @@ import (
241247
if isVersioned {
242248
// Versioned types use flattened envelope
243249
content += `
244-
"github.com/openchami/fabrica/pkg/fabrica"
250+
"github.com/openchami/fabrica/pkg/fabrica"`
251+
252+
// Add hub package import for spoke versions (for conversions)
253+
if !isHub && config != nil {
254+
modulePath := config.Project.Module
255+
hubVersion := config.Features.Versioning.StorageVersion
256+
group := config.Features.Versioning.Group
257+
hubPackage := strings.ReplaceAll(hubVersion, ".", "")
258+
259+
content += `
260+
` + hubPackage + ` "` + modulePath + `/apis/` + group + `/` + hubVersion + `"`
261+
}
262+
263+
content += `
245264
)
246265
247266
// ` + resourceName + ` represents a ` + strings.ToLower(resourceName) + ` resource
@@ -352,6 +371,62 @@ func (r *` + resourceName + `) GetUID() string {
352371
return r.Metadata.UID
353372
}
354373
`
374+
375+
// Add conversion stubs for non-hub versions (spokes)
376+
if !isHub && config != nil {
377+
hubVersion := config.Features.Versioning.StorageVersion
378+
group := config.Features.Versioning.Group
379+
hubPackage := strings.ReplaceAll(hubVersion, ".", "")
380+
381+
content += `
382+
// ConvertTo converts this ` + packageName + ` ` + resourceName + ` to the hub version (` + hubVersion + `)
383+
func (src *` + resourceName + `) ConvertTo(dstRaw interface{}) error {
384+
dst := dstRaw.(*` + hubPackage + `.` + resourceName + `)
385+
386+
// TODO: Implement conversion logic from ` + packageName + ` to ` + hubVersion + `
387+
388+
// Copy common fields
389+
dst.APIVersion = "` + group + `/` + hubVersion + `"
390+
dst.Kind = src.Kind
391+
dst.Metadata = src.Metadata
392+
393+
// TODO: Convert Spec fields
394+
// Map fields from src.Spec to dst.Spec
395+
// Handle any field additions, removals, or transformations
396+
397+
// TODO: Convert Status fields
398+
// Map fields from src.Status to dst.Status
399+
// Handle any field additions, removals, or transformations
400+
401+
return nil
402+
}
403+
404+
// ConvertFrom converts from the hub version (` + hubVersion + `) to this ` + packageName + ` ` + resourceName + `
405+
func (dst *` + resourceName + `) ConvertFrom(srcRaw interface{}) error {
406+
src := srcRaw.(*` + hubPackage + `.` + resourceName + `)
407+
408+
// TODO: Implement conversion logic from ` + hubVersion + ` to ` + packageName + `
409+
410+
// Copy common fields
411+
dst.APIVersion = "` + group + `/` + packageName + `"
412+
dst.Kind = src.Kind
413+
dst.Metadata = src.Metadata
414+
415+
// TODO: Convert Spec fields
416+
// Map fields from src.Spec to dst.Spec
417+
// Handle any field additions, removals, or transformations
418+
// Drop fields that don't exist in ` + packageName + `
419+
420+
// TODO: Convert Status fields
421+
// Map fields from src.Status to dst.Status
422+
// Handle any field additions, removals, or transformations
423+
// Drop fields that don't exist in ` + packageName + `
424+
425+
return nil
426+
}
427+
`
428+
}
429+
355430
} else {
356431
// Legacy: embedded resource
357432
content += `// GetKind returns the kind of the resource

examples/08-api-versioning/apis/infra.example.io/v1/device_types.go

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,16 @@ type Device struct {
2020

2121
// DeviceSpec defines the desired state of Device
2222
type DeviceSpec struct {
23-
Name string `json:"name" validate:"required"`
24-
IPAddress string `json:"ipAddress" validate:"required,ip"`
25-
Location string `json:"location,omitempty"`
26-
DeviceType string `json:"deviceType" validate:"oneof=server switch router"`
27-
Tags map[string]string `json:"tags,omitempty"`
28-
Description string `json:"description,omitempty" validate:"max=200"`
23+
Description string `json:"description,omitempty" validate:"max=200"`
24+
// Add your spec fields here
2925
}
3026

3127
// DeviceStatus defines the observed state of Device
3228
type DeviceStatus struct {
33-
Phase string `json:"phase,omitempty"`
34-
Message string `json:"message,omitempty"`
35-
Ready bool `json:"ready"`
36-
Health string `json:"health,omitempty" validate:"omitempty,oneof=healthy degraded unhealthy"`
37-
LastChecked string `json:"lastChecked,omitempty"`
38-
Conditions []Condition `json:"conditions,omitempty"`
39-
}
40-
41-
// Condition represents a status condition
42-
type Condition struct {
43-
Type string `json:"type"`
44-
Status string `json:"status"`
45-
Reason string `json:"reason,omitempty"`
29+
Phase string `json:"phase,omitempty"`
4630
Message string `json:"message,omitempty"`
31+
Ready bool `json:"ready"`
32+
// Add your status fields here
4733
}
4834

4935
// Validate implements custom validation logic for Device

examples/08-api-versioning/apis/infra.example.io/v1alpha1/device_types.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package v1alpha1
66

77
import (
88
"context"
9+
v1 "github.com/example/device-api-versioned/apis/infra.example.io/v1"
910
"github.com/openchami/fabrica/pkg/fabrica"
1011
)
1112

@@ -61,3 +62,49 @@ func (r *Device) GetName() string {
6162
func (r *Device) GetUID() string {
6263
return r.Metadata.UID
6364
}
65+
66+
// ConvertTo converts this v1alpha1 Device to the hub version (v1)
67+
func (src *Device) ConvertTo(dstRaw interface{}) error {
68+
dst := dstRaw.(*v1.Device)
69+
70+
// TODO: Implement conversion logic from v1alpha1 to v1
71+
72+
// Copy common fields
73+
dst.APIVersion = "infra.example.io/v1"
74+
dst.Kind = src.Kind
75+
dst.Metadata = src.Metadata
76+
77+
// TODO: Convert Spec fields
78+
// Map fields from src.Spec to dst.Spec
79+
// Handle any field additions, removals, or transformations
80+
81+
// TODO: Convert Status fields
82+
// Map fields from src.Status to dst.Status
83+
// Handle any field additions, removals, or transformations
84+
85+
return nil
86+
}
87+
88+
// ConvertFrom converts from the hub version (v1) to this v1alpha1 Device
89+
func (dst *Device) ConvertFrom(srcRaw interface{}) error {
90+
src := srcRaw.(*v1.Device)
91+
92+
// TODO: Implement conversion logic from v1 to v1alpha1
93+
94+
// Copy common fields
95+
dst.APIVersion = "infra.example.io/v1alpha1"
96+
dst.Kind = src.Kind
97+
dst.Metadata = src.Metadata
98+
99+
// TODO: Convert Spec fields
100+
// Map fields from src.Spec to dst.Spec
101+
// Handle any field additions, removals, or transformations
102+
// Drop fields that don't exist in v1alpha1
103+
104+
// TODO: Convert Status fields
105+
// Map fields from src.Status to dst.Status
106+
// Handle any field additions, removals, or transformations
107+
// Drop fields that don't exist in v1alpha1
108+
109+
return nil
110+
}

examples/08-api-versioning/apis/infra.example.io/v1beta1/device_types.go

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package v1beta1
66

77
import (
88
"context"
9+
v1 "github.com/example/device-api-versioned/apis/infra.example.io/v1"
910
"github.com/openchami/fabrica/pkg/fabrica"
1011
)
1112

@@ -20,30 +21,20 @@ type Device struct {
2021

2122
// DeviceSpec defines the desired state of Device
2223
type DeviceSpec struct {
23-
Name string `json:"name" validate:"required"`
24-
IPAddress string `json:"ipAddress" validate:"required,ip"`
25-
Location string `json:"location,omitempty"`
26-
DeviceType string `json:"deviceType" validate:"oneof=server switch router"`
27-
Tags map[string]string `json:"tags,omitempty"` // NEW in v1beta1: Added tags
28-
Description string `json:"description,omitempty" validate:"max=200"`
24+
Name string `json:"name" validate:"required"`
25+
IPAddress string `json:"ipAddress" validate:"required,ip"`
26+
Location string `json:"location,omitempty"`
27+
DeviceType string `json:"deviceType" validate:"oneof=server switch router"`
28+
Description string `json:"description,omitempty" validate:"max=200"`
2929
}
3030

3131
// DeviceStatus defines the observed state of Device
3232
type DeviceStatus struct {
33-
Phase string `json:"phase,omitempty"`
34-
Message string `json:"message,omitempty"`
35-
Ready bool `json:"ready"`
36-
Health string `json:"health,omitempty" validate:"omitempty,oneof=healthy degraded unhealthy"`
37-
LastChecked string `json:"lastChecked,omitempty"`
38-
Conditions []Condition `json:"conditions,omitempty"` // NEW in v1beta1: Added conditions
39-
}
40-
41-
// Condition represents a status condition
42-
type Condition struct {
43-
Type string `json:"type"`
44-
Status string `json:"status"`
45-
Reason string `json:"reason,omitempty"`
46-
Message string `json:"message,omitempty"`
33+
Phase string `json:"phase,omitempty"`
34+
Message string `json:"message,omitempty"`
35+
Ready bool `json:"ready"`
36+
Health string `json:"health,omitempty" validate:"omitempty,oneof=healthy degraded unhealthy"`
37+
LastChecked string `json:"lastChecked,omitempty"`
4738
}
4839

4940
// Validate implements custom validation logic for Device
@@ -71,3 +62,49 @@ func (r *Device) GetName() string {
7162
func (r *Device) GetUID() string {
7263
return r.Metadata.UID
7364
}
65+
66+
// ConvertTo converts this v1alpha1 Device to the hub version (v1)
67+
func (src *Device) ConvertTo(dstRaw interface{}) error {
68+
dst := dstRaw.(*v1.Device)
69+
70+
// TODO: Implement conversion logic from v1alpha1 to v1
71+
72+
// Copy common fields
73+
dst.APIVersion = "infra.example.io/v1"
74+
dst.Kind = src.Kind
75+
dst.Metadata = src.Metadata
76+
77+
// TODO: Convert Spec fields
78+
// Map fields from src.Spec to dst.Spec
79+
// Handle any field additions, removals, or transformations
80+
81+
// TODO: Convert Status fields
82+
// Map fields from src.Status to dst.Status
83+
// Handle any field additions, removals, or transformations
84+
85+
return nil
86+
}
87+
88+
// ConvertFrom converts from the hub version (v1) to this v1alpha1 Device
89+
func (dst *Device) ConvertFrom(srcRaw interface{}) error {
90+
src := srcRaw.(*v1.Device)
91+
92+
// TODO: Implement conversion logic from v1 to v1alpha1
93+
94+
// Copy common fields
95+
dst.APIVersion = "infra.example.io/v1alpha1"
96+
dst.Kind = src.Kind
97+
dst.Metadata = src.Metadata
98+
99+
// TODO: Convert Spec fields
100+
// Map fields from src.Spec to dst.Spec
101+
// Handle any field additions, removals, or transformations
102+
// Drop fields that don't exist in v1alpha1
103+
104+
// TODO: Convert Status fields
105+
// Map fields from src.Status to dst.Status
106+
// Handle any field additions, removals, or transformations
107+
// Drop fields that don't exist in v1alpha1
108+
109+
return nil
110+
}

0 commit comments

Comments
 (0)