Skip to content

Commit

Permalink
fix: save s3SecretKey and s3KeyID in secret, allow s3key deletion (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristiGuranIonos authored May 29, 2024
1 parent b92155f commit c60d037
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 49 deletions.
8 changes: 1 addition & 7 deletions apis/compute/v1alpha1/s3key_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ type S3KeyParameters struct {
//
// +kubebuilder:validation:Required
UserID string `json:"userID"`
// The S3 Secret key.
//
// +immutable
// +kubebuilder:validation:Optional
SecretKey string `json:"secretKey"`
// Whether the S3 is active / enabled or not. Can only be updated to false, by default the key will be created as active. Default value is true.
//
// +kubebuilder:validation:Optional
Expand Down Expand Up @@ -60,8 +55,7 @@ type S3KeyStatus struct {

// S3KeyObservation are the observable fields of a S3Key.
type S3KeyObservation struct {
SecretKey string `json:"secretKey,omitempty"`
S3KeyID string `json:"s3KeyID,omitempty"`
S3KeyID string `json:"s3KeyID,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
6 changes: 5 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
## [1.0.13] (TBD)
## [1.0.13]
- **Features**:
- Add `datacenterID` field for node pool lan in k8s `NodePool` CRD managed resources:
- Save `s3SecretKey` and `s3keyID` to the `Secrets` field in the `S3Key` CRD

- **Fixes**:
- S3Key should be properly deleted
- Remove useless `secretKey` field in s3Key
- **Misc**:
- Use builtin `controller.Options` in controller setup functions

Expand Down
2 changes: 0 additions & 2 deletions docs/api/compute-engine/s3key.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ In order to configure the IONOS Cloud Resource, the user can set the `spec.forPr

* `active` (boolean)
* description: Whether the S3 is active / enabled or not. Can only be updated to false, by default the key will be created as active. Default value is true.
* `secretKey` (string)
* description: The S3 Secret key.
* `userID` (string)
* description: The UUID of the user owning the S3 Key.

Expand Down
2 changes: 2 additions & 0 deletions docs/api/managed-kubernetes/nodepool.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ viable upgrade targets for all prior versions.
* `lans` (array)
* description: Array of additional private LANs attached to worker nodes.
* properties:
* `datacenterID` (string)
* description: The datacenter ID, requires system privileges, for internal usage only
* `dhcp` (boolean)
* description: Indicates if the Kubernetes NodePool LAN will reserve an IP using DHCP.
* `lanConfig` (object)
Expand Down
10 changes: 8 additions & 2 deletions examples/ionoscloud/compute/s3key.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Use this file to create a S3 key.
# Required values for creating a S3Key CR are:
# userId (via ID).
# SecretKey is automatically returned by the API and cannot be set.
# s3SecretKey is automatically returned by the API and will be saved in s3key secret in crossplane-system namespace.
# To see the data inside the secret, run the following commands:
# kubectl get secrets/s3key --template={{.data.s3SecretKey}} | base64
# kubectl get secrets/s3key --template={{.data.s3KeyID}} | base64
apiVersion: compute.ionoscloud.crossplane.io/v1alpha1
kind: S3Key
metadata:
Expand All @@ -11,8 +14,11 @@ spec:
- "*"
forProvider:
# replace with the user id for which you want the s3 key to be created
userID: "000000-0000-0000-0000-0000"
userID: "ee6bcd16-bb84-4848-a4f3-9e4b1449250f"
# optional, can be left out. the key can only be updated to false after creation
active: true
providerConfigRef:
name: example
writeConnectionSecretToRef:
name: "s3key"
namespace: "crossplane-system"
27 changes: 5 additions & 22 deletions internal/clients/compute/s3key/s3key.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,32 +39,15 @@ func (cp *APIClient) GetAPIClient() *sdkgo.APIClient {
return cp.ComputeClient
}

// LateInitializer fills the empty fields in *v1alpha1.S3KeyParameters with
// the values seen in sdkgo.S3Key.
func LateInitializer(in *v1alpha1.S3KeyParameters, s3Key *sdkgo.S3Key) {
if s3Key == nil {
return
}
// Add secretKey to the Spec, if it was set by the API
if propertiesOk, ok := s3Key.GetPropertiesOk(); ok && propertiesOk != nil {
if secretKeyOk, ok := propertiesOk.GetSecretKeyOk(); ok && secretKeyOk != nil {
if in.SecretKey == "" {
in.SecretKey = *secretKeyOk
}
}
}
}

// IsS3KeyUpToDate returns true if the S3Key is up-to-date or false if it does not
func IsS3KeyUpToDate(cr *v1alpha1.S3Key, s3Key sdkgo.S3Key) bool { // nolint:gocyclo
if cr == nil {
return false
}
switch {
case cr == nil && s3Key.Properties == nil:
case s3Key.Properties == nil:
return true
case cr == nil && s3Key.Properties != nil:
return false
case cr != nil && s3Key.Properties == nil:
return false
case s3Key.Properties.SecretKey != nil && *s3Key.Properties.SecretKey != cr.Spec.ForProvider.SecretKey:
case s3Key.Properties != nil:
return false
case s3Key.Properties.Active == nil && cr.Spec.ForProvider.Active != *s3Key.Properties.Active:
return false
Expand Down
35 changes: 26 additions & 9 deletions internal/controller/compute/s3key/s3Key.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ package s3key
import (
"context"
"fmt"
"net/http"
"strings"

ionosdk "github.com/ionos-cloud/sdk-go/v6"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
Expand Down Expand Up @@ -118,17 +121,20 @@ func (c *externalS3Key) Observe(ctx context.Context, mg resource.Managed) (manag
observed, apiResponse, err := c.service.GetS3Key(ctx, cr.Spec.ForProvider.UserID, cr.Status.AtProvider.S3KeyID)
if err != nil {
retErr := fmt.Errorf("failed to get S3Key by id. error: %w", err)
// we get a 422 error response on key not found instead of 404
if apiResponse != nil && apiResponse.Response != nil && apiResponse.StatusCode == http.StatusUnprocessableEntity && strings.Contains(err.Error(), "The access key cannot be found") {
return managed.ExternalObservation{}, nil
}
return managed.ExternalObservation{}, compute.ErrorUnlessNotFound(apiResponse, retErr)
}
current := cr.Spec.ForProvider.DeepCopy()
s3key.LateInitializer(&cr.Spec.ForProvider, &observed)
cr.Status.AtProvider.S3KeyID = meta.GetExternalName(cr)
cr.SetConditions(xpv1.Available())

return managed.ExternalObservation{
ResourceExists: true,
ResourceUpToDate: s3key.IsS3KeyUpToDate(cr, observed),
ConnectionDetails: managed.ConnectionDetails{},
ConnectionDetails: s3ConnectionDetailsTo(observed),
ResourceLateInitialized: !cmp.Equal(current, &cr.Spec.ForProvider),
}, nil
}
Expand All @@ -141,16 +147,13 @@ func (c *externalS3Key) Create(ctx context.Context, mg resource.Managed) (manage
cr.SetConditions(xpv1.Creating())

newInstance, apiResponse, err := c.service.CreateS3Key(ctx, cr.Spec.ForProvider.UserID)
creation := managed.ExternalCreation{ConnectionDetails: managed.ConnectionDetails{}}
if err != nil {
retErr := fmt.Errorf("failed to create S3Key. error: %w", err)
return creation, compute.AddAPIResponseInfo(apiResponse, retErr)
return managed.ExternalCreation{}, compute.AddAPIResponseInfo(apiResponse, retErr)
}

cr.Status.AtProvider.SecretKey = *newInstance.Properties.SecretKey
cr.Status.AtProvider.S3KeyID = *newInstance.Id
meta.SetExternalName(cr, *newInstance.Id)
return creation, nil
return managed.ExternalCreation{ConnectionDetails: s3ConnectionDetailsTo(newInstance)}, nil
}

func (c *externalS3Key) Update(ctx context.Context, mg resource.Managed) (managed.ExternalUpdate, error) {
Expand All @@ -165,15 +168,17 @@ func (c *externalS3Key) Update(ctx context.Context, mg resource.Managed) (manage
return managed.ExternalUpdate{}, err
}

_, apiResponse, err := c.service.UpdateS3Key(ctx, cr.Spec.ForProvider.UserID, S3KeyID, *instanceInput)
observed, apiResponse, err := c.service.UpdateS3Key(ctx, cr.Spec.ForProvider.UserID, S3KeyID, *instanceInput)
if err != nil {
retErr := fmt.Errorf("failed to update S3Key. error: %w", err)
return managed.ExternalUpdate{}, compute.AddAPIResponseInfo(apiResponse, retErr)
}
if err = compute.WaitForRequest(ctx, c.service.GetAPIClient(), apiResponse); err != nil {
return managed.ExternalUpdate{}, fmt.Errorf("while waiting for request. %w", err)
}
return managed.ExternalUpdate{}, nil
return managed.ExternalUpdate{
ConnectionDetails: s3ConnectionDetailsTo(observed),
}, nil
}

func (c *externalS3Key) Delete(ctx context.Context, mg resource.Managed) error {
Expand All @@ -192,3 +197,15 @@ func (c *externalS3Key) Delete(ctx context.Context, mg resource.Managed) error {

return compute.WaitForRequest(ctx, c.service.GetAPIClient(), apiResponse)
}

func s3ConnectionDetailsTo(observed ionosdk.S3Key) managed.ConnectionDetails {
var details = make(managed.ConnectionDetails)
if observed.Id == nil || observed.Properties == nil {
return details
}
props := observed.GetProperties()
details["s3KeyID"] = []byte(utils.DereferenceOrZero(observed.Id))
details["s3SecretKey"] = []byte(*props.SecretKey)

return details
}
5 changes: 0 additions & 5 deletions package/crds/compute.ionoscloud.crossplane.io_s3keys.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ spec:
be updated to false, by default the key will be created as active.
Default value is true.
type: boolean
secretKey:
description: The S3 Secret key.
type: string
userID:
description: The UUID of the user owning the S3 Key.
type: string
Expand Down Expand Up @@ -266,8 +263,6 @@ spec:
properties:
s3KeyID:
type: string
secretKey:
type: string
type: object
conditions:
description: Conditions of the resource.
Expand Down
2 changes: 1 addition & 1 deletion package/crds/k8s.ionoscloud.crossplane.io_nodepools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ spec:
description: KubernetesNodePoolLan struct for KubernetesNodePoolLan.
properties:
datacenterID:
description: // The datacenter ID, requires system privileges,
description: The datacenter ID, requires system privileges,
for internal usage only
type: string
dhcp:
Expand Down

0 comments on commit c60d037

Please sign in to comment.