343 lines
11 KiB
Go
343 lines
11 KiB
Go
//
|
|
// Copyright (c) 2021 Red Hat, Inc.
|
|
// This program and the accompanying materials are made
|
|
// available under the terms of the Eclipse Public License 2.0
|
|
// which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
//
|
|
// SPDX-License-Identifier: EPL-2.0
|
|
//
|
|
// Contributors:
|
|
// Red Hat, Inc. - initial API and implementation
|
|
//
|
|
package checlusterbackup
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
chev1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1"
|
|
"github.com/eclipse-che/che-operator/pkg/deploy"
|
|
"github.com/eclipse-che/che-operator/pkg/util"
|
|
"github.com/sirupsen/logrus"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
)
|
|
|
|
const (
|
|
InternalBackupServerType = "rest"
|
|
InternalBackupServerComponent = "che-backup-internal-rest-server"
|
|
|
|
BackupServerConfigurationName = "backup-rest-server-configuration"
|
|
BackupServerRepoPasswordSecretName = "backup-rest-server-repo-password"
|
|
BackupServerDeploymentName = "backup-rest-server-deployment"
|
|
backupServerPodName = "backup-rest-server-pod"
|
|
backupServerServiceName = "backup-rest-server-service"
|
|
backupServerPort = 8000
|
|
)
|
|
|
|
// ConfigureInternalBackupServer checks for existance of internal REST backup server and deploys it if it doesn't.
|
|
func ConfigureInternalBackupServer(bctx *BackupContext) (bool, error) {
|
|
taskList := []func(*BackupContext) (bool, error){
|
|
ensureInternalBackupServerDeploymentExist,
|
|
ensureInternalBackupServerServiceExists,
|
|
ensureInternalBackupServerPodReady,
|
|
ensureInternalBackupServerConfigurationExistAndCorrect,
|
|
ensureInternalBackupServerConfigurationCurrent,
|
|
ensureInternalBackupServerSecretExists,
|
|
}
|
|
|
|
for _, task := range taskList {
|
|
done, err := task(bctx)
|
|
if !(done && err == nil) {
|
|
return done, err
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func ensureInternalBackupServerDeploymentExist(bctx *BackupContext) (bool, error) {
|
|
backupServerDeployment := &appsv1.Deployment{}
|
|
namespacedName := types.NamespacedName{
|
|
Namespace: bctx.namespace,
|
|
Name: BackupServerDeploymentName,
|
|
}
|
|
err := bctx.r.client.Get(context.TODO(), namespacedName, backupServerDeployment)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if !errors.IsNotFound(err) {
|
|
return false, err
|
|
}
|
|
|
|
// Get default configuration of the backup server deployment
|
|
backupServerDeployment = getBackupServerDeploymentSpec(bctx)
|
|
// Create backup server deployment
|
|
err = bctx.r.client.Create(context.TODO(), backupServerDeployment)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// Backup server created successfully, reconcile
|
|
return false, nil
|
|
}
|
|
|
|
func getBackupServerDeploymentSpec(bctx *BackupContext) *appsv1.Deployment {
|
|
labels, labelSelector := deploy.GetLabelsAndSelector(bctx.cheCR, InternalBackupServerComponent)
|
|
// TODO should we use component label to select backup related resources instead of part-of label ?
|
|
labels[deploy.KubernetesPartOfLabelKey] = BackupCheEclipseOrg
|
|
|
|
restBackupServerImage := deploy.DefaultInternalBackupServerImage(bctx.cheCR)
|
|
replicas := int32(1)
|
|
|
|
deployment := &appsv1.Deployment{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Deployment",
|
|
APIVersion: "apps/v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: BackupServerDeploymentName,
|
|
Namespace: bctx.namespace,
|
|
Labels: labels,
|
|
},
|
|
Spec: appsv1.DeploymentSpec{
|
|
Replicas: &replicas,
|
|
Selector: &metav1.LabelSelector{MatchLabels: labelSelector},
|
|
Template: corev1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: labels,
|
|
GenerateName: backupServerPodName + "-",
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: backupServerPodName,
|
|
Image: restBackupServerImage,
|
|
ImagePullPolicy: "Always",
|
|
Ports: []corev1.ContainerPort{
|
|
{
|
|
Name: "rest",
|
|
ContainerPort: backupServerPort,
|
|
Protocol: "TCP",
|
|
},
|
|
},
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Capabilities: &corev1.Capabilities{
|
|
Drop: []corev1.Capability{"ALL"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: "Always",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
return deployment
|
|
}
|
|
|
|
func ensureInternalBackupServerPodReady(bctx *BackupContext) (bool, error) {
|
|
// It is not possible to implement the check via StartupProbe of the pod,
|
|
// because the probe requires 2xx status, but a fresh REST server responds with 404 only.
|
|
|
|
restServerBaseUrl := fmt.Sprintf("http://%s:%d", backupServerServiceName, backupServerPort)
|
|
_, err := http.Head(restServerBaseUrl)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "connection refused") {
|
|
// Internal REST server is not ready yet. Reconcile.
|
|
logrus.Info("Waiting for internal REST server to be ready")
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func ensureInternalBackupServerServiceExists(bctx *BackupContext) (bool, error) {
|
|
backupServerService := &corev1.Service{}
|
|
namespacedName := types.NamespacedName{
|
|
Namespace: bctx.namespace,
|
|
Name: backupServerServiceName,
|
|
}
|
|
err := bctx.r.client.Get(context.TODO(), namespacedName, backupServerService)
|
|
if err == nil {
|
|
// Backup server service already exists, do nothing
|
|
return true, nil
|
|
}
|
|
if !errors.IsNotFound(err) {
|
|
return false, err
|
|
}
|
|
|
|
// Backup server service doesn't exists, create it
|
|
backupServerService = getBackupServerServiceSpec(bctx)
|
|
// Create backup server service
|
|
err = bctx.r.client.Create(context.TODO(), backupServerService)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// Backup server service created successfully, reconcile
|
|
return false, nil
|
|
}
|
|
|
|
func getBackupServerServiceSpec(bctx *BackupContext) *corev1.Service {
|
|
labels := deploy.GetLabels(bctx.cheCR, InternalBackupServerComponent)
|
|
labels[deploy.KubernetesPartOfLabelKey] = BackupCheEclipseOrg
|
|
|
|
port := corev1.ServicePort{
|
|
Name: backupServerServiceName + "-port",
|
|
Port: backupServerPort,
|
|
Protocol: "TCP",
|
|
}
|
|
ports := []corev1.ServicePort{}
|
|
ports = append(ports, port)
|
|
|
|
service := &corev1.Service{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Service",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: backupServerServiceName,
|
|
Namespace: bctx.namespace,
|
|
Labels: labels,
|
|
},
|
|
Spec: corev1.ServiceSpec{
|
|
Ports: ports,
|
|
Selector: labels,
|
|
},
|
|
}
|
|
|
|
return service
|
|
}
|
|
|
|
// ensureInternalBackupServerConfigurationExistAndCorrect makes sure that there is CR with correct internal backup server configuration
|
|
func ensureInternalBackupServerConfigurationExistAndCorrect(bctx *BackupContext) (bool, error) {
|
|
// Check if the internal backup server configuration exists
|
|
internalBackupServerConfiguration := &chev1.CheBackupServerConfiguration{}
|
|
namespacedName := types.NamespacedName{Namespace: bctx.namespace, Name: BackupServerConfigurationName}
|
|
err := bctx.r.client.Get(context.TODO(), namespacedName, internalBackupServerConfiguration)
|
|
if err == nil {
|
|
// Configuration exist, check if it is correct
|
|
expectedInternalRestServerConfig := getExpectedInternalRestServerConfiguration()
|
|
if *internalBackupServerConfiguration.Spec.Rest != *expectedInternalRestServerConfig {
|
|
// Something is wrong in the configuration
|
|
if err := bctx.r.client.Delete(context.TODO(), internalBackupServerConfiguration); err != nil {
|
|
return false, err
|
|
}
|
|
// Reconcile to recreate the configuration
|
|
return false, nil
|
|
}
|
|
// Everything is correct
|
|
return true, nil
|
|
}
|
|
if !errors.IsNotFound(err) {
|
|
return false, err
|
|
}
|
|
|
|
// Create internal backup server configuration
|
|
internalBackupServerConfiguration = getInternalBackupServerConfigurationSpec(bctx)
|
|
err = bctx.r.client.Create(context.TODO(), internalBackupServerConfiguration)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// Reconcile after internal backup server configuration creation
|
|
return false, nil
|
|
}
|
|
|
|
func getInternalBackupServerConfigurationSpec(bctx *BackupContext) *chev1.CheBackupServerConfiguration {
|
|
labels := deploy.GetLabels(bctx.cheCR, InternalBackupServerComponent)
|
|
labels[deploy.KubernetesPartOfLabelKey] = BackupCheEclipseOrg
|
|
|
|
return &chev1.CheBackupServerConfiguration{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "CheBackupServerConfiguration",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: BackupServerConfigurationName,
|
|
Namespace: bctx.namespace,
|
|
Labels: labels,
|
|
},
|
|
Spec: chev1.CheBackupServerConfigurationSpec{
|
|
Rest: getExpectedInternalRestServerConfiguration(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func getExpectedInternalRestServerConfiguration() *chev1.RestServerConfig {
|
|
return &chev1.RestServerConfig{
|
|
Protocol: "http",
|
|
Hostname: backupServerServiceName,
|
|
Port: backupServerPort,
|
|
RepositoryPath: "che",
|
|
RepositoryPasswordSecretRef: BackupServerRepoPasswordSecretName,
|
|
}
|
|
}
|
|
|
|
// ensureInternalBackupServerConfigurationCurrent makes sure that current backup configuration is internal backup server
|
|
func ensureInternalBackupServerConfigurationCurrent(bctx *BackupContext) (bool, error) {
|
|
if bctx.backupCR.Spec.BackupServerConfigRef != BackupServerConfigurationName {
|
|
// Update backup CR configuration
|
|
bctx.backupCR.Spec.BackupServerConfigRef = BackupServerConfigurationName
|
|
if err := bctx.r.UpdateCR(bctx.backupCR); err != nil {
|
|
return false, err
|
|
}
|
|
// Reconcile after backup server config reference update
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func ensureInternalBackupServerSecretExists(bctx *BackupContext) (bool, error) {
|
|
// Check if the secret with restic repository password exists
|
|
repoPasswordSecret := &corev1.Secret{}
|
|
namespacedName := types.NamespacedName{
|
|
Namespace: bctx.namespace,
|
|
Name: bctx.backupServerConfigCR.Spec.Rest.RepositoryPasswordSecretRef,
|
|
}
|
|
err := bctx.r.client.Get(context.TODO(), namespacedName, repoPasswordSecret)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if !errors.IsNotFound(err) {
|
|
return false, err
|
|
}
|
|
|
|
repoPassword := util.GeneratePasswd(12)
|
|
repoPasswordSecret = getRepoPasswordSecretSpec(bctx, repoPassword)
|
|
err = bctx.r.client.Create(context.TODO(), repoPasswordSecret)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// Reconcile after secret creation
|
|
return false, nil
|
|
}
|
|
|
|
func getRepoPasswordSecretSpec(bctx *BackupContext, password string) *corev1.Secret {
|
|
labels := deploy.GetLabels(bctx.cheCR, InternalBackupServerComponent)
|
|
labels[deploy.KubernetesPartOfLabelKey] = BackupCheEclipseOrg
|
|
|
|
data := map[string][]byte{chev1.RESTIC_REPO_PASSWORD_SECRET_KEY: []byte(password)}
|
|
|
|
secret := &corev1.Secret{
|
|
TypeMeta: metav1.TypeMeta{
|
|
Kind: "Secret",
|
|
APIVersion: "v1",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: BackupServerRepoPasswordSecretName,
|
|
Namespace: bctx.namespace,
|
|
Labels: labels,
|
|
},
|
|
Data: data,
|
|
}
|
|
|
|
return secret
|
|
}
|