Skip to content

Commit 075c011

Browse files
committed
feat: add ExtendedMetadata (#502)
1 parent 0921e00 commit 075c011

12 files changed

+209
-5
lines changed

api/v1beta1/conversion_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ import (
3636
func fuzzFuncs(_ runtimeserializer.CodecFactory) []interface{} {
3737
return []interface{}{
3838
OCIMachineFuzzer,
39+
OCIMachineHubFuzzer,
3940
OCIMachineTemplateFuzzer,
41+
OCIMachineTemplateHubFuzzer,
4042
OCIClusterFuzzer,
4143
OCIClusterTemplateFuzzer,
4244
OCIManagedClusterFuzzer,
@@ -47,6 +49,7 @@ func OCIMachineFuzzer(obj *OCIMachine, c fuzz.Continue) {
4749
c.FuzzNoCustom(obj)
4850
// nil fields which have been removed so that tests dont fail
4951
obj.Spec.NSGName = ""
52+
obj.Spec.ExtendedMetadata = nil
5053
}
5154

5255
func OCIClusterFuzzer(obj *OCICluster, c fuzz.Continue) {
@@ -97,6 +100,17 @@ func OCIMachineTemplateFuzzer(obj *OCIMachineTemplate, c fuzz.Continue) {
97100
c.FuzzNoCustom(obj)
98101
// nil fields which ave been removed so that tests dont fail
99102
obj.Spec.Template.Spec.NSGName = ""
103+
obj.Spec.Template.Spec.ExtendedMetadata = nil
104+
}
105+
106+
func OCIMachineHubFuzzer(obj *v1beta2.OCIMachine, c randfill.Continue) {
107+
c.FillNoCustom(obj)
108+
obj.Spec.ExtendedMetadata = nil
109+
}
110+
111+
func OCIMachineTemplateHubFuzzer(obj *v1beta2.OCIMachineTemplate, c randfill.Continue) {
112+
c.FillNoCustom(obj)
113+
obj.Spec.Template.Spec.ExtendedMetadata = nil
100114
}
101115

102116
func OCIManagedClusterFuzzer(obj *OCIManagedCluster, c fuzz.Continue) {

api/v1beta1/ocimachine_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2223
)
@@ -114,6 +115,11 @@ type OCIMachineSpec struct {
114115
// required to connect to the instance.
115116
Metadata map[string]string `json:"metadata,omitempty"`
116117

118+
// Additional metadata key/value pairs that provide non-string values.
119+
// Values support nested JSON objects and arrays and map to OCI LaunchInstanceDetails.extendedMetadata.
120+
// +kubebuilder:pruning:PreserveUnknownFields
121+
ExtendedMetadata map[string]apiextensionsv1.JSON `json:"extendedMetadata,omitempty"`
122+
117123
// Free-form tags for this resource.
118124
// +optional
119125
FreeformTags map[string]string `json:"freeformTags,omitempty"`

api/v1beta1/zz_generated.conversion.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/ocimachine_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta2
1818

1919
import (
20+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2223
)
@@ -112,6 +113,11 @@ type OCIMachineSpec struct {
112113
// required to connect to the instance.
113114
Metadata map[string]string `json:"metadata,omitempty"`
114115

116+
// Additional metadata key/value pairs that provide non-string values.
117+
// Values support nested JSON objects and arrays and map to OCI LaunchInstanceDetails.extendedMetadata.
118+
// +kubebuilder:pruning:PreserveUnknownFields
119+
ExtendedMetadata map[string]apiextensionsv1.JSON `json:"extendedMetadata,omitempty"`
120+
115121
// Free-form tags for this resource.
116122
// +optional
117123
FreeformTags map[string]string `json:"freeformTags,omitempty"`

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/scope/machine.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,15 @@ func (m *MachineScope) GetOrCreateMachine(ctx context.Context) (*core.Instance,
211211
return nil, err
212212
}
213213

214-
metadata := m.OCIMachine.Spec.Metadata
215-
if metadata == nil {
216-
metadata = make(map[string]string)
214+
metadata := make(map[string]string)
215+
for key, value := range m.OCIMachine.Spec.Metadata {
216+
metadata[key] = value
217217
}
218218
metadata["user_data"] = base64.StdEncoding.EncodeToString([]byte(cloudInitData))
219+
extendedMetadata, err := ConvertMachineExtendedMetadata(m.OCIMachine.Spec.ExtendedMetadata)
220+
if err != nil {
221+
return nil, err
222+
}
219223

220224
tags := m.getFreeFormTags()
221225

@@ -236,14 +240,14 @@ func (m *MachineScope) GetOrCreateMachine(ctx context.Context) (*core.Instance,
236240
},
237241
ComputeClusterId: m.OCIMachine.Spec.ComputeClusterId,
238242
Metadata: metadata,
243+
ExtendedMetadata: extendedMetadata,
239244
Shape: common.String(m.OCIMachine.Spec.Shape),
240245
AvailabilityDomain: common.String(availabilityDomain),
241246
CompartmentId: common.String(m.getCompartmentId()),
242247
IsPvEncryptionInTransitEnabled: common.Bool(m.OCIMachine.Spec.IsPvEncryptionInTransitEnabled),
243248
FreeformTags: tags,
244249
DefinedTags: definedTags,
245-
// ExtendedMetadata: m.OCIMachine.Spec.ExtendedMetadata,
246-
DedicatedVmHostId: m.OCIMachine.Spec.DedicatedVmHostId,
250+
DedicatedVmHostId: m.OCIMachine.Spec.DedicatedVmHostId,
247251
}
248252
// Compute API does not behave well if the shape config is empty for fixed shapes
249253
// hence set it only if it non empty

cloud/scope/machine_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/oracle/oci-go-sdk/v65/core"
4141
"github.com/pkg/errors"
4242
corev1 "k8s.io/api/core/v1"
43+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
4344
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4445
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
4546
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -284,6 +285,48 @@ func TestInstanceReconciliation(t *testing.T) {
284285
})).Return(core.LaunchInstanceResponse{}, nil)
285286
},
286287
},
288+
{
289+
name: "launch includes extended metadata",
290+
errorExpected: false,
291+
testSpecificSetup: func(machineScope *MachineScope, computeClient *mock_compute.MockComputeClient) {
292+
setupAllParams(ms)
293+
ms.OCIMachine.Spec.ExtendedMetadata = map[string]apiextensionsv1.JSON{
294+
"cilium-primary-vnic": {
295+
Raw: []byte(`{"ip-count":32,"cidr-blocks":["10.0.0.0/24"]}`),
296+
},
297+
}
298+
299+
computeClient.EXPECT().ListInstances(gomock.Any(), gomock.Eq(core.ListInstancesRequest{
300+
DisplayName: common.String("name"),
301+
CompartmentId: common.String("test"),
302+
})).Return(core.ListInstancesResponse{}, nil)
303+
304+
expectedExtendedMetadata := map[string]interface{}{
305+
"cilium-primary-vnic": map[string]interface{}{
306+
"ip-count": float64(32),
307+
"cidr-blocks": []interface{}{"10.0.0.0/24"},
308+
},
309+
}
310+
computeClient.EXPECT().LaunchInstance(gomock.Any(), Eq(func(request interface{}) error {
311+
return extendedMetadataMatcher(request, expectedExtendedMetadata)
312+
})).Return(core.LaunchInstanceResponse{}, nil)
313+
},
314+
},
315+
{
316+
name: "launch does not mutate spec metadata",
317+
errorExpected: false,
318+
testSpecificSetup: func(machineScope *MachineScope, computeClient *mock_compute.MockComputeClient) {
319+
setupAllParams(ms)
320+
ms.OCIMachine.Spec.Metadata = map[string]string{
321+
"managed": "true",
322+
}
323+
computeClient.EXPECT().ListInstances(gomock.Any(), gomock.Eq(core.ListInstancesRequest{
324+
DisplayName: common.String("name"),
325+
CompartmentId: common.String("test"),
326+
})).Return(core.ListInstancesResponse{}, nil)
327+
computeClient.EXPECT().LaunchInstance(gomock.Any(), gomock.Any()).Return(core.LaunchInstanceResponse{}, nil)
328+
},
329+
},
287330
{
288331
name: "check compartment at cluster",
289332
errorExpected: false,
@@ -1627,6 +1670,9 @@ func TestInstanceReconciliation(t *testing.T) {
16271670
}
16281671
} else {
16291672
g.Expect(err).To(BeNil())
1673+
if tc.name == "launch does not mutate spec metadata" {
1674+
g.Expect(ms.OCIMachine.Spec.Metadata).To(Equal(map[string]string{"managed": "true"}))
1675+
}
16301676
}
16311677
})
16321678
}
@@ -1692,6 +1738,17 @@ func faultDomainRequestWithRetryTokenMatcher(request interface{}, matchStr strin
16921738
return nil
16931739
}
16941740

1741+
func extendedMetadataMatcher(actual interface{}, expected map[string]interface{}) error {
1742+
r, ok := actual.(core.LaunchInstanceRequest)
1743+
if !ok {
1744+
return errors.New("expecting LaunchInstanceRequest type")
1745+
}
1746+
if !reflect.DeepEqual(r.LaunchInstanceDetails.ExtendedMetadata, expected) {
1747+
return errors.New(fmt.Sprintf("expecting extended metadata %v, actual %v", expected, r.LaunchInstanceDetails.ExtendedMetadata))
1748+
}
1749+
return nil
1750+
}
1751+
16951752
func platformConfigMatcher(actual interface{}, expected core.PlatformConfig) error {
16961753
r, ok := actual.(core.LaunchInstanceRequest)
16971754
if !ok {

cloud/scope/util.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ limitations under the License.
1717
package scope
1818

1919
import (
20+
"encoding/json"
21+
2022
infrastructurev1beta2 "github.com/oracle/cluster-api-provider-oci/api/v1beta2"
2123
"github.com/oracle/cluster-api-provider-oci/cloud/ociutil/ptr"
24+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2225
)
2326

2427
const (
@@ -80,3 +83,25 @@ func ConvertMachineDefinedTags(machineDefinedTags map[string]map[string]string)
8083

8184
return definedTags
8285
}
86+
87+
// ConvertMachineExtendedMetadata converts API extended metadata values into OCI SDK values.
88+
func ConvertMachineExtendedMetadata(machineExtendedMetadata map[string]apiextensionsv1.JSON) (map[string]interface{}, error) {
89+
if len(machineExtendedMetadata) == 0 {
90+
return nil, nil
91+
}
92+
93+
extendedMetadata := make(map[string]interface{}, len(machineExtendedMetadata))
94+
for k, v := range machineExtendedMetadata {
95+
if len(v.Raw) == 0 {
96+
extendedMetadata[k] = nil
97+
continue
98+
}
99+
var converted interface{}
100+
if err := json.Unmarshal(v.Raw, &converted); err != nil {
101+
return nil, err
102+
}
103+
extendedMetadata[k] = converted
104+
}
105+
106+
return extendedMetadata, nil
107+
}

cloud/scope/util_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/onsi/gomega"
2323
infrastructurev1beta2 "github.com/oracle/cluster-api-provider-oci/api/v1beta2"
2424
"github.com/oracle/oci-go-sdk/v65/common"
25+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2526
)
2627

2728
func Test_GetNsgNamesFromId(t *testing.T) {
@@ -194,3 +195,43 @@ func Test_GetSubnetNamesFromId(t *testing.T) {
194195
})
195196
}
196197
}
198+
199+
func TestConvertMachineExtendedMetadata(t *testing.T) {
200+
t.Run("converts nested json values", func(t *testing.T) {
201+
g := gomega.NewWithT(t)
202+
input := map[string]apiextensionsv1.JSON{
203+
"cilium-primary-vnic": {
204+
Raw: []byte(`{"ip-count":32,"cidr-blocks":["10.0.0.0/24"]}`),
205+
},
206+
}
207+
208+
actual, err := ConvertMachineExtendedMetadata(input)
209+
g.Expect(err).ToNot(gomega.HaveOccurred())
210+
g.Expect(actual).To(gomega.Equal(map[string]interface{}{
211+
"cilium-primary-vnic": map[string]interface{}{
212+
"ip-count": float64(32),
213+
"cidr-blocks": []interface{}{"10.0.0.0/24"},
214+
},
215+
}))
216+
})
217+
218+
t.Run("returns nil for empty input", func(t *testing.T) {
219+
g := gomega.NewWithT(t)
220+
actual, err := ConvertMachineExtendedMetadata(nil)
221+
g.Expect(err).ToNot(gomega.HaveOccurred())
222+
g.Expect(actual).To(gomega.BeNil())
223+
})
224+
225+
t.Run("returns error for invalid json", func(t *testing.T) {
226+
g := gomega.NewWithT(t)
227+
input := map[string]apiextensionsv1.JSON{
228+
"invalid": {
229+
Raw: []byte(`{"broken":`),
230+
},
231+
}
232+
233+
actual, err := ConvertMachineExtendedMetadata(input)
234+
g.Expect(err).To(gomega.HaveOccurred())
235+
g.Expect(actual).To(gomega.BeNil())
236+
})
237+
}

0 commit comments

Comments
 (0)