Skip to content

Commit 2581087

Browse files
hamir-suspectskipi
authored andcommitted
feat: Log stats of artifact pull command
1 parent 8c077ce commit 2581087

File tree

6 files changed

+184
-12
lines changed

6 files changed

+184
-12
lines changed

cmd/pull.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"fmt"
45
errutil "github.com/semaphoreci/artifact/pkg/errors"
56
"github.com/semaphoreci/artifact/pkg/files"
67
"github.com/semaphoreci/artifact/pkg/hub"
@@ -18,7 +19,7 @@ artifact push. With artifact pull you can download them to the current directory
1819
to use them in a later phase, debug, or getting the results.`,
1920
}
2021

21-
func runPullForCategory(cmd *cobra.Command, args []string, resolver *files.PathResolver) (*files.ResolvedPath, error) {
22+
func runPullForCategory(cmd *cobra.Command, args []string, resolver *files.PathResolver) (*files.ResolvedPath, *storage.PullStats, error) {
2223
destinationOverride, err := cmd.Flags().GetString("destination")
2324
errutil.Check(err)
2425

@@ -49,7 +50,7 @@ func NewPullJobCmd() *cobra.Command {
4950
resolver, err := files.NewPathResolver(files.ResourceTypeJob, jobId)
5051
errutil.Check(err)
5152

52-
paths, err := runPullForCategory(cmd, args, resolver)
53+
paths, stats, err := runPullForCategory(cmd, args, resolver)
5354
if err != nil {
5455
log.Errorf("Error pulling artifact: %v\n", err)
5556
log.Error("Please check if the artifact you are trying to pull exists.\n")
@@ -60,6 +61,7 @@ func NewPullJobCmd() *cobra.Command {
6061
log.Info("Successfully pulled artifact for current job.\n")
6162
log.Infof("* Remote source: '%s'.\n", paths.Source)
6263
log.Infof("* Local destination: '%s'.\n", paths.Destination)
64+
log.Infof("Pulled %d files. Total of %s\n", stats.FileCount, formatBytes(stats.TotalSize))
6365
},
6466
}
6567

@@ -83,7 +85,7 @@ func NewPullWorkflowCmd() *cobra.Command {
8385
resolver, err := files.NewPathResolver(files.ResourceTypeWorkflow, workflowId)
8486
errutil.Check(err)
8587

86-
paths, err := runPullForCategory(cmd, args, resolver)
88+
paths, stats, err := runPullForCategory(cmd, args, resolver)
8789
if err != nil {
8890
log.Errorf("Error pulling artifact: %v\n", err)
8991
log.Error("Please check if the artifact you are trying to pull exists.\n")
@@ -94,6 +96,7 @@ func NewPullWorkflowCmd() *cobra.Command {
9496
log.Info("Successfully pulled artifact for current workflow.\n")
9597
log.Infof("* Remote source: '%s'.\n", paths.Source)
9698
log.Infof("* Local destination: '%s'.\n", paths.Destination)
99+
log.Infof("Pulled %d files. Total of %s\n", stats.FileCount, formatBytes(stats.TotalSize))
97100
},
98101
}
99102

@@ -117,7 +120,7 @@ func NewPullProjectCmd() *cobra.Command {
117120
resolver, err := files.NewPathResolver(files.ResourceTypeProject, projectId)
118121
errutil.Check(err)
119122

120-
paths, err := runPullForCategory(cmd, args, resolver)
123+
paths, stats, err := runPullForCategory(cmd, args, resolver)
121124
if err != nil {
122125
log.Errorf("Error pulling artifact: %v\n", err)
123126
log.Error("Please check if the artifact you are trying to pull exists.\n")
@@ -128,6 +131,7 @@ func NewPullProjectCmd() *cobra.Command {
128131
log.Info("Successfully pulled artifact for current project.\n")
129132
log.Infof("* Remote source: '%s'.\n", paths.Source)
130133
log.Infof("* Local destination: '%s'.\n", paths.Destination)
134+
log.Infof("Pulled %d files. Total of %s\n", stats.FileCount, formatBytes(stats.TotalSize))
131135
},
132136
}
133137

@@ -137,6 +141,20 @@ func NewPullProjectCmd() *cobra.Command {
137141
return cmd
138142
}
139143

144+
// formatBytes converts bytes to human readable format
145+
func formatBytes(bytes int64) string {
146+
const unit = 1024
147+
if bytes < unit {
148+
return fmt.Sprintf("%d B", bytes)
149+
}
150+
div, exp := int64(unit), 0
151+
for n := bytes / unit; n >= unit; n /= unit {
152+
div *= unit
153+
exp++
154+
}
155+
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
156+
}
157+
140158
func init() {
141159
rootCmd.AddCommand(pullCmd)
142160
pullCmd.AddCommand(NewPullJobCmd())

cmd/pull_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,27 @@ func assertFileDoesNotExist(t *testing.T, fileName string) {
172172
_, err := os.Stat(fileName)
173173
assert.True(t, os.IsNotExist(err))
174174
}
175+
176+
func Test__formatBytes(t *testing.T) {
177+
testCases := []struct {
178+
name string
179+
bytes int64
180+
expected string
181+
}{
182+
{"zero bytes", 0, "0 B"},
183+
{"small bytes", 512, "512 B"},
184+
{"exactly 1KB", 1024, "1.0 KB"},
185+
{"1.5KB", 1536, "1.5 KB"},
186+
{"exactly 1MB", 1024*1024, "1.0 MB"},
187+
{"498MB", 498*1024*1024, "498.0 MB"},
188+
{"1.2GB", int64(1288490188), "1.2 GB"},
189+
{"large size", 5*1024*1024*1024*1024, "5.0 TB"},
190+
}
191+
192+
for _, tc := range testCases {
193+
t.Run(tc.name, func(t *testing.T) {
194+
result := formatBytes(tc.bytes)
195+
assert.Equal(t, tc.expected, result)
196+
})
197+
}
198+
}

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ services:
77
tty: true
88
command: "sleep 0"
99
container_name: 'artifact-cli'
10+
environment:
11+
- HTTP_PROXY=localhost:3000
1012
volumes:
1113
- go-pkg-cache:/go
1214
- .:/app

pkg/hub/hub_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ func Test__GenerateSignedURL(t *testing.T) {
9999
assert.Contains(t, err.Error(), "request did not return a non-5xx response")
100100
}
101101
})
102+
103+
t.Run("Check if http_proxy enviroment variable is used", func(t *testing.T) {
104+
noOfCalls := 0
105+
mockArtifactHubServer := generateMockServer(&noOfCalls, 500, []byte("{}"))
106+
defer mockArtifactHubServer.Close()
107+
108+
generateSignedURLsHelper(mockArtifactHubServer.URL)
109+
110+
})
102111
}
103112

104113
func generateSignedURLsHelper(url string) (*GenerateSignedURLsResponse, error) {

pkg/storage/pull.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ type PullOptions struct {
1717
Force bool
1818
}
1919

20-
func Pull(hubClient *hub.Client, resolver *files.PathResolver, options PullOptions) (*files.ResolvedPath, error) {
20+
type PullStats struct {
21+
FileCount int
22+
TotalSize int64
23+
}
24+
25+
func Pull(hubClient *hub.Client, resolver *files.PathResolver, options PullOptions) (*files.ResolvedPath, *PullStats, error) {
2126
paths, err := resolver.Resolve(files.OperationPull, options.SourcePath, options.DestinationOverride)
2227
if err != nil {
23-
return nil, err
28+
return nil, nil, err
2429
}
2530

2631
log.Debug("Pulling...\n")
@@ -30,15 +35,20 @@ func Pull(hubClient *hub.Client, resolver *files.PathResolver, options PullOptio
3035

3136
response, err := hubClient.GenerateSignedURLs([]string{paths.Source}, hub.GenerateSignedURLsRequestPULL)
3237
if err != nil {
33-
return nil, err
38+
return nil, nil, err
3439
}
3540

3641
artifacts, err := buildArtifacts(response.Urls, paths, options.Force)
3742
if err != nil {
38-
return nil, err
43+
return nil, nil, err
44+
}
45+
46+
stats, err := doPull(options.Force, artifacts, response.Urls)
47+
if err != nil {
48+
return nil, nil, err
3949
}
4050

41-
return paths, doPull(options.Force, artifacts, response.Urls)
51+
return paths, stats, nil
4252
}
4353

4454
func buildArtifacts(signedURLs []*api.SignedURL, paths *files.ResolvedPath, force bool) ([]*api.Artifact, error) {
@@ -68,16 +78,23 @@ func buildArtifacts(signedURLs []*api.SignedURL, paths *files.ResolvedPath, forc
6878
return artifacts, nil
6979
}
7080

71-
func doPull(force bool, artifacts []*api.Artifact, signedURLs []*api.SignedURL) error {
81+
func doPull(force bool, artifacts []*api.Artifact, signedURLs []*api.SignedURL) (*PullStats, error) {
7282
client := newHTTPClient()
83+
stats := &PullStats{}
7384

7485
for _, artifact := range artifacts {
7586
for _, signedURL := range artifact.URLs {
7687
if err := signedURL.Follow(client, artifact); err != nil {
77-
return err
88+
return nil, err
89+
}
90+
91+
// Get file size after successful download
92+
if fileInfo, err := os.Stat(artifact.LocalPath); err == nil {
93+
stats.FileCount++
94+
stats.TotalSize += fileInfo.Size()
7895
}
7996
}
8097
}
8198

82-
return nil
99+
return stats, nil
83100
}

pkg/storage/pull_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package storage
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/semaphoreci/artifact/pkg/api"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func Test__doPull_Stats(t *testing.T) {
15+
// Create temporary directory for test files
16+
tempDir, err := ioutil.TempDir("", "pull_test")
17+
require.NoError(t, err)
18+
defer os.RemoveAll(tempDir)
19+
20+
// Create test artifacts with known sizes
21+
testFiles := []struct {
22+
name string
23+
content string
24+
size int64
25+
}{
26+
{"file1.txt", "hello world", 11},
27+
{"file2.txt", "test content here", 17},
28+
{"file3.txt", "a", 1},
29+
}
30+
31+
artifacts := []*api.Artifact{}
32+
for _, tf := range testFiles {
33+
localPath := filepath.Join(tempDir, tf.name)
34+
artifacts = append(artifacts, &api.Artifact{
35+
RemotePath: tf.name,
36+
LocalPath: localPath,
37+
URLs: []*api.SignedURL{}, // Empty for this test
38+
})
39+
40+
// Pre-create the files to simulate successful downloads
41+
err := ioutil.WriteFile(localPath, []byte(tf.content), 0644)
42+
require.NoError(t, err)
43+
}
44+
45+
// Mock the doPull function to skip actual HTTP calls
46+
// We'll test the stats collection logic by creating a modified version
47+
stats := &PullStats{}
48+
49+
// Simulate the stats collection that happens in doPull
50+
for _, artifact := range artifacts {
51+
if fileInfo, err := os.Stat(artifact.LocalPath); err == nil {
52+
stats.FileCount++
53+
stats.TotalSize += fileInfo.Size()
54+
}
55+
}
56+
57+
// Verify stats
58+
assert.Equal(t, 3, stats.FileCount)
59+
assert.Equal(t, int64(29), stats.TotalSize) // 11 + 17 + 1
60+
}
61+
62+
func Test__PullStats_EmptyDirectory(t *testing.T) {
63+
// Test with no files
64+
stats := &PullStats{}
65+
66+
assert.Equal(t, 0, stats.FileCount)
67+
assert.Equal(t, int64(0), stats.TotalSize)
68+
}
69+
70+
func Test__PullStats_LargeFiles(t *testing.T) {
71+
// Create temporary directory for test files
72+
tempDir, err := ioutil.TempDir("", "pull_large_test")
73+
require.NoError(t, err)
74+
defer os.RemoveAll(tempDir)
75+
76+
// Create a larger test file
77+
largeContent := make([]byte, 1024*1024) // 1MB
78+
for i := range largeContent {
79+
largeContent[i] = byte(i % 256)
80+
}
81+
82+
localPath := filepath.Join(tempDir, "large_file.bin")
83+
err = ioutil.WriteFile(localPath, largeContent, 0644)
84+
require.NoError(t, err)
85+
86+
artifact := &api.Artifact{
87+
RemotePath: "large_file.bin",
88+
LocalPath: localPath,
89+
URLs: []*api.SignedURL{},
90+
}
91+
92+
stats := &PullStats{}
93+
94+
// Simulate stats collection
95+
if fileInfo, err := os.Stat(artifact.LocalPath); err == nil {
96+
stats.FileCount++
97+
stats.TotalSize += fileInfo.Size()
98+
}
99+
100+
assert.Equal(t, 1, stats.FileCount)
101+
assert.Equal(t, int64(1024*1024), stats.TotalSize)
102+
}

0 commit comments

Comments
 (0)