Skip to content

Commit

Permalink
Support objects for user-provided services credentials
Browse files Browse the repository at this point in the history
Previously Korifi supported plain `map[string]string` for UPSI
credentials. This deviates from the CF API spec where credentials can be
arbitrary objects.

As credentials are stored in secrets which are glorified
`map[string][]byte` we had to change the way we store the credentials
object:
* We marshal the credentials object into a byte array and store it under
  the `crdentials` key in the secret. We no longer use a secret key per
  every credentials value. The `CFServiceInstance.Spec.SecretName`
  references that credentials secret.
* We introduce `CFServiceInstance.Status.Credentials` and
  `CFServiceInstance.Status.CredentialsObservedVersion` that aremanaged
  by the `CFServiceInstance` controller. Once the controller
  reconciles the service instance, it sets the secret name and its
  resource version into the new status fields.
* The CFServiceBindingController reconciles the CFServiceInstance
  credentials secret into the flat format that is then used for the
  servicebinding.io binding. In case of a json object value to a key,
  the value is represented as a json string.
* Existing bindings are not affected when upgrading Korifi. However,
  once the user updates the service instance credentials, the binding
  secret is rebuilt which causes statefulset restart. Further updates to
  the credentials will not cause additional restarts.

fixes #2900

Co-authored-by: Danail Branekov <danailster@gmail.com>
  • Loading branch information
georgethebeatle and danail-branekov committed Feb 23, 2024
1 parent 4a55850 commit 50f3c4e
Show file tree
Hide file tree
Showing 23 changed files with 1,266 additions and 525 deletions.
4 changes: 2 additions & 2 deletions api/handlers/service_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ var _ = Describe("ServiceInstance", func() {
requestValidator.DecodeAndValidateJSONPayloadStub = decodeAndValidatePayloadStub(&payloads.ServiceInstancePatch{
Name: tools.PtrTo("new-name"),
Tags: &[]string{"alice", "bob"},
Credentials: &map[string]string{"foo": "bar"},
Credentials: &map[string]any{"foo": "bar"},
Metadata: payloads.MetadataPatch{
Annotations: map[string]*string{"ann2": tools.PtrTo("ann_val2")},
Labels: map[string]*string{"lab2": tools.PtrTo("lab_val2")},
Expand Down Expand Up @@ -306,7 +306,7 @@ var _ = Describe("ServiceInstance", func() {
GUID: "service-instance-guid",
SpaceGUID: "space-guid",
Name: tools.PtrTo("new-name"),
Credentials: &map[string]string{"foo": "bar"},
Credentials: &map[string]any{"foo": "bar"},
Tags: &[]string{"alice", "bob"},
MetadataPatch: repositories.MetadataPatch{
Annotations: map[string]*string{"ann2": tools.PtrTo("ann_val2")},
Expand Down
15 changes: 8 additions & 7 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

var createTimeout = time.Second * 120
var conditionTimeout = time.Second * 120

func init() {
utilruntime.Must(korifiv1alpha1.AddToScheme(scheme.Scheme))
Expand Down Expand Up @@ -126,14 +126,14 @@ func main() {
privilegedCRClient,
userClientFactory,
nsPermissions,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList](createTimeout),
conditions.NewConditionAwaiter[*korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList](conditionTimeout),
)
spaceRepo := repositories.NewSpaceRepo(
namespaceRetriever,
orgRepo,
userClientFactory,
nsPermissions,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpaceList](createTimeout),
conditions.NewConditionAwaiter[*korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpaceList](conditionTimeout),
)
processRepo := repositories.NewProcessRepo(
namespaceRetriever,
Expand All @@ -147,7 +147,7 @@ func main() {
namespaceRetriever,
userClientFactory,
nsPermissions,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFApp, korifiv1alpha1.CFAppList](createTimeout),
conditions.NewConditionAwaiter[*korifiv1alpha1.CFApp, korifiv1alpha1.CFAppList](conditionTimeout),
)
dropletRepo := repositories.NewDropletRepo(
userClientFactory,
Expand Down Expand Up @@ -183,18 +183,19 @@ func main() {
nsPermissions,
toolsregistry.NewRepositoryCreator(cfg.ContainerRegistryType),
cfg.ContainerRepositoryPrefix,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFPackage, korifiv1alpha1.CFPackageList](createTimeout),
conditions.NewConditionAwaiter[*korifiv1alpha1.CFPackage, korifiv1alpha1.CFPackageList](conditionTimeout),
)
serviceInstanceRepo := repositories.NewServiceInstanceRepo(
namespaceRetriever,
userClientFactory,
nsPermissions,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceInstance, korifiv1alpha1.CFServiceInstanceList](conditionTimeout),
)
serviceBindingRepo := repositories.NewServiceBindingRepo(
namespaceRetriever,
userClientFactory,
nsPermissions,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBindingList](createTimeout),
conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBindingList](conditionTimeout),
)
buildpackRepo := repositories.NewBuildpackRepository(cfg.BuilderName,
userClientFactory,
Expand All @@ -221,7 +222,7 @@ func main() {
userClientFactory,
namespaceRetriever,
nsPermissions,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList](createTimeout),
conditions.NewConditionAwaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList](conditionTimeout),
)
metricsRepo := repositories.NewMetricsRepo(userClientFactory)

Expand Down
12 changes: 6 additions & 6 deletions api/payloads/service_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type ServiceInstanceCreate struct {
Name string `json:"name"`
Type string `json:"type"`
Tags []string `json:"tags"`
Credentials map[string]string `json:"credentials"`
Credentials map[string]any `json:"credentials"`
Relationships *ServiceInstanceRelationships `json:"relationships"`
Metadata Metadata `json:"metadata"`
}
Expand Down Expand Up @@ -74,10 +74,10 @@ func (r ServiceInstanceRelationships) Validate() error {
}

type ServiceInstancePatch struct {
Name *string `json:"name,omitempty"`
Tags *[]string `json:"tags,omitempty"`
Credentials *map[string]string `json:"credentials,omitempty"`
Metadata MetadataPatch `json:"metadata"`
Name *string `json:"name,omitempty"`
Tags *[]string `json:"tags,omitempty"`
Credentials *map[string]any `json:"credentials,omitempty"`
Metadata MetadataPatch `json:"metadata"`
}

func (p ServiceInstancePatch) Validate() error {
Expand Down Expand Up @@ -120,7 +120,7 @@ func (p *ServiceInstancePatch) UnmarshalJSON(data []byte) error {
}

if v, ok := patchMap["credentials"]; ok && v == nil {
patch.Credentials = &map[string]string{}
patch.Credentials = &map[string]any{}
}

*p = ServiceInstancePatch(patch)
Expand Down
27 changes: 18 additions & 9 deletions api/payloads/service_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@ var _ = Describe("ServiceInstanceCreate", func() {
Name: "service-instance-name",
Type: "user-provided",
Tags: []string{"foo", "bar"},
Credentials: map[string]string{
Credentials: map[string]any{
"username": "bob",
"password": "float",
"object": map[string]any{
"a": "b",
},
},
Relationships: &payloads.ServiceInstanceRelationships{
Space: &payloads.Relationship{
Expand Down Expand Up @@ -188,9 +191,13 @@ var _ = Describe("ServiceInstanceCreate", func() {
Expect(msg.Annotations).To(HaveKeyWithValue("ann1", "val_ann1"))
Expect(msg.Labels).To(HaveLen(1))
Expect(msg.Labels).To(HaveKeyWithValue("lab1", "val_lab1"))
Expect(msg.Credentials).To(HaveLen(2))
Expect(msg.Credentials).To(HaveKeyWithValue("username", "bob"))
Expect(msg.Credentials).To(HaveKeyWithValue("password", "float"))
Expect(msg.Credentials).To(MatchAllKeys(Keys{
"username": Equal("bob"),
"password": Equal("float"),
"object": MatchAllKeys(Keys{
"a": Equal("b"),
}),
}))
})
})
})
Expand Down Expand Up @@ -261,9 +268,10 @@ var _ = Describe("ServiceInstancePatch", func() {
patchPayload = payloads.ServiceInstancePatch{
Name: tools.PtrTo("service-instance-name"),
Tags: &[]string{"foo", "bar"},
Credentials: &map[string]string{
"username": "bob",
"password": "float",
Credentials: &map[string]any{
"object": map[string]any{
"a": "b",
},
},
Metadata: payloads.MetadataPatch{
Annotations: map[string]*string{"ann1": tools.PtrTo("val_ann1")},
Expand Down Expand Up @@ -316,8 +324,9 @@ var _ = Describe("ServiceInstancePatch", func() {
"lab1": PointTo(Equal("val_lab1")),
}))
Expect(msg.Credentials).To(PointTo(MatchAllKeys(Keys{
"username": Equal("bob"),
"password": Equal("float"),
"object": MatchAllKeys(Keys{
"a": Equal("b"),
}),
})))
})
})
Expand Down
Loading

0 comments on commit 50f3c4e

Please sign in to comment.