@opensoha/contracts 0.1.27 → 0.1.29

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 (94) hide show
  1. package/COMPATIBILITY.md +4 -3
  2. package/README.md +8 -9
  3. package/compat/consumer-revisions.json +1 -1
  4. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  5. package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
  6. package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/metric-cluster-value-length.json +3 -0
  7. package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/metric-cluster-value-type.json +3 -0
  8. package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/metric-cluster-alias.json +5 -0
  9. package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/metric-cluster-reset.json +3 -0
  10. package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/invalid/revision-overflow.json +4 -0
  11. package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/valid/legacy.json +3 -0
  12. package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/valid/observed-revision.json +4 -0
  13. package/fixtures/openapi/KubernetesJobDetail/invalid/execution-observation.json +23 -0
  14. package/fixtures/openapi/KubernetesJobDetail/valid/execution-observation.json +23 -0
  15. package/fixtures/openapi/KubernetesNode/invalid/labels.json +10 -0
  16. package/fixtures/openapi/KubernetesNode/valid/labels.json +11 -0
  17. package/fixtures/openapi/KubernetesPodListEnvelope/invalid/empty-uid.json +13 -0
  18. package/fixtures/openapi/KubernetesPodListEnvelope/invalid/invalid-container-state.json +20 -0
  19. package/fixtures/openapi/KubernetesPodListEnvelope/invalid/missing-container-ready.json +19 -0
  20. package/fixtures/openapi/KubernetesPodListEnvelope/valid/container-statuses.json +67 -0
  21. package/fixtures/openapi/KubernetesPodListEnvelope/valid/running-pod.json +1 -0
  22. package/fixtures/openapi/KubernetesPrometheusDetail/invalid/metric-cluster-value-type.json +4 -0
  23. package/fixtures/openapi/KubernetesPrometheusDetail/valid/metric-cluster-alias.json +5 -0
  24. package/fixtures/openapi/KubernetesResourceGraphEnvelope/invalid/network-edge-source-payload.json +73 -0
  25. package/fixtures/openapi/KubernetesResourceGraphEnvelope/valid/network-edge-facts.json +67 -0
  26. package/fixtures/openapi/KubernetesSecretCertificate/invalid/secret-data.json +1 -0
  27. package/fixtures/openapi/KubernetesSecretCertificate/invalid/status.json +1 -0
  28. package/fixtures/openapi/KubernetesSecretCertificate/valid/available.json +1 -0
  29. package/fixtures/openapi/KubernetesSecretCertificate/valid/missing.json +1 -0
  30. package/gen/go/sohaapi/client.go +1 -1
  31. package/gen/go/sohaapi/client_test.go +13 -0
  32. package/gen/go/sohaapi/kubernetes_workload_optional_test.go +40 -0
  33. package/gen/go/sohaapi/pod_status_compat_test.go +36 -0
  34. package/gen/go/sohaapi/types.go +421 -88
  35. package/gen/ts/sohaapi/index.ts +756 -4
  36. package/helmrelease/runtime/values_upgrade.go +78 -0
  37. package/helmrelease/runtime/values_upgrade_test.go +123 -0
  38. package/helmrelease/values_update.go +31 -0
  39. package/openapi/soha-api.json +1191 -14
  40. package/openapi/soha-api.yaml +588 -28
  41. package/openapi/spec_generated.go +3 -3
  42. package/package.json +1 -1
  43. package/resource/agent_profiles.go +9 -15
  44. package/resource/agent_profiles_test.go +9 -3
  45. package/resource/custom_graph_page.go +18 -0
  46. package/resource/models.go +102 -57
  47. package/resource/network_topology.go +22 -0
  48. package/resource/network_topology_projection.go +36 -22
  49. package/resource/network_topology_projection_test.go +4 -0
  50. package/resource/network_topology_test.go +20 -0
  51. package/resource/network_topology_validation.go +5 -0
  52. package/resource/observed_workload.go +18 -0
  53. package/resource/pod_status_test.go +22 -0
  54. package/resource/rbac_graph.go +4 -0
  55. package/resource/rollout_optional_test.go +33 -0
  56. package/resource/runtime/network_relationship_primitives.go +66 -0
  57. package/resource/runtime/network_relationship_primitives_test.go +147 -0
  58. package/resource/runtime/network_topology.go +17 -5
  59. package/resource/runtime/network_topology_builder.go +405 -0
  60. package/resource/runtime/network_topology_builder_backends.go +195 -0
  61. package/resource/runtime/network_topology_builder_gateway_l4_test.go +86 -0
  62. package/resource/runtime/network_topology_builder_ingress.go +302 -0
  63. package/resource/runtime/network_topology_builder_legacy.go +66 -0
  64. package/resource/runtime/network_topology_builder_providers.go +242 -0
  65. package/resource/runtime/network_topology_builder_test.go +186 -0
  66. package/resource/runtime/network_topology_declarations.go +329 -0
  67. package/resource/runtime/resource_edit.go +22 -0
  68. package/resource/runtime/resource_edit_test.go +32 -0
  69. package/resource/runtime/resource_graph_collect.go +325 -0
  70. package/resource/runtime/resource_graph_configuration.go +59 -0
  71. package/resource/runtime/resource_graph_configuration_collect.go +198 -0
  72. package/resource/runtime/resource_graph_configuration_test.go +470 -0
  73. package/resource/runtime/resource_graph_custom_page.go +159 -0
  74. package/resource/runtime/resource_graph_custom_root.go +132 -0
  75. package/resource/runtime/resource_graph_custom_root_test.go +197 -0
  76. package/resource/runtime/resource_graph_enrichment.go +417 -0
  77. package/resource/runtime/resource_graph_enrichment_hpa_test.go +117 -0
  78. package/resource/runtime/resource_graph_enrichment_test.go +201 -0
  79. package/resource/runtime/resource_graph_metadata.go +125 -0
  80. package/resource/runtime/resource_graph_metadata_test.go +204 -0
  81. package/resource/runtime/resource_graph_network.go +224 -0
  82. package/resource/runtime/resource_graph_network_collect.go +174 -0
  83. package/resource/runtime/resource_graph_network_collect_test.go +147 -0
  84. package/resource/runtime/resource_graph_network_test.go +342 -0
  85. package/resource/runtime/resource_graph_rbac.go +278 -0
  86. package/resource/runtime/resource_graph_rbac_test.go +372 -0
  87. package/resource/runtime/resource_graph_rules.go +834 -0
  88. package/resource/runtime/resource_graph_storage.go +144 -0
  89. package/resource/runtime/resource_graph_storage_collect.go +163 -0
  90. package/resource/runtime/resource_graph_storage_test.go +397 -0
  91. package/resource/runtime/resource_graph_test.go +627 -0
  92. package/resource/runtime/secret_certificate.go +44 -0
  93. package/resource/runtime/secret_certificate_test.go +87 -0
  94. package/resource/storage_graph.go +4 -0
@@ -0,0 +1,78 @@
1
+ package runtime
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+
7
+ "github.com/opensoha/soha-contracts/helmrelease"
8
+ "helm.sh/helm/v4/pkg/action"
9
+ "helm.sh/helm/v4/pkg/release"
10
+ helmreleasev1 "helm.sh/helm/v4/pkg/release/v1"
11
+ "helm.sh/helm/v4/pkg/storage/driver"
12
+ )
13
+
14
+ // GuardValuesUpgradeRevision protects a single Values upgrade on a request-local Helm configuration.
15
+ func GuardValuesUpgradeRevision(cfg *action.Configuration, upgrade *action.Upgrade, current *helmreleasev1.Release, expected int) error {
16
+ if expected == 0 {
17
+ return nil
18
+ }
19
+ if !helmrelease.ValuesExpectedRevision(expected).Valid() || current.Version != expected ||
20
+ (current.Info != nil && current.Info.Status.IsPending()) {
21
+ return helmValuesRevisionConflict()
22
+ }
23
+ // Storage.Create otherwise prunes history before it attempts to reserve the revision.
24
+ upgrade.MaxHistory = 0
25
+ cfg.Releases.Driver = valuesRevisionDriver{Driver: cfg.Releases.Driver, name: current.Name, expected: expected}
26
+ return nil
27
+ }
28
+
29
+ type valuesRevisionDriver struct {
30
+ driver.Driver
31
+ name string
32
+ expected int
33
+ }
34
+
35
+ func (d valuesRevisionDriver) Query(labels map[string]string) ([]release.Releaser, error) {
36
+ items, err := d.Driver.Query(labels)
37
+ // Helm's preparation reads history again and can encounter a newer pending release.
38
+ if len(labels) != 2 || labels["name"] != d.name || labels["owner"] != "helm" {
39
+ return items, err
40
+ }
41
+ if errors.Is(err, driver.ErrReleaseNotFound) {
42
+ return nil, helmValuesRevisionConflict()
43
+ }
44
+ if err != nil {
45
+ return nil, err
46
+ }
47
+ latest := 0
48
+ for _, item := range items {
49
+ revision, err := release.NewAccessor(item)
50
+ if err != nil {
51
+ return nil, err
52
+ }
53
+ latest = max(latest, revision.Version())
54
+ }
55
+ if latest != d.expected {
56
+ return nil, helmValuesRevisionConflict()
57
+ }
58
+ return items, nil
59
+ }
60
+
61
+ func (d valuesRevisionDriver) Create(key string, item release.Releaser) error {
62
+ revision, err := release.NewAccessor(item)
63
+ if err != nil {
64
+ return err
65
+ }
66
+ if revision.Name() != d.name || revision.Version() != d.expected+1 {
67
+ return helmValuesRevisionConflict()
68
+ }
69
+ err = d.Driver.Create(key, item)
70
+ if errors.Is(err, driver.ErrReleaseExists) {
71
+ return helmValuesRevisionConflict()
72
+ }
73
+ return err
74
+ }
75
+
76
+ func helmValuesRevisionConflict() error {
77
+ return fmt.Errorf("%w: Helm release revision changed or is pending; refresh its values before saving", ErrConflict)
78
+ }
@@ -0,0 +1,123 @@
1
+ package runtime
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "io"
7
+ "testing"
8
+
9
+ "helm.sh/helm/v4/pkg/action"
10
+ "helm.sh/helm/v4/pkg/chart/common"
11
+ chartv2 "helm.sh/helm/v4/pkg/chart/v2"
12
+ "helm.sh/helm/v4/pkg/kube"
13
+ kubefake "helm.sh/helm/v4/pkg/kube/fake"
14
+ "helm.sh/helm/v4/pkg/release"
15
+ releasecommon "helm.sh/helm/v4/pkg/release/common"
16
+ releasev1 "helm.sh/helm/v4/pkg/release/v1"
17
+ "helm.sh/helm/v4/pkg/storage"
18
+ "helm.sh/helm/v4/pkg/storage/driver"
19
+ )
20
+
21
+ func TestGuardValuesUpgradeRevision(t *testing.T) {
22
+ for _, tc := range []struct {
23
+ name string
24
+ expected int
25
+ change bool
26
+ collision bool
27
+ wantRevisions int
28
+ wantWrites int
29
+ }{
30
+ {name: "stale before upgrade", expected: 1, wantRevisions: 2},
31
+ {name: "external pending revision after editor read", expected: 2, change: true, wantRevisions: 3},
32
+ {name: "same revision storage collision", expected: 2, collision: true, wantRevisions: 3},
33
+ {name: "current revision", expected: 2, wantRevisions: 3, wantWrites: 1},
34
+ } {
35
+ t.Run(tc.name, func(t *testing.T) {
36
+ chart := &chartv2.Chart{Metadata: &chartv2.Metadata{APIVersion: "v2", Name: "app", Version: "1.0.0"}}
37
+ first := &releasev1.Release{Name: "app", Namespace: "test", Version: 1, Chart: chart, Info: &releasev1.Info{Status: releasecommon.StatusSuperseded}}
38
+ current := &releasev1.Release{Name: "app", Namespace: "test", Version: 2, Chart: chart, Info: &releasev1.Info{Status: releasecommon.StatusDeployed}}
39
+ winner := &releasev1.Release{Name: "app", Namespace: "test", Version: 3, Chart: chart, Info: &releasev1.Info{Status: releasecommon.StatusPendingUpgrade}, Config: map[string]any{"winner": true}}
40
+ history := storage.Init(driver.NewMemory())
41
+ for _, item := range []*releasev1.Release{first, current} {
42
+ if err := history.Create(item); err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ }
46
+ kubeClient := &valuesUpgradeKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, history: history}
47
+ racingDriver := valuesUpgradeRaceDriver{Driver: history.Driver}
48
+ if tc.collision {
49
+ racingDriver.winner = winner
50
+ }
51
+ cfg := &action.Configuration{Releases: storage.Init(racingDriver), KubeClient: kubeClient, Capabilities: common.DefaultCapabilities}
52
+ upgrade := action.NewUpgrade(cfg)
53
+ upgrade.Namespace = current.Namespace
54
+ upgrade.MaxHistory = 1
55
+ err := GuardValuesUpgradeRevision(cfg, upgrade, current, tc.expected)
56
+ if tc.change {
57
+ if err := history.Create(winner); err != nil {
58
+ t.Fatal(err)
59
+ }
60
+ }
61
+ if err == nil {
62
+ _, err = upgrade.RunWithContext(context.Background(), current.Name, chart, map[string]any{"edited": true})
63
+ }
64
+ if tc.wantWrites == 0 && !errors.Is(err, ErrConflict) || tc.wantWrites > 0 && err != nil {
65
+ t.Fatalf("upgrade error = %v", err)
66
+ }
67
+ if kubeClient.writes != tc.wantWrites {
68
+ t.Fatalf("Kubernetes updates = %d, want %d", kubeClient.writes, tc.wantWrites)
69
+ }
70
+ if kubeClient.writes > 0 && !kubeClient.reservedBeforeUpdate {
71
+ t.Fatal("Kubernetes updated before revision 3 was reserved")
72
+ }
73
+ items, err := history.History(current.Name)
74
+ if err != nil || len(items) != tc.wantRevisions {
75
+ t.Fatalf("history contains %d revisions, want %d, err=%v", len(items), tc.wantRevisions, err)
76
+ }
77
+ if tc.change || tc.collision {
78
+ latest, err := history.Last(current.Name)
79
+ if err != nil {
80
+ t.Fatal(err)
81
+ }
82
+ latestRelease, ok := latest.(*releasev1.Release)
83
+ if !ok {
84
+ t.Fatalf("latest revision has unexpected type %T", latest)
85
+ }
86
+ if latestRelease.Config["winner"] != true {
87
+ t.Fatal("concurrent writer's Values were overwritten")
88
+ }
89
+ }
90
+ })
91
+ }
92
+ }
93
+
94
+ type valuesUpgradeRaceDriver struct {
95
+ driver.Driver
96
+ winner *releasev1.Release
97
+ }
98
+
99
+ func (d valuesUpgradeRaceDriver) Create(key string, item release.Releaser) error {
100
+ if d.winner != nil {
101
+ if err := d.Driver.Create(key, d.winner); err != nil {
102
+ return err
103
+ }
104
+ }
105
+ return d.Driver.Create(key, item)
106
+ }
107
+
108
+ type valuesUpgradeKubeClient struct {
109
+ kubefake.PrintingKubeClient
110
+ history *storage.Storage
111
+ writes int
112
+ reservedBeforeUpdate bool
113
+ }
114
+
115
+ func (c *valuesUpgradeKubeClient) Update(original, target kube.ResourceList, opts ...kube.ClientUpdateOption) (*kube.Result, error) {
116
+ c.writes++
117
+ latest, err := c.history.Last("app")
118
+ if err == nil {
119
+ latestRelease, ok := latest.(*releasev1.Release)
120
+ c.reservedBeforeUpdate = ok && latestRelease.Version == 3 && latestRelease.Info.Status == releasecommon.StatusPendingUpgrade
121
+ }
122
+ return c.PrintingKubeClient.Update(original, target, opts...)
123
+ }
@@ -0,0 +1,31 @@
1
+ package helmrelease
2
+
3
+ import (
4
+ "encoding/json"
5
+ "errors"
6
+ "math"
7
+ )
8
+
9
+ type ValuesUpdateRequest struct {
10
+ Content string `json:"content"`
11
+ ExpectedRevision ValuesExpectedRevision `json:"expectedRevision,omitempty"`
12
+ }
13
+
14
+ // ValuesExpectedRevision uses zero internally for legacy requests that omit the field.
15
+ type ValuesExpectedRevision int
16
+
17
+ func (r ValuesExpectedRevision) Valid() bool {
18
+ return r > 0 && r < math.MaxInt32
19
+ }
20
+
21
+ func (r *ValuesExpectedRevision) UnmarshalJSON(data []byte) error {
22
+ var value int
23
+ if err := json.Unmarshal(data, &value); err != nil {
24
+ return err
25
+ }
26
+ if !ValuesExpectedRevision(value).Valid() {
27
+ return errors.New("expectedRevision must be between 1 and 2147483646")
28
+ }
29
+ *r = ValuesExpectedRevision(value)
30
+ return nil
31
+ }