@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,144 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
cr "github.com/opensoha/soha-contracts/resource"
|
|
5
|
+
corev1 "k8s.io/api/core/v1"
|
|
6
|
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
const (
|
|
10
|
+
ResourceGraphRelationMounts = "mounts"
|
|
11
|
+
ResourceGraphRelationBindsTo = "binds-to"
|
|
12
|
+
ResourceGraphRelationUsesStorageClass = "uses-storage-class"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func graphStorageRootKind(kind string) bool {
|
|
16
|
+
switch kind {
|
|
17
|
+
case "PersistentVolumeClaim", "PersistentVolume", "StorageClass":
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func graphStorageClassName(o *unstructured.Unstructured) string {
|
|
24
|
+
name, _, _ := unstructured.NestedString(o.Object, "spec", "storageClassName")
|
|
25
|
+
return name
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func graphClaimVolumeName(claim *unstructured.Unstructured) string {
|
|
29
|
+
name, _, _ := unstructured.NestedString(claim.Object, "spec", "volumeName")
|
|
30
|
+
return name
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func graphVolumeClaimName(volume map[string]interface{}) string {
|
|
34
|
+
return graphString(graphMap(volume, "persistentVolumeClaim"), "claimName")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func graphVolumeClaimRef(volume *unstructured.Unstructured) (graphNamespacedName, string) {
|
|
38
|
+
ref, _, _ := graphNestedMap(volume.Object, "spec", "claimRef")
|
|
39
|
+
if api := graphString(ref, "apiVersion"); api != "" && api != "v1" {
|
|
40
|
+
return graphNamespacedName{}, ""
|
|
41
|
+
}
|
|
42
|
+
if kind := graphString(ref, "kind"); kind != "" && kind != "PersistentVolumeClaim" {
|
|
43
|
+
return graphNamespacedName{}, ""
|
|
44
|
+
}
|
|
45
|
+
return graphNamespacedName{namespace: graphString(ref, "namespace"), name: graphString(ref, "name")}, graphString(ref, "uid")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func graphStorageBound(claim, volume *unstructured.Unstructured) bool {
|
|
49
|
+
if claim == nil || volume == nil || claim.GetDeletionTimestamp() != nil || volume.GetDeletionTimestamp() != nil {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
if graphStatus(claim) != string(corev1.ClaimBound) || graphStatus(volume) != string(corev1.VolumeBound) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
ref, uid := graphVolumeClaimRef(volume)
|
|
56
|
+
return ref.namespace != "" && ref.name != "" && uid != "" && uid == string(claim.GetUID()) &&
|
|
57
|
+
ref.namespace == claim.GetNamespace() && ref.name == claim.GetName() && graphClaimVolumeName(claim) == volume.GetName()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func (b *graphRules) storageRelations(o *unstructured.Unstructured, id string) {
|
|
61
|
+
if !graphBuiltinSpec(o) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
switch o.GetKind() {
|
|
65
|
+
case "Pod":
|
|
66
|
+
for _, raw := range graphSlice(graphPodSpec(o), "volumes") {
|
|
67
|
+
if !b.active() {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
volume, _ := raw.(map[string]interface{})
|
|
71
|
+
b.storageMountEdge(id, o.GetNamespace(), graphVolumeClaimName(volume))
|
|
72
|
+
}
|
|
73
|
+
return
|
|
74
|
+
case "PersistentVolumeClaim":
|
|
75
|
+
if o.GetNamespace() == "" {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
if volumeName := graphClaimVolumeName(o); volumeName != "" {
|
|
79
|
+
volume := b.objects[graphIdentity("v1", "PersistentVolume", "", volumeName)]
|
|
80
|
+
edge := cr.ResourceGraphEdge{SourceID: id, Relation: ResourceGraphRelationBindsTo, FieldPath: "spec.volumeName"}
|
|
81
|
+
if graphStorageBound(o, volume) {
|
|
82
|
+
edge.TargetID = b.node(b.ref(volume), graphStatus(volume))
|
|
83
|
+
edge.ResolutionState = "resolved"
|
|
84
|
+
edge.Note = "PVC and PV report Bound with matching references"
|
|
85
|
+
} else {
|
|
86
|
+
edge.TargetID = b.unresolvedStorageNode("PersistentVolume", "", volumeName, "")
|
|
87
|
+
edge.ResolutionState = "unresolved"
|
|
88
|
+
edge.Note = "PVC volume reference requires binding confirmation"
|
|
89
|
+
}
|
|
90
|
+
b.addEdge(edge)
|
|
91
|
+
}
|
|
92
|
+
case "PersistentVolume":
|
|
93
|
+
if o.GetNamespace() != "" {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
ref, uid := graphVolumeClaimRef(o)
|
|
97
|
+
if ref.namespace != "" && ref.name != "" {
|
|
98
|
+
claim := b.objects[graphIdentity("v1", "PersistentVolumeClaim", ref.namespace, ref.name)]
|
|
99
|
+
if !graphStorageBound(claim, o) {
|
|
100
|
+
declaration := b.ref(o)
|
|
101
|
+
b.addEdge(cr.ResourceGraphEdge{
|
|
102
|
+
SourceID: b.unresolvedStorageNode("PersistentVolumeClaim", ref.namespace, ref.name, uid),
|
|
103
|
+
TargetID: id, Relation: ResourceGraphRelationBindsTo, ResolutionState: "unresolved",
|
|
104
|
+
FieldPath: "spec.claimRef", Note: "PV claim reference requires binding confirmation", SourceRef: &declaration,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
default:
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
b.storageReferenceEdge(id, "storage.k8s.io/v1", "StorageClass", "", graphStorageClassName(o),
|
|
112
|
+
ResourceGraphRelationUsesStorageClass, "spec.storageClassName", "StorageClass reference declared in spec")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func (b *graphRules) storageMountEdge(source, namespace, claim string) {
|
|
116
|
+
if namespace != "" {
|
|
117
|
+
b.storageReferenceEdge(source, "v1", "PersistentVolumeClaim", namespace, claim,
|
|
118
|
+
ResourceGraphRelationMounts, "spec.volumes", "PVC reference declared in Pod spec")
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func (b *graphRules) unresolvedStorageNode(kind, namespace, name, uid string) string {
|
|
123
|
+
ref := cr.ResourceRef{ClusterID: b.cluster, APIVersion: "v1", Kind: kind, Namespace: namespace, Name: name, UID: uid, ScopeMode: topologyScope(namespace != "")}
|
|
124
|
+
id := graphNodeID(ref)
|
|
125
|
+
if observed := b.objects[graphIdentity(ref.APIVersion, kind, namespace, name)]; observed != nil && graphNodeID(b.ref(observed)) == id {
|
|
126
|
+
// A one-sided declaration can name an observed object without binding it.
|
|
127
|
+
id += ":unresolved"
|
|
128
|
+
}
|
|
129
|
+
return b.addNode(id, ref, "unresolved")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func (b *graphRules) storageReferenceEdge(source, api, kind, namespace, name, relation, fieldPath, note string) {
|
|
133
|
+
if name == "" {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
state := "unresolved"
|
|
137
|
+
if b.objects[graphIdentity(api, kind, namespace, name)] != nil {
|
|
138
|
+
state = "resolved"
|
|
139
|
+
}
|
|
140
|
+
b.addEdge(cr.ResourceGraphEdge{
|
|
141
|
+
SourceID: source, TargetID: b.target(api, kind, namespace, name, ""), Relation: relation,
|
|
142
|
+
ResolutionState: state, FieldPath: fieldPath, Note: note,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
|
|
7
|
+
corev1 "k8s.io/api/core/v1"
|
|
8
|
+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
9
|
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
10
|
+
kruntime "k8s.io/apimachinery/pkg/runtime"
|
|
11
|
+
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
12
|
+
"k8s.io/client-go/kubernetes"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func graphStorageProjection(obj kruntime.Object, projected *unstructured.Unstructured) error {
|
|
16
|
+
kind := projected.GetKind()
|
|
17
|
+
_, metadataOnly := obj.(*metav1.PartialObjectMetadata)
|
|
18
|
+
if gvk := obj.GetObjectKind().GroupVersionKind(); !metadataOnly && !gvk.Empty() && gvk != schema.FromAPIVersionAndKind(projected.GetAPIVersion(), kind) {
|
|
19
|
+
return errors.New("storage relationship observation GVK mismatch")
|
|
20
|
+
}
|
|
21
|
+
if projected.GetName() == "" || (kind == "PersistentVolumeClaim") != (projected.GetNamespace() != "") {
|
|
22
|
+
return errors.New("storage relationship observation scope mismatch")
|
|
23
|
+
}
|
|
24
|
+
switch item := obj.(type) {
|
|
25
|
+
case *corev1.PersistentVolumeClaim:
|
|
26
|
+
spec := map[string]interface{}{"volumeName": item.Spec.VolumeName}
|
|
27
|
+
if item.Spec.StorageClassName != nil {
|
|
28
|
+
spec["storageClassName"] = *item.Spec.StorageClassName
|
|
29
|
+
}
|
|
30
|
+
projected.Object["spec"] = spec
|
|
31
|
+
projected.Object["status"] = map[string]interface{}{"phase": string(item.Status.Phase)}
|
|
32
|
+
if item.DeletionTimestamp != nil {
|
|
33
|
+
projected.SetDeletionTimestamp(item.DeletionTimestamp)
|
|
34
|
+
}
|
|
35
|
+
case *corev1.PersistentVolume:
|
|
36
|
+
spec := map[string]interface{}{"storageClassName": item.Spec.StorageClassName}
|
|
37
|
+
if ref := item.Spec.ClaimRef; ref != nil {
|
|
38
|
+
spec["claimRef"] = map[string]interface{}{"apiVersion": ref.APIVersion, "kind": ref.Kind, "namespace": ref.Namespace, "name": ref.Name, "uid": string(ref.UID)}
|
|
39
|
+
}
|
|
40
|
+
projected.Object["spec"] = spec
|
|
41
|
+
projected.Object["status"] = map[string]interface{}{"phase": string(item.Status.Phase)}
|
|
42
|
+
if item.DeletionTimestamp != nil {
|
|
43
|
+
projected.SetDeletionTimestamp(item.DeletionTimestamp)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func collectGraphStorage(ctx context.Context, client kubernetes.Interface, sources map[string]graphSourceDefinition, root *unstructured.Unstructured, objects *[]*unstructured.Unstructured, warnings *[]string) error {
|
|
50
|
+
var claim, volume *unstructured.Unstructured
|
|
51
|
+
var err error
|
|
52
|
+
switch root.GetKind() {
|
|
53
|
+
case "PersistentVolumeClaim":
|
|
54
|
+
claim = root
|
|
55
|
+
volume, err = collectGraphStorageTarget(ctx, "PersistentVolume", "", graphClaimVolumeName(claim), client.CoreV1().PersistentVolumes().Get, objects, warnings)
|
|
56
|
+
case "PersistentVolume":
|
|
57
|
+
volume = root
|
|
58
|
+
ref, _ := graphVolumeClaimRef(volume)
|
|
59
|
+
if ref.namespace != "" {
|
|
60
|
+
claim, err = collectGraphStorageTarget(ctx, "PersistentVolumeClaim", ref.namespace, ref.name, client.CoreV1().PersistentVolumeClaims(ref.namespace).Get, objects, warnings)
|
|
61
|
+
}
|
|
62
|
+
case "StorageClass":
|
|
63
|
+
return collectGraphStorageClassSources(ctx, client, sources, root.GetName(), objects, warnings)
|
|
64
|
+
default:
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
if err != nil {
|
|
68
|
+
return err
|
|
69
|
+
}
|
|
70
|
+
class := graphStorageClassName(root)
|
|
71
|
+
getClass := client.StorageV1().StorageClasses().Get
|
|
72
|
+
if _, err = collectGraphStorageTarget(ctx, "StorageClass", "", class, getClass, objects, warnings); err != nil {
|
|
73
|
+
return err
|
|
74
|
+
}
|
|
75
|
+
bound := graphStorageBound(claim, volume)
|
|
76
|
+
if bound && root == claim && graphStorageClassName(volume) != class {
|
|
77
|
+
if _, err = collectGraphStorageTarget(ctx, "StorageClass", "", graphStorageClassName(volume), getClass, objects, warnings); err != nil {
|
|
78
|
+
return err
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if root != claim && !bound {
|
|
82
|
+
return ctx.Err()
|
|
83
|
+
}
|
|
84
|
+
ref := graphNamespacedName{namespace: claim.GetNamespace(), name: claim.GetName()}
|
|
85
|
+
return collectGraphStoragePods(ctx, client, ref.namespace, map[graphNamespacedName]bool{ref: true}, objects, warnings)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func collectGraphStorageTarget[T kruntime.Object](ctx context.Context, kind, namespace, name string, get func(context.Context, string, metav1.GetOptions) (T, error), objects *[]*unstructured.Unstructured, warnings *[]string) (*unstructured.Unstructured, error) {
|
|
89
|
+
if err := ctx.Err(); err != nil || name == "" {
|
|
90
|
+
return nil, err
|
|
91
|
+
}
|
|
92
|
+
item, err := get(ctx, name, metav1.GetOptions{})
|
|
93
|
+
if err != nil {
|
|
94
|
+
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
95
|
+
return nil, err
|
|
96
|
+
}
|
|
97
|
+
*warnings = append(*warnings, kind+" reference source "+graphSourceErrorClass(err))
|
|
98
|
+
return nil, ctx.Err()
|
|
99
|
+
}
|
|
100
|
+
projected, err := graphObjectProjection(item, graphBuiltinAPIs[kind], kind)
|
|
101
|
+
if err != nil || projected.GetName() != name || projected.GetNamespace() != namespace {
|
|
102
|
+
*warnings = append(*warnings, kind+" reference source projection unavailable")
|
|
103
|
+
return nil, ctx.Err()
|
|
104
|
+
}
|
|
105
|
+
*objects = append(*objects, projected)
|
|
106
|
+
return projected, ctx.Err()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
func collectGraphStorageClassSources(ctx context.Context, client kubernetes.Interface, sources map[string]graphSourceDefinition, name string, objects *[]*unstructured.Unstructured, warnings *[]string) error {
|
|
110
|
+
volumesStart := len(*objects)
|
|
111
|
+
if err := sources["PersistentVolume"].collect(ctx, objects, warnings); err != nil {
|
|
112
|
+
return err
|
|
113
|
+
}
|
|
114
|
+
classVolumes := map[string]*unstructured.Unstructured{}
|
|
115
|
+
for _, volume := range (*objects)[volumesStart:] {
|
|
116
|
+
if graphStorageClassName(volume) == name && classVolumes[volume.GetName()] == nil {
|
|
117
|
+
classVolumes[volume.GetName()] = volume
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
claimsStart := len(*objects)
|
|
121
|
+
if err := sources["PersistentVolumeClaim"].collect(ctx, objects, warnings); err != nil {
|
|
122
|
+
return err
|
|
123
|
+
}
|
|
124
|
+
claims := map[graphNamespacedName]bool{}
|
|
125
|
+
for _, claim := range (*objects)[claimsStart:] {
|
|
126
|
+
if graphStorageClassName(claim) == name || graphStorageBound(claim, classVolumes[graphClaimVolumeName(claim)]) {
|
|
127
|
+
claims[graphNamespacedName{namespace: claim.GetNamespace(), name: claim.GetName()}] = true
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if len(claims) == 0 {
|
|
131
|
+
return ctx.Err()
|
|
132
|
+
}
|
|
133
|
+
return collectGraphStoragePods(ctx, client, "", claims, objects, warnings)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func collectGraphStoragePods(ctx context.Context, client kubernetes.Interface, namespace string, claims map[graphNamespacedName]bool, objects *[]*unstructured.Unstructured, warnings *[]string) error {
|
|
137
|
+
project := func(obj kruntime.Object) (*unstructured.Unstructured, error) {
|
|
138
|
+
pod, ok := obj.(*corev1.Pod)
|
|
139
|
+
if !ok || pod.Namespace == "" || (namespace != "" && pod.Namespace != namespace) {
|
|
140
|
+
return nil, errors.New("storage relationship Pod scope mismatch")
|
|
141
|
+
}
|
|
142
|
+
volumes := []interface{}{}
|
|
143
|
+
for _, volume := range pod.Spec.Volumes {
|
|
144
|
+
if err := ctx.Err(); err != nil {
|
|
145
|
+
return nil, err
|
|
146
|
+
}
|
|
147
|
+
if claim := volume.PersistentVolumeClaim; claim != nil && claims[graphNamespacedName{namespace: pod.Namespace, name: claim.ClaimName}] {
|
|
148
|
+
volumes = append(volumes, map[string]interface{}{"persistentVolumeClaim": map[string]interface{}{"claimName": claim.ClaimName}})
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if len(volumes) == 0 {
|
|
152
|
+
return nil, nil
|
|
153
|
+
}
|
|
154
|
+
projected, err := graphObjectMetadataProjection(pod, "v1", "Pod", false)
|
|
155
|
+
if err != nil {
|
|
156
|
+
return nil, err
|
|
157
|
+
}
|
|
158
|
+
projected.Object["spec"] = map[string]interface{}{"volumes": volumes}
|
|
159
|
+
projected.Object["status"] = map[string]interface{}{"phase": string(pod.Status.Phase)}
|
|
160
|
+
return projected, nil
|
|
161
|
+
}
|
|
162
|
+
return collectGraphSource(ctx, client.CoreV1().Pods(namespace).List, "Pod", project, objects, warnings)
|
|
163
|
+
}
|