Skip to content

Commit c4ae57a

Browse files
authored
chore: add provider id functions (#139)
Added helper functions to set and retrieve the provider ID.
1 parent f323720 commit c4ae57a

File tree

3 files changed

+166
-4
lines changed

3 files changed

+166
-4
lines changed

internal/provider/instances_v2.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ type InstancesV2 struct {
2525
// InstanceExists checks whether the provided Kubernetes node exists as instance
2626
// in Oxide.
2727
func (i *InstancesV2) InstanceExists(ctx context.Context, node *v1.Node) (bool, error) {
28-
instanceID := strings.TrimPrefix(node.Spec.ProviderID, "oxide://")
28+
instanceID, err := InstanceIDFromProviderID(node.Spec.ProviderID)
29+
if err != nil {
30+
return false, fmt.Errorf("failed retrieving instance id from provider id: %w", err)
31+
}
2932

3033
if _, err := i.client.InstanceView(ctx, oxide.InstanceViewParams{
3134
Instance: oxide.NameOrId(instanceID),
@@ -50,7 +53,10 @@ func (i *InstancesV2) InstanceMetadata(ctx context.Context, node *v1.Node) (*clo
5053
)
5154

5255
if node.Spec.ProviderID != "" {
53-
instanceID = strings.TrimPrefix(node.Spec.ProviderID, "oxide://")
56+
instanceID, err = InstanceIDFromProviderID(node.Spec.ProviderID)
57+
if err != nil {
58+
return nil, fmt.Errorf("failed retrieving instance id from provider id: %w", err)
59+
}
5460

5561
instance, err = i.client.InstanceView(ctx, oxide.InstanceViewParams{
5662
Instance: oxide.NameOrId(instanceID),
@@ -109,15 +115,18 @@ func (i *InstancesV2) InstanceMetadata(ctx context.Context, node *v1.Node) (*clo
109115
}
110116

111117
return &cloudprovider.InstanceMetadata{
112-
ProviderID: fmt.Sprintf("oxide://%s", instanceID),
118+
ProviderID: NewProviderID(instanceID),
113119
InstanceType: fmt.Sprintf("%v-%v", instance.Ncpus, (instance.Memory / (1024 * 1024 * 1024))),
114120
NodeAddresses: nodeAddresses,
115121
}, nil
116122
}
117123

118124
// InstanceShutdown checks whether the provided node is shut down in Oxide.
119125
func (i *InstancesV2) InstanceShutdown(ctx context.Context, node *v1.Node) (bool, error) {
120-
instanceID := strings.TrimPrefix(node.Spec.ProviderID, "oxide://")
126+
instanceID, err := InstanceIDFromProviderID(node.Spec.ProviderID)
127+
if err != nil {
128+
return false, fmt.Errorf("failed retrieving instance id from provider id: %w", err)
129+
}
121130

122131
instance, err := i.client.InstanceView(ctx, oxide.InstanceViewParams{
123132
Instance: oxide.NameOrId(instanceID),

internal/provider/provider.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package provider
22

33
import (
4+
"errors"
5+
"fmt"
46
"io"
57
"os"
8+
"strings"
69

10+
"github.com/google/uuid"
711
"github.com/oxidecomputer/oxide.go/oxide"
812
"k8s.io/client-go/kubernetes"
913
cloudprovider "k8s.io/cloud-provider"
@@ -116,3 +120,27 @@ func (o *Oxide) Routes() (cloudprovider.Routes, bool) {
116120
func (o *Oxide) Zones() (cloudprovider.Zones, bool) {
117121
return nil, false
118122
}
123+
124+
// InstanceIDFromProviderID extracts the Oxide instance ID from a provider ID.
125+
func InstanceIDFromProviderID(providerID string) (string, error) {
126+
if providerID == "" {
127+
return "", errors.New("provider id is empty")
128+
}
129+
130+
if !strings.HasPrefix(providerID, "oxide://") {
131+
return "", errors.New("provider id does not have 'oxide://' prefix")
132+
}
133+
134+
instanceID := strings.TrimPrefix(providerID, "oxide://")
135+
136+
if _, err := uuid.Parse(instanceID); err != nil {
137+
return "", fmt.Errorf("provider id contains invalid uuid: %w", err)
138+
}
139+
140+
return instanceID, nil
141+
}
142+
143+
// NewProviderID formats an Oxide instance ID as a provider ID.
144+
func NewProviderID(instanceID string) string {
145+
return fmt.Sprintf("oxide://%s", instanceID)
146+
}

internal/provider/provider_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package provider
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestInstanceIDFromProviderID(t *testing.T) {
9+
t.Run("Success", func(t *testing.T) {
10+
tt := []struct {
11+
name string
12+
providerID string
13+
expected string
14+
}{
15+
{
16+
name: "valid provider ID with UUID",
17+
providerID: "oxide://12345678-1234-1234-1234-123456789abc",
18+
expected: "12345678-1234-1234-1234-123456789abc",
19+
},
20+
{
21+
name: "valid provider ID with uppercase UUID",
22+
providerID: "oxide://12345678-1234-1234-1234-123456789ABC",
23+
expected: "12345678-1234-1234-1234-123456789ABC",
24+
},
25+
}
26+
27+
for _, tc := range tt {
28+
t.Run(tc.name, func(t *testing.T) {
29+
result, err := InstanceIDFromProviderID(tc.providerID)
30+
if err != nil {
31+
t.Errorf("TestInstanceIDFromProviderID(%s) returned non-nil error %v, want nil error", tc.providerID, err)
32+
}
33+
34+
if result != tc.expected {
35+
t.Errorf("TestInstanceIDFromProviderID(%s) returned %s, want %s", tc.providerID, result, tc.expected)
36+
}
37+
})
38+
}
39+
})
40+
41+
t.Run("Error", func(t *testing.T) {
42+
tt := []struct {
43+
name string
44+
providerID string
45+
errorMsg string
46+
}{
47+
{
48+
name: "empty provider ID",
49+
providerID: "",
50+
errorMsg: "provider id is empty",
51+
},
52+
{
53+
name: "provider ID without oxide:// prefix",
54+
providerID: "12345678-1234-1234-1234-123456789abc",
55+
errorMsg: "provider id does not have 'oxide://' prefix",
56+
},
57+
{
58+
name: "provider ID with invalid UUID",
59+
providerID: "oxide://not-a-valid-uuid",
60+
errorMsg: "provider id contains invalid uuid",
61+
},
62+
{
63+
name: "provider ID with empty UUID",
64+
providerID: "oxide://",
65+
errorMsg: "provider id contains invalid uuid",
66+
},
67+
{
68+
name: "provider ID with partial UUID",
69+
providerID: "oxide://12345678-1234",
70+
errorMsg: "provider id contains invalid uuid",
71+
},
72+
{
73+
name: "provider ID with wrong prefix",
74+
providerID: "aws://12345678-1234-1234-1234-123456789abc",
75+
errorMsg: "provider id does not have 'oxide://' prefix",
76+
},
77+
}
78+
79+
for _, tc := range tt {
80+
t.Run(tc.name, func(t *testing.T) {
81+
_, err := InstanceIDFromProviderID(tc.providerID)
82+
if err == nil {
83+
t.Errorf("TestInstanceIDFromProviderID(%s) returned nil error, want non-nil error", tc.providerID)
84+
}
85+
86+
if !strings.Contains(err.Error(), tc.errorMsg) {
87+
t.Errorf("TestInstanceIDFromProviderID(%s) returned error %v, want %s", tc.providerID, err.Error(), tc.errorMsg)
88+
}
89+
})
90+
}
91+
})
92+
}
93+
94+
func TestNewProviderID(t *testing.T) {
95+
tests := []struct {
96+
name string
97+
instanceID string
98+
expected string
99+
}{
100+
{
101+
name: "valid instance ID",
102+
instanceID: "12345678-1234-1234-1234-123456789abc",
103+
expected: "oxide://12345678-1234-1234-1234-123456789abc",
104+
},
105+
{
106+
name: "empty instance ID",
107+
instanceID: "",
108+
expected: "oxide://",
109+
},
110+
{
111+
name: "instance ID with uppercase UUID",
112+
instanceID: "12345678-1234-1234-1234-123456789ABC",
113+
expected: "oxide://12345678-1234-1234-1234-123456789ABC",
114+
},
115+
}
116+
117+
for _, tt := range tests {
118+
t.Run(tt.name, func(t *testing.T) {
119+
result := NewProviderID(tt.instanceID)
120+
if result != tt.expected {
121+
t.Errorf("NewProviderID(%s) = %s, want %s", tt.instanceID, result, tt.expected)
122+
}
123+
})
124+
}
125+
}

0 commit comments

Comments
 (0)