Skip to content
This repository has been archived by the owner on Mar 4, 2024. It is now read-only.

EVEREST-107: config and secrets consistency via owner references #303

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 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
194 changes: 114 additions & 80 deletions api/backup_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package api ...
//
//nolint:dupl
package api

import (
"context"
"fmt"
"net/http"

Expand Down Expand Up @@ -50,14 +48,15 @@
BucketName: s.Spec.Bucket,
Region: s.Spec.Region,
Url: &s.Spec.EndpointURL,
Status: s.Status.Status,

Check failure on line 51 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 51 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

s.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 51 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 51 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

s.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 51 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 51 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

s.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
})
}

return ctx.JSON(http.StatusOK, result)
}

// CreateBackupStorage creates a new backup storage object.
func (e *EverestServer) CreateBackupStorage(ctx echo.Context) error { //nolint:funlen
func (e *EverestServer) CreateBackupStorage(ctx echo.Context) error {
params, err := validateCreateBackupStorageRequest(ctx, e.l)
if err != nil {
return ctx.JSON(http.StatusBadRequest, Error{Message: pointer.ToString(err.Error())})
Expand All @@ -74,41 +73,11 @@
// if s != nil passes
if s != nil && s.Name != "" {
return ctx.JSON(http.StatusConflict, Error{
Message: pointer.ToString(fmt.Sprintf("Storage %s already exists", params.Name)),
Message: pointer.ToString(fmt.Sprintf("Backup storage %s already exists", params.Name)),
})
}
_, err = e.kubeClient.CreateSecret(c, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: params.Name,
Namespace: e.kubeClient.Namespace(),
},
Type: corev1.SecretTypeOpaque,
StringData: e.backupSecretData(params.SecretKey, params.AccessKey),
})
if err != nil {
if k8serrors.IsAlreadyExists(err) {
_, err = e.kubeClient.UpdateSecret(c, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: params.Name,
Namespace: e.kubeClient.Namespace(),
},
Type: corev1.SecretTypeOpaque,
StringData: e.backupSecretData(params.SecretKey, params.AccessKey),
})
if err != nil {
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString(fmt.Sprintf("Failed updating the secret %s", params.Name)),
})
}
} else {
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Failed creating the secret for the backup storage"),
})
}
}
bs := &everestv1alpha1.BackupStorage{

bsData := &everestv1alpha1.BackupStorage{
ObjectMeta: metav1.ObjectMeta{
Name: params.Name,
Namespace: e.kubeClient.Namespace(),
Expand All @@ -119,34 +88,68 @@
Region: params.Region,
CredentialsSecretName: params.Name,
},
Status: {

Check failure on line 91 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

missing type in composite literal

Check failure on line 91 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

missing type in composite literal

Check failure on line 91 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

missing type in composite literal
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
Status: "Initializing",
},
}
if params.Url != nil {
bs.Spec.EndpointURL = *params.Url
bsData.Spec.EndpointURL = *params.Url
}
if params.Description != nil {
bs.Spec.Description = *params.Description
bsData.Spec.Description = *params.Description
}
err = e.kubeClient.CreateBackupStorage(c, bs)
bs, err := e.kubeClient.CreateBackupStorage(c, bsData)
if err != nil {
e.l.Error(err)
// TODO: Move this logic to the operator
dErr := e.kubeClient.DeleteSecret(c, params.Name)
if dErr != nil {
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Failing cleaning up the secret because failed creating backup storage"),
})
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Could not create a backup storage"),
})
}

secret, err := e.createBackupStorageSecret(c, bs, params.AccessKey, params.SecretKey)
if err != nil {
e.l.Error(err)

if err := e.kubeClient.DeleteBackupStorage(c, bs.Name); err != nil {
// TODO: We shall clean up the backup storage. Options:
// 1. Retry delete here
// 2. Backend to poll for not-ready storages and delete
// 3. Operator to poll for not-ready storages and delete
e.l.Errorf("Could not delete backup storage %s in initializing state", bs.Name)
}

return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Failed creating backup storage"),
Message: pointer.ToString("Could not create a secret for the backup storage"),
})
}

bs.Status.Status = "Ready"

Check failure on line 126 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 126 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 126 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have the possible statuses defined as constants in one place

bs.Spec.CredentialsSecretName = secret.Name

if err := e.kubeClient.UpdateBackupStorage(c, bs); err != nil {
e.l.Error(err)
if err := e.kubeClient.DeleteBackupStorage(c, bs.Name); err != nil {
// TODO: We shall clean up the backup storage. Options:
// 1. Retry delete here
// 2. Backend to poll for not-ready storages and delete
// 3. Operator to poll for not-ready storages and delete
e.l.Errorf("Could not delete backup storage %s in initializing state", bs.Name)
}

return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Could not finish backup storage configuration"),
})

}

result := BackupStorage{
Type: BackupStorageType(params.Type),
Name: params.Name,
Description: params.Description,
BucketName: params.BucketName,
Region: params.Region,
Url: params.Url,
Type: BackupStorageType(bs.Spec.Type),
Name: bs.Name,
Description: &bs.Spec.Description,
BucketName: bs.Spec.Bucket,
Region: bs.Spec.Region,
Url: &bs.Spec.EndpointURL,
Status: bs.Status.Status,

Check failure on line 152 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 152 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 152 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 152 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 152 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 152 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to expose the status to the API? Looks like the "Initializing" will never returned to the API user anyway.

}

return ctx.JSON(http.StatusOK, result)
Expand All @@ -158,7 +161,7 @@
if err != nil {
if k8serrors.IsNotFound(err) {
return ctx.JSON(http.StatusNotFound, Error{
Message: pointer.ToString("Backup storage is not found"),
Message: pointer.ToString("Backup storage not found"),
})
}
e.l.Error(err)
Expand All @@ -168,27 +171,16 @@
}
if used {
return ctx.JSON(http.StatusBadRequest, Error{
Message: pointer.ToString(fmt.Sprintf("Backup storage %s is used", backupStorageName)),
Message: pointer.ToString(fmt.Sprintf("Backup storage %s is in use", backupStorageName)),
})
}
if err := e.kubeClient.DeleteBackupStorage(ctx.Request().Context(), backupStorageName); err != nil {
if k8serrors.IsNotFound(err) {
return ctx.JSON(http.StatusNotFound, Error{
Message: pointer.ToString("Backup storage is not found"),
})
}
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Failed to delete BackupStorage"),
})
}
if err := e.kubeClient.DeleteSecret(ctx.Request().Context(), backupStorageName); err != nil {
if k8serrors.IsNotFound(err) {
return ctx.NoContent(http.StatusNoContent)
}
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Failed to delete the secret"),
Message: pointer.ToString("Failed to delete a backup storage"),
})
}

Expand All @@ -208,7 +200,7 @@
if err != nil {
if k8serrors.IsNotFound(err) {
return ctx.JSON(http.StatusNotFound, Error{
Message: pointer.ToString("Backup storage is not found"),
Message: pointer.ToString("Backup storage not found"),
})
}
e.l.Error(err)
Expand All @@ -223,6 +215,7 @@
BucketName: s.Spec.Bucket,
Region: s.Spec.Region,
Url: &s.Spec.EndpointURL,
Status: s.Status.Status,

Check failure on line 218 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 218 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

s.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 218 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 218 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

s.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 218 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

unknown field Status in struct literal of type BackupStorage

Check failure on line 218 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

s.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
})
}

Expand All @@ -233,31 +226,32 @@
if err != nil {
if k8serrors.IsNotFound(err) {
return ctx.JSON(http.StatusNotFound, Error{
Message: pointer.ToString("Backup storage is not found"),
Message: pointer.ToString("Backup storage not found"),
})
}
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Failed getting backup storage"),
})
}

if bs.Status.Status != "Ready" {

Check failure on line 238 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Test (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 238 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / API Integration Tests (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)

Check failure on line 238 in api/backup_storage.go

View workflow job for this annotation

GitHub Actions / Check (1.21.x, false)

bs.Status.Status undefined (type "github.com/percona/everest-operator/api/v1alpha1".BackupStorageStatus has no field or method Status)) (typecheck)
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
return ctx.JSON(http.StatusNotFound, Error{
Message: pointer.ToString("Backup storage is not ready"),
})
}

params, err := validateUpdateBackupStorageRequest(ctx, bs, e.l)
if err != nil {
return ctx.JSON(http.StatusBadRequest, Error{Message: pointer.ToString(err.Error())})
}
var secret *corev1.Secret
if params.AccessKey != nil && params.SecretKey != nil {
_, err = e.kubeClient.UpdateSecret(c, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: backupStorageName,
Namespace: e.kubeClient.Namespace(),
},
Type: corev1.SecretTypeOpaque,
StringData: e.backupSecretData(*params.SecretKey, *params.AccessKey),
})
secret, err = e.createBackupStorageSecret(c, bs, *params.AccessKey, *params.SecretKey)
if err != nil {
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString(fmt.Sprintf("Failed updating the secret %s", backupStorageName)),
Message: pointer.ToString(fmt.Sprintf("Could not create secret for backup storage %s", backupStorageName)),
})
}
}
Expand All @@ -274,13 +268,29 @@
bs.Spec.Description = *params.Description
}

err = e.kubeClient.UpdateBackupStorage(c, bs)
if err != nil {
var prevSecretName string
if secret != nil {
prevSecretName = bs.Spec.CredentialsSecretName
bs.Spec.CredentialsSecretName = secret.Name
}

if err = e.kubeClient.UpdateBackupStorage(c, bs); err != nil {
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Message: pointer.ToString("Failed updating backup storage"),
})
}

if prevSecretName != "" {
if err := e.kubeClient.DeleteSecret(c, prevSecretName); err != nil {
// TODO: We shall clean up the old secret. Options:
// 1. Retry delete here
// 2. Backend to poll for not-used secrets and delete
// 3. Operator to poll for not-used secrets and delete
e.l.Errorf("Could not delete secret %s", prevSecretName)
}
}

result := BackupStorage{
Type: BackupStorageType(bs.Spec.Type),
Name: bs.Name,
Expand All @@ -292,3 +302,27 @@

return ctx.JSON(http.StatusOK, result)
}

func (e *EverestServer) createBackupStorageSecret(
ctx context.Context, bs *everestv1alpha1.BackupStorage,
accessKey, secretKey string,
) (*corev1.Secret, error) {
secretData := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: bs.Name + "-",
Namespace: e.kubeClient.Namespace(),
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: bs.APIVersion,
Kind: bs.Kind,
Name: bs.Name,
UID: bs.UID,
},
},
},
Type: corev1.SecretTypeOpaque,
StringData: e.backupSecretData(secretKey, accessKey),
}

return e.kubeClient.CreateSecret(ctx, secretData)
}
17 changes: 4 additions & 13 deletions api/monitoring_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package api ...
//
//nolint:dupl
package api

import (
Expand Down Expand Up @@ -71,24 +68,18 @@ func (e *EverestServer) CreateMonitoringInstance(ctx echo.Context) error { //nol
})
}
}
_, err = e.kubeClient.CreateSecret(c, &corev1.Secret{
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: params.Name,
Namespace: e.kubeClient.Namespace(),
},
Type: corev1.SecretTypeOpaque,
StringData: e.monitoringConfigSecretData(apiKey),
})
}
_, err = e.kubeClient.CreateSecret(c, secret)
if err != nil {
if k8serrors.IsAlreadyExists(err) {
_, err = e.kubeClient.UpdateSecret(c, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: params.Name,
Namespace: e.kubeClient.Namespace(),
},
Type: corev1.SecretTypeOpaque,
StringData: e.monitoringConfigSecretData(apiKey),
})
_, err = e.kubeClient.UpdateSecret(c, secret)
if err != nil {
e.l.Error(err)
return ctx.JSON(http.StatusInternalServerError, Error{
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubernetes/backup_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (k *Kubernetes) GetBackupStorage(ctx context.Context, name string) (*everes
}

// CreateBackupStorage returns backup storages by provided name.
func (k *Kubernetes) CreateBackupStorage(ctx context.Context, storage *everestv1alpha1.BackupStorage) error {
func (k *Kubernetes) CreateBackupStorage(ctx context.Context, storage *everestv1alpha1.BackupStorage) (*everestv1alpha1.BackupStorage, error) {
return k.client.CreateBackupStorage(ctx, storage)
}

Expand Down
5 changes: 2 additions & 3 deletions pkg/kubernetes/client/backup_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ import (
)

// CreateBackupStorage creates an backupStorage.
func (c *Client) CreateBackupStorage(ctx context.Context, storage *everestv1alpha1.BackupStorage) error {
_, err := c.customClientSet.BackupStorage(storage.Namespace).Create(ctx, storage, metav1.CreateOptions{})
return err
func (c *Client) CreateBackupStorage(ctx context.Context, storage *everestv1alpha1.BackupStorage) (*everestv1alpha1.BackupStorage, error) {
return c.customClientSet.BackupStorage(storage.Namespace).Create(ctx, storage, metav1.CreateOptions{})
}

// UpdateBackupStorage updates an backupStorage.
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubernetes/client/kubeclient_interface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading