app/cluster/backup_schedule.go (147 lines of code) (raw):
package cluster
import (
"context"
"ddm-admin-console/app/registry"
"ddm-admin-console/router"
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
"gopkg.in/yaml.v3"
)
const (
veleroValuesIndex = "velero"
)
type ScheduleItem struct {
Schedule string `yaml:"schedule" json:"schedule"`
ExpiresInDays int `yaml:"expires_in_days" json:"expiresInDays"`
}
type BackupScheduleForm struct {
NexusSchedule string `form:"nexus-schedule" binding:"required,cron-expression"`
NexusExpiresInDays string `form:"nexus-expires-in-days" binding:"required,cron-expires"`
ControlPlaneSchedule string `form:"control-plane-schedule" binding:"required,cron-expression"`
ControlPlaneExpiresInDays string `form:"control-plane-expires-in-days" binding:"required,cron-expires"`
UserManagementSchedule string `form:"user-management-schedule" binding:"required,cron-expression"`
UserManagementExpiresInDays string `form:"user-management-expires-in-days" binding:"required,cron-expires"`
MonitoringSchedule string `form:"monitoring-schedule" binding:"required,cron-expression"`
MonitoringExpiresInDays string `form:"monitoring-expires-in-days" binding:"required,cron-expires"`
}
func (bs BackupSchedule) ToForm() BackupScheduleForm {
return BackupScheduleForm{
UserManagementExpiresInDays: strconv.Itoa(bs.UserManagement.ExpiresInDays),
UserManagementSchedule: bs.UserManagement.Schedule,
MonitoringExpiresInDays: strconv.Itoa(bs.Monitoring.ExpiresInDays),
MonitoringSchedule: bs.Monitoring.Schedule,
ControlPlaneExpiresInDays: strconv.Itoa(bs.ControlPlane.ExpiresInDays),
ControlPlaneSchedule: bs.ControlPlane.Schedule,
NexusExpiresInDays: strconv.Itoa(bs.Nexus.ExpiresInDays),
NexusSchedule: bs.Nexus.Schedule,
}
}
func mustInt(s string) int {
i, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0
}
return int(i)
}
func (bsf BackupScheduleForm) ToNestedStruct() BackupSchedule {
return BackupSchedule{
Nexus: ScheduleItem{
ExpiresInDays: mustInt(bsf.NexusExpiresInDays),
Schedule: bsf.NexusSchedule,
},
ControlPlane: ScheduleItem{
Schedule: bsf.ControlPlaneSchedule,
ExpiresInDays: mustInt(bsf.ControlPlaneExpiresInDays),
},
Monitoring: ScheduleItem{
Schedule: bsf.MonitoringSchedule,
ExpiresInDays: mustInt(bsf.MonitoringExpiresInDays),
},
UserManagement: ScheduleItem{
Schedule: bsf.UserManagementSchedule,
ExpiresInDays: mustInt(bsf.UserManagementExpiresInDays),
},
}
}
type BackupSchedule struct {
Nexus ScheduleItem `yaml:"controlPlaneNexus" json:"nexus"`
ControlPlane ScheduleItem `yaml:"controlPlane" json:"controlPlane"`
UserManagement ScheduleItem `yaml:"userManagement" json:"userManagement"`
Monitoring ScheduleItem `yaml:"monitoring" json:"monitoring"`
}
func (a *App) backupSchedule(ctx *gin.Context) (router.Response, error) {
var bs BackupScheduleForm
if err := ctx.ShouldBind(&bs); err != nil {
validationErrors, ok := err.(validator.ValidationErrors)
if !ok {
return nil, errors.Wrap(err, "unable to parse registry form")
}
errorsMap := make(map[string][]interface{})
for _, v := range validationErrors {
errorsMap[v.Field()] = append(errorsMap[v.Field()], v.Tag())
}
return router.MakeJSONResponse(http.StatusUnprocessableEntity, gin.H{"errors": errorsMap}), nil
}
values, err := a.getValuesDict(ctx)
if err != nil {
return nil, errors.Wrap(err, "unable to get values")
}
valuesDict := values.OriginalYaml
velero, ok := valuesDict[veleroValuesIndex]
if !ok {
velero = make(map[string]interface{})
}
veleroDict := velero.(map[string]interface{})
veleroDict["backup"] = bs.ToNestedStruct()
valuesDict[veleroValuesIndex] = veleroDict
bts, err := yaml.Marshal(valuesDict)
if err != nil {
return nil, errors.Wrap(err, "unable to encode yaml")
}
if err := a.createValuesMergeRequest(ctx, &valuesMrConfig{
values: string(bts),
authorEmail: ctx.GetString(router.UserEmailSessionKey),
authorName: ctx.GetString(router.UserNameSessionKey),
commitMessage: "update platform backup schedule config",
targetLabel: MRTargetClusterBackupSchedule,
name: fmt.Sprintf("backup-schedule-%s-%d", a.Config.CodebaseName, time.Now().Unix()),
}); err != nil {
return nil, errors.Wrap(err, "unable to create merge request")
}
return router.MakeJSONResponse(http.StatusOK, gin.H{"errors": nil}), nil
}
func CronExpressionValidator(fl validator.FieldLevel) bool {
expression := fl.Field().String()
if _, err := cron.ParseStandard(expression); err != nil {
return false
}
return true
}
func CronDaysValidator(fl validator.FieldLevel) bool {
expression := fl.Field().String()
if _, err := strconv.ParseInt(expression, 10, 64); err != nil {
return false
}
if expression == "0" {
return false
}
return true
}
func (a *App) loadKeycloakDefaultHostname(ctx context.Context, values *Values, rspParams gin.H) error {
hostname, err := registry.LoadKeycloakDefaultHostname(ctx, a.KeycloakDefaultHostname, a.EDPComponent)
if err != nil {
return fmt.Errorf("unable to load keycloak hostname, %w", err)
}
rspParams["keycloakHostname"] = hostname
return nil
}
func (a *App) loadBackupScheduleConfig(_ context.Context, values *Values, rspParams gin.H) error {
rspParams["backupSchedule"] = values.Velero.Backup.ToForm()
return nil
}