You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ab...@apache.org on 2026/08/05 08:09:47 UTC

(cloudstack-go) branch main updated: Scope GetTemplateByName/GetIsoByName by-ID lookup to the zone (#87) (#153)

This is an automated email from the ASF dual-hosted git repository.

abh1sar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 70fb49a  Scope GetTemplateByName/GetIsoByName by-ID lookup to the zone (#87) (#153)
70fb49a is described below

commit 70fb49a01b425d6f7c6590980ca11c9e2b83f3d8
Author: James Peru Mmbono <jm...@gmail.com>
AuthorDate: Wed Aug 5 11:09:42 2026 +0300

    Scope GetTemplateByName/GetIsoByName by-ID lookup to the zone (#87) (#153)
    
    GetTemplateByName (and GetIsoByName) resolve the id with the zoneid
    constraint via GetTemplateID, then call GetTemplateByID/GetIsoByID WITHOUT
    the zone. A template/ISO registered in multiple zones lists one row per
    zone for the same UUID, so the helper fails with 'There is more then one
    result for Template UUID: ...!' (#87).
    
    Fix the generator to pass the zone into the by-ID lookup via the existing
    WithZone option for Template/Iso, and apply it to the two generated helpers.
    WithZone is a no-op on empty zoneid and uses the id directly for a UUID.
    Adds a regression test reproducing #87.
    
    Signed-off-by: James Peru <ja...@xcobean.com>
    Co-authored-by: James Peru <ja...@xcobean.com>
---
 cloudstack/ISOService.go                     |  2 +-
 cloudstack/TemplateService.go                |  2 +-
 generate/generate.go                         |  8 +++-
 test/GetTemplateByNameZoneRegression_test.go | 66 ++++++++++++++++++++++++++++
 4 files changed, 75 insertions(+), 3 deletions(-)

diff --git a/cloudstack/ISOService.go b/cloudstack/ISOService.go
index 5000bc5..d98f004 100644
--- a/cloudstack/ISOService.go
+++ b/cloudstack/ISOService.go
@@ -2300,7 +2300,7 @@ func (s *ISOService) GetIsoByName(name string, isofilter string, zoneid string,
 		return nil, count, err
 	}
 
-	r, count, err := s.GetIsoByID(id, opts...)
+	r, count, err := s.GetIsoByID(id, append(opts, WithZone(zoneid))...)
 	if err != nil {
 		return nil, count, err
 	}
diff --git a/cloudstack/TemplateService.go b/cloudstack/TemplateService.go
index e9f9071..dd7f68e 100644
--- a/cloudstack/TemplateService.go
+++ b/cloudstack/TemplateService.go
@@ -2844,7 +2844,7 @@ func (s *TemplateService) GetTemplateByName(name string, templatefilter string,
 		return nil, count, err
 	}
 
-	r, count, err := s.GetTemplateByID(id, templatefilter, opts...)
+	r, count, err := s.GetTemplateByID(id, templatefilter, append(opts, WithZone(zoneid))...)
 	if err != nil {
 		return nil, count, err
 	}
diff --git a/generate/generate.go b/generate/generate.go
index 7fbeb79..c967967 100644
--- a/generate/generate.go
+++ b/generate/generate.go
@@ -1671,7 +1671,13 @@ func (s *service) generateHelperFuncs(a *API) {
 						p("%s, ", s.parseParamName(ap.Name))
 					}
 				}
-				pn("opts...)")
+				// Constrain the by-ID lookup to the same zone; otherwise a Template/ISO
+				// registered in multiple zones returns multiple rows for one UUID (#87).
+				if parseSingular(ln) == "Template" || parseSingular(ln) == "Iso" {
+					pn("append(opts, WithZone(zoneid))...)")
+				} else {
+					pn("opts...)")
+				}
 				pn("  if err != nil {")
 				pn("    return nil, count, err")
 				pn("  }")
diff --git a/test/GetTemplateByNameZoneRegression_test.go b/test/GetTemplateByNameZoneRegression_test.go
new file mode 100644
index 0000000..f579f42
--- /dev/null
+++ b/test/GetTemplateByNameZoneRegression_test.go
@@ -0,0 +1,66 @@
+//
+// 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 test
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/apache/cloudstack-go/v2/cloudstack"
+)
+
+// Regression test for issue #87: GetTemplateByName must carry the zoneid
+// constraint into the by-ID lookup. Otherwise a template registered in multiple
+// zones lists multiple rows for one UUID and the helper fails with
+// "There is more then one result for Template UUID: ...!".
+func TestGetTemplateByNameScopesByIDLookupToZone(t *testing.T) {
+	const tmplID = "06145677-058a-456a-89a0-af4afd6fffcf"
+	const zoneID = "11111111-2222-3333-4444-555555555555"
+
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		q := r.URL.Query()
+		w.Header().Set("Content-Type", "application/json")
+		if q.Get("id") != "" {
+			// by-ID lookup: a multi-zone template returns one row PER zone unless
+			// the zoneid is passed through (the bug). With the fix, zoneid is set.
+			if q.Get("zoneid") != "" {
+				fmt.Fprintf(w, `{"listtemplatesresponse":{"count":1,"template":[{"id":%q,"name":"mytmpl","zoneid":"zoneA"}]}}`, tmplID)
+			} else {
+				fmt.Fprintf(w, `{"listtemplatesresponse":{"count":2,"template":[{"id":%q,"name":"mytmpl","zoneid":"zoneA"},{"id":%q,"name":"mytmpl","zoneid":"zoneB"}]}}`, tmplID, tmplID)
+			}
+			return
+		}
+		// by-name lookup (GetTemplateID) resolves to a single template id
+		fmt.Fprintf(w, `{"listtemplatesresponse":{"count":1,"template":[{"id":%q,"name":"mytmpl"}]}}`, tmplID)
+	}))
+	defer server.Close()
+
+	client := cloudstack.NewClient(server.URL, "APIKEY", "SECRETKEY", true)
+
+	tmpl, _, err := client.Template.GetTemplateByName("mytmpl", "all", zoneID)
+	if err != nil {
+		t.Fatalf("expected zone-scoped GetTemplateByName to succeed, got error: %v", err)
+	}
+	if tmpl == nil || tmpl.Id != tmplID {
+		t.Fatalf("expected template %s, got %+v", tmplID, tmpl)
+	}
+}