a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
291 lines
7.9 KiB
Go
291 lines
7.9 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// nodesByID indexes a slice of nodes by ID. The K8s extractor emits
|
|
// duplicate config_key nodes when multiple containers declare the
|
|
// same env var; this is intentional (the graph ingester dedupes), so
|
|
// tests treat IDs as a set rather than a multiset.
|
|
func nodesByID(ns []*graph.Node) map[string]*graph.Node {
|
|
m := make(map[string]*graph.Node, len(ns))
|
|
for _, n := range ns {
|
|
m[n.ID] = n
|
|
}
|
|
return m
|
|
}
|
|
|
|
// hasEdgeBetween returns true when at least one edge of the given
|
|
// kind links from→to. Distinct from the package-local `hasEdge`
|
|
// helper in go_dataflow_test.go (different signature).
|
|
func hasEdgeBetween(es []*graph.Edge, kind graph.EdgeKind, from, to string) bool {
|
|
for _, e := range es {
|
|
if e.Kind == kind && e.From == from && e.To == to {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func TestK8s_DeploymentEmitsResourceImageAndEnv(t *testing.T) {
|
|
src := []byte(`apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: api
|
|
namespace: prod
|
|
labels:
|
|
app: api
|
|
spec:
|
|
template:
|
|
spec:
|
|
containers:
|
|
- name: api
|
|
image: ghcr.io/acme/api:1.2.3
|
|
ports:
|
|
- containerPort: 8080
|
|
protocol: TCP
|
|
env:
|
|
- name: DATABASE_URL
|
|
value: postgres://...
|
|
- name: JWT_SECRET
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: api-secrets
|
|
key: jwt
|
|
- name: FEATURE_FLAGS
|
|
valueFrom:
|
|
configMapKeyRef:
|
|
name: api-config
|
|
key: features
|
|
envFrom:
|
|
- configMapRef:
|
|
name: shared-config
|
|
- secretRef:
|
|
name: shared-secrets
|
|
`)
|
|
e := NewYAMLExtractor()
|
|
result, err := e.Extract("k8s/api.yaml", src)
|
|
require.NoError(t, err)
|
|
|
|
nodes := nodesByID(result.Nodes)
|
|
resID := "k8s::Deployment::prod::api"
|
|
require.Contains(t, nodes, resID)
|
|
assert.Equal(t, graph.KindResource, nodes[resID].Kind)
|
|
assert.Equal(t, "Deployment/api", nodes[resID].QualName)
|
|
|
|
imageID := "image::ghcr.io/acme/api:1.2.3"
|
|
require.Contains(t, nodes, imageID, "should emit Image node for container.image")
|
|
assert.Equal(t, graph.KindImage, nodes[imageID].Kind)
|
|
|
|
// Resource → Image via EdgeDependsOn.
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDependsOn, resID, imageID))
|
|
|
|
// Env vars surface as cfg::env::<NAME> KindConfigKey nodes.
|
|
for _, key := range []string{"DATABASE_URL", "JWT_SECRET", "FEATURE_FLAGS"} {
|
|
id := "cfg::env::" + key
|
|
require.Contains(t, nodes, id, "missing env config_key %q", key)
|
|
assert.Equal(t, graph.KindConfigKey, nodes[id].Kind)
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeUsesEnv, resID, id),
|
|
"missing EdgeUsesEnv to %q", id)
|
|
}
|
|
|
|
// EdgeConfigures wires valueFrom + envFrom to source resources.
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeConfigures, resID,
|
|
"k8s::ConfigMap::prod::api-config"))
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeConfigures, resID,
|
|
"k8s::Secret::prod::api-secrets"))
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeConfigures, resID,
|
|
"k8s::ConfigMap::prod::shared-config"))
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeConfigures, resID,
|
|
"k8s::Secret::prod::shared-secrets"))
|
|
|
|
// EdgeExposes surfaces ports.
|
|
exposes := edgesOfKind(result.Edges, graph.EdgeExposes)
|
|
require.GreaterOrEqual(t, len(exposes), 1)
|
|
assert.Equal(t, "port::tcp::8080", exposes[0].To)
|
|
}
|
|
|
|
func TestK8s_VolumesEmitMounts(t *testing.T) {
|
|
src := []byte(`apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: web
|
|
namespace: prod
|
|
spec:
|
|
template:
|
|
spec:
|
|
containers:
|
|
- name: web
|
|
image: nginx:1.25
|
|
volumes:
|
|
- name: cfg
|
|
configMap:
|
|
name: web-config
|
|
- name: tls
|
|
secret:
|
|
secretName: web-tls
|
|
- name: data
|
|
persistentVolumeClaim:
|
|
claimName: web-data
|
|
`)
|
|
e := NewYAMLExtractor()
|
|
result, err := e.Extract("k8s/web.yaml", src)
|
|
require.NoError(t, err)
|
|
|
|
res := "k8s::Deployment::prod::web"
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeMounts, res,
|
|
"k8s::ConfigMap::prod::web-config"))
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeMounts, res,
|
|
"k8s::Secret::prod::web-tls"))
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeMounts, res,
|
|
"k8s::PersistentVolumeClaim::prod::web-data"))
|
|
}
|
|
|
|
func TestK8s_ServiceExposesPorts(t *testing.T) {
|
|
src := []byte(`apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: api
|
|
namespace: prod
|
|
spec:
|
|
ports:
|
|
- name: http
|
|
port: 80
|
|
targetPort: 8080
|
|
- name: grpc
|
|
port: 50051
|
|
protocol: TCP
|
|
`)
|
|
e := NewYAMLExtractor()
|
|
result, err := e.Extract("k8s/svc.yaml", src)
|
|
require.NoError(t, err)
|
|
|
|
res := "k8s::Service::prod::api"
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeExposes, res, "port::tcp::80"))
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeExposes, res, "port::tcp::50051"))
|
|
}
|
|
|
|
func TestK8s_IngressDependsOnService(t *testing.T) {
|
|
src := []byte(`apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: web
|
|
namespace: prod
|
|
spec:
|
|
rules:
|
|
- host: api.example.com
|
|
http:
|
|
paths:
|
|
- path: /v1
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: api
|
|
port:
|
|
number: 80
|
|
- path: /admin
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: admin
|
|
port:
|
|
number: 8080
|
|
`)
|
|
e := NewYAMLExtractor()
|
|
result, err := e.Extract("k8s/ing.yaml", src)
|
|
require.NoError(t, err)
|
|
|
|
res := "k8s::Ingress::prod::web"
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDependsOn, res, "k8s::Service::prod::api"))
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDependsOn, res, "k8s::Service::prod::admin"))
|
|
}
|
|
|
|
func TestK8s_ConfigMapAndSecretEmitConfigKeys(t *testing.T) {
|
|
src := []byte(`apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: api-config
|
|
namespace: prod
|
|
data:
|
|
features: "alpha,beta"
|
|
log_level: info
|
|
---
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: api-secrets
|
|
namespace: prod
|
|
data:
|
|
jwt: c2VjcmV0
|
|
stringData:
|
|
api-key: literal
|
|
`)
|
|
e := NewYAMLExtractor()
|
|
result, err := e.Extract("k8s/cm.yaml", src)
|
|
require.NoError(t, err)
|
|
|
|
nodes := nodesByID(result.Nodes)
|
|
require.Contains(t, nodes, "cfg::k8s_cm::api-config::features")
|
|
require.Contains(t, nodes, "cfg::k8s_cm::api-config::log_level")
|
|
require.Contains(t, nodes, "cfg::k8s_secret::api-secrets::jwt")
|
|
require.Contains(t, nodes, "cfg::k8s_secret::api-secrets::api-key")
|
|
|
|
cmRes := "k8s::ConfigMap::prod::api-config"
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeDefines, cmRes,
|
|
"cfg::k8s_cm::api-config::features"))
|
|
}
|
|
|
|
func TestK8s_CronJobReachesPodSpec(t *testing.T) {
|
|
src := []byte(`apiVersion: batch/v1
|
|
kind: CronJob
|
|
metadata:
|
|
name: nightly
|
|
spec:
|
|
schedule: "0 0 * * *"
|
|
jobTemplate:
|
|
spec:
|
|
template:
|
|
spec:
|
|
containers:
|
|
- name: run
|
|
image: ghcr.io/acme/job:latest
|
|
env:
|
|
- name: TZ
|
|
value: UTC
|
|
`)
|
|
e := NewYAMLExtractor()
|
|
result, err := e.Extract("k8s/cron.yaml", src)
|
|
require.NoError(t, err)
|
|
|
|
nodes := nodesByID(result.Nodes)
|
|
res := "k8s::CronJob::_default::nightly"
|
|
require.Contains(t, nodes, res)
|
|
require.Contains(t, nodes, "image::ghcr.io/acme/job:latest")
|
|
require.Contains(t, nodes, "cfg::env::TZ")
|
|
assert.True(t, hasEdgeBetween(result.Edges, graph.EdgeUsesEnv, res, "cfg::env::TZ"))
|
|
}
|
|
|
|
func TestK8s_NonK8sYAMLFallsThroughToGeneric(t *testing.T) {
|
|
src := []byte(`name: my-app
|
|
version: "1.0"
|
|
services:
|
|
web:
|
|
image: nginx
|
|
`)
|
|
e := NewYAMLExtractor()
|
|
result, err := e.Extract("docker-compose.yml", src)
|
|
require.NoError(t, err)
|
|
|
|
// Generic walker should still emit KindVariable for top-level keys.
|
|
vars := nodesOfKind(result.Nodes, graph.KindVariable)
|
|
assert.GreaterOrEqual(t, len(vars), 3)
|
|
|
|
// And no Resource/Image/ConfigKey nodes since this isn't K8s.
|
|
assert.Empty(t, nodesOfKind(result.Nodes, graph.KindResource))
|
|
}
|