@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.
- package/COMPATIBILITY.md +4 -3
- package/README.md +8 -9
- package/compat/consumer-revisions.json +1 -1
- package/compat/json-schema-ecosystem-0.1.0.json +1 -1
- package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/metric-cluster-value-length.json +3 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/metric-cluster-value-type.json +3 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/metric-cluster-alias.json +5 -0
- package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/metric-cluster-reset.json +3 -0
- package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/invalid/revision-overflow.json +4 -0
- package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/valid/legacy.json +3 -0
- package/fixtures/openapi/KubernetesHelmReleaseValuesUpdateInput/valid/observed-revision.json +4 -0
- package/fixtures/openapi/KubernetesJobDetail/invalid/execution-observation.json +23 -0
- package/fixtures/openapi/KubernetesJobDetail/valid/execution-observation.json +23 -0
- package/fixtures/openapi/KubernetesNode/invalid/labels.json +10 -0
- package/fixtures/openapi/KubernetesNode/valid/labels.json +11 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/invalid/empty-uid.json +13 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/invalid/invalid-container-state.json +20 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/invalid/missing-container-ready.json +19 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/valid/container-statuses.json +67 -0
- package/fixtures/openapi/KubernetesPodListEnvelope/valid/running-pod.json +1 -0
- package/fixtures/openapi/KubernetesPrometheusDetail/invalid/metric-cluster-value-type.json +4 -0
- package/fixtures/openapi/KubernetesPrometheusDetail/valid/metric-cluster-alias.json +5 -0
- package/fixtures/openapi/KubernetesResourceGraphEnvelope/invalid/network-edge-source-payload.json +73 -0
- package/fixtures/openapi/KubernetesResourceGraphEnvelope/valid/network-edge-facts.json +67 -0
- package/fixtures/openapi/KubernetesSecretCertificate/invalid/secret-data.json +1 -0
- package/fixtures/openapi/KubernetesSecretCertificate/invalid/status.json +1 -0
- package/fixtures/openapi/KubernetesSecretCertificate/valid/available.json +1 -0
- package/fixtures/openapi/KubernetesSecretCertificate/valid/missing.json +1 -0
- package/gen/go/sohaapi/client.go +1 -1
- package/gen/go/sohaapi/client_test.go +13 -0
- package/gen/go/sohaapi/kubernetes_workload_optional_test.go +40 -0
- package/gen/go/sohaapi/pod_status_compat_test.go +36 -0
- package/gen/go/sohaapi/types.go +421 -88
- package/gen/ts/sohaapi/index.ts +756 -4
- package/helmrelease/runtime/values_upgrade.go +78 -0
- package/helmrelease/runtime/values_upgrade_test.go +123 -0
- package/helmrelease/values_update.go +31 -0
- package/openapi/soha-api.json +1191 -14
- package/openapi/soha-api.yaml +588 -28
- package/openapi/spec_generated.go +3 -3
- package/package.json +1 -1
- package/resource/agent_profiles.go +9 -15
- package/resource/agent_profiles_test.go +9 -3
- package/resource/custom_graph_page.go +18 -0
- package/resource/models.go +102 -57
- package/resource/network_topology.go +22 -0
- package/resource/network_topology_projection.go +36 -22
- package/resource/network_topology_projection_test.go +4 -0
- package/resource/network_topology_test.go +20 -0
- package/resource/network_topology_validation.go +5 -0
- package/resource/observed_workload.go +18 -0
- package/resource/pod_status_test.go +22 -0
- package/resource/rbac_graph.go +4 -0
- package/resource/rollout_optional_test.go +33 -0
- package/resource/runtime/network_relationship_primitives.go +66 -0
- package/resource/runtime/network_relationship_primitives_test.go +147 -0
- package/resource/runtime/network_topology.go +17 -5
- package/resource/runtime/network_topology_builder.go +405 -0
- package/resource/runtime/network_topology_builder_backends.go +195 -0
- package/resource/runtime/network_topology_builder_gateway_l4_test.go +86 -0
- package/resource/runtime/network_topology_builder_ingress.go +302 -0
- package/resource/runtime/network_topology_builder_legacy.go +66 -0
- package/resource/runtime/network_topology_builder_providers.go +242 -0
- package/resource/runtime/network_topology_builder_test.go +186 -0
- package/resource/runtime/network_topology_declarations.go +329 -0
- package/resource/runtime/resource_edit.go +22 -0
- package/resource/runtime/resource_edit_test.go +32 -0
- package/resource/runtime/resource_graph_collect.go +325 -0
- package/resource/runtime/resource_graph_configuration.go +59 -0
- package/resource/runtime/resource_graph_configuration_collect.go +198 -0
- package/resource/runtime/resource_graph_configuration_test.go +470 -0
- package/resource/runtime/resource_graph_custom_page.go +159 -0
- package/resource/runtime/resource_graph_custom_root.go +132 -0
- package/resource/runtime/resource_graph_custom_root_test.go +197 -0
- package/resource/runtime/resource_graph_enrichment.go +417 -0
- package/resource/runtime/resource_graph_enrichment_hpa_test.go +117 -0
- package/resource/runtime/resource_graph_enrichment_test.go +201 -0
- package/resource/runtime/resource_graph_metadata.go +125 -0
- package/resource/runtime/resource_graph_metadata_test.go +204 -0
- package/resource/runtime/resource_graph_network.go +224 -0
- package/resource/runtime/resource_graph_network_collect.go +174 -0
- package/resource/runtime/resource_graph_network_collect_test.go +147 -0
- package/resource/runtime/resource_graph_network_test.go +342 -0
- package/resource/runtime/resource_graph_rbac.go +278 -0
- package/resource/runtime/resource_graph_rbac_test.go +372 -0
- package/resource/runtime/resource_graph_rules.go +834 -0
- package/resource/runtime/resource_graph_storage.go +144 -0
- package/resource/runtime/resource_graph_storage_collect.go +163 -0
- package/resource/runtime/resource_graph_storage_test.go +397 -0
- package/resource/runtime/resource_graph_test.go +627 -0
- package/resource/runtime/secret_certificate.go +44 -0
- package/resource/runtime/secret_certificate_test.go +87 -0
- 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
|
+
}
|