@opensoha/contracts 0.1.26 → 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 +14 -3
- package/README.md +8 -9
- package/auth/catalog_generated.go +2 -2
- package/auth/permission-catalog.json +481 -1
- 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/endpointcontrol/decode.go +64 -0
- package/endpointcontrol/models.go +138 -0
- package/endpointcontrol/models_test.go +51 -0
- package/endpointcontrol/protocol.schema.json +397 -0
- package/fixtures/json-schema/endpoint-controls/invalid/controls.json +16 -0
- package/fixtures/json-schema/endpoint-controls/invalid/missing-channel-decision.json +15 -0
- package/fixtures/json-schema/endpoint-controls/invalid/receipt-content.json +36 -0
- package/fixtures/json-schema/endpoint-controls/valid/controls.json +16 -0
- package/fixtures/json-schema/endpoint-controls/valid/receipt.json +35 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-activate-bad-fingerprint.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-gateway-request.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-private-key-request.json +15 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/eap-profile-without-eap-credential.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/endpoint-dns-observation.json +25 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/gateway-presence.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/mihomo-protection-zero-version.json +45 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/presence-raw-key.json +23 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/presence-zero-sequence.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/invalid/workload-wide-vpn.json +17 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/eap-certificate-activate.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/eap-certificate-request.json +14 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/eap-profile-enrollment.json +24 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/endpoint-presence.json +22 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/ipv6-configuration.json +102 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/mihomo-protected-policy.json +45 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/site-dns-ipv6.json +25 -0
- package/fixtures/json-schema/network-runtime-protocol/valid/workload-direct-ztna.json +20 -0
- package/fixtures/openapi/DLPShadowDecision/invalid/step-up-type.json +14 -0
- package/fixtures/openapi/DLPShadowDecision/valid/archive-v2.json +14 -0
- package/fixtures/openapi/DLPShadowDecision/valid/step-up.json +14 -0
- package/fixtures/openapi/DLPShadowInput/invalid/self-reported-result.json +7 -0
- package/fixtures/openapi/DLPShadowInput/invalid/too-many-samples.json +14 -0
- package/fixtures/openapi/DLPShadowInput/valid/bounded.json +7 -0
- package/fixtures/openapi/DockerVolumeDLPAppealInput/invalid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPAppealInput/valid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPAppealReviewInput/invalid/p4-response.json +5 -0
- package/fixtures/openapi/DockerVolumeDLPAppealReviewInput/valid/p4-response.json +5 -0
- package/fixtures/openapi/DockerVolumeDLPAuthorizationInput/invalid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPAuthorizationInput/valid/p4-response.json +3 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/archive-unsupported-channel.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/p4.json +6 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/step-up-audit.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/unavailable-engine.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPContent/invalid/unknown-data-level.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPContent/valid/classified-rule.json +16 -0
- package/fixtures/openapi/DockerVolumeDLPContent/valid/p4.json +8 -0
- package/fixtures/openapi/DockerVolumeDLPContent/valid/step-up.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/invalid/p4.json +9 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/invalid/step-up-type.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/valid/archive-v2.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/valid/p4.json +11 -0
- package/fixtures/openapi/DockerVolumeDLPDecision/valid/step-up.json +12 -0
- package/fixtures/openapi/DockerVolumeDLPSaveInput/invalid/p4.json +11 -0
- package/fixtures/openapi/DockerVolumeDLPSaveInput/valid/p4.json +11 -0
- package/fixtures/openapi/DockerVolumeFileReadInput/invalid/p4-response.json +5 -0
- package/fixtures/openapi/DockerVolumeFileReadInput/valid/p4-response.json +4 -0
- package/fixtures/openapi/EndpointDeviceInput/invalid/reported-management.json +5 -0
- package/fixtures/openapi/EndpointDeviceInput/valid/management-state.json +5 -0
- package/fixtures/openapi/EndpointDeviceRegistrationInput/invalid/self-promote-management.json +5 -0
- 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/fixtures/openapi/ManagedContentPasteInput/invalid/boundary.json +5 -0
- package/fixtures/openapi/ManagedContentPasteInput/valid/boundary.json +6 -0
- package/fixtures/openapi/ManagedContentPolicy/invalid/self-reported-allow.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/invalid/step-up-audit.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/invalid/unknown-extractor.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/valid/archive-v2.json +8 -0
- package/fixtures/openapi/ManagedContentPolicy/valid/network-and-level.json +10 -0
- package/fixtures/openapi/ManagedContentPolicy/valid/step-up.json +8 -0
- package/fixtures/openapi/ManagedContentReviewInput/invalid/boundary.json +5 -0
- package/fixtures/openapi/ManagedContentReviewInput/valid/boundary.json +5 -0
- package/fixtures/openapi/ManagedContentSaveInput/invalid/boundary.json +14 -0
- package/fixtures/openapi/ManagedContentSaveInput/valid/boundary.json +16 -0
- package/fixtures/openapi/ManagedContentUploadInput/invalid/boundary.json +7 -0
- package/fixtures/openapi/ManagedContentUploadInput/valid/boundary.json +6 -0
- package/fixtures/openapi/NetworkAccessPolicyInput/invalid/unknown-management-state.json +35 -0
- package/fixtures/openapi/NetworkAccessPolicyInput/valid/managed-device.json +35 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/invalid/deviceId-characters.json +4 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/invalid/nasBindingId-characters.json +4 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/invalid/subject-override.json +1 -0
- package/fixtures/openapi/NetworkAdmissionPreviewRequest/valid/device-nas.json +1 -0
- package/fixtures/openapi/NetworkAdmissionPreviewResult/invalid/missing-attributeVersion.json +13 -0
- package/fixtures/openapi/NetworkAdmissionPreviewResult/invalid/missing-evaluatedAt.json +13 -0
- package/fixtures/openapi/NetworkAdmissionPreviewResult/valid/versioned.json +14 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/invalid/missing-runtime-ranges.json +1 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/invalid/unknown-source-type.json +9 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/valid/empty-runtime-ranges.json +3 -0
- package/fixtures/openapi/NetworkConflictAnalysisRequest/{invalid → valid}/ipv6-range.json +1 -1
- package/fixtures/openapi/NetworkEAPServerTrust/invalid/no-server-validation.json +5 -0
- package/fixtures/openapi/NetworkEAPServerTrust/valid/named-server.json +9 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/invalid/all-sites-implicit.json +45 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/invalid/unbounded-stages.json +54 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/invalid/zero-heartbeat.json +47 -0
- package/fixtures/openapi/NetworkEndpointResponsePolicyInput/valid/staged-protection.json +47 -0
- package/fixtures/openapi/NetworkEndpointResponseRestoreInput/invalid/missing-reason.json +3 -0
- package/fixtures/openapi/NetworkEndpointResponseRestoreInput/valid/expected-current.json +4 -0
- package/fixtures/openapi/NetworkPolicyPreviewResult/invalid/attribute-version.json +1 -0
- package/fixtures/openapi/NetworkPolicyPreviewResult/valid/admission-version.json +1 -0
- package/fixtures/openapi/NetworkPolicySubjects/invalid/duplicate-role.json +1 -0
- package/fixtures/openapi/NetworkPolicySubjects/invalid/role-characters.json +5 -0
- package/fixtures/openapi/NetworkPolicySubjects/valid/role-bound.json +1 -0
- package/fixtures/openapi/NetworkProxyConfigurationInput/invalid/missing-content.json +1 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/invalid/bad-binding-id.json +9 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/invalid/empty-content.json +1 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/valid/binding-only.json +11 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/valid/clear-binding-disabled.json +5 -0
- package/fixtures/openapi/NetworkProxyConfigurationPatchInput/valid/enabled.json +1 -0
- package/fixtures/openapi/NetworkSiteInput/invalid/unknown-governance.json +6 -0
- package/fixtures/openapi/NetworkSiteInput/valid/restricted-unmanaged.json +6 -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/proxy_configuration_test.go +38 -0
- package/gen/go/sohaapi/types.go +3222 -260
- package/gen/ts/sohaapi/index.ts +15506 -10104
- 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/network/README.md +34 -0
- package/network/network-radius-accounting.schema.json +4 -1
- package/network/network-runtime-protocol.schema.json +2166 -317
- package/network/proxy-runtime.schema.json +482 -67
- package/openapi/network_control_test.go +64 -0
- package/openapi/soha-api.json +50412 -41310
- package/openapi/soha-api.yaml +16859 -10699
- package/openapi/spec_generated.go +3 -3
- package/package.json +6 -3
- package/proxytransport/handler.go +271 -0
- package/proxytransport/probe.go +30 -0
- package/proxytransport/scope.go +89 -0
- package/proxytransport/transport_test.go +60 -0
- 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,325 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"fmt"
|
|
7
|
+
"sort"
|
|
8
|
+
"time"
|
|
9
|
+
|
|
10
|
+
cr "github.com/opensoha/soha-contracts/resource"
|
|
11
|
+
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
12
|
+
"k8s.io/apimachinery/pkg/api/meta"
|
|
13
|
+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
14
|
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
15
|
+
kruntime "k8s.io/apimachinery/pkg/runtime"
|
|
16
|
+
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
17
|
+
"k8s.io/client-go/kubernetes"
|
|
18
|
+
"k8s.io/client-go/metadata"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const ResourceGraphSourceLimit int64 = 500
|
|
22
|
+
const ResourceGraphNodeLimit = 500
|
|
23
|
+
const ResourceGraphEdgeLimit = 1000
|
|
24
|
+
const ResourceGraphDepth = 3
|
|
25
|
+
const ResourceGraphTimeout = 8 * time.Second
|
|
26
|
+
|
|
27
|
+
var graphSecretResource = schema.GroupVersionResource{Version: "v1", Resource: "secrets"}
|
|
28
|
+
|
|
29
|
+
// BuildKubernetesResourceGraph is the common Direct/Agent read-only entry point.
|
|
30
|
+
// Root failures remain wrapped Kubernetes errors. Partial source failures are
|
|
31
|
+
// explicit warnings; cancellation and the total collection deadline are errors.
|
|
32
|
+
func BuildKubernetesResourceGraph(ctx context.Context, client kubernetes.Interface, root cr.ResourceRef) (cr.ResourceGraph, error) {
|
|
33
|
+
return BuildKubernetesResourceGraphWithMetadata(ctx, client, nil, root)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// BuildKubernetesResourceGraphWithMetadata optionally confirms Secret identities
|
|
37
|
+
// using metadata-only HTTP negotiation. Callers must supply a metadata client
|
|
38
|
+
// whose transport excludes the client-go full-JSON fallback from Accept headers.
|
|
39
|
+
// It never calls the typed Secret API.
|
|
40
|
+
func BuildKubernetesResourceGraphWithMetadata(ctx context.Context, client kubernetes.Interface, metadataClient metadata.Interface, root cr.ResourceRef) (cr.ResourceGraph, error) {
|
|
41
|
+
root, err := NormalizeResourceGraphRoot(root)
|
|
42
|
+
if err != nil {
|
|
43
|
+
return cr.ResourceGraph{}, err
|
|
44
|
+
}
|
|
45
|
+
ctx, cancel := context.WithTimeout(ctx, ResourceGraphTimeout)
|
|
46
|
+
defer cancel()
|
|
47
|
+
sources := graphSources(client, root.Namespace)
|
|
48
|
+
source, ok := sources[root.Kind]
|
|
49
|
+
if !ok && (root.Kind != "Secret" || metadataClient == nil) {
|
|
50
|
+
return cr.ResourceGraph{}, fmt.Errorf("%w: %s; Secret roots require metadata-only collection", ErrUnsupportedResourceGraphRoot, root.Kind)
|
|
51
|
+
}
|
|
52
|
+
if root.APIVersion != "" && root.APIVersion != graphBuiltinAPIs[root.Kind] {
|
|
53
|
+
return cr.ResourceGraph{}, invalidGraphRoot("resource graph root apiVersion mismatch")
|
|
54
|
+
}
|
|
55
|
+
var object kruntime.Object
|
|
56
|
+
if root.Kind == "Secret" {
|
|
57
|
+
object, err = metadataClient.Resource(graphSecretResource).Namespace(root.Namespace).Get(ctx, root.Name, metav1.GetOptions{})
|
|
58
|
+
} else {
|
|
59
|
+
object, err = source.get(ctx, root.Name, metav1.GetOptions{})
|
|
60
|
+
}
|
|
61
|
+
if err != nil {
|
|
62
|
+
return cr.ResourceGraph{}, fmt.Errorf("get resource graph root %s/%s: %w", root.Kind, root.Name, err)
|
|
63
|
+
}
|
|
64
|
+
if err = ctx.Err(); err != nil {
|
|
65
|
+
return cr.ResourceGraph{}, err
|
|
66
|
+
}
|
|
67
|
+
projected, err := graphObjectProjection(object, graphBuiltinAPIs[root.Kind], root.Kind)
|
|
68
|
+
if err != nil {
|
|
69
|
+
if graphStorageRootKind(root.Kind) {
|
|
70
|
+
return cr.ResourceGraph{}, invalidGraphRoot("storage resource graph root projection unavailable")
|
|
71
|
+
}
|
|
72
|
+
return cr.ResourceGraph{}, err
|
|
73
|
+
}
|
|
74
|
+
if root.UID != "" && root.UID != string(projected.GetUID()) {
|
|
75
|
+
return cr.ResourceGraph{}, invalidGraphRoot("resource graph root UID changed")
|
|
76
|
+
}
|
|
77
|
+
if (graphStorageRootKind(root.Kind) || graphRBACRootKind(root.Kind)) && (projected.GetName() != root.Name || projected.GetNamespace() != root.Namespace) {
|
|
78
|
+
return cr.ResourceGraph{}, invalidGraphRoot("resource graph root identity changed")
|
|
79
|
+
}
|
|
80
|
+
objects := []*unstructured.Unstructured{projected}
|
|
81
|
+
warnings := []string{}
|
|
82
|
+
kinds := make([]string, 0, len(sources))
|
|
83
|
+
for kind := range sources {
|
|
84
|
+
if graphCollectKind(root.Kind, kind) {
|
|
85
|
+
kinds = append(kinds, kind)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
sort.Strings(kinds)
|
|
89
|
+
for _, kind := range kinds {
|
|
90
|
+
if err = sources[kind].collect(ctx, &objects, &warnings); err != nil {
|
|
91
|
+
return cr.ResourceGraph{}, err
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if err = collectGraphCrossNamespaceAccounts(ctx, client, projected, &objects, &warnings); err != nil {
|
|
95
|
+
return cr.ResourceGraph{}, err
|
|
96
|
+
}
|
|
97
|
+
if err = collectGraphStorage(ctx, client, sources, projected, &objects, &warnings); err != nil {
|
|
98
|
+
return cr.ResourceGraph{}, err
|
|
99
|
+
}
|
|
100
|
+
metadataReads := 0
|
|
101
|
+
if err = collectGraphConfigurationTargets(ctx, metadataClient, root, projected, &metadataReads, &objects, &warnings); err != nil {
|
|
102
|
+
return cr.ResourceGraph{}, err
|
|
103
|
+
}
|
|
104
|
+
graph, err := buildResourceGraphWithContext(ctx, root, objects)
|
|
105
|
+
if err != nil {
|
|
106
|
+
return cr.ResourceGraph{}, err
|
|
107
|
+
}
|
|
108
|
+
if metadataClient != nil {
|
|
109
|
+
changed, metadataErr := collectGraphSecretMetadata(ctx, metadataClient, graph, &metadataReads, &objects, &warnings)
|
|
110
|
+
if metadataErr != nil {
|
|
111
|
+
return cr.ResourceGraph{}, metadataErr
|
|
112
|
+
}
|
|
113
|
+
if changed {
|
|
114
|
+
graph, err = buildResourceGraphWithContext(ctx, root, objects)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if canceled := ctx.Err(); canceled != nil {
|
|
118
|
+
return cr.ResourceGraph{}, canceled
|
|
119
|
+
}
|
|
120
|
+
graph.Warnings = append(graph.Warnings, warnings...)
|
|
121
|
+
for _, node := range graph.Nodes {
|
|
122
|
+
if node.Resource.APIVersion == "v1" && node.Resource.Kind == "Secret" && node.Status == "unresolved" {
|
|
123
|
+
graph.Warnings = append(graph.Warnings, "Secret targets are declared references only; metadata was not read and existence is unverified")
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return graph, err
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type graphSourceDefinition struct {
|
|
131
|
+
get func(context.Context, string, metav1.GetOptions) (kruntime.Object, error)
|
|
132
|
+
collect func(context.Context, *[]*unstructured.Unstructured, *[]string) error
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func graphSource[O kruntime.Object, L kruntime.Object](kind string, get func(context.Context, string, metav1.GetOptions) (O, error), list func(context.Context, metav1.ListOptions) (L, error)) graphSourceDefinition {
|
|
136
|
+
return graphSourceDefinition{
|
|
137
|
+
get: func(ctx context.Context, name string, options metav1.GetOptions) (kruntime.Object, error) {
|
|
138
|
+
return get(ctx, name, options)
|
|
139
|
+
},
|
|
140
|
+
collect: func(ctx context.Context, objects *[]*unstructured.Unstructured, warnings *[]string) error {
|
|
141
|
+
return collectGraphSource(ctx, list, kind, func(obj kruntime.Object) (*unstructured.Unstructured, error) {
|
|
142
|
+
return graphObjectProjection(obj, graphBuiltinAPIs[kind], kind)
|
|
143
|
+
}, objects, warnings)
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
func graphSources(client kubernetes.Interface, namespace string) map[string]graphSourceDefinition {
|
|
148
|
+
return map[string]graphSourceDefinition{
|
|
149
|
+
"Pod": graphSource("Pod", client.CoreV1().Pods(namespace).Get, client.CoreV1().Pods(namespace).List),
|
|
150
|
+
"Deployment": graphSource("Deployment", client.AppsV1().Deployments(namespace).Get, client.AppsV1().Deployments(namespace).List),
|
|
151
|
+
"StatefulSet": graphSource("StatefulSet", client.AppsV1().StatefulSets(namespace).Get, client.AppsV1().StatefulSets(namespace).List),
|
|
152
|
+
"DaemonSet": graphSource("DaemonSet", client.AppsV1().DaemonSets(namespace).Get, client.AppsV1().DaemonSets(namespace).List),
|
|
153
|
+
"Job": graphSource("Job", client.BatchV1().Jobs(namespace).Get, client.BatchV1().Jobs(namespace).List),
|
|
154
|
+
"CronJob": graphSource("CronJob", client.BatchV1().CronJobs(namespace).Get, client.BatchV1().CronJobs(namespace).List),
|
|
155
|
+
"ReplicaSet": graphSource("ReplicaSet", client.AppsV1().ReplicaSets(namespace).Get, client.AppsV1().ReplicaSets(namespace).List),
|
|
156
|
+
"ReplicationController": graphSource("ReplicationController", client.CoreV1().ReplicationControllers(namespace).Get, client.CoreV1().ReplicationControllers(namespace).List),
|
|
157
|
+
"Service": graphSource("Service", client.CoreV1().Services(namespace).Get, client.CoreV1().Services(namespace).List),
|
|
158
|
+
"Ingress": graphSource("Ingress", client.NetworkingV1().Ingresses(namespace).Get, client.NetworkingV1().Ingresses(namespace).List),
|
|
159
|
+
"HorizontalPodAutoscaler": graphSource("HorizontalPodAutoscaler", client.AutoscalingV2().HorizontalPodAutoscalers(namespace).Get, client.AutoscalingV2().HorizontalPodAutoscalers(namespace).List),
|
|
160
|
+
"PodDisruptionBudget": graphSource("PodDisruptionBudget", client.PolicyV1().PodDisruptionBudgets(namespace).Get, client.PolicyV1().PodDisruptionBudgets(namespace).List),
|
|
161
|
+
"ConfigMap": graphSource("ConfigMap", client.CoreV1().ConfigMaps(namespace).Get, client.CoreV1().ConfigMaps(namespace).List),
|
|
162
|
+
"ResourceQuota": graphSource("ResourceQuota", client.CoreV1().ResourceQuotas(namespace).Get, client.CoreV1().ResourceQuotas(namespace).List),
|
|
163
|
+
"LimitRange": graphSource("LimitRange", client.CoreV1().LimitRanges(namespace).Get, client.CoreV1().LimitRanges(namespace).List),
|
|
164
|
+
"PriorityClass": graphSource("PriorityClass", client.SchedulingV1().PriorityClasses().Get, client.SchedulingV1().PriorityClasses().List),
|
|
165
|
+
"RuntimeClass": graphSource("RuntimeClass", client.NodeV1().RuntimeClasses().Get, client.NodeV1().RuntimeClasses().List),
|
|
166
|
+
"Lease": graphSource("Lease", client.CoordinationV1().Leases(namespace).Get, client.CoordinationV1().Leases(namespace).List),
|
|
167
|
+
"MutatingWebhookConfiguration": graphSource("MutatingWebhookConfiguration", client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get, client.AdmissionregistrationV1().MutatingWebhookConfigurations().List),
|
|
168
|
+
"ValidatingWebhookConfiguration": graphSource("ValidatingWebhookConfiguration", client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get, client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List),
|
|
169
|
+
"PersistentVolumeClaim": graphSource("PersistentVolumeClaim", client.CoreV1().PersistentVolumeClaims(namespace).Get, client.CoreV1().PersistentVolumeClaims(namespace).List),
|
|
170
|
+
"PersistentVolume": graphSource("PersistentVolume", client.CoreV1().PersistentVolumes().Get, client.CoreV1().PersistentVolumes().List),
|
|
171
|
+
"StorageClass": graphSource("StorageClass", client.StorageV1().StorageClasses().Get, client.StorageV1().StorageClasses().List),
|
|
172
|
+
"ServiceAccount": graphSource("ServiceAccount", client.CoreV1().ServiceAccounts(namespace).Get, client.CoreV1().ServiceAccounts(namespace).List),
|
|
173
|
+
"Role": graphSource("Role", client.RbacV1().Roles(namespace).Get, client.RbacV1().Roles(namespace).List),
|
|
174
|
+
"RoleBinding": graphSource("RoleBinding", client.RbacV1().RoleBindings(namespace).Get, client.RbacV1().RoleBindings(namespace).List),
|
|
175
|
+
"ClusterRole": graphSource("ClusterRole", client.RbacV1().ClusterRoles().Get, client.RbacV1().ClusterRoles().List),
|
|
176
|
+
"ClusterRoleBinding": graphSource("ClusterRoleBinding", client.RbacV1().ClusterRoleBindings().Get, client.RbacV1().ClusterRoleBindings().List),
|
|
177
|
+
"Node": graphSource("Node", client.CoreV1().Nodes().Get, client.CoreV1().Nodes().List),
|
|
178
|
+
"Event": graphSource("Event", client.CoreV1().Events(namespace).Get, client.CoreV1().Events(namespace).List),
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Projection immediately discards payloads outside relationship inputs; Secret
|
|
183
|
+
// data, ConfigMap data and annotations are never inspected or retained.
|
|
184
|
+
func graphObjectProjection(obj kruntime.Object, api, kind string) (*unstructured.Unstructured, error) {
|
|
185
|
+
if _, metadataOnly := obj.(*metav1.PartialObjectMetadata); graphRBACRootKind(kind) && !metadataOnly {
|
|
186
|
+
if gvk := obj.GetObjectKind().GroupVersionKind(); !gvk.Empty() && (gvk.GroupVersion().String() != api || gvk.Kind != kind) {
|
|
187
|
+
return nil, invalidGraphRoot("RBAC resource graph object apiVersion or kind mismatch")
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
storage := graphStorageRootKind(kind)
|
|
191
|
+
u, err := graphObjectMetadataProjection(obj, api, kind, !storage || kind == "PersistentVolumeClaim")
|
|
192
|
+
if err != nil {
|
|
193
|
+
return nil, err
|
|
194
|
+
}
|
|
195
|
+
if storage {
|
|
196
|
+
return u, graphStorageProjection(obj, u)
|
|
197
|
+
}
|
|
198
|
+
if graphRBACRootKind(kind) {
|
|
199
|
+
return u, graphRBACProjection(obj, u)
|
|
200
|
+
}
|
|
201
|
+
if kind == "Secret" || kind == "ConfigMap" || kind == "Node" || kind == "Namespace" {
|
|
202
|
+
return u, nil
|
|
203
|
+
}
|
|
204
|
+
full, err := kruntime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
|
205
|
+
if err != nil {
|
|
206
|
+
return nil, err
|
|
207
|
+
}
|
|
208
|
+
if spec, ok := full["spec"]; ok {
|
|
209
|
+
u.Object["spec"] = spec
|
|
210
|
+
}
|
|
211
|
+
if status, ok := full["status"]; ok {
|
|
212
|
+
u.Object["status"] = status
|
|
213
|
+
}
|
|
214
|
+
if kind == "Event" {
|
|
215
|
+
for _, key := range []string{"involvedObject", "reason", "message", "type", "lastTimestamp", "eventTime"} {
|
|
216
|
+
u.Object[key] = full[key]
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if kind == "MutatingWebhookConfiguration" || kind == "ValidatingWebhookConfiguration" {
|
|
220
|
+
// Admission rules, CA bundles and expressions are not relationship inputs.
|
|
221
|
+
u.Object["webhooks"] = graphWebhookProjection(full)
|
|
222
|
+
}
|
|
223
|
+
return u, nil
|
|
224
|
+
}
|
|
225
|
+
func graphObjectMetadataProjection(obj kruntime.Object, api, kind string, includeRelationships bool) (*unstructured.Unstructured, error) {
|
|
226
|
+
accessor, err := metav1Object(obj)
|
|
227
|
+
if err != nil {
|
|
228
|
+
return nil, err
|
|
229
|
+
}
|
|
230
|
+
metadata := map[string]interface{}{"name": accessor.GetName(), "namespace": accessor.GetNamespace(), "uid": string(accessor.GetUID()), "resourceVersion": accessor.GetResourceVersion()}
|
|
231
|
+
if created := accessor.GetCreationTimestamp(); kind == "Event" && !created.IsZero() {
|
|
232
|
+
metadata["creationTimestamp"] = created.UTC().Format(time.RFC3339Nano)
|
|
233
|
+
}
|
|
234
|
+
if includeRelationships {
|
|
235
|
+
labels := map[string]interface{}{}
|
|
236
|
+
for k, v := range accessor.GetLabels() {
|
|
237
|
+
labels[k] = v
|
|
238
|
+
}
|
|
239
|
+
metadata["labels"] = labels
|
|
240
|
+
owners := []interface{}{}
|
|
241
|
+
for _, owner := range accessor.GetOwnerReferences() {
|
|
242
|
+
projected, err := kruntime.DefaultUnstructuredConverter.ToUnstructured(&owner)
|
|
243
|
+
if err != nil {
|
|
244
|
+
return nil, err
|
|
245
|
+
}
|
|
246
|
+
owners = append(owners, projected)
|
|
247
|
+
}
|
|
248
|
+
metadata["ownerReferences"] = owners
|
|
249
|
+
}
|
|
250
|
+
return &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": api, "kind": kind, "metadata": metadata}}, nil
|
|
251
|
+
}
|
|
252
|
+
func metav1Object(obj kruntime.Object) (metav1.Object, error) {
|
|
253
|
+
o, ok := obj.(metav1.Object)
|
|
254
|
+
if !ok {
|
|
255
|
+
return nil, fmt.Errorf("resource lacks metadata")
|
|
256
|
+
}
|
|
257
|
+
return o, nil
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Only one bounded page is read. A continuation token is an explicit partial
|
|
261
|
+
// observation, never permission to issue an unbounded namespace scan.
|
|
262
|
+
func collectGraphSource[T kruntime.Object](ctx context.Context, list func(context.Context, metav1.ListOptions) (T, error), kind string, project func(kruntime.Object) (*unstructured.Unstructured, error), objects *[]*unstructured.Unstructured, warnings *[]string) error {
|
|
263
|
+
if err := ctx.Err(); err != nil {
|
|
264
|
+
return err
|
|
265
|
+
}
|
|
266
|
+
result, err := list(ctx, metav1.ListOptions{Limit: ResourceGraphSourceLimit})
|
|
267
|
+
if err != nil {
|
|
268
|
+
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
269
|
+
return err
|
|
270
|
+
}
|
|
271
|
+
if canceled := ctx.Err(); canceled != nil {
|
|
272
|
+
return canceled
|
|
273
|
+
}
|
|
274
|
+
*warnings = append(*warnings, kind+" source "+graphSourceErrorClass(err))
|
|
275
|
+
return nil
|
|
276
|
+
}
|
|
277
|
+
if err := ctx.Err(); err != nil {
|
|
278
|
+
return err
|
|
279
|
+
}
|
|
280
|
+
items, err := meta.ExtractList(result)
|
|
281
|
+
if err != nil {
|
|
282
|
+
*warnings = append(*warnings, kind+" source projection unavailable")
|
|
283
|
+
return nil
|
|
284
|
+
}
|
|
285
|
+
accessor, err := meta.ListAccessor(result)
|
|
286
|
+
if err != nil {
|
|
287
|
+
return err
|
|
288
|
+
}
|
|
289
|
+
count := len(items)
|
|
290
|
+
if count > int(ResourceGraphSourceLimit) {
|
|
291
|
+
count = int(ResourceGraphSourceLimit)
|
|
292
|
+
}
|
|
293
|
+
for _, item := range items[:count] {
|
|
294
|
+
if err := ctx.Err(); err != nil {
|
|
295
|
+
return err
|
|
296
|
+
}
|
|
297
|
+
projected, err := project(item)
|
|
298
|
+
if err != nil {
|
|
299
|
+
*warnings = append(*warnings, kind+" source projection unavailable")
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
if projected != nil {
|
|
303
|
+
*objects = append(*objects, projected)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if accessor.GetContinue() != "" || len(items) > int(ResourceGraphSourceLimit) || graphHasRemaining(accessor) {
|
|
307
|
+
*warnings = append(*warnings, kind+" source truncated at 500 objects")
|
|
308
|
+
}
|
|
309
|
+
return nil
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
func graphHasRemaining(accessor metav1.ListInterface) bool {
|
|
313
|
+
remaining := accessor.GetRemainingItemCount()
|
|
314
|
+
return remaining != nil && *remaining > 0
|
|
315
|
+
}
|
|
316
|
+
func graphSourceErrorClass(err error) string {
|
|
317
|
+
switch {
|
|
318
|
+
case apierrors.IsForbidden(err):
|
|
319
|
+
return "Forbidden"
|
|
320
|
+
case apierrors.IsNotFound(err):
|
|
321
|
+
return "NotFound"
|
|
322
|
+
default:
|
|
323
|
+
return "unavailable"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
4
|
+
|
|
5
|
+
// Scope and declared references have separate tokens from ownership and traffic.
|
|
6
|
+
const (
|
|
7
|
+
ResourceGraphRelationAppliesIn = "applies-in"
|
|
8
|
+
ResourceGraphRelationUsesPriorityClass = "uses-priority-class"
|
|
9
|
+
ResourceGraphRelationUsesRuntimeClass = "uses-runtime-class"
|
|
10
|
+
ResourceGraphRelationWebhookService = "webhook-service"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func graphClusterConfigurationKind(kind string) bool {
|
|
14
|
+
switch kind {
|
|
15
|
+
case "PriorityClass", "RuntimeClass", "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration":
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func (b *graphRules) configurationRelations(o *unstructured.Unstructured, id string) {
|
|
22
|
+
if !graphBuiltinSpec(o) {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
switch o.GetKind() {
|
|
26
|
+
case "ResourceQuota", "LimitRange":
|
|
27
|
+
// Namespace scope is a declaration; selectors/limits do not prove ownership.
|
|
28
|
+
if o.GetNamespace() != "" {
|
|
29
|
+
b.edge(id, b.target("v1", "Namespace", "", o.GetNamespace(), ""), ResourceGraphRelationAppliesIn)
|
|
30
|
+
}
|
|
31
|
+
case "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration":
|
|
32
|
+
for _, target := range graphWebhookServices(o, b.active) {
|
|
33
|
+
b.edge(id, b.target("v1", "Service", target.namespace, target.name, ""), ResourceGraphRelationWebhookService)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Lease holderIdentity is opaque. Only metadata ownerReferences add edges.
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type graphNamespacedName struct{ namespace, name string }
|
|
40
|
+
|
|
41
|
+
func graphWebhookServices(o *unstructured.Unstructured, active func() bool) []graphNamespacedName {
|
|
42
|
+
if !graphBuiltinSpec(o) || (o.GetKind() != "MutatingWebhookConfiguration" && o.GetKind() != "ValidatingWebhookConfiguration") {
|
|
43
|
+
return nil
|
|
44
|
+
}
|
|
45
|
+
result := []graphNamespacedName{}
|
|
46
|
+
for _, raw := range graphSlice(o.Object, "webhooks") {
|
|
47
|
+
if !active() {
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
webhook, _ := raw.(map[string]interface{})
|
|
51
|
+
config := graphMap(webhook, "clientConfig")
|
|
52
|
+
service := graphMap(config, "service")
|
|
53
|
+
namespace, name := graphString(service, "namespace"), graphString(service, "name")
|
|
54
|
+
if graphString(config, "url") == "" && namespace != "" && name != "" {
|
|
55
|
+
result = append(result, graphNamespacedName{namespace, name})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
package runtime
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"sort"
|
|
7
|
+
|
|
8
|
+
cr "github.com/opensoha/soha-contracts/resource"
|
|
9
|
+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
10
|
+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
11
|
+
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
12
|
+
"k8s.io/client-go/metadata"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const ResourceGraphMetadataTargetLimit = 16
|
|
16
|
+
|
|
17
|
+
func graphWorkloadKind(kind string) bool {
|
|
18
|
+
switch kind {
|
|
19
|
+
case "Pod", "Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob", "ReplicaSet", "ReplicationController":
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func graphCollectKind(rootKind, kind string) bool {
|
|
26
|
+
if graphRBACRootKind(rootKind) {
|
|
27
|
+
return graphCollectRBACKind(rootKind, kind)
|
|
28
|
+
}
|
|
29
|
+
switch rootKind {
|
|
30
|
+
case "PriorityClass", "RuntimeClass":
|
|
31
|
+
// One cluster-wide page per workload source, never a loop over namespaces.
|
|
32
|
+
return graphWorkloadKind(kind)
|
|
33
|
+
case "ResourceQuota", "LimitRange", "Lease", "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration", "PersistentVolumeClaim", "PersistentVolume", "StorageClass":
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
switch kind {
|
|
37
|
+
case "ResourceQuota", "LimitRange", "Lease", "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration", "PersistentVolume", "StorageClass",
|
|
38
|
+
"Role", "RoleBinding", "ClusterRole", "ClusterRoleBinding":
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func graphWebhookProjection(full map[string]interface{}) []interface{} {
|
|
45
|
+
result := []interface{}{}
|
|
46
|
+
for _, raw := range graphSlice(full, "webhooks") {
|
|
47
|
+
webhook, _ := raw.(map[string]interface{})
|
|
48
|
+
config := graphMap(webhook, "clientConfig")
|
|
49
|
+
service := graphMap(config, "service")
|
|
50
|
+
if graphString(config, "url") != "" || service == nil {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
result = append(result, map[string]interface{}{"clientConfig": map[string]interface{}{"service": map[string]interface{}{
|
|
54
|
+
"namespace": graphString(service, "namespace"), "name": graphString(service, "name"),
|
|
55
|
+
}}})
|
|
56
|
+
}
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func collectGraphConfigurationTargets(ctx context.Context, client metadata.Interface, root cr.ResourceRef, object *unstructured.Unstructured, reads *int, objects *[]*unstructured.Unstructured, warnings *[]string) error {
|
|
61
|
+
targets := []cr.ResourceRef{}
|
|
62
|
+
switch root.Kind {
|
|
63
|
+
case "ResourceQuota", "LimitRange":
|
|
64
|
+
targets = append(targets, cr.ResourceRef{APIVersion: "v1", Kind: "Namespace", Name: root.Namespace})
|
|
65
|
+
case "MutatingWebhookConfiguration", "ValidatingWebhookConfiguration":
|
|
66
|
+
for _, target := range graphWebhookServices(object, func() bool { return ctx.Err() == nil }) {
|
|
67
|
+
targets = append(targets, cr.ResourceRef{APIVersion: "v1", Kind: "Service", Namespace: target.namespace, Name: target.name})
|
|
68
|
+
}
|
|
69
|
+
case "Lease":
|
|
70
|
+
for _, owner := range object.GetOwnerReferences() {
|
|
71
|
+
if owner.Name == "" || owner.UID == "" || graphBuiltinAPIs[owner.Kind] != owner.APIVersion {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
namespace := root.Namespace
|
|
75
|
+
if owner.Kind == "Node" || owner.Kind == "Namespace" || graphClusterConfigurationKind(owner.Kind) {
|
|
76
|
+
namespace = ""
|
|
77
|
+
}
|
|
78
|
+
targets = append(targets, cr.ResourceRef{APIVersion: owner.APIVersion, Kind: owner.Kind, Namespace: namespace, Name: owner.Name, UID: string(owner.UID)})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
seen := map[string]bool{}
|
|
82
|
+
for _, target := range targets {
|
|
83
|
+
if err := ctx.Err(); err != nil {
|
|
84
|
+
return err
|
|
85
|
+
}
|
|
86
|
+
key := graphIdentity(target.APIVersion, target.Kind, target.Namespace, target.Name)
|
|
87
|
+
if seen[key] {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
if *reads >= ResourceGraphMetadataTargetLimit {
|
|
91
|
+
*warnings = append(*warnings, "configuration reference metadata truncated at 16 targets")
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
seen[key] = true
|
|
95
|
+
resource, ok := graphMetadataResource(target.APIVersion, target.Kind)
|
|
96
|
+
if !ok {
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
if client == nil {
|
|
100
|
+
*warnings = append(*warnings, target.Kind+" reference metadata unavailable")
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
*reads += 1
|
|
104
|
+
obj, err := client.Resource(resource).Namespace(target.Namespace).Get(ctx, target.Name, metav1.GetOptions{})
|
|
105
|
+
if err != nil {
|
|
106
|
+
if fatal := graphConfigurationReadError(ctx, err, target.Kind, warnings); fatal != nil {
|
|
107
|
+
return fatal
|
|
108
|
+
}
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
projected, err := graphObjectProjection(obj, target.APIVersion, target.Kind)
|
|
112
|
+
if err != nil {
|
|
113
|
+
*warnings = append(*warnings, target.Kind+" reference metadata projection unavailable")
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
if projected.GetName() != target.Name || projected.GetNamespace() != target.Namespace || (target.UID != "" && string(projected.GetUID()) != target.UID) {
|
|
117
|
+
*warnings = append(*warnings, target.Kind+" reference metadata identity changed")
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
*objects = append(*objects, projected)
|
|
121
|
+
}
|
|
122
|
+
return ctx.Err()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func graphConfigurationReadError(ctx context.Context, err error, kind string, warnings *[]string) error {
|
|
126
|
+
if canceled := ctx.Err(); canceled != nil {
|
|
127
|
+
return canceled
|
|
128
|
+
}
|
|
129
|
+
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
130
|
+
return err
|
|
131
|
+
}
|
|
132
|
+
*warnings = append(*warnings, kind+" reference metadata "+graphSourceErrorClass(err))
|
|
133
|
+
return nil
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func collectGraphSecretMetadata(ctx context.Context, client metadata.Interface, graph cr.ResourceGraph, reads *int, objects *[]*unstructured.Unstructured, warnings *[]string) (bool, error) {
|
|
137
|
+
targets := []cr.ResourceRef{}
|
|
138
|
+
for _, node := range graph.Nodes {
|
|
139
|
+
if node.Status == "unresolved" && node.Resource.APIVersion == "v1" && node.Resource.Kind == "Secret" && node.Resource.Namespace != "" {
|
|
140
|
+
targets = append(targets, node.Resource)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
sort.Slice(targets, func(i, j int) bool {
|
|
144
|
+
return graphNodeID(targets[i]) < graphNodeID(targets[j])
|
|
145
|
+
})
|
|
146
|
+
changed := false
|
|
147
|
+
seen := map[string]bool{}
|
|
148
|
+
for _, target := range targets {
|
|
149
|
+
if err := ctx.Err(); err != nil {
|
|
150
|
+
return changed, err
|
|
151
|
+
}
|
|
152
|
+
key := graphIdentity("v1", "Secret", target.Namespace, target.Name)
|
|
153
|
+
if seen[key] {
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
if *reads >= ResourceGraphMetadataTargetLimit {
|
|
157
|
+
*warnings = append(*warnings, "Secret reference metadata truncated at 16 targets")
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
seen[key] = true
|
|
161
|
+
*reads += 1
|
|
162
|
+
object, err := client.Resource(graphSecretResource).Namespace(target.Namespace).Get(ctx, target.Name, metav1.GetOptions{})
|
|
163
|
+
if err != nil {
|
|
164
|
+
if fatal := graphConfigurationReadError(ctx, err, "Secret", warnings); fatal != nil {
|
|
165
|
+
return changed, fatal
|
|
166
|
+
}
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
projected, err := graphObjectProjection(object, "v1", "Secret")
|
|
170
|
+
if err != nil {
|
|
171
|
+
*warnings = append(*warnings, "Secret reference metadata projection unavailable")
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
if projected.GetName() != target.Name || projected.GetNamespace() != target.Namespace || (target.UID != "" && string(projected.GetUID()) != target.UID) {
|
|
175
|
+
*warnings = append(*warnings, "Secret reference metadata identity changed")
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
*objects = append(*objects, projected)
|
|
179
|
+
changed = true
|
|
180
|
+
}
|
|
181
|
+
return changed, ctx.Err()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func graphMetadataResource(api, kind string) (schema.GroupVersionResource, bool) {
|
|
185
|
+
// Exact built-in resource names only; CR owners use the existing discovery path.
|
|
186
|
+
resources := map[string]string{
|
|
187
|
+
"Pod": "pods", "Deployment": "deployments", "StatefulSet": "statefulsets", "DaemonSet": "daemonsets", "ReplicaSet": "replicasets", "ReplicationController": "replicationcontrollers", "Job": "jobs", "CronJob": "cronjobs",
|
|
188
|
+
"ConfigMap": "configmaps", "Secret": "secrets", "Service": "services", "PersistentVolumeClaim": "persistentvolumeclaims", "ServiceAccount": "serviceaccounts", "Node": "nodes", "Namespace": "namespaces",
|
|
189
|
+
"Ingress": "ingresses", "HorizontalPodAutoscaler": "horizontalpodautoscalers", "PodDisruptionBudget": "poddisruptionbudgets", "ResourceQuota": "resourcequotas", "LimitRange": "limitranges", "PriorityClass": "priorityclasses", "RuntimeClass": "runtimeclasses", "Lease": "leases",
|
|
190
|
+
"MutatingWebhookConfiguration": "mutatingwebhookconfigurations", "ValidatingWebhookConfiguration": "validatingwebhookconfigurations",
|
|
191
|
+
}
|
|
192
|
+
resource, ok := resources[kind]
|
|
193
|
+
gv, err := schema.ParseGroupVersion(api)
|
|
194
|
+
if !ok || err != nil || graphBuiltinAPIs[kind] != api {
|
|
195
|
+
return schema.GroupVersionResource{}, false
|
|
196
|
+
}
|
|
197
|
+
return gv.WithResource(resource), true
|
|
198
|
+
}
|