Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cli/cmd/download_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ type DownloadPackageOpts struct {
Version string
Hash string
Filename string
Quiet bool
}

func (c *DownloadPackageCmd) RunE(_ *cobra.Command, args []string) error {
if c.Opts.Hash != "" {
if c.Opts.Hash != "" {
log.Printf("Downloading package '%s' with hash '%s'\n", c.Opts.Version, c.Opts.Hash)
} else {
log.Printf("Downloading package '%s'\n", c.Opts.Version)
Expand Down Expand Up @@ -66,6 +67,7 @@ func AddDownloadPackageCmd(download *cobra.Command, opts GlobalOptions) {
pkg.cmd.Flags().StringVarP(&pkg.Opts.Version, "version", "V", "", "Codesphere version to download")
pkg.cmd.Flags().StringVarP(&pkg.Opts.Hash, "hash", "H", "", "Hash of the version to download if multiple builds exist for the same version")
pkg.cmd.Flags().StringVarP(&pkg.Opts.Filename, "file", "f", "installer.tar.gz", "Specify artifact to download")
pkg.cmd.Flags().BoolVarP(&pkg.Opts.Quiet, "quiet", "q", false, "Suppress progress output during download")
download.AddCommand(pkg.cmd)

pkg.cmd.RunE = pkg.RunE
Expand All @@ -83,7 +85,7 @@ func (c *DownloadPackageCmd) DownloadBuild(p portal.Portal, build portal.Build,
}
defer func() { _ = out.Close() }()

err = p.DownloadBuildArtifact("codesphere", download, out)
err = p.DownloadBuildArtifact("codesphere", download, out, c.Opts.Quiet)
if err != nil {
return fmt.Errorf("failed to download build: %w", err)
}
Expand Down
3 changes: 2 additions & 1 deletion cli/cmd/download_package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var _ = Describe("ListPackages", func() {
Opts: cmd.DownloadPackageOpts{
Version: version,
Filename: filename,
Quiet: false,
},
FileWriter: mockFileWriter,
}
Expand Down Expand Up @@ -63,7 +64,7 @@ var _ = Describe("ListPackages", func() {

fakeFile := os.NewFile(uintptr(0), filename)
mockFileWriter.EXPECT().Create(version+"-"+filename).Return(fakeFile, nil)
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything).Return(nil)
mockPortal.EXPECT().DownloadBuildArtifact(portal.CodesphereProduct, expectedBuildToDownload, mock.Anything, false).Return(nil)
err := c.DownloadBuild(mockPortal, build, filename)
Expect(err).NotTo(HaveOccurred())
})
Expand Down
12 changes: 8 additions & 4 deletions internal/portal/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
type Portal interface {
ListBuilds(product Product) (availablePackages Builds, err error)
GetBuild(product Product, version string, hash string) (Build, error)
DownloadBuildArtifact(product Product, build Build, file io.Writer) error
DownloadBuildArtifact(product Product, build Build, file io.Writer, quiet bool) error
RegisterAPIKey(owner string, organization string, role string, expiresAt time.Time) error
RevokeAPIKey(key string) error
UpdateAPIKey(key string, expiresAt time.Time) error
Expand Down Expand Up @@ -176,7 +176,7 @@ func (c *PortalClient) GetBuild(product Product, version string, hash string) (B
return matchingPackages[len(matchingPackages)-1], nil
}

func (c *PortalClient) DownloadBuildArtifact(product Product, build Build, file io.Writer) error {
func (c *PortalClient) DownloadBuildArtifact(product Product, build Build, file io.Writer, quiet bool) error {
reqBody, err := json.Marshal(build)
if err != nil {
return fmt.Errorf("failed to generate request body: %w", err)
Expand All @@ -188,8 +188,12 @@ func (c *PortalClient) DownloadBuildArtifact(product Product, build Build, file
}
defer func() { _ = resp.Body.Close() }()

// Create a WriteCounter to wrap the output file and report progress.
counter := NewWriteCounter(file)
// Create a WriteCounter to wrap the output file and report progress, unless quiet is requested.
// Default behavior: report progress. Quiet callers should pass true for quiet.
counter := file
if !quiet {
counter = NewWriteCounter(file)
}

_, err = io.Copy(counter, resp.Body)
if err != nil {
Expand Down
28 changes: 27 additions & 1 deletion internal/portal/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"io"
"log"
"net/http"
"net/url"
"time"
Expand Down Expand Up @@ -172,6 +173,7 @@ var _ = Describe("PortalClient", func() {
build portal.Build
downloadResponse string
)

BeforeEach(func() {
buildDate, _ := time.Parse("2006-01-02", "2025-05-01")

Expand All @@ -193,11 +195,35 @@ var _ = Describe("PortalClient", func() {

It("downloads the build", func() {
fakeWriter := NewFakeWriter()
err := client.DownloadBuildArtifact(product, build, fakeWriter)
err := client.DownloadBuildArtifact(product, build, fakeWriter, false)
Expect(err).NotTo(HaveOccurred())
Expect(fakeWriter.String()).To(Equal(downloadResponse))
Expect(getUrl.String()).To(Equal("fake-portal.com/packages/codesphere/download"))
})

It("emits progress logs when not quiet", func() {
var logBuf bytes.Buffer
prev := log.Writer()
log.SetOutput(&logBuf)
defer log.SetOutput(prev)

fakeWriter := NewFakeWriter()
err := client.DownloadBuildArtifact(product, build, fakeWriter, false)
Expect(err).NotTo(HaveOccurred())
Expect(logBuf.String()).To(ContainSubstring("Downloading..."))
})

It("does not emit progress logs when quiet", func() {
var logBuf bytes.Buffer
prev := log.Writer()
log.SetOutput(&logBuf)
defer log.SetOutput(prev)

fakeWriter := NewFakeWriter()
err := client.DownloadBuildArtifact(product, build, fakeWriter, true)
Expect(err).NotTo(HaveOccurred())
Expect(logBuf.String()).NotTo(ContainSubstring("Downloading..."))
})
})

Describe("GetLatestOmsBuild", func() {
Expand Down
19 changes: 10 additions & 9 deletions internal/portal/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions internal/portal/write_counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ type WriteCounter struct {
// NewWriteCounter creates a new WriteCounter.
func NewWriteCounter(writer io.Writer) *WriteCounter {
return &WriteCounter{
Writer: writer,
LastUpdate: time.Now(), // Initialize last update time
Writer: writer,
// Initialize to zero so the first Write triggers an immediate log
LastUpdate: time.Time{},
}
}

Expand Down
37 changes: 37 additions & 0 deletions internal/portal/write_counter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Codesphere Inc.
// SPDX-License-Identifier: Apache-2.0

package portal_test

import (
"bytes"
"log"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/codesphere-cloud/oms/internal/portal"
)

var _ = Describe("WriteCounter", func() {
It("emits progress logs on write", func() {
// capture log output
var logBuf bytes.Buffer
prev := log.Writer()
log.SetOutput(&logBuf)
defer log.SetOutput(prev)

var underlying bytes.Buffer
wc := portal.NewWriteCounter(&underlying)

// force an update by setting LastUpdate sufficiently in the past
wc.LastUpdate = time.Now().Add(-time.Second)

_, err := wc.Write([]byte("hello world"))
Expect(err).NotTo(HaveOccurred())

out := logBuf.String()
Expect(out).NotTo(BeEmpty())
})
})