From 10fe20c21eab518189df7f9ebef1736212086198 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Fri, 17 Mar 2023 10:40:36 +0100 Subject: [PATCH 1/3] refactor: extract render code to client Signed-off-by: Miguel Martinez Trivino --- app/cli/internal/action/artifact_download.go | 48 +++++++++++++- app/cli/internal/action/artifact_upload.go | 10 +++ app/cli/internal/casclient/grpc/downloader.go | 58 ++--------------- app/cli/internal/casclient/grpc/uploader.go | 62 ++----------------- 4 files changed, 67 insertions(+), 111 deletions(-) diff --git a/app/cli/internal/action/artifact_download.go b/app/cli/internal/action/artifact_download.go index f7600d03c..95a502bde 100644 --- a/app/cli/internal/action/artifact_download.go +++ b/app/cli/internal/action/artifact_download.go @@ -23,8 +23,10 @@ import ( "io" "os" "path" + "time" casclient "github.com/chainloop-dev/chainloop/app/cli/internal/casclient/grpc" + "github.com/jedib0t/go-pretty/v6/progress" "google.golang.org/grpc" crv1 "github.com/google/go-containerregistry/pkg/v1" @@ -50,7 +52,7 @@ func (a *ArtifactDownload) Run(downloadPath, digest string) error { return fmt.Errorf("invalid digest: %w", err) } - client := casclient.NewDownloader(a.artifactsCASConn, casclient.WithLogger(a.Logger), casclient.WithProgressRender(true)) + client := casclient.NewDownloader(a.artifactsCASConn, casclient.WithProgressRender(true)) ctx := context.Background() info, err := client.Describe(ctx, h.Hex) if err != nil { @@ -77,6 +79,11 @@ func (a *ArtifactDownload) Run(downloadPath, digest string) error { w := io.MultiWriter(f, hash) a.Logger.Info().Str("name", info.Filename).Str("to", downloadPath).Msg("downloading file") + + // render progress bar + go renderOperationStatus(ctx, client.ProgressStatus, a.Logger) + defer close(client.ProgressStatus) + err = client.Download(ctx, w, h.Hex, info.Size) if err != nil { a.Logger.Debug().Err(err).Msg("problem downloading file") @@ -87,7 +94,46 @@ func (a *ArtifactDownload) Run(downloadPath, digest string) error { return fmt.Errorf("checksums mismatch: got: %s, expected: %s", got, want) } + // Give some time for the progress renderer to finish + // TODO: Implement with proper subroutine messaging + time.Sleep(progress.DefaultUpdateFrequency) + a.Logger.Info().Str("path", downloadPath).Msg("file downloaded!") return nil } + +func renderOperationStatus(ctx context.Context, progressChan casclient.ProgressStatusChan, output io.Writer) { + pw := progress.NewWriter() + pw.Style().Visibility.ETA = true + pw.Style().Visibility.Speed = true + pw.SetUpdateFrequency(progress.DefaultUpdateFrequency) + + var tracker *progress.Tracker + go pw.Render() + defer pw.Stop() + + for { + select { + case <-ctx.Done(): + return + case status, ok := <-progressChan: + if !ok { + return + } + + // Initialize tracker + if tracker == nil { + // Hack: Add 1 to the total to make sure the tracker is not marked as done before the upload is finished + // this way the current value will never reach the total + // but instead the tracker will be marked as done by the defer statement + total := status.TotalSizeBytes + 1 + tracker = &progress.Tracker{Total: total, Units: progress.UnitsBytes} + defer tracker.MarkAsDone() + pw.AppendTracker(tracker) + } + + tracker.SetValue(status.ProcessedBytes) + } + } +} diff --git a/app/cli/internal/action/artifact_upload.go b/app/cli/internal/action/artifact_upload.go index 89ddb3949..9feba4358 100644 --- a/app/cli/internal/action/artifact_upload.go +++ b/app/cli/internal/action/artifact_upload.go @@ -17,8 +17,10 @@ package action import ( "context" + "time" casclient "github.com/chainloop-dev/chainloop/app/cli/internal/casclient/grpc" + "github.com/jedib0t/go-pretty/v6/progress" "google.golang.org/grpc" ) @@ -42,10 +44,18 @@ func NewArtifactUpload(opts *ArtifactUploadOpts) *ArtifactUpload { func (a *ArtifactUpload) Run(filePath string) (*CASArtifact, error) { client := casclient.NewUploader(a.artifactsCASConn, casclient.WithLogger(a.Logger), casclient.WithProgressRender(true)) + // render progress bar + go renderOperationStatus(context.Background(), client.ProgressStatus, a.Logger) + defer close(client.ProgressStatus) + res, err := client.Upload(context.Background(), filePath) if err != nil { return nil, err } + // Give some time for the progress renderer to finish + // TODO: Implement with proper subroutine messaging + time.Sleep(progress.DefaultUpdateFrequency) + return &CASArtifact{Digest: res.Digest, fileName: res.Filename}, nil } diff --git a/app/cli/internal/casclient/grpc/downloader.go b/app/cli/internal/casclient/grpc/downloader.go index fac5a2729..949beb7d7 100644 --- a/app/cli/internal/casclient/grpc/downloader.go +++ b/app/cli/internal/casclient/grpc/downloader.go @@ -20,12 +20,9 @@ import ( "errors" "fmt" "io" - "os" - "time" v1 "github.com/chainloop-dev/chainloop/app/artifact-cas/api/cas/v1" "github.com/chainloop-dev/chainloop/internal/attestation/crafter/materials" - "github.com/jedib0t/go-pretty/v6/progress" "github.com/rs/zerolog" "google.golang.org/genproto/googleapis/bytestream" "google.golang.org/grpc" @@ -39,8 +36,8 @@ func NewDownloader(conn *grpc.ClientConn, opts ...ClientOpts) *DownloaderClient client := &DownloaderClient{ casClient: &casClient{ conn: conn, - logger: zerolog.New(os.Stderr), - progressStatus: make(chan *materials.UpDownStatus, 2), + ProgressStatus: make(chan *materials.UpDownStatus, 2), + logger: zerolog.Nop(), }, } @@ -61,11 +58,6 @@ func (c *DownloaderClient) Download(ctx context.Context, w io.Writer, digest str ctx, cancel := context.WithCancel(ctx) defer cancel() - if c.renderProgress { - go c.renderDownloadStatus(ctx, c.logger) - defer close(c.progressStatus) - } - // Open the stream to start reading chunks reader, err := bytestream.NewByteStreamClient(c.conn).Read(ctx, &bytestream.ReadRequest{ResourceName: digest}) if err != nil { @@ -97,22 +89,15 @@ func (c *DownloaderClient) Download(ctx context.Context, w io.Writer, digest str } if c.renderProgress { - c.progressStatus <- latestStatus + c.ProgressStatus <- latestStatus } } - // Give some time for the progress renderer to finish - // TODO: Implement with proper subroutine messaging - if c.renderProgress { - time.Sleep(renderUpdateFrequency) - // Block until the buffer has been filled or the upload process has been canceled - } - return nil } // Describe returns the metadata of a resource by its digest -// We use this to get the filename and the total size of the artifacct +// We use this to get the filename and the total size of the artifact func (c *DownloaderClient) Describe(ctx context.Context, digest string) (*materials.ResourceInfo, error) { client := v1.NewResourceServiceClient(c.conn) resp, err := client.Describe(ctx, &v1.ResourceServiceDescribeRequest{Digest: digest}) @@ -124,38 +109,3 @@ func (c *DownloaderClient) Describe(ctx context.Context, digest string) (*materi Digest: resp.GetResult().GetDigest(), Filename: resp.Result.GetFileName(), Size: resp.Result.GetSize(), }, nil } - -func (c *DownloaderClient) renderDownloadStatus(ctx context.Context, output io.Writer) { - pw := progress.NewWriter() - pw.Style().Visibility.ETA = true - pw.Style().Visibility.Speed = true - pw.SetUpdateFrequency(renderUpdateFrequency) - - var tracker *progress.Tracker - go pw.Render() - defer pw.Stop() - - for { - select { - case <-ctx.Done(): - return - case s, ok := <-c.progressStatus: - if !ok { - return - } - - // Initialize tracker - if tracker == nil { - total := s.TotalSizeBytes - tracker = &progress.Tracker{ - Total: total, - Units: progress.UnitsBytes, - } - defer tracker.MarkAsDone() - pw.AppendTracker(tracker) - } - - tracker.SetValue(s.ProcessedBytes) - } - } -} diff --git a/app/cli/internal/casclient/grpc/uploader.go b/app/cli/internal/casclient/grpc/uploader.go index cfa72b916..25a61998b 100644 --- a/app/cli/internal/casclient/grpc/uploader.go +++ b/app/cli/internal/casclient/grpc/uploader.go @@ -25,12 +25,10 @@ import ( "io" "os" "path" - "time" "code.cloudfoundry.org/bytefmt" v1 "github.com/chainloop-dev/chainloop/app/artifact-cas/api/cas/v1" "github.com/chainloop-dev/chainloop/internal/attestation/crafter/materials" - "github.com/jedib0t/go-pretty/v6/progress" "github.com/rs/zerolog" "google.golang.org/genproto/googleapis/bytestream" "google.golang.org/grpc" @@ -38,11 +36,12 @@ import ( cr_v1 "github.com/google/go-containerregistry/pkg/v1" ) +type ProgressStatusChan chan (*materials.UpDownStatus) type casClient struct { conn *grpc.ClientConn logger zerolog.Logger // channel to send progress status to the go-routine that's rendering the progress bar - progressStatus chan (*materials.UpDownStatus) + ProgressStatus ProgressStatusChan // wether to render progress bar renderProgress bool } @@ -71,8 +70,8 @@ func NewUploader(conn *grpc.ClientConn, opts ...ClientOpts) *UploaderClient { client := &UploaderClient{ casClient: &casClient{ conn: conn, - progressStatus: make(chan *materials.UpDownStatus, 2), // Adding some buffer - logger: zerolog.New(os.Stderr), + ProgressStatus: make(chan *materials.UpDownStatus, 2), // Adding some buffer + logger: zerolog.Nop(), }, bufferSize: defaultUploadChunkSize, } @@ -89,12 +88,6 @@ func (c *UploaderClient) Upload(ctx context.Context, filepath string) (*material ctx, cancel := context.WithCancel(ctx) defer cancel() - // inititate progress bar - if c.renderProgress { - go c.renderUploadStatus(ctx, c.logger) - defer close(c.progressStatus) - } - // open file and calculate digest f, err := os.Open(filepath) if err != nil { @@ -104,7 +97,7 @@ func (c *UploaderClient) Upload(ctx context.Context, filepath string) (*material hash, _, err := cr_v1.SHA256(f) if err != nil { - return nil, fmt.Errorf("genering digest: %w", err) + return nil, fmt.Errorf("generating digest: %w", err) } // Since we have already iterated on the file to calculate the digest @@ -191,7 +184,7 @@ doUpload: } if c.renderProgress { - c.progressStatus <- latestStatus + c.ProgressStatus <- latestStatus } c.logger.Debug(). @@ -204,52 +197,9 @@ doUpload: return nil, err } - // Give some time for the progress renderer to finish - // TODO: Implement with proper subroutine messaging - if c.renderProgress { - time.Sleep(renderUpdateFrequency) - // Block until the buffer has been filled or the upload process has been canceled - } - return latestStatus, nil } -var renderUpdateFrequency = progress.DefaultUpdateFrequency - -func (c *UploaderClient) renderUploadStatus(ctx context.Context, output io.Writer) { - pw := progress.NewWriter() - pw.Style().Visibility.ETA = true - pw.Style().Visibility.Speed = true - pw.SetUpdateFrequency(renderUpdateFrequency) - - var tracker *progress.Tracker - go pw.Render() - defer pw.Stop() - - for { - select { - case <-ctx.Done(): - return - case status, ok := <-c.progressStatus: - if !ok { - return - } - - if tracker == nil { - // Hack: Add 1 to the total to make sure the tracker is not marked as done before the upload is finished - // this way the current value will never reach the total - // but instead the tracker will be marked as done by the defer statement - total := status.TotalSizeBytes + 1 - tracker = &progress.Tracker{Total: total, Units: progress.UnitsBytes} - defer tracker.MarkAsDone() - pw.AppendTracker(tracker) - } - - tracker.SetValue(status.ProcessedBytes) - } - } -} - // encodedResource returns a base64-encoded v1.UploadResource which wraps both the digest and fileName func encodeResource(fileName, digest string) (string, error) { if fileName == "" { From 6d15f14e7aba853ab17443353a818ccf3f537567 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Fri, 17 Mar 2023 10:52:00 +0100 Subject: [PATCH 2/3] refactor: extract render code to client Signed-off-by: Miguel Martinez Trivino --- app/cli/cmd/attestation_add.go | 2 +- app/cli/internal/action/artifact_download.go | 2 +- app/cli/internal/action/artifact_upload.go | 2 +- app/cli/internal/action/attestation_add.go | 6 +++--- app/cli/internal/casclient/grpc/downloader.go | 7 +++++-- app/cli/internal/casclient/grpc/uploader.go | 17 ++++++----------- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 36888a514..6f330c25e 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -57,7 +57,7 @@ func newAttestationAddCmd() *cobra.Command { }, RunE: func(cmd *cobra.Command, args []string) error { a := action.NewAttestationAdd( - &action.AttestationAddOpts{ActionsOpts: actionOpts, ArtifacsCASConn: artifactCASConn}, + &action.AttestationAddOpts{ActionsOpts: actionOpts, ArtifactsCASConn: artifactCASConn}, ) err := a.Run(name, value) diff --git a/app/cli/internal/action/artifact_download.go b/app/cli/internal/action/artifact_download.go index 95a502bde..506056be5 100644 --- a/app/cli/internal/action/artifact_download.go +++ b/app/cli/internal/action/artifact_download.go @@ -52,7 +52,7 @@ func (a *ArtifactDownload) Run(downloadPath, digest string) error { return fmt.Errorf("invalid digest: %w", err) } - client := casclient.NewDownloader(a.artifactsCASConn, casclient.WithProgressRender(true)) + client := casclient.NewDownloader(a.artifactsCASConn) ctx := context.Background() info, err := client.Describe(ctx, h.Hex) if err != nil { diff --git a/app/cli/internal/action/artifact_upload.go b/app/cli/internal/action/artifact_upload.go index 9feba4358..ffb3e1622 100644 --- a/app/cli/internal/action/artifact_upload.go +++ b/app/cli/internal/action/artifact_upload.go @@ -43,7 +43,7 @@ func NewArtifactUpload(opts *ArtifactUploadOpts) *ArtifactUpload { } func (a *ArtifactUpload) Run(filePath string) (*CASArtifact, error) { - client := casclient.NewUploader(a.artifactsCASConn, casclient.WithLogger(a.Logger), casclient.WithProgressRender(true)) + client := casclient.NewUploader(a.artifactsCASConn, casclient.WithLogger(a.Logger)) // render progress bar go renderOperationStatus(context.Background(), client.ProgressStatus, a.Logger) defer close(client.ProgressStatus) diff --git a/app/cli/internal/action/attestation_add.go b/app/cli/internal/action/attestation_add.go index 681481f81..bb3845837 100644 --- a/app/cli/internal/action/attestation_add.go +++ b/app/cli/internal/action/attestation_add.go @@ -25,7 +25,7 @@ import ( type AttestationAddOpts struct { *ActionsOpts - ArtifacsCASConn *grpc.ClientConn + ArtifactsCASConn *grpc.ClientConn } type AttestationAdd struct { @@ -39,9 +39,9 @@ func NewAttestationAdd(cfg *AttestationAddOpts) *AttestationAdd { ActionsOpts: cfg.ActionsOpts, c: crafter.NewCrafter( crafter.WithLogger(&cfg.Logger), - crafter.WithUploader(casclient.NewUploader(cfg.ArtifacsCASConn, casclient.WithLogger(cfg.Logger), casclient.WithProgressRender(true))), + crafter.WithUploader(casclient.NewUploader(cfg.ArtifactsCASConn, casclient.WithLogger(cfg.Logger))), ), - artifactsCASConn: cfg.ArtifacsCASConn, + artifactsCASConn: cfg.ArtifactsCASConn, } } diff --git a/app/cli/internal/casclient/grpc/downloader.go b/app/cli/internal/casclient/grpc/downloader.go index 949beb7d7..967e9e5ca 100644 --- a/app/cli/internal/casclient/grpc/downloader.go +++ b/app/cli/internal/casclient/grpc/downloader.go @@ -88,8 +88,11 @@ func (c *DownloaderClient) Download(ctx context.Context, w io.Writer, digest str TotalSizeBytes: totalBytes, ProcessedBytes: totalDownloaded, } - if c.renderProgress { - c.ProgressStatus <- latestStatus + select { + case c.ProgressStatus <- latestStatus: + // message sent + default: + c.logger.Debug().Msg("nobody listening to progress updates, dropping message") } } diff --git a/app/cli/internal/casclient/grpc/uploader.go b/app/cli/internal/casclient/grpc/uploader.go index 25a61998b..13b336d68 100644 --- a/app/cli/internal/casclient/grpc/uploader.go +++ b/app/cli/internal/casclient/grpc/uploader.go @@ -42,8 +42,6 @@ type casClient struct { logger zerolog.Logger // channel to send progress status to the go-routine that's rendering the progress bar ProgressStatus ProgressStatusChan - // wether to render progress bar - renderProgress bool } type UploaderClient struct { *casClient @@ -52,12 +50,6 @@ type UploaderClient struct { type ClientOpts func(u *casClient) -func WithProgressRender(b bool) ClientOpts { - return func(u *casClient) { - u.renderProgress = b - } -} - func WithLogger(l zerolog.Logger) ClientOpts { return func(u *casClient) { u.logger = l @@ -70,7 +62,7 @@ func NewUploader(conn *grpc.ClientConn, opts ...ClientOpts) *UploaderClient { client := &UploaderClient{ casClient: &casClient{ conn: conn, - ProgressStatus: make(chan *materials.UpDownStatus, 2), // Adding some buffer + ProgressStatus: make(chan *materials.UpDownStatus), logger: zerolog.Nop(), }, bufferSize: defaultUploadChunkSize, @@ -183,8 +175,11 @@ doUpload: Digest: hash.String(), TotalSizeBytes: info.Size(), ProcessedBytes: totalUploaded, } - if c.renderProgress { - c.ProgressStatus <- latestStatus + select { + case c.ProgressStatus <- latestStatus: + // message sent + default: + c.logger.Debug().Msg("nobody listening to progress updates, dropping message") } c.logger.Debug(). From 39e15287b863fa5524a63f7f24814c31b08c1d8c Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Fri, 17 Mar 2023 11:25:32 +0100 Subject: [PATCH 3/3] chore: move casclient Signed-off-by: Miguel Martinez Trivino --- app/artifact-cas/README.md | 6 +++++- app/cli/internal/action/artifact_download.go | 2 +- app/cli/internal/action/artifact_upload.go | 2 +- app/cli/internal/action/attestation_add.go | 2 +- internal/casclient/README.md | 5 +++++ .../casclient/grpc => internal/casclient}/downloader.go | 2 +- .../casclient/grpc => internal/casclient}/uploader.go | 2 +- .../casclient/grpc => internal/casclient}/uploader_test.go | 2 +- 8 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 internal/casclient/README.md rename {app/cli/internal/casclient/grpc => internal/casclient}/downloader.go (99%) rename {app/cli/internal/casclient/grpc => internal/casclient}/uploader.go (99%) rename {app/cli/internal/casclient/grpc => internal/casclient}/uploader_test.go (99%) diff --git a/app/artifact-cas/README.md b/app/artifact-cas/README.md index 6c783d7cc..41aa59e67 100644 --- a/app/artifact-cas/README.md +++ b/app/artifact-cas/README.md @@ -20,7 +20,7 @@ Its structure contains the following top to down layers. ## System Dependencies -The CAS proxy **has only one running dependency**. A secret storage backend to retrieve the OCI repository credentials. Currently we support both [Hashicorp Vault](https://www.vaultproject.io/) and [AWS Secret Manager](https://aws.amazon.com/secrets-manager/). +The CAS proxy **has only one running dependency**. A secret storage backend to retrieve the OCI repository credentials. Currently, we support both [Hashicorp Vault](https://www.vaultproject.io/) and [AWS Secret Manager](https://aws.amazon.com/secrets-manager/). This secret backend is used to download OCI repository credentials (repository path + key pair) during upload/downloads. This makes the Artifact CAS multi-tenant by default since the destination OCI backend gets selected at runtime. @@ -34,6 +34,10 @@ The token gets signed by the control plane with a private key and verified by th Note: there are plans to support [JWKS endpoints](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets) to enable easy rotation of credentials. +## Client + +The client code can be found [here](/internal/casclient/) + ## Runbook We use `make` for most development tasks. Run `make -C app/artifact-cas` to see a list of the available tasks. diff --git a/app/cli/internal/action/artifact_download.go b/app/cli/internal/action/artifact_download.go index 506056be5..687b01cc5 100644 --- a/app/cli/internal/action/artifact_download.go +++ b/app/cli/internal/action/artifact_download.go @@ -25,7 +25,7 @@ import ( "path" "time" - casclient "github.com/chainloop-dev/chainloop/app/cli/internal/casclient/grpc" + "github.com/chainloop-dev/chainloop/internal/casclient" "github.com/jedib0t/go-pretty/v6/progress" "google.golang.org/grpc" diff --git a/app/cli/internal/action/artifact_upload.go b/app/cli/internal/action/artifact_upload.go index ffb3e1622..97f5e8e01 100644 --- a/app/cli/internal/action/artifact_upload.go +++ b/app/cli/internal/action/artifact_upload.go @@ -19,7 +19,7 @@ import ( "context" "time" - casclient "github.com/chainloop-dev/chainloop/app/cli/internal/casclient/grpc" + "github.com/chainloop-dev/chainloop/internal/casclient" "github.com/jedib0t/go-pretty/v6/progress" "google.golang.org/grpc" ) diff --git a/app/cli/internal/action/attestation_add.go b/app/cli/internal/action/attestation_add.go index bb3845837..0531ea296 100644 --- a/app/cli/internal/action/attestation_add.go +++ b/app/cli/internal/action/attestation_add.go @@ -18,8 +18,8 @@ package action import ( "errors" - casclient "github.com/chainloop-dev/chainloop/app/cli/internal/casclient/grpc" "github.com/chainloop-dev/chainloop/internal/attestation/crafter" + "github.com/chainloop-dev/chainloop/internal/casclient" "google.golang.org/grpc" ) diff --git a/internal/casclient/README.md b/internal/casclient/README.md new file mode 100644 index 000000000..8d2e2359d --- /dev/null +++ b/internal/casclient/README.md @@ -0,0 +1,5 @@ +# Artifact Content Addressable Storage (CAS) Client code + +Client code used to talk to the [Artifact Storage Proxy](/app/artifact-cas/). + +It's a [bytestream gRPC client](https://pkg.go.dev/google.golang.org/api/transport/bytestream) that currently supports download by content digest (sha256) and upload methods. diff --git a/app/cli/internal/casclient/grpc/downloader.go b/internal/casclient/downloader.go similarity index 99% rename from app/cli/internal/casclient/grpc/downloader.go rename to internal/casclient/downloader.go index 967e9e5ca..b437d4ad9 100644 --- a/app/cli/internal/casclient/grpc/downloader.go +++ b/internal/casclient/downloader.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package grpc +package casclient import ( "context" diff --git a/app/cli/internal/casclient/grpc/uploader.go b/internal/casclient/uploader.go similarity index 99% rename from app/cli/internal/casclient/grpc/uploader.go rename to internal/casclient/uploader.go index 13b336d68..132f121f8 100644 --- a/app/cli/internal/casclient/grpc/uploader.go +++ b/internal/casclient/uploader.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package grpc +package casclient import ( "bytes" diff --git a/app/cli/internal/casclient/grpc/uploader_test.go b/internal/casclient/uploader_test.go similarity index 99% rename from app/cli/internal/casclient/grpc/uploader_test.go rename to internal/casclient/uploader_test.go index 0b2a9a6a8..8048bfef1 100644 --- a/app/cli/internal/casclient/grpc/uploader_test.go +++ b/internal/casclient/uploader_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package grpc +package casclient import ( "bytes"