@opensoha/contracts 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/COMPATIBILITY.md +46 -0
  2. package/README.md +12 -3
  3. package/auth/catalog_generated.go +2 -2
  4. package/auth/catalog_test.go +32 -0
  5. package/auth/permission-catalog.json +537 -8
  6. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  7. package/compat/openapi-loose-boundaries-0.1.0.json +58 -1
  8. package/fixtures/json-schema/network-ingest-event/invalid/duplicate-event.json +33 -0
  9. package/fixtures/json-schema/network-ingest-event/invalid/message-payload-mismatch.json +23 -0
  10. package/fixtures/json-schema/network-ingest-event/invalid/password.json +22 -0
  11. package/fixtures/json-schema/network-ingest-event/invalid/proxy-flow-destination.json +28 -0
  12. package/fixtures/json-schema/network-ingest-event/valid/endpoint-proxy-flow.json +27 -0
  13. package/fixtures/json-schema/network-ingest-event/valid/freeradius-accounting.json +25 -0
  14. package/fixtures/json-schema/network-ingest-event/valid/gateway.json +53 -0
  15. package/fixtures/json-schema/network-radius-accounting/invalid/password.json +9 -0
  16. package/fixtures/json-schema/network-radius-accounting/valid/accounting.json +16 -0
  17. package/fixtures/json-schema/network-runtime-protocol/invalid/invalid-cidr.json +31 -0
  18. package/fixtures/json-schema/network-runtime-protocol/invalid/message-payload-mismatch.json +19 -0
  19. package/fixtures/json-schema/network-runtime-protocol/invalid/mihomo-raw-subscription.json +33 -0
  20. package/fixtures/json-schema/network-runtime-protocol/invalid/nas-authorization-password.json +18 -0
  21. package/fixtures/json-schema/network-runtime-protocol/invalid/nas-eap-without-certificate.json +17 -0
  22. package/fixtures/json-schema/network-runtime-protocol/invalid/private-key.json +20 -0
  23. package/fixtures/json-schema/network-runtime-protocol/invalid/raw-credential-reference.json +16 -0
  24. package/fixtures/json-schema/network-runtime-protocol/invalid/runtime-interval-too-short.json +23 -0
  25. package/fixtures/json-schema/network-runtime-protocol/invalid/wireguard-capability-without-key.json +19 -0
  26. package/fixtures/json-schema/network-runtime-protocol/invalid/wireguard-default-route.json +31 -0
  27. package/fixtures/json-schema/network-runtime-protocol/invalid/ztna-without-access-grant.json +17 -0
  28. package/fixtures/json-schema/network-runtime-protocol/valid/configuration-desired.json +86 -0
  29. package/fixtures/json-schema/network-runtime-protocol/valid/direct-ztna-connect.json +19 -0
  30. package/fixtures/json-schema/network-runtime-protocol/valid/direct-ztna-result.json +33 -0
  31. package/fixtures/json-schema/network-runtime-protocol/valid/internal-ztna-connect.json +19 -0
  32. package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-app-subscription-configuration.json +32 -0
  33. package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-manual-node-configuration.json +32 -0
  34. package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-wireguard-configuration.json +70 -0
  35. package/fixtures/json-schema/network-runtime-protocol/valid/nas-authorization-request.json +19 -0
  36. package/fixtures/json-schema/network-runtime-protocol/valid/nas-authorization-result.json +24 -0
  37. package/fixtures/json-schema/network-runtime-protocol/valid/nas-session-command-result.json +17 -0
  38. package/fixtures/json-schema/network-runtime-protocol/valid/nas-session-command.json +27 -0
  39. package/fixtures/json-schema/network-runtime-protocol/valid/vpn-connect-request.json +17 -0
  40. package/fixtures/json-schema/network-runtime-protocol/valid/vpn-connect-result.json +34 -0
  41. package/fixtures/json-schema/network-runtime-protocol/valid/wireguard-enrollment.json +20 -0
  42. package/fixtures/json-schema/network-runtime-protocol/valid/wireguard-site-link.json +48 -0
  43. package/fixtures/openapi/AlertRuleEnvelope/invalid/missing-data.json +1 -0
  44. package/fixtures/openapi/AlertRuleEnvelope/valid/metrics.json +11 -0
  45. package/fixtures/openapi/AlertRuleInput/invalid/missing-name.json +3 -0
  46. package/fixtures/openapi/AlertRuleInput/valid/metrics.json +25 -0
  47. package/fixtures/openapi/AlertRuleRunListEnvelope/invalid/missing-rule-id.json +12 -0
  48. package/fixtures/openapi/AlertRuleRunListEnvelope/valid/items.json +13 -0
  49. package/fixtures/openapi/AlertRuleTestResultEnvelope/invalid/missing-executed-at.json +5 -0
  50. package/fixtures/openapi/AlertRuleTestResultEnvelope/valid/clear.json +10 -0
  51. package/fixtures/openapi/AnnouncementReceiptPageEnvelope/invalid/missing-read-count.json +9 -0
  52. package/fixtures/openapi/AnnouncementReceiptPageEnvelope/valid/read-and-unread.json +28 -0
  53. package/fixtures/openapi/ApplicationRuntimeEnvironment/invalid/unknown-status.json +7 -0
  54. package/fixtures/openapi/ApplicationRuntimeEnvironment/valid/unavailable.json +7 -0
  55. package/fixtures/openapi/ApplicationWorkflowInput/invalid/owner-fields.json +7 -0
  56. package/fixtures/openapi/ApplicationWorkflowInput/valid/minimal.json +9 -0
  57. package/fixtures/openapi/BrandingAssetUploadEnvelope/valid/svg-data-url.json +5 -0
  58. package/fixtures/openapi/BrowserHandoffCompletionEnvelope/invalid/token-leak.json +7 -0
  59. package/fixtures/openapi/BrowserHandoffCompletionEnvelope/valid/completed.json +6 -0
  60. package/fixtures/openapi/BrowserHandoffEnvelope/invalid/missing-principal.json +12 -0
  61. package/fixtures/openapi/BrowserHandoffEnvelope/valid/pending.json +11 -0
  62. package/fixtures/openapi/ChangePasswordRequest/invalid/missing-current-password.json +3 -0
  63. package/fixtures/openapi/ChangePasswordRequest/invalid/short-new-password.json +4 -0
  64. package/fixtures/openapi/ChangePasswordRequest/valid/password.json +4 -0
  65. package/fixtures/openapi/DesktopAuthAttemptCreateRequest/invalid/https-redirect.json +6 -0
  66. package/fixtures/openapi/DesktopAuthAttemptCreateRequest/invalid/localhost-redirect.json +6 -0
  67. package/fixtures/openapi/DesktopAuthAttemptCreateRequest/invalid/plain-challenge.json +6 -0
  68. package/fixtures/openapi/DesktopAuthAttemptCreateRequest/invalid/privileged-port.json +6 -0
  69. package/fixtures/openapi/DesktopAuthAttemptCreateRequest/invalid/short-callback.json +6 -0
  70. package/fixtures/openapi/DesktopAuthAttemptCreateRequest/valid/loopback.json +6 -0
  71. package/fixtures/openapi/DesktopAuthAttemptEnvelope/invalid/missing-expiry.json +6 -0
  72. package/fixtures/openapi/DesktopAuthAttemptEnvelope/valid/attempt.json +7 -0
  73. package/fixtures/openapi/DesktopAuthAttemptExchangeRequest/invalid/short-verifier.json +4 -0
  74. package/fixtures/openapi/DesktopAuthAttemptExchangeRequest/valid/exchange.json +4 -0
  75. package/fixtures/openapi/EndpointDevice/invalid/missing-owner.json +8 -0
  76. package/fixtures/openapi/EndpointDevice/valid/active.json +35 -0
  77. package/fixtures/openapi/EndpointDeviceRegistrationInput/invalid/client-owned-status.json +5 -0
  78. package/fixtures/openapi/EndpointDeviceRegistrationInput/invalid/oversized-network-inventory.json +18 -0
  79. package/fixtures/openapi/EndpointDeviceRegistrationInput/valid/macos.json +28 -0
  80. package/fixtures/openapi/IdentityApplication/invalid/missing-id.json +7 -0
  81. package/fixtures/openapi/IdentityApplication/valid/full.json +30 -0
  82. package/fixtures/openapi/IdentityApplicationEnvelope/invalid/missing-data.json +1 -0
  83. package/fixtures/openapi/IdentityApplicationEnvelope/valid/minimal.json +10 -0
  84. package/fixtures/openapi/IdentityApplicationLaunchListEnvelope/invalid/missing-user.json +11 -0
  85. package/fixtures/openapi/IdentityApplicationLaunchListEnvelope/valid/minimal.json +12 -0
  86. package/fixtures/openapi/LLMUpstreamTestResultEnvelope/invalid/models-not-array.json +9 -0
  87. package/fixtures/openapi/LLMUpstreamTestResultEnvelope/valid/discovered-models.json +11 -0
  88. package/fixtures/openapi/LogQuery/invalid/empty-data-source-id.json +7 -0
  89. package/fixtures/openapi/LogQuery/valid/durable-data-source.json +14 -0
  90. package/fixtures/openapi/ManifestPackage/invalid/missing-application.json +12 -0
  91. package/fixtures/openapi/ManifestPackage/valid/service-scoped.json +29 -0
  92. package/fixtures/openapi/NetworkAccessGrant/invalid/token-digest.json +15 -0
  93. package/fixtures/openapi/NetworkAccessGrant/valid/issued.json +14 -0
  94. package/fixtures/openapi/NetworkAccessGrantInput/invalid/no-resources.json +7 -0
  95. package/fixtures/openapi/NetworkAccessGrantInput/valid/direct-ztna.json +8 -0
  96. package/fixtures/openapi/NetworkAccessGrantSecret/invalid/short-token.json +17 -0
  97. package/fixtures/openapi/NetworkAccessGrantSecret/valid/issued.json +17 -0
  98. package/fixtures/openapi/NetworkAccessPolicy/invalid/missing-version.json +20 -0
  99. package/fixtures/openapi/NetworkAccessPolicy/valid/published-draft.json +21 -0
  100. package/fixtures/openapi/NetworkAccessPolicyInput/invalid/duplicate-team.json +17 -0
  101. package/fixtures/openapi/NetworkAccessPolicyInput/valid/team-and-tag-allow.json +17 -0
  102. package/fixtures/openapi/NetworkConflictAnalysisRequest/invalid/ipv6-range.json +9 -0
  103. package/fixtures/openapi/NetworkConflictAnalysisRequest/valid/runtime-ranges.json +15 -0
  104. package/fixtures/openapi/NetworkConnectionOption/invalid/secret.json +10 -0
  105. package/fixtures/openapi/NetworkConnectionOption/valid/wifi.json +9 -0
  106. package/fixtures/openapi/NetworkConnectionOption/valid/wired.json +8 -0
  107. package/fixtures/openapi/NetworkGateway/invalid/missing-site.json +7 -0
  108. package/fixtures/openapi/NetworkGateway/valid/online.json +23 -0
  109. package/fixtures/openapi/NetworkGatewayInput/invalid/default-route-overlay.json +13 -0
  110. package/fixtures/openapi/NetworkGatewayInput/valid/routed.json +14 -0
  111. package/fixtures/openapi/NetworkLease/invalid/empty-cidrs.json +12 -0
  112. package/fixtures/openapi/NetworkLease/valid/active.json +12 -0
  113. package/fixtures/openapi/NetworkMihomoProfile/invalid/subscription-leak.json +20 -0
  114. package/fixtures/openapi/NetworkMihomoProfile/valid/managed.json +21 -0
  115. package/fixtures/openapi/NetworkMihomoProfileInput/invalid/app-subscription-secret.json +14 -0
  116. package/fixtures/openapi/NetworkMihomoProfileInput/invalid/manual-node-with-subscription.json +20 -0
  117. package/fixtures/openapi/NetworkMihomoProfileInput/valid/app-subscription.json +14 -0
  118. package/fixtures/openapi/NetworkMihomoProfileInput/valid/managed.json +16 -0
  119. package/fixtures/openapi/NetworkMihomoProfileInput/valid/manual-node.json +21 -0
  120. package/fixtures/openapi/NetworkNASBinding/invalid/secret.json +13 -0
  121. package/fixtures/openapi/NetworkNASBinding/valid/default.json +16 -0
  122. package/fixtures/openapi/NetworkNASBindingInput/invalid/secret.json +10 -0
  123. package/fixtures/openapi/NetworkNASBindingInput/valid/default.json +13 -0
  124. package/fixtures/openapi/NetworkPolicyPreviewRequest/invalid/unknown-mode.json +6 -0
  125. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/external-direct-ztna.json +6 -0
  126. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/external-vpn-ztna.json +6 -0
  127. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/external-vpn.json +6 -0
  128. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/internal-direct.json +7 -0
  129. package/fixtures/openapi/NetworkPolicyPreviewRequest/valid/internal-ztna.json +7 -0
  130. package/fixtures/openapi/NetworkPolicyPreviewResult/invalid/bypass-path.json +9 -0
  131. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/onboarding.json +10 -0
  132. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/protected-broad-path-denied.json +9 -0
  133. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/protected-resource.json +9 -0
  134. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/quarantine.json +10 -0
  135. package/fixtures/openapi/NetworkPolicyPreviewResult/valid/restricted.json +10 -0
  136. package/fixtures/openapi/NetworkPolicySnapshot/invalid/zero-version.json +7 -0
  137. package/fixtures/openapi/NetworkPolicySnapshot/valid/published.json +7 -0
  138. package/fixtures/openapi/NetworkResourceInput/invalid/invalid-port.json +10 -0
  139. package/fixtures/openapi/NetworkResourceInput/valid/protected-cidr.json +10 -0
  140. package/fixtures/openapi/NetworkRuntimeEnrollmentInput/invalid/excessive-ttl.json +7 -0
  141. package/fixtures/openapi/NetworkRuntimeEnrollmentInput/valid/endpoint.json +7 -0
  142. package/fixtures/openapi/NetworkRuntimeEnrollmentSecret/invalid/short-token.json +15 -0
  143. package/fixtures/openapi/NetworkRuntimeEnrollmentSecret/valid/pending.json +15 -0
  144. package/fixtures/openapi/NetworkSession/invalid/unknown-status.json +14 -0
  145. package/fixtures/openapi/NetworkSession/valid/restricted-vpn-ztna.json +16 -0
  146. package/fixtures/openapi/NetworkSessionActionExecuteInput/invalid/short-hash.json +6 -0
  147. package/fixtures/openapi/NetworkSessionActionExecuteInput/valid/restrict.json +6 -0
  148. package/fixtures/openapi/NetworkSessionActionInput/invalid/unknown-action.json +5 -0
  149. package/fixtures/openapi/NetworkSessionActionInput/valid/restrict.json +5 -0
  150. package/fixtures/openapi/NetworkSessionActionPlan/invalid/missing-hash.json +12 -0
  151. package/fixtures/openapi/NetworkSessionActionPlan/valid/fallback-disconnect.json +13 -0
  152. package/fixtures/openapi/NetworkSessionCommand/invalid/unknown-status.json +14 -0
  153. package/fixtures/openapi/NetworkSessionCommand/valid/pending.json +14 -0
  154. package/fixtures/openapi/NetworkSiteInput/invalid/unknown-status.json +4 -0
  155. package/fixtures/openapi/NetworkSiteInput/valid/shanghai.json +5 -0
  156. package/fixtures/openapi/NetworkSiteProfileBinding/invalid/vlan.json +9 -0
  157. package/fixtures/openapi/NetworkSiteProfileBinding/valid/default.json +10 -0
  158. package/fixtures/openapi/NetworkSiteProfileBindingInput/invalid/timeout.json +7 -0
  159. package/fixtures/openapi/NetworkSiteProfileBindingInput/valid/default.json +7 -0
  160. package/fixtures/openapi/NetworkSpaceInput/invalid/empty-cidrs.json +6 -0
  161. package/fixtures/openapi/NetworkSpaceInput/invalid/malformed-cidr.json +6 -0
  162. package/fixtures/openapi/NetworkSpaceInput/valid/corp.json +6 -0
  163. package/fixtures/openapi/NetworkTelemetrySummary/invalid/raw-destination.json +29 -0
  164. package/fixtures/openapi/NetworkTelemetrySummary/valid/proxy-flow.json +36 -0
  165. package/fixtures/openapi/OIDCClientInput/valid/generated-client-id.json +8 -0
  166. package/fixtures/openapi/OIDCClientSecretRevealEnvelope/invalid/missing-secret.json +6 -0
  167. package/fixtures/openapi/OIDCClientSecretRevealEnvelope/valid/reveal.json +7 -0
  168. package/fixtures/openapi/PasswordChangeResult/invalid/unexpected-status.json +3 -0
  169. package/fixtures/openapi/PasswordChangeResult/valid/ok.json +3 -0
  170. package/fixtures/openapi/PortalApplicationListEnvelope/invalid/missing-items.json +1 -0
  171. package/fixtures/openapi/PortalApplicationListEnvelope/valid/minimal.json +12 -0
  172. package/fixtures/openapi/PortalBootstrapEnvelope/invalid/missing-security.json +17 -0
  173. package/fixtures/openapi/PortalBootstrapEnvelope/valid/minimal.json +31 -0
  174. package/fixtures/openapi/PortalLaunchDecisionEnvelope/invalid/missing-launch-url.json +14 -0
  175. package/fixtures/openapi/PortalLaunchDecisionEnvelope/valid/desktop-handoff.json +16 -0
  176. package/fixtures/openapi/PortalLaunchRequest/invalid/unknown-surface.json +3 -0
  177. package/fixtures/openapi/PortalLaunchRequest/valid/default-web.json +1 -0
  178. package/fixtures/openapi/PortalLaunchRequest/valid/desktop.json +3 -0
  179. package/fixtures/openapi/PortalSecuritySummaryEnvelope/invalid/missing-session-count.json +15 -0
  180. package/fixtures/openapi/PortalSecuritySummaryEnvelope/valid/minimal.json +16 -0
  181. package/fixtures/openapi/ResourceLease/invalid/missing-session.json +10 -0
  182. package/fixtures/openapi/ResourceLease/valid/active.json +11 -0
  183. package/fixtures/openapi/ScopeGrantInput/invalid/delivery-missing-business-line.json +7 -0
  184. package/fixtures/openapi/ScopeGrantInput/invalid/platform-missing-clusters.json +7 -0
  185. package/fixtures/openapi/ScopeGrantInput/valid/platform.json +8 -0
  186. package/fixtures/openapi/SoftwarePackage/valid/software-package.json +6 -0
  187. package/fixtures/openapi/SoftwarePackageDownloadRecord/invalid/negative-duration.json +6 -0
  188. package/fixtures/openapi/SoftwarePackageDownloadRecord/valid/software-package-download-record.json +8 -0
  189. package/fixtures/openapi/SoftwarePackageURLImportRequest/valid/software-package-url-import-request.json +4 -0
  190. package/fixtures/openapi/SoftwareStorage/invalid/software-storage.json +1 -1
  191. package/fixtures/openapi/SoftwareStorage/valid/software-storage.json +13 -1
  192. package/fixtures/openapi/SystemIntegrationCreateRequest/invalid/s3-storage-missing-name.json +5 -0
  193. package/fixtures/openapi/SystemIntegrationCreateRequest/valid/s3-software-storage.json +18 -0
  194. package/fixtures/openapi/UpdateProfileRequest/invalid/invalid-avatar-fit.json +5 -0
  195. package/fixtures/openapi/UpdateProfileRequest/invalid/missing-email.json +3 -0
  196. package/fixtures/openapi/UpdateProfileRequest/valid/profile.json +7 -0
  197. package/gen/go/sohaapi/client.go +210 -5
  198. package/gen/go/sohaapi/client_test.go +128 -5
  199. package/gen/go/sohaapi/types.go +3326 -369
  200. package/gen/go/sohaapi/types_test.go +22 -0
  201. package/gen/ts/sohaapi/index.ts +11612 -6479
  202. package/network/README.md +116 -0
  203. package/network/network-ingest-event.schema.json +187 -0
  204. package/network/network-radius-accounting.schema.json +41 -0
  205. package/network/network-runtime-protocol.schema.json +752 -0
  206. package/network/schema.go +26 -0
  207. package/network/schema_test.go +22 -0
  208. package/openapi/soha-api.json +29591 -18317
  209. package/openapi/soha-api.yaml +11603 -6720
  210. package/openapi/spec_generated.go +3 -3
  211. package/package.json +5 -1
@@ -2,8 +2,10 @@
2
2
  package sohaapi
3
3
 
4
4
  import (
5
+ "bufio"
5
6
  "bytes"
6
7
  "context"
8
+ "crypto/sha256"
7
9
  "encoding/json"
8
10
  "fmt"
9
11
  "io"
@@ -14,7 +16,7 @@ import (
14
16
  "time"
15
17
  )
16
18
 
17
- const defaultUserAgent = "opensoha-contracts/0.1.15"
19
+ const defaultUserAgent = "opensoha-contracts/0.1.17"
18
20
 
19
21
  type Client struct {
20
22
  BaseURL string
@@ -544,6 +546,97 @@ func (c *Client) queryLogs(ctx context.Context, path string, query LogQuery) (Lo
544
546
  return out.Data, nil
545
547
  }
546
548
 
549
+ func (c *Client) GetComputeCapabilities(ctx context.Context) (ComputeCapabilityManifest, error) {
550
+ var out ComputeCapabilityManifestEnvelope
551
+ if err := c.doJSON(ctx, http.MethodGet, "/compute/capabilities", true, nil, &out); err != nil {
552
+ return ComputeCapabilityManifest{}, err
553
+ }
554
+ return out.Data, nil
555
+ }
556
+
557
+ func (c *Client) GetComputeOverview(ctx context.Context) (ComputeOverview, error) {
558
+ var out ComputeOverviewEnvelope
559
+ if err := c.doJSON(ctx, http.MethodGet, "/compute/overview", true, nil, &out); err != nil {
560
+ return ComputeOverview{}, err
561
+ }
562
+ return out.Data, nil
563
+ }
564
+
565
+ func (c *Client) ListComputeAccessSources(ctx context.Context, params ListComputeAccessSourcesParams) (ComputeAccessSourceListEnvelope, error) {
566
+ var out ComputeAccessSourceListEnvelope
567
+ if err := c.doJSON(ctx, http.MethodGet, "/compute/access-sources"+computeAccessSourceQuery(params), true, nil, &out); err != nil {
568
+ return ComputeAccessSourceListEnvelope{}, err
569
+ }
570
+ return out, nil
571
+ }
572
+
573
+ func (c *Client) ListComputeProviders(ctx context.Context, params ListComputeProvidersParams) (ComputeProviderListEnvelope, error) {
574
+ var out ComputeProviderListEnvelope
575
+ if err := c.doJSON(ctx, http.MethodGet, "/compute/providers"+computeProviderQuery(params), true, nil, &out); err != nil {
576
+ return ComputeProviderListEnvelope{}, err
577
+ }
578
+ return out, nil
579
+ }
580
+
581
+ func (c *Client) ListComputeProviderInstances(ctx context.Context, params ListComputeProviderInstancesParams) (ComputeProviderInstanceListEnvelope, error) {
582
+ var out ComputeProviderInstanceListEnvelope
583
+ if err := c.doJSON(ctx, http.MethodGet, "/compute/provider-instances"+computeProviderInstanceQuery(params), true, nil, &out); err != nil {
584
+ return ComputeProviderInstanceListEnvelope{}, err
585
+ }
586
+ return out, nil
587
+ }
588
+
589
+ func (c *Client) GetComputeProviderInstance(ctx context.Context, domain ComputeProviderDomain, providerKey, instanceRef string) (ComputeProviderInstance, error) {
590
+ var out ComputeProviderInstanceEnvelope
591
+ if err := c.doJSON(ctx, http.MethodGet, computeProviderInstancePath(domain, providerKey, instanceRef), true, nil, &out); err != nil {
592
+ return ComputeProviderInstance{}, err
593
+ }
594
+ return out.Data, nil
595
+ }
596
+
597
+ func (c *Client) CheckComputeProviderInstanceHealth(ctx context.Context, domain ComputeProviderDomain, providerKey, instanceRef, idempotencyKey string, req ComputeProviderReadRequest) (ComputeTaskView, error) {
598
+ return c.mutateComputeProviderInstance(ctx, domain, providerKey, instanceRef, "health-checks", idempotencyKey, req)
599
+ }
600
+
601
+ func (c *Client) DiscoverComputeProviderInstance(ctx context.Context, domain ComputeProviderDomain, providerKey, instanceRef, idempotencyKey string, req ComputeProviderDiscoverRequest) (ComputeTaskView, error) {
602
+ return c.mutateComputeProviderInstance(ctx, domain, providerKey, instanceRef, "discoveries", idempotencyKey, req)
603
+ }
604
+
605
+ func (c *Client) mutateComputeProviderInstance(ctx context.Context, domain ComputeProviderDomain, providerKey, instanceRef, action, idempotencyKey string, req any) (ComputeTaskView, error) {
606
+ var out ComputeTaskEnvelope
607
+ path := computeProviderInstancePath(domain, providerKey, instanceRef) + "/" + action
608
+ if err := c.doJSONWithHeaders(ctx, http.MethodPost, path, true, req, &out, idempotencyHeader(idempotencyKey)); err != nil {
609
+ return ComputeTaskView{}, err
610
+ }
611
+ return out.Data, nil
612
+ }
613
+
614
+ func (c *Client) GetComputeResource(ctx context.Context, domain ComputeDomain, kind ComputeResourceKind, resourceID string) (AnyValue, error) {
615
+ var out GenericDataEnvelope
616
+ if err := c.doJSON(ctx, http.MethodGet, computeResourcePath(domain, kind, resourceID), true, nil, &out); err != nil {
617
+ return nil, err
618
+ }
619
+ return out.Data, nil
620
+ }
621
+
622
+ func (c *Client) ListComputeResourceRelations(ctx context.Context, domain ComputeDomain, kind ComputeResourceKind, resourceID string, params ListComputeResourceRelationsParams) (ComputeResourceRelations, error) {
623
+ var out ComputeResourceRelationListEnvelope
624
+ path := computeResourcePath(domain, kind, resourceID) + "/relations" + computeRelationQuery(params)
625
+ if err := c.doJSON(ctx, http.MethodGet, path, true, nil, &out); err != nil {
626
+ return ComputeResourceRelations{}, err
627
+ }
628
+ return out.Data, nil
629
+ }
630
+
631
+ func (c *Client) ExecuteComputeResourceAction(ctx context.Context, domain ComputeDomain, kind ComputeResourceKind, resourceID, action, idempotencyKey string, req ComputeResourceActionRequest) (ComputeTaskView, error) {
632
+ var out ComputeTaskEnvelope
633
+ path := computeResourcePath(domain, kind, resourceID) + "/actions/" + url.PathEscape(strings.TrimSpace(action))
634
+ if err := c.doJSONWithHeaders(ctx, http.MethodPost, path, true, req, &out, idempotencyHeader(idempotencyKey)); err != nil {
635
+ return ComputeTaskView{}, err
636
+ }
637
+ return out.Data, nil
638
+ }
639
+
547
640
  func (c *Client) ListComputeTasks(ctx context.Context, params ListComputeTasksParams) (ComputeTaskListEnvelope, error) {
548
641
  var out ComputeTaskListEnvelope
549
642
  if err := c.doJSON(ctx, http.MethodGet, "/compute/tasks"+computeTaskQuery(params), true, nil, &out); err != nil {
@@ -569,22 +662,80 @@ func (c *Client) ListComputeTaskLogs(ctx context.Context, domain ComputeTaskDoma
569
662
  }
570
663
 
571
664
  func (c *Client) CancelComputeTask(ctx context.Context, domain ComputeTaskDomain, taskID string, req ComputeTaskMutationRequest) (ComputeTaskView, error) {
572
- return c.mutateComputeTask(ctx, domain, taskID, "cancel", req)
665
+ return c.CancelComputeTaskWithKey(ctx, domain, taskID, computeMutationKey(domain, taskID, "cancel", req), req)
573
666
  }
574
667
 
575
668
  func (c *Client) RetryComputeTask(ctx context.Context, domain ComputeTaskDomain, taskID string, req ComputeTaskMutationRequest) (ComputeTaskView, error) {
576
- return c.mutateComputeTask(ctx, domain, taskID, "retry", req)
669
+ return c.RetryComputeTaskWithKey(ctx, domain, taskID, computeMutationKey(domain, taskID, "retry", req), req)
670
+ }
671
+
672
+ func (c *Client) CancelComputeTaskWithKey(ctx context.Context, domain ComputeTaskDomain, taskID, idempotencyKey string, req ComputeTaskMutationRequest) (ComputeTaskView, error) {
673
+ return c.mutateComputeTask(ctx, domain, taskID, "cancel", idempotencyKey, req)
577
674
  }
578
675
 
579
- func (c *Client) mutateComputeTask(ctx context.Context, domain ComputeTaskDomain, taskID, action string, req ComputeTaskMutationRequest) (ComputeTaskView, error) {
676
+ func (c *Client) RetryComputeTaskWithKey(ctx context.Context, domain ComputeTaskDomain, taskID, idempotencyKey string, req ComputeTaskMutationRequest) (ComputeTaskView, error) {
677
+ return c.mutateComputeTask(ctx, domain, taskID, "retry", idempotencyKey, req)
678
+ }
679
+
680
+ func (c *Client) mutateComputeTask(ctx context.Context, domain ComputeTaskDomain, taskID, action, idempotencyKey string, req ComputeTaskMutationRequest) (ComputeTaskView, error) {
580
681
  var out ComputeTaskEnvelope
581
682
  path := computeTaskPath(domain, taskID) + "/" + action
582
- if err := c.doJSON(ctx, http.MethodPost, path, true, req, &out); err != nil {
683
+ if err := c.doJSONWithHeaders(ctx, http.MethodPost, path, true, req, &out, idempotencyHeader(idempotencyKey)); err != nil {
583
684
  return ComputeTaskView{}, err
584
685
  }
585
686
  return out.Data, nil
586
687
  }
587
688
 
689
+ func (c *Client) StreamComputeTask(ctx context.Context, domain ComputeTaskDomain, taskID string, handle func(ComputeTaskStreamEvent) error) error {
690
+ if handle == nil {
691
+ return fmt.Errorf("compute task stream handler is required")
692
+ }
693
+ endpoint, err := c.endpoint(computeTaskPath(domain, taskID) + "/stream")
694
+ if err != nil {
695
+ return err
696
+ }
697
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
698
+ if err != nil {
699
+ return err
700
+ }
701
+ req.Header.Set("Accept", "text/event-stream")
702
+ if c.UserAgent != "" {
703
+ req.Header.Set("User-Agent", c.UserAgent)
704
+ }
705
+ if strings.TrimSpace(c.Token) != "" {
706
+ req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(c.Token))
707
+ }
708
+ client := c.HTTPClient
709
+ if client == nil {
710
+ client = http.DefaultClient
711
+ }
712
+ resp, err := client.Do(req)
713
+ if err != nil {
714
+ return err
715
+ }
716
+ defer resp.Body.Close()
717
+ if resp.StatusCode >= http.StatusBadRequest {
718
+ raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
719
+ return fmt.Errorf("GET %s failed: %s: %s", computeTaskPath(domain, taskID)+"/stream", resp.Status, responseErrorMessage(raw))
720
+ }
721
+ scanner := bufio.NewScanner(resp.Body)
722
+ scanner.Buffer(make([]byte, 64<<10), 1<<20)
723
+ for scanner.Scan() {
724
+ line := strings.TrimSpace(scanner.Text())
725
+ if !strings.HasPrefix(line, "data:") {
726
+ continue
727
+ }
728
+ var event ComputeTaskStreamEvent
729
+ if err := json.Unmarshal([]byte(strings.TrimSpace(strings.TrimPrefix(line, "data:"))), &event); err != nil {
730
+ return fmt.Errorf("decode compute task stream event: %w", err)
731
+ }
732
+ if err := handle(event); err != nil {
733
+ return err
734
+ }
735
+ }
736
+ return scanner.Err()
737
+ }
738
+
588
739
  func (c *Client) ListAIGatewayAuditLogs(ctx context.Context, params ListAIGatewayAuditLogsParams) ([]AuditLog, error) {
589
740
  var out AuditLogListEnvelope
590
741
  if err := c.doJSON(ctx, http.MethodGet, "/ai-gateway/audit-logs"+auditLogQuery(params), true, nil, &out); err != nil {
@@ -959,6 +1110,48 @@ func computeTaskPath(domain ComputeTaskDomain, taskID string) string {
959
1110
  return "/compute/tasks/" + url.PathEscape(string(domain)) + "/" + url.PathEscape(strings.TrimSpace(taskID))
960
1111
  }
961
1112
 
1113
+ func computeProviderInstancePath(domain ComputeProviderDomain, providerKey, instanceRef string) string {
1114
+ return "/compute/provider-instances/" + url.PathEscape(string(domain)) + "/" + url.PathEscape(strings.TrimSpace(providerKey)) + "/" + url.PathEscape(strings.TrimSpace(instanceRef))
1115
+ }
1116
+
1117
+ func computeResourcePath(domain ComputeDomain, kind ComputeResourceKind, resourceID string) string {
1118
+ return "/compute/resources/" + url.PathEscape(string(domain)) + "/" + url.PathEscape(string(kind)) + "/" + url.PathEscape(strings.TrimSpace(resourceID))
1119
+ }
1120
+
1121
+ func computeAccessSourceQuery(params ListComputeAccessSourcesParams) string {
1122
+ values := url.Values{}
1123
+ addQueryString(values, "sourceType", string(params.SourceType))
1124
+ addQueryString(values, "providerKey", params.ProviderKey)
1125
+ addQueryString(values, "cursor", string(params.Cursor))
1126
+ addQueryInt(values, "limit", int(params.Limit))
1127
+ return encodeQuery(values)
1128
+ }
1129
+
1130
+ func computeProviderQuery(params ListComputeProvidersParams) string {
1131
+ values := url.Values{}
1132
+ addQueryString(values, "domain", string(params.Domain))
1133
+ addQueryString(values, "source", string(params.Source))
1134
+ addQueryString(values, "cursor", string(params.Cursor))
1135
+ addQueryInt(values, "limit", int(params.Limit))
1136
+ return encodeQuery(values)
1137
+ }
1138
+
1139
+ func computeProviderInstanceQuery(params ListComputeProviderInstancesParams) string {
1140
+ values := url.Values{}
1141
+ addQueryString(values, "domain", string(params.Domain))
1142
+ addQueryString(values, "providerKey", params.ProviderKey)
1143
+ addQueryString(values, "cursor", string(params.Cursor))
1144
+ addQueryInt(values, "limit", int(params.Limit))
1145
+ return encodeQuery(values)
1146
+ }
1147
+
1148
+ func computeRelationQuery(params ListComputeResourceRelationsParams) string {
1149
+ values := url.Values{}
1150
+ addQueryString(values, "cursor", string(params.Cursor))
1151
+ addQueryInt(values, "limit", int(params.Limit))
1152
+ return encodeQuery(values)
1153
+ }
1154
+
962
1155
  func computeTaskQuery(params ListComputeTasksParams) string {
963
1156
  values := url.Values{}
964
1157
  addQueryString(values, "domain", string(params.Domain))
@@ -967,11 +1160,23 @@ func computeTaskQuery(params ListComputeTasksParams) string {
967
1160
  addQueryString(values, "category", string(params.Category))
968
1161
  addQueryString(values, "resourceKind", params.ResourceKind)
969
1162
  addQueryString(values, "resourceId", params.ResourceID)
1163
+ addQueryString(values, "sortBy", params.SortBy)
1164
+ addQueryString(values, "sortOrder", string(params.SortOrder))
970
1165
  addQueryString(values, "cursor", string(params.Cursor))
971
1166
  addQueryInt(values, "limit", int(params.Limit))
972
1167
  return encodeQuery(values)
973
1168
  }
974
1169
 
1170
+ func idempotencyHeader(key string) http.Header {
1171
+ return http.Header{"Idempotency-Key": []string{strings.TrimSpace(key)}}
1172
+ }
1173
+
1174
+ func computeMutationKey(domain ComputeTaskDomain, taskID, action string, req ComputeTaskMutationRequest) string {
1175
+ payload, _ := json.Marshal(req)
1176
+ sum := sha256.Sum256([]byte(string(domain) + "\x00" + strings.TrimSpace(taskID) + "\x00" + action + "\x00" + string(payload)))
1177
+ return fmt.Sprintf("compute-%x", sum)
1178
+ }
1179
+
975
1180
  func addQueryString(values url.Values, key, value string) {
976
1181
  if value != "" {
977
1182
  values.Set(key, value)
@@ -6,6 +6,7 @@ import (
6
6
  "io"
7
7
  "net/http"
8
8
  "net/http/httptest"
9
+ "strings"
9
10
  "testing"
10
11
  "time"
11
12
  )
@@ -155,9 +156,9 @@ func TestExchangeDockerHostAgentEnrollmentIsPublicAndReturnsCredentials(t *testi
155
156
  w.WriteHeader(http.StatusCreated)
156
157
  writeJSON(t, w, map[string]any{"data": map[string]any{
157
158
  "hostId": "host-1", "operationId": "operation-1", "agentId": "host-1",
158
- "agentBearerToken": "agent-secret-012345678901234567890",
159
+ "agentBearerToken": "agent-secret-012345678901234567890",
159
160
  "runtimeBearerToken": "runtime-secret-012345678901234567",
160
- "issuedAt": issuedAt,
161
+ "issuedAt": issuedAt,
161
162
  }})
162
163
  })
163
164
 
@@ -331,7 +332,7 @@ func TestComputeTaskCenterClientSurface(t *testing.T) {
331
332
  case 1:
332
333
  assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/tasks")
333
334
  assertQuery(t, r, map[string]string{
334
- "domain": "virtualization", "resourceKind": "cluster", "resourceId": "cluster-1", "limit": "1",
335
+ "domain": "virtualization", "resourceKind": "cluster", "resourceId": "cluster-1", "sortBy": "status", "sortOrder": "desc", "limit": "1",
335
336
  })
336
337
  writeJSON(t, w, map[string]any{"items": []any{}})
337
338
  case 2:
@@ -344,12 +345,18 @@ func TestComputeTaskCenterClientSurface(t *testing.T) {
344
345
  }}})
345
346
  case 4:
346
347
  assertCommonRequest(t, r, http.MethodPost, "/api/v1/compute/tasks/virtualization/task%2Fone/cancel")
348
+ if key := r.Header.Get("Idempotency-Key"); !strings.HasPrefix(key, "compute-") {
349
+ t.Fatalf("cancel Idempotency-Key = %q", key)
350
+ }
347
351
  if body := decodeJSONRequestBody[ComputeTaskMutationRequest](t, r); body.Reason != "operator request" {
348
352
  t.Fatalf("cancel reason = %q", body.Reason)
349
353
  }
350
354
  writeJSON(t, w, computeTaskEnvelopeFixture("task/one"))
351
355
  case 5:
352
356
  assertCommonRequest(t, r, http.MethodPost, "/api/v1/compute/tasks/virtualization/task%2Fone/retry")
357
+ if key := r.Header.Get("Idempotency-Key"); key != "retry-key-1" {
358
+ t.Fatalf("retry Idempotency-Key = %q", key)
359
+ }
353
360
  writeJSON(t, w, computeTaskEnvelopeFixture("task/two"))
354
361
  default:
355
362
  t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
@@ -357,7 +364,7 @@ func TestComputeTaskCenterClientSurface(t *testing.T) {
357
364
  })
358
365
 
359
366
  items, err := client.ListComputeTasks(context.Background(), ListComputeTasksParams{
360
- Domain: ComputeTaskDomainVirtualization, ResourceKind: "cluster", ResourceID: "cluster-1", Limit: 1,
367
+ Domain: ComputeTaskDomainVirtualization, ResourceKind: "cluster", ResourceID: "cluster-1", SortBy: "status", SortOrder: ListComputeTasksParamsSortOrder("desc"), Limit: 1,
361
368
  })
362
369
  if err != nil || len(items.Items) != 0 {
363
370
  t.Fatalf("ListComputeTasks = %#v, %v", items, err)
@@ -371,11 +378,127 @@ func TestComputeTaskCenterClientSurface(t *testing.T) {
371
378
  if _, err := client.CancelComputeTask(context.Background(), ComputeTaskDomainVirtualization, "task/one", ComputeTaskMutationRequest{Reason: "operator request"}); err != nil {
372
379
  t.Fatalf("CancelComputeTask returned error: %v", err)
373
380
  }
374
- if task, err := client.RetryComputeTask(context.Background(), ComputeTaskDomainVirtualization, "task/one", ComputeTaskMutationRequest{}); err != nil || task.ID != "task/two" {
381
+ if task, err := client.RetryComputeTaskWithKey(context.Background(), ComputeTaskDomainVirtualization, "task/one", "retry-key-1", ComputeTaskMutationRequest{}); err != nil || task.ID != "task/two" {
375
382
  t.Fatalf("RetryComputeTask = %#v, %v", task, err)
376
383
  }
377
384
  }
378
385
 
386
+ func TestComputeInventoryClientSurface(t *testing.T) {
387
+ requests := 0
388
+ client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
389
+ requests++
390
+ switch requests {
391
+ case 1:
392
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/capabilities")
393
+ writeJSON(t, w, map[string]any{"data": map[string]any{"generation": 1, "features": []any{}}})
394
+ case 2:
395
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/overview")
396
+ writeJSON(t, w, map[string]any{"data": map[string]any{"attention": []any{}, "providerHealth": []any{}, "partial": false, "warnings": []any{}}})
397
+ case 3:
398
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/access-sources")
399
+ assertQuery(t, r, map[string]string{"sourceType": "virtualization_connection", "providerKey": "pve", "cursor": "next", "limit": "10"})
400
+ writeJSON(t, w, map[string]any{"items": []any{}})
401
+ case 4:
402
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/providers")
403
+ assertQuery(t, r, map[string]string{"domain": "virtualization", "source": "builtin", "limit": "10"})
404
+ writeJSON(t, w, map[string]any{"items": []any{}})
405
+ case 5:
406
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/provider-instances")
407
+ assertQuery(t, r, map[string]string{"domain": "virtualization", "providerKey": "pve", "limit": "10"})
408
+ writeJSON(t, w, map[string]any{"items": []any{}})
409
+ case 6:
410
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/provider-instances/virtualization/pve/connection%2Fone")
411
+ writeJSON(t, w, map[string]any{"data": map[string]any{}})
412
+ case 7:
413
+ assertCommonRequest(t, r, http.MethodPost, "/api/v1/compute/provider-instances/virtualization/pve/connection%2Fone/health-checks")
414
+ if key := r.Header.Get("Idempotency-Key"); key != "health-key-1" {
415
+ t.Fatalf("health Idempotency-Key = %q", key)
416
+ }
417
+ writeJSON(t, w, computeTaskEnvelopeFixture("health-task"))
418
+ case 8:
419
+ assertCommonRequest(t, r, http.MethodPost, "/api/v1/compute/provider-instances/virtualization/pve/connection%2Fone/discoveries")
420
+ if key := r.Header.Get("Idempotency-Key"); key != "discover-key-1" {
421
+ t.Fatalf("discover Idempotency-Key = %q", key)
422
+ }
423
+ writeJSON(t, w, computeTaskEnvelopeFixture("discover-task"))
424
+ case 9:
425
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/resources/virtualization/vm/vm%2Fone")
426
+ writeJSON(t, w, map[string]any{"data": map[string]any{"id": "vm/one"}})
427
+ case 10:
428
+ assertCommonRequest(t, r, http.MethodGet, "/api/v1/compute/resources/virtualization/vm/vm%2Fone/relations")
429
+ assertQuery(t, r, map[string]string{"cursor": "next", "limit": "20"})
430
+ writeJSON(t, w, map[string]any{"data": map[string]any{"resource": map[string]any{"domain": "virtualization", "kind": "vm", "id": "vm/one", "displayName": "vm-one"}, "relations": []any{}}})
431
+ case 11:
432
+ assertCommonRequest(t, r, http.MethodPost, "/api/v1/compute/resources/virtualization/vm/vm%2Fone/actions/start")
433
+ if key := r.Header.Get("Idempotency-Key"); key != "action-key-1" {
434
+ t.Fatalf("action Idempotency-Key = %q", key)
435
+ }
436
+ writeJSON(t, w, computeTaskEnvelopeFixture("action-task"))
437
+ default:
438
+ t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
439
+ }
440
+ })
441
+
442
+ ctx := context.Background()
443
+ if _, err := client.GetComputeCapabilities(ctx); err != nil {
444
+ t.Fatal(err)
445
+ }
446
+ if _, err := client.GetComputeOverview(ctx); err != nil {
447
+ t.Fatal(err)
448
+ }
449
+ if _, err := client.ListComputeAccessSources(ctx, ListComputeAccessSourcesParams{SourceType: ComputeAccessSourceTypeVirtualizationConnection, ProviderKey: "pve", Cursor: "next", Limit: 10}); err != nil {
450
+ t.Fatal(err)
451
+ }
452
+ if _, err := client.ListComputeProviders(ctx, ListComputeProvidersParams{Domain: ComputeProviderDomainVirtualization, Source: ComputeProviderSourceBuiltin, Limit: 10}); err != nil {
453
+ t.Fatal(err)
454
+ }
455
+ if _, err := client.ListComputeProviderInstances(ctx, ListComputeProviderInstancesParams{Domain: ComputeProviderDomainVirtualization, ProviderKey: "pve", Limit: 10}); err != nil {
456
+ t.Fatal(err)
457
+ }
458
+ if _, err := client.GetComputeProviderInstance(ctx, ComputeProviderDomainVirtualization, "pve", "connection/one"); err != nil {
459
+ t.Fatal(err)
460
+ }
461
+ if _, err := client.CheckComputeProviderInstanceHealth(ctx, ComputeProviderDomainVirtualization, "pve", "connection/one", "health-key-1", ComputeProviderReadRequest{ExpectedGeneration: 1}); err != nil {
462
+ t.Fatal(err)
463
+ }
464
+ if _, err := client.DiscoverComputeProviderInstance(ctx, ComputeProviderDomainVirtualization, "pve", "connection/one", "discover-key-1", ComputeProviderDiscoverRequest{ExpectedGeneration: 1}); err != nil {
465
+ t.Fatal(err)
466
+ }
467
+ if _, err := client.GetComputeResource(ctx, ComputeDomainVirtualization, ComputeResourceKindVM, "vm/one"); err != nil {
468
+ t.Fatal(err)
469
+ }
470
+ if _, err := client.ListComputeResourceRelations(ctx, ComputeDomainVirtualization, ComputeResourceKindVM, "vm/one", ListComputeResourceRelationsParams{Cursor: "next", Limit: 20}); err != nil {
471
+ t.Fatal(err)
472
+ }
473
+ if _, err := client.ExecuteComputeResourceAction(ctx, ComputeDomainVirtualization, ComputeResourceKindVM, "vm/one", "start", "action-key-1", ComputeResourceActionRequest{}); err != nil {
474
+ t.Fatal(err)
475
+ }
476
+ }
477
+
478
+ func TestStreamComputeTask(t *testing.T) {
479
+ client := newTestClient(t, func(w http.ResponseWriter, r *http.Request) {
480
+ if r.Method != http.MethodGet || r.URL.EscapedPath() != "/api/v1/compute/tasks/virtualization/task%2Fone/stream" {
481
+ t.Fatalf("request = %s %s", r.Method, r.URL.EscapedPath())
482
+ }
483
+ if accept := r.Header.Get("Accept"); accept != "text/event-stream" {
484
+ t.Fatalf("Accept = %q", accept)
485
+ }
486
+ if r.Header.Get("Authorization") != "Bearer test-token" || r.Header.Get("User-Agent") != "sohaapi-test" {
487
+ t.Fatalf("unexpected authenticated stream headers: %#v", r.Header)
488
+ }
489
+ _, _ = io.WriteString(w, "event: snapshot\ndata: {\"type\":\"snapshot\",\"observedAt\":\"2026-08-24T12:00:00Z\",\"sequence\":1}\n\n")
490
+ })
491
+
492
+ var events []ComputeTaskStreamEvent
493
+ err := client.StreamComputeTask(context.Background(), ComputeTaskDomainVirtualization, "task/one", func(event ComputeTaskStreamEvent) error {
494
+ events = append(events, event)
495
+ return nil
496
+ })
497
+ if err != nil || len(events) != 1 || events[0].Sequence != 1 {
498
+ t.Fatalf("StreamComputeTask events=%#v err=%v", events, err)
499
+ }
500
+ }
501
+
379
502
  func TestResourceLogClientSurface(t *testing.T) {
380
503
  requests := []string{
381
504
  "/api/v1/clusters/cluster%2Fone/logs/query",