Skip to content

Commit

Permalink
feat(cluster-backup): add apis for bsl
Browse files Browse the repository at this point in the history
Signed-off-by: rajaSahil <[email protected]>

feat(cluster-backup): add apis for bsl

Signed-off-by: rajaSahil <[email protected]>
  • Loading branch information
rajaSahil committed Jan 29, 2025
1 parent adcc929 commit 88e4677
Show file tree
Hide file tree
Showing 8 changed files with 780 additions and 1 deletion.
26 changes: 25 additions & 1 deletion modules/api/cmd/kubermatic-api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -7492,12 +7492,19 @@
"in": "path",
"required": true
},
{
"type": "string",
"x-go-name": "ClusterID",
"name": "cluster_id",
"in": "path",
"required": true
},
{
"name": "Body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/CbslBody"
"$ref": "#/definitions/BslBody"
}
}
],
Expand Down Expand Up @@ -28884,6 +28891,23 @@
"type": "object",
"x-go-package": "k8c.io/dashboard/v2/pkg/api/v2"
},
"BslBody": {
"type": "object",
"properties": {
"cbslSpec": {
"$ref": "#/definitions/BackupStorageLocationSpec"
},
"credentials": {
"$ref": "#/definitions/BackupCredentials"
},
"name": {
"description": "Name of the cluster backup",
"type": "string",
"x-go-name": "Name"
}
},
"x-go-package": "k8c.io/dashboard/v2/pkg/ee/clusterbackup/backupstoragelocation"
},
"ByPodStatus": {
"description": "ByPodStatus defines the observed state of ConstraintTemplate as seen by\nan individual controller\n+kubebuilder:pruning:PreserveUnknownFields",
"type": "object",
Expand Down
296 changes: 296 additions & 0 deletions modules/api/pkg/ee/clusterbackup/backupstoragelocation/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
/*
Copyright 2025 The Kubermatic Kubernetes Platform contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package backupstoragelocation

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

"github.com/gorilla/mux"
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"

apiv2 "k8c.io/dashboard/v2/pkg/api/v2"
clusterbackup "k8c.io/dashboard/v2/pkg/ee/clusterbackup/backup"
handlercommon "k8c.io/dashboard/v2/pkg/handler/common"
"k8c.io/dashboard/v2/pkg/handler/v1/common"
"k8c.io/dashboard/v2/pkg/handler/v2/cluster"
"k8c.io/dashboard/v2/pkg/provider"
kubermaticv1 "k8c.io/kubermatic/v2/pkg/apis/kubermatic/v1"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/storage/names"
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
)

const (
displayNameLabelKey = "bsl-display-name"
credentialsSecretKeyName = "cloud-credentials"
clusterIdLabelKey = "cluster-id"
)

// createBSLReq defines HTTP request for createBSL
// swagger:parameters createClusterBackupStorageLocation
type createBslReq struct {
cluster.GetClusterReq

// in: body
// required: true
Body BslBody
}

type BslBody struct {
// Name of the cluster backup
Name string `json:"name,omitempty"`
// Spec of a Velero cluster backup
Credentials apiv2.BackupCredentials `json:"credentials,omitempty"`
BSLSpec velerov1.BackupStorageLocationSpec `json:"cbslSpec,omitempty"`
}

func DecodeCreateBSLReq(c context.Context, r *http.Request) (interface{}, error) {
var req createBslReq
cr, err := cluster.DecodeGetClusterReq(c, r)

if err != nil {
return nil, err
}

req.GetClusterReq = cr.(cluster.GetClusterReq)

if err = json.NewDecoder(r.Body).Decode(&req.Body); err != nil {
return nil, err
}
return req, nil
}

func CreateBSLEndpoint(ctx context.Context, request interface{}, userInfoGetter provider.UserInfoGetter, projectProvider provider.ProjectProvider, privilegedProjectProvider provider.PrivilegedProjectProvider, settingsProvider provider.SettingsProvider) (interface{}, error) {
if err := clusterbackup.IsClusterBackupEnabled(ctx, settingsProvider); err != nil {
return nil, err
}

req := request.(createBslReq)
client, err := handlercommon.GetClusterClientWithClusterID(ctx, userInfoGetter, projectProvider, privilegedProjectProvider, req.ProjectID, req.ClusterID)
if err != nil {
return nil, err
}

bslName := req.Body.Name
bslSpec := req.Body.BSLSpec.DeepCopy()
creds := req.Body.Credentials
bsl := &velerov1.BackupStorageLocation{
Spec: *bslSpec,
}

bsl, err = createBSL(ctx, client, bslName, req.ProjectID, req.ClusterID, bsl, &creds)
if err != nil {
return nil, err
}

return bsl, nil
}

type GetBslReq struct {
cluster.GetClusterReq
//in: path
// required: true
BslName string `json:"bsl_name"`
}

func DecodeGetBSLReq(c context.Context, r *http.Request) (interface{}, error) {
var req GetBslReq

cr, err := cluster.DecodeGetClusterReq(c, r)
if err != nil {
return nil, err
}

req.GetClusterReq = cr.(cluster.GetClusterReq)

req.BslName = mux.Vars(r)["bsl_name"]
if req.BslName == "" {
return nil, fmt.Errorf("'bsl_name' parameter is required but was not provided")
}
return req, nil
}

func GetBSLEndpoint(ctx context.Context, request interface{}, userInfoGetter provider.UserInfoGetter, projectProvider provider.ProjectProvider, privilegedProjectProvider provider.PrivilegedProjectProvider, settingsProvider provider.SettingsProvider) (interface{}, error) {
if err := clusterbackup.IsClusterBackupEnabled(ctx, settingsProvider); err != nil {
return nil, err
}

req := request.(GetBslReq)

client, err := handlercommon.GetClusterClientWithClusterID(ctx, userInfoGetter, projectProvider, privilegedProjectProvider, req.ProjectID, req.ClusterID)
if err != nil {
return nil, err
}

bsl := &velerov1.BackupStorageLocation{}
if err := client.Get(ctx, types.NamespacedName{Name: req.BslName, Namespace: clusterbackup.UserClusterBackupNamespace}, bsl); err != nil {
return nil, common.KubernetesErrorToHTTPError(err)
}
return bsl, nil
}

type listBslReq struct {
cluster.GetClusterReq
}

func DecodeListBSLReq(c context.Context, r *http.Request) (interface{}, error) {
var req listBslReq

cr, err := cluster.DecodeGetClusterReq(c, r)
if err != nil {
return nil, err
}

req.GetClusterReq = cr.(cluster.GetClusterReq)
return req, nil
}

func ListBSLEndpoint(ctx context.Context, request interface{}, userInfoGetter provider.UserInfoGetter, projectProvider provider.ProjectProvider, privilegedProjectProvider provider.PrivilegedProjectProvider, settingsProvider provider.SettingsProvider) (interface{}, error) {
if err := clusterbackup.IsClusterBackupEnabled(ctx, settingsProvider); err != nil {
return nil, err
}

req := request.(listBslReq)
client, err := handlercommon.GetClusterClientWithClusterID(ctx, userInfoGetter, projectProvider, privilegedProjectProvider, req.ProjectID, req.ClusterID)
if err != nil {
return nil, err
}
bslList := &velerov1.BackupStorageLocationList{}

if err := client.List(ctx, bslList, ctrlruntimeclient.InNamespace(clusterbackup.UserClusterBackupNamespace)); err != nil {
return nil, common.KubernetesErrorToHTTPError(err)
}

return bslList, nil
}

type deleteBSLReq struct {
cluster.GetClusterReq
// in: body
BslName string `json:"bsl_name"`
}

func DecodeDeleteBSLReq(c context.Context, r *http.Request) (interface{}, error) {
var req deleteBSLReq
cr, err := cluster.DecodeGetClusterReq(c, r)
if err != nil {
return nil, err
}
req.GetClusterReq = cr.(cluster.GetClusterReq)

req.BslName = mux.Vars(r)["bsl_name"]
if req.BslName == "" {
return nil, fmt.Errorf("'bsl_name' parameter is required but was not provided")
}
return req, nil
}

func DeleteBSLEndpoint(ctx context.Context, request interface{}, userInfoGetter provider.UserInfoGetter, projectProvider provider.ProjectProvider, privilegedProjectProvider provider.PrivilegedProjectProvider, settingsProvider provider.SettingsProvider) (interface{}, error) {
if err := clusterbackup.IsClusterBackupEnabled(ctx, settingsProvider); err != nil {
return nil, err
}

req := request.(deleteBSLReq)

client, err := handlercommon.GetClusterClientWithClusterID(ctx, userInfoGetter, projectProvider, privilegedProjectProvider, req.ProjectID, req.ClusterID)
if err != nil {
return nil, err
}
bsl := &velerov1.BackupStorageLocation{}
if err := client.Get(ctx, types.NamespacedName{Name: req.BslName, Namespace: clusterbackup.UserClusterBackupNamespace}, bsl); err != nil {
if apierrors.IsNotFound(err) {
return nil, nil
}
return nil, err
}
if err := client.Delete(ctx, bsl); err != nil {
return nil, common.KubernetesErrorToHTTPError(err)
}
return nil, nil
}

func createBSL(ctx context.Context, client ctrlruntimeclient.Client, bslName, projectID, clusterID string, bsl *velerov1.BackupStorageLocation, credentials *apiv2.BackupCredentials) (*velerov1.BackupStorageLocation, error) {
bslFullName := fmt.Sprintf("%s-%s-%s", bslName, projectID, clusterID)
secretName := fmt.Sprintf("credential-%s-", bslFullName)

secret := &corev1.Secret{}
if credentials != nil {
secret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: names.SimpleNameGenerator.GenerateName(secretName),
Namespace: clusterbackup.UserClusterBackupNamespace,
Labels: getBSLLabels(bslFullName, projectID, clusterID),
},
Data: map[string][]byte{
"accessKeyId": []byte(credentials.S3BackupCredentials.AccessKeyID),
"secretAccessKey": []byte(credentials.S3BackupCredentials.SecretAccessKey),
},
}
}

bsl.ObjectMeta = metav1.ObjectMeta{
Name: bslFullName,
Namespace: clusterbackup.UserClusterBackupNamespace,
Labels: getBSLLabels(bslName, projectID, clusterID),
}

if credentials != nil {
bsl.Spec.Credential = &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secret.Name,
},
Key: credentialsSecretKeyName,
}
}

if err := client.Create(ctx, bsl); err != nil {
return nil, common.KubernetesErrorToHTTPError(err)
}

if credentials != nil {
ownerReferences := []metav1.OwnerReference{
{
APIVersion: bsl.APIVersion,
Kind: bsl.Kind,
Name: bslFullName,
UID: bsl.UID,
},
}

secret.ObjectMeta.OwnerReferences = ownerReferences

if err := client.Create(ctx, secret); err != nil {
return nil, err
}
}
return bsl, nil
}

func getBSLLabels(displayName, projectID, clusterID string) map[string]string {
return map[string]string{
kubermaticv1.ProjectIDLabelKey: projectID,
clusterIdLabelKey: clusterID,
displayNameLabelKey: displayName,
}
}
Loading

0 comments on commit 88e4677

Please sign in to comment.