package cli_test import ( "encoding/json" "os" "path" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/kopia/kopia/internal/testutil" "github.com/kopia/kopia/snapshot" "github.com/kopia/kopia/snapshot/policy" "github.com/kopia/kopia/tests/testenv" ) // note: dependent on policy export working. func TestImportPolicy(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, "--override-username=user", "--override-hostname=host") td := testutil.TempDirectory(t) policyFilePath := path.Join(td, "policy.json") // poor man's deep copy defaultPolicyJSON, err := json.Marshal(policy.DefaultPolicy) if err != nil { t.Fatalf("unable to marshal policy: %v", err) } var defaultPolicy *policy.Policy testutil.MustParseJSONLines(t, []string{string(defaultPolicyJSON)}, &defaultPolicy) specifiedPolicies := map[string]*policy.Policy{ "(global)": defaultPolicy, } makePolicyFile := func() { data, err := json.Marshal(specifiedPolicies) if err != nil { t.Fatalf("unable to marshal policy: %v", err) } err = os.WriteFile(policyFilePath, data, 0o600) if err != nil { t.Fatalf("unable to write policy file: %v", err) } } // sanity check that we have the default global policy assertPoliciesEqual(t, e, specifiedPolicies) // change the global policy specifiedPolicies["(global)"].SplitterPolicy.Algorithm = "FIXED-4M" makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath) assertPoliciesEqual(t, e, specifiedPolicies) // create a new policy id := snapshot.SourceInfo{ Host: "host", UserName: "user", Path: filepath.ToSlash(td), }.String() specifiedPolicies[id] = &policy.Policy{ SplitterPolicy: policy.SplitterPolicy{ Algorithm: "FIXED-8M", }, } makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath) assertPoliciesEqual(t, e, specifiedPolicies) // import from a file specifying changes in both policies but limiting import to only one specifiedPolicies["(global)"].CompressionPolicy.CompressorName = "zstd" specifiedPolicies[id].CompressionPolicy.CompressorName = "gzip" makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, "(global)") // local policy should not have changed specifiedPolicies[id].CompressionPolicy.CompressorName = "" assertPoliciesEqual(t, e, specifiedPolicies) specifiedPolicies[id].CompressionPolicy.CompressorName = "gzip" e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, id) assertPoliciesEqual(t, e, specifiedPolicies) // deleting values should work specifiedPolicies[id].CompressionPolicy.CompressorName = "" makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, id) assertPoliciesEqual(t, e, specifiedPolicies) // create a new policy td2 := testutil.TempDirectory(t) id2 := snapshot.SourceInfo{ Host: "host", UserName: "user", Path: filepath.ToSlash(td2), }.String() policy2 := &policy.Policy{ MetadataCompressionPolicy: policy.MetadataCompressionPolicy{ CompressorName: "zstd", }, } specifiedPolicies[id2] = policy2 makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, id2) assertPoliciesEqual(t, e, specifiedPolicies) // unknown fields should be disallowed by default err = os.WriteFile(policyFilePath, []byte(`{ "`+id2+`": { "not-a-real-field": 50, "metadataCompression": { "compressorName": "zstd" } } }`), 0o600) if err != nil { t.Fatalf("unable to write policy file: %v", err) } e.RunAndExpectFailure(t, "policy", "import", "--from-file", policyFilePath, id2) // unless explicitly allowed e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, "--allow-unknown-fields", id2) assertPoliciesEqual(t, e, specifiedPolicies) // no change // deleteOtherPolicies should work delete(specifiedPolicies, id2) makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, "--delete-other-policies") assertPoliciesEqual(t, e, specifiedPolicies) // add it back in specifiedPolicies[id2] = policy2 makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath) assertPoliciesEqual(t, e, specifiedPolicies) // deleteOtherPolicies should work with specified targets as well // don't change policy file e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, "--delete-other-policies", "(global)", id) delete(specifiedPolicies, id2) assertPoliciesEqual(t, e, specifiedPolicies) // --global should be equivalent to (global) specifiedPolicies[id2] = policy2 makePolicyFile() e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, "--global") delete(specifiedPolicies, id2) // should NOT have been imported assertPoliciesEqual(t, e, specifiedPolicies) // sanity check against (global) e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath, "(global)") assertPoliciesEqual(t, e, specifiedPolicies) // another sanity check e.RunAndExpectSuccess(t, "policy", "import", "--from-file", policyFilePath) specifiedPolicies[id2] = policy2 assertPoliciesEqual(t, e, specifiedPolicies) // reading an invalid file should fail e.RunAndExpectFailure(t, "policy", "import", "--from-file", "/not/a/real/file") // invalid targets should fail err = os.WriteFile(policyFilePath, []byte(`{ "userwithouthost@": { "metadataCompression": { "compressorName": "zstd" } } }`), 0o600) if err != nil { t.Fatalf("unable to write policy file: %v", err) } e.RunAndExpectFailure(t, "policy", "import", "--from-file", policyFilePath) } func assertPoliciesEqual(t *testing.T, e *testenv.CLITest, expected map[string]*policy.Policy) { t.Helper() var policies map[string]*policy.Policy testutil.MustParseJSONLines(t, e.RunAndExpectSuccess(t, "policy", "export"), &policies) assert.Equal(t, expected, policies, "unexpected policies") }