mirror of
https://github.com/kopia/kopia.git
synced 2026-03-05 23:08:11 -05:00
feat(snapshots): Implement volume shadow copy support on Windows (#3543)
* Implement volume shadow copy support on Windows * Update go-vss version * Fix unused variables * Rename upload_actions*.go files * Move vss settings to a separate policy section * Handle existing shadow copy root * Fix tests * Fix lint issues * Add cli policy test * Add OS snapshot integration test * Add GitHub Actions VSS test * Fix "Incorrect function" error for root VSS snapshots * Rename err to finalErr in createOSSnapshot * Add OSSnapshotMode test * Do not modify paths starting with \\?\ on Windows * Allow warning messages in logfile tests * Fix ignorefs not wrapping OS snapshot directory * Retry VSS creation if another op was in progress --------- Co-authored-by: Jarek Kowalski <jaak@jkowalski.net>
This commit is contained in:
@@ -24,6 +24,7 @@ type commandPolicySet struct {
|
||||
policyLoggingFlags
|
||||
policyRetentionFlags
|
||||
policySchedulingFlags
|
||||
policyOSSnapshotFlags
|
||||
policyUploadFlags
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ func (c *commandPolicySet) setup(svc appServices, parent commandParent) {
|
||||
c.policyLoggingFlags.setup(cmd)
|
||||
c.policyRetentionFlags.setup(cmd)
|
||||
c.policySchedulingFlags.setup(cmd)
|
||||
c.policyOSSnapshotFlags.setup(cmd)
|
||||
c.policyUploadFlags.setup(cmd)
|
||||
|
||||
cmd.Action(svc.repositoryWriterAction(c.run))
|
||||
@@ -112,6 +114,10 @@ func (c *commandPolicySet) setPolicyFromFlags(ctx context.Context, p *policy.Pol
|
||||
return errors.Wrap(err, "actions policy")
|
||||
}
|
||||
|
||||
if err := c.setOSSnapshotPolicyFromFlags(ctx, &p.OSSnapshotPolicy, changeCount); err != nil {
|
||||
return errors.Wrap(err, "OS snapshot policy")
|
||||
}
|
||||
|
||||
if err := c.setLoggingPolicyFromFlags(ctx, &p.LoggingPolicy, changeCount); err != nil {
|
||||
return errors.Wrap(err, "actions policy")
|
||||
}
|
||||
|
||||
64
cli/command_policy_set_os_snapshot.go
Normal file
64
cli/command_policy_set_os_snapshot.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kopia/kopia/snapshot/policy"
|
||||
)
|
||||
|
||||
type policyOSSnapshotFlags struct {
|
||||
policyEnableVolumeShadowCopy string
|
||||
}
|
||||
|
||||
func (c *policyOSSnapshotFlags) setup(cmd *kingpin.CmdClause) {
|
||||
osSnapshotMode := []string{policy.OSSnapshotNeverString, policy.OSSnapshotAlwaysString, policy.OSSnapshotWhenAvailableString, inheritPolicyString}
|
||||
|
||||
cmd.Flag("enable-volume-shadow-copy", "Enable Volume Shadow Copy snapshots ('never', 'always', 'when-available', 'inherit')").PlaceHolder("MODE").EnumVar(&c.policyEnableVolumeShadowCopy, osSnapshotMode...)
|
||||
}
|
||||
|
||||
func (c *policyOSSnapshotFlags) setOSSnapshotPolicyFromFlags(ctx context.Context, fp *policy.OSSnapshotPolicy, changeCount *int) error {
|
||||
if err := applyPolicyOSSnapshotMode(ctx, "enable volume shadow copy", &fp.VolumeShadowCopy.Enable, c.policyEnableVolumeShadowCopy, changeCount); err != nil {
|
||||
return errors.Wrap(err, "enable volume shadow copy")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyPolicyOSSnapshotMode(ctx context.Context, desc string, val **policy.OSSnapshotMode, str string, changeCount *int) error {
|
||||
if str == "" {
|
||||
// not changed
|
||||
return nil
|
||||
}
|
||||
|
||||
var mode policy.OSSnapshotMode
|
||||
|
||||
switch str {
|
||||
case inheritPolicyString, defaultPolicyString:
|
||||
*changeCount++
|
||||
|
||||
log(ctx).Infof(" - resetting %q to a default value inherited from parent.", desc)
|
||||
|
||||
*val = nil
|
||||
|
||||
return nil
|
||||
case policy.OSSnapshotNeverString:
|
||||
mode = policy.OSSnapshotNever
|
||||
case policy.OSSnapshotAlwaysString:
|
||||
mode = policy.OSSnapshotAlways
|
||||
case policy.OSSnapshotWhenAvailableString:
|
||||
mode = policy.OSSnapshotWhenAvailable
|
||||
default:
|
||||
return errors.Errorf("invalid %q mode %q", desc, str)
|
||||
}
|
||||
|
||||
*changeCount++
|
||||
|
||||
log(ctx).Infof(" - setting %q to %v.", desc, mode)
|
||||
|
||||
*val = &mode
|
||||
|
||||
return nil
|
||||
}
|
||||
42
cli/command_policy_set_os_snapshot_test.go
Normal file
42
cli/command_policy_set_os_snapshot_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/kopia/kopia/internal/testutil"
|
||||
"github.com/kopia/kopia/tests/testenv"
|
||||
)
|
||||
|
||||
func TestSetOSSnapshotPolicy(t *testing.T) {
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, testenv.NewInProcRunner(t))
|
||||
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
|
||||
e.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", e.RepoDir)
|
||||
|
||||
lines := e.RunAndExpectSuccess(t, "policy", "show", "--global")
|
||||
lines = compressSpaces(lines)
|
||||
require.Contains(t, lines, " Volume Shadow Copy: when-available (defined for this target)")
|
||||
|
||||
// make some directory we'll be setting policy on
|
||||
td := testutil.TempDirectory(t)
|
||||
|
||||
lines = e.RunAndExpectSuccess(t, "policy", "show", td)
|
||||
lines = compressSpaces(lines)
|
||||
require.Contains(t, lines, " Volume Shadow Copy: when-available inherited from (global)")
|
||||
|
||||
e.RunAndExpectSuccess(t, "policy", "set", "--global", "--enable-volume-shadow-copy=always")
|
||||
|
||||
lines = e.RunAndExpectSuccess(t, "policy", "show", td)
|
||||
lines = compressSpaces(lines)
|
||||
|
||||
require.Contains(t, lines, " Volume Shadow Copy: always inherited from (global)")
|
||||
|
||||
e.RunAndExpectSuccess(t, "policy", "set", "--enable-volume-shadow-copy=never", td)
|
||||
|
||||
lines = e.RunAndExpectSuccess(t, "policy", "show", td)
|
||||
lines = compressSpaces(lines)
|
||||
|
||||
require.Contains(t, lines, " Volume Shadow Copy: never (defined for this target)")
|
||||
}
|
||||
@@ -128,6 +128,8 @@ func printPolicy(out *textOutput, p *policy.Policy, def *policy.Definition) {
|
||||
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))
|
||||
@@ -449,6 +451,15 @@ func appendActionCommandRows(rows []policyTableRow, h *policy.ActionCommand) []p
|
||||
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 "-"
|
||||
|
||||
Reference in New Issue
Block a user