mirror of
https://github.com/kopia/kopia.git
synced 2026-01-23 13:58:08 -05:00
This is useful when backing up directories that have giant files aligned at MiB boundary, such as VM disk backups, etc.
493 lines
16 KiB
Go
493 lines
16 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/internal/units"
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/snapshot"
|
|
"github.com/kopia/kopia/snapshot/policy"
|
|
)
|
|
|
|
type commandPolicyShow struct {
|
|
policyTargetFlags
|
|
jo jsonOutput
|
|
out textOutput
|
|
}
|
|
|
|
func (c *commandPolicyShow) setup(svc appServices, parent commandParent) {
|
|
cmd := parent.Command("show", "Show snapshot policy.").Alias("get")
|
|
c.policyTargetFlags.setup(cmd)
|
|
c.jo.setup(svc, cmd)
|
|
c.out.setup(svc)
|
|
cmd.Action(svc.repositoryReaderAction(c.run))
|
|
}
|
|
|
|
func (c *commandPolicyShow) run(ctx context.Context, rep repo.Repository) error {
|
|
targets, err := c.policyTargets(ctx, rep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, target := range targets {
|
|
effective, definition, _, err := policy.GetEffectivePolicy(ctx, rep, target)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "can't get effective policy for %q", target)
|
|
}
|
|
|
|
if c.jo.jsonOutput {
|
|
c.out.printStdout("%s\n", c.jo.jsonBytes(effective))
|
|
} else {
|
|
printPolicy(&c.out, effective, definition)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type policyTableRow struct {
|
|
name string
|
|
value string
|
|
def string
|
|
}
|
|
|
|
func alignedPolicyTableRows(v []policyTableRow) string {
|
|
var nameValueLen int
|
|
|
|
const (
|
|
nameValueSpace = " "
|
|
defSpace = " "
|
|
)
|
|
|
|
for _, it := range v {
|
|
if it.value == "" {
|
|
continue
|
|
}
|
|
|
|
t := it.name
|
|
if it.value != "" {
|
|
t += nameValueSpace + it.value
|
|
}
|
|
|
|
if len(t) > nameValueLen {
|
|
nameValueLen = len(t)
|
|
}
|
|
}
|
|
|
|
var lines []string
|
|
|
|
for _, it := range v {
|
|
l := it.name
|
|
|
|
if it.value != "" || it.def != "" {
|
|
if spaces := nameValueLen - len(l) - len(it.value); spaces > 0 {
|
|
l += strings.Repeat(" ", spaces)
|
|
}
|
|
|
|
l += it.value
|
|
}
|
|
|
|
if it.def != "" {
|
|
l += defSpace
|
|
l += it.def
|
|
}
|
|
|
|
lines = append(lines, l)
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
func definitionPointToString(target, src snapshot.SourceInfo) string {
|
|
if src == target {
|
|
return "(defined for this target)"
|
|
}
|
|
|
|
return "inherited from " + src.String()
|
|
}
|
|
|
|
func printPolicy(out *textOutput, p *policy.Policy, def *policy.Definition) {
|
|
var rows []policyTableRow
|
|
|
|
rows = appendRetentionPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendFilesPolicyValue(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendErrorHandlingPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendSchedulingPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendUploadPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendCompressionPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendSplitterPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendActionsPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendOSSnapshotPolicyRows(rows, p, def)
|
|
rows = append(rows, policyTableRow{})
|
|
rows = appendLoggingPolicyRows(rows, p, def)
|
|
|
|
out.printStdout("Policy for %v:\n\n%v\n", p.Target(), alignedPolicyTableRows(rows))
|
|
}
|
|
|
|
func appendRetentionPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
return append(rows,
|
|
policyTableRow{"Retention:", "", ""},
|
|
policyTableRow{" Annual snapshots:", valueOrNotSet(p.RetentionPolicy.KeepAnnual), definitionPointToString(p.Target(), def.RetentionPolicy.KeepAnnual)},
|
|
policyTableRow{" Monthly snapshots:", valueOrNotSet(p.RetentionPolicy.KeepMonthly), definitionPointToString(p.Target(), def.RetentionPolicy.KeepMonthly)},
|
|
policyTableRow{" Weekly snapshots:", valueOrNotSet(p.RetentionPolicy.KeepWeekly), definitionPointToString(p.Target(), def.RetentionPolicy.KeepWeekly)},
|
|
policyTableRow{" Daily snapshots:", valueOrNotSet(p.RetentionPolicy.KeepDaily), definitionPointToString(p.Target(), def.RetentionPolicy.KeepDaily)},
|
|
policyTableRow{" Hourly snapshots:", valueOrNotSet(p.RetentionPolicy.KeepHourly), definitionPointToString(p.Target(), def.RetentionPolicy.KeepHourly)},
|
|
policyTableRow{" Latest snapshots:", valueOrNotSet(p.RetentionPolicy.KeepLatest), definitionPointToString(p.Target(), def.RetentionPolicy.KeepLatest)},
|
|
policyTableRow{" Ignore identical snapshots:", boolToString(p.RetentionPolicy.IgnoreIdenticalSnapshots.OrDefault(false)), definitionPointToString(p.Target(), def.RetentionPolicy.IgnoreIdenticalSnapshots)},
|
|
)
|
|
}
|
|
|
|
func boolToString(v bool) string {
|
|
if v {
|
|
return "true"
|
|
}
|
|
|
|
return "false"
|
|
}
|
|
|
|
func logDetailToString(v policy.LogDetail) string {
|
|
return fmt.Sprintf("%v", v)
|
|
}
|
|
|
|
func appendFilesPolicyValue(items []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
items = append(items,
|
|
policyTableRow{"Files policy:", "", ""},
|
|
policyTableRow{
|
|
" Ignore cache directories:",
|
|
boolToString(p.FilesPolicy.IgnoreCacheDirectories.OrDefault(true)),
|
|
definitionPointToString(p.Target(), def.FilesPolicy.IgnoreCacheDirectories),
|
|
})
|
|
|
|
if len(p.FilesPolicy.IgnoreRules) > 0 {
|
|
items = append(items, policyTableRow{
|
|
" Ignore rules:", "", definitionPointToString(p.Target(), def.FilesPolicy.IgnoreRules),
|
|
})
|
|
for _, rule := range p.FilesPolicy.IgnoreRules {
|
|
items = append(items, policyTableRow{" " + rule, "", ""})
|
|
}
|
|
} else {
|
|
items = append(items, policyTableRow{" No ignore rules:", "", ""})
|
|
}
|
|
|
|
if len(p.FilesPolicy.DotIgnoreFiles) > 0 {
|
|
items = append(items, policyTableRow{
|
|
" Read ignore rules from files:", "",
|
|
definitionPointToString(p.Target(), def.FilesPolicy.DotIgnoreFiles),
|
|
})
|
|
|
|
for _, dotFile := range p.FilesPolicy.DotIgnoreFiles {
|
|
items = append(items, policyTableRow{" " + dotFile, "", ""})
|
|
}
|
|
}
|
|
|
|
if maxSize := p.FilesPolicy.MaxFileSize; maxSize > 0 {
|
|
items = append(items, policyTableRow{
|
|
" Ignore files above:",
|
|
units.BytesString(maxSize),
|
|
definitionPointToString(p.Target(), def.FilesPolicy.MaxFileSize),
|
|
})
|
|
}
|
|
|
|
items = append(items, policyTableRow{
|
|
" Scan one filesystem only:",
|
|
boolToString(p.FilesPolicy.OneFileSystem.OrDefault(false)),
|
|
definitionPointToString(p.Target(), def.FilesPolicy.OneFileSystem),
|
|
})
|
|
|
|
return items
|
|
}
|
|
|
|
func appendErrorHandlingPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
return append(rows,
|
|
policyTableRow{"Error handling policy:", "", ""},
|
|
policyTableRow{
|
|
" Ignore file read errors:",
|
|
boolToString(p.ErrorHandlingPolicy.IgnoreFileErrors.OrDefault(false)),
|
|
definitionPointToString(p.Target(), def.ErrorHandlingPolicy.IgnoreFileErrors),
|
|
},
|
|
policyTableRow{
|
|
" Ignore directory read errors:",
|
|
boolToString(p.ErrorHandlingPolicy.IgnoreDirectoryErrors.OrDefault(false)),
|
|
definitionPointToString(p.Target(), def.ErrorHandlingPolicy.IgnoreDirectoryErrors),
|
|
},
|
|
policyTableRow{
|
|
" Ignore unknown types:",
|
|
boolToString(p.ErrorHandlingPolicy.IgnoreUnknownTypes.OrDefault(true)),
|
|
definitionPointToString(p.Target(), def.ErrorHandlingPolicy.IgnoreUnknownTypes),
|
|
},
|
|
)
|
|
}
|
|
|
|
func appendLoggingPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
return append(rows,
|
|
policyTableRow{
|
|
fmt.Sprintf("Logging details (%v-none, %v-maximum):", policy.LogDetailNone, policy.LogDetailMax), "", "",
|
|
},
|
|
policyTableRow{
|
|
" Directory snapshotted:",
|
|
logDetailToString(p.LoggingPolicy.Directories.Snapshotted.OrDefault(policy.LogDetailNone)),
|
|
definitionPointToString(p.Target(), def.LoggingPolicy.Directories.Snapshotted),
|
|
},
|
|
policyTableRow{
|
|
" Directory ignored:",
|
|
logDetailToString(p.LoggingPolicy.Directories.Ignored.OrDefault(policy.LogDetailNone)),
|
|
definitionPointToString(p.Target(), def.LoggingPolicy.Directories.Ignored),
|
|
},
|
|
policyTableRow{
|
|
" Entry snapshotted:",
|
|
logDetailToString(p.LoggingPolicy.Entries.Snapshotted.OrDefault(policy.LogDetailNone)),
|
|
definitionPointToString(p.Target(), def.LoggingPolicy.Entries.Snapshotted),
|
|
},
|
|
policyTableRow{
|
|
" Entry ignored:",
|
|
logDetailToString(p.LoggingPolicy.Entries.Ignored.OrDefault(policy.LogDetailNone)),
|
|
definitionPointToString(p.Target(), def.LoggingPolicy.Entries.Ignored),
|
|
},
|
|
policyTableRow{
|
|
" Entry cache hit:",
|
|
logDetailToString(p.LoggingPolicy.Entries.CacheHit.OrDefault(policy.LogDetailNone)),
|
|
definitionPointToString(p.Target(), def.LoggingPolicy.Entries.CacheHit),
|
|
},
|
|
policyTableRow{
|
|
" Entry cache miss:",
|
|
logDetailToString(p.LoggingPolicy.Entries.CacheMiss.OrDefault(policy.LogDetailNone)),
|
|
definitionPointToString(p.Target(), def.LoggingPolicy.Entries.CacheMiss),
|
|
},
|
|
)
|
|
}
|
|
|
|
func appendUploadPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
return append(rows,
|
|
policyTableRow{"Uploads:", "", ""},
|
|
policyTableRow{" Max parallel snapshots (server/UI):", valueOrNotSet(p.UploadPolicy.MaxParallelSnapshots), definitionPointToString(p.Target(), def.UploadPolicy.MaxParallelSnapshots)},
|
|
policyTableRow{" Max parallel file reads:", valueOrNotSet(p.UploadPolicy.MaxParallelFileReads), definitionPointToString(p.Target(), def.UploadPolicy.MaxParallelFileReads)},
|
|
policyTableRow{" Parallel upload above size:", valueOrNotSetOptionalInt64Bytes(p.UploadPolicy.ParallelUploadAboveSize), definitionPointToString(p.Target(), def.UploadPolicy.ParallelUploadAboveSize)},
|
|
)
|
|
}
|
|
|
|
func appendSchedulingPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
rows = append(rows, policyTableRow{"Scheduling policy:", "", ""})
|
|
|
|
hasAny := false
|
|
|
|
rows = append(rows, policyTableRow{" Scheduled snapshots:", "", ""})
|
|
|
|
if p.SchedulingPolicy.Interval() != 0 {
|
|
rows = append(rows, policyTableRow{
|
|
" Snapshot interval:",
|
|
p.SchedulingPolicy.Interval().String(),
|
|
definitionPointToString(p.Target(), def.SchedulingPolicy.IntervalSeconds),
|
|
})
|
|
|
|
hasAny = true
|
|
}
|
|
|
|
if len(p.SchedulingPolicy.TimesOfDay) > 0 {
|
|
rows = append(rows,
|
|
policyTableRow{
|
|
" Run missed snapshots:",
|
|
boolToString(p.SchedulingPolicy.RunMissed.OrDefault(false)),
|
|
definitionPointToString(p.Target(), def.SchedulingPolicy.RunMissed),
|
|
},
|
|
policyTableRow{
|
|
" Snapshot times:",
|
|
"",
|
|
definitionPointToString(p.Target(), def.SchedulingPolicy.TimesOfDay),
|
|
})
|
|
|
|
for _, tod := range p.SchedulingPolicy.TimesOfDay {
|
|
rows = append(rows, policyTableRow{" " + tod.String(), "", ""})
|
|
}
|
|
|
|
hasAny = true
|
|
}
|
|
|
|
if len(p.SchedulingPolicy.Cron) > 0 {
|
|
rows = append(rows, policyTableRow{" Crontab expressions:", "", definitionPointToString(p.Target(), def.SchedulingPolicy.Cron)})
|
|
|
|
for _, cron := range p.SchedulingPolicy.Cron {
|
|
rows = append(rows, policyTableRow{" " + cron, "", ""})
|
|
}
|
|
|
|
hasAny = true
|
|
}
|
|
|
|
if !hasAny {
|
|
rows = append(rows, policyTableRow{" None.", "", ""})
|
|
}
|
|
|
|
rows = append(rows, policyTableRow{" Manual snapshot:", boolToString(p.SchedulingPolicy.Manual), definitionPointToString(p.Target(), def.SchedulingPolicy.Manual)})
|
|
|
|
return rows
|
|
}
|
|
|
|
func appendCompressionPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
if p.CompressionPolicy.CompressorName == "" || p.CompressionPolicy.CompressorName == "none" {
|
|
rows = append(rows, policyTableRow{"Compression disabled.", "", ""})
|
|
return rows
|
|
}
|
|
|
|
rows = append(rows,
|
|
policyTableRow{"Compression:", "", ""},
|
|
policyTableRow{" Compressor:", string(p.CompressionPolicy.CompressorName), definitionPointToString(p.Target(), def.CompressionPolicy.CompressorName)})
|
|
|
|
switch {
|
|
case len(p.CompressionPolicy.OnlyCompress) > 0:
|
|
rows = append(rows, policyTableRow{
|
|
" Only compress files with the following extensions:", "",
|
|
definitionPointToString(p.Target(), def.CompressionPolicy.OnlyCompress),
|
|
})
|
|
|
|
for _, rule := range p.CompressionPolicy.OnlyCompress {
|
|
rows = append(rows, policyTableRow{" - " + rule, "", ""})
|
|
}
|
|
|
|
case len(p.CompressionPolicy.NeverCompress) > 0:
|
|
rows = append(rows, policyTableRow{
|
|
" Compress all files except the following extensions:", "",
|
|
definitionPointToString(p.Target(), def.CompressionPolicy.NeverCompress),
|
|
})
|
|
|
|
for _, rule := range p.CompressionPolicy.NeverCompress {
|
|
rows = append(rows, policyTableRow{" " + rule, "", ""})
|
|
}
|
|
|
|
default:
|
|
rows = append(rows, policyTableRow{" Compress files regardless of extensions.", "", ""})
|
|
}
|
|
|
|
switch {
|
|
case p.CompressionPolicy.MaxSize > 0:
|
|
rows = append(rows, policyTableRow{fmt.Sprintf(
|
|
" Only compress files between %v and %v.",
|
|
units.BytesString(p.CompressionPolicy.MinSize),
|
|
units.BytesString(p.CompressionPolicy.MaxSize)), "", ""})
|
|
|
|
case p.CompressionPolicy.MinSize > 0:
|
|
rows = append(rows, policyTableRow{fmt.Sprintf(
|
|
" Only compress files bigger than %v.",
|
|
units.BytesString(p.CompressionPolicy.MinSize)), "", ""})
|
|
|
|
default:
|
|
rows = append(rows, policyTableRow{" Compress files of all sizes.", "", ""})
|
|
}
|
|
|
|
return rows
|
|
}
|
|
|
|
func appendSplitterPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
algorithm := p.SplitterPolicy.Algorithm
|
|
if algorithm == "" {
|
|
algorithm = "(repository default)"
|
|
}
|
|
|
|
rows = append(rows,
|
|
policyTableRow{"Splitter:", "", ""},
|
|
policyTableRow{" Algorithm override:", algorithm, definitionPointToString(p.Target(), def.SplitterPolicy.Algorithm)})
|
|
|
|
return rows
|
|
}
|
|
|
|
func appendActionsPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
var anyActions bool
|
|
|
|
if h := p.Actions.BeforeSnapshotRoot; h != nil {
|
|
rows = append(rows,
|
|
policyTableRow{"Run command before snapshot root:", "", definitionPointToString(p.Target(), def.Actions.BeforeSnapshotRoot)})
|
|
rows = appendActionCommandRows(rows, h)
|
|
|
|
anyActions = true
|
|
}
|
|
|
|
if h := p.Actions.AfterSnapshotRoot; h != nil {
|
|
rows = append(rows, policyTableRow{"Run command after snapshot root:", "", definitionPointToString(p.Target(), def.Actions.AfterSnapshotRoot)})
|
|
rows = appendActionCommandRows(rows, h)
|
|
|
|
anyActions = true
|
|
}
|
|
|
|
if h := p.Actions.BeforeFolder; h != nil {
|
|
rows = append(rows, policyTableRow{"Run command before this folder:", "", "(non-inheritable)"})
|
|
rows = appendActionCommandRows(rows, h)
|
|
|
|
anyActions = true
|
|
}
|
|
|
|
if h := p.Actions.AfterFolder; h != nil {
|
|
rows = append(rows, policyTableRow{"Run command after this folder:", "", "(non-inheritable)"})
|
|
rows = appendActionCommandRows(rows, h)
|
|
|
|
anyActions = true
|
|
}
|
|
|
|
if !anyActions {
|
|
rows = append(rows, policyTableRow{"No actions defined.", "", ""})
|
|
}
|
|
|
|
return rows
|
|
}
|
|
|
|
func appendActionCommandRows(rows []policyTableRow, h *policy.ActionCommand) []policyTableRow {
|
|
if h.Script != "" {
|
|
rows = append(rows,
|
|
policyTableRow{" Embedded script (stored in repository):", "", ""},
|
|
policyTableRow{indentMultilineString(h.Script, " "), "", ""},
|
|
)
|
|
} else {
|
|
rows = append(rows,
|
|
policyTableRow{" Command:", "", ""},
|
|
policyTableRow{" " + h.Command + " " + strings.Join(h.Arguments, " "), "", ""})
|
|
}
|
|
|
|
actualMode := h.Mode
|
|
if actualMode == "" {
|
|
actualMode = "sync"
|
|
}
|
|
|
|
rows = append(rows,
|
|
policyTableRow{" Mode:", actualMode, ""},
|
|
policyTableRow{" Timeout:", (time.Second * time.Duration(h.TimeoutSeconds)).String(), ""},
|
|
policyTableRow{"", "", ""},
|
|
)
|
|
|
|
return rows
|
|
}
|
|
|
|
func appendOSSnapshotPolicyRows(rows []policyTableRow, p *policy.Policy, def *policy.Definition) []policyTableRow {
|
|
rows = append(rows,
|
|
policyTableRow{"OS-level snapshot support:", "", ""},
|
|
policyTableRow{" Volume Shadow Copy:", p.OSSnapshotPolicy.VolumeShadowCopy.Enable.String(), definitionPointToString(p.Target(), def.OSSnapshotPolicy.VolumeShadowCopy.Enable)},
|
|
)
|
|
|
|
return rows
|
|
}
|
|
|
|
func valueOrNotSet(p *policy.OptionalInt) string {
|
|
if p == nil {
|
|
return "-"
|
|
}
|
|
|
|
return fmt.Sprintf("%v", *p)
|
|
}
|
|
|
|
func valueOrNotSetOptionalInt64Bytes(p *policy.OptionalInt64) string {
|
|
if p == nil {
|
|
return "-"
|
|
}
|
|
|
|
return units.BytesString(int64(*p))
|
|
}
|