From 044792db30cd8e82c6f3df02e920c56b112013c7 Mon Sep 17 00:00:00 2001 From: Ali Dowair Date: Fri, 1 Apr 2022 04:03:51 +0300 Subject: [PATCH] feat(cli): show storage capacity in repo status (#1867) The connected repository's backing storage capacity and available space can be now retrieved from `kopia repository status`. In text format, these fields are printed in a human friendly form (MiB, GiB). In JSON mode (`--json`), they are output as bytes. Co-authored-by: Shikhar Mall Co-authored-by: Julio --- cli/command_repository_status.go | 25 +++++++++++++++++++++++-- repo/blob/storage.go | 4 ++-- repo/repository.go | 6 ++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/cli/command_repository_status.go b/cli/command_repository_status.go index 96e32a47c..95444f456 100644 --- a/cli/command_repository_status.go +++ b/cli/command_repository_status.go @@ -34,6 +34,7 @@ type RepositoryStatus struct { ClientOptions repo.ClientOptions `json:"clientOptions"` Storage blob.ConnectionInfo `json:"storage"` + Capacity *blob.Capacity `json:"volume,omitempty"` ContentFormat content.FormattingOptions `json:"contentFormat"` ObjectFormat object.Format `json:"objectFormat"` BlobRetention content.BlobCfgBlob `json:"blobRetention"` @@ -50,7 +51,7 @@ func (c *commandRepositoryStatus) setup(svc advancedAppServices, parent commandP c.jo.setup(svc, cmd) } -func (c *commandRepositoryStatus) outputJSON(r repo.Repository) error { +func (c *commandRepositoryStatus) outputJSON(ctx context.Context, r repo.Repository) error { s := RepositoryStatus{ ConfigFile: c.svc.repositoryConfigFileName(), ClientOptions: r.ClientOptions(), @@ -64,6 +65,15 @@ func (c *commandRepositoryStatus) outputJSON(r repo.Repository) error { s.BlobRetention = dr.BlobCfg() s.Storage = scrubber.ScrubSensitiveData(reflect.ValueOf(ci)).Interface().(blob.ConnectionInfo) // nolint:forcetypeassert s.ContentFormat = scrubber.ScrubSensitiveData(reflect.ValueOf(dr.ContentReader().ContentFormat())).Interface().(content.FormattingOptions) // nolint:forcetypeassert + + switch cp, err := dr.BlobVolume().GetCapacity(ctx); { + case err == nil: + s.Capacity = &cp + case errors.Is(err, blob.ErrNotAVolume): + // This is okay, we will just not populate the result. + default: + return errors.Wrap(err, "unable to get storage volume capacity") + } } c.out.printStdout("%s\n", c.jo.jsonBytes(s)) @@ -71,9 +81,10 @@ func (c *commandRepositoryStatus) outputJSON(r repo.Repository) error { return nil } +// nolint: funlen func (c *commandRepositoryStatus) run(ctx context.Context, rep repo.Repository) error { if c.jo.jsonOutput { - return c.outputJSON(rep) + return c.outputJSON(ctx, rep) } c.out.printStdout("Config file: %v\n", c.svc.repositoryConfigFileName()) @@ -99,6 +110,16 @@ func (c *commandRepositoryStatus) run(ctx context.Context, rep repo.Repository) ci := dr.BlobReader().ConnectionInfo() c.out.printStdout("Storage type: %v\n", ci.Type) + switch cp, err := dr.BlobVolume().GetCapacity(ctx); { + case err == nil: + c.out.printStdout("Storage capacity: %v\n", units.BytesStringBase10(int64(cp.SizeB))) + c.out.printStdout("Storage available: %v\n", units.BytesStringBase10(int64(cp.FreeB))) + case errors.Is(err, blob.ErrNotAVolume): + c.out.printStdout("Storage capacity: unbounded\n") + default: + return errors.Wrap(err, "unable to get storage volume capacity") + } + if cjson, err := json.MarshalIndent(scrubber.ScrubSensitiveData(reflect.ValueOf(ci.Config)).Interface(), " ", " "); err == nil { c.out.printStdout("Storage config: %v\n", string(cjson)) } diff --git a/repo/blob/storage.go b/repo/blob/storage.go index 20e6af00c..dab8c413a 100644 --- a/repo/blob/storage.go +++ b/repo/blob/storage.go @@ -56,9 +56,9 @@ type OutputBuffer interface { // Capacity describes the storage capacity and usage of a Volume. type Capacity struct { // Size of volume in bytes. - SizeB uint64 + SizeB uint64 `json:"capacity,omitempty"` // Available (writeable) space in bytes. - FreeB uint64 + FreeB uint64 `json:"available"` } // Volume defines disk/volume access API to blob storage. diff --git a/repo/repository.go b/repo/repository.go index b625f9163..66547a75f 100644 --- a/repo/repository.go +++ b/repo/repository.go @@ -50,6 +50,7 @@ type DirectRepository interface { ObjectFormat() object.Format BlobCfg() content.BlobCfgBlob BlobReader() blob.Reader + BlobVolume() blob.Volume ContentReader() content.Reader IndexBlobs(ctx context.Context, includeInactive bool) ([]content.IndexBlobInfo, error) Crypter() *content.Crypter @@ -308,6 +309,11 @@ func (r *directRepository) BlobReader() blob.Reader { return r.blobs } +// BlobVolume returns the blob volume interface. +func (r *directRepository) BlobVolume() blob.Volume { + return r.blobs +} + // ContentReader returns the content reader. func (r *directRepository) ContentReader() content.Reader { return r.cmgr