diff --git a/client/incus.go b/client/incus.go index 77b7efd45ca..f8b82985350 100644 --- a/client/incus.go +++ b/client/incus.go @@ -197,7 +197,7 @@ func (r *ProtocolIncus) RawWebsocket(path string) (*websocket.Conn, error) { // RawOperation allows direct querying of an Incus API endpoint returning // background operations. func (r *ProtocolIncus) RawOperation(method string, path string, data any, ETag string) (Operation, string, error) { - return r.queryOperation(method, path, data, ETag) + return r.queryOperation(method, path, data, ETag, true) } // Internal functions. @@ -363,12 +363,14 @@ func (r *ProtocolIncus) queryStruct(method string, path string, data any, ETag s } // queryOperation sends a query to the Incus server and then converts the response metadata into an Operation object. -// It sets up an early event listener, performs the query, processes the response, and manages the lifecycle of the event listener. -func (r *ProtocolIncus) queryOperation(method string, path string, data any, ETag string) (Operation, string, error) { - // Attempt to setup an early event listener - listener, err := r.GetEvents() - if err != nil { - listener = nil +// If useEventListener is true it will set up an early event listener and manage its lifecycle. +// If useEventListener is false, it will not set up an event listener and calls to Operation.Wait will use the operations API instead. +// In this case the returned Operation will error if the user calls Operation.AddHandler or Operation.RemoveHandler. +func (r *ProtocolIncus) queryOperation(method string, path string, data any, ETag string, useEventListener bool) (Operation, string, error) { + // Attempt to setup an early event listener if requested. + var listener *EventListener + if useEventListener { + listener, _ = r.GetEvents() } // Send the query @@ -393,10 +395,11 @@ func (r *ProtocolIncus) queryOperation(method string, path string, data any, ETa // Setup an Operation wrapper op := operation{ - Operation: *respOperation, - r: r, - listener: listener, - chActive: make(chan bool), + Operation: *respOperation, + r: r, + listener: listener, + chActive: make(chan bool), + skipListener: !useEventListener, } // Log the data diff --git a/client/incus_certificates.go b/client/incus_certificates.go index f90e3e52641..4a1dc00c0b4 100644 --- a/client/incus_certificates.go +++ b/client/incus_certificates.go @@ -97,7 +97,7 @@ func (r *ProtocolIncus) CreateCertificateToken(certificate api.CertificatesPost) } // Send the request - op, _, err := r.queryOperation("POST", "/certificates", certificate, "") + op, _, err := r.queryOperation("POST", "/certificates", certificate, "", true) if err != nil { return nil, err } diff --git a/client/incus_cluster.go b/client/incus_cluster.go index 03d6f3ebe4b..bda17517315 100644 --- a/client/incus_cluster.go +++ b/client/incus_cluster.go @@ -33,7 +33,7 @@ func (r *ProtocolIncus) UpdateCluster(cluster api.ClusterPut, ETag string) (Oper } } - op, _, err := r.queryOperation("PUT", "/cluster", cluster, "") + op, _, err := r.queryOperation("PUT", "/cluster", cluster, "", true) if err != nil { return nil, err } @@ -150,7 +150,7 @@ func (r *ProtocolIncus) CreateClusterMember(member api.ClusterMembersPost) (Oper return nil, fmt.Errorf("The server is missing the required \"clustering_join_token\" API extension") } - op, _, err := r.queryOperation("POST", "/cluster/members", member, "") + op, _, err := r.queryOperation("POST", "/cluster/members", member, "", true) if err != nil { return nil, err } @@ -195,7 +195,7 @@ func (r *ProtocolIncus) UpdateClusterMemberState(name string, state api.ClusterM return nil, fmt.Errorf("The server is missing the required \"clustering_evacuation\" API extension") } - op, _, err := r.queryOperation("POST", fmt.Sprintf("/cluster/members/%s/state", name), state, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/cluster/members/%s/state", name), state, "", true) if err != nil { return nil, err } diff --git a/client/incus_images.go b/client/incus_images.go index d12747d2e1b..c94bb84b17d 100644 --- a/client/incus_images.go +++ b/client/incus_images.go @@ -394,7 +394,7 @@ func (r *ProtocolIncus) CreateImage(image api.ImagesPost, args *ImageCreateArgs) // Send the JSON based request if args == nil { - op, _, err := r.queryOperation("POST", "/images", image, "") + op, _, err := r.queryOperation("POST", "/images", image, "", true) if err != nil { return nil, err } @@ -917,7 +917,7 @@ func (r *ProtocolIncus) UpdateImage(fingerprint string, image api.ImagePut, ETag // DeleteImage requests that Incus removes an image from the store. func (r *ProtocolIncus) DeleteImage(fingerprint string) (Operation, error) { // Send the request - op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/images/%s", url.PathEscape(fingerprint)), nil, "") + op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/images/%s", url.PathEscape(fingerprint)), nil, "", true) if err != nil { return nil, err } @@ -932,7 +932,7 @@ func (r *ProtocolIncus) RefreshImage(fingerprint string) (Operation, error) { } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/refresh", url.PathEscape(fingerprint)), nil, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/refresh", url.PathEscape(fingerprint)), nil, "", true) if err != nil { return nil, err } @@ -943,7 +943,7 @@ func (r *ProtocolIncus) RefreshImage(fingerprint string) (Operation, error) { // CreateImageSecret requests that Incus issues a temporary image secret. func (r *ProtocolIncus) CreateImageSecret(fingerprint string) (Operation, error) { // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/secret", url.PathEscape(fingerprint)), nil, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/secret", url.PathEscape(fingerprint)), nil, "", true) if err != nil { return nil, err } @@ -1002,7 +1002,7 @@ func (r *ProtocolIncus) ExportImage(fingerprint string, image api.ImageExportPos } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/export", url.PathEscape(fingerprint)), &image, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/export", url.PathEscape(fingerprint)), &image, "", true) if err != nil { return nil, err } diff --git a/client/incus_instances.go b/client/incus_instances.go index 60a903d1334..804e11c90ff 100644 --- a/client/incus_instances.go +++ b/client/incus_instances.go @@ -193,7 +193,7 @@ func (r *ProtocolIncus) UpdateInstances(state api.InstancesPut, ETag string) (Op } // Send the request - op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s?%s", path, v.Encode()), state, ETag) + op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s?%s", path, v.Encode()), state, ETag, true) if err != nil { return nil, err } @@ -209,7 +209,7 @@ func (r *ProtocolIncus) rebuildInstance(instanceName string, instance api.Instan } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/rebuild?project=%s", path, url.PathEscape(instanceName), r.project), instance, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/rebuild", path, url.PathEscape(instanceName)), instance, "", true) if err != nil { return nil, err } @@ -523,7 +523,7 @@ func (r *ProtocolIncus) CreateInstanceFromBackup(args InstanceBackupArgs) (Opera if args.PoolName == "" && args.Name == "" { // Send the request - op, _, err := r.queryOperation("POST", path, args.BackupFile, "") + op, _, err := r.queryOperation("POST", path, args.BackupFile, "", true) if err != nil { return nil, err } @@ -604,7 +604,7 @@ func (r *ProtocolIncus) CreateInstance(instance api.InstancesPost) (Operation, e } // Send the request - op, _, err := r.queryOperation("POST", path, instance, "") + op, _, err := r.queryOperation("POST", path, instance, "", true) if err != nil { return nil, err } @@ -943,7 +943,7 @@ func (r *ProtocolIncus) UpdateInstance(name string, instance api.InstancePut, ET } // Send the request - op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, ETag) + op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, ETag, true) if err != nil { return nil, err } @@ -964,7 +964,7 @@ func (r *ProtocolIncus) RenameInstance(name string, instance api.InstancePost) ( } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "", true) if err != nil { return nil, err } @@ -1060,7 +1060,7 @@ func (r *ProtocolIncus) MigrateInstance(name string, instance api.InstancePost) } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "", true) if err != nil { return nil, err } @@ -1076,7 +1076,7 @@ func (r *ProtocolIncus) DeleteInstance(name string) (Operation, error) { } // Send the request - op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), nil, "") + op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), nil, "", true) if err != nil { return nil, err } @@ -1112,7 +1112,7 @@ func (r *ProtocolIncus) ExecInstance(instanceName string, exec api.InstanceExecP } // Send the request - op, _, err := r.queryOperation("POST", uri, exec, "") + op, _, err := r.queryOperation("POST", uri, exec, "", false) if err != nil { return nil, err } @@ -1681,7 +1681,7 @@ func (r *ProtocolIncus) CreateInstanceSnapshot(instanceName string, snapshot api } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots", path, url.PathEscape(instanceName)), snapshot, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots", path, url.PathEscape(instanceName)), snapshot, "", true) if err != nil { return nil, err } @@ -1928,7 +1928,7 @@ func (r *ProtocolIncus) RenameInstanceSnapshot(instanceName string, name string, } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "", true) if err != nil { return nil, err } @@ -2004,7 +2004,7 @@ func (r *ProtocolIncus) MigrateInstanceSnapshot(instanceName string, name string } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "", true) if err != nil { return nil, err } @@ -2020,7 +2020,7 @@ func (r *ProtocolIncus) DeleteInstanceSnapshot(instanceName string, name string) } // Send the request - op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "") + op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "", true) if err != nil { return nil, err } @@ -2040,7 +2040,7 @@ func (r *ProtocolIncus) UpdateInstanceSnapshot(instanceName string, name string, } // Send the request - op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, ETag) + op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, ETag, true) if err != nil { return nil, err } @@ -2082,7 +2082,7 @@ func (r *ProtocolIncus) UpdateInstanceState(name string, state api.InstanceState } // Send the request - op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/state", path, url.PathEscape(name)), state, ETag) + op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/state", path, url.PathEscape(name)), state, ETag, true) if err != nil { return nil, err } @@ -2405,7 +2405,7 @@ func (r *ProtocolIncus) ConsoleInstance(instanceName string, console api.Instanc } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), console, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), console, "", false) if err != nil { return nil, err } @@ -2493,7 +2493,7 @@ func (r *ProtocolIncus) ConsoleInstanceDynamic(instanceName string, console api. } // Send the request. - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), console, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), console, "", true) if err != nil { return nil, nil, err } @@ -2699,7 +2699,7 @@ func (r *ProtocolIncus) CreateInstanceBackup(instanceName string, backup api.Ins } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups", path, url.PathEscape(instanceName)), backup, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups", path, url.PathEscape(instanceName)), backup, "", true) if err != nil { return nil, err } @@ -2719,7 +2719,7 @@ func (r *ProtocolIncus) RenameInstanceBackup(instanceName string, name string, b } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), backup, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), backup, "", true) if err != nil { return nil, err } @@ -2739,7 +2739,7 @@ func (r *ProtocolIncus) DeleteInstanceBackup(instanceName string, name string) ( } // Send the request - op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "") + op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "", true) if err != nil { return nil, err } diff --git a/client/incus_projects.go b/client/incus_projects.go index a75683dd9bb..86e68146b73 100644 --- a/client/incus_projects.go +++ b/client/incus_projects.go @@ -115,7 +115,7 @@ func (r *ProtocolIncus) RenameProject(name string, project api.ProjectPost) (Ope } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/projects/%s", url.PathEscape(name)), project, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/projects/%s", url.PathEscape(name)), project, "", true) if err != nil { return nil, err } diff --git a/client/incus_storage_volumes.go b/client/incus_storage_volumes.go index dc12590e6b0..3c959ff2787 100644 --- a/client/incus_storage_volumes.go +++ b/client/incus_storage_volumes.go @@ -231,7 +231,7 @@ func (r *ProtocolIncus) CreateStoragePoolVolumeSnapshot(pool string, volumeType url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName)) - op, _, err := r.queryOperation("POST", path, snapshot, "") + op, _, err := r.queryOperation("POST", path, snapshot, "", true) if err != nil { return nil, err } @@ -308,7 +308,7 @@ func (r *ProtocolIncus) RenameStoragePoolVolumeSnapshot(pool string, volumeType path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/snapshots/%s", url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(snapshotName)) // Send the request - op, _, err := r.queryOperation("POST", path, snapshot, "") + op, _, err := r.queryOperation("POST", path, snapshot, "", true) if err != nil { return nil, err } @@ -327,7 +327,7 @@ func (r *ProtocolIncus) DeleteStoragePoolVolumeSnapshot(pool string, volumeType "/storage-pools/%s/volumes/%s/%s/snapshots/%s", url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(snapshotName)) - op, _, err := r.queryOperation("DELETE", path, nil, "") + op, _, err := r.queryOperation("DELETE", path, nil, "", true) if err != nil { return nil, err } @@ -343,7 +343,7 @@ func (r *ProtocolIncus) UpdateStoragePoolVolumeSnapshot(pool string, volumeType // Send the request path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/snapshots/%s", url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(snapshotName)) - _, _, err := r.queryOperation("PUT", path, volume, ETag) + _, _, err := r.queryOperation("PUT", path, volume, ETag, true) if err != nil { return err } @@ -386,7 +386,7 @@ func (r *ProtocolIncus) MigrateStoragePoolVolume(pool string, volume api.Storage } // Send the request - op, _, err := r.queryOperation("POST", path, req, "") + op, _, err := r.queryOperation("POST", path, req, "", true) if err != nil { return nil, err } @@ -475,7 +475,7 @@ func (r *ProtocolIncus) tryCreateStoragePoolVolume(pool string, req api.StorageV // Send the request path := fmt.Sprintf("/storage-pools/%s/volumes/%s", url.PathEscape(pool), url.PathEscape(req.Type)) - top, _, err := r.queryOperation("POST", path, req, "") + top, _, err := r.queryOperation("POST", path, req, "", true) if err != nil { errors = append(errors, remoteOperationResult{URL: serverURL, Error: err}) continue @@ -567,7 +567,7 @@ func (r *ProtocolIncus) CopyStoragePoolVolume(pool string, source InstanceServer } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/%s", url.PathEscape(pool), url.PathEscape(volume.Type)), req, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/%s", url.PathEscape(pool), url.PathEscape(volume.Type)), req, "", true) if err != nil { return nil, err } @@ -616,7 +616,7 @@ func (r *ProtocolIncus) CopyStoragePoolVolume(pool string, source InstanceServer path := fmt.Sprintf("/storage-pools/%s/volumes/%s", url.PathEscape(pool), url.PathEscape(volume.Type)) // Send the request - op, _, err := r.queryOperation("POST", path, req, "") + op, _, err := r.queryOperation("POST", path, req, "", true) if err != nil { return nil, err } @@ -668,7 +668,7 @@ func (r *ProtocolIncus) CopyStoragePoolVolume(pool string, source InstanceServer path := fmt.Sprintf("/storage-pools/%s/volumes/%s", url.PathEscape(pool), url.PathEscape(volume.Type)) // Send the request - targetOp, _, err := r.queryOperation("POST", path, req, "") + targetOp, _, err := r.queryOperation("POST", path, req, "", true) if err != nil { return nil, err } @@ -736,7 +736,7 @@ func (r *ProtocolIncus) MoveStoragePoolVolume(pool string, source InstanceServer } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/%s/%s", url.PathEscape(sourcePool), url.PathEscape(volume.Type), volume.Name), req, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/%s/%s", url.PathEscape(sourcePool), url.PathEscape(volume.Type), volume.Name), req, "", true) if err != nil { return nil, err } @@ -866,7 +866,7 @@ func (r *ProtocolIncus) CreateStoragePoolVolumeBackup(pool string, volName strin } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/custom/%s/backups", url.PathEscape(pool), url.PathEscape(volName)), backup, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/custom/%s/backups", url.PathEscape(pool), url.PathEscape(volName)), backup, "", true) if err != nil { return nil, err } @@ -881,7 +881,7 @@ func (r *ProtocolIncus) RenameStoragePoolVolumeBackup(pool string, volName strin } // Send the request - op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/custom/%s/backups/%s", url.PathEscape(pool), url.PathEscape(volName), url.PathEscape(name)), backup, "") + op, _, err := r.queryOperation("POST", fmt.Sprintf("/storage-pools/%s/volumes/custom/%s/backups/%s", url.PathEscape(pool), url.PathEscape(volName), url.PathEscape(name)), backup, "", true) if err != nil { return nil, err } @@ -896,7 +896,7 @@ func (r *ProtocolIncus) DeleteStoragePoolVolumeBackup(pool string, volName strin } // Send the request - op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/storage-pools/%s/volumes/custom/%s/backups/%s", url.PathEscape(pool), url.PathEscape(volName), url.PathEscape(name)), nil, "") + op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/storage-pools/%s/volumes/custom/%s/backups/%s", url.PathEscape(pool), url.PathEscape(volName), url.PathEscape(name)), nil, "", true) if err != nil { return nil, err } diff --git a/client/operations.go b/client/operations.go index a71bbd4a073..8046ef5c3da 100644 --- a/client/operations.go +++ b/client/operations.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "sync" + "time" "github.com/gorilla/websocket" @@ -20,12 +21,17 @@ type operation struct { listener *EventListener handlerReady bool handlerLock sync.Mutex + skipListener bool chActive chan bool } // AddHandler adds a function to be called whenever an event is received. func (op *operation) AddHandler(function func(api.Operation)) (*EventTarget, error) { + if op.skipListener { + return nil, fmt.Errorf("Cannot add handler, client operation does not support event listeners") + } + // Make sure we have a listener setup err := op.setupListener() if err != nil { @@ -77,6 +83,10 @@ func (op *operation) GetWebsocket(secret string) (*websocket.Conn, error) { // RemoveHandler removes a function to be called whenever an event is received. func (op *operation) RemoveHandler(target *EventTarget) error { + if op.skipListener { + return fmt.Errorf("Cannot remove handler, client operation does not support event listeners") + } + // Make sure we're not racing with ourselves op.handlerLock.Lock() defer op.handlerLock.Unlock() @@ -110,6 +120,27 @@ func (op *operation) Wait() error { // WaitContext lets you wait until the operation reaches a final state with context.Context. func (op *operation) WaitContext(ctx context.Context) error { + if op.skipListener { + timeout := -1 + deadline, ok := ctx.Deadline() + if ok { + timeout = int(time.Until(deadline).Seconds()) + } + + opAPI, _, err := op.r.GetOperationWait(op.ID, timeout) + if err != nil { + return err + } + + op.Operation = *opAPI + + if opAPI.Err != "" { + return errors.New(opAPI.Err) + } + + return nil + } + op.handlerLock.Lock() // Check if not done already if op.StatusCode.IsFinal() { @@ -148,6 +179,10 @@ func (op *operation) WaitContext(ctx context.Context) error { // It adds handlers to process events, monitors the listener for completion or errors, // and triggers a manual refresh of the operation's state to prevent race conditions. func (op *operation) setupListener() error { + if op.skipListener { + return fmt.Errorf("Cannot set up event listener, client operation does not support event listeners") + } + // Make sure we're not racing with ourselves op.handlerLock.Lock() defer op.handlerLock.Unlock()