Skip to content

Commit ea7f30c

Browse files
committed
changes for adding argument
Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 0562607 commit ea7f30c

File tree

4 files changed

+194
-68
lines changed

4 files changed

+194
-68
lines changed

cmd/api.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"errors"
2222
"fmt"
2323
"strings"
24+
25+
"github.com/apache/cloudstack-cloudmonkey/config"
2426
)
2527

2628
var apiCommand *Command
@@ -46,11 +48,23 @@ func init() {
4648
apiArgs = r.Args[2:]
4749
}
4850

49-
for _, arg := range r.Args {
51+
var uploadFiles []string
52+
53+
for _, arg := range apiArgs {
5054
if arg == "-h" {
5155
r.Args[0] = apiName
5256
return helpCommand.Handle(r)
5357
}
58+
if strings.HasPrefix(arg, config.FilePathArg) && config.IsFileUploadAPI(apiName) {
59+
var err error
60+
uploadFiles, err = ValidateAndGetFileList(arg[len(config.FilePathArg):])
61+
if err != nil {
62+
return err
63+
}
64+
if len(uploadFiles) == 0 {
65+
return errors.New("no valid files to upload")
66+
}
67+
}
5468
}
5569

5670
api := r.Config.GetCache()[apiName]
@@ -111,7 +125,11 @@ func init() {
111125

112126
if len(response) > 0 {
113127
printResult(r.Config.Core.Output, response, filterKeys, excludeKeys)
114-
PromptAndUploadFileIfNeeded(r, api.Name, response)
128+
if len(uploadFiles) > 0 {
129+
UploadFiles(r, api.Name, response, uploadFiles)
130+
} else {
131+
PromptAndUploadFilesIfNeeded(r, api.Name, response)
132+
}
115133
}
116134
return nil
117135
},

cmd/fileupload.go

Lines changed: 126 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package cmd
1919

2020
import (
21-
"bytes"
2221
"fmt"
2322
"io"
2423
"mime/multipart"
@@ -27,28 +26,20 @@ import (
2726
"path/filepath"
2827
"reflect"
2928
"strings"
29+
"time"
3030

31+
"github.com/apache/cloudstack-cloudmonkey/config"
3132
"github.com/briandowns/spinner"
3233
)
3334

3435
const (
35-
uploadingMessage = "Uploading files, please wait..."
36+
uploadingMessage = "Uploading files, please wait..."
37+
progressCharCount = 24
3638
)
3739

38-
// PromptAndUploadFileIfNeeded prompts the user to provide file paths for upload and the API is getUploadParamsFor*
39-
func PromptAndUploadFileIfNeeded(r *Request, api string, response map[string]interface{}) {
40-
if !r.Config.HasShell {
41-
return
42-
}
43-
apiName := strings.ToLower(api)
44-
if apiName != "getuploadparamsforiso" &&
45-
apiName != "getuploadparamsforvolume" &&
46-
apiName != "getuploadparamsfotemplate" {
47-
return
48-
}
49-
fmt.Print("Enter path of the file(s) to upload (comma-separated): ")
50-
var filePaths string
51-
fmt.Scanln(&filePaths)
40+
// ValidateAndGetFileList parses a comma-separated string of file paths, trims them,
41+
// checks for existence, and returns a slice of valid file paths or an error if any are missing.
42+
func ValidateAndGetFileList(filePaths string) ([]string, error) {
5243
filePathsList := strings.FieldsFunc(filePaths, func(r rune) bool { return r == ',' })
5344

5445
var missingFiles []string
@@ -65,13 +56,41 @@ func PromptAndUploadFileIfNeeded(r *Request, api string, response map[string]int
6556
}
6657
}
6758
if len(missingFiles) > 0 {
68-
fmt.Println("File(s) do not exist or are not accessible:", strings.Join(missingFiles, ", "))
59+
return nil, fmt.Errorf("file(s) do not exist or are not accessible: %s", strings.Join(missingFiles, ", "))
60+
}
61+
return validFiles, nil
62+
}
63+
64+
// PromptAndUploadFilesIfNeeded prompts the user to provide file paths for upload and the API is getUploadParamsFor*
65+
func PromptAndUploadFilesIfNeeded(r *Request, api string, response map[string]interface{}) {
66+
if !r.Config.HasShell {
67+
return
68+
}
69+
apiName := strings.ToLower(api)
70+
if !config.IsFileUploadAPI(apiName) {
71+
return
72+
}
73+
fmt.Print("Enter path of the file(s) to upload (comma-separated), leave empty to skip: ")
74+
var filePaths string
75+
fmt.Scanln(&filePaths)
76+
if filePaths == "" {
77+
return
78+
}
79+
validFiles, err := ValidateAndGetFileList(filePaths)
80+
if err != nil {
81+
fmt.Println(err)
6982
return
7083
}
7184
if len(validFiles) == 0 {
7285
fmt.Println("No valid files to upload.")
7386
return
7487
}
88+
UploadFiles(r, api, response, validFiles)
89+
}
90+
91+
// UploadFiles uploads files to a remote server using parameters from the API response.
92+
// Shows progress for each file and reports any failures.
93+
func UploadFiles(r *Request, api string, response map[string]interface{}, validFiles []string) {
7594
paramsRaw, ok := response["getuploadparams"]
7695
if !ok || reflect.TypeOf(paramsRaw).Kind() != reflect.Map {
7796
fmt.Println("Invalid response format for getuploadparams.")
@@ -85,7 +104,6 @@ func PromptAndUploadFileIfNeeded(r *Request, api string, response map[string]int
85104
return
86105
}
87106
}
88-
89107
postURL, _ := params["postURL"].(string)
90108
signature, _ := params["signature"].(string)
91109
expires, _ := params["expires"].(string)
@@ -96,7 +114,7 @@ func PromptAndUploadFileIfNeeded(r *Request, api string, response map[string]int
96114
errored := 0
97115
for i, filePath := range validFiles {
98116
spinner.Suffix = fmt.Sprintf(" uploading %d/%d %s...", i+1, len(validFiles), filepath.Base(filePath))
99-
if err := uploadFile(postURL, filePath, signature, expires, metadata, spinner); err != nil {
117+
if err := uploadFile(i, len(validFiles), postURL, filePath, signature, expires, metadata, spinner); err != nil {
100118
spinner.Stop()
101119
fmt.Println("Error uploading", filePath, ":", err)
102120
errored++
@@ -112,79 +130,126 @@ func PromptAndUploadFileIfNeeded(r *Request, api string, response map[string]int
112130
}
113131
}
114132

115-
type progressReader struct {
116-
file *os.File
117-
total int64
118-
read int64
119-
updateSuffix func(percent int)
133+
// progressReader streams file data and updates progress as bytes are read.
134+
type progressBody struct {
135+
f *os.File
136+
read int64
137+
total int64
138+
update func(int)
120139
}
121140

122-
func (pr *progressReader) Read(p []byte) (int, error) {
123-
n, err := pr.file.Read(p)
141+
func (pb *progressBody) Read(p []byte) (int, error) {
142+
n, err := pb.f.Read(p)
124143
if n > 0 {
125-
pr.read += int64(n)
126-
percent := int(float64(pr.read) / float64(pr.total) * 100)
127-
pr.updateSuffix(percent)
144+
pb.read += int64(n)
145+
pct := int(float64(pb.read) * 100 / float64(pb.total))
146+
pb.update(pct)
128147
}
129148
return n, err
130149
}
150+
func (pb *progressBody) Close() error { return pb.f.Close() }
151+
152+
func barArrow(pct int) string {
153+
width := progressCharCount
154+
if pct < 0 {
155+
pct = 0
156+
}
157+
if pct > 100 {
158+
pct = 100
159+
}
160+
pos := (pct * width) / 100
161+
// 100%: full bar, no head
162+
if pos >= width {
163+
return fmt.Sprintf("[%s]",
164+
strings.Repeat("=", width))
165+
}
166+
left := strings.Repeat("=", pos) + ">"
167+
right := strings.Repeat(" ", width-pos-1)
168+
169+
return fmt.Sprintf("[%s%s]", left, right)
170+
}
131171

132-
// uploadFile uploads a single file to the given postURL with the required headers.
133-
func uploadFile(postURL, filePath, signature, expires, metadata string, spinner *spinner.Spinner) error {
134-
originalSuffix := spinner.Suffix
135-
file, err := os.Open(filePath)
172+
// uploadFile streams a large file to the server with progress updates.
173+
func uploadFile(index, count int, postURL, filePath, signature, expires, metadata string, spn *spinner.Spinner) error {
174+
fileName := filepath.Base(filePath)
175+
in, err := os.Open(filePath)
136176
if err != nil {
137177
return err
138178
}
139-
defer file.Close()
140-
141-
fileInfo, err := file.Stat()
179+
defer in.Close()
180+
_, err = in.Stat()
142181
if err != nil {
143182
return err
144183
}
145-
146-
var body bytes.Buffer
147-
writer := multipart.NewWriter(&body)
148-
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
184+
tmp, err := os.CreateTemp("", "multipart-body-*.tmp")
149185
if err != nil {
150186
return err
151187
}
152-
153-
pr := &progressReader{
154-
file: file,
155-
total: fileInfo.Size(),
156-
updateSuffix: func(percent int) {
157-
spinner.Suffix = fmt.Sprintf(" %s (%d%%)", originalSuffix, percent)
158-
},
188+
defer func() {
189+
tmp.Close()
190+
os.Remove(tmp.Name())
191+
}()
192+
mw := multipart.NewWriter(tmp)
193+
part, err := mw.CreateFormFile("file", filepath.Base(filePath))
194+
if err != nil {
195+
return err
159196
}
160-
if _, err := io.Copy(part, pr); err != nil {
197+
if _, err := io.Copy(part, in); err != nil {
161198
return err
162199
}
163-
writer.Close()
164-
165-
req, err := http.NewRequest("POST", postURL, &body)
200+
if err := mw.Close(); err != nil {
201+
return err
202+
}
203+
size, err := tmp.Seek(0, io.SeekEnd)
166204
if err != nil {
167205
return err
168206
}
169-
req.Header.Set("Content-Type", writer.FormDataContentType())
207+
if _, err := tmp.Seek(0, io.SeekStart); err != nil {
208+
return err
209+
}
210+
req, err := http.NewRequest("POST", postURL, nil)
211+
if err != nil {
212+
return err
213+
}
214+
req.Header.Set("Content-Type", mw.FormDataContentType())
170215
req.Header.Set("x-signature", signature)
171216
req.Header.Set("x-expires", expires)
172217
req.Header.Set("x-metadata", metadata)
173-
174-
client := &http.Client{}
218+
req.ContentLength = size
219+
pb := &progressBody{
220+
f: tmp,
221+
total: size,
222+
update: func(pct int) {
223+
spn.Suffix = fmt.Sprintf(" [%d/%d] %s\t%s %d%%", index+1, count, fileName, barArrow(pct), pct)
224+
},
225+
}
226+
req.Body = pb
227+
req.GetBody = func() (io.ReadCloser, error) {
228+
f, err := os.Open(tmp.Name())
229+
if err != nil {
230+
return nil, err
231+
}
232+
return f, nil
233+
}
234+
client := &http.Client{
235+
Timeout: 24 * time.Hour,
236+
Transport: &http.Transport{
237+
ExpectContinueTimeout: 0,
238+
},
239+
}
175240
resp, err := client.Do(req)
176241
if err != nil {
177242
return err
178243
}
179244
defer resp.Body.Close()
180-
181245
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
182-
respBody, _ := io.ReadAll(resp.Body)
183-
return fmt.Errorf("upload failed: %s", string(respBody))
246+
b, _ := io.ReadAll(resp.Body)
247+
return fmt.Errorf("[%d/%d] %s\tupload failed: %s", index+1, count, fileName, string(b))
184248
}
185-
spinner.Stop()
186-
fmt.Println("Upload successful for:", filePath)
187-
spinner.Suffix = fmt.Sprintf(" %s", uploadingMessage)
188-
spinner.Start()
249+
250+
spn.Stop()
251+
fmt.Printf("[%d/%d] %s\t%s ✅\n", index+1, count, fileName, barArrow(100))
252+
spn.Suffix = fmt.Sprintf(" %s", uploadingMessage)
253+
spn.Start()
189254
return nil
190255
}

cmd/network.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,21 @@ func pollAsyncJob(r *Request, jobID string) (map[string]interface{}, error) {
278278
func NewAPIRequest(r *Request, api string, args []string, isAsync bool) (map[string]interface{}, error) {
279279
params := make(url.Values)
280280
params.Add("command", api)
281+
apiData := r.Config.GetCache()[api]
281282
for _, arg := range args {
283+
if apiData != nil {
284+
skip := false
285+
for _, fakeArg := range apiData.FakeArgs {
286+
if strings.HasPrefix(arg, fakeArg) {
287+
skip = true
288+
break
289+
}
290+
}
291+
if skip {
292+
continue
293+
}
294+
295+
}
282296
parts := strings.SplitN(arg, "=", 2)
283297
if len(parts) == 2 {
284298
key := parts[0]

0 commit comments

Comments
 (0)