mirror of
https://github.com/kopia/kopia.git
synced 2026-03-06 23:38:24 -05:00
* feat(cli): print upgrade owner in repository status To help users understand the state of their repository better, this one line change also prints out the upgrade owner's ID in the output of `kopia repository status`. * Upgrade `create --format-version` help message To show that there is now a format version 3 that can be set.
197 lines
6.9 KiB
Go
197 lines
6.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/alecthomas/kingpin"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/blob"
|
|
"github.com/kopia/kopia/repo/ecc"
|
|
"github.com/kopia/kopia/repo/encryption"
|
|
"github.com/kopia/kopia/repo/format"
|
|
"github.com/kopia/kopia/repo/hashing"
|
|
"github.com/kopia/kopia/repo/splitter"
|
|
"github.com/kopia/kopia/snapshot/policy"
|
|
)
|
|
|
|
const runValidationNote = `NOTE: To validate that your provider is compatible with Kopia, please run:
|
|
|
|
$ kopia repository validate-provider
|
|
|
|
`
|
|
|
|
type commandRepositoryCreate struct {
|
|
createBlockHashFormat string
|
|
createBlockEncryptionFormat string
|
|
createBlockECCFormat string
|
|
createBlockECCOverheadPercent int
|
|
createSplitter string
|
|
createOnly bool
|
|
createFormatVersion int
|
|
retentionMode string
|
|
retentionPeriod time.Duration
|
|
|
|
co connectOptions
|
|
svc advancedAppServices
|
|
out textOutput
|
|
}
|
|
|
|
func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandParent) {
|
|
cmd := parent.Command("create", "Create new repository in a specified location.")
|
|
|
|
cmd.Flag("block-hash", "Content hash algorithm.").PlaceHolder("ALGO").Default(hashing.DefaultAlgorithm).EnumVar(&c.createBlockHashFormat, hashing.SupportedAlgorithms()...)
|
|
cmd.Flag("encryption", "Content encryption algorithm.").PlaceHolder("ALGO").Default(encryption.DefaultAlgorithm).EnumVar(&c.createBlockEncryptionFormat, encryption.SupportedAlgorithms(false)...)
|
|
cmd.Flag("ecc", "[EXPERIMENTAL] Error correction algorithm.").PlaceHolder("ALGO").Default(ecc.DefaultAlgorithm).EnumVar(&c.createBlockECCFormat, ecc.SupportedAlgorithms()...)
|
|
cmd.Flag("ecc-overhead-percent", "[EXPERIMENTAL] How much space overhead can be used for error correction, in percentage. Use 0 to disable ECC.").Default("0").IntVar(&c.createBlockECCOverheadPercent)
|
|
cmd.Flag("object-splitter", "The splitter to use for new objects in the repository").Default(splitter.DefaultAlgorithm).EnumVar(&c.createSplitter, splitter.SupportedAlgorithms()...)
|
|
cmd.Flag("create-only", "Create repository, but don't connect to it.").Short('c').BoolVar(&c.createOnly)
|
|
cmd.Flag("format-version", "Force a particular repository format version (1, 2 or 3, 0==default)").IntVar(&c.createFormatVersion)
|
|
cmd.Flag("retention-mode", "Set the blob retention-mode for supported storage backends.").EnumVar(&c.retentionMode, blob.Governance.String(), blob.Compliance.String())
|
|
cmd.Flag("retention-period", "Set the blob retention-period for supported storage backends.").DurationVar(&c.retentionPeriod)
|
|
|
|
c.co.setup(svc, cmd)
|
|
c.svc = svc
|
|
c.out.setup(svc)
|
|
|
|
for _, prov := range svc.storageProviders() {
|
|
if prov.Name == "from-config" {
|
|
continue
|
|
}
|
|
|
|
// Set up 'create' subcommand
|
|
f := prov.NewFlags()
|
|
cc := cmd.Command(prov.Name, "Create repository in "+prov.Description)
|
|
f.Setup(svc, cc)
|
|
cc.Action(func(kpc *kingpin.ParseContext) error {
|
|
//nolint:wrapcheck
|
|
return svc.runAppWithContext(kpc.SelectedCommand, func(ctx context.Context) error {
|
|
st, err := f.Connect(ctx, true, c.createFormatVersion)
|
|
if err != nil {
|
|
return errors.Wrap(err, "can't connect to storage")
|
|
}
|
|
|
|
return c.runCreateCommandWithStorage(ctx, st)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func (c *commandRepositoryCreate) newRepositoryOptionsFromFlags() *repo.NewRepositoryOptions {
|
|
return &repo.NewRepositoryOptions{
|
|
BlockFormat: format.ContentFormat{
|
|
MutableParameters: format.MutableParameters{
|
|
Version: format.Version(c.createFormatVersion),
|
|
},
|
|
Hash: c.createBlockHashFormat,
|
|
Encryption: c.createBlockEncryptionFormat,
|
|
ECC: c.createBlockECCFormat,
|
|
ECCOverheadPercent: c.createBlockECCOverheadPercent,
|
|
},
|
|
|
|
ObjectFormat: format.ObjectFormat{
|
|
Splitter: c.createSplitter,
|
|
},
|
|
|
|
RetentionMode: blob.RetentionMode(c.retentionMode),
|
|
RetentionPeriod: c.retentionPeriod,
|
|
}
|
|
}
|
|
|
|
func (c *commandRepositoryCreate) ensureEmpty(ctx context.Context, s blob.Storage) error {
|
|
hasDataError := errors.Errorf("has data")
|
|
|
|
err := s.ListBlobs(ctx, "", func(cb blob.Metadata) error {
|
|
return hasDataError
|
|
})
|
|
|
|
if errors.Is(err, hasDataError) {
|
|
return errors.New("found existing data in storage location")
|
|
}
|
|
|
|
return errors.Wrap(err, "error listing blobs")
|
|
}
|
|
|
|
func (c *commandRepositoryCreate) runCreateCommandWithStorage(ctx context.Context, st blob.Storage) error {
|
|
err := c.ensureEmpty(ctx, st)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to get repository storage")
|
|
}
|
|
|
|
options := c.newRepositoryOptionsFromFlags()
|
|
|
|
pass, err := c.svc.getPasswordFromFlags(ctx, true, false)
|
|
if err != nil {
|
|
return errors.Wrap(err, "getting password")
|
|
}
|
|
|
|
log(ctx).Infof("Initializing repository with:")
|
|
|
|
if options.BlockFormat.Version != 0 {
|
|
log(ctx).Infof(" format version: %v", options.BlockFormat.Version)
|
|
}
|
|
|
|
log(ctx).Infof(" block hash: %v", options.BlockFormat.Hash)
|
|
log(ctx).Infof(" encryption: %v", options.BlockFormat.Encryption)
|
|
|
|
if options.BlockFormat.ECC != "" && options.BlockFormat.ECCOverheadPercent > 0 {
|
|
log(ctx).Infof(" ecc: %v with %v%% overhead", options.BlockFormat.ECC, options.BlockFormat.ECCOverheadPercent)
|
|
}
|
|
|
|
log(ctx).Infof(" splitter: %v", options.ObjectFormat.Splitter)
|
|
|
|
if err := repo.Initialize(ctx, st, options, pass); err != nil {
|
|
return errors.Wrap(err, "cannot initialize repository")
|
|
}
|
|
|
|
if c.createOnly {
|
|
return nil
|
|
}
|
|
|
|
if err := c.svc.runConnectCommandWithStorageAndPassword(ctx, &c.co, st, pass); err != nil {
|
|
return errors.Wrap(err, "unable to connect to repository")
|
|
}
|
|
|
|
if err := c.populateRepository(ctx, pass); err != nil {
|
|
return errors.Wrap(err, "error populating repository")
|
|
}
|
|
|
|
noteColor.Fprintf(c.out.stdout(), runValidationNote) //nolint:errcheck
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *commandRepositoryCreate) populateRepository(ctx context.Context, password string) error {
|
|
rep, err := repo.Open(ctx, c.svc.repositoryConfigFileName(), password, c.svc.optionsFromFlags(ctx))
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to open repository")
|
|
}
|
|
defer rep.Close(ctx) //nolint:errcheck
|
|
|
|
//nolint:wrapcheck
|
|
return repo.WriteSession(ctx, rep, repo.WriteSessionOptions{
|
|
Purpose: "populate repository",
|
|
}, func(ctx context.Context, w repo.RepositoryWriter) error {
|
|
if err := policy.SetPolicy(ctx, w, policy.GlobalPolicySourceInfo, policy.DefaultPolicy); err != nil {
|
|
return errors.Wrap(err, "unable to set global policy")
|
|
}
|
|
|
|
var rows []policyTableRow
|
|
|
|
rows = appendRetentionPolicyRows(rows, policy.DefaultPolicy, &policy.Definition{})
|
|
rows = appendCompressionPolicyRows(rows, policy.DefaultPolicy, &policy.Definition{})
|
|
|
|
c.out.printStdout("%v\n", alignedPolicyTableRows(rows))
|
|
|
|
c.out.printStderr("\nTo find more information about default policy run 'kopia policy get'.\nTo change the policy use 'kopia policy set' command.\n")
|
|
|
|
if err := setDefaultMaintenanceParameters(ctx, w); err != nil {
|
|
return errors.Wrap(err, "unable to set maintenance parameters")
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|