Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/serviceinstance/service_instance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ spec:
servicePlan:
offering: destination
plan: lite
# tags are applied to the instance and surfaced in apps' VCAP_SERVICES.
# Omitting this field or setting it to [] results in no tags.
tags:
- prod
- eu-central
50 changes: 49 additions & 1 deletion internal/clients/serviceinstance/serviceinstance.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/cloudfoundry/go-cfclient/v3/resource"
xpresource "github.com/crossplane/crossplane-runtime/v2/pkg/resource"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/ptr"

"github.com/SAP/crossplane-provider-cloudfoundry/apis/resources/v1alpha1"
Expand Down Expand Up @@ -178,6 +179,7 @@ func (c *Client) createManaged(ctx context.Context, mg xpresource.Managed, spec

opt := resource.NewServiceInstanceCreateManaged(*spec.Name, *spec.Space, *spec.ServicePlan.ID)
opt.Metadata = metadata.BuildMetadata(mg, spec.Labels, spec.Annotations)
opt.WithTags(toTagSlice(spec.Tags))

if params != nil {
opt.Parameters = &params
Expand All @@ -204,6 +206,7 @@ func (c *Client) createUserProvided(ctx context.Context, mg xpresource.Managed,
// create the service instance
opt := resource.NewServiceInstanceCreateUserProvided(*spec.Name, *spec.Space)
opt.Metadata = metadata.BuildMetadata(mg, spec.Labels, spec.Annotations)

si, err := c.CreateUserProvided(ctx, opt)
if err != nil {
return nil, err
Expand All @@ -214,7 +217,8 @@ func (c *Client) createUserProvided(ctx context.Context, mg xpresource.Managed,
if creds != nil {
upt.WithCredentials(creds)
}
upt.WithRouteServiceURL(spec.RouteServiceURL).
upt.WithTags(toTagSlice(spec.Tags)).
WithRouteServiceURL(spec.RouteServiceURL).
WithSyslogDrainURL(spec.SyslogDrainURL)

return c.UpdateUserProvided(ctx, si.GUID, upt)
Expand Down Expand Up @@ -252,6 +256,8 @@ func (c *Client) updateManaged(ctx context.Context, observed *resource.ServiceIn
upd.WithParameters(params)
}

upd.WithTags(toTagSlice(desired.Tags))

upd.Metadata = metadata.BuildMetadata(mg, desired.Labels, desired.Annotations)

// Update the service instance
Expand Down Expand Up @@ -283,6 +289,9 @@ func (c *Client) updateUserProvided(ctx context.Context, observed *resource.Serv
if creds != nil {
upd.WithCredentials(creds)
}

upd.WithTags(toTagSlice(desired.Tags))

upd.WithRouteServiceURL(desired.RouteServiceURL).
WithSyslogDrainURL(desired.SyslogDrainURL)

Expand Down Expand Up @@ -320,6 +329,9 @@ func UpdateObservation(in *v1alpha1.ServiceInstanceObservation, r *resource.Serv
}

in.ID = &r.GUID

in.Tags = toTagPtrSlice(r.Tags)

in.LastOperation = v1alpha1.LastOperation{
Type: r.LastOperation.Type,
State: r.LastOperation.State,
Expand All @@ -344,6 +356,16 @@ func specUpToDate(in *v1alpha1.ServiceInstanceParameters, observed *resource.Ser
return false
}

if !tagsUpToDate(in.Tags, observed.Tags) {
return false
}

return typeSpecUpToDate(in, observed)
}

// typeSpecUpToDate checks the type-specific spec fields (service plan for managed,
// route service and syslog drain URLs for user-provided) against the observed CF resource.
func typeSpecUpToDate(in *v1alpha1.ServiceInstanceParameters, observed *resource.ServiceInstance) bool {
switch in.Type {
case v1alpha1.ManagedService:
if in.ServicePlan != nil && in.ServicePlan.ID != nil && observed.Relationships.ServicePlan.Data.GUID != *in.ServicePlan.ID {
Expand Down Expand Up @@ -440,6 +462,7 @@ func getDesiredSharedSpaces(refs []v1alpha1.SpaceReference) []string {
return guids
}

// REVISE: This can be simplified using the sets k8s helper, like in tagsUpToDate
// diffSharedSpaces compares the current and desired shared spaces and returns the spaces to add and remove to match the desired state
func diffSharedSpaces(current, desired []string) (toAdd, toRemove []string) {
currentSet := make(map[string]struct{}, len(current))
Expand All @@ -466,3 +489,28 @@ func diffSharedSpaces(current, desired []string) (toAdd, toRemove []string) {

return toAdd, toRemove
}

func toTagSlice(in []*string) []string {
out := make([]string, 0, len(in))
for _, t := range in {
if t != nil {
out = append(out, *t)
}
}
return out
}

func toTagPtrSlice(in []string) []*string {
if len(in) == 0 {
return nil
}
out := make([]*string, len(in))
for i := range in {
out[i] = &in[i]
}
return out
}

func tagsUpToDate(desired []*string, observed []string) bool {
return sets.New(toTagSlice(desired)...).Equal(sets.New(observed...))
}
78 changes: 78 additions & 0 deletions internal/clients/serviceinstance/serviceinstance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,84 @@ func TestIsUpToDate_Metadata(t *testing.T) {
},
want: false,
},
"Tags match same order": {
in: &v1alpha1.ServiceInstanceParameters{
Name: ptr.To("name"),
Type: v1alpha1.ManagedService,
Tags: []*string{ptr.To("tag1"), ptr.To("tag2"), ptr.To("tag3")},
},
observed: &resource.ServiceInstance{
Name: "name",
Type: "managed",
Tags: []string{"tag1", "tag2", "tag3"},
},
want: true,
},
"Tags match different order": {
in: &v1alpha1.ServiceInstanceParameters{
Name: ptr.To("name"),
Type: v1alpha1.ManagedService,
Tags: []*string{ptr.To("tag2"), ptr.To("tag3"), ptr.To("tag1")},
},
observed: &resource.ServiceInstance{
Name: "name",
Type: "managed",
Tags: []string{"tag1", "tag2", "tag3"},
},
want: true,
},
"Tags drift nil vs non-empty": {
in: &v1alpha1.ServiceInstanceParameters{
Name: ptr.To("name"),
Type: v1alpha1.ManagedService,
Tags: nil,
},
observed: &resource.ServiceInstance{
Name: "name",
Type: "managed",
Tags: []string{"tag"},
},
want: false,
},
"Tags match nil vs empty": {
in: &v1alpha1.ServiceInstanceParameters{
Name: ptr.To("name"),
Type: v1alpha1.ManagedService,
Tags: nil,
},
observed: &resource.ServiceInstance{
Name: "name",
Type: "managed",
Tags: []string{},
},
want: true,
},
"Tags match empty tags": {
in: &v1alpha1.ServiceInstanceParameters{
Name: ptr.To("name"),
Type: v1alpha1.ManagedService,
Tags: []*string{},
},
observed: &resource.ServiceInstance{
Name: "name",
Type: "managed",
Tags: []string{},
},
want: true,
},
"Tags drift empty vs observed non-empty": {
in: &v1alpha1.ServiceInstanceParameters{
Name: ptr.To("name"),
Type: v1alpha1.ManagedService,
Tags: []*string{},
},
observed: &resource.ServiceInstance{
Name: "name",
Type: "managed",
Tags: []string{"tag"},
},
want: false,
},
}

for n, tc := range cases {
Expand Down
Loading