@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,508 @@
1
+ // Package runtime executes the versioned Helm task contract using native Helm actions.
2
+ // Callers supply an authorized cluster connection; policy, queues and credentials stay with callers.
3
+ package runtime
4
+
5
+ import (
6
+ "bytes"
7
+ "context"
8
+ "encoding/base64"
9
+ "encoding/json"
10
+ "errors"
11
+ "fmt"
12
+ "maps"
13
+ "sort"
14
+ "strings"
15
+ "time"
16
+
17
+ "github.com/opensoha/soha-contracts/gen/go/sohaapi"
18
+ "github.com/opensoha/soha-contracts/helmrelease"
19
+ contractresource "github.com/opensoha/soha-contracts/resource"
20
+ resourceruntime "github.com/opensoha/soha-contracts/resource/runtime"
21
+ "helm.sh/helm/v4/pkg/action"
22
+ "helm.sh/helm/v4/pkg/chart"
23
+ "helm.sh/helm/v4/pkg/chart/loader"
24
+ "helm.sh/helm/v4/pkg/kube"
25
+ "helm.sh/helm/v4/pkg/release"
26
+ helmreleasev1 "helm.sh/helm/v4/pkg/release/v1"
27
+ "helm.sh/helm/v4/pkg/storage/driver"
28
+ "k8s.io/apimachinery/pkg/api/meta"
29
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30
+ "k8s.io/apimachinery/pkg/runtime"
31
+ "k8s.io/client-go/dynamic"
32
+ )
33
+
34
+ func Prepare(ctx context.Context, cfg *action.Configuration, input sohaapi.HelmExecutionTaskPayload) (sohaapi.HelmExecutionTaskPayload, error) {
35
+ if err := helmrelease.ValidateTaskIdentity(input); err != nil {
36
+ return input, fmt.Errorf("%w: %v", ErrInvalidArgument, err)
37
+ }
38
+ current, err := currentHelmDelivery(cfg, input.Snapshot.ReleaseName, 0)
39
+ if err != nil {
40
+ return input, err
41
+ }
42
+ input.Snapshot.ExpectedRevision = 0
43
+ input.Snapshot.Operation = sohaapi.HelmDeliverySnapshotOperationInstall
44
+ if current != nil {
45
+ if current.Labels["soha-delivery-owner"] != helmDeliveryOwner(input.Snapshot) || current.Info == nil || strings.HasPrefix(string(current.Info.Status), "pending-") {
46
+ return input, fmt.Errorf("%w: Helm release belongs to another owner", ErrConflict)
47
+ }
48
+ input.Snapshot.ExpectedRevision = current.Version
49
+ input.Snapshot.Operation = sohaapi.HelmDeliverySnapshotOperationUpgrade
50
+ }
51
+ var rendered *helmreleasev1.Release
52
+ if input.Snapshot.RollbackRevision > 0 {
53
+ if current == nil {
54
+ return input, fmt.Errorf("%w: Helm rollback requires an installed release", ErrConflict)
55
+ }
56
+ rendered, err = currentHelmDelivery(cfg, input.Snapshot.ReleaseName, input.Snapshot.RollbackRevision)
57
+ if err == nil && (rendered == nil || rendered.Labels["soha-delivery-owner"] != helmDeliveryOwner(input.Snapshot)) {
58
+ err = fmt.Errorf("%w: Helm rollback revision is not owned by this service", ErrConflict)
59
+ }
60
+ input.Snapshot.Operation = sohaapi.HelmDeliverySnapshotOperationRollback
61
+ if err == nil {
62
+ input.Snapshot.ChartDigest = "sha256:" + rendered.Labels["soha-chart-sha-a"] + rendered.Labels["soha-chart-sha-b"]
63
+ if len(input.Snapshot.ChartDigest) != 71 || (rendered.Chart == nil || rendered.Chart.Metadata == nil) {
64
+ err = fmt.Errorf("%w: rollback revision lacks a verified chart origin", ErrConflict)
65
+ } else {
66
+ input.Snapshot.Chart, input.Snapshot.ChartVersion = rendered.Chart.Metadata.Name, rendered.Chart.Metadata.Version
67
+ }
68
+ }
69
+ } else {
70
+ var ch chart.Charter
71
+ ch, err = preparedHelmChart(input, false)
72
+ if err == nil {
73
+ rendered, err = runHelmDeliveryAction(ctx, cfg, input, ch, true)
74
+ }
75
+ }
76
+ if err != nil {
77
+ return input, fmt.Errorf("%w: Helm rendering or preflight failed", ErrInvalidArgument)
78
+ }
79
+ if input.Prepared == nil {
80
+ input.Prepared = &sohaapi.HelmPreparedRelease{}
81
+ }
82
+ input.Prepared.Manifest, input.Prepared.Hooks = rendered.Manifest, helmDeliveryHooks(rendered)
83
+ values, err := json.Marshal(rendered.Config)
84
+ if err != nil {
85
+ return input, err
86
+ }
87
+ if err := json.Unmarshal(values, &input.Prepared.Values); err != nil {
88
+ return input, err
89
+ }
90
+ input.Snapshot.ValuesDigest = helmrelease.SHA256(values)
91
+ input.Snapshot.Resources, input.Snapshot.RenderedDigest, err = helmDeliveryResources(cfg, input.Snapshot.Namespace, rendered.Manifest, input.Prepared.Hooks)
92
+ if err == nil {
93
+ err = helmrelease.ValidateTaskIdentity(input)
94
+ }
95
+ return input, err
96
+ }
97
+
98
+ func Execute(ctx context.Context, cfg *action.Configuration, input sohaapi.HelmExecutionTaskPayload) (sohaapi.HelmExecutionTaskResult, error) {
99
+ if err := helmrelease.ValidateTaskIdentity(input); err != nil {
100
+ return sohaapi.HelmExecutionTaskResult{Stopped: true}, fmt.Errorf("%w: %v", ErrInvalidArgument, err)
101
+ }
102
+ result := sohaapi.HelmExecutionTaskResult{Stopped: true, RenderedDigest: input.Snapshot.RenderedDigest, Resources: []sohaapi.ManifestResourceInventory{}}
103
+ current, err := currentHelmDelivery(cfg, input.Snapshot.ReleaseName, 0)
104
+ if err != nil {
105
+ return result, err
106
+ }
107
+ if input.Action == sohaapi.Observe {
108
+ return observeHelmDelivery(ctx, cfg, input.Snapshot, current)
109
+ }
110
+ if err := validateHelmPrepared(cfg, input); err != nil {
111
+ return result, err
112
+ }
113
+ if helmDeliveryAlreadyApplied(current, input.Snapshot) {
114
+ return observeHelmDelivery(ctx, cfg, input.Snapshot, current)
115
+ }
116
+ if err := helmDeliveryRevisionMatches(current, input.Snapshot); err != nil {
117
+ return result, err
118
+ }
119
+ if input.Action == sohaapi.Preflight {
120
+ result.Ready, result.Status, result.Revision = true, "preflighted", input.Snapshot.ExpectedRevision
121
+ return result, nil
122
+ }
123
+ if input.Action != sohaapi.Apply {
124
+ return result, fmt.Errorf("%w: unsupported Helm delivery action", ErrInvalidArgument)
125
+ }
126
+ if err := ctx.Err(); err != nil {
127
+ return result, err
128
+ }
129
+ var ch chart.Charter
130
+ if input.Snapshot.Operation != sohaapi.HelmDeliverySnapshotOperationRollback {
131
+ ch, err = preparedHelmChart(input, true)
132
+ if err != nil {
133
+ return result, err
134
+ }
135
+ }
136
+ // Helm's RunWithContext may return while its mutation goroutine is still
137
+ // running. Once mutation begins, native Timeout bounds it; cancellation is
138
+ // acknowledged only after Run has returned and the release is inspected.
139
+ _, runErr := runHelmDeliveryAction(context.Background(), cfg, input, ch, false)
140
+ current, err = currentHelmDelivery(cfg, input.Snapshot.ReleaseName, 0)
141
+ if err == nil && current != nil {
142
+ result, err = observeHelmDelivery(ctx, cfg, input.Snapshot, current)
143
+ }
144
+ if runErr != nil {
145
+ return result, fmt.Errorf("%w: Helm execution failed; inspect the recorded revision and resource state", ErrUnavailable)
146
+ }
147
+ if ctx.Err() != nil {
148
+ return result, ctx.Err()
149
+ }
150
+ return result, err
151
+ }
152
+
153
+ func runHelmDeliveryAction(ctx context.Context, cfg *action.Configuration, input sohaapi.HelmExecutionTaskPayload, ch chart.Charter, dryRun bool) (*helmreleasev1.Release, error) {
154
+ if dryRun {
155
+ // Discover real cluster capabilities, but keep template lookup disabled.
156
+ original := cfg
157
+ cfg = &action.Configuration{
158
+ RESTClientGetter: original.RESTClientGetter, Releases: original.Releases,
159
+ KubeClient: original.KubeClient, RegistryClient: original.RegistryClient,
160
+ Capabilities: original.Capabilities, HookOutputFunc: original.HookOutputFunc,
161
+ CustomTemplateFuncs: maps.Clone(original.CustomTemplateFuncs),
162
+ }
163
+ cfg.SetLogger(original.Logger().Handler())
164
+ if cfg.CustomTemplateFuncs == nil {
165
+ cfg.CustomTemplateFuncs = make(map[string]any)
166
+ }
167
+ cfg.CustomTemplateFuncs["lookup"] = func(string, string, string, string) (map[string]any, error) { return map[string]any{}, nil }
168
+ }
169
+ values := map[string]any{}
170
+ if input.Prepared != nil {
171
+ encoded, err := json.Marshal(input.Prepared.Values)
172
+ if err != nil {
173
+ return nil, err
174
+ }
175
+ if err := json.Unmarshal(encoded, &values); err != nil {
176
+ return nil, err
177
+ }
178
+ }
179
+ timeout := time.Duration(input.Snapshot.TimeoutSeconds) * time.Second
180
+ if timeout <= 0 {
181
+ timeout = 300 * time.Second
182
+ }
183
+ labels := helmDeliveryLabels(input.Snapshot)
184
+ if input.Snapshot.Operation == sohaapi.HelmDeliverySnapshotOperationRollback {
185
+ return runHelmDeliveryRollback(cfg, input.Snapshot, timeout)
186
+ }
187
+ if input.Snapshot.Operation == sohaapi.HelmDeliverySnapshotOperationInstall {
188
+ install := action.NewInstall(cfg)
189
+ install.ReleaseName, install.Namespace = input.Snapshot.ReleaseName, input.Snapshot.Namespace
190
+ install.ServerSideApply, install.CreateNamespace = false, false
191
+ install.Timeout, install.WaitStrategy, install.WaitForJobs = timeout, kube.LegacyStrategy, true
192
+ install.Labels, install.Description = labels, "Soha delivery "+input.Snapshot.DeliveryPlanID
193
+ if dryRun {
194
+ install.DryRunStrategy = action.DryRunServer
195
+ install.PostRenderer = helmPreflightScope{cfg: cfg, namespace: input.Snapshot.Namespace}
196
+ }
197
+ release, err := install.RunWithContext(ctx, ch, values)
198
+ if err != nil {
199
+ return nil, err
200
+ }
201
+ return helmSDKReleaseV1(release)
202
+ }
203
+ upgrade := action.NewUpgrade(cfg)
204
+ upgrade.Namespace, upgrade.ResetValues = input.Snapshot.Namespace, true
205
+ upgrade.ServerSideApply = "false"
206
+ upgrade.Timeout, upgrade.WaitStrategy, upgrade.WaitForJobs = timeout, kube.LegacyStrategy, true
207
+ upgrade.Labels, upgrade.Description = labels, "Soha delivery "+input.Snapshot.DeliveryPlanID
208
+ if dryRun {
209
+ upgrade.DryRunStrategy = action.DryRunServer
210
+ upgrade.PostRenderer = helmPreflightScope{cfg: cfg, namespace: input.Snapshot.Namespace}
211
+ }
212
+ release, err := upgrade.RunWithContext(ctx, input.Snapshot.ReleaseName, ch, values)
213
+ if err != nil {
214
+ return nil, err
215
+ }
216
+ return helmSDKReleaseV1(release)
217
+ }
218
+
219
+ // Validate before Helm's ownership checks can read any rendered resource.
220
+ type helmPreflightScope struct {
221
+ cfg *action.Configuration
222
+ namespace string
223
+ }
224
+
225
+ func (p helmPreflightScope) Run(manifest *bytes.Buffer) (*bytes.Buffer, error) {
226
+ _, _, err := helmDeliveryResources(p.cfg, p.namespace, manifest.String(), nil)
227
+ return manifest, err
228
+ }
229
+
230
+ func preparedHelmChart(input sohaapi.HelmExecutionTaskPayload, frozen bool) (chart.Charter, error) {
231
+ if input.Prepared == nil {
232
+ return nil, fmt.Errorf("%w: confidential Helm preparation is missing", ErrInvalidArgument)
233
+ }
234
+ archive, err := base64.StdEncoding.DecodeString(input.Prepared.ChartArchive)
235
+ if err != nil || helmrelease.SHA256(archive) != input.Snapshot.ChartDigest {
236
+ return nil, fmt.Errorf("%w: Helm chart snapshot digest mismatch", ErrConflict)
237
+ }
238
+ if err := helmrelease.ValidateChartArchive(archive); err != nil {
239
+ return nil, fmt.Errorf("%w: %v", ErrInvalidArgument, err)
240
+ }
241
+ if frozen {
242
+ archive, err = helmrelease.FrozenChartArchive(archive, input.Prepared.Manifest, input.Prepared.Hooks)
243
+ if err != nil {
244
+ return nil, err
245
+ }
246
+ }
247
+ return loader.LoadArchive(bytes.NewReader(archive))
248
+ }
249
+
250
+ func validateHelmPrepared(cfg *action.Configuration, input sohaapi.HelmExecutionTaskPayload) error {
251
+ if input.Prepared == nil {
252
+ return fmt.Errorf("%w: confidential Helm preparation is missing", ErrInvalidArgument)
253
+ }
254
+ values, err := json.Marshal(input.Prepared.Values)
255
+ if err != nil || helmrelease.SHA256(values) != input.Snapshot.ValuesDigest {
256
+ return fmt.Errorf("%w: Helm values snapshot changed", ErrConflict)
257
+ }
258
+ _, digest, err := helmDeliveryResources(cfg, input.Snapshot.Namespace, input.Prepared.Manifest, input.Prepared.Hooks)
259
+ if err != nil {
260
+ return err
261
+ }
262
+ if digest != input.Snapshot.RenderedDigest {
263
+ return fmt.Errorf("%w: Helm rendered resources changed", ErrConflict)
264
+ }
265
+ if input.Snapshot.Operation == sohaapi.HelmDeliverySnapshotOperationRollback {
266
+ selected, err := currentHelmDelivery(cfg, input.Snapshot.ReleaseName, input.Snapshot.RollbackRevision)
267
+ if err != nil || selected == nil || selected.Labels["soha-delivery-owner"] != helmDeliveryOwner(input.Snapshot) {
268
+ return fmt.Errorf("%w: Helm rollback revision is unavailable", ErrConflict)
269
+ }
270
+ _, selectedDigest, err := helmDeliveryResources(cfg, input.Snapshot.Namespace, selected.Manifest, helmDeliveryHooks(selected))
271
+ if err != nil || selectedDigest != digest {
272
+ return fmt.Errorf("%w: Helm rollback revision differs from the approved render", ErrConflict)
273
+ }
274
+ selectedValues, err := json.Marshal(selected.Config)
275
+ if err != nil || helmrelease.SHA256(selectedValues) != input.Snapshot.ValuesDigest || "sha256:"+selected.Labels["soha-chart-sha-a"]+selected.Labels["soha-chart-sha-b"] != input.Snapshot.ChartDigest {
276
+ return fmt.Errorf("%w: Helm rollback values or chart origin changed", ErrConflict)
277
+ }
278
+ }
279
+ return nil
280
+ }
281
+
282
+ func runHelmDeliveryRollback(cfg *action.Configuration, snapshot sohaapi.HelmDeliverySnapshot, timeout time.Duration) (*helmreleasev1.Release, error) {
283
+ // Native rollback reuses the old labels. Decorate only the newly created
284
+ // revision so retry recovery recognizes this plan without rewriting history.
285
+ original := cfg.Releases.Driver
286
+ cfg.Releases.Driver = helmRollbackDriver{Driver: original, snapshot: snapshot}
287
+ defer func() { cfg.Releases.Driver = original }()
288
+ rollback := action.NewRollback(cfg)
289
+ rollback.Version, rollback.ServerSideApply = snapshot.RollbackRevision, "false"
290
+ rollback.Timeout, rollback.WaitStrategy, rollback.WaitForJobs = timeout, kube.LegacyStrategy, true
291
+ if err := rollback.Run(snapshot.ReleaseName); err != nil {
292
+ return nil, err
293
+ }
294
+ return currentHelmDelivery(cfg, snapshot.ReleaseName, 0)
295
+ }
296
+
297
+ type helmRollbackDriver struct {
298
+ driver.Driver
299
+ snapshot sohaapi.HelmDeliverySnapshot
300
+ }
301
+
302
+ func (d helmRollbackDriver) decorate(value release.Releaser) error {
303
+ item, err := helmSDKReleaseV1(value)
304
+ if err != nil {
305
+ return err
306
+ }
307
+ if item.Name == d.snapshot.ReleaseName && item.Version == d.snapshot.ExpectedRevision+1 {
308
+ labels := map[string]string{}
309
+ for key, value := range item.Labels {
310
+ labels[key] = value
311
+ }
312
+ for key, value := range helmDeliveryLabels(d.snapshot) {
313
+ labels[key] = value
314
+ }
315
+ item.Labels = labels
316
+ }
317
+ return nil
318
+ }
319
+
320
+ func (d helmRollbackDriver) Create(key string, value release.Releaser) error {
321
+ if err := d.decorate(value); err != nil {
322
+ return err
323
+ }
324
+ return d.Driver.Create(key, value)
325
+ }
326
+
327
+ func (d helmRollbackDriver) Update(key string, value release.Releaser) error {
328
+ if err := d.decorate(value); err != nil {
329
+ return err
330
+ }
331
+ return d.Driver.Update(key, value)
332
+ }
333
+
334
+ func currentHelmDelivery(cfg *action.Configuration, name string, revision int) (*helmreleasev1.Release, error) {
335
+ get := action.NewGet(cfg)
336
+ get.Version = revision
337
+ release, err := get.Run(name)
338
+ if errors.Is(err, driver.ErrReleaseNotFound) {
339
+ return nil, nil
340
+ }
341
+ if err != nil {
342
+ return nil, fmt.Errorf("%w: cannot read Helm release state", ErrUnavailable)
343
+ }
344
+ return helmSDKReleaseV1(release)
345
+ }
346
+
347
+ // RequireUnmanaged prevents legacy Helm mutation endpoints bypassing delivery plans.
348
+ func RequireUnmanaged(cfg *action.Configuration, name string) error {
349
+ current, err := currentHelmDelivery(cfg, name, 0)
350
+ if err != nil {
351
+ return err
352
+ }
353
+ if current != nil && current.Labels["soha-delivery-owner"] != "" {
354
+ return fmt.Errorf("%w: application Helm releases are managed through delivery plans", ErrConflict)
355
+ }
356
+ return nil
357
+ }
358
+
359
+ func helmDeliveryOwner(snapshot sohaapi.HelmDeliverySnapshot) string {
360
+ return helmrelease.SHA256([]byte(snapshot.ApplicationID + "/" + snapshot.ServiceID + "/" + snapshot.ApplicationEnvironmentID + "/" + snapshot.TargetID))[7:39]
361
+ }
362
+
363
+ func helmDeliveryLabels(snapshot sohaapi.HelmDeliverySnapshot) map[string]string {
364
+ labels := map[string]string{"soha-delivery-owner": helmDeliveryOwner(snapshot), "soha-delivery-plan": helmrelease.SHA256([]byte(snapshot.DeliveryPlanID))[7:39]}
365
+ if len(snapshot.ChartDigest) == 71 {
366
+ labels["soha-chart-sha-a"], labels["soha-chart-sha-b"] = snapshot.ChartDigest[7:39], snapshot.ChartDigest[39:]
367
+ }
368
+ return labels
369
+ }
370
+
371
+ func helmDeliveryAlreadyApplied(release *helmreleasev1.Release, snapshot sohaapi.HelmDeliverySnapshot) bool {
372
+ return release != nil && release.Version == snapshot.ExpectedRevision+1 && release.Labels["soha-delivery-plan"] == helmDeliveryLabels(snapshot)["soha-delivery-plan"] && release.Labels["soha-delivery-owner"] == helmDeliveryOwner(snapshot) && release.Info != nil && string(release.Info.Status) == "deployed"
373
+ }
374
+
375
+ func helmDeliveryRevisionMatches(release *helmreleasev1.Release, snapshot sohaapi.HelmDeliverySnapshot) error {
376
+ if release == nil && snapshot.ExpectedRevision == 0 && snapshot.Operation == sohaapi.HelmDeliverySnapshotOperationInstall {
377
+ return nil
378
+ }
379
+ if release == nil || release.Version != snapshot.ExpectedRevision || release.Labels["soha-delivery-owner"] != helmDeliveryOwner(snapshot) || release.Info == nil || strings.HasPrefix(string(release.Info.Status), "pending-") {
380
+ return fmt.Errorf("%w: Helm release revision, ownership or operation state changed; create a new plan", ErrConflict)
381
+ }
382
+ return nil
383
+ }
384
+
385
+ func helmDeliveryHooks(release *helmreleasev1.Release) []string {
386
+ hooks := make([]string, 0, len(release.Hooks))
387
+ for _, hook := range release.Hooks {
388
+ hooks = append(hooks, hook.Manifest)
389
+ }
390
+ return hooks
391
+ }
392
+
393
+ func helmDeliveryResources(cfg *action.Configuration, namespace, manifest string, hooks []string) ([]sohaapi.HelmDeliveryResource, string, error) {
394
+ resources := []sohaapi.HelmDeliveryResource{}
395
+ objects := map[string]any{}
396
+ for index, content := range append([]string{manifest}, hooks...) {
397
+ items, err := cfg.KubeClient.Build(strings.NewReader(content), true)
398
+ if err != nil {
399
+ return nil, "", fmt.Errorf("%w: Helm rendered resource validation failed", ErrInvalidArgument)
400
+ }
401
+ for _, item := range items {
402
+ if len(resources) >= 300 || item.Name == "" || item.Mapping == nil || item.Mapping.Scope.Name() != meta.RESTScopeNameNamespace || item.Namespace != namespace {
403
+ return nil, "", fmt.Errorf("%w: application Helm charts require namespaced resources within their target and at most 300 resources", ErrInvalidArgument)
404
+ }
405
+ accessor, err := meta.Accessor(item.Object)
406
+ if err != nil || accessor.GetAnnotations()["helm.sh/resource-policy"] == "keep" {
407
+ return nil, "", fmt.Errorf("%w: retained Helm resources cannot be safely reassigned by delivery plans", ErrInvalidArgument)
408
+ }
409
+ object, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item.Object)
410
+ if err != nil {
411
+ return nil, "", err
412
+ }
413
+ identity := sohaapi.HelmDeliveryResource{APIVersion: item.Mapping.GroupVersionKind.GroupVersion().String(), Kind: item.Mapping.GroupVersionKind.Kind, Namespace: item.Namespace, Name: item.Name, Hook: index > 0}
414
+ key := item.Mapping.GroupVersionKind.Group + "/" + identity.Kind + "/" + identity.Namespace + "/" + identity.Name
415
+ if _, exists := objects[key]; exists {
416
+ return nil, "", fmt.Errorf("%w: Helm chart contains duplicate resource identities", ErrInvalidArgument)
417
+ }
418
+ objects[key], resources = object, append(resources, identity)
419
+ }
420
+ }
421
+ sort.Slice(resources, func(i, j int) bool { return fmt.Sprint(resources[i]) < fmt.Sprint(resources[j]) })
422
+ encoded, err := json.Marshal(objects)
423
+ if err != nil {
424
+ return nil, "", err
425
+ }
426
+ return resources, helmrelease.SHA256(encoded), nil
427
+ }
428
+
429
+ func observeHelmDelivery(ctx context.Context, cfg *action.Configuration, snapshot sohaapi.HelmDeliverySnapshot, release *helmreleasev1.Release) (sohaapi.HelmExecutionTaskResult, error) {
430
+ result := sohaapi.HelmExecutionTaskResult{Stopped: true, RenderedDigest: snapshot.RenderedDigest, Resources: []sohaapi.ManifestResourceInventory{}}
431
+ if release != nil {
432
+ result.Revision = release.Version
433
+ if release.Info != nil {
434
+ result.Status = string(release.Info.Status)
435
+ }
436
+ }
437
+ if !helmDeliveryAlreadyApplied(release, snapshot) {
438
+ return result, fmt.Errorf("%w: expected Helm deployment is not complete", ErrConflict)
439
+ }
440
+ _, digest, err := helmDeliveryResources(cfg, snapshot.Namespace, release.Manifest, helmDeliveryHooks(release))
441
+ if err != nil || digest != snapshot.RenderedDigest {
442
+ return result, fmt.Errorf("%w: Helm release differs from the approved render", ErrConflict)
443
+ }
444
+ items, err := cfg.KubeClient.Build(strings.NewReader(release.Manifest), false)
445
+ if err != nil {
446
+ return result, err
447
+ }
448
+ ready := true
449
+ for _, item := range items {
450
+ if err := item.Get(); err != nil {
451
+ return result, fmt.Errorf("%w: Helm resource observation failed", ErrUnavailable)
452
+ }
453
+ object, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item.Object)
454
+ if err != nil {
455
+ return result, err
456
+ }
457
+ health, err := observeHelmObjectHealth(ctx, cfg, object)
458
+ if err != nil {
459
+ return result, fmt.Errorf("%w: Operator resource observation failed", ErrUnavailable)
460
+ }
461
+ accessor, err := meta.Accessor(item.Object)
462
+ if err != nil {
463
+ return result, err
464
+ }
465
+ result.Resources = append(result.Resources, sohaapi.ManifestResourceInventory{APIVersion: item.Mapping.GroupVersionKind.GroupVersion().String(), Kind: item.Mapping.GroupVersionKind.Kind, Namespace: item.Namespace, Name: item.Name, UID: string(accessor.GetUID()), ResourceVersion: accessor.GetResourceVersion(), Health: health})
466
+ ready = ready && health == "healthy"
467
+ }
468
+ result.Ready = ready
469
+ return result, nil
470
+ }
471
+
472
+ var (
473
+ ErrConflict = errors.New("helm delivery conflict")
474
+ ErrInvalidArgument = errors.New("invalid Helm delivery")
475
+ ErrUnavailable = errors.New("helm runtime unavailable")
476
+ )
477
+
478
+ func helmSDKReleaseV1(value release.Releaser) (*helmreleasev1.Release, error) {
479
+ switch item := value.(type) {
480
+ case nil:
481
+ return nil, nil
482
+ case helmreleasev1.Release:
483
+ return &item, nil
484
+ case *helmreleasev1.Release:
485
+ return item, nil
486
+ default:
487
+ return nil, fmt.Errorf("%w: unsupported native release format", ErrInvalidArgument)
488
+ }
489
+ }
490
+
491
+ func observeHelmObjectHealth(ctx context.Context, cfg *action.Configuration, object map[string]any) (string, error) {
492
+ item := &unstructured.Unstructured{Object: object}
493
+ if item.GetAPIVersion() != "workloads.soha.io/v1alpha1" || item.GetKind() != "WorkloadCronJob" {
494
+ return contractresource.ManifestHealth(object), nil
495
+ }
496
+ if cfg.RESTClientGetter == nil {
497
+ return "unknown", ErrUnavailable
498
+ }
499
+ config, err := cfg.RESTClientGetter.ToRESTConfig()
500
+ if err != nil {
501
+ return "unknown", err
502
+ }
503
+ client, err := dynamic.NewForConfig(config)
504
+ if err != nil {
505
+ return "unknown", err
506
+ }
507
+ return resourceruntime.ManifestHealth(ctx, client, item)
508
+ }