This is an automated email from the ASF dual-hosted git repository.
DaanHoogland pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git
The following commit(s) were added to refs/heads/main by this push:
new 57e39ce fix: support non-interactive template uploads (#228)
57e39ce is described below
commit 57e39ce7dc52e9003db690d5a0d610f0bef5c178
Author: Dheeraj Bansal <[email protected]>
AuthorDate: Mon Oct 12 18:22:07 2026 +0530
fix: support non-interactive template uploads (#228)
---
cmd/fileupload.go | 39 +++++++++++++++-----
cmd/fileupload_test.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 129 insertions(+), 9 deletions(-)
diff --git a/cmd/fileupload.go b/cmd/fileupload.go
index 30bf986..ecd6a06 100644
--- a/cmd/fileupload.go
+++ b/cmd/fileupload.go
@@ -110,18 +110,33 @@ func UploadFiles(r *Request, api string, response
map[string]interface{}, validF
metadata, _ := params["metadata"].(string)
fmt.Println("Uploading files for", api, ":", validFiles)
- spinner := r.Config.StartSpinner(uploadingMessage)
+
+ var spinner *spinner.Spinner
+ if r.Config.HasShell {
+ spinner = r.Config.StartSpinner(uploadingMessage)
+ }
+
errored := 0
for i, filePath := range validFiles {
- spinner.Suffix = fmt.Sprintf(" uploading %d/%d %s...", i+1,
len(validFiles), filepath.Base(filePath))
+ if spinner != nil {
+ spinner.Suffix = fmt.Sprintf(" uploading %d/%d %s...",
i+1, len(validFiles), filepath.Base(filePath))
+ }
+
if err := uploadFile(i, len(validFiles), postURL, filePath,
signature, expires, metadata, spinner); err != nil {
- spinner.Stop()
+ if spinner != nil {
+ spinner.Stop()
+ }
+
fmt.Println("Error uploading", filePath, ":", err)
errored++
- spinner.Suffix = fmt.Sprintf(" %s", uploadingMessage)
- spinner.Start()
+
+ if spinner != nil {
+ spinner.Suffix = fmt.Sprintf(" %s",
uploadingMessage)
+ spinner.Start()
+ }
}
}
+
r.Config.StopSpinner(spinner)
if errored > 0 {
fmt.Printf("🙈 %d out of %d files failed to upload.\n", errored,
len(validFiles))
@@ -220,7 +235,9 @@ func uploadFile(index, count int, postURL, filePath,
signature, expires, metadat
f: tmp,
total: size,
update: func(pct int) {
- spn.Suffix = fmt.Sprintf(" [%d/%d] %s\t%s %d%%",
index+1, count, fileName, barArrow(pct), pct)
+ if spn != nil {
+ spn.Suffix = fmt.Sprintf(" [%d/%d] %s\t%s
%d%%", index+1, count, fileName, barArrow(pct), pct)
+ }
},
}
req.Body = pb
@@ -247,9 +264,13 @@ func uploadFile(index, count int, postURL, filePath,
signature, expires, metadat
return fmt.Errorf("[%d/%d] %s\tupload failed: %s", index+1,
count, fileName, string(b))
}
- spn.Stop()
+ if spn != nil {
+ spn.Stop()
+ }
fmt.Printf("[%d/%d] %s\t%s ✅\n", index+1, count, fileName,
barArrow(100))
- spn.Suffix = fmt.Sprintf(" %s", uploadingMessage)
- spn.Start()
+ if spn != nil {
+ spn.Suffix = fmt.Sprintf(" %s", uploadingMessage)
+ spn.Start()
+ }
return nil
}
diff --git a/cmd/fileupload_test.go b/cmd/fileupload_test.go
new file mode 100644
index 0000000..93b93c8
--- /dev/null
+++ b/cmd/fileupload_test.go
@@ -0,0 +1,99 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package cmd
+
+import (
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/apache/cloudstack-cloudmonkey/config"
+)
+
+func TestUploadFilesNonInteractive(t *testing.T) {
+ file, err := os.CreateTemp("", "cloudmonkey-upload-test-*")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(file.Name())
+
+ const fileContent = "test upload content"
+
+ if _, err := file.WriteString(fileContent); err != nil {
+ t.Fatal(err)
+ }
+ if err := file.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ var uploaded bool
+
+ server := httptest.NewServer(http.HandlerFunc(func(w
http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ t.Errorf("request method = %s, want POST", r.Method)
+ }
+
+ if err := r.ParseMultipartForm(10 << 20); err != nil {
+ t.Fatalf("failed to parse multipart form: %v", err)
+ }
+
+ uploadedFile, _, err := r.FormFile("file")
+ if err != nil {
+ t.Fatalf("failed to get uploaded file: %v", err)
+ }
+ defer uploadedFile.Close()
+
+ data, err := io.ReadAll(uploadedFile)
+ if err != nil {
+ t.Fatalf("failed to read uploaded file: %v", err)
+ }
+
+ if string(data) != fileContent {
+ t.Errorf("uploaded content = %q, want %q",
string(data), fileContent)
+ }
+
+ uploaded = true
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer server.Close()
+
+ cfg := &config.Config{
+ HasShell: false,
+ }
+
+ request := &Request{
+ Config: cfg,
+ }
+
+ response := map[string]interface{}{
+ "getuploadparams": map[string]interface{}{
+ "postURL": server.URL,
+ "metadata": "metadata",
+ "signature": "signature",
+ "expires": "expires",
+ },
+ }
+
+ UploadFiles(request, "getUploadParamsForTemplate", response,
[]string{file.Name()})
+
+ if !uploaded {
+ t.Fatal("file was not uploaded")
+ }
+}