// 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!