app/registry/edit.go (434 lines of code) (raw):

package registry import ( "context" "crypto/sha1" "ddm-admin-console/router" "ddm-admin-console/service/codebase" "ddm-admin-console/service/gerrit" "ddm-admin-console/service/k8s" "encoding/base64" "encoding/json" "errors" "fmt" "net/http" "strings" "time" "github.com/hashicorp/go-version" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "gopkg.in/yaml.v3" ) const ( MasterBranch = "master" VersionQueryParam = "version" ) func (a *App) editRegistryGet(ctx *gin.Context) (response router.Response, retErr error) { registryName := ctx.Param("name") mrExists, err := ProjectHasOpenMR(ctx, registryName, a.Gerrit) if err != nil { return nil, fmt.Errorf("unable to check MRs exists, %w", err) } if mrExists { return router.MakeHTMLResponse(200, "registry/edit-mr-exists.html", gin.H{ "registryName": registryName, "page": "registry", }), nil } userCtx := router.ContextWithUserAccessToken(ctx) cbService, err := a.Services.Codebase.ServiceForContext(userCtx) if err != nil { return nil, fmt.Errorf("unable to init service for user context, %w", err) } k8sService, err := a.Services.K8S.ServiceForContext(userCtx) if err != nil { return nil, fmt.Errorf("unable to init service for user context, %w", err) } if err := a.checkUpdateAccess(registryName, k8sService); err != nil { return nil, errors.New("access denied") } reg, err := cbService.Get(registryName) if err != nil { return nil, fmt.Errorf("unable to get registry, %w", err) } model := registry{KeyDeviceType: KeyDeviceTypeFile, Name: reg.Name} if reg.Spec.Description != nil { model.Description = *reg.Spec.Description } hwINITemplateContent, err := GetINITemplateContent(a.Config.HardwareINITemplatePath) if err != nil { return nil, fmt.Errorf("unable to get ini template data, %w", err) } hasUpdate, branches, registryVersion, err := HasUpdate(userCtx, a.Services.Gerrit, reg, MRTargetRegistryVersionUpdate) if err != nil { return nil, fmt.Errorf("unable to check for updates, %w", err) } if is, rsp := isVersionRedirect(ctx, "/admin/registry/edit", registryName, registryVersion); is { return rsp, nil } dnsManual, err := a.getDNSManualURL(ctx) if err != nil { return nil, fmt.Errorf("unable to get dns manual, %w", err) } valuesFromDefaultBranch, err := a.GetValuesFromBranch(a.Config.RegistryTemplateName, registryVersion.Original()) if err != nil { return nil, fmt.Errorf("unable to get template content, %w", err) } responseParams := gin.H{ "dnsManual": dnsManual, "registry": reg, "page": "registry", "updateBranches": branches, "hasUpdate": hasUpdate, "action": "edit", "registryVersion": MajorVersion(registryVersion.Core().Original()), "platformStatusType": a.Config.CloudProvider, "isPlatformAdmin": ctx.GetBool(router.CanViewClusterManagementSessionKey), "defaultRegistryValues": valuesFromDefaultBranch, } values, err := GetValuesFromGit(registryName, MasterBranch, a.Gerrit) if err != nil { return nil, fmt.Errorf("unable to get values from git, %w", err) } if err := a.loadValuesEditConfig(ctx, values, responseParams, &model); err != nil { return nil, fmt.Errorf("unable to load edit values from config, %w", err) } if err := a.viewDNSConfig(ctx, reg, values, responseParams); err != nil { return nil, fmt.Errorf("unable to load dns config, %w", err) } templateArgs, templateErr := json.Marshal(responseParams) if templateErr != nil { return nil, fmt.Errorf("unable to encode template arguments, %w", templateErr) } responseParams["templateArgs"] = string(templateArgs) responseParams["hwINITemplateContent"] = hwINITemplateContent return router.MakeHTMLResponse(200, "registry/edit.html", responseParams), nil } func MajorVersion(fullVersion string) string { if fullVersion == "" { return "" } parts := strings.Split(fullVersion, ".") if len(parts) > 3 { parts = parts[:len(parts)-1] } return strings.Join(parts, ".") } func isVersionRedirect(ctx *gin.Context, basePath, registryName string, registryVersion *version.Version) (bool, router.Response) { qVersion := ctx.Query(VersionQueryParam) if qVersion == "" { return true, router.MakeRedirectResponse(http.StatusTemporaryRedirect, fmt.Sprintf("%s/%s?%s=%s", basePath, registryName, VersionQueryParam, MajorVersion(registryVersion.Core().Original()))) } if qVersion != MajorVersion(registryVersion.Core().Original()) { return true, router.MakeRedirectResponse(http.StatusTemporaryRedirect, fmt.Sprintf("%s/%s?%s=%s", basePath, registryName, VersionQueryParam, MajorVersion(registryVersion.Core().Original()))) } return false, nil } func (a *App) loadValuesEditConfig(ctx context.Context, values *Values, rspParams gin.H, r *registry) error { //TODO: refactor to values struct if err := a.loadSMTPConfig(values.OriginalYaml, rspParams); err != nil { return fmt.Errorf("unable to load smtp config, %w", err) } //TODO: refactor to values struct if err := a.loadAdminsConfig(values.OriginalYaml, r); err != nil { return fmt.Errorf("unable to load admins config, %w", err) } if err := a.loadOBCConfig(values, r); err != nil { return fmt.Errorf("unable to load obc config, %w", err) } if err := a.loadIDGovUAClientID(values); err != nil { return fmt.Errorf("unable to load secret: %w", err) } keycloakHostname, err := LoadKeycloakDefaultHostname(ctx, a.KeycloakDefaultHostname, a.EDPComponent) if err != nil { return fmt.Errorf("unable to load keycloak default hostname, %w", err) } rspParams["keycloakHostname"] = keycloakHostname keycloakHostnames, err := a.loadKeycloakHostnames() if err != nil { return fmt.Errorf("unable to load keycloak hostnames, %w", err) } rspParams["keycloakHostnames"] = keycloakHostnames rspParams["keycloakCustomHost"] = values.Keycloak.CustomHost rspParams["model"] = r registryData, err := json.Marshal(r) if err != nil { return fmt.Errorf("unable to encode registry data, %w", err) } rspParams["registryData"] = string(registryData) rspParams["registryValues"] = values return nil } func (a *App) loadIDGovUAClientID(values *Values) error { if values.Keycloak.IdentityProviders.IDGovUA.SecretKey == "" { return nil } dataDict, err := a.Vault.Read(values.Keycloak.IdentityProviders.IDGovUA.SecretKey) if err != nil { return fmt.Errorf("unable to load id-gov-ua secret, err: %w", err) } d, ok := dataDict[idGovUASecretClientID] if !ok { return nil } str, ok := d.(string) if !ok { return nil } values.Keycloak.IdentityProviders.IDGovUA.ClientID = str return nil } func (a *App) loadAdminsConfig(values map[string]interface{}, r *registry) error { adminsInterface, ok := values[AdministratorsValuesKey] if !ok { r.Admins = "[]" return nil } adminsJs, err := json.Marshal(adminsInterface) if err != nil { return fmt.Errorf("unable to marshal admins, %w", err) } var admins []Admin if err := json.Unmarshal(adminsJs, &admins); err != nil { return fmt.Errorf("unable tro unmarshal admins, %w", err) } //TODO: maybe load admin password for i := range admins { admins[i].TmpPassword = "" admins[i].PasswordVaultSecret = "" admins[i].PasswordVaultSecretKey = "" } adminsJs, err = json.Marshal(admins) if err != nil { return fmt.Errorf("unable to marshal admins, %w", err) } r.Admins = string(adminsJs) return nil } func (a *App) loadOBCConfig(values *Values, r *registry) error { if values.Global.RegistryBackup.OBC.Credentials == "" { return nil } dataDict, err := a.Vault.Read(values.Global.RegistryBackup.OBC.Credentials) if err != nil { return fmt.Errorf("unable to load obc credential, err: %w", err) } login, ok := dataDict[a.Config.BackupBucketAccessKeyID] if !ok { return nil } password, ok := dataDict[a.Config.BackupBucketSecretAccessKey] if !ok { return nil } loginStr, ok := login.(string) if !ok { return nil } passwordStr, ok := password.(string) if !ok { return nil } r.OBCLogin = loginStr r.OBCPassword = passwordStr return nil } func (a *App) loadSMTPConfig(values map[string]interface{}, rspParams gin.H) error { global, ok := values[GlobalValuesIndex] if !ok { rspParams["smtpConfig"] = "{}" return nil } globalDict := global.(map[string]interface{}) if _, ok := globalDict["notifications"]; !ok { return nil } emailDict := globalDict["notifications"].(map[string]interface{})["email"].(map[string]interface{}) mailConfig, err := json.Marshal(emailDict) if err != nil { return fmt.Errorf("unable to encode ot JSON smtp config, %w", err) } rspParams["smtpConfig"] = string(mailConfig) return nil } func (a *App) checkUpdateAccess(codebaseName string, userK8sService k8s.ServiceInterface) error { allowedToUpdate, err := a.Services.Codebase.CheckIsAllowedToUpdate(codebaseName, userK8sService) if err != nil { return fmt.Errorf("unable to check create access, %w", err) } if !allowedToUpdate { return errors.New("access denied") } return nil } func (a *App) editRegistryPost(ctx *gin.Context) (response router.Response, retErr error) { userCtx := router.ContextWithUserAccessToken(ctx) cbService, err := a.Services.Codebase.ServiceForContext(userCtx) if err != nil { return nil, fmt.Errorf("unable to init service for user context, %w", err) } k8sService, err := a.Services.K8S.ServiceForContext(userCtx) if err != nil { return nil, fmt.Errorf("unable to init service for user context, %w", err) } registryName := ctx.Param("name") //TODO: move this to middleware allowed, err := cbService.CheckIsAllowedToUpdate(registryName, k8sService) if err != nil { return nil, fmt.Errorf("unable to check access, %w", err) } if !allowed { return nil, errors.New("access denied") } cb, err := cbService.Get(registryName) if err != nil { return nil, fmt.Errorf("unable to get registry, %w", err) } r := registry{ Name: registryName, RegistryGitBranch: cb.Spec.DefaultBranch, Scenario: ScenarioKeyNotRequired, } if err := ctx.ShouldBind(&r); err != nil { return nil, fmt.Errorf("unable to parse registry form, %w", err) } if err := a.editRegistry(userCtx, ctx, &r, cb, cbService); err != nil { return nil, fmt.Errorf("unable to edit registry, %w", err) } return router.MakeRedirectResponse(http.StatusFound, fmt.Sprintf("/admin/registry/view/%s", r.Name)), nil } func (a *App) editRegistry(ctx context.Context, ginContext *gin.Context, r *registry, cb *codebase.Codebase, cbService codebase.ServiceInterface) error { cb.Spec.Description = &r.Description if cb.Annotations == nil { cb.Annotations = make(map[string]string) } values, err := GetValuesFromGit(r.Name, MasterBranch, a.Gerrit) if err != nil { return fmt.Errorf("unable to get values from git, %w", err) } var ( vaultSecretData = make(map[string]map[string]interface{}) mrActions = make([]string, 0) valuesChanged = false repoFiles = make(map[string]string) ) for _, proc := range a.createUpdateRegistryProcessors() { procValuesChanged, err := proc(ginContext, r, values, vaultSecretData, &mrActions) if err != nil { return fmt.Errorf("error during registry create, %w", err) } if procValuesChanged { valuesChanged = true } } keysModified, err := PrepareRegistryKeys(keyManagement{ r: r, vaultSecretPath: a.vaultRegistryPathKey(r.Name, fmt.Sprintf("%s-%s", KeyManagementVaultPath, time.Now().Format("20060201T150405Z"))), }, ginContext.Request, vaultSecretData, values.OriginalYaml, repoFiles) if err != nil { return fmt.Errorf("unable to create registry keys, %w", err) } if keysModified { if err := CacheRepoFiles(a.TempFolder, r.Name, repoFiles, a.Cache); err != nil { return fmt.Errorf("unable to cache repo file, %w", err) } } if valuesChanged || len(repoFiles) > 0 || keysModified { if err := CreateEditMergeRequest(ginContext, r.Name, values.OriginalYaml, a.Gerrit, mrActions); err != nil { return fmt.Errorf("unable to create edit merge request, %w", err) } } if len(vaultSecretData) > 0 { if err := CreateVaultSecrets(a.Vault, vaultSecretData, false); err != nil { return fmt.Errorf("unable to create vault secrets, %w", err) } } if err := cbService.Update(ctx, cb); err != nil { return fmt.Errorf("unable to update codebase, %w", err) } return nil } func MapHash(v map[string]interface{}) (string, error) { bts, err := json.Marshal(v) if err != nil { return "", fmt.Errorf("unable to encode map, %w", err) } hasher := sha1.New() hasher.Write(bts) return base64.URLEncoding.EncodeToString(hasher.Sum(nil)), nil } type MRLabel struct { Key string Value string } func CreateEditMergeRequest(ctx *gin.Context, projectName string, values map[string]interface{}, gerritService gerrit.ServiceInterface, mrActions []string, labels ...MRLabel) error { valuesYaml, err := yaml.Marshal(values) if err != nil { return fmt.Errorf("unable to encode values yaml, %w", err) } mrExists, err := ProjectHasOpenMR(ctx, projectName, gerritService) if err != nil { return fmt.Errorf("unable to check project MR exists, %w", err) } if mrExists { return MRExists("there is already open merge request(s) for this registry") } _labels := map[string]string{ MRLabelTarget: mrTargetEditRegistry, } for _, l := range labels { _labels[l.Key] = l.Value } mrActionsJsonBts, err := json.Marshal(mrActions) if err != nil { return fmt.Errorf("unable to encode mr actions, %w", err) } if err := gerritService.CreateMergeRequestWithContents(ctx, &gerrit.MergeRequest{ ProjectName: projectName, Name: fmt.Sprintf("reg-edit-mr-%s-%d", projectName, time.Now().Unix()), AuthorEmail: ctx.GetString(router.UserEmailSessionKey), AuthorName: ctx.GetString(router.UserNameSessionKey), CommitMessage: fmt.Sprintf("edit registry"), TargetBranch: "master", Labels: _labels, Annotations: map[string]string{ MRAnnotationActions: string(mrActionsJsonBts), }, }, map[string]string{ ValuesLocation: string(valuesYaml), }); err != nil { return fmt.Errorf("unable to create MR with new values, %w", err) } return nil } func ProjectHasOpenMR(ctx *gin.Context, projectName string, gerritService gerrit.ServiceInterface) (bool, error) { mrs, err := gerritService.GetMergeRequestByProject(ctx, projectName) if err != nil { return false, fmt.Errorf("unable to get MRs, %w", err) } for _, mr := range mrs { if mr.Status.Value == gerrit.StatusNew { return true, nil } } return false, nil } func validateAdmins(adminsLine string) ([]Admin, error) { var admins []Admin if err := json.Unmarshal([]byte(adminsLine), &admins); err != nil { return nil, fmt.Errorf("unable to unmarshal admins, %w", err) } validate := validator.New() for i, admin := range admins { errs := validate.Var(admin.Email, "required,email") if errs != nil { return nil, validator.ValidationErrors([]validator.FieldError{router.MakeFieldError("Admins", "required")}) } admins[i].Username = admin.Email } return admins, nil }