// File: installation # Installation Cyphernetes can be installed in multiple ways depending on your operating system and preferences. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; The easiest way to install Cyphernetes on macOS or Linux is via Homebrew: ```bash brew install cyphernetes ``` ```bash go install github.com/avitaltamir/cyphernetes/cmd/cyphernetes@latest ``` You can download the pre-compiled binary for your operating system:

Linux

```bash # For AMD64 curl -LO https://github.com/avitaltamir/cyphernetes/releases/latest/download/cyphernetes-linux-amd64 chmod +x cyphernetes-linux-amd64 sudo mv cyphernetes-linux-amd64 /usr/local/bin/cyphernetes # For ARM64 curl -LO https://github.com/avitaltamir/cyphernetes/releases/latest/download/cyphernetes-linux-arm64 chmod +x cyphernetes-linux-arm64 sudo mv cyphernetes-linux-arm64 /usr/local/bin/cyphernetes ```

macOS

```bash # For AMD64 curl -LO https://github.com/avitaltamir/cyphernetes/releases/latest/download/cyphernetes-darwin-amd64 chmod +x cyphernetes-darwin-amd64 sudo mv cyphernetes-darwin-amd64 /usr/local/bin/cyphernetes # For ARM64 (Apple Silicon) curl -LO https://github.com/avitaltamir/cyphernetes/releases/latest/download/cyphernetes-darwin-arm64 chmod +x cyphernetes-darwin-arm64 sudo mv cyphernetes-darwin-arm64 /usr/local/bin/cyphernetes ```

Windows

Download the latest Windows binary from our [releases page](https://github.com/avitaltamir/cyphernetes/releases/latest).
To build Cyphernetes from source, you'll need: - Go (Latest) - Make - NodeJS (Latest) - pnpm (9+) ```bash # Clone the repository git clone https://github.com/avitaltamir/cyphernetes.git # Navigate to the project directory cd cyphernetes # Build the project make # The binary will be available in the dist/ directory sudo mv dist/cyphernetes /usr/local/bin/cyphernetes ```
## Verifying the Installation After installation, verify that Cyphernetes is working correctly: ```bash cyphernetes --version ``` ## Running Cyphernetes There are multiple ways to run Cyphernetes: 1. **Web Interface** ```bash cyphernetes web ``` Then visit `http://localhost:8080` in your browser. 2. **Interactive Shell** ```bash cyphernetes shell ``` 3. **Single Query** ```bash cyphernetes query "MATCH (p:Pod) RETURN p" ``` --- // File: language # Language Hi, welcome to Cyphernetes. Let's get you started, you'll be querying the Kubernetes resource graph like a pro in no time! ---- ## Node Patterns In Cyphernetes, we draw patterns of resources using ASCII-art, using parentheses to denote nodes and arrows to denote relationships. Let's draw a circle using parenthesis: ```graphql () ``` This is called a _node_. Nodes are the basic building blocks of the Kubernetes resource graph. Nodes are usually not empty. They tend to look more like this: ```graphql (p:Pod) ``` This node contains a _variable_ (in this example it's called `p`), followed by a colon, followed by a _label_ (in our case, it's `Pod`). We assign variable names to nodes so we can refer to them later in the query. Labels are used to specify the node's Kubernetes resource kind. > 💡 Variable names are allowed to be mixed-case and have any length. Labels may also be mixed-case. > Unlike labels, variable names are case-sensitive, so `(d:Deployment)` and `(D:Deployment)` are not the same. Now, imagine a flow chart where each node represents a Kubernetes resource, and the edges (arrows) between them represent the relationships between the resources: ```graphql (d:Deployment)->(s:Service) ``` This is Cyphernetes in a nutshell. You draw a pattern of the resources you want to work with, Cyphernetes will then match all instances of this pattern on the current context, and translate it into the appropriate Kubernetes API calls. The arrow `->` is used to express a relationship between two nodes. This pattern will match all Deployments that are exposed by a Service. It will not match Deployments that are not exposed, or Services that do not expose any Deployments. This is a key feature of Cypher (and Cyphernetes): **We act on patterns** - and only select resources that exactly match the patterns we draw. > 💡 When specifying the label we can use the resource's singular name, plural name or shortname, just like in kubectl. > Unlike kubectl, labels in Cyphernetes are case-insensitive, so `(p:Pod)`, `(p:POD)`, `(p:pod)`, `(p:pods)`, `(p:po)` etc. are all legal and mean the same. > This document adheres to a convention of using minified, lowercase variable names and CamelCase, singular-name labels i.e. `(d:Deployment)`, `(rs:ReplicaSet)` - however this is completely up to the user. ## Reading Resources from the Graph To query the Kubernetes resource graph, we use `MATCH`/`RETURN` expressions. In the `MATCH` clause we draw a pattern or patterns of resources. `RETURN` is then used to organize the results. It takes a list of comma-separated JSONPaths, and returns the results in a JSON object, allowing us to easily craft a custom payload that only contains the fields we need. > 💡 Note that the names of resources are always returned in the special `name` field, even when not specified in the `RETURN` clause. ```graphql // Single-line comments are supported /* ...and so are multi-line comments */ MATCH (d:Deployment) RETURN d.spec.replicas ``` This query will match all Deployments in the current context and return their desired replica count (as well as their name): ```json { "d": [ { "name": "nginx", "spec": { "replicas": 4 }, }, { "name": "nginx-internal", "spec": { "replicas": 2 }, } ] } ``` Let's do one more: ```graphql MATCH (d:Deployment) RETURN d.metadata.labels, d.spec.replicas ``` This query will match all Deployments in the current context, and return a custom payload containing the fields we asked for: ```json { "d": [ { "metadata": { "labels": { "app": "nginx", }, }, "name": "nginx", "spec": { "replicas": 4 } }, { "metadata": { "labels": { "app": "nginx", }, }, "name": "nginx-internal", "spec": { "replicas": 2 } } ] } ``` The returned payload is a JSON object that contains a key for every variable defined in the `RETURN` clause. Each of these keys' value is a list of Kubernetes resources that matched the respective node pattern in the `MATCH` clause. > 💡 Unlike kubectl, Cyphernetes will **always return a list** (a JSON array), even if only one or zero resources were matched. The payload will only include the fields requested in the `RETURN` clause - as well as the `name` field, which is always present. To see the full resource, we simply return the variable name: ```graphql MATCH (d:Deployment) RETURN d ``` ## Context > 💡 Some Cyphernetes programs may allow you to change the default namespace or context using command line arguments and UI elements, but this is beyond the scope of this document which is focused on the Cyphernetes query language itself. By default, Cyphernetes will query the current context (as defined by `kubectl config current-context`). If no namespace is specified in the current context, Cyphernetes will default to using the `default` namespace, similar to kubectl. ### Overriding the Default Namespace You can override the default namespace per node by specifying the `namespace` property in the node's properties: ```graphql MATCH (d:Deployment {namespace: "staging"})->(s:Service) RETURN d.metadata.name, s.spec.clusterIP ``` You can use this language feature to query resources across namespaces: ```graphql MATCH (d:Deployment {namespace: "staging"}), (d2:Deployment {namespace: "production"}) RETURN d.spec.replicas, d2.spec.replicas ``` ### Querying Multiple Clusters Cyphernetes supports querying multiple clusters using the `IN` keyword. ```graphql IN staging, production MATCH (d:Deployment {name: "coredns", namespace: "kube-system"}) RETURN d.spec.replicas ``` Cyphernetes will run the query for each context in the `IN` clause, and return the results in a single payload. The results will be prefixed with the context name, followed by an underscore: ```json { "staging_d": [ { "name": "coredns", "spec": { "replicas": 2 } } ], "production_d": [ { "name": "coredns", "spec": { "replicas": 2 } } ] } ``` ## Advanced Pattern Matching ### Match by Name and Labels A node may contain an optional set of properties. Node properties let us query the resource by name or by any of it's labels. ```graphql MATCH (d:Deployment {name: "nginx-internal", app: "nginx"}) RETURN d.metadata.labels, d.spec.template.spec.containers[0].image ``` (output) ```json { "d": [ { "metadata": { "labels": { "app": "nginx", }, }, "name": "nginx-internal", "spec": { "template": { "spec": { "containers[0]": { "image": "nginx" } } } } } ] } ``` ### Match by Any Field Using the `WHERE` clause, we can filter our results by any field in the Kubernetes resource: ```graphql MATCH (d:Deployment {app: "nginx", namespace: "default"}) WHERE d.spec.replicas=4 RETURN d.spec.replicas ``` (output) ```json { "d": [ { "name": "nginx", "spec": { "replicas": 4 } } ] } ``` ### Escaping Dots in JSONPaths Cyphernetes supports escaping dots in JSONPaths using a backslash. This is useful when querying resources that have dots in their field names. ```graphql MATCH (d:Deployment {name: "nginx-internal"}) WHERE d.metadata.annotations.meta\.cyphernet\.es/foo-bar = "baz" RETURN d.metadata.annotations.meta\.cyphernet\.es/foo-bar ``` (output) ```json { "d": [ { "name": "nginx-internal", "metadata": { "annotations": { "meta.cyphernet.es/foo-bar": "baz" } } } ] } ``` `WHERE` clauses support the following operators: * `=` - equal to * `!=` - not equal to * `<` - less than * `>` - greater than * `<=` - less than or equal to * `>=` - greater than or equal to * `=~` - regex matching * `CONTAINS` - partial string matching Examples: ```graphql // Get all deployments with more than 2 replicas MATCH (d:Deployment) WHERE d.spec.replicas > 2 RETURN d.metadata.name, d.spec.replicas ``` ```graphql // Get all pods that are not running MATCH (p:Pod) WHERE p.status.phase != "Running" RETURN p.metadata.name, p.status.phase ``` ```graphql // Find all deployments scaled above zero and set their related ingresses' ingressClassName to "active" MATCH (d:Deployment)->(s:Service)->(i:Ingress) WHERE d.spec.replicas >= 1 SET i.spec.ingressClassName = "active" ``` ```graphql // Find all deployments that end with "api" MATCH (d:Deployment) WHERE d.metadata.name =~ "^.*api$" RETURN d.spec ``` ### Matching Multiple Nodes Use commas to match two or more nodes: ```graphql MATCH (d:Deployment), (s:Service) RETURN d.spec.replicas, s.spec.clusterIP ``` (output) ```json { "d": [ { "name": "nginx", "spec": { "replicas": 4 } }, { "name": "nginx-internal", "spec": { "replicas": 2 } } ], "s": [ { "name": "nginx", "spec": { "clusterIP": "10.96.0.1" } }, { "name": "nginx-internal", "spec": { "clusterIP": "10.96.0.2" } } ] } ``` ## Relationships Relationships are the glue that holds the Kubernetes resource graph together. Cyphernetes understands the relationships between Kubernetes resources, and lets us query them in a natural way. Relationships are expressed using the `->` and `<-` operators: ```graphql MATCH (d:Deployment)->(s:Service) RETURN d.metadata.service, s.metadata.name ``` This query returns all Services that expose a Deployment, and the name of the Deployment they expose. Only Deployments and Services that have a relationship between them will be returned. The relationship's direction is unimportant. `(d:Deployment)->(s:Service)` is the same as `(d:Deployment)<-(s:Service)`. > If you're familiar with Cypher, you might be wondering about relationship properties. At this time, Cyphernetes does not make use of relationship properties - they are, however, legal - and you may use them if you wish for your own documentation purposes. i.e. `(d:Deployment)->[r:SERVICE_EXPOSE_DEPLOYMENT {"service-type": "kubernetes-internal"}]->(s:Service)` is legal Cyphernetes syntax, but does not affect the query's outcome. The variable `r` is not defined in this query, and is not available for use in a `RETURN` clause or otherwise. ### Basic Relationship Match Cyphernetes understands the relationships between Kubernetes resources: ```graphql MATCH (d:Deployment {name: "nginx"})->(s:Service) RETURN s.spec.ports ``` (output) ```json { "s": [ { "name": "nginx", "spec": { "ports": [ { "port": 80, "protocol": "TCP", "targetPort": 80 } ] } } ] } ``` Cyphernetes knows how to find related resources using a set of predefined rules. For example, Cyphernetes knows that a Service exposes a Deployment if the two resources have matching selectors. Similarly, Cyphernetes knows that a Deployment owns a ReplicaSet if the ReplicaSet's `metadata.ownerReferences` contains a reference to the Deployment. ### Relationships with Multiple Nodes We can match multiple nodes and relationships in a single MATCH clause. This is useful for working with resources that have multiple owners or with custom resources that Cyphernetes doesn't yet understand. ```graphql MATCH (vs:VirtualService), (d:Deployment {name: "my-app"})->(s:Service)->(i:Ingress) WHERE vs.metadata.labels.app="my-app" RETURN i.spec.rules, vs.spec.http.paths ``` (output) ```json { "i": [ { "name": "my-app", "spec": { "rules": [ { "host": "my-app.example.com", "http": { "paths": [ { "backend": { "serviceName": "my-app", "servicePort": 80 }, "path": "/" } ] } } ] } } ] "vs": [ { "name": "my-app", "spec": { "http": { "paths": [ { "backend": { "serviceName": "my-app", "servicePort": 80 }, "path": "/" } ] } } } ] } ``` > Here we match a Deployment, the Service that exposes it, and through the Service also the Ingress that routes to it. We also match the Istio VirtualService that belongs to the same application. Cyphernetes doesn't yet understand Istio, so we fallback to using the app label. ### Kindless Nodes Sometimes you might want to match or operate on resources connected to another resource without knowing their kind in advance. Cyphernetes supports this through "kindless nodes" - nodes where you omit the kind label: ```graphql // Find all resources related to a deployment MATCH (d:Deployment {name: "nginx"})->(x) RETURN x.kind ``` This query will find and return all resources that have a relationship with the "nginx" deployment, such as ReplicaSets and Services. Cyphernetes will automatically expand this query to try all possible kinds that can have a relationship with a Deployment. > Some things to consider when using kindless nodes: > * While kindless nodes are a powerful feature, they should be used judiciously. Being explicit about the kinds of resources you're operating on makes queries more predictable and easier to understand. > * Chaining two kindless nodes (e.g., `MATCH (x)->(y)`) is not supported as it would be ambiguous and potentially expensive to resolve. At least one node in a relationship must have a known kind. > * Standalone kindless nodes (e.g., `MATCH (x)`) are not supported. Kindless nodes must be part of a relationship. ### Anonymous Nodes Anonymous nodes are nodes without a variable name. They are useful when you want to express a relationship path but don't want to use the intermediate resources in a subsequent `RETURN`, `SET` or `DELETE` clause. ```graphql // Find all configmaps that are related to a pod // Notice that we don't specify a variable name for the Pod MATCH (cm:ConfigMap)->(:Pod) RETURN cm.data ``` For even more flexibility, you can use nodes that are both kindless and anonymous - nodes without both a variable name and kind: ```graphql // Find all pods that are two relationships away from a deployment MATCH (d:Deployment {name: "nginx"})->()->(p:Pod) RETURN p.metadata.name ``` Kindless anonymous nodes are useful when you want to express a relationship path but don't care about the intermediate resources. ## Mutating the Graph Cyphernetes supports creating, updating and deleting resources in the graph using the `CREATE`, `SET` and `DELETE` keywords. ### Creating Resources Cyphernetes supports creating resources using the `CREATE` statement. Currently, properties of nodes in `CREATE` clauses must be valid JSON. This is a temporary limitation that will be removed in the future. ```graphql CREATE (k:Kind {"k": "v", "k2": "v2", ...}) ``` ### Creating a Standalone Resource ```graphql CREATE (d:Deployment { "name": "nginx", "metadata": { "labels": { "app": "nginx" } }, "spec": { "replicas": 4, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ {"name": "nginx", "image": "nginx"} ] } } } }) ``` Create expressions may optionally be followed by a `RETURN` clause: ```graphql CREATE (d:Deployment { "name": "nginx", "metadata": { "labels": { "app": "nginx" } }, "spec": { "replicas": 4, "selector": { "matchLabels": { "app": "nginx" } }, "template": { "metadata": { "labels": { "app": "nginx" } }, "spec": { "containers": [ {"name": "nginx", "image": "nginx"} ] } } } }) RETURN d ``` ### Creating Resources by Relationship Relationships can also appear in `CREATE` clauses. Currently, only two nodes may be connected by a relationship in a `CREATE` clause. In `CREATE` clause relationships, one side of the relationship must contain a node variable that was previously defined in a `MATCH` clause. This node does not require a label, as it's label is inferred from the `MATCH` clause. The other side of the relationship is the new node being created. Cyphernetes can infer the created resource's name, labels and other fields from the relationship rule defined between the two nodes' resource kinds. ```graphql MATCH (d:Deployment {name: "nginx"}) CREATE (d)->(s:Service) ``` > This query is equivalent to `kubectl expose deployment nginx --type=ClusterIP`. Cyphernetes' relationship rules contain a set default values for the created resource's fields. These defaults can be overridden by specifying properties in the `CREATE` clause. Default relationship fields should usually be enough for creating a resource by relationship without having to specify any properties on the created node. ### Patching Resources Cyphernetes supports patching resources using the `SET` clause. The `SET` clause is similar to the `CREATE` clause, but instead of creating a new resource, it updates an existing one. `SET` clauses take a list of comma-separated key-value pairs, where the key is a jsonPath to the field to update, and the value is the new value to set. `SET` clauses may only appear after a `MATCH` clause. They may also be followed by a `RETURN` clause. ```graphql MATCH (d:Deployment {name: "nginx"}) SET d.spec.replicas=4 RETURN d.spec.replicas ``` ### Patch by Relationship Relationships in `MATCH` clauses may be used to patch resources that are connected to other resources. ```graphql MATCH (d:Deployment {name: "nginx"})->(s:Service) SET s.spec.ports[0].port=8080 ``` ### Deleting Resources Deleting resources is done using the `DELETE` clause. `DELETE` clauses may only appear after a `MATCH` clause. A `DELETE` clause takes a list of variables from the `MATCH` clause - All Kubernetes resources matched by those variables will be deleted. ```graphql MATCH (d:Deployment {name: "nginx"}) DELETE d ``` ### Delete by Relationship Relationships in `MATCH` clauses may be used to delete resources that are connected to other resources. ```graphql MATCH (d:Deployment {name: "nginx"})->(s:Service)->(i:Ingress) DELETE s, i ``` ## Aggregations Cyphernetes supports aggregations in the `RETURN` clause. Currently, only the `COUNT` and `SUM` functions are supported. ```graphql MATCH (d:Deployment)->(rs:ReplicaSet)->(p:Pod) RETURN COUNT{p} AS TotalPods, SUM{d.spec.replicas} AS TotalReplicas { ... "aggregate": { "TotalPods": 10, "TotalReplicas": 20 }, ... } ``` ```graphql MATCH (d:deployment {name:"auth-service"})->(s:svc)->(p:pod) RETURN SUM { p.spec.containers[*].resources.requests.cpu } AS totalCPUReq, SUM {p.spec.containers[*].resources.requests.memory } AS totalMemReq; { ... "aggregate": { "totalCPUReq": "5", "totalMemReq": "336.0Mi" }, ... } ``` ## Result Ordering and Pagination Cyphernetes supports ordering, limiting, and paginating query results using `ORDER BY`, `LIMIT`, `SKIP`, and `OFFSET` clauses. ### Ordering Results with ORDER BY You can sort query results using the `ORDER BY` clause. It supports sorting by any field accessible via JSONPath or by aliases defined with `AS`. ```graphql // Order deployments by name in ascending order (default) MATCH (d:Deployment) RETURN d.metadata.name AS name, d.spec.replicas AS replicas ORDER BY name ``` ```graphql // Order deployments by replica count in descending order MATCH (d:Deployment) RETURN d.metadata.name AS name, d.spec.replicas AS replicas ORDER BY replicas DESC ``` ```graphql // Order by JSON path directly MATCH (d:Deployment) RETURN d.metadata.name, d.spec.replicas ORDER BY d.metadata.name DESC ``` You can specify multiple sort fields: ```graphql MATCH (d:Deployment) RETURN d.metadata.name, d.spec.replicas, d.metadata.namespace ORDER BY d.metadata.namespace ASC, d.spec.replicas DESC ``` ### Limiting Results with LIMIT Use `LIMIT` to restrict the number of results returned: ```graphql // Get the 5 most recent deployments MATCH (d:Deployment) RETURN d.metadata.name, d.metadata.creationTimestamp ORDER BY d.metadata.creationTimestamp DESC LIMIT 5 ``` ### Skipping Results with SKIP and OFFSET Use `SKIP` or `OFFSET` to skip a specified number of results. Both keywords are aliases and work identically: ```graphql // Skip the first 10 deployments MATCH (d:Deployment) RETURN d.metadata.name ORDER BY d.metadata.name SKIP 10 ``` ```graphql // OFFSET is an alias for SKIP MATCH (d:Deployment) RETURN d.metadata.name ORDER BY d.metadata.name OFFSET 10 ``` ### Combining ORDER BY, LIMIT, and SKIP You can combine these clauses for pagination: ```graphql // Get the second page of deployments (items 11-20) MATCH (d:Deployment) RETURN d.metadata.name AS name, d.spec.replicas AS replicas ORDER BY name SKIP 10 LIMIT 10 ``` The order of `LIMIT` and `SKIP`/`OFFSET` can be reversed: ```graphql // This is equivalent to the above query MATCH (d:Deployment) RETURN d.metadata.name AS name, d.spec.replicas AS replicas ORDER BY name LIMIT 10 OFFSET 10 ``` ### Relationship Pattern Ordering Ordering also works with relationship patterns: ```graphql // Find deployment->pod relationships, ordered by pod name MATCH (d:Deployment)->(rs:ReplicaSet)->(p:Pod) RETURN d.metadata.name AS deployment, p.metadata.name AS pod ORDER BY pod DESC LIMIT 5 ``` ## Temporal Expressions Cyphernetes supports temporal expressions for filtering resources based on their creation or modification times. The `datetime()` function returns the current date and time in ISO 8601 format when called without arguments. The `duration()` function returns a duration in ISO 8601 format. You may use plus (+) and minus (-) operators to add or subtract durations from a datetime: ```graphql // Find pods that were created in the last 24 hours MATCH (p:Pod) WHERE p.metadata.creationTimestamp > datetime() - duration("PT24H") RETURN p.metadata.name; // Delete pods that were created more than 7 days ago MATCH (p:Pod) WHERE p.metadata.creationTimestamp < datetime() - duration("P7D") DELETE p; ``` --- // File: cli # CLI > Note: Dry Run mode is available for all CLI commands. The `-d, --dry-run` flag can be used with any CLI command to enable dry run mode. When dry run mode is enabled, Cyphernetes will print the actions it would take without actually performing them. ```bash cyphernetes --dry-run query 'CREATE (d:Deployment {name: "nginx"})' cyphernetes --dry-run shell cyphernetes --dry-run web ``` > Note: Selecting a Kubernetes context. By default Cyphernetes uses your kubeconfig's current context. The global `--context` flag lets you target a different context per invocation, just like `kubectl --context`. It works with every command (`query`, `shell`, `web`, `operator`) and respects the `KUBECONFIG` environment variable. ```bash cyphernetes --context staging query 'MATCH (p:Pod) RETURN p.metadata.name' cyphernetes --context staging shell ``` **Which context is used** — Cyphernetes picks the first that applies: 1. The context named by `--context`, read from your kubeconfig. Setting this flag always uses the kubeconfig, even when running inside a Pod. 2. In-cluster config, when running inside a Pod and `--context` is not set. 3. The kubeconfig's `current-context`, when neither of the above applies. Cyphernetes reads your kubeconfig from `$KUBECONFIG` if that variable is set, and otherwise from `~/.kube/config` — the same as `kubectl`. This is independent of the in-query `IN` multi-context syntax (e.g. `IN prod, staging MATCH (p:Pod) RETURN p.metadata.name`); an explicit `IN` clause overrides `--context` for that query. ## Shell Cyphernetes comes with a shell that lets you interactively query the Kubernetes API using Cyphernetes. To start the shell, run: ```bash cyphernetes shell ``` The shell supports syntax highlighting, autocompletion, and history. Use tab to autocomplete keywords, labels, and jsonPaths. By default the shell works in multiline mode, which means your query will be executed when you type a semicolon (`;`). You can toggle multiline mode by typing `\m` in the shell. At any time, you can type `exit` to exit the shell, or `help` to get a list of available commands. Available shell commands: * `help` - Display help and documentation. * `exit` - Exit the shell. * `\n |all` - Set the namespace context for the shell to either `` or all namespaces. * `\m` - Toggle multiline mode (execute query on ';'). * `\v` - Toggle Vi keybindings. * `\g` - Toggle graph mode (print graph as ASCII art). * `\gl` - Toggle graph layout (Left to Right or Top to Bottom). * `\d` - Print debug information. * `\q` - Toggle printing query execution time. * `\r` - Toggle raw output (disable colorized JSON). * `\cc` - Clear the cache. * `\pc` - Print the cache. * `\lm` - List available macros. * `:macro_name [args]` - Execute a macro. ### Graphs Cyphernetes can print the Kubernetes resource graph as an ASCII graph. To toggle printing the graph, use the `\g` command. To change the graph layout, use the `\gl` command. ### Macros Cyphernetes comes with a set of default macros that can be used to query the Kubernetes API. There are many built-in macros for performing common tasks such as listing pods, services, deployments, etc. as well as for performing common tasks such as exposing a deployment as a service. You can list available macros by running `\lm` in the shell. You can use a macro by running `:` in the shell: ```graphql > :getpo { "pods": [ { "Age": "2024-08-06T21:29:05Z", "IP": "10.244.0.5", "Name": "nginx-bf5d5cf98-m69mz", "Node": "kind-control-plane", "Status": "Running" } ] } Macro executed in 14.971875ms ``` User macros are defined in the `~/.cyphernetes/macros` file. Macros are defined using the following syntax: ``` :macro [] [// description] MATCH (p:Pods) RETURN p.metadata.name; // Multi-line queries are supported :macro my-macro // Return all pod names MATCH (p:Pods) RETURN p.metadata.name; ``` ---- ## Query The `query` command lets you run a single Cyphernetes query from the command line. Available flags: * `-r, --raw-output` - Disable colorized JSON output. ```bash cyphernetes query 'MATCH (d:Deployment {name: "nginx"}) RETURN d' ``` ## Web The `web` command starts a web server that lets you interact with Cyphernetes using a web interface. To start the web server, run: ```bash cyphernetes web ``` You can then visit `http://localhost:8080` in your browser to interact with Cyphernetes. ## Custom Relationships Cyphernetes allows defining custom relationships between Kubernetes resources in a `~/.cyphernetes/relationships.yaml` file. This is useful when working with custom resources or when you want to define relationships that aren't built into Cyphernetes. Example relationships.yaml: ```yaml relationships: - kindA: applications.argoproj.io kindB: services relationship: ARGOAPP_SYNC_SERVICE matchCriteria: - fieldA: "$.spec.source.targetRevision" fieldB: "$.metadata.labels.targetRevision" comparisonType: ExactMatch - fieldA: "$.spec.project" fieldB: "$.metadata.labels.project" comparisonType: ExactMatch - kindA: pods kindB: deployments relationship: DEPLOYMENT_OWN_POD matchCriteria: - fieldA: "$.metadata.name" fieldB: "$.metadata.name" comparisonType: StringContains ``` The relationships.yaml file supports the following fields: - `kindA`, `kindB`: The Kubernetes resource kinds to relate (use plural form, e.g. "deployments" not "Deployment") - `relationship`: A unique identifier for this relationship type (conventionally UPPERCASE) - `matchCriteria`: List of criteria that must all match for the relationship to exist - `fieldA`: JSONPath to field in kindA resource - `fieldB`: JSONPath to field in kindB resource - `comparisonType`: One of: - `ExactMatch`: Values must match exactly - `ContainsAll`: All key-value pairs in fieldB must exist in fieldA - `StringContains`: The value in fieldA contains the value in fieldB as a substring - `defaultProps`: Optional default values to use when creating resources - `fieldA`: JSONPath to field in kindA - `fieldB`: JSONPath to field in kindB - `default`: Default value if field is not specified Custom relationships are loaded on startup and can be used just like built-in relationships in queries: ```graphql MATCH (d:Deployment)->(p:Pod) RETURN d.metadata.name, p.metadata.name ``` --- // File: operator # Operator Cyphernetes is available as a Kubernetes Operator that can be used to define child operators on-the-fly. ## Usage The cyphernetes-operator watches for CustomResourceDefinitions (CRDs) of type `DynamicOperator` and sets up watches on the specified Kubernetes resources. When a change is detected, the operator executes the Cypher queries and updates the resources accordingly. Here is a simple example of a DynamicOperator that sets the ingress class name to "inactive" when the deployment has 0 replicas and to "active" when the deployment has more than 0 replicas: ```yaml apiVersion: cyphernetes-operator.cyphernet.es/v1 kind: DynamicOperator metadata: name: ingress-activator-operator spec: resourceKind: deployments namespace: default onUpdate: | MATCH (d:Deployment {name: "{{$.metadata.name}}"})->(s:Service)->(i:Ingress) WHERE d.spec.replicas = 0 SET i.spec.ingressClassName = "inactive"; MATCH (d:Deployment {name: "{{$.metadata.name}}"})->(s:Service)->(i:Ingress) WHERE d.spec.replicas > 0 SET i.spec.ingressClassName = "active"; ``` In addition to the `onUpdate` field, the operator also supports the `onCreate` and `onDelete` fields. ## Installation The operator can be installed either using helm, or using the Cyphernetes CLI. ### Helm To install the operator using helm, run the following command: ```bash helm pull oci://ghcr.io/avitaltamir/cyphernetes/cyphernetes-operator tar -xvf cyphernetes-operator-*.tgz cd cyphernetes-operator helm upgrade --install cyphernetes-operator . --namespace cyphernetes-operator --create-namespace ``` Make sure to edit the values.yaml file and configure the operator's RBAC rules. By default, the operator will have no permissions and will not be able to watch any resources. ### Cyphernetes CLI Alternatively, you can install the operator using the Cyphernetes CLI - this is meant for development and testing purposes: ```bash cyphernetes operator deploy ``` (or to remove): ```bash cyphernetes operator remove ``` ## Using the operator To start watching resources, you need to provision your first `DynamicOperator` resource. ```yaml apiVersion: cyphernetes-operator.cyphernet.es/v1 kind: DynamicOperator metadata: name: ingress-activator-operator namespace: default spec: resourceKind: deployments namespace: default onUpdate: | MATCH (d:Deployment {name: "{{$.metadata.name}}"})->(s:Service)->(i:Ingress) WHERE d.spec.replicas = 0 SET i.spec.ingressClassName = "inactive"; MATCH (d:Deployment {name: "{{$.metadata.name}}"})->(s:Service)->(i:Ingress) WHERE d.spec.replicas > 0 SET i.spec.ingressClassName = "active"; ``` The operator will now watch the `deployments` resource in the `default` namespace and update the ingress class name accordingly. In addition to the `onUpdate` field, the operator also supports the `onCreate` and `onDelete` fields. You can easily template `DynamicOperator` resources using the cyphernetes cli: ```bash cyphernetes operator create my-operator --on-create "MATCH (n) RETURN n" | kubectl apply -f - ``` ## Dry-run mode A `DynamicOperator` can be created in dry-run mode by setting `dryRun: true` (it defaults to `false`). In dry-run mode the operator still watches the target resource and evaluates the `onCreate`/`onUpdate`/`onDelete` queries, but every mutation is sent to the Kubernetes API with the dry-run option, so **nothing is actually persisted**. The operator also skips its own side effects in this mode (it does not add finalizers or owner references to your resources). This lets you preview what an operator *would* do — visible in the operator logs as `Dry run mode: would create/patch/delete ...` — before enabling it for real. ```yaml apiVersion: cyphernetes-operator.cyphernet.es/v1 kind: DynamicOperator metadata: name: ingress-activator-operator namespace: default spec: resourceKind: deployments namespace: default dryRun: true # preview only; no changes are persisted onUpdate: | MATCH (d:Deployment {name: "{{$.metadata.name}}"})->(s:Service)->(i:Ingress) WHERE d.spec.replicas = 0 SET i.spec.ingressClassName = "inactive"; ``` To make the operator apply changes for real, remove the `dryRun` field or set it to `false`. --- // File: examples # Examples This guide provides practical examples of using Cyphernetes in various scenarios. Each example includes explanations and variations to help you understand how to adapt them to your needs. ## Basic Patterns ### Node Patterns Basic node patterns with and without variables: ```graphql // Basic node pattern MATCH (p:Pod) RETURN p; // Node with properties MATCH (d:Deployment {metadata: {name: "nginx"}}) RETURN d; // Anonymous nodes MATCH (p:Pod)->(:Service)->(e:Endpoints) RETURN p, e; // Kindless nodes (without specified resource type) MATCH (d:Deployment {metadata: {name: "nginx"}})->(x) RETURN p, x.kind; ``` ### Resource Relationships Different ways to express relationships between resources: ```graphql // Right direction relationship MATCH (p:Pod)->(s:Service) RETURN p.metadata.name, s.metadata.name; // Relationship direction doesn't matter MATCH (p:Pod)<-(s:Service) RETURN p.metadata.name, s.metadata.name; // Chained relationships MATCH (d:Deployment)->(rs:ReplicaSet)->(p:Pod) RETURN d.metadata.name, rs.metadata.name, p.metadata.name; // Anonymous, kindless relationships MATCH (d:Deployment)->()->(p:Pod) RETURN d.metadata.name, p.metadata.name; // Find all resources related to a deployment MATCH (d:Deployment {app: "my-app"})->(x) RETURN d, x.kind, x.metadata.name; // Complex relationship chains MATCH (d:Deployment {app: "my-app"})->(rs:ReplicaSet)->(p:Pod)->(s:Service)->(i:Ingress) RETURN d.metadata.name, rs.metadata.name, p.metadata.name, s.metadata.name, i.metadata.name; ``` ## Resource Management ### Pod Management Find and manage pods in your cluster: ```graphql // Delete all pods that aren't running MATCH (p:Pod) WHERE p.status.phase != "Running" DELETE p; // Find pods with no node assigned MATCH (p:Pod) WHERE p.spec.nodeName = NULL RETURN p.metadata.name; // Find pods with specific labels (with escaped dots) MATCH (p:Pod) WHERE p.metadata.labels.kubernetes\.io/name = "nginx" RETURN p.metadata.name; // Find pods with high restart counts MATCH (p:Pod) WHERE p.status.containerStatuses[0].restartCount > 5 RETURN p.metadata.name, p.status.containerStatuses[0].restartCount; ``` ### Deployment Management Work with deployments and their related resources: ```graphql // Scale deployments in a namespace MATCH (d:Deployment {namespace: "production"}) SET d.spec.replicas = 3; // Find deployments with mismatched replicas MATCH (d:Deployment) WHERE d.status.availableReplicas < d.spec.replicas RETURN d.metadata.name, d.spec.replicas, d.status.availableReplicas; // List pods for a specific deployment MATCH (d:Deployment {app: "my-app"})->(:ReplicaSet)->(p:Pod) RETURN p.metadata.name, p.status.phase; // Update container images MATCH (d:Deployment {app: "my-app"}) SET d.spec.template.spec.containers[0].image = "nginx:latest" RETURN d.metadata.name; ``` ### Cluster Maintenance ```graphql // Find configmaps not used by any pod MATCH (cm:ConfigMap) WHERE NOT (cm)->(:Pod) RETURN cm.metadata.name; // Find orphaned PersistentVolumeClaims MATCH (pvc:PersistentVolumeClaim) WHERE NOT (pvc)->(:PersistentVolume) AND pvc.status.phase != "Bound" RETURN pvc.metadata.name; // Delete pods that are not running and were created more than 7 days ago MATCH (p:Pod) WHERE p.status.phase != "Running" AND p.metadata.creationTimestamp < datetime() - duration("P7D") DELETE p; ``` ### Service and Endpoint Analysis ```graphql // Find services without endpoints MATCH (s:Service) WHERE NOT (s)->(:core.Endpoints) RETURN s.metadata.name; // Find services with specific labels MATCH (s:Service {app: "frontend"}) RETURN s.metadata.name; // Find services in multiple contexts IN production, staging MATCH (s:Service {name: "api"}) RETURN s.metadata.name, s.spec.clusterIP; ``` --- // File: integration # Integration This guide will help you integrate Cyphernetes into your own Go project. Cyphernetes is made up of two main packages: 1. The `pkg/core` package, which contains the Cyphernetes parser and engine. 2. The `pkg/provider` package, which contains the Cyphernetes provider interface and a default implementation for an api-server client. ## Integrating the `pkg/core` package The `pkg/core` package is a library that you can import into your own Go project. It provides a single function, `Parse`, which takes a Cyphernetes query and returns a Result object, which contains the results data and a graph made up of nodes and edges. To use the `Parse` function, you need to import the `pkg/core` package and instantiate a new `QueryExecutor` using the `NewQueryExecutor` function - to which you pass a `Provider` implementation. Here's how this looks in the Cyphernetes CLI and Cyphernetes operator projects, both using the default `apiserver` provider: ```go import ( "github.com/avitaltamir/cyphernetes/pkg/core" "github.com/avitaltamir/cyphernetes/pkg/provider/apiserver" ) provider := apiserver.NewAPIServerProvider() executor := core.NewQueryExecutor(provider) query := "MATCH (p:Pod) WHERE p.status.phase != 'Running' RETURN p.metadata.name" result, err := executor.Parse(query) if err != nil { log.Fatalf("Error parsing query: %v", err) } fmt.Printf("Result: %+v\n", result) ``` Out of the box, Cyphernetes ships with a default implementation for an api-server client, which is the `pkg/provider/apiserver` package. This package is a wrapper around the Kubernetes client-go library, and provides a `Provider` interface that you can implement in your own project - and use the Cyphernetes parser and engine with a different backend. The provider interface is defined in the `pkg/provider/interface.go` file: ```go type Provider interface { // Resource Operations GetK8sResources(kind, fieldSelector, labelSelector, namespace string) (interface{}, error) DeleteK8sResources(kind, name, namespace string) error CreateK8sResource(kind, name, namespace string, body interface{}) error PatchK8sResource(kind, name, namespace string, body interface{}) error // Schema Operations FindGVR(kind string) (schema.GroupVersionResource, error) GetOpenAPIResourceSpecs() (map[string][]string, error) CreateProviderForContext(context string) (Provider, error) } ``` To implement the provider interface, you can use the `pkg/provider/apiserver` package as a reference implementation. The 4 CRUD operations are pretty straightforward. They all take a kind, and namespace, "Get" operations take a fieldSelector and labelSelector, while "Create" and "Patch" operations take a body (JSON for "Create", and a JSON patch for "Patch"). The schema operation functions are as follows: - `FindGVR` is used to find the GVR for a given kind. This is used by the Cyphernetes parser to find the correct API endpoint to query. It returns an `apimachinery/pkg/runtime/schema.GroupVersionResource`, which contains the Group, Version, and Resource for the given kind. - `GetOpenAPIResourceSpecs` is used to get a flat list of JSONPaths for a given kind. This is used by the Cyphernetes parser to understand the API schema, which allows it to infer relationships between resources. Example: ``` { ... "pods": [ // Plural name of the kind ... "$.metadata.name", "$.spec.containers[*].name", "$.spec.containers[*].resources.requests.cpu", "$.spec.containers[*].resources.requests.memory", "$.spec.containers[*].resources.limits.cpu", "$.spec.containers[*].resources.limits.memory" ... ], ... } ``` - `CreateProviderForContext` is used to create a new provider for a given context. This is used by the Cyphernetes engine when running multi-context queries only. # Kubernetes Client The `pkg/provider/apiserver` package is a wrapper around the Kubernetes client-go library and may be used as a base implementation for your own provider. If your program already uses client-go, you can re-use a lot of the code in the `pkg/provider/apiserver` package, you can pass a clientSet to the `NewAPIServerProvider` function - or you can initialize the provider with no options and a new clientSet will be created from the available configuration. Alternatively, you can implement your own provider from scratch, as long as it: - implements the `Provider` interface - `FindGVR` correctly resolves strings to `schema.GroupVersionResource` objects - `GetOpenAPIResourceSpecs` provides a list of JSONPaths for each kind - You may choose to make this a "read-only" provider by having CUD operations return an error or warning - or implement the full CRUD operations. - You may choose to support multiple Kubernetes contexts, or leave out this functionality and return an error from `CreateProviderForContext` if the user tries to run a multi-context query. --- // File: roadmap # Roadmap For the most up-to-date project roadmap and development status, please visit our [GitHub Issues](https://github.com/avitaltamir/cyphernetes/issues) page. We actively track feature requests, bug reports, and development progress through GitHub Issues. Feel free to: - Submit feature requests - Report bugs - Contribute to discussions - Track development progress Visit our [GitHub repository](https://github.com/avitaltamir/cyphernetes) to get involved!