https://github.com/stackb/grpc-starlark
Starlark infused gRPC server
https://github.com/stackb/grpc-starlark
grpc mock-server starlark
Last synced: 6 months ago
JSON representation
Starlark infused gRPC server
- Host: GitHub
- URL: https://github.com/stackb/grpc-starlark
- Owner: stackb
- License: apache-2.0
- Created: 2023-05-24T20:19:40.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2023-06-14T19:04:52.000Z (over 2 years ago)
- Last Synced: 2025-02-14T14:51:41.048Z (8 months ago)
- Topics: grpc, mock-server, starlark
- Language: Go
- Homepage:
- Size: 200 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://github.com/stackb/grpc-starlark/actions/workflows/ci.yaml)
# grpc-starlark
![]()
![]()
![]()
protobuf
grpc
starlark
`grpc-starlark` is a:
- library for embedding a gRPC-capable starlark interpreter,
- standalone binary `grpcstar` that executes starlark scripts.> The author pronounces this as `grip-ster` (like "napster", but you can say it however you like).
`grpcstar` use cases include:
- replacement for `grpcurl` when calling gRPC services from the command line
- stand-in for `postman`
- testing gRPC backends
- mocking gRPC backends in integration tests
- gRPC microservices with things like [Google Cloud
Run](https://cloud.google.com/run/docs/triggering/grpc)## Installation
Download a binary from the [releases
page](https://github.com/stackb/grpc-starlark/releases), or install from source:```sh
go install github.com/stackb/grpc-starlark/cmd/grpcstar@latest
```## Usage
```
usage: grpcstar [OPTIONS...] [ARGS...]github:
https://github.com/stackb/grpc-starlarkoptions:
-h, --help [optional, false]
show this help screen
-p, --protoset [required]
filename of proto descriptor set
-f, --file [required]
filename of entrypoint starlark script
(conventionally named *.grpc.star)
-e, --entrypoint [optional, "main"]
name of function in global scope to invoke upon script start
-o, --output [optional, "json", oneof "json|proto|text|yaml"]
formatter for output protobufs returned by entrypoint function
-i, --interactive [optional, false]
start a REPL session (rather then exec the entrypoint)example:
$ grpcstar \
-p routeguide.pb \
-f routeguide.grpc.star \
-e call_get_feature \
longitude=35.0 latitude=109.1
```## Bazel Usage
See [bazel rule documentation](rules/).
## Docker Usage
An image is pushed to [ghcr.io/stackb/grpc-starlark/grpcstar](https://github.com/orgs/stackb/packages/container/package/grpc-starlark%2Fgrpcstar) during the release
workflow. It consists of small base layer and the `grpcstar` binary at the root
of the container with the `Entrypoint` to set `/grpcstar`.```dockerfile
FROM ghcr.io/stackb/grpc-starlark/grpcstar:v0.6.0COPY service.descriptor.pb /
COPY server.grpc.star /CMD --protoset /service.descriptor.pb --file /server.grpc.star
```### Proto Descriptor Set
grpcstar requires a precompiled proto descriptor set via the `--protoset` (`-p`)
flag. This file defines the universe of message, enum, and service types that
can be used in your script.This file can be generated by the protoc `--descriptor_set_out` flag and is used
by other tools in the protobuf/gRPC ecosystem (see
[grpcurl](https://github.com/fullstorydev/grpcurl#protoset-files)).For bazel users, the `proto_library` rule produces this as its output file. The
[proto_descriptor_set](https://github.com/bazelbuild/rules_proto/blob/master/proto/private/rules/proto_descriptor_set.bzl)
concatenates multiple descriptor sets together (`cat foo.descriptor.pb
bar.descriptor.pb > combined.descriptor.pb`).### Script File
The script file `--file` (`-f`) is the entrypoint file executed by the embedded starlark interpreter.
Use load statements (e.g. `load("filename{.star}", "symbol")`) to populate
additional symbols into the entrypoint file.### Script Entrypoint
The script **must** contain a function named `main` that takes a single
positional argument `ctx` (e.g.`def main(ctx):`). The `--entrypoint` (`-e`)
flag can be used to override this.The `ctx` is a struct; `ctx.vars` holds key-value pairs that can be set on the
command line (e.g. `name=foo` would satisfy `ctx.vars.name == 'foo'`).### Script Output
The entrypoint function can either return nothing (`None`) or a list of protobuf
messages. The messages will be printed to stdout and formatted according the
the `--output` flag (`-o`). Choose one of `json`, `proto`, `text`, or `yaml`;
default is `json`.`print(...)` statements are sent to stderr.
### Script Concurrency Model
The starlark interpreter starts a single `main` thread for the top-level
entrypoint file. Each invocation of a `grpc.Server` handler callback function
is run concurrently in a new thread. `thread.defer` callbacks also occur in a
new thread.## API
`grpc-starlark` is implemented using go and has an API similar to `grpc-go`.
### Protobuf
The message and enum types are available via the `proto.package` function:
```py
pb = proto.package("example.routeguide")
print(pb.Rectangle)
```These define "strongly-typed" structs for use in creating and interacting with
protobuf messages:```py
colorado = pb.Rectangle(
lo = pb.Point(latitude = 36.999, longitude = -109.045),
hi = pb.Point(latitude = 40.979, longitude = -102.051),
)
```For more details see
[github.com/stripe/skycfg](https://github.com/stripe/skycfg), which provides the
core protobuf functionality.## gRPC
### Server
Use the `grpc.Server` constructor to make a new server. Use the register
function to provide function implementations for the service handlers. Example:```py
server = grpc.Server()server.register("example.routeguide.RouteGuide", {
"GetFeature": get_feature,
"ListFeatures": list_features,
"RecordRoute": record_route,
"RouteChat": route_chat,
})
```Use a `net.Listener` to bind the server to a network address:
```py
listener = net.Listener(address = "localhost:8080")
```To bind to a free port, use the defaults (`localhost` is the `host` and `0` is
the port)```py
listener = net.Listener()
print(listener.address) # localhost:50234
```#### Unary RPC
```py
def get_feature(stream, point):
"""get_feature implements a unary method handlerArgs:
stream: the stream object
point: the requested Point
Returns:
a Feature, ideally nearest to the given point."""
return pb.Feature(name = "point (%d,%d)" % (point.longitude, point.latitude))
```The `stream` object can be used to access incoming headers `stream.ctx.metadata`
or set outgoing headers/trailers (`stream.set_header`, `stream.set_trailer`).The second positional argument is the request message.
The function should return an appropriate response message or a `grpc.Error`
using an status code and message (e.g. `return grpc.Error(code =
grpc.status.UNAUTHENTICATED, message = "authorization header is required"))`)#### Server Streaming RPC
```py
def list_features(stream, rect):
"""list_features implements a server streaming handlerArgs:
stream: the stream object
rect: the rectangle to get features within
Returns:
None"""
features = [
pb.Feature(name = "lo (%d,%d)" % (rect.lo.longitude, rect.lo.latitude)),
pb.Feature(name = "hi (%d,%d)" % (rect.lo.longitude, rect.hi.latitude)),
]
for feature in features:
stream.send(feature)
```The `stream.send` function is used to post response messages.
#### Client Streaming RPC
```py
def record_route(stream):
"""record_route implements a client streaming handlerArgs:
stream: the stream object
Returns:
a RouteSummary with a summary of the traversed points."""
points = []
for point in stream:
points.append(point)
return pb.RouteSummary(
point_count = len(points),
distance = 2,
elapsed_time = 10,
)
```The `stream` is an iterable that will call `.RevcMsg` until the stream has been closed by the client.
Alternatively, the function `stream.recv` can be used to get a single message, or `None` if there are no more messages.
The return value of the function should return an appropriately typed message.
#### Bidirectional Streaming RPC
```py
def route_chat(stream):
"""route_chat implements a bidirectional streaming handlerArgs:
stream: the stream object
Returns:
None"""
notes = []
for note in stream:
notes.append(note)
stream.send(note)
```In this implementation the function broadcasts a reponse on every request.
## time
The `time` module contains time-related functions. For details, see .
## os
The `os` module contains functions for interacting with the operating system.
- `os.getenv("NAME")` returns the value of the environment variable `NAME` or `None` if not set.
See for details.
## thread
The `thread` module can be used to interact with the interpreter threading model.
- `thread.sleep(duration)` pauses the current thread.
- `thread.defer(fn, delay, count)` runs another function in a new thread after
the given delay. An optional `count` argument will repeat the callback
invocation. This function is akin to the javascript functions `setTimout` and
`setInterval`.
- `thread.name` returns the name of the current thread.Example:
```py
thread.defer(lambda: server.start(listener))`
```## net
The `net` module contains network-related functions.
- `net.Listener` constructs a new listener via the
[net.Listen](https://pkg.go.dev/net#Listen) func.## process
The `process` module contains subprocess-related functions.
- `process.run` runs a subprocess.