Fix service_offering lookup by filtering with zone parameter (#325)

* Fix service_offering lookup by filtering with zone parameter

The cloudstack_instance resource was not filtering service_offering lookups by zone,
which could cause failures when multiple service offerings share the same name but
are restricted to specific zones.

This change introduces a new retrieveServiceOfferingID function that filters
service offerings by both name and zone ID, similar to how templates are looked up.

Fixes: #260
diff --git a/cloudstack/resource_cloudstack_instance.go b/cloudstack/resource_cloudstack_instance.go
index 026ec4c..afa5093 100644
--- a/cloudstack/resource_cloudstack_instance.go
+++ b/cloudstack/resource_cloudstack_instance.go
@@ -264,14 +264,14 @@
 
 	cs := meta.(*cloudstack.CloudStackClient)
 
-	// Retrieve the service_offering ID
-	serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
+	// Retrieve the zone ID first (needed for service_offering lookup)
+	zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
 	if e != nil {
 		return e.Error()
 	}
 
-	// Retrieve the zone ID
-	zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
+	// Retrieve the service_offering ID (filtered by zone)
+	serviceofferingid, e := retrieveServiceOfferingID(cs, zoneid, d.Get("service_offering").(string))
 	if e != nil {
 		return e.Error()
 	}
@@ -727,8 +727,14 @@
 		if d.HasChange("service_offering") {
 			log.Printf("[DEBUG] Service offering changed for %s, starting update", name)
 
-			// Retrieve the service_offering ID
-			serviceofferingid, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
+			// Retrieve the zone ID first (needed for service_offering lookup)
+			zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
+			if e != nil {
+				return e.Error()
+			}
+
+			// Retrieve the service_offering ID (filtered by zone)
+			serviceofferingid, e := retrieveServiceOfferingID(cs, zoneid, d.Get("service_offering").(string))
 			if e != nil {
 				return e.Error()
 			}
diff --git a/cloudstack/resources.go b/cloudstack/resources.go
index 5a75b77..c08f202 100644
--- a/cloudstack/resources.go
+++ b/cloudstack/resources.go
@@ -128,6 +128,31 @@
 	return id, nil
 }
 
+func retrieveServiceOfferingID(cs *cloudstack.CloudStackClient, zoneid, value string) (id string, e *retrieveError) {
+	// If the supplied value isn't a ID, try to retrieve the ID ourselves
+	if cloudstack.IsID(value) {
+		return value, nil
+	}
+
+	log.Printf("[DEBUG] Retrieving ID of service offering: %s in zone: %s", value, zoneid)
+
+	// List service offerings filtered by zone and name to handle zone-specific offerings
+	p := cs.ServiceOffering.NewListServiceOfferingsParams()
+	p.SetName(value)
+	p.SetZoneid(zoneid)
+	l, err := cs.ServiceOffering.ListServiceOfferings(p)
+	if err != nil {
+		return "", &retrieveError{name: "service_offering", value: value, err: err}
+	}
+
+	if l.Count != 1 {
+		err := fmt.Errorf("Found %d service offering(s) with name %s in zone %s", l.Count, value, zoneid)
+		return "", &retrieveError{name: "service_offering", value: value, err: err}
+	}
+
+	return l.ServiceOfferings[0].Id, nil
+}
+
 // RetryFunc is the function retried n times
 type RetryFunc func() (interface{}, error)