cmd/hub/compose/unbundle.go (163 lines of code) (raw):

// Copyright (c) 2022 EPAM Systems, Inc. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package compose import ( "fmt" "log" "os" "strings" "gopkg.in/yaml.v2" "github.com/epam/hubctl/cmd/hub/config" "github.com/epam/hubctl/cmd/hub/manifest" "github.com/epam/hubctl/cmd/hub/state" "github.com/epam/hubctl/cmd/hub/storage" "github.com/epam/hubctl/cmd/hub/util" ) func BackupUnbundle(bundlesFilenames [][]string, parametersFiles []string, rename, erase, includeOnly []string) { renames := prepareComponentRenames(rename) if len(parametersFiles) == 0 && config.Verbose && !config.Debug { config.Verbose = false config.AggWarnings = false } if config.Verbose { params := "" if len(parametersFiles) > 0 { params = fmt.Sprintf(" into %v", parametersFiles) } log.Printf("Unbundling %v%s", bundlesFilenames, params) } bundles := parseBundles(bundlesFilenames) var outputs *storage.Files if len(parametersFiles) > 0 { checked, errs := storage.Check(parametersFiles, "restore parameters") if len(errs) > 0 { if config.Force { util.Warn("Unable to check output parameters file(s): %v", util.Errors2(errs...)) } else { log.Fatalf("Unable to check output parameters file(s): %v", util.Errors2(errs...)) } } outputs = checked } allBackups := extractBackups(bundles, renames, erase, includeOnly) backups := selectBackups(allBackups) warnBackupPriority(backups, allBackups) params := transformBackupsToParameters(backups) yamlBytes, err := yaml.Marshal(params) if err != nil { log.Fatalf("Unable to marshal parameters into YAML: %v", err) } if outputs != nil { _, errs := storage.Write(yamlBytes, outputs) if len(errs) > 0 { log.Fatalf("Unable to write restore parameters: %s", util.Errors2(errs...)) } } else { os.Stdout.Write([]byte("--- yaml\n")) os.Stdout.Write(yamlBytes) } } func prepareComponentRenames(rename []string) map[string]string { renames := make(map[string]string) for _, ren := range rename { fromTo := strings.Split(ren, ":") if len(fromTo) != 2 { log.Fatalf("Bad component rename specification `%s`", ren) } from := fromTo[0] to := fromTo[1] if from == "" || to == "" { log.Fatalf("Bad component rename specification `%s`", ren) } toAlready, exist := renames[from] if exist { log.Fatalf("`%s` already renamed to `%s`, cannot rename to `%s`", from, toAlready, to) } } return renames } func parseBundles(bundlesFilenames [][]string) []*state.BackupManifest { bundles := make([]*state.BackupManifest, 0, len(bundlesFilenames)) for _, filename := range bundlesFilenames { bundles = append(bundles, state.MustParseBackupBundles(filename)) } return bundles } func extractBackups(bundles []*state.BackupManifest, renames map[string]string, erase, includeOnly []string) map[string][]state.ComponentBackup { allBackups := make(map[string][]state.ComponentBackup) for fileIndex, bundle := range bundles { for componentName, backup := range bundle.Components { if util.Contains(erase, componentName) { continue } if len(includeOnly) > 0 && !util.Contains(includeOnly, componentName) { continue } renamedTo, renamed := renames[componentName] if renamed { componentName = renamedTo } prev, exist := allBackups[componentName] backup.Source = bundle.Source backup.FileIndex = fileIndex if !exist { allBackups[componentName] = []state.ComponentBackup{backup} } else { allBackups[componentName] = append(prev, backup) } } } return allBackups } func selectBackups(allBackups map[string][]state.ComponentBackup) map[string]state.ComponentBackup { backups := make(map[string]state.ComponentBackup) for componentName, componentBackups := range allBackups { var i int for i = len(componentBackups) - 1; i >= 0; i-- { backup := componentBackups[i] if backup.Status == "success" { backups[componentName] = backup break } } if i < 0 { util.Warn("No valid backup found for component `%s`", componentName) } } return backups } func warnBackupPriority(backups map[string]state.ComponentBackup, allBackups map[string][]state.ComponentBackup) { for componentName, componentBackup := range backups { others := allBackups[componentName] for i := len(others) - 1; i >= 0; i-- { other := others[i] if componentBackup.Source != other.Source { if componentBackup.FileIndex < other.FileIndex && other.Status == "error" { util.Warn("Component `%s` backup from `%s` (status `%s`) is used instead of backup from `%s` (status `%s`)", componentName, componentBackup.Source, componentBackup.Status, other.Source, other.Status) } if componentBackup.Timestamp.Before(other.Timestamp) && componentBackup.FileIndex > other.FileIndex { util.Warn("Component `%s` backup from `%s` (timestamp `%s`) is used instead of backup from `%s` (timestamp `%s`)", componentName, componentBackup.Source, componentBackup.Timestamp.String(), other.Source, other.Timestamp.String()) } } } } } func transformBackupsToParameters(backups map[string]state.ComponentBackup) *manifest.ParametersManifest { params := make([]manifest.Parameter, 0, len(backups)) for componentName, componentBackup := range backups { // TODO find a common root and output a parameters sub-tree with single `component:` declaration for _, output := range componentBackup.Outputs { params = append(params, manifest.Parameter{ Component: componentName, Name: output.Name, Value: output.Value, }) } } return &manifest.ParametersManifest{Parameters: params} }