@opensoha/contracts 0.1.17 → 0.1.18
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.
- package/COMPATIBILITY.md +45 -0
- package/README.md +37 -0
- package/auth/catalog_generated.go +2 -2
- package/auth/outpost_headers.go +17 -0
- package/auth/outpost_headers_test.go +16 -0
- package/auth/permission-catalog.json +383 -1
- package/compat/consumer-revisions.json +3 -0
- package/compat/json-schema-ecosystem-0.1.0.json +1 -1
- package/compat/openapi-loose-boundaries-0.1.0.json +78 -46
- package/delivery/canonical.go +136 -0
- package/delivery/document.go +203 -0
- package/delivery/document.schema.json +1527 -0
- package/delivery/document_test.go +191 -0
- package/delivery/parser.go +155 -0
- package/evaluation/evaluation-run.schema.json +64 -10
- package/fixtures/json-schema/gateway-manifest/invalid/bad-execution-mode.json +73 -0
- package/fixtures/json-schema/gateway-manifest/invalid/unversioned-check.json +85 -0
- package/fixtures/json-schema/gateway-manifest/valid/checks.json +86 -0
- package/fixtures/json-schema/gateway-manifest/valid/versioned-task.json +73 -0
- package/fixtures/json-schema/network-ingest-event/invalid/vpn-probe-negative-rtt.json +34 -0
- package/fixtures/json-schema/network-ingest-event/invalid/vpn-stats-secret.json +27 -0
- package/fixtures/json-schema/network-ingest-event/valid/vpn-probe-batch.json +45 -0
- package/fixtures/json-schema/network-ingest-event/valid/vpn-tunnel-stats.json +26 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-bad-token.json +15 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/vpn-managed-mode-override.json +16 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/managed-vpn-macos-enrollment.json +27 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/vpn-managed-connect.json +15 -0
- package/fixtures/openapi/AgentChatInput/invalid/empty-question.json +4 -0
- package/fixtures/openapi/AgentChatInput/invalid/system-role.json +9 -0
- package/fixtures/openapi/AgentChatInput/valid/authorized-background.json +5 -0
- package/fixtures/openapi/AgentChatInput/valid/conversation.json +15 -0
- package/fixtures/openapi/AgentRunnerToolCallRequest/invalid/model-identity.json +1 -0
- package/fixtures/openapi/AgentRunnerToolCallRequest/valid/search.json +1 -0
- package/fixtures/openapi/ApplicationInput/invalid/negative-version.json +6 -0
- package/fixtures/openapi/ApplicationInput/invalid/zero-version.json +6 -0
- package/fixtures/openapi/ApplicationInput/valid/versioned.json +6 -0
- package/fixtures/openapi/ApprovalRequest/invalid/resolved-scopes.json +20 -0
- package/fixtures/openapi/ApprovalRequest/valid/resolved-scopes.json +21 -0
- package/fixtures/openapi/BuildTemplateInput/invalid/negative-revision.json +6 -0
- package/fixtures/openapi/BuildTemplateInput/valid/draft.json +11 -0
- package/fixtures/openapi/BuildTriggerRequest/invalid/no-ref.json +1 -0
- package/fixtures/openapi/BuildTriggerRequest/valid/no-environment.json +1 -0
- package/fixtures/openapi/BuildpacksConfiguration/invalid/mutable-builder.json +5 -0
- package/fixtures/openapi/BuildpacksConfiguration/invalid/privilege-override.json +6 -0
- package/fixtures/openapi/BuildpacksConfiguration/invalid/unsupported-platform.json +5 -0
- package/fixtures/openapi/BuildpacksConfiguration/valid/pinned.json +5 -0
- package/fixtures/openapi/CapabilityAssessment/invalid/contract.json +16 -0
- package/fixtures/openapi/CapabilityAssessment/valid/contract.json +16 -0
- package/fixtures/openapi/CapabilityPlan/invalid/contract.json +16 -0
- package/fixtures/openapi/CapabilityPlan/valid/contract.json +19 -0
- package/fixtures/openapi/CapabilityPlanValidation/invalid/contract.json +5 -0
- package/fixtures/openapi/CapabilityPlanValidation/valid/contract.json +11 -0
- package/fixtures/openapi/CapabilityTask/invalid/contract.json +33 -0
- package/fixtures/openapi/CapabilityTask/valid/contract.json +34 -0
- package/fixtures/openapi/CapabilityTaskInput/invalid/contract.json +22 -0
- package/fixtures/openapi/CapabilityTaskInput/valid/contract.json +22 -0
- package/fixtures/openapi/CapabilityValueSemantic/invalid/contract.json +4 -0
- package/fixtures/openapi/CapabilityValueSemantic/valid/contract.json +7 -0
- package/fixtures/openapi/DeliveryAccessResult/invalid/release-verification.json +8 -0
- package/fixtures/openapi/DeliveryAccessResult/valid/release-verification.json +8 -0
- package/fixtures/openapi/DeliveryBatchAssessment/invalid/release-verification.json +11 -0
- package/fixtures/openapi/DeliveryBatchAssessment/valid/release-verification.json +11 -0
- package/fixtures/openapi/DeliveryBatchAssessmentInput/invalid/release-verification.json +9 -0
- package/fixtures/openapi/DeliveryBatchAssessmentInput/valid/release-verification.json +8 -0
- package/fixtures/openapi/DeliveryBatchInput/invalid/ambiguous-source.json +19 -0
- package/fixtures/openapi/DeliveryBatchInput/invalid/missing-idempotency.json +16 -0
- package/fixtures/openapi/DeliveryBatchInput/valid/quick-deploy.json +17 -0
- package/fixtures/openapi/DeliveryBatchInput/valid/saved-workflow.json +5 -0
- package/fixtures/openapi/DeliveryBlueprintInput/invalid/unversioned-preset.json +31 -0
- package/fixtures/openapi/DeliveryBlueprintInput/valid/service-preset.json +32 -0
- package/fixtures/openapi/DeliveryDocument/invalid/unknown-field.json +24 -0
- package/fixtures/openapi/DeliveryDocument/invalid/unknown-kind.json +23 -0
- package/fixtures/openapi/DeliveryDocument/invalid/unknown-version.json +23 -0
- package/fixtures/openapi/DeliveryDocument/valid/build-template.json +26 -0
- package/fixtures/openapi/DeliveryDocument/valid/deployment-template.json +26 -0
- package/fixtures/openapi/DeliveryDocument/valid/legacy-dag.json +39 -0
- package/fixtures/openapi/DeliveryDocument/valid/workflow-template.json +23 -0
- package/fixtures/openapi/DeliveryDocument/valid/workflow.json +20 -0
- package/fixtures/openapi/DeliveryDraftInput/invalid/short-idempotency-key.json +30 -0
- package/fixtures/openapi/DeliveryDraftInput/valid/idempotent-create.json +30 -0
- package/fixtures/openapi/DeliveryDraftService/invalid/zero-version.json +7 -0
- package/fixtures/openapi/DeliveryDraftService/valid/versioned-template.json +9 -0
- package/fixtures/openapi/DeliveryExecutionHistoryPage/invalid-kind.json +9 -0
- package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-build.json +16 -0
- package/fixtures/openapi/DeliveryExecutionHistoryPage/valid-empty.json +3 -0
- package/fixtures/openapi/DeliveryPlanApprovalInput/invalid/confirm.json +1 -0
- package/fixtures/openapi/DeliveryPlanApprovalInput/valid/approve.json +1 -0
- package/fixtures/openapi/DeliveryPlanInput/invalid/draft-manifest-revision.json +1 -0
- package/fixtures/openapi/DeliveryPlanInput/valid/manifest-revision.json +1 -0
- package/fixtures/openapi/DeliveryTargetInput/invalid/deploy-without-environment.json +7 -0
- package/fixtures/openapi/DeliveryTargetInput/valid/build-without-environment.json +6 -0
- package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/duplicate-kind.json +13 -0
- package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/embedded-credentials.json +16 -0
- package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/empty-kinds.json +10 -0
- package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/negative-generation.json +15 -0
- package/fixtures/openapi/DeliveryTemplateSourceInput/invalid/unknown-kind.json +12 -0
- package/fixtures/openapi/DeliveryTemplateSourceInput/valid/manual-source.json +15 -0
- package/fixtures/openapi/DeliveryWorkflowDefinition/invalid/unversioned-template.json +15 -0
- package/fixtures/openapi/DeliveryWorkflowDefinition/valid/cross-app.json +24 -0
- package/fixtures/openapi/DeliveryWorkflowInput/invalid/short-key.json +1 -0
- package/fixtures/openapi/DeliveryWorkflowInput/valid/idempotent-create.json +1 -0
- package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-target-override.json +7 -0
- package/fixtures/openapi/DeploymentTemplatePreviewInput/invalid/invalid-zero-version.json +6 -0
- package/fixtures/openapi/DeploymentTemplatePreviewInput/valid/valid.json +6 -0
- package/fixtures/openapi/DockerDeliveryConfiguration/invalid/empty-mappings.json +5 -0
- package/fixtures/openapi/DockerDeliveryConfiguration/valid/compose.json +7 -0
- package/fixtures/openapi/DockerDeliverySnapshot/invalid/missing-required-field.json +19 -0
- package/fixtures/openapi/DockerDeliverySnapshot/valid/frozen-project.json +20 -0
- package/fixtures/openapi/DockerOperationCallbackRequest/invalid/cancellation-acknowledged-not-boolean.json +13 -0
- package/fixtures/openapi/DockerOperationCallbackRequest/valid/cancellation-acknowledged.json +13 -0
- package/fixtures/openapi/DockerProjectInput/invalid/empty-host.json +4 -0
- package/fixtures/openapi/DockerProjectInput/valid/inline-idempotent.json +7 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/invalid/capacity-type.json +1 -0
- package/fixtures/openapi/DockerQuickCreateHostInput/valid/capacity.json +1 -0
- package/fixtures/openapi/HelmDeliveryConfiguration/invalid/plain-credential.json +21 -0
- package/fixtures/openapi/HelmDeliveryConfiguration/invalid/release-name.json +20 -0
- package/fixtures/openapi/HelmDeliveryConfiguration/valid/private-oci.json +20 -0
- package/fixtures/openapi/HelmDeliverySnapshot/invalid/secret-plan.json +31 -0
- package/fixtures/openapi/HelmDeliverySnapshot/invalid/unknown-operation.json +28 -0
- package/fixtures/openapi/HelmDeliverySnapshot/valid/first-install.json +28 -0
- package/fixtures/openapi/IdentityApplication/invalid/outpost-status.json +1 -0
- package/fixtures/openapi/IdentityApplicationOnboardingInput/invalid/missing-access.json +12 -0
- package/fixtures/openapi/IdentityApplicationOnboardingInput/valid/link.json +13 -0
- package/fixtures/openapi/IdentityOutpost/invalid/missing-runtime-status.json +9 -0
- package/fixtures/openapi/IdentityOutpost/valid/healthy.json +17 -0
- package/fixtures/openapi/IdentityOutpost/valid/unregistered.json +12 -0
- package/fixtures/openapi/IdentityOutpostDeployment/invalid/missing-protocol.json +3 -0
- package/fixtures/openapi/IdentityOutpostDeployment/valid/remote.json +6 -0
- package/fixtures/openapi/IdentityOutpostInput/invalid/forged-observation.json +7 -0
- package/fixtures/openapi/IdentityOutpostInput/valid/forward-auth.json +6 -0
- package/fixtures/openapi/IdentityOutpostInput/valid/legacy.json +8 -0
- package/fixtures/openapi/IdentityProviderSetup/invalid/fake-verification.json +1 -0
- package/fixtures/openapi/IdentityProviderSetup/valid/incomplete.json +1 -0
- package/fixtures/openapi/IdentityProviderSetup/valid/remote.json +1 -0
- package/fixtures/openapi/IdentityProviderUserMetadata/invalid/attributes.json +8 -0
- package/fixtures/openapi/IdentityProviderUserMetadata/valid/attributes.json +13 -0
- package/fixtures/openapi/KubernetesCustomResource/invalid/negative-generation.json +1 -0
- package/fixtures/openapi/KubernetesCustomResource/valid/deleting-operator.json +1 -0
- package/fixtures/openapi/KubernetesResourceCreationReceipt/invalid/manifest-content.json +20 -0
- package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/confirmed.json +20 -0
- package/fixtures/openapi/KubernetesResourceCreationReceipt/valid/unknown.json +19 -0
- package/fixtures/openapi/ManifestDeploymentEnvelope/invalid/negative-resource-observation.json +57 -0
- package/fixtures/openapi/ManifestDeploymentEnvelope/valid/operator-deleting.json +57 -0
- package/fixtures/openapi/ManifestExecutionTaskPayload/invalid/gitops-empty.json +7 -0
- package/fixtures/openapi/ManifestExecutionTaskPayload/valid/gitops-frozen-children.json +18 -0
- package/fixtures/openapi/ManifestKustomizeOptions/invalid/escaping-entry.json +1 -0
- package/fixtures/openapi/ManifestKustomizeOptions/invalid/mutable-image.json +1 -0
- package/fixtures/openapi/ManifestKustomizeOptions/valid/overlay-images.json +7 -0
- package/fixtures/openapi/ManifestRevisionInput/invalid/missing-version.json +1 -0
- package/fixtures/openapi/ManifestRevisionInput/valid/current-draft.json +1 -0
- package/fixtures/openapi/ManifestServiceTemplateInputs/invalid/mutable-image.json +8 -0
- package/fixtures/openapi/ManifestServiceTemplateInputs/valid/frozen-service-image.json +9 -0
- package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/auto-with-gateway.json +6 -0
- package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/missing-manual-gateway.json +5 -0
- package/fixtures/openapi/NetworkVPNConnectionIntentInput/invalid/mode-override.json +7 -0
- package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/auto-intent.json +5 -0
- package/fixtures/openapi/NetworkVPNConnectionIntentInput/valid/manual-intent.json +6 -0
- package/fixtures/openapi/NetworkVPNProfileInput/invalid/unknown-private-key.json +25 -0
- package/fixtures/openapi/NetworkVPNProfileInput/valid/assigned-profile.json +24 -0
- package/fixtures/openapi/NetworkVPNSelectionPolicyInput/invalid/unbounded-attempts.json +21 -0
- package/fixtures/openapi/NetworkVPNSelectionPolicyInput/valid/provider-policy.json +21 -0
- package/fixtures/openapi/ObservabilityMetricAssessmentInput/invalid/arbitrary-expression.json +10 -0
- package/fixtures/openapi/ObservabilityMetricAssessmentInput/valid/metric-window.json +10 -0
- package/fixtures/openapi/PluginManifest/valid/hermes-api.json +53 -0
- package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/missing-identity.json +5 -0
- package/fixtures/openapi/ProgressiveRolloutControlInput/invalid/skip-analysis.json +5 -0
- package/fixtures/openapi/ProgressiveRolloutControlInput/valid/pause.json +5 -0
- package/fixtures/openapi/ReleaseTargetInput/invalid/missing-required-field.json +15 -0
- package/fixtures/openapi/ReleaseTargetInput/valid/docker-compose.json +16 -0
- package/fixtures/openapi/RepositoryAnalysis/invalid/moving-ref.json +11 -0
- package/fixtures/openapi/RepositoryAnalysis/valid/go.json +29 -0
- package/fixtures/openapi/RepositoryAnalysisInput/invalid/arbitrary-source-url.json +6 -0
- package/fixtures/openapi/RepositoryAnalysisInput/invalid/missing-ref.json +4 -0
- package/fixtures/openapi/RepositoryAnalysisInput/valid/nested-project.json +6 -0
- package/fixtures/openapi/ServiceDeploymentTemplateBinding/invalid/invalid-unpinned.json +4 -0
- package/fixtures/openapi/ServiceDeploymentTemplateBinding/valid/valid.json +7 -0
- package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-executable-schema.json +25 -0
- package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-missing-schema.json +21 -0
- package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/invalid-mixed-renderers.json +40 -0
- package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-nested-parameters.json +41 -0
- package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/valid-yaml.json +34 -0
- package/fixtures/openapi/TemplatePublishInput/invalid/missing-revision.json +1 -0
- package/fixtures/openapi/TemplatePublishInput/invalid/unknown-field.json +4 -0
- package/fixtures/openapi/TemplatePublishInput/invalid/zero-revision.json +3 -0
- package/fixtures/openapi/TemplatePublishInput/valid/revision.json +3 -0
- package/fixtures/openapi/ToolCapability/invalid/bad-idempotency.json +35 -0
- package/fixtures/openapi/ToolCapability/invalid/unversioned-check.json +47 -0
- package/fixtures/openapi/ToolCapability/valid/checks.json +48 -0
- package/fixtures/openapi/ToolCapability/valid/versioned-task.json +35 -0
- package/fixtures/openapi/ToolInvocationRequest/invalid/empty-version.json +8 -0
- package/fixtures/openapi/ToolInvocationRequest/valid/versioned.json +8 -0
- package/fixtures/openapi/ToolInvocationResult/invalid/missing-status-call.json +13 -0
- package/fixtures/openapi/ToolInvocationResult/valid/task.json +20 -0
- package/fixtures/openapi/VirtualMachineCreateInput/invalid/capacity-type.json +1 -0
- package/fixtures/openapi/VirtualMachineCreateInput/valid/capacity.json +1 -0
- package/fixtures/openapi/VirtualizationCapacityInput/invalid/missing-disk.json +1 -0
- package/fixtures/openapi/VirtualizationCapacityInput/valid/running-vm.json +1 -0
- package/fixtures/openapi/VirtualizationCapacityResult/invalid/invented-status.json +1 -0
- package/fixtures/openapi/VirtualizationCapacityResult/valid/available.json +1 -0
- package/fixtures/openapi/VirtualizationWorkerCreateInput/invalid/secret-input.json +5 -0
- package/fixtures/openapi/VirtualizationWorkerCreateInput/valid/one-worker.json +4 -0
- package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/external-owner.json +27 -0
- package/fixtures/openapi/VirtualizationWorkerPoolInput/invalid/zero-budget.json +27 -0
- package/fixtures/openapi/VirtualizationWorkerPoolInput/valid/registered.json +27 -0
- package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/invalid/nested-data.json +5 -0
- package/fixtures/openapi/VirtualizationWorkerPoolListEnvelope/valid/empty.json +3 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/missing-file-content.json +8 -0
- package/fixtures/openapi/WorkbenchContextSelection/invalid/unknown-reference-kind.json +8 -0
- package/fixtures/openapi/WorkbenchContextSelection/valid/text-and-resource.json +17 -0
- package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/trigger-without-plan.json +10 -0
- package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/unknown-trigger.json +28 -0
- package/fixtures/openapi/WorkbenchInspectionTaskInput/invalid/zero-interval.json +28 -0
- package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/alert-capability.json +30 -0
- package/fixtures/openapi/WorkbenchInspectionTaskInput/valid/scheduled-capability.json +28 -0
- package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/missing-rule.json +3 -0
- package/fixtures/openapi/WorkbenchInspectionTrigger/invalid/schedule-alert-selector.json +4 -0
- package/fixtures/openapi/WorkbenchInspectionTrigger/valid/schedule.json +3 -0
- package/fixtures/openapi/WorkbenchModelPreferences/invalid/unknown-effort.json +1 -0
- package/fixtures/openapi/WorkbenchModelPreferences/valid/selected.json +1 -0
- package/fixtures/openapi/WorkflowCatalogPage/invalid/unsupported-kind.json +6 -0
- package/fixtures/openapi/WorkflowCatalogPage/valid/build-without-environment.json +6 -0
- package/fixtures/openapi/WorkflowTemplateInput/invalid/zero-revision.json +6 -0
- package/fixtures/openapi/WorkflowTemplateInput/valid/draft.json +10 -0
- package/gen/go/sohaapi/client.go +1 -1
- package/gen/go/sohaapi/types.go +8070 -859
- package/gen/go/sohaapi/types_test.go +100 -1
- package/gen/go/sohaapi/vpn_models_test.go +21 -0
- package/gen/ts/sohaapi/index.ts +20343 -13162
- package/go.mod +107 -5
- package/go.sum +379 -16
- package/helmrelease/delivery.go +283 -0
- package/helmrelease/delivery_test.go +102 -0
- package/helmrelease/frozen_chart.go +91 -0
- package/helmrelease/result.go +27 -0
- package/helmrelease/runtime/delivery.go +508 -0
- package/helmrelease/runtime/delivery_test.go +236 -0
- package/helmrelease/runtime/resources_test.go +60 -0
- package/helmrelease/values.go +99 -0
- package/knowledge/context-envelope.schema.json +1 -0
- package/mcp/gateway-manifest.schema.json +387 -72
- package/network/README.md +40 -0
- package/network/network-ingest-event.schema.json +228 -0
- package/network/network-runtime-protocol.schema.json +457 -1
- package/openapi/soha-api.json +54958 -38899
- package/openapi/soha-api.yaml +12143 -4984
- package/openapi/spec_generated.go +3 -3
- package/package.json +3 -1
- package/resource/manifest_health.go +155 -0
- package/resource/manifest_health_test.go +37 -0
- package/resource/models.go +14 -0
- package/resource/runtime/argocd.go +166 -0
- package/resource/runtime/argocd_integration_test.go +142 -0
- package/resource/runtime/argocd_observation.go +143 -0
- package/resource/runtime/argocd_operation.go +253 -0
- package/resource/runtime/argocd_resources.go +115 -0
- package/resource/runtime/argocd_resources_test.go +107 -0
- package/resource/runtime/argocd_test.go +386 -0
- package/resource/runtime/health.go +115 -0
- package/resource/runtime/health_test.go +78 -0
- package/resource/runtime/operator_integration_test.go +174 -0
- package/resource/runtime/ownership.go +178 -0
- package/resource/runtime/ownership_test.go +178 -0
- package/resource/runtime/rollout_integration_test.go +287 -0
- package/resource/runtime/rollout_observation.go +252 -0
- package/resource/runtime/rollout_operation.go +398 -0
- package/resource/runtime/rollout_resources.go +385 -0
- package/resource/runtime/rollout_task.go +165 -0
- package/resource/runtime/rollout_test.go +450 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"archive/tar"
|
|
5
|
+
"bytes"
|
|
6
|
+
"compress/gzip"
|
|
7
|
+
"context"
|
|
8
|
+
"encoding/base64"
|
|
9
|
+
"encoding/json"
|
|
10
|
+
"io"
|
|
11
|
+
"net/http"
|
|
12
|
+
"strings"
|
|
13
|
+
"testing"
|
|
14
|
+
"time"
|
|
15
|
+
|
|
16
|
+
"github.com/opensoha/soha-contracts/gen/go/sohaapi"
|
|
17
|
+
"github.com/opensoha/soha-contracts/helmrelease"
|
|
18
|
+
"helm.sh/helm/v4/pkg/action"
|
|
19
|
+
"helm.sh/helm/v4/pkg/chart/common"
|
|
20
|
+
"helm.sh/helm/v4/pkg/chart/loader"
|
|
21
|
+
chartv2 "helm.sh/helm/v4/pkg/chart/v2"
|
|
22
|
+
"helm.sh/helm/v4/pkg/engine"
|
|
23
|
+
"helm.sh/helm/v4/pkg/kube"
|
|
24
|
+
kubefake "helm.sh/helm/v4/pkg/kube/fake"
|
|
25
|
+
releasecommon "helm.sh/helm/v4/pkg/release/common"
|
|
26
|
+
releasev1 "helm.sh/helm/v4/pkg/release/v1"
|
|
27
|
+
"helm.sh/helm/v4/pkg/storage"
|
|
28
|
+
"helm.sh/helm/v4/pkg/storage/driver"
|
|
29
|
+
"k8s.io/apimachinery/pkg/api/meta"
|
|
30
|
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
31
|
+
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
32
|
+
kuberesource "k8s.io/cli-runtime/pkg/resource"
|
|
33
|
+
restfake "k8s.io/client-go/rest/fake"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
type observedHelmKubeClient struct {
|
|
37
|
+
kubefake.PrintingKubeClient
|
|
38
|
+
deleting bool
|
|
39
|
+
missing bool
|
|
40
|
+
namespace string
|
|
41
|
+
reads int
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func (c *observedHelmKubeClient) Build(io.Reader, bool) (kube.ResourceList, error) {
|
|
45
|
+
object := `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"app","namespace":"dev"}}`
|
|
46
|
+
client := &restfake.RESTClient{GroupVersion: schema.GroupVersion{Version: "v1"}, NegotiatedSerializer: kuberesource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, Client: restfake.CreateHTTPClient(func(*http.Request) (*http.Response, error) {
|
|
47
|
+
c.reads++
|
|
48
|
+
if c.missing {
|
|
49
|
+
return &http.Response{StatusCode: http.StatusNotFound, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(`{"apiVersion":"v1","kind":"Status","status":"Failure","reason":"NotFound","code":404}`))}, nil
|
|
50
|
+
}
|
|
51
|
+
body := object
|
|
52
|
+
if c.deleting {
|
|
53
|
+
body = strings.Replace(body, `"name":"app"`, `"name":"app","deletionTimestamp":"2026-09-12T00:00:00Z"`, 1)
|
|
54
|
+
}
|
|
55
|
+
return &http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": {"application/json"}}, Body: io.NopCloser(strings.NewReader(body))}, nil
|
|
56
|
+
})}
|
|
57
|
+
var item unstructured.Unstructured
|
|
58
|
+
if err := json.Unmarshal([]byte(object), &item); err != nil {
|
|
59
|
+
return nil, err
|
|
60
|
+
}
|
|
61
|
+
namespace := "dev"
|
|
62
|
+
if c.namespace != "" {
|
|
63
|
+
namespace = c.namespace
|
|
64
|
+
}
|
|
65
|
+
return kube.ResourceList{&kuberesource.Info{Client: client, Namespace: namespace, Name: "app", Object: &item, Mapping: &meta.RESTMapping{Resource: schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, GroupVersionKind: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Scope: meta.RESTScopeNamespace}}}, nil
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func TestHelmObservationUsesLiveResourceHealth(t *testing.T) {
|
|
69
|
+
client := &observedHelmKubeClient{}
|
|
70
|
+
cfg := &action.Configuration{KubeClient: client}
|
|
71
|
+
snapshot := sohaapi.HelmDeliverySnapshot{ApplicationID: "app", ServiceID: "svc", ApplicationEnvironmentID: "dev", TargetID: "target", DeliveryPlanID: "plan", ReleaseName: "app", Namespace: "dev"}
|
|
72
|
+
_, digest, err := helmDeliveryResources(cfg, "dev", "manifest", nil)
|
|
73
|
+
if err != nil {
|
|
74
|
+
t.Fatal(err)
|
|
75
|
+
}
|
|
76
|
+
snapshot.RenderedDigest = digest
|
|
77
|
+
release := &releasev1.Release{Name: "app", Namespace: "dev", Version: 1, Manifest: "manifest", Labels: helmDeliveryLabels(snapshot), Info: &releasev1.Info{Status: releasecommon.StatusDeployed}}
|
|
78
|
+
for _, deleting := range []bool{false, true} {
|
|
79
|
+
client.deleting = deleting
|
|
80
|
+
result, err := observeHelmDelivery(context.Background(), cfg, snapshot, release)
|
|
81
|
+
if err != nil || result.Ready == deleting || len(result.Resources) != 1 {
|
|
82
|
+
t.Fatalf("live health deleting=%v: %+v %v", deleting, result, err)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func TestHelmDeliveryFrozenRenderAndNativeRollbackRecovery(t *testing.T) {
|
|
88
|
+
manifest := "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: app\ndata:\n literal: '{{ this is data }}'\n"
|
|
89
|
+
hook := "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: hook\n annotations:\n helm.sh/hook: pre-install,pre-upgrade\n"
|
|
90
|
+
original := helmSourceTestArchive(t)
|
|
91
|
+
frozen, err := helmrelease.FrozenChartArchive(original, manifest, []string{hook})
|
|
92
|
+
if err != nil {
|
|
93
|
+
t.Fatal(err)
|
|
94
|
+
}
|
|
95
|
+
ch, err := loader.LoadArchive(bytes.NewReader(frozen))
|
|
96
|
+
if err != nil {
|
|
97
|
+
t.Fatal(err)
|
|
98
|
+
}
|
|
99
|
+
files, err := engine.Render(ch, map[string]any{})
|
|
100
|
+
if err != nil || files["app/templates/resources.yaml"] != manifest || files["app/templates/hook-0.yaml"] != hook {
|
|
101
|
+
t.Fatalf("frozen render changed approved resources: %v %#v", err, files)
|
|
102
|
+
}
|
|
103
|
+
cfg := &action.Configuration{Releases: storage.Init(driver.NewMemory()), KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, Capabilities: common.DefaultCapabilities}
|
|
104
|
+
snapshot := sohaapi.HelmDeliverySnapshot{ClusterID: "cluster", ApplicationID: "app", ServiceID: "svc", ApplicationEnvironmentID: "dev", TargetID: "target", DeliveryPlanID: "old-plan", ReleaseName: "app", Namespace: "dev", ChartDigest: helmrelease.SHA256(original)}
|
|
105
|
+
nativeChart, ok := ch.(*chartv2.Chart)
|
|
106
|
+
if !ok {
|
|
107
|
+
t.Fatal("frozen chart has an unexpected type")
|
|
108
|
+
}
|
|
109
|
+
for version := 1; version <= 2; version++ {
|
|
110
|
+
item := &releasev1.Release{Name: "app", Namespace: "dev", Version: version, Info: &releasev1.Info{Status: releasecommon.StatusDeployed}, Labels: helmDeliveryLabels(snapshot), Chart: nativeChart, Config: map[string]any{"version": version}, Manifest: manifest}
|
|
111
|
+
if err := cfg.Releases.Create(item); err != nil {
|
|
112
|
+
t.Fatal(err)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
snapshot.DeliveryPlanID, snapshot.ExpectedRevision, snapshot.RollbackRevision = "rollback-plan", 2, 1
|
|
116
|
+
if err := RequireUnmanaged(cfg, snapshot.ReleaseName); err == nil {
|
|
117
|
+
t.Fatal("legacy mutation accepted a governed release")
|
|
118
|
+
}
|
|
119
|
+
if err := RequireUnmanaged(cfg, "missing-release"); err != nil {
|
|
120
|
+
t.Fatal("ownership guard replaced normal missing-release handling", err)
|
|
121
|
+
}
|
|
122
|
+
snapshot.Operation = sohaapi.HelmDeliverySnapshotOperationRollback
|
|
123
|
+
_, snapshot.RenderedDigest, err = helmDeliveryResources(cfg, "dev", manifest, nil)
|
|
124
|
+
if err != nil {
|
|
125
|
+
t.Fatal(err)
|
|
126
|
+
}
|
|
127
|
+
rolledBack, err := runHelmDeliveryRollback(cfg, snapshot, time.Second)
|
|
128
|
+
if err != nil || rolledBack.Version != 3 || rolledBack.Config["version"] != 1 || !helmDeliveryAlreadyApplied(rolledBack, snapshot) {
|
|
129
|
+
t.Fatalf("native rollback did not produce the selected revision with new plan ownership: %+v %v", rolledBack, err)
|
|
130
|
+
}
|
|
131
|
+
old, err := currentHelmDelivery(cfg, "app", 1)
|
|
132
|
+
if err != nil || old.Labels["soha-delivery-plan"] == rolledBack.Labels["soha-delivery-plan"] {
|
|
133
|
+
t.Fatalf("rollback rewrote previous history: %v", err)
|
|
134
|
+
}
|
|
135
|
+
prepared := &sohaapi.HelmPreparedRelease{Manifest: manifest}
|
|
136
|
+
if err := json.Unmarshal([]byte(`{"version":1}`), &prepared.Values); err != nil {
|
|
137
|
+
t.Fatal(err)
|
|
138
|
+
}
|
|
139
|
+
snapshot.ValuesDigest = helmrelease.SHA256([]byte(`{"version":1}`))
|
|
140
|
+
result, err := Execute(context.Background(), cfg, sohaapi.HelmExecutionTaskPayload{Action: sohaapi.Apply, Snapshot: snapshot, Prepared: prepared})
|
|
141
|
+
if err != nil || !result.Ready || result.Revision != 3 {
|
|
142
|
+
t.Fatalf("recovered callback repeated or lost native rollback: %+v %v", result, err)
|
|
143
|
+
}
|
|
144
|
+
history, err := cfg.Releases.History("app")
|
|
145
|
+
if err != nil || len(history) != 3 {
|
|
146
|
+
t.Fatalf("retry created another native revision: %d %v", len(history), err)
|
|
147
|
+
}
|
|
148
|
+
old.Config["version"] = 99
|
|
149
|
+
if err := cfg.Releases.Update(old); err != nil {
|
|
150
|
+
t.Fatal(err)
|
|
151
|
+
}
|
|
152
|
+
if _, err := Execute(context.Background(), cfg, sohaapi.HelmExecutionTaskPayload{Action: sohaapi.Apply, Snapshot: snapshot, Prepared: prepared}); err == nil {
|
|
153
|
+
t.Fatal("rollback accepted changed historical values")
|
|
154
|
+
}
|
|
155
|
+
rolledBack.Info.Status = releasecommon.StatusFailed
|
|
156
|
+
result, err = observeHelmDelivery(context.Background(), cfg, snapshot, rolledBack)
|
|
157
|
+
if err == nil || result.Ready || !result.Stopped || result.Revision != 3 || result.Status != "failed" {
|
|
158
|
+
t.Fatalf("failed side effect disappeared: %+v %v", result, err)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func helmSourceTestArchive(t *testing.T) []byte {
|
|
163
|
+
t.Helper()
|
|
164
|
+
var buffer bytes.Buffer
|
|
165
|
+
zip := gzip.NewWriter(&buffer)
|
|
166
|
+
writer := tar.NewWriter(zip)
|
|
167
|
+
for name, content := range map[string]string{
|
|
168
|
+
"app/Chart.yaml": "apiVersion: v2\nname: app\nversion: 1.2.3\n",
|
|
169
|
+
"app/values.yaml": "replicaCount: 1\n",
|
|
170
|
+
"app/values.schema.json": `{"type":"object","properties":{"replicaCount":{"type":"integer","minimum":1}}}`,
|
|
171
|
+
} {
|
|
172
|
+
if err := writer.WriteHeader(&tar.Header{Name: name, Mode: 0600, Size: int64(len(content))}); err != nil {
|
|
173
|
+
t.Fatal(err)
|
|
174
|
+
}
|
|
175
|
+
if _, err := writer.Write([]byte(content)); err != nil {
|
|
176
|
+
t.Fatal(err)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if err := writer.Close(); err != nil {
|
|
180
|
+
t.Fatal(err)
|
|
181
|
+
}
|
|
182
|
+
if err := zip.Close(); err != nil {
|
|
183
|
+
t.Fatal(err)
|
|
184
|
+
}
|
|
185
|
+
return buffer.Bytes()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func TestHelmPrepareKeepsLiveClientsForResourceValidation(t *testing.T) {
|
|
189
|
+
client := &observedHelmKubeClient{missing: true}
|
|
190
|
+
releases := storage.Init(driver.NewMemory())
|
|
191
|
+
cfg := &action.Configuration{KubeClient: client, Releases: releases, Capabilities: common.DefaultCapabilities}
|
|
192
|
+
archive := helmSourceTestArchive(t)
|
|
193
|
+
input := sohaapi.HelmExecutionTaskPayload{Action: sohaapi.Preflight, Snapshot: sohaapi.HelmDeliverySnapshot{DeliveryPlanID: "plan", ApplicationID: "app", ServiceID: "svc", ServiceVersion: 1, ApplicationEnvironmentID: "dev", TargetID: "target", ClusterID: "cluster", Namespace: "dev", ReleaseName: "app", ChartDigest: helmrelease.SHA256(archive)}, Prepared: &sohaapi.HelmPreparedRelease{ChartArchive: base64.StdEncoding.EncodeToString(archive)}}
|
|
194
|
+
result, err := Prepare(context.Background(), cfg, input)
|
|
195
|
+
if err != nil || len(result.Snapshot.Resources) != 1 || cfg.KubeClient != client || cfg.Releases != releases {
|
|
196
|
+
t.Fatalf("prepare lost live validation: resources=%d client=%T err=%v", len(result.Snapshot.Resources), cfg.KubeClient, err)
|
|
197
|
+
}
|
|
198
|
+
if _, err := releases.Last("app"); err == nil {
|
|
199
|
+
t.Fatal("preparation persisted a release")
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
func TestHelmPreflightUsesClusterCapabilitiesWithoutTemplateLookup(t *testing.T) {
|
|
204
|
+
client := &observedHelmKubeClient{missing: true}
|
|
205
|
+
caps := common.DefaultCapabilities.Copy()
|
|
206
|
+
caps.KubeVersion = common.KubeVersion{Version: "v1.36.0", Major: "1", Minor: "36"}
|
|
207
|
+
caps.APIVersions = common.VersionSet{"v1"}
|
|
208
|
+
lookups := 0
|
|
209
|
+
cfg := &action.Configuration{KubeClient: client, Releases: storage.Init(driver.NewMemory()), Capabilities: caps, CustomTemplateFuncs: map[string]any{"lookup": func(string, string, string, string) (map[string]any, error) {
|
|
210
|
+
lookups++
|
|
211
|
+
return map[string]any{"secret": "private"}, nil
|
|
212
|
+
}}}
|
|
213
|
+
ch := &chartv2.Chart{Metadata: &chartv2.Metadata{APIVersion: "v2", Name: "app", Version: "1.0.0", KubeVersion: ">=1.35.0"}, Templates: []*common.File{{Name: "templates/config.yaml", Data: []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: app\ndata:\n version: {{ .Capabilities.KubeVersion.Version | quote }}\n absentAPI: {{ .Capabilities.APIVersions.Has \"apps/v1\" | quote }}\n lookup: {{ lookup \"v1\" \"Secret\" \"other-namespace\" \"private\" | toJson | quote }}\n")}}}
|
|
214
|
+
input := sohaapi.HelmExecutionTaskPayload{Snapshot: sohaapi.HelmDeliverySnapshot{Operation: sohaapi.HelmDeliverySnapshotOperationInstall, ReleaseName: "app", Namespace: "dev"}}
|
|
215
|
+
result, err := runHelmDeliveryAction(context.Background(), cfg, input, ch, true)
|
|
216
|
+
if err != nil {
|
|
217
|
+
t.Fatal(err)
|
|
218
|
+
}
|
|
219
|
+
if lookups != 0 || !strings.Contains(result.Manifest, `version: "v1.36.0"`) || !strings.Contains(result.Manifest, `absentAPI: "false"`) || !strings.Contains(result.Manifest, `lookup: "{}"`) {
|
|
220
|
+
t.Fatal("preflight ignored actual capabilities or read template data", result.Manifest)
|
|
221
|
+
}
|
|
222
|
+
lookup, ok := cfg.CustomTemplateFuncs["lookup"].(func(string, string, string, string) (map[string]any, error))
|
|
223
|
+
if !ok {
|
|
224
|
+
t.Fatal("preflight replaced the caller's template functions")
|
|
225
|
+
}
|
|
226
|
+
if original, err := lookup("", "", "", ""); err != nil || original["secret"] != "private" {
|
|
227
|
+
t.Fatal("preflight changed the caller's lookup function")
|
|
228
|
+
}
|
|
229
|
+
if _, err := cfg.Releases.Last("app"); err == nil {
|
|
230
|
+
t.Fatal("preflight persisted a release")
|
|
231
|
+
}
|
|
232
|
+
client.namespace, client.reads = "other-namespace", 0
|
|
233
|
+
if _, err := runHelmDeliveryAction(context.Background(), cfg, input, ch, true); err == nil || client.reads != 0 {
|
|
234
|
+
t.Fatal("preflight read a resource outside the authorized namespace", err, client.reads)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"io"
|
|
5
|
+
"testing"
|
|
6
|
+
|
|
7
|
+
"helm.sh/helm/v4/pkg/action"
|
|
8
|
+
"helm.sh/helm/v4/pkg/kube"
|
|
9
|
+
kubefake "helm.sh/helm/v4/pkg/kube/fake"
|
|
10
|
+
"k8s.io/apimachinery/pkg/api/meta"
|
|
11
|
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
12
|
+
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
13
|
+
cliresource "k8s.io/cli-runtime/pkg/resource"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
type deliveryResourceClient struct {
|
|
17
|
+
*kubefake.PrintingKubeClient
|
|
18
|
+
items kube.ResourceList
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func (c deliveryResourceClient) Build(io.Reader, bool) (kube.ResourceList, error) {
|
|
22
|
+
return c.items, nil
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func TestHelmResourceAdmissionRejectsUntrackedOrRetainedObjects(t *testing.T) {
|
|
26
|
+
for _, test := range []struct {
|
|
27
|
+
name, namespace, objectName string
|
|
28
|
+
scope meta.RESTScope
|
|
29
|
+
keep, duplicate, invalid bool
|
|
30
|
+
}{
|
|
31
|
+
{name: "namespaced", namespace: "dev", objectName: "app", scope: meta.RESTScopeNamespace},
|
|
32
|
+
{name: "other namespace", namespace: "prod", objectName: "app", scope: meta.RESTScopeNamespace, invalid: true},
|
|
33
|
+
{name: "cluster scope", objectName: "app", scope: meta.RESTScopeRoot, invalid: true},
|
|
34
|
+
{name: "generated name", namespace: "dev", scope: meta.RESTScopeNamespace, invalid: true},
|
|
35
|
+
{name: "retained resource", namespace: "dev", objectName: "app", scope: meta.RESTScopeNamespace, keep: true, invalid: true},
|
|
36
|
+
{name: "duplicate resource", namespace: "dev", objectName: "app", scope: meta.RESTScopeNamespace, duplicate: true, invalid: true},
|
|
37
|
+
} {
|
|
38
|
+
t.Run(test.name, func(t *testing.T) {
|
|
39
|
+
object := &unstructured.Unstructured{Object: map[string]any{"apiVersion": "v1", "kind": "ConfigMap"}}
|
|
40
|
+
object.SetName(test.objectName)
|
|
41
|
+
object.SetNamespace(test.namespace)
|
|
42
|
+
if test.keep {
|
|
43
|
+
object.SetAnnotations(map[string]string{"helm.sh/resource-policy": "keep"})
|
|
44
|
+
}
|
|
45
|
+
item := &cliresource.Info{Name: test.objectName, Namespace: test.namespace, Object: object, Mapping: &meta.RESTMapping{Scope: test.scope, GroupVersionKind: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}}}
|
|
46
|
+
items := kube.ResourceList{item}
|
|
47
|
+
if test.duplicate {
|
|
48
|
+
items = append(items, item)
|
|
49
|
+
}
|
|
50
|
+
cfg := &action.Configuration{KubeClient: deliveryResourceClient{&kubefake.PrintingKubeClient{Out: io.Discard}, items}}
|
|
51
|
+
resources, digest, err := helmDeliveryResources(cfg, "dev", "manifest", nil)
|
|
52
|
+
if (err != nil) != test.invalid {
|
|
53
|
+
t.Fatalf("err=%v", err)
|
|
54
|
+
}
|
|
55
|
+
if err == nil && (len(resources) != 1 || len(digest) != 71) {
|
|
56
|
+
t.Fatal("resource identity or digest missing")
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
package helmrelease
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"errors"
|
|
6
|
+
"fmt"
|
|
7
|
+
"strconv"
|
|
8
|
+
"strings"
|
|
9
|
+
|
|
10
|
+
"github.com/opensoha/soha-contracts/gen/go/sohaapi"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// MapImages returns a copy; mapping never changes a saved values configuration.
|
|
14
|
+
func MapImages(values map[string]any, mappings []sohaapi.HelmImageMapping, images map[string]string) (map[string]any, error) {
|
|
15
|
+
encoded, err := json.Marshal(values)
|
|
16
|
+
if err != nil {
|
|
17
|
+
return nil, err
|
|
18
|
+
}
|
|
19
|
+
result := map[string]any{}
|
|
20
|
+
if err := json.Unmarshal(encoded, &result); err != nil {
|
|
21
|
+
return nil, err
|
|
22
|
+
}
|
|
23
|
+
if result == nil {
|
|
24
|
+
result = map[string]any{}
|
|
25
|
+
}
|
|
26
|
+
for _, mapping := range mappings {
|
|
27
|
+
image := images[mapping.ContainerName]
|
|
28
|
+
repository, digest, ok := strings.Cut(image, "@")
|
|
29
|
+
if !ok || repository == "" || !helmSHA256.MatchString(digest) {
|
|
30
|
+
return nil, fmt.Errorf("helm image mapping requires a verified digest for container %s", mapping.ContainerName)
|
|
31
|
+
}
|
|
32
|
+
value := image
|
|
33
|
+
switch mapping.Value {
|
|
34
|
+
case sohaapi.HelmImageMappingValueRepository:
|
|
35
|
+
value = repository
|
|
36
|
+
case sohaapi.HelmImageMappingValueDigest:
|
|
37
|
+
value = digest
|
|
38
|
+
case sohaapi.HelmImageMappingValueImage:
|
|
39
|
+
default:
|
|
40
|
+
return nil, errors.New("unsupported Helm image mapping value")
|
|
41
|
+
}
|
|
42
|
+
parts, err := pointerParts(mapping.Path)
|
|
43
|
+
if err != nil {
|
|
44
|
+
return nil, err
|
|
45
|
+
}
|
|
46
|
+
if err := setPointer(result, parts, value); err != nil {
|
|
47
|
+
return nil, fmt.Errorf("helm image path %s: %w", mapping.Path, err)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return result, nil
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func pointerParts(pointer string) ([]string, error) {
|
|
54
|
+
if !strings.HasPrefix(pointer, "/") || len(pointer) > 512 {
|
|
55
|
+
return nil, errors.New("helm image path must be an RFC 6901 pointer of at most 512 characters")
|
|
56
|
+
}
|
|
57
|
+
parts := strings.Split(pointer[1:], "/")
|
|
58
|
+
for index, part := range parts {
|
|
59
|
+
for offset := 0; offset < len(part); offset++ {
|
|
60
|
+
if part[offset] == '~' {
|
|
61
|
+
if offset+1 >= len(part) || part[offset+1] != '0' && part[offset+1] != '1' {
|
|
62
|
+
return nil, errors.New("invalid escape in Helm image path")
|
|
63
|
+
}
|
|
64
|
+
offset++
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
parts[index] = strings.ReplaceAll(strings.ReplaceAll(part, "~1", "/"), "~0", "~")
|
|
68
|
+
}
|
|
69
|
+
return parts, nil
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func setPointer(node any, parts []string, value string) error {
|
|
73
|
+
key := parts[0]
|
|
74
|
+
switch typed := node.(type) {
|
|
75
|
+
case map[string]any:
|
|
76
|
+
if len(parts) == 1 {
|
|
77
|
+
typed[key] = value
|
|
78
|
+
return nil
|
|
79
|
+
}
|
|
80
|
+
child, ok := typed[key]
|
|
81
|
+
if !ok {
|
|
82
|
+
child = map[string]any{}
|
|
83
|
+
typed[key] = child
|
|
84
|
+
}
|
|
85
|
+
return setPointer(child, parts[1:], value)
|
|
86
|
+
case []any:
|
|
87
|
+
index, err := strconv.Atoi(key)
|
|
88
|
+
if err != nil || index < 0 || index >= len(typed) || strconv.Itoa(index) != key {
|
|
89
|
+
return errors.New("array index is missing or out of bounds")
|
|
90
|
+
}
|
|
91
|
+
if len(parts) == 1 {
|
|
92
|
+
typed[index] = value
|
|
93
|
+
return nil
|
|
94
|
+
}
|
|
95
|
+
return setPointer(typed[index], parts[1:], value)
|
|
96
|
+
default:
|
|
97
|
+
return errors.New("path traverses a scalar value")
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"environment": { "type": "object" },
|
|
23
23
|
"budgets": { "type": "object" },
|
|
24
24
|
"budgetUsage": { "type": "object" },
|
|
25
|
+
"truncations": { "type": "array", "items": { "type": "string" } },
|
|
25
26
|
"policySnapshot": { "$ref": "#/$defs/versionRef" },
|
|
26
27
|
"contentHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
27
28
|
"createdAt": { "type": "string", "format": "date-time" }
|