Files
kopia/cli/command_policy_import.go
2025-04-15 22:49:13 -07:00

130 lines
3.0 KiB
Go

package cli
import (
"context"
"encoding/json"
"io"
"os"
"slices"
"github.com/pkg/errors"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
)
type commandPolicyImport struct {
policyTargetFlags
filePath string
allowUnknownFields bool
deleteOtherPolicies bool
svc appServices
}
func (c *commandPolicyImport) setup(svc appServices, parent commandParent) {
cmd := parent.Command("import", "Imports policies from a specified file, or stdin if no file is specified.")
cmd.Flag("from-file", "File path to import from").StringVar(&c.filePath)
cmd.Flag("allow-unknown-fields", "Allow unknown fields in the policy file").BoolVar(&c.allowUnknownFields)
cmd.Flag("delete-other-policies", "Delete all other policies, keeping only those that got imported").BoolVar(&c.deleteOtherPolicies)
c.policyTargetFlags.setup(cmd)
c.svc = svc
cmd.Action(svc.repositoryWriterAction(c.run))
}
func (c *commandPolicyImport) run(ctx context.Context, rep repo.RepositoryWriter) error {
var input io.Reader
var err error
if c.filePath != "" {
file, err := os.Open(c.filePath)
if err != nil {
return errors.Wrap(err, "unable to read policy file")
}
defer file.Close() //nolint:errcheck
input = file
} else {
input = c.svc.stdin()
}
policies := make(map[string]*policy.Policy)
d := json.NewDecoder(input)
if !c.allowUnknownFields {
d.DisallowUnknownFields()
}
err = d.Decode(&policies)
if err != nil {
return errors.Wrap(err, "unable to decode policy file as valid json")
}
var targetLimit []snapshot.SourceInfo
if c.global || len(c.targets) > 0 {
targetLimit, err = c.policyTargets(ctx, rep)
if err != nil {
return err
}
}
shouldImportSource := func(target snapshot.SourceInfo) bool {
if targetLimit == nil {
return true
}
return slices.Contains(targetLimit, target)
}
importedSources := make([]string, 0, len(policies))
for ts, newPolicy := range policies {
target, err := snapshot.ParseSourceInfo(ts, rep.ClientOptions().Hostname, rep.ClientOptions().Username)
if err != nil {
return errors.Wrapf(err, "unable to parse source info: %q", ts)
}
if !shouldImportSource(target) {
continue
}
// used for deleteOtherPolicies
importedSources = append(importedSources, ts)
if err := policy.SetPolicy(ctx, rep, target, newPolicy); err != nil {
return errors.Wrapf(err, "can't save policy for %v", target)
}
}
if c.deleteOtherPolicies {
err := deleteOthers(ctx, rep, importedSources)
if err != nil {
return err
}
}
return nil
}
func deleteOthers(ctx context.Context, rep repo.RepositoryWriter, importedSources []string) error {
ps, err := policy.ListPolicies(ctx, rep)
if err != nil {
return errors.Wrap(err, "failed to list policies")
}
for _, p := range ps {
if !slices.Contains(importedSources, p.Target().String()) {
if err := policy.RemovePolicy(ctx, rep, p.Target()); err != nil {
return errors.Wrapf(err, "can't delete policy for %v", p.Target())
}
}
}
return nil
}