Skip to content

Commit 921e535

Browse files
authored
Merge pull request #499 from stoyangenchev/feature/service-broker-provided-metadata
Add support for Service Broker-provided metadata on Service Instances
2 parents 76a5917 + e629767 commit 921e535

File tree

6 files changed

+217
-18
lines changed

6 files changed

+217
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Support for setting and reading application lifecycle (`buildpack`, `docker`, and `cnb`/Cloud Native Buildpacks) in V3 API.
88
- Custom marshaling/unmarshaling for `Lifecycle` struct to support multiple lifecycle types.
99
- Unit tests for all lifecycle types, including CNB.
10+
- Support for Service Broker-provided metadata (labels and attributes) on Service Instances. This includes the `BrokerProvidedMetadata` field on `ServiceInstance`, `ServiceInstanceManagedCreate`, and `ServiceInstanceManagedUpdate` structs, along with fluent builder methods for managing labels and attributes.
1011

1112
### Changed
1213

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package resource
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// BrokerProvidedMetadata allows the Service Broker to tag API resources with information that does not directly affect their functionality.
9+
type BrokerProvidedMetadata struct {
10+
Labels map[string]*string `json:"labels"`
11+
Attributes map[string]*string `json:"attributes"`
12+
}
13+
14+
// NewBrokerProvidedMetadata creates a new broker provided metadata instance
15+
func NewBrokerProvidedMetadata() *BrokerProvidedMetadata {
16+
return &BrokerProvidedMetadata{}
17+
}
18+
19+
// WithAttribute is a fluent method alias for SetAttribute
20+
func (m *BrokerProvidedMetadata) WithAttribute(prefix, key string, v string) *BrokerProvidedMetadata {
21+
m.SetAttribute(prefix, key, v)
22+
return m
23+
}
24+
25+
// WithLabel is a fluent method alias for SetLabel
26+
func (m *BrokerProvidedMetadata) WithLabel(prefix, key string, v string) *BrokerProvidedMetadata {
27+
m.SetLabel(prefix, key, v)
28+
return m
29+
}
30+
31+
// SetAttribute to the broker provided metadata instance
32+
//
33+
// The prefix and value are optional and may be an empty string. The key must be at least 1 character in length.
34+
func (m *BrokerProvidedMetadata) SetAttribute(prefix, key string, v string) {
35+
if m.Attributes == nil {
36+
m.Attributes = make(map[string]*string)
37+
}
38+
if len(prefix) > 0 {
39+
m.Attributes[fmt.Sprintf("%s/%s", prefix, key)] = &v
40+
} else {
41+
m.Attributes[key] = &v
42+
}
43+
}
44+
45+
// RemoveAttribute removes an attribute by setting the specified key's value to nil which can then be passed to the API
46+
func (m *BrokerProvidedMetadata) RemoveAttribute(prefix, key string) {
47+
if m.Attributes == nil {
48+
m.Attributes = make(map[string]*string)
49+
}
50+
if len(prefix) > 0 {
51+
m.Attributes[fmt.Sprintf("%s/%s", prefix, key)] = nil
52+
} else {
53+
m.Attributes[key] = nil
54+
}
55+
}
56+
57+
// SetLabel to the broker provided metadata instance
58+
//
59+
// The prefix and value are optional and may be an empty string. The key must be at least 1 character in length.
60+
func (m *BrokerProvidedMetadata) SetLabel(prefix, key string, v string) {
61+
if m.Labels == nil {
62+
m.Labels = make(map[string]*string)
63+
}
64+
if len(prefix) > 0 {
65+
m.Labels[fmt.Sprintf("%s/%s", prefix, key)] = &v
66+
} else {
67+
m.Labels[key] = &v
68+
}
69+
}
70+
71+
// RemoveLabel removes a label by setting the specified key's value to nil which can then be passed to the API
72+
func (m *BrokerProvidedMetadata) RemoveLabel(prefix, key string) {
73+
if m.Labels == nil {
74+
m.Labels = make(map[string]*string)
75+
}
76+
if len(prefix) > 0 {
77+
m.Labels[fmt.Sprintf("%s/%s", prefix, key)] = nil
78+
} else {
79+
m.Labels[key] = nil
80+
}
81+
}
82+
83+
// Clear automatically calls Remove on all attributes and labels present in the broker provided metadata instance
84+
func (m *BrokerProvidedMetadata) Clear() {
85+
splitKey := func(k string) (string, string) {
86+
p := strings.Split(k, "/")
87+
if len(p) == 1 {
88+
return "", p[0]
89+
}
90+
return p[0], p[1]
91+
}
92+
for k := range m.Attributes {
93+
prefix, key := splitKey(k)
94+
m.RemoveAttribute(prefix, key)
95+
}
96+
for k := range m.Labels {
97+
prefix, key := splitKey(k)
98+
m.RemoveLabel(prefix, key)
99+
}
100+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package resource_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/cloudfoundry/go-cfclient/v3/resource"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestBrokerProvidedMetadata(t *testing.T) {
13+
type metaTest struct {
14+
prefix string
15+
key string
16+
value string
17+
}
18+
tests := []metaTest{
19+
{
20+
prefix: "pre",
21+
key: "key",
22+
value: "val",
23+
},
24+
{
25+
prefix: "",
26+
key: "empty-pre-key",
27+
value: "val",
28+
},
29+
{
30+
prefix: "cf.example.org",
31+
key: "key",
32+
value: "val",
33+
},
34+
{
35+
prefix: "pre",
36+
key: "no-val-key",
37+
value: "",
38+
},
39+
{
40+
prefix: "",
41+
key: "only-key",
42+
value: "",
43+
},
44+
}
45+
46+
for _, tt := range tests {
47+
k := fmt.Sprintf("%s/%s", tt.prefix, tt.key)
48+
if tt.prefix == "" {
49+
k = tt.key
50+
}
51+
52+
// add some attributes and labels
53+
m := resource.BrokerProvidedMetadata{}
54+
m.SetAttribute(tt.prefix, tt.key, tt.value)
55+
m.SetLabel(tt.prefix, tt.key, tt.value)
56+
require.Equal(t, tt.value, *m.Attributes[k], "key: %s", k)
57+
require.Equal(t, tt.value, *m.Labels[k], "key: %s", k)
58+
59+
// remove them
60+
m.RemoveAttribute(tt.prefix, tt.key)
61+
m.RemoveLabel(tt.prefix, tt.key)
62+
require.Nil(t, m.Attributes[k], "key: %s", k)
63+
require.Nil(t, m.Labels[k], "key: %s", k)
64+
65+
// new attributes and labels
66+
m = resource.BrokerProvidedMetadata{}
67+
m.SetAttribute(tt.prefix, tt.key, tt.value)
68+
m.SetLabel(tt.prefix, tt.key, tt.value)
69+
require.Equal(t, tt.value, *m.Attributes[k], "key: %s", k)
70+
require.Equal(t, tt.value, *m.Labels[k], "key: %s", k)
71+
72+
// clear
73+
m.Clear()
74+
require.Nil(t, m.Attributes[k], "key: %s", k)
75+
require.Nil(t, m.Labels[k], "key: %s", k)
76+
}
77+
}

resource/service_instance.go

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import (
55
)
66

77
type ServiceInstance struct {
8-
Name string `json:"name"`
9-
Tags []string `json:"tags"` // Used by apps to identify service instances; they are shown in the app VCAP_SERVICES env
10-
Type string `json:"type"` // Either managed or user-provided
11-
LastOperation LastOperation `json:"last_operation"`
12-
Relationships ServiceInstanceRelationships `json:"relationships"`
13-
Metadata *Metadata `json:"metadata"`
8+
Name string `json:"name"`
9+
Tags []string `json:"tags"` // Used by apps to identify service instances; they are shown in the app VCAP_SERVICES env
10+
Type string `json:"type"` // Either managed or user-provided
11+
LastOperation LastOperation `json:"last_operation"`
12+
Relationships ServiceInstanceRelationships `json:"relationships"`
13+
Metadata *Metadata `json:"metadata"`
14+
BrokerProvidedMetadata *BrokerProvidedMetadata `json:"broker_provided_metadata"`
1415

1516
// Information about the version of this service instance; only shown when type is managed
1617
MaintenanceInfo *ServiceInstanceMaintenanceInfo `json:"maintenance_info,omitempty"`
@@ -32,12 +33,13 @@ type ServiceInstance struct {
3233
}
3334

3435
type ServiceInstanceManagedCreate struct {
35-
Type string `json:"type"` // Either managed or user-provided
36-
Name string `json:"name"`
37-
Relationships ServiceInstanceRelationships `json:"relationships"`
38-
Metadata *Metadata `json:"metadata,omitempty"`
39-
Parameters *json.RawMessage `json:"parameters,omitempty"` // A JSON object that is passed to the service broker
40-
Tags []string `json:"tags,omitempty"`
36+
Type string `json:"type"` // Either managed or user-provided
37+
Name string `json:"name"`
38+
Relationships ServiceInstanceRelationships `json:"relationships"`
39+
Metadata *Metadata `json:"metadata,omitempty"`
40+
BrokerProvidedMetadata *BrokerProvidedMetadata `json:"broker_provided_metadata,omitempty"`
41+
Parameters *json.RawMessage `json:"parameters,omitempty"` // A JSON object that is passed to the service broker
42+
Tags []string `json:"tags,omitempty"`
4143
}
4244
type ServiceInstanceUserProvidedCreate struct {
4345
Type string `json:"type"` // Either managed or user-provided
@@ -51,12 +53,13 @@ type ServiceInstanceUserProvidedCreate struct {
5153
}
5254

5355
type ServiceInstanceManagedUpdate struct {
54-
Name *string `json:"name,omitempty"`
55-
Relationships *ServiceInstanceRelationships `json:"relationships,omitempty"`
56-
MaintenanceInfo *ServiceInstanceMaintenanceInfo `json:"maintenance_info,omitempty"`
57-
Parameters *json.RawMessage `json:"parameters,omitempty"` // A JSON object that is passed to the service broker
58-
Tags []string `json:"tags"`
59-
Metadata *Metadata `json:"metadata,omitempty"`
56+
Name *string `json:"name,omitempty"`
57+
Relationships *ServiceInstanceRelationships `json:"relationships,omitempty"`
58+
MaintenanceInfo *ServiceInstanceMaintenanceInfo `json:"maintenance_info,omitempty"`
59+
Parameters *json.RawMessage `json:"parameters,omitempty"` // A JSON object that is passed to the service broker
60+
Tags []string `json:"tags"`
61+
Metadata *Metadata `json:"metadata,omitempty"`
62+
BrokerProvidedMetadata *BrokerProvidedMetadata `json:"broker_provided_metadata,omitempty"`
6063
}
6164

6265
type ServiceInstanceUserProvidedUpdate struct {
@@ -140,6 +143,11 @@ func (u *ServiceInstanceManagedCreate) WithParameters(parameters json.RawMessage
140143
return u
141144
}
142145

146+
func (u *ServiceInstanceManagedCreate) WithBrokerProvidedMetadata(metadata *BrokerProvidedMetadata) *ServiceInstanceManagedCreate {
147+
u.BrokerProvidedMetadata = metadata
148+
return u
149+
}
150+
143151
func NewServiceInstanceCreateUserProvided(name, spaceGUID string) *ServiceInstanceUserProvidedCreate {
144152
return &ServiceInstanceUserProvidedCreate{
145153
Type: "user-provided",
@@ -212,6 +220,11 @@ func (u *ServiceInstanceManagedUpdate) WithMaintenanceInfo(version, description
212220
return u
213221
}
214222

223+
func (u *ServiceInstanceManagedUpdate) WithBrokerProvidedMetadata(metadata *BrokerProvidedMetadata) *ServiceInstanceManagedUpdate {
224+
u.BrokerProvidedMetadata = metadata
225+
return u
226+
}
227+
215228
func NewServiceInstanceUserProvidedUpdate() *ServiceInstanceUserProvidedUpdate {
216229
return &ServiceInstanceUserProvidedUpdate{}
217230
}

testutil/template/service_instance.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
"labels": {},
3434
"annotations": {}
3535
},
36+
"broker_provided_metadata": {
37+
"labels": {},
38+
"attributes": {}
39+
},
3640
"links": {
3741
"self": {
3842
"href": "https://api.example.org/v3/service_instances/{{.GUID}}"

testutil/template/service_instance_user_provided.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
"labels": {},
2626
"annotations": {}
2727
},
28+
"broker_provided_metadata": {
29+
"labels": {},
30+
"attributes": {}
31+
},
2832
"links": {
2933
"self": {
3034
"href": "https://api.example.org/v3/service_instances/88ce23e5-27c3-4381-a2df-32a28ec43133"

0 commit comments

Comments
 (0)