Using the API
Everything the dicer command line does, it does through the daemon’s gRPC
API, and so can any program. The API is one service,
dicerd.v1.DaemonService, defined in
proto/dicerd/v1/dicerd.proto;
every call and message is listed in the API reference.
Connecting
The daemon serves the API on its Unix socket, unix:///run/dicer/dicer.sock,
which only root can open, and optionally on a TCP listener, with TLS. See
Remote access for the listener and its certificates.
From Go
The github.com/konradasb/dicer package makes the connection, and the
generated code in proto/dicerd/v1 is the API:
$ go get github.com/konradasb/dicer
package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/konradasb/dicer"
dicerdv1 "github.com/konradasb/dicer/proto/dicerd/v1"
)
func main() {
ctx := context.Background()
// The local daemon, on its socket.
c, err := dicer.NewClient()
if err != nil {
log.Fatal(err)
}
defer c.Close()
inst, err := c.CreateInstance(ctx, &dicerdv1.CreateInstanceRequest{
Name: "web",
ImageRef: "nginx:1.27",
Vcpus: 1,
MemoryBytes: 512 << 20,
DiskBytes: 10 << 30,
Ports: []*dicerdv1.PortMapping{
{HostPort: 8080, GuestPort: 80, Protocol: dicerdv1.Protocol_PROTOCOL_TCP},
},
Start: true,
})
switch status.Code(err) {
case codes.OK:
fmt.Printf("%s is %s at %s\n", inst.GetName(), inst.GetState(), inst.GetIp())
case codes.AlreadyExists:
fmt.Println("web exists already")
default:
log.Fatal(status.Convert(err).Message())
}
// Follow its console until it stops.
logs, err := c.GetInstanceLogs(ctx, &dicerdv1.GetInstanceLogsRequest{Name: "web", Follow: true})
if err != nil {
log.Fatal(err)
}
for {
chunk, err := logs.Recv()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
log.Fatal(err)
}
os.Stdout.Write(chunk.GetData())
}
}A dicer.Client is the generated DaemonServiceClient, so its methods are
the API’s calls. It is safe for concurrent use; make one and share it.
For a daemon’s TCP listener, give its address and a TLS configuration:
cert, err := tls.LoadX509KeyPair("client.pem", "client-key.pem")
// …
roots := x509.NewCertPool()
roots.AppendCertsFromPEM(caPEM)
c, err := dicer.NewClient(
dicer.WithAddress("dicer1.example.com:7443"),
dicer.WithTLS(&tls.Config{
RootCAs: roots,
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}),
)dicer.WithDialOptions adds any other gRPC dial option, such as an
interceptor or keepalives.
From other languages
Generate a client from the proto file with your language’s gRPC tools. It imports only Google’s well-known types, which the tools include. For Python:
$ pip install grpcio grpcio-tools
$ python -m grpc_tools.protoc -I dicer/proto \
--python_out=. --grpc_python_out=. dicerd/v1/dicerd.proto
import grpc
from dicerd.v1 import dicerd_pb2, dicerd_pb2_grpc
with grpc.insecure_channel("unix:///run/dicer/dicer.sock") as channel:
daemon = dicerd_pb2_grpc.DaemonServiceStub(channel)
for inst in daemon.ListInstances(dicerd_pb2.ListInstancesRequest()).instances:
print(inst.name, dicerd_pb2.InstanceState.Name(inst.state), inst.ip)
try:
daemon.GetInstance(dicerd_pb2.GetInstanceRequest(name="db"))
except grpc.RpcError as e:
if e.code() == grpc.StatusCode.NOT_FOUND:
print("no db")insecure_channel is right for the socket, which has no TLS; for a TCP
listener with TLS, use grpc.secure_channel with
grpc.ssl_channel_credentials.
From the shell
The daemon does not serve reflection, so give grpcurl the proto file:
$ sudo grpcurl -plaintext -unix \
-import-path dicer/proto -proto dicerd/v1/dicerd.proto \
/run/dicer/dicer.sock dicerd.v1.DaemonService/GetHostInfo
$ sudo grpcurl -plaintext -unix \
-import-path dicer/proto -proto dicerd/v1/dicerd.proto \
-d '{"name": "web"}' \
/run/dicer/dicer.sock dicerd.v1.DaemonService/GetInstance