Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/.idea
/.vscode/**/*
!/.vscode/settings.json
/.codex

# System
.DS_Store
Expand Down
4 changes: 3 additions & 1 deletion charts/console/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -605,8 +605,10 @@ cloudQuery:

resources:
requests:
memory: 250Mi
memory: 512Mi
cpu: 100m
limits:
memory: 1Gi

livenessProbe: ~

Expand Down
2 changes: 1 addition & 1 deletion go/cloud-query/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.26.5 AS builder
FROM golang:1.26.6 AS builder

WORKDIR /workspace

Expand Down
14 changes: 13 additions & 1 deletion go/cloud-query/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ Cloud Query is a service part of the Plural Console ecosystem that provides clou
- Query cloud resources across multiple providers
- Embedded PostgreSQL database for data storage and retrieval through PostgreSQL FDW steampipe extension
- gRPC API for integration with other services
- Sandboxed execution for Lua and Monty's limited Python subset
- Containerized deployment for easy scaling

CloudQuery provider support includes AWS, Azure, GCP, and VMware vSphere.

## Prerequisites

- Go 1.24.2 or higher
- Go 1.26.6 or higher
- Docker (for containerized deployment)
- Make

Running the complete service locally requires a writable temporary directory.
The Python worker extracts gomonty's embedded native library there on first use.

## Getting Started

### Local Development
Expand Down Expand Up @@ -89,6 +93,14 @@ Cloud-Query also exposes ToolQuery gRPC endpoints for observability tools (metri

ToolQuery also supports cloud function invocation via `InvokeLambda` for AWS Lambda, GCP Cloud Run services (Gen2), and Azure Functions using canonical identifiers and cloud connection credentials.

### Sandboxed Python execution

`RunPython` executes Monty's limited Python subset in a crash-isolated `cloud-query python-worker` subprocess. The request's optional JSON object is available as `input`; `output` starts as an empty dictionary and must remain a JSON-serializable dictionary. The response returns that dictionary as `result_json` and standard-output text from `print()` separately as `stdout`.

This is not CPython. The sandbox has no host filesystem, environment, network, subprocess, shell, `pip`, third-party packages, or callback access. Two workers start with only `TMPDIR=/tmp`; each request gets a fresh gomonty REPL. Failed or canceled workers are killed and replaced, and healthy workers are periodically recycled. Source is limited to 64 KiB, input and result JSON to 1 MiB, stdout to 64 KiB, interpreter execution to 10 seconds, interpreter-managed memory to 64 MiB, and recursion to 200 frames. Two runs execute concurrently and up to 16 more wait in a FIFO queue. A full queue is rejected. A parent watchdog ends a run after 15 seconds or the caller's earlier deadline.
Comment thread
floreks marked this conversation as resolved.
Outdated

The image embeds gomonty `v0.0.14` and its platform-specific glibc library, built against official Monty commit `c9802b5f30d11fecf9f153feb1dfdab3abda070e`. It contains no separate `monty` executable. Both pins are recorded in OCI labels. Monty's memory limit covers interpreter-managed allocations rather than total pod RSS; operators should measure the workload before reducing the default cloud-query memory allocation.

### Tool Provider Credentials and Permissions

- `Dynatrace`:
Expand Down
17 changes: 17 additions & 0 deletions go/cloud-query/api/proto/toolquery.proto
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,22 @@ message RunLuaOutput {
string result_json = 1;
}

message RunPythonInput {
// Python source. Write structured results to the global `output` dictionary.
string script = 1;

// Optional JSON object exposed as the global `input` dictionary. Empty means `{}`.
string input_json = 2;
}

message RunPythonOutput {
// JSON-encoded contents of the global `output` dictionary.
string result_json = 1;

// Text emitted by print() during the user script.
string stdout = 2;
}

service ToolQuery {
rpc Metrics(MetricsQueryInput) returns (MetricsQueryOutput) {}
rpc MetricsSearch(MetricsSearchInput) returns (MetricsSearchOutput) {}
Expand All @@ -308,4 +324,5 @@ service ToolQuery {
rpc Traces(TracesQueryInput) returns (TracesQueryOutput) {}
rpc InvokeLambda(InvokeLambdaInput) returns (InvokeLambdaOutput) {}
rpc RunLua(RunLuaInput) returns (RunLuaOutput) {}
rpc RunPython(RunPythonInput) returns (RunPythonOutput) {}
}
17 changes: 13 additions & 4 deletions go/cloud-query/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/pluralsh/console/go/cloud-query/internal/pool"
"github.com/pluralsh/console/go/cloud-query/internal/server"
"github.com/pluralsh/console/go/cloud-query/internal/service"
pythontools "github.com/pluralsh/console/go/cloud-query/internal/tools/python"
)

func startHealthzHandler() {
Expand All @@ -27,9 +28,20 @@ func startHealthzHandler() {
}

func main() {
if len(os.Args) == 2 && os.Args[1] == "python-worker" {
if err := pythontools.NewWorker().Run(os.Stdin, os.Stdout); err != nil {
os.Exit(1)
}
return
}

startHealthzHandler()

services := []service.Service{service.NewToolQueryService()}
toolQueryService, err := service.NewToolQueryService(context.Background())
if err != nil {
klog.Fatalf("failed to initialize tool query service: %v", err)
}
services := []service.Service{toolQueryService}

if args.DatabaseEnabled() {
p, err := pool.NewConnectionPool(args.DatabaseConnectionTTL())
Expand Down Expand Up @@ -67,8 +79,5 @@ func handleShutdown(cancel context.CancelFunc, s *server.Server) {
<-signalChan
klog.Info("received shutdown signal, shutting down gracefully...")

s.Stop()
cancel()
klog.Info("stopped gracefully")
os.Exit(0)
}
20 changes: 20 additions & 0 deletions go/cloud-query/docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ service ToolQuery {
rpc Logs(LogsQueryInput) returns (LogsQueryOutput) {}
rpc Traces(TracesQueryInput) returns (TracesQueryOutput) {}
rpc InvokeLambda(InvokeLambdaInput) returns (InvokeLambdaOutput) {}
rpc RunLua(RunLuaInput) returns (RunLuaOutput) {}
rpc RunPython(RunPythonInput) returns (RunPythonOutput) {}
}
```

Expand Down Expand Up @@ -1507,6 +1509,24 @@ message JaegerTracesOptions {
}
```

## Run Python

`RunPython` synchronously executes Monty's limited Python subset in a fresh logical sandbox session. `input_json` is optional but, when present, must encode an object. It is exposed as the global `input`; scripts write their structured response to the global `output` dictionary. Printed text is returned separately.

```protobuf
message RunPythonInput {
string script = 1;
string input_json = 2;
}

message RunPythonOutput {
string result_json = 1;
string stdout = 2;
}
```

The runtime exposes no host filesystem, environment, network, subprocess, shell, package installation, third-party package, or host-tool callback. It limits source to 64 KiB, input and result JSON to 1 MiB, stdout to 64 KiB, execution to 10 seconds, memory to 64 MiB, recursion to 200 frames, wall time to 15 seconds, and concurrency to two runs per process. Up to 16 additional requests wait in a bounded FIFO queue. It uses gomonty `v0.0.14`, built against official Monty commit `c9802b5f30d11fecf9f153feb1dfdab3abda070e`; it is not CPython.

## Invoke Lambda

`InvokeLambda` invokes serverless functions using canonical provider identifiers only.
Expand Down
6 changes: 5 additions & 1 deletion go/cloud-query/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/pluralsh/console/go/cloud-query

go 1.26.5
go 1.26.6

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1
Expand All @@ -18,6 +18,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/lambda v1.89.1
github.com/aws/aws-sdk-go-v2/service/sts v1.42.2
github.com/elastic/go-elasticsearch/v9 v9.3.1
github.com/ewhauser/gomonty v0.0.14
github.com/gofrs/uuid v4.4.0+incompatible
github.com/lib/pq v1.12.3
github.com/orcaman/concurrent-map/v2 v2.0.1
Expand Down Expand Up @@ -58,6 +59,7 @@ require (
github.com/aws/smithy-go v1.27.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/elastic/elastic-transport-go/v8 v8.8.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
Expand All @@ -80,6 +82,8 @@ require (
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/rogpeppe/go-internal v1.15.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go/cloud-query/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,14 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elastic/elastic-transport-go/v8 v8.8.0 h1:7k1Ua+qluFr6p1jfJjGDl97ssJS/P7cHNInzfxgBQAo=
github.com/elastic/elastic-transport-go/v8 v8.8.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
github.com/elastic/go-elasticsearch/v9 v9.3.1 h1:v5A9uFw0nLFA0luD3xAqliBXbscfuhch409HIinfhKY=
github.com/elastic/go-elasticsearch/v9 v9.3.1/go.mod h1:B5u4H2jo2/v0+PrgbmIUdEyHdenFyavWtjciAFl7TA0=
github.com/ewhauser/gomonty v0.0.14 h1:DM+iSZ/WJzl+huEH5SGONLl8CTooQrfVFbC0xY1ghO4=
github.com/ewhauser/gomonty v0.0.14/go.mod h1:XCLUVfUFX733MIidhDw3iLWwaVNxT23i9xK+ioAAOVc=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down Expand Up @@ -163,6 +167,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
Expand Down
Loading