@opensoha/contracts 0.1.17 → 0.1.19

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 (275) hide show
  1. package/COMPATIBILITY.md +72 -0
  2. package/README.md +38 -0
  3. package/auth/catalog_generated.go +2 -2
  4. package/auth/outpost_headers.go +17 -0
  5. package/auth/outpost_headers_test.go +16 -0
  6. package/auth/permission-catalog.json +383 -1
  7. package/compat/consumer-revisions.json +7 -0
  8. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  9. package/compat/openapi-loose-boundaries-0.1.0.json +78 -46
  10. package/delivery/canonical.go +136 -0
  11. package/delivery/document.go +203 -0
  12. package/delivery/document.schema.json +1527 -0
  13. package/delivery/document_test.go +191 -0
  14. package/delivery/parser.go +155 -0
  15. package/evaluation/evaluation-run.schema.json +64 -10
  16. package/fixtures/json-schema/gateway-manifest/invalid/bad-execution-mode.json +73 -0
  17. package/fixtures/json-schema/gateway-manifest/invalid/unversioned-check.json +85 -0
  18. package/fixtures/json-schema/gateway-manifest/valid/checks.json +86 -0
  19. package/fixtures/json-schema/gateway-manifest/valid/versioned-task.json +73 -0
  20. package/fixtures/json-schema/network-ingest-event/invalid/vpn-probe-negative-rtt.json +34 -0
  21. package/fixtures/json-schema/network-ingest-event/invalid/vpn-stats-secret.json +27 -0
  22. package/fixtures/json-schema/network-ingest-event/valid/vpn-probe-batch.json +45 -0
  23. package/fixtures/json-schema/network-ingest-event/valid/vpn-tunnel-stats.json +26 -0
  24. package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-bad-token.json +15 -0
  25. package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-mode-override.json +16 -0
  26. package/fixtures/json-schema/network-runtime-protocol/valid/managed-vpn-macos-enrollment.json +27 -0
  27. package/fixtures/json-schema/network-runtime-protocol/valid/vpn-managed-connect.json +15 -0
  28. package/fixtures/openapi/AgentChatInput/invalid/empty-question.json +4 -0
  29. package/fixtures/openapi/AgentChatInput/invalid/system-role.json +9 -0
  30. package/fixtures/openapi/AgentChatInput/valid/authorized-background.json +5 -0
  31. package/fixtures/openapi/AgentChatInput/valid/conversation.json +15 -0
  32. package/fixtures/openapi/AgentRunnerToolCallRequest/invalid/model-identity.json +1 -0
  33. package/fixtures/openapi/AgentRunnerToolCallRequest/valid/search.json +1 -0
  34. package/fixtures/openapi/ApplicationInput/invalid/negative-version.json +6 -0
  35. package/fixtures/openapi/ApplicationInput/invalid/zero-version.json +6 -0
  36. package/fixtures/openapi/ApplicationInput/valid/versioned.json +6 -0
  37. package/fixtures/openapi/ApprovalRequest/invalid/resolved-scopes.json +20 -0
  38. package/fixtures/openapi/ApprovalRequest/valid/resolved-scopes.json +21 -0
  39. package/fixtures/openapi/BuildTemplateInput/invalid/negative-revision.json +6 -0
  40. package/fixtures/openapi/BuildTemplateInput/valid/draft.json +11 -0
  41. package/fixtures/openapi/BuildTriggerRequest/invalid/no-ref.json +1 -0
  42. package/fixtures/openapi/BuildTriggerRequest/valid/no-environment.json +1 -0
  43. package/fixtures/openapi/BuildpacksConfiguration/invalid/mutable-builder.json +5 -0
  44. package/fixtures/openapi/BuildpacksConfiguration/invalid/privilege-override.json +6 -0
  45. package/fixtures/openapi/BuildpacksConfiguration/invalid/unsupported-platform.json +5 -0
  46. package/fixtures/openapi/BuildpacksConfiguration/valid/pinned.json +5 -0
  47. package/fixtures/openapi/CapabilityAssessment/invalid/contract.json +16 -0
  48. package/fixtures/openapi/CapabilityAssessment/valid/contract.json +16 -0
  49. package/fixtures/openapi/CapabilityPlan/invalid/contract.json +16 -0
  50. package/fixtures/openapi/CapabilityPlan/valid/contract.json +19 -0
  51. package/fixtures/openapi/CapabilityPlanValidation/invalid/contract.json +5 -0
  52. package/fixtures/openapi/CapabilityPlanValidation/valid/contract.json +11 -0
  53. package/fixtures/openapi/CapabilityTask/invalid/contract.json +33 -0
  54. package/fixtures/openapi/CapabilityTask/valid/contract.json +34 -0
  55. package/fixtures/openapi/CapabilityTaskInput/invalid/contract.json +22 -0
  56. package/fixtures/openapi/CapabilityTaskInput/valid/contract.json +22 -0
  57. package/fixtures/openapi/CapabilityValueSemantic/invalid/contract.json +4 -0
  58. package/fixtures/openapi/CapabilityValueSemantic/valid/contract.json +7 -0
  59. package/fixtures/openapi/ConnectionCheckResult/invalid/missing-health.json +1 -0
  60. package/fixtures/openapi/ConnectionCheckResult/invalid/task-id.json +1 -0
  61. package/fixtures/openapi/ConnectionCheckResult/valid/failure.json +1 -0
  62. package/fixtures/openapi/ConnectionCheckResult/valid/healthy.json +1 -0
  63. package/fixtures/openapi/DeliveryAccessResult/invalid/release-verification.json +8 -0
  64. package/fixtures/openapi/DeliveryAccessResult/valid/release-verification.json +8 -0
  65. package/fixtures/openapi/DeliveryBatchAssessment/invalid/release-verification.json +11 -0
  66. package/fixtures/openapi/DeliveryBatchAssessment/valid/release-verification.json +11 -0
  67. package/fixtures/openapi/DeliveryBatchAssessmentInput/invalid/release-verification.json +9 -0
  68. package/fixtures/openapi/DeliveryBatchAssessmentInput/valid/release-verification.json +8 -0
  69. package/fixtures/openapi/DeliveryBatchInput/invalid/ambiguous-source.json +19 -0
  70. package/fixtures/openapi/DeliveryBatchInput/invalid/missing-idempotency.json +16 -0
  71. package/fixtures/openapi/DeliveryBatchInput/valid/quick-deploy.json +17 -0
  72. package/fixtures/openapi/DeliveryBatchInput/valid/saved-workflow.json +5 -0
  73. package/fixtures/openapi/DeliveryBlueprintInput/invalid/unversioned-preset.json +31 -0
  74. package/fixtures/openapi/DeliveryBlueprintInput/valid/service-preset.json +32 -0
  75. package/fixtures/openapi/DeliveryDocument/invalid/unknown-field.json +24 -0
  76. package/fixtures/openapi/DeliveryDocument/invalid/unknown-kind.json +23 -0
  77. package/fixtures/openapi/DeliveryDocument/invalid/unknown-version.json +23 -0
  78. package/fixtures/openapi/DeliveryDocument/valid/build-template.json +26 -0
  79. package/fixtures/openapi/DeliveryDocument/valid/deployment-template.json +26 -0
  80. package/fixtures/openapi/DeliveryDocument/valid/legacy-dag.json +39 -0
  81. package/fixtures/openapi/DeliveryDocument/valid/workflow-template.json +23 -0
  82. package/fixtures/openapi/DeliveryDocument/valid/workflow.json +20 -0
  83. package/fixtures/openapi/DeliveryDraftInput/invalid/short-idempotency-key.json +30 -0
  84. package/fixtures/openapi/DeliveryDraftInput/valid/idempotent-create.json +30 -0
  85. package/fixtures/openapi/DeliveryDraftService/invalid/zero-version.json +7 -0
  86. package/fixtures/openapi/DeliveryDraftService/valid/versioned-template.json +9 -0
  87. package/fixtures/openapi/DeliveryExecutionHistoryPage/invalid-kind.json +9 -0
  88. package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-build.json +16 -0
  89. package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-empty.json +3 -0
  90. package/fixtures/openapi/DeliveryPlanApprovalInput/invalid/confirm.json +1 -0
  91. package/fixtures/openapi/DeliveryPlanApprovalInput/valid/approve.json +1 -0
  92. package/fixtures/openapi/DeliveryPlanInput/invalid/draft-manifest-revision.json +1 -0
  93. package/fixtures/openapi/DeliveryPlanInput/valid/manifest-revision.json +1 -0
  94. package/fixtures/openapi/DeliveryTargetInput/invalid/deploy-without-environment.json +7 -0
  95. package/fixtures/openapi/DeliveryTargetInput/valid/build-without-environment.json +6 -0
  96. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/duplicate-kind.json +13 -0
  97. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/embedded-credentials.json +16 -0
  98. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/empty-kinds.json +10 -0
  99. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/negative-generation.json +15 -0
  100. package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/unknown-kind.json +12 -0
  101. package/fixtures/openapi/DeliveryTemplateSourceInput/valid/manual-source.json +15 -0
  102. package/fixtures/openapi/DeliveryWorkflowDefinition/invalid/unversioned-template.json +15 -0
  103. package/fixtures/openapi/DeliveryWorkflowDefinition/valid/cross-app.json +24 -0
  104. package/fixtures/openapi/DeliveryWorkflowInput/invalid/short-key.json +1 -0
  105. package/fixtures/openapi/DeliveryWorkflowInput/valid/idempotent-create.json +1 -0
  106. package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-target-override.json +7 -0
  107. package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-zero-version.json +6 -0
  108. package/fixtures/openapi/DeploymentTemplatePreviewInput/valid/valid.json +6 -0
  109. package/fixtures/openapi/DockerDeliveryConfiguration/invalid/empty-mappings.json +5 -0
  110. package/fixtures/openapi/DockerDeliveryConfiguration/valid/compose.json +7 -0
  111. package/fixtures/openapi/DockerDeliverySnapshot/invalid/missing-required-field.json +19 -0
  112. package/fixtures/openapi/DockerDeliverySnapshot/valid/frozen-project.json +20 -0
  113. package/fixtures/openapi/DockerOperationCallbackRequest/invalid/cancellation-acknowledged-not-boolean.json +13 -0
  114. package/fixtures/openapi/DockerOperationCallbackRequest/valid/cancellation-acknowledged.json +13 -0
  115. package/fixtures/openapi/DockerProjectInput/invalid/empty-host.json +4 -0
  116. package/fixtures/openapi/DockerProjectInput/valid/inline-idempotent.json +7 -0
  117. package/fixtures/openapi/DockerQuickCreateHostInput/invalid/capacity-type.json +1 -0
  118. package/fixtures/openapi/DockerQuickCreateHostInput/valid/capacity.json +1 -0
  119. package/fixtures/openapi/HelmDeliveryConfiguration/invalid/plain-credential.json +21 -0
  120. package/fixtures/openapi/HelmDeliveryConfiguration/invalid/release-name.json +20 -0
  121. package/fixtures/openapi/HelmDeliveryConfiguration/valid/private-oci.json +20 -0
  122. package/fixtures/openapi/HelmDeliverySnapshot/invalid/secret-plan.json +31 -0
  123. package/fixtures/openapi/HelmDeliverySnapshot/invalid/unknown-operation.json +28 -0
  124. package/fixtures/openapi/HelmDeliverySnapshot/valid/first-install.json +28 -0
  125. package/fixtures/openapi/IdentityApplication/invalid/outpost-status.json +1 -0
  126. package/fixtures/openapi/IdentityApplicationOnboardingInput/invalid/missing-access.json +12 -0
  127. package/fixtures/openapi/IdentityApplicationOnboardingInput/valid/link.json +13 -0
  128. package/fixtures/openapi/IdentityOutpost/invalid/missing-runtime-status.json +9 -0
  129. package/fixtures/openapi/IdentityOutpost/valid/healthy.json +17 -0
  130. package/fixtures/openapi/IdentityOutpost/valid/unregistered.json +12 -0
  131. package/fixtures/openapi/IdentityOutpostDeployment/invalid/missing-protocol.json +3 -0
  132. package/fixtures/openapi/IdentityOutpostDeployment/valid/remote.json +6 -0
  133. package/fixtures/openapi/IdentityOutpostInput/invalid/forged-observation.json +7 -0
  134. package/fixtures/openapi/IdentityOutpostInput/valid/forward-auth.json +6 -0
  135. package/fixtures/openapi/IdentityOutpostInput/valid/legacy.json +8 -0
  136. package/fixtures/openapi/IdentityProviderSetup/invalid/fake-verification.json +1 -0
  137. package/fixtures/openapi/IdentityProviderSetup/valid/incomplete.json +1 -0
  138. package/fixtures/openapi/IdentityProviderSetup/valid/remote.json +1 -0
  139. package/fixtures/openapi/IdentityProviderUserMetadata/invalid/attributes.json +8 -0
  140. package/fixtures/openapi/IdentityProviderUserMetadata/valid/attributes.json +13 -0
  141. package/fixtures/openapi/KubernetesCustomResource/invalid/negative-generation.json +1 -0
  142. package/fixtures/openapi/KubernetesCustomResource/valid/deleting-operator.json +1 -0
  143. package/fixtures/openapi/KubernetesResourceCreationReceipt/invalid/manifest-content.json +20 -0
  144. package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/confirmed.json +20 -0
  145. package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/unknown.json +19 -0
  146. package/fixtures/openapi/ManifestDeploymentEnvelope/invalid/negative-resource-observation.json +57 -0
  147. package/fixtures/openapi/ManifestDeploymentEnvelope/valid/operator-deleting.json +57 -0
  148. package/fixtures/openapi/ManifestExecutionTaskPayload/invalid/gitops-empty.json +7 -0
  149. package/fixtures/openapi/ManifestExecutionTaskPayload/valid/gitops-frozen-children.json +18 -0
  150. package/fixtures/openapi/ManifestKustomizeOptions/invalid/escaping-entry.json +1 -0
  151. package/fixtures/openapi/ManifestKustomizeOptions/invalid/mutable-image.json +1 -0
  152. package/fixtures/openapi/ManifestKustomizeOptions/valid/overlay-images.json +7 -0
  153. package/fixtures/openapi/ManifestRevisionInput/invalid/missing-version.json +1 -0
  154. package/fixtures/openapi/ManifestRevisionInput/valid/current-draft.json +1 -0
  155. package/fixtures/openapi/ManifestServiceTemplateInputs/invalid/mutable-image.json +8 -0
  156. package/fixtures/openapi/ManifestServiceTemplateInputs/valid/frozen-service-image.json +9 -0
  157. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/auto-with-gateway.json +6 -0
  158. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/missing-manual-gateway.json +5 -0
  159. package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/mode-override.json +7 -0
  160. package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/auto-intent.json +5 -0
  161. package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/manual-intent.json +6 -0
  162. package/fixtures/openapi/NetworkVPNProfileInput/invalid/unknown-private-key.json +25 -0
  163. package/fixtures/openapi/NetworkVPNProfileInput/valid/assigned-profile.json +24 -0
  164. package/fixtures/openapi/NetworkVPNSelectionPolicyInput/invalid/unbounded-attempts.json +21 -0
  165. package/fixtures/openapi/NetworkVPNSelectionPolicyInput/valid/provider-policy.json +21 -0
  166. package/fixtures/openapi/ObservabilityMetricAssessmentInput/invalid/arbitrary-expression.json +10 -0
  167. package/fixtures/openapi/ObservabilityMetricAssessmentInput/valid/metric-window.json +10 -0
  168. package/fixtures/openapi/PluginManifest/valid/hermes-api.json +53 -0
  169. package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/missing-identity.json +5 -0
  170. package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/skip-analysis.json +5 -0
  171. package/fixtures/openapi/ProgressiveRolloutControlInput/valid/pause.json +5 -0
  172. package/fixtures/openapi/ReleaseTargetInput/invalid/missing-required-field.json +15 -0
  173. package/fixtures/openapi/ReleaseTargetInput/valid/docker-compose.json +16 -0
  174. package/fixtures/openapi/RepositoryAnalysis/invalid/moving-ref.json +11 -0
  175. package/fixtures/openapi/RepositoryAnalysis/valid/go.json +29 -0
  176. package/fixtures/openapi/RepositoryAnalysisInput/invalid/arbitrary-source-url.json +6 -0
  177. package/fixtures/openapi/RepositoryAnalysisInput/invalid/missing-ref.json +4 -0
  178. package/fixtures/openapi/RepositoryAnalysisInput/valid/nested-project.json +6 -0
  179. package/fixtures/openapi/ServiceDeploymentTemplateBinding/invalid/invalid-unpinned.json +4 -0
  180. package/fixtures/openapi/ServiceDeploymentTemplateBinding/valid/valid.json +7 -0
  181. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-executable-schema.json +25 -0
  182. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-missing-schema.json +21 -0
  183. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-mixed-renderers.json +40 -0
  184. package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-nested-parameters.json +41 -0
  185. package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-yaml.json +34 -0
  186. package/fixtures/openapi/TemplatePublishInput/invalid/missing-revision.json +1 -0
  187. package/fixtures/openapi/TemplatePublishInput/invalid/unknown-field.json +4 -0
  188. package/fixtures/openapi/TemplatePublishInput/invalid/zero-revision.json +3 -0
  189. package/fixtures/openapi/TemplatePublishInput/valid/revision.json +3 -0
  190. package/fixtures/openapi/ToolCapability/invalid/bad-idempotency.json +35 -0
  191. package/fixtures/openapi/ToolCapability/invalid/unversioned-check.json +47 -0
  192. package/fixtures/openapi/ToolCapability/valid/checks.json +48 -0
  193. package/fixtures/openapi/ToolCapability/valid/versioned-task.json +35 -0
  194. package/fixtures/openapi/ToolInvocationRequest/invalid/empty-version.json +8 -0
  195. package/fixtures/openapi/ToolInvocationRequest/valid/versioned.json +8 -0
  196. package/fixtures/openapi/ToolInvocationResult/invalid/missing-status-call.json +13 -0
  197. package/fixtures/openapi/ToolInvocationResult/valid/task.json +20 -0
  198. package/fixtures/openapi/UpdateBrandingSettingsRequest/invalid/dark-logo-type.json +9 -0
  199. package/fixtures/openapi/UpdateBrandingSettingsRequest/valid/dark-logos.json +10 -0
  200. package/fixtures/openapi/VirtualMachineCreateInput/invalid/capacity-type.json +1 -0
  201. package/fixtures/openapi/VirtualMachineCreateInput/valid/capacity.json +1 -0
  202. package/fixtures/openapi/VirtualizationCapacityInput/invalid/missing-disk.json +1 -0
  203. package/fixtures/openapi/VirtualizationCapacityInput/valid/running-vm.json +1 -0
  204. package/fixtures/openapi/VirtualizationCapacityResult/invalid/invented-status.json +1 -0
  205. package/fixtures/openapi/VirtualizationCapacityResult/valid/available.json +1 -0
  206. package/fixtures/openapi/VirtualizationWorkerCreateInput/invalid/secret-input.json +5 -0
  207. package/fixtures/openapi/VirtualizationWorkerCreateInput/valid/one-worker.json +4 -0
  208. package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/external-owner.json +27 -0
  209. package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/zero-budget.json +27 -0
  210. package/fixtures/openapi/VirtualizationWorkerPoolInput/valid/registered.json +27 -0
  211. package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/invalid/nested-data.json +5 -0
  212. package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/valid/empty.json +3 -0
  213. package/fixtures/openapi/WorkbenchContextSelection/invalid/missing-file-content.json +8 -0
  214. package/fixtures/openapi/WorkbenchContextSelection/invalid/unknown-reference-kind.json +8 -0
  215. package/fixtures/openapi/WorkbenchContextSelection/valid/text-and-resource.json +17 -0
  216. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/trigger-without-plan.json +10 -0
  217. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/unknown-trigger.json +28 -0
  218. package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/zero-interval.json +28 -0
  219. package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/alert-capability.json +30 -0
  220. package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/scheduled-capability.json +28 -0
  221. package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/missing-rule.json +3 -0
  222. package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/schedule-alert-selector.json +4 -0
  223. package/fixtures/openapi/WorkbenchInspectionTrigger/valid/schedule.json +3 -0
  224. package/fixtures/openapi/WorkbenchModelPreferences/invalid/unknown-effort.json +1 -0
  225. package/fixtures/openapi/WorkbenchModelPreferences/valid/selected.json +1 -0
  226. package/fixtures/openapi/WorkflowCatalogPage/invalid/unsupported-kind.json +6 -0
  227. package/fixtures/openapi/WorkflowCatalogPage/valid/build-without-environment.json +6 -0
  228. package/fixtures/openapi/WorkflowTemplateInput/invalid/zero-revision.json +6 -0
  229. package/fixtures/openapi/WorkflowTemplateInput/valid/draft.json +10 -0
  230. package/gen/go/sohaapi/client.go +11 -3
  231. package/gen/go/sohaapi/client_test.go +26 -3
  232. package/gen/go/sohaapi/types.go +8103 -863
  233. package/gen/go/sohaapi/types_test.go +100 -1
  234. package/gen/go/sohaapi/vpn_models_test.go +21 -0
  235. package/gen/ts/sohaapi/index.ts +20390 -13160
  236. package/go.mod +107 -5
  237. package/go.sum +379 -16
  238. package/helmrelease/delivery.go +283 -0
  239. package/helmrelease/delivery_test.go +102 -0
  240. package/helmrelease/frozen_chart.go +91 -0
  241. package/helmrelease/result.go +27 -0
  242. package/helmrelease/runtime/delivery.go +508 -0
  243. package/helmrelease/runtime/delivery_test.go +236 -0
  244. package/helmrelease/runtime/resources_test.go +60 -0
  245. package/helmrelease/values.go +99 -0
  246. package/knowledge/context-envelope.schema.json +1 -0
  247. package/mcp/gateway-manifest.schema.json +387 -72
  248. package/network/README.md +40 -0
  249. package/network/network-ingest-event.schema.json +228 -0
  250. package/network/network-runtime-protocol.schema.json +457 -1
  251. package/openapi/soha-api.json +54874 -38722
  252. package/openapi/soha-api.yaml +12199 -4982
  253. package/openapi/spec_generated.go +3 -3
  254. package/package.json +3 -1
  255. package/resource/manifest_health.go +155 -0
  256. package/resource/manifest_health_test.go +37 -0
  257. package/resource/models.go +14 -0
  258. package/resource/runtime/argocd.go +166 -0
  259. package/resource/runtime/argocd_integration_test.go +142 -0
  260. package/resource/runtime/argocd_observation.go +143 -0
  261. package/resource/runtime/argocd_operation.go +253 -0
  262. package/resource/runtime/argocd_resources.go +115 -0
  263. package/resource/runtime/argocd_resources_test.go +107 -0
  264. package/resource/runtime/argocd_test.go +386 -0
  265. package/resource/runtime/health.go +115 -0
  266. package/resource/runtime/health_test.go +78 -0
  267. package/resource/runtime/operator_integration_test.go +174 -0
  268. package/resource/runtime/ownership.go +178 -0
  269. package/resource/runtime/ownership_test.go +178 -0
  270. package/resource/runtime/rollout_integration_test.go +287 -0
  271. package/resource/runtime/rollout_observation.go +252 -0
  272. package/resource/runtime/rollout_operation.go +398 -0
  273. package/resource/runtime/rollout_resources.go +385 -0
  274. package/resource/runtime/rollout_task.go +165 -0
  275. package/resource/runtime/rollout_test.go +450 -0
@@ -0,0 +1,398 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "reflect"
9
+ "time"
10
+
11
+ "github.com/opensoha/soha-contracts/gen/go/sohaapi"
12
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
13
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15
+ "k8s.io/apimachinery/pkg/types"
16
+ "k8s.io/client-go/dynamic"
17
+ "k8s.io/client-go/util/retry"
18
+ )
19
+
20
+ var ErrRolloutStopUnconfirmed = errors.New("rollout stop is not confirmed")
21
+
22
+ // ApplyRollout delegates reconciliation and analysis to Argo Rollouts. Its
23
+ // operation annotation resumes an accepted write after a worker restart.
24
+ func ApplyRollout(ctx context.Context, client dynamic.Interface, documents []*unstructured.Unstructured, options metav1.PatchOptions, operation string) (*unstructured.Unstructured, error) {
25
+ desired, err := ValidateRolloutResources(documents)
26
+ if err != nil {
27
+ return nil, err
28
+ }
29
+ if options.FieldManager == "" || operation == "" || options.Force != nil && *options.Force {
30
+ return nil, fmt.Errorf("rollout requires a stable non-forced owner and operation")
31
+ }
32
+ observed := map[string]*unstructured.Unstructured{}
33
+ for _, document := range documents {
34
+ gvk := document.GroupVersionKind()
35
+ live, getErr := client.Resource(rolloutResourceTypes[gvk.Group+"/"+gvk.Kind]).Namespace(document.GetNamespace()).Get(ctx, document.GetName(), metav1.GetOptions{})
36
+ if apierrors.IsNotFound(getErr) {
37
+ continue
38
+ }
39
+ if getErr != nil {
40
+ return nil, getErr
41
+ }
42
+ if err := validateRolloutOwner(live, options.FieldManager, desired.GetName()); err != nil {
43
+ return nil, err
44
+ }
45
+ observed[document.GetKind()+"/"+document.GetName()] = live
46
+ }
47
+ root := observed["Rollout/"+desired.GetName()]
48
+ if root != nil && root.GetAnnotations()[RolloutOperationAnnotation] == operation && len(options.DryRun) == 0 {
49
+ if !RolloutMatchesDesired(desired, root) {
50
+ return root, fmt.Errorf("accepted rollout operation no longer matches its frozen spec")
51
+ }
52
+ return waitRollout(ctx, client, documents, options.FieldManager, operation, root)
53
+ }
54
+ if root != nil {
55
+ state := rolloutState(root)
56
+ if state.ObservedGeneration == nil || *state.ObservedGeneration != state.Generation || state.Phase != "Healthy" && !state.Aborted {
57
+ return root, fmt.Errorf("another rollout operation is still active")
58
+ }
59
+ if state.Aborted {
60
+ observation, err := ObserveRollout(ctx, client, rolloutPreviousDocuments(documents, root), options.FieldManager, "")
61
+ if err != nil || !rolloutStopped(observation.State) {
62
+ return root, fmt.Errorf("previous rollout has not restored stable traffic")
63
+ }
64
+ }
65
+ }
66
+ ordered := make([]*unstructured.Unstructured, 0, len(documents))
67
+ for _, document := range documents {
68
+ if !IsArgoRollout(document) {
69
+ ordered = append(ordered, document)
70
+ }
71
+ }
72
+ ordered = append(ordered, desired)
73
+ for _, document := range ordered {
74
+ gvk := document.GroupVersionKind()
75
+ resources := client.Resource(rolloutResourceTypes[gvk.Group+"/"+gvk.Kind]).Namespace(document.GetNamespace())
76
+ live := observed[document.GetKind()+"/"+document.GetName()]
77
+ // The native router owns an atomic weighted backend list after creation.
78
+ // Changing its identities requires a separate target, never force SSA.
79
+ if live != nil && document.GetKind() == "TraefikService" {
80
+ if !RolloutMatchesDesired(document, live) {
81
+ return root, fmt.Errorf("weighted backend identities changed; use a new rollout target")
82
+ }
83
+ continue
84
+ }
85
+ write := document.DeepCopy()
86
+ if live != nil && document.GetKind() == "Service" {
87
+ wanted, _, _ := unstructured.NestedMap(document.Object, "spec", "selector")
88
+ actual, _, _ := unstructured.NestedMap(live.Object, "spec", "selector")
89
+ if !argoMatchesDesired(wanted, actual) {
90
+ return root, fmt.Errorf("rollout Service selector changed; use a new rollout target")
91
+ }
92
+ unstructured.RemoveNestedField(write.Object, "spec", "selector")
93
+ }
94
+ annotations := write.GetAnnotations()
95
+ if annotations == nil {
96
+ annotations = map[string]string{}
97
+ }
98
+ annotations[RolloutOwnerAnnotation] = options.FieldManager + ":" + desired.GetName()
99
+ if IsArgoRollout(write) {
100
+ annotations[RolloutOperationAnnotation] = operation
101
+ }
102
+ write.SetAnnotations(annotations)
103
+ var applied *unstructured.Unstructured
104
+ if live == nil {
105
+ applied, err = resources.Create(ctx, write, metav1.CreateOptions{FieldManager: options.FieldManager, DryRun: options.DryRun})
106
+ } else {
107
+ applied, err = updateRolloutResource(ctx, resources, write, live, options, desired.GetName())
108
+ }
109
+ if IsArgoRollout(document) && applied != nil {
110
+ root = applied
111
+ }
112
+ if err != nil {
113
+ if IsArgoRollout(document) && len(options.DryRun) == 0 {
114
+ query, stop := context.WithTimeout(context.Background(), 30*time.Second)
115
+ defer stop()
116
+ accepted, readErr := resources.Get(query, document.GetName(), metav1.GetOptions{})
117
+ if readErr != nil {
118
+ return root, errors.Join(err, ErrRolloutStopUnconfirmed, readErr)
119
+ }
120
+ if accepted.GetAnnotations()[RolloutOperationAnnotation] == operation && validateRolloutOwner(accepted, options.FieldManager, desired.GetName()) == nil {
121
+ return accepted, errors.Join(err, StopRollout(query, client, documents, options.FieldManager, operation, string(accepted.GetUID())))
122
+ }
123
+ }
124
+ return root, err
125
+ }
126
+ }
127
+ if len(options.DryRun) != 0 {
128
+ return root, nil
129
+ }
130
+ return waitRollout(ctx, client, documents, options.FieldManager, operation, root)
131
+ }
132
+
133
+ func updateRolloutResource(ctx context.Context, client dynamic.ResourceInterface, desired, initial *unstructured.Unstructured, options metav1.PatchOptions, rootName string) (*unstructured.Unstructured, error) {
134
+ var applied *unstructured.Unstructured
135
+ err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
136
+ live, err := client.Get(ctx, desired.GetName(), metav1.GetOptions{})
137
+ if err != nil {
138
+ return err
139
+ }
140
+ if live.GetUID() != initial.GetUID() || validateRolloutOwner(live, options.FieldManager, rootName) != nil {
141
+ return fmt.Errorf("%w: rollout resource identity changed", ErrResourceOwnership)
142
+ }
143
+ if IsArgoRollout(live) && (live.GetAnnotations()[RolloutOperationAnnotation] != initial.GetAnnotations()[RolloutOperationAnnotation] || !reflect.DeepEqual(live.Object["spec"], initial.Object["spec"])) {
144
+ return fmt.Errorf("rollout changed while preparing the approved operation")
145
+ }
146
+ write := live.DeepCopy()
147
+ spec, _, _ := unstructured.NestedMap(desired.Object, "spec")
148
+ if live.GetKind() == "Service" {
149
+ // Service selector is atomic and belongs to Rollouts; cluster-assigned
150
+ // addresses also survive subsequent approved configuration updates.
151
+ // Match API defaults so an unchanged Service does not contend with
152
+ // the controller continuously updating its selector during an abort.
153
+ ports, _ := spec["ports"].([]any)
154
+ for _, raw := range ports {
155
+ port, _ := raw.(map[string]any)
156
+ if port["protocol"] == nil {
157
+ port["protocol"] = "TCP"
158
+ }
159
+ if port["targetPort"] == nil || port["targetPort"] == int64(0) {
160
+ port["targetPort"] = port["port"]
161
+ }
162
+ }
163
+ merged, _, _ := unstructured.NestedMap(live.Object, "spec")
164
+ for key, value := range spec {
165
+ if key != "selector" {
166
+ merged[key] = value
167
+ }
168
+ }
169
+ spec = merged
170
+ }
171
+ write.Object["spec"] = spec
172
+ annotations := write.GetAnnotations()
173
+ if annotations == nil {
174
+ annotations = map[string]string{}
175
+ }
176
+ for key, value := range desired.GetAnnotations() {
177
+ annotations[key] = value
178
+ }
179
+ write.SetAnnotations(annotations)
180
+ labels := write.GetLabels()
181
+ if labels == nil {
182
+ labels = map[string]string{}
183
+ }
184
+ for key, value := range desired.GetLabels() {
185
+ labels[key] = value
186
+ }
187
+ if len(labels) > 0 {
188
+ write.SetLabels(labels)
189
+ }
190
+ if len(options.DryRun) == 0 && reflect.DeepEqual(write.Object, live.Object) {
191
+ applied = live
192
+ return nil
193
+ }
194
+ applied, err = client.Update(ctx, write, metav1.UpdateOptions{FieldManager: options.FieldManager, DryRun: options.DryRun})
195
+ return err
196
+ })
197
+ return applied, err
198
+ }
199
+
200
+ // Only the old Rollout spec is needed to observe stable traffic before replacing
201
+ // an aborted template; dependency identities must still match the approved plan.
202
+ func rolloutPreviousDocuments(documents []*unstructured.Unstructured, live *unstructured.Unstructured) []*unstructured.Unstructured {
203
+ result := make([]*unstructured.Unstructured, 0, len(documents))
204
+ for _, document := range documents {
205
+ if !IsArgoRollout(document) {
206
+ result = append(result, document)
207
+ continue
208
+ }
209
+ prior := document.DeepCopy()
210
+ prior.Object["spec"], _, _ = unstructured.NestedMap(live.Object, "spec")
211
+ unstructured.RemoveNestedField(prior.Object, "spec", "paused")
212
+ result = append(result, prior)
213
+ }
214
+ return result
215
+ }
216
+
217
+ func waitRollout(ctx context.Context, client dynamic.Interface, documents []*unstructured.Unstructured, owner, operation string, initial *unstructured.Unstructured) (*unstructured.Unstructured, error) {
218
+ ticker := time.NewTicker(time.Second)
219
+ defer ticker.Stop()
220
+ for {
221
+ observation, err := ObserveRollout(ctx, client, documents, owner, operation)
222
+ var live *unstructured.Unstructured
223
+ for _, object := range observation.Resources {
224
+ if IsArgoRollout(object) {
225
+ live = object
226
+ break
227
+ }
228
+ }
229
+ if live != nil && live.GetUID() != initial.GetUID() {
230
+ return live, fmt.Errorf("rollout identity changed while waiting")
231
+ }
232
+ if err == nil && observation.Health == "healthy" {
233
+ return live, nil
234
+ }
235
+ if err != nil || observation.Health == "degraded" {
236
+ if err == nil {
237
+ err = fmt.Errorf("rollout failed its health or analysis gate")
238
+ }
239
+ stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
240
+ stopErr := StopRollout(stopCtx, client, documents, owner, operation, string(initial.GetUID()))
241
+ cancel()
242
+ return live, errors.Join(err, stopErr)
243
+ }
244
+ select {
245
+ case <-ctx.Done():
246
+ stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
247
+ stopErr := StopRollout(stopCtx, client, documents, owner, operation, string(initial.GetUID()))
248
+ cancel()
249
+ return live, errors.Join(ctx.Err(), stopErr)
250
+ case <-ticker.C:
251
+ }
252
+ }
253
+ }
254
+
255
+ // ControlRollout never skips an analysis or timed pause. A stale browser action
256
+ // cannot accidentally promote the next pause because both UID and RV are tested.
257
+ func ControlRollout(ctx context.Context, client dynamic.Interface, documents []*unstructured.Unstructured, owner, operation string, input sohaapi.ProgressiveRolloutControlInput) (RolloutObservation, error) {
258
+ observation, err := ObserveRollout(ctx, client, documents, owner, operation)
259
+ if err != nil {
260
+ return observation, err
261
+ }
262
+ state := observation.State
263
+ if input.UID == "" || input.ResourceVersion == "" || input.UID != state.UID || input.ResourceVersion != state.ResourceVersion {
264
+ return observation, apierrors.NewConflict(rollouts.GroupResource(), state.Name, fmt.Errorf("rollout changed; refresh before controlling it"))
265
+ }
266
+ if observation.Drifted {
267
+ return observation, fmt.Errorf("rollout drift must be resolved before control")
268
+ }
269
+ if state.Aborted || state.Phase == "Healthy" {
270
+ return observation, fmt.Errorf("rollout is no longer progressing")
271
+ }
272
+ if state.ObservedGeneration == nil || *state.ObservedGeneration != state.Generation {
273
+ return observation, fmt.Errorf("wait for the rollout controller to observe the current generation")
274
+ }
275
+ var root *unstructured.Unstructured
276
+ for _, object := range observation.Resources {
277
+ if IsArgoRollout(object) {
278
+ root = object
279
+ }
280
+ }
281
+ field, changes := "spec", map[string]any{}
282
+ switch input.Action {
283
+ case "pause":
284
+ changes["paused"] = true
285
+ case "abort":
286
+ field, changes = "status", map[string]any{"abort": true}
287
+ case "promote":
288
+ manual, _, _ := unstructured.NestedBool(root.Object, "spec", "paused")
289
+ if manual {
290
+ changes["paused"] = false
291
+ break
292
+ }
293
+ if len(state.PauseReasons) != 1 {
294
+ return observation, fmt.Errorf("promotion requires a manual pause")
295
+ }
296
+ switch state.PauseReasons[0] {
297
+ case "BlueGreenPause":
298
+ if len(state.Metrics) == 0 {
299
+ return observation, fmt.Errorf("pre-promotion analysis is not observed")
300
+ }
301
+ for _, metric := range state.Metrics {
302
+ if metric.Phase != "Successful" {
303
+ return observation, fmt.Errorf("pre-promotion analysis has not succeeded")
304
+ }
305
+ }
306
+ case "CanaryPauseStep":
307
+ steps, _, _ := unstructured.NestedSlice(root.Object, "spec", "strategy", "canary", "steps")
308
+ if state.CurrentStep == nil || *state.CurrentStep >= len(steps) {
309
+ return observation, fmt.Errorf("canary pause has no current step")
310
+ }
311
+ step, _ := steps[*state.CurrentStep].(map[string]any)
312
+ pause, ok := step["pause"].(map[string]any)
313
+ if !ok || len(pause) != 0 {
314
+ return observation, fmt.Errorf("a timed observation pause cannot be skipped")
315
+ }
316
+ default:
317
+ return observation, fmt.Errorf("analysis pauses cannot be overridden")
318
+ }
319
+ field, changes = "status", map[string]any{"pauseConditions": nil}
320
+ default:
321
+ return observation, fmt.Errorf("unsupported rollout control")
322
+ }
323
+ if _, err := patchRolloutControl(ctx, client, root, field, changes); err != nil {
324
+ return observation, err
325
+ }
326
+ return ObserveRollout(ctx, client, documents, owner, operation)
327
+ }
328
+
329
+ func patchRolloutControl(ctx context.Context, client dynamic.Interface, live *unstructured.Unstructured, field string, values map[string]any) (*unstructured.Unstructured, error) {
330
+ body, err := json.Marshal(map[string]any{"metadata": map[string]any{"uid": string(live.GetUID()), "resourceVersion": live.GetResourceVersion()}, field: values})
331
+ if err != nil {
332
+ return nil, err
333
+ }
334
+ subresource := []string{}
335
+ if field == "status" {
336
+ subresource = append(subresource, "status")
337
+ }
338
+ return client.Resource(rollouts).Namespace(live.GetNamespace()).Patch(ctx, live.GetName(), types.MergePatchType, body, metav1.PatchOptions{}, subresource...)
339
+ }
340
+
341
+ func StopRollout(ctx context.Context, client dynamic.Interface, documents []*unstructured.Unstructured, owner, operation, uid string) error {
342
+ desired, err := ValidateRolloutResources(documents)
343
+ if err != nil {
344
+ return errors.Join(ErrRolloutStopUnconfirmed, err)
345
+ }
346
+ ticker := time.NewTicker(time.Second)
347
+ defer ticker.Stop()
348
+ for {
349
+ live, err := client.Resource(rollouts).Namespace(desired.GetNamespace()).Get(ctx, desired.GetName(), metav1.GetOptions{})
350
+ if err != nil {
351
+ return errors.Join(ErrRolloutStopUnconfirmed, err)
352
+ }
353
+ if string(live.GetUID()) != uid || uid == "" || live.GetAnnotations()[RolloutOperationAnnotation] != operation || validateRolloutOwner(live, owner, desired.GetName()) != nil {
354
+ return fmt.Errorf("%w: rollout identity or operation changed", ErrRolloutStopUnconfirmed)
355
+ }
356
+ state := rolloutState(live)
357
+ if state.Phase == "Healthy" && state.ObservedGeneration != nil && *state.ObservedGeneration == state.Generation {
358
+ observation, err := ObserveRollout(ctx, client, documents, owner, operation)
359
+ if err == nil && observation.Health == "healthy" {
360
+ return nil
361
+ }
362
+ return errors.Join(ErrRolloutStopUnconfirmed, err, fmt.Errorf("completed rollout traffic cannot be verified"))
363
+ }
364
+ if !state.Aborted {
365
+ if _, err := patchRolloutControl(ctx, client, live, "status", map[string]any{"abort": true}); err != nil && !apierrors.IsConflict(err) {
366
+ return errors.Join(ErrRolloutStopUnconfirmed, err)
367
+ }
368
+ } else {
369
+ observation, err := ObserveRollout(ctx, client, documents, owner, operation)
370
+ if err == nil && !observation.Drifted && rolloutStopped(observation.State) {
371
+ return nil
372
+ }
373
+ }
374
+ select {
375
+ case <-ctx.Done():
376
+ return errors.Join(ErrRolloutStopUnconfirmed, ctx.Err())
377
+ case <-ticker.C:
378
+ }
379
+ }
380
+ }
381
+
382
+ func rolloutStopped(state sohaapi.ProgressiveRolloutStatus) bool {
383
+ if !state.Aborted || state.Phase != "Degraded" || state.ObservedGeneration == nil || *state.ObservedGeneration != state.Generation {
384
+ return false
385
+ }
386
+ if state.StableRevision != "" && state.ActiveRevision != state.StableRevision {
387
+ return false
388
+ }
389
+ if state.Strategy == "canary" && (state.StableWeight == nil || state.CanaryWeight == nil || *state.StableWeight != 100 || *state.CanaryWeight != 0) {
390
+ return false
391
+ }
392
+ for _, metric := range state.Metrics {
393
+ if metric.Phase == "Running" || metric.Phase == "Pending" || metric.Phase == "" {
394
+ return false
395
+ }
396
+ }
397
+ return true
398
+ }