mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
feat(machine-logs): add server side of journal logs (#282)
* Add server side of journal logs This add the server side and grpc methods to get a journal logs from a machine. It repurposes ServiceLogEntry for these logs to keep the changes somewhat to a minimum. And it lets us re-use the merging of the various logs. In the protobufs ContainerLog has been renamed to just Log and LogEntry, as these are now also used for journal logs. It does api.LogOptions in more places to reduce the various logOpts that were used. It does not yet plumb it through to the uc client, that needs a follow up pr. Following logs is also not yet implemented. Signed-off-by: Miek Gieben <miek@miek.nl> * Fix test too Signed-off-by: Miek Gieben <miek@miek.nl> * remove entire comment Signed-off-by: Miek Gieben <miek@miek.nl> * Implement the follow option, untested mind you Signed-off-by: Miek Gieben <miek@miek.nl> * update debug line Signed-off-by: Miek Gieben <miek@miek.nl> * internal/jounal: First batch of PR comments Signed-off-by: Miek Gieben <miek@miek.nl> * internal/journal: code review comments Signed-off-by: Miek Gieben <miek@miek.nl> * Manually apply suggestion Signed-off-by: Miek Gieben <miek@miek.nl> * apply comment manually Signed-off-by: Miek Gieben <miek@miek.nl> * internal/journal: add unit test Signed-off-by: Miek Gieben <miek@miek.nl> * Use testify Signed-off-by: Miek Gieben <miek@miek.nl> * -amFix scanner.Err checking Signed-off-by: Miek Gieben <miek@miek.nl> * Implement code review comments Signed-off-by: Miek Gieben <miek@miek.nl> --------- Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
package journal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const journalctl = "journalctl"
|
||||||
|
|
||||||
|
var commandContext = exec.CommandContext // overidable for the test
|
||||||
|
|
||||||
|
func logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (io.ReadCloser, error) {
|
||||||
|
args := []string{"-u", unit, "--no-hostname"}
|
||||||
|
args = append(args, "-n")
|
||||||
|
if opts.Tail > -1 {
|
||||||
|
args = append(args, fmt.Sprintf("%d", opts.Tail))
|
||||||
|
} else {
|
||||||
|
args = append(args, "all")
|
||||||
|
}
|
||||||
|
if opts.Follow {
|
||||||
|
args = append(args, "-f")
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, "-o")
|
||||||
|
args = append(args, "short-iso-precise")
|
||||||
|
|
||||||
|
if opts.Since != "" {
|
||||||
|
args = append(args, "-S")
|
||||||
|
args = append(args, opts.Since)
|
||||||
|
}
|
||||||
|
if opts.Until != "" {
|
||||||
|
args = append(args, "-U")
|
||||||
|
args = append(args, opts.Until)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := commandContext(ctx, journalctl, args...)
|
||||||
|
p, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// follow synchronously follows the io.Reader, writing each new journal entry to channel.
|
||||||
|
func follow(ctx context.Context, reader io.Reader, outCh chan api.LogEntry) error {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
select {
|
||||||
|
case outCh <- entry(scanner.Bytes()):
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
outCh <- api.LogEntry{Err: fmt.Errorf("journal logs: %w", err)}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package journal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logs streams logs from a service and returns entries via a channel.
|
||||||
|
func Logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (<-chan api.LogEntry, error) {
|
||||||
|
// Hard code unit check for now
|
||||||
|
switch unit {
|
||||||
|
case "uncloud":
|
||||||
|
case "corrosion":
|
||||||
|
case "docker":
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("journal logs: invalid unit: %s", unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := logs(ctx, unit, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outCh := make(chan api.LogEntry)
|
||||||
|
|
||||||
|
switch opts.Follow {
|
||||||
|
case false:
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(outCh)
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
outCh <- entry(scanner.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
outCh <- api.LogEntry{Err: fmt.Errorf("journal logs: %w", err)}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
case true:
|
||||||
|
go func() {
|
||||||
|
defer close(outCh)
|
||||||
|
|
||||||
|
err := follow(ctx, reader, outCh)
|
||||||
|
if err != nil {
|
||||||
|
outCh <- api.LogEntry{Err: fmt.Errorf("journal logs: %w", err)}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return outCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func entry(data []byte) api.LogEntry {
|
||||||
|
// 2025-10-12T11:03:27+02:00 systemd[1]:
|
||||||
|
timestamp := time.Time{}
|
||||||
|
message := data
|
||||||
|
if len(data) > 30 && data[4] == '-' && data[7] == '-' && data[10] == 'T' {
|
||||||
|
timestampPart, messagePart, found := bytes.Cut(data, []byte(" "))
|
||||||
|
var err error
|
||||||
|
if found {
|
||||||
|
timestamp, err = time.Parse(time.RFC3339Nano, string(timestampPart))
|
||||||
|
if err != nil {
|
||||||
|
timestamp = time.Time{}
|
||||||
|
}
|
||||||
|
message = messagePart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.LogEntry{
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Message: slices.Clone(message), // scanner controls the buffer
|
||||||
|
Stream: api.LogStreamStdout,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package journal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
commandContext = func(ctx context.Context, _ string, _ ...string) *exec.Cmd {
|
||||||
|
return exec.CommandContext(ctx, "/usr/bin/tail", "testdata/logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ch, err := Logs(ctx, "uncloud", api.ServiceLogsOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for range ch {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
assert.Equal(t, i, 6)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
commandContext = func(ctx context.Context, _ string, _ ...string) *exec.Cmd {
|
||||||
|
return exec.CommandContext(ctx, "/usr/bin/tail", "-f", "testdata/logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.Background()
|
||||||
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
|
go func() { time.Sleep(1 * time.Second); cancel() }()
|
||||||
|
|
||||||
|
ch, err = Logs(ctx, "uncloud", api.ServiceLogsOptions{Tail: 3})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for range ch {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
assert.Equal(t, i, 6) // still six is hardbeats are not written here.
|
||||||
|
}
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
2026-01-23T17:19:33.686964+01:00 fedora kernel: apple-dcp 271c00000.dcp: DCP index:1 dptx target phy: 5 dptx die: 0
|
||||||
|
2026-01-23T17:19:33.687155+01:00 fedora kernel: platform 271c00000.dcp:piodma: Adding to iommu group 9
|
||||||
|
2026-01-23T17:19:33.687343+01:00 fedora kernel: apple-dcp 271c00000.dcp: RTKit: Initializing (protocol version 12)
|
||||||
|
2026-01-23T17:19:33.687500+01:00 fedora kernel: apple-dcp 271c00000.dcp: shmem_setup: iova: 880255000 -> pa: be4f29000 -> iomem: ffff800082>
|
||||||
|
2026-01-23T17:19:33.687657+01:00 fedora kernel: apple-dcp 271c00000.dcp: shmem_setup: iova: ffffec000, buffer: ffff8000817cc000
|
||||||
|
2026-01-23T17:19:33.687826+01:00 fedora kernel: apple-dcp 271c00000.dcp: shmem_setup: iova: ffffe8000, buffer: ffff8000817d4000
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
status "google.golang.org/genproto/googleapis/rpc/status"
|
status "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
sync "sync"
|
sync "sync"
|
||||||
)
|
)
|
||||||
@@ -21,6 +22,58 @@ const (
|
|||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LogEntry_StreamType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogEntry_UNKNOWN LogEntry_StreamType = 0
|
||||||
|
LogEntry_STDOUT LogEntry_StreamType = 1
|
||||||
|
LogEntry_STDERR LogEntry_StreamType = 2
|
||||||
|
LogEntry_HEARTBEAT LogEntry_StreamType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for LogEntry_StreamType.
|
||||||
|
var (
|
||||||
|
LogEntry_StreamType_name = map[int32]string{
|
||||||
|
0: "UNKNOWN",
|
||||||
|
1: "STDOUT",
|
||||||
|
2: "STDERR",
|
||||||
|
3: "HEARTBEAT",
|
||||||
|
}
|
||||||
|
LogEntry_StreamType_value = map[string]int32{
|
||||||
|
"UNKNOWN": 0,
|
||||||
|
"STDOUT": 1,
|
||||||
|
"STDERR": 2,
|
||||||
|
"HEARTBEAT": 3,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x LogEntry_StreamType) Enum() *LogEntry_StreamType {
|
||||||
|
p := new(LogEntry_StreamType)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x LogEntry_StreamType) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LogEntry_StreamType) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_internal_machine_api_pb_common_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LogEntry_StreamType) Type() protoreflect.EnumType {
|
||||||
|
return &file_internal_machine_api_pb_common_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x LogEntry_StreamType) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LogEntry_StreamType.Descriptor instead.
|
||||||
|
func (LogEntry_StreamType) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{7, 0}
|
||||||
|
}
|
||||||
|
|
||||||
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
|
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
|
||||||
// about the machine that responded to the request.
|
// about the machine that responded to the request.
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
@@ -343,6 +396,150 @@ func (x *IPPrefix) GetBits() uint32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogsRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
// Options for logs retrieval.
|
||||||
|
Follow bool `protobuf:"varint,2,opt,name=follow,proto3" json:"follow,omitempty"`
|
||||||
|
Tail int32 `protobuf:"varint,3,opt,name=tail,proto3" json:"tail,omitempty"` // -1 means all
|
||||||
|
Since string `protobuf:"bytes,4,opt,name=since,proto3" json:"since,omitempty"` // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
Until string `protobuf:"bytes,5,opt,name=until,proto3" json:"until,omitempty"` // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) Reset() {
|
||||||
|
*x = LogsRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_internal_machine_api_pb_common_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_machine_api_pb_common_proto_msgTypes[6]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LogsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetFollow() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Follow
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetTail() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tail
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetSince() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Since
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogsRequest) GetUntil() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Until
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogEntry struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Stream LogEntry_StreamType `protobuf:"varint,1,opt,name=stream,proto3,enum=api.LogEntry_StreamType" json:"stream,omitempty"`
|
||||||
|
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
|
||||||
|
// Log line content. Empty for heartbeat entries.
|
||||||
|
Message []byte `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) Reset() {
|
||||||
|
*x = LogEntry{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_internal_machine_api_pb_common_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogEntry) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogEntry) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_internal_machine_api_pb_common_proto_msgTypes[7]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogEntry) Descriptor() ([]byte, []int) {
|
||||||
|
return file_internal_machine_api_pb_common_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) GetStream() LogEntry_StreamType {
|
||||||
|
if x != nil {
|
||||||
|
return x.Stream
|
||||||
|
}
|
||||||
|
return LogEntry_UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) GetTimestamp() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.Timestamp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogEntry) GetMessage() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.Message
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_internal_machine_api_pb_common_proto protoreflect.FileDescriptor
|
var File_internal_machine_api_pb_common_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_internal_machine_api_pb_common_proto_rawDesc = []byte{
|
var file_internal_machine_api_pb_common_proto_rawDesc = []byte{
|
||||||
@@ -350,32 +547,55 @@ var file_internal_machine_api_pb_common_proto_rawDesc = []byte{
|
|||||||
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f,
|
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x17, 0x67, 0x6f, 0x6f,
|
||||||
0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70,
|
0x67, 0x6c, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x22, 0x66, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
|
||||||
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e,
|
||||||
0x09, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72,
|
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x66, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||||
0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
|
0x61, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
|
0x28, 0x09, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65,
|
||||||
0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74,
|
0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
|
||||||
0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a, 0x05,
|
0x72, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
0x0b, 0x32, 0x12, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x53,
|
||||||
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65,
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x32, 0x0a,
|
||||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
|
0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
|
||||||
0x22, 0x37, 0x0a, 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d,
|
||||||
0x65, 0x12, 0x26, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20,
|
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
|
||||||
0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52,
|
0x61, 0x22, 0x37, 0x0a, 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50, 0x12,
|
0x73, 0x65, 0x12, 0x26, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01,
|
||||||
0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x22,
|
0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
0x35, 0x0a, 0x06, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18,
|
0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x14, 0x0a, 0x02, 0x49, 0x50,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02,
|
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70,
|
||||||
0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d,
|
0x22, 0x35, 0x0a, 0x06, 0x49, 0x50, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70,
|
||||||
0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52,
|
||||||
0x69, 0x78, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07,
|
0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x62,
|
0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x37, 0x0a, 0x08, 0x49, 0x50, 0x50, 0x72, 0x65,
|
||||||
0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x42,
|
0x66, 0x69, 0x78, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73,
|
0x07, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04,
|
||||||
0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
|
0x62, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73,
|
||||||
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
0x22, 0x75, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||||
0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
|
||||||
|
0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
|
0x06, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x18,
|
||||||
|
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x73,
|
||||||
|
0x69, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63,
|
||||||
|
0x65, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x22, 0xd2, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45,
|
||||||
|
0x6e, 0x74, 0x72, 0x79, 0x12, 0x30, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e,
|
||||||
|
0x74, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06,
|
||||||
|
0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||||
|
0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||||
|
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
||||||
|
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||||
|
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||||
|
0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x40, 0x0a, 0x0a, 0x53, 0x74,
|
||||||
|
0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,
|
||||||
|
0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x4f, 0x55, 0x54, 0x10,
|
||||||
|
0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x44, 0x45, 0x52, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a,
|
||||||
|
0x09, 0x48, 0x45, 0x41, 0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x03, 0x42, 0x37, 0x5a, 0x35,
|
||||||
|
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64,
|
||||||
|
0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e,
|
||||||
|
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61,
|
||||||
|
0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -390,27 +610,34 @@ func file_internal_machine_api_pb_common_proto_rawDescGZIP() []byte {
|
|||||||
return file_internal_machine_api_pb_common_proto_rawDescData
|
return file_internal_machine_api_pb_common_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_internal_machine_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
var file_internal_machine_api_pb_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_internal_machine_api_pb_common_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
var file_internal_machine_api_pb_common_proto_goTypes = []any{
|
var file_internal_machine_api_pb_common_proto_goTypes = []any{
|
||||||
(*Metadata)(nil), // 0: api.Metadata
|
(LogEntry_StreamType)(0), // 0: api.LogEntry.StreamType
|
||||||
(*Empty)(nil), // 1: api.Empty
|
(*Metadata)(nil), // 1: api.Metadata
|
||||||
(*EmptyResponse)(nil), // 2: api.EmptyResponse
|
(*Empty)(nil), // 2: api.Empty
|
||||||
(*IP)(nil), // 3: api.IP
|
(*EmptyResponse)(nil), // 3: api.EmptyResponse
|
||||||
(*IPPort)(nil), // 4: api.IPPort
|
(*IP)(nil), // 4: api.IP
|
||||||
(*IPPrefix)(nil), // 5: api.IPPrefix
|
(*IPPort)(nil), // 5: api.IPPort
|
||||||
(*status.Status)(nil), // 6: google.rpc.Status
|
(*IPPrefix)(nil), // 6: api.IPPrefix
|
||||||
|
(*LogsRequest)(nil), // 7: api.LogsRequest
|
||||||
|
(*LogEntry)(nil), // 8: api.LogEntry
|
||||||
|
(*status.Status)(nil), // 9: google.rpc.Status
|
||||||
|
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_internal_machine_api_pb_common_proto_depIdxs = []int32{
|
var file_internal_machine_api_pb_common_proto_depIdxs = []int32{
|
||||||
6, // 0: api.Metadata.status:type_name -> google.rpc.Status
|
9, // 0: api.Metadata.status:type_name -> google.rpc.Status
|
||||||
0, // 1: api.Empty.metadata:type_name -> api.Metadata
|
1, // 1: api.Empty.metadata:type_name -> api.Metadata
|
||||||
1, // 2: api.EmptyResponse.messages:type_name -> api.Empty
|
2, // 2: api.EmptyResponse.messages:type_name -> api.Empty
|
||||||
3, // 3: api.IPPort.ip:type_name -> api.IP
|
4, // 3: api.IPPort.ip:type_name -> api.IP
|
||||||
3, // 4: api.IPPrefix.ip:type_name -> api.IP
|
4, // 4: api.IPPrefix.ip:type_name -> api.IP
|
||||||
5, // [5:5] is the sub-list for method output_type
|
0, // 5: api.LogEntry.stream:type_name -> api.LogEntry.StreamType
|
||||||
5, // [5:5] is the sub-list for method input_type
|
10, // 6: api.LogEntry.timestamp:type_name -> google.protobuf.Timestamp
|
||||||
5, // [5:5] is the sub-list for extension type_name
|
7, // [7:7] is the sub-list for method output_type
|
||||||
5, // [5:5] is the sub-list for extension extendee
|
7, // [7:7] is the sub-list for method input_type
|
||||||
0, // [0:5] is the sub-list for field type_name
|
7, // [7:7] is the sub-list for extension type_name
|
||||||
|
7, // [7:7] is the sub-list for extension extendee
|
||||||
|
0, // [0:7] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_internal_machine_api_pb_common_proto_init() }
|
func init() { file_internal_machine_api_pb_common_proto_init() }
|
||||||
@@ -491,19 +718,44 @@ func file_internal_machine_api_pb_common_proto_init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
file_internal_machine_api_pb_common_proto_msgTypes[6].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*LogsRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_internal_machine_api_pb_common_proto_msgTypes[7].Exporter = func(v any, i int) any {
|
||||||
|
switch v := v.(*LogEntry); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_internal_machine_api_pb_common_proto_rawDesc,
|
RawDescriptor: file_internal_machine_api_pb_common_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 1,
|
||||||
NumMessages: 6,
|
NumMessages: 8,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
GoTypes: file_internal_machine_api_pb_common_proto_goTypes,
|
GoTypes: file_internal_machine_api_pb_common_proto_goTypes,
|
||||||
DependencyIndexes: file_internal_machine_api_pb_common_proto_depIdxs,
|
DependencyIndexes: file_internal_machine_api_pb_common_proto_depIdxs,
|
||||||
|
EnumInfos: file_internal_machine_api_pb_common_proto_enumTypes,
|
||||||
MessageInfos: file_internal_machine_api_pb_common_proto_msgTypes,
|
MessageInfos: file_internal_machine_api_pb_common_proto_msgTypes,
|
||||||
}.Build()
|
}.Build()
|
||||||
File_internal_machine_api_pb_common_proto = out.File
|
File_internal_machine_api_pb_common_proto = out.File
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
|||||||
|
|
||||||
// Vendored at internal/machine/api/vendor/google/rpc/status.proto.
|
// Vendored at internal/machine/api/vendor/google/rpc/status.proto.
|
||||||
import "google/rpc/status.proto";
|
import "google/rpc/status.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
|
// Common metadata message nested in all reply message types, injected by the gRPC proxy to provide information
|
||||||
// about the machine that responded to the request.
|
// about the machine that responded to the request.
|
||||||
@@ -42,3 +43,25 @@ message IPPrefix {
|
|||||||
IP ip = 1;
|
IP ip = 1;
|
||||||
uint32 bits = 2;
|
uint32 bits = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LogsRequest {
|
||||||
|
string id = 1;
|
||||||
|
// Options for logs retrieval.
|
||||||
|
bool follow = 2;
|
||||||
|
int32 tail = 3; // -1 means all
|
||||||
|
string since = 4; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
string until = 5; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
||||||
|
}
|
||||||
|
|
||||||
|
message LogEntry {
|
||||||
|
enum StreamType {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
STDOUT = 1;
|
||||||
|
STDERR = 2;
|
||||||
|
HEARTBEAT = 3;
|
||||||
|
}
|
||||||
|
StreamType stream = 1;
|
||||||
|
google.protobuf.Timestamp timestamp = 2;
|
||||||
|
// Log line content. Empty for heartbeat entries.
|
||||||
|
bytes message = 3;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ package api;
|
|||||||
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb";
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
|
||||||
import "internal/machine/api/pb/common.proto";
|
import "internal/machine/api/pb/common.proto";
|
||||||
|
|
||||||
service Docker {
|
service Docker {
|
||||||
@@ -17,7 +16,7 @@ service Docker {
|
|||||||
rpc RemoveContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
rpc RemoveContainer(RemoveContainerRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
rpc ExecContainer(stream ExecContainerRequest) returns (stream ExecContainerResponse);
|
rpc ExecContainer(stream ExecContainerRequest) returns (stream ExecContainerResponse);
|
||||||
rpc ContainerLogs(ContainerLogsRequest) returns (stream ContainerLogEntry);
|
rpc ContainerLogs(LogsRequest) returns (stream LogEntry);
|
||||||
|
|
||||||
rpc PullImage(PullImageRequest) returns (stream JSONMessage);
|
rpc PullImage(PullImageRequest) returns (stream JSONMessage);
|
||||||
rpc InspectImage(InspectImageRequest) returns (InspectImageResponse);
|
rpc InspectImage(InspectImageRequest) returns (InspectImageResponse);
|
||||||
@@ -132,28 +131,6 @@ message ExecContainerResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ContainerLogsRequest {
|
|
||||||
string container_id = 1;
|
|
||||||
// Options for logs retrieval.
|
|
||||||
bool follow = 2;
|
|
||||||
int32 tail = 3; // -1 means all
|
|
||||||
string since = 4; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
|
||||||
string until = 5; // https://www.rfc-editor.org/rfc/rfc3339.html timestamp or Go duration string
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContainerLogEntry {
|
|
||||||
enum StreamType {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
STDOUT = 1;
|
|
||||||
STDERR = 2;
|
|
||||||
HEARTBEAT = 3;
|
|
||||||
}
|
|
||||||
StreamType stream = 1;
|
|
||||||
google.protobuf.Timestamp timestamp = 2;
|
|
||||||
// Log line content. Empty for heartbeat entries.
|
|
||||||
bytes message = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PullImageRequest {
|
message PullImageRequest {
|
||||||
string image = 1;
|
string image = 1;
|
||||||
// JSON serialised image.PullOptions.
|
// JSON serialised image.PullOptions.
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ type DockerClient interface {
|
|||||||
ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error)
|
ListContainers(ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption) (*ListContainersResponse, error)
|
||||||
RemoveContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
RemoveContainer(ctx context.Context, in *RemoveContainerRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
ExecContainer(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse], error)
|
ExecContainer(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse], error)
|
||||||
ContainerLogs(ctx context.Context, in *ContainerLogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ContainerLogEntry], error)
|
ContainerLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error)
|
||||||
PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error)
|
PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error)
|
||||||
InspectImage(ctx context.Context, in *InspectImageRequest, opts ...grpc.CallOption) (*InspectImageResponse, error)
|
InspectImage(ctx context.Context, in *InspectImageRequest, opts ...grpc.CallOption) (*InspectImageResponse, error)
|
||||||
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
||||||
@@ -149,13 +149,13 @@ func (c *dockerClient) ExecContainer(ctx context.Context, opts ...grpc.CallOptio
|
|||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type Docker_ExecContainerClient = grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse]
|
type Docker_ExecContainerClient = grpc.BidiStreamingClient[ExecContainerRequest, ExecContainerResponse]
|
||||||
|
|
||||||
func (c *dockerClient) ContainerLogs(ctx context.Context, in *ContainerLogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ContainerLogEntry], error) {
|
func (c *dockerClient) ContainerLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &Docker_ServiceDesc.Streams[1], Docker_ContainerLogs_FullMethodName, cOpts...)
|
stream, err := c.cc.NewStream(ctx, &Docker_ServiceDesc.Streams[1], Docker_ContainerLogs_FullMethodName, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
x := &grpc.GenericClientStream[ContainerLogsRequest, ContainerLogEntry]{ClientStream: stream}
|
x := &grpc.GenericClientStream[LogsRequest, LogEntry]{ClientStream: stream}
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ func (c *dockerClient) ContainerLogs(ctx context.Context, in *ContainerLogsReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type Docker_ContainerLogsClient = grpc.ServerStreamingClient[ContainerLogEntry]
|
type Docker_ContainerLogsClient = grpc.ServerStreamingClient[LogEntry]
|
||||||
|
|
||||||
func (c *dockerClient) PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error) {
|
func (c *dockerClient) PullImage(ctx context.Context, in *PullImageRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[JSONMessage], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
@@ -298,7 +298,7 @@ type DockerServer interface {
|
|||||||
ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error)
|
ListContainers(context.Context, *ListContainersRequest) (*ListContainersResponse, error)
|
||||||
RemoveContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
RemoveContainer(context.Context, *RemoveContainerRequest) (*emptypb.Empty, error)
|
||||||
ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error
|
ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error
|
||||||
ContainerLogs(*ContainerLogsRequest, grpc.ServerStreamingServer[ContainerLogEntry]) error
|
ContainerLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error
|
||||||
PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error
|
PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error
|
||||||
InspectImage(context.Context, *InspectImageRequest) (*InspectImageResponse, error)
|
InspectImage(context.Context, *InspectImageRequest) (*InspectImageResponse, error)
|
||||||
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
// InspectRemoteImage returns the image metadata for an image in a remote registry using the machine's
|
||||||
@@ -343,7 +343,7 @@ func (UnimplementedDockerServer) RemoveContainer(context.Context, *RemoveContain
|
|||||||
func (UnimplementedDockerServer) ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error {
|
func (UnimplementedDockerServer) ExecContainer(grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method ExecContainer not implemented")
|
return status.Errorf(codes.Unimplemented, "method ExecContainer not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedDockerServer) ContainerLogs(*ContainerLogsRequest, grpc.ServerStreamingServer[ContainerLogEntry]) error {
|
func (UnimplementedDockerServer) ContainerLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method ContainerLogs not implemented")
|
return status.Errorf(codes.Unimplemented, "method ContainerLogs not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedDockerServer) PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error {
|
func (UnimplementedDockerServer) PullImage(*PullImageRequest, grpc.ServerStreamingServer[JSONMessage]) error {
|
||||||
@@ -516,15 +516,15 @@ func _Docker_ExecContainer_Handler(srv interface{}, stream grpc.ServerStream) er
|
|||||||
type Docker_ExecContainerServer = grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]
|
type Docker_ExecContainerServer = grpc.BidiStreamingServer[ExecContainerRequest, ExecContainerResponse]
|
||||||
|
|
||||||
func _Docker_ContainerLogs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _Docker_ContainerLogs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(ContainerLogsRequest)
|
m := new(LogsRequest)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return srv.(DockerServer).ContainerLogs(m, &grpc.GenericServerStream[ContainerLogsRequest, ContainerLogEntry]{ServerStream: stream})
|
return srv.(DockerServer).ContainerLogs(m, &grpc.GenericServerStream[LogsRequest, LogEntry]{ServerStream: stream})
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type Docker_ContainerLogsServer = grpc.ServerStreamingServer[ContainerLogEntry]
|
type Docker_ContainerLogsServer = grpc.ServerStreamingServer[LogEntry]
|
||||||
|
|
||||||
func _Docker_PullImage_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _Docker_PullImage_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(PullImageRequest)
|
m := new(PullImageRequest)
|
||||||
|
|||||||
@@ -1146,7 +1146,7 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
|
|||||||
0x03, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6d, 0x69, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73,
|
0x03, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6d, 0x69, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73,
|
||||||
0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x73, 0x18,
|
0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x73, 0x18,
|
||||||
0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70,
|
0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70,
|
||||||
0x73, 0x32, 0xe3, 0x04, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x4d, 0x0a,
|
0x73, 0x32, 0x95, 0x05, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x4d, 0x0a,
|
||||||
0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69,
|
0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69,
|
||||||
0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
0x74, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x61, 0x70,
|
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x61, 0x70,
|
||||||
@@ -1184,11 +1184,14 @@ var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{
|
|||||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76,
|
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76,
|
||||||
0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69,
|
0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x70, 0x69,
|
||||||
0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52,
|
0x2e, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75,
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x0b, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69,
|
0x6e, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x6f, 0x67,
|
||||||
0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
|
||||||
0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62,
|
0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x30, 0x01, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74,
|
||||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
|
||||||
|
0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
|
||||||
|
0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
||||||
|
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1227,6 +1230,8 @@ var file_internal_machine_api_pb_machine_proto_goTypes = []any{
|
|||||||
(*Metadata)(nil), // 19: api.Metadata
|
(*Metadata)(nil), // 19: api.Metadata
|
||||||
(*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp
|
(*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp
|
||||||
(*emptypb.Empty)(nil), // 21: google.protobuf.Empty
|
(*emptypb.Empty)(nil), // 21: google.protobuf.Empty
|
||||||
|
(*LogsRequest)(nil), // 22: api.LogsRequest
|
||||||
|
(*LogEntry)(nil), // 23: api.LogEntry
|
||||||
}
|
}
|
||||||
var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{
|
var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{
|
||||||
1, // 0: api.MachineInfo.network:type_name -> api.NetworkConfig
|
1, // 0: api.MachineInfo.network:type_name -> api.NetworkConfig
|
||||||
@@ -1256,17 +1261,19 @@ var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{
|
|||||||
21, // 24: api.Machine.InspectWireGuardNetwork:input_type -> google.protobuf.Empty
|
21, // 24: api.Machine.InspectWireGuardNetwork:input_type -> google.protobuf.Empty
|
||||||
9, // 25: api.Machine.Reset:input_type -> api.ResetRequest
|
9, // 25: api.Machine.Reset:input_type -> api.ResetRequest
|
||||||
11, // 26: api.Machine.InspectService:input_type -> api.InspectServiceRequest
|
11, // 26: api.Machine.InspectService:input_type -> api.InspectServiceRequest
|
||||||
2, // 27: api.Machine.CheckPrerequisites:output_type -> api.CheckPrerequisitesResponse
|
22, // 27: api.Machine.MachineLogs:input_type -> api.LogsRequest
|
||||||
4, // 28: api.Machine.InitCluster:output_type -> api.InitClusterResponse
|
2, // 28: api.Machine.CheckPrerequisites:output_type -> api.CheckPrerequisitesResponse
|
||||||
21, // 29: api.Machine.JoinCluster:output_type -> google.protobuf.Empty
|
4, // 29: api.Machine.InitCluster:output_type -> api.InitClusterResponse
|
||||||
8, // 30: api.Machine.Token:output_type -> api.TokenResponse
|
21, // 30: api.Machine.JoinCluster:output_type -> google.protobuf.Empty
|
||||||
0, // 31: api.Machine.Inspect:output_type -> api.MachineInfo
|
8, // 31: api.Machine.Token:output_type -> api.TokenResponse
|
||||||
6, // 32: api.Machine.InspectMachine:output_type -> api.InspectMachineResponse
|
0, // 32: api.Machine.Inspect:output_type -> api.MachineInfo
|
||||||
13, // 33: api.Machine.InspectWireGuardNetwork:output_type -> api.InspectWireGuardNetworkResponse
|
6, // 33: api.Machine.InspectMachine:output_type -> api.InspectMachineResponse
|
||||||
21, // 34: api.Machine.Reset:output_type -> google.protobuf.Empty
|
13, // 34: api.Machine.InspectWireGuardNetwork:output_type -> api.InspectWireGuardNetworkResponse
|
||||||
12, // 35: api.Machine.InspectService:output_type -> api.InspectServiceResponse
|
21, // 35: api.Machine.Reset:output_type -> google.protobuf.Empty
|
||||||
27, // [27:36] is the sub-list for method output_type
|
12, // 36: api.Machine.InspectService:output_type -> api.InspectServiceResponse
|
||||||
18, // [18:27] is the sub-list for method input_type
|
23, // 37: api.Machine.MachineLogs:output_type -> api.LogEntry
|
||||||
|
28, // [28:38] is the sub-list for method output_type
|
||||||
|
18, // [18:28] is the sub-list for method input_type
|
||||||
18, // [18:18] is the sub-list for extension type_name
|
18, // [18:18] is the sub-list for extension type_name
|
||||||
18, // [18:18] is the sub-list for extension extendee
|
18, // [18:18] is the sub-list for extension extendee
|
||||||
0, // [0:18] is the sub-list for field type_name
|
0, // [0:18] is the sub-list for field type_name
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ service Machine {
|
|||||||
rpc Reset(ResetRequest) returns (google.protobuf.Empty);
|
rpc Reset(ResetRequest) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
rpc InspectService(InspectServiceRequest) returns (InspectServiceResponse);
|
rpc InspectService(InspectServiceRequest) returns (InspectServiceResponse);
|
||||||
|
|
||||||
|
rpc MachineLogs(LogsRequest) returns (stream LogEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
message MachineInfo {
|
message MachineInfo {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const (
|
|||||||
Machine_InspectWireGuardNetwork_FullMethodName = "/api.Machine/InspectWireGuardNetwork"
|
Machine_InspectWireGuardNetwork_FullMethodName = "/api.Machine/InspectWireGuardNetwork"
|
||||||
Machine_Reset_FullMethodName = "/api.Machine/Reset"
|
Machine_Reset_FullMethodName = "/api.Machine/Reset"
|
||||||
Machine_InspectService_FullMethodName = "/api.Machine/InspectService"
|
Machine_InspectService_FullMethodName = "/api.Machine/InspectService"
|
||||||
|
Machine_MachineLogs_FullMethodName = "/api.Machine/MachineLogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MachineClient is the client API for Machine service.
|
// MachineClient is the client API for Machine service.
|
||||||
@@ -49,6 +50,7 @@ type MachineClient interface {
|
|||||||
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
|
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
|
||||||
Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error)
|
InspectService(ctx context.Context, in *InspectServiceRequest, opts ...grpc.CallOption) (*InspectServiceResponse, error)
|
||||||
|
MachineLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type machineClient struct {
|
type machineClient struct {
|
||||||
@@ -149,6 +151,25 @@ func (c *machineClient) InspectService(ctx context.Context, in *InspectServiceRe
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *machineClient) MachineLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogEntry], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &Machine_ServiceDesc.Streams[0], Machine_MachineLogs_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[LogsRequest, LogEntry]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type Machine_MachineLogsClient = grpc.ServerStreamingClient[LogEntry]
|
||||||
|
|
||||||
// MachineServer is the server API for Machine service.
|
// MachineServer is the server API for Machine service.
|
||||||
// All implementations must embed UnimplementedMachineServer
|
// All implementations must embed UnimplementedMachineServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -167,6 +188,7 @@ type MachineServer interface {
|
|||||||
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
|
// Reset restores the machine to a clean state, removing all cluster-related configuration and data.
|
||||||
Reset(context.Context, *ResetRequest) (*emptypb.Empty, error)
|
Reset(context.Context, *ResetRequest) (*emptypb.Empty, error)
|
||||||
InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error)
|
InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error)
|
||||||
|
MachineLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error
|
||||||
mustEmbedUnimplementedMachineServer()
|
mustEmbedUnimplementedMachineServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +226,9 @@ func (UnimplementedMachineServer) Reset(context.Context, *ResetRequest) (*emptyp
|
|||||||
func (UnimplementedMachineServer) InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error) {
|
func (UnimplementedMachineServer) InspectService(context.Context, *InspectServiceRequest) (*InspectServiceResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method InspectService not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method InspectService not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedMachineServer) MachineLogs(*LogsRequest, grpc.ServerStreamingServer[LogEntry]) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method MachineLogs not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedMachineServer) mustEmbedUnimplementedMachineServer() {}
|
func (UnimplementedMachineServer) mustEmbedUnimplementedMachineServer() {}
|
||||||
func (UnimplementedMachineServer) testEmbeddedByValue() {}
|
func (UnimplementedMachineServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@@ -387,6 +412,17 @@ func _Machine_InspectService_Handler(srv interface{}, ctx context.Context, dec f
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Machine_MachineLogs_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(LogsRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(MachineServer).MachineLogs(m, &grpc.GenericServerStream[LogsRequest, LogEntry]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type Machine_MachineLogsServer = grpc.ServerStreamingServer[LogEntry]
|
||||||
|
|
||||||
// Machine_ServiceDesc is the grpc.ServiceDesc for Machine service.
|
// Machine_ServiceDesc is the grpc.ServiceDesc for Machine service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -431,6 +467,12 @@ var Machine_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _Machine_InspectService_Handler,
|
Handler: _Machine_InspectService_Handler,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "MachineLogs",
|
||||||
|
Handler: _Machine_MachineLogs_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
Metadata: "internal/machine/api/pb/machine.proto",
|
Metadata: "internal/machine/api/pb/machine.proto",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1078,20 +1078,19 @@ const logsHeartbeatInterval = 200 * time.Millisecond
|
|||||||
|
|
||||||
// ContainerLogs streams logs from a container.
|
// ContainerLogs streams logs from a container.
|
||||||
func (s *Server) ContainerLogs(
|
func (s *Server) ContainerLogs(
|
||||||
req *pb.ContainerLogsRequest, stream grpc.ServerStreamingServer[pb.ContainerLogEntry],
|
req *pb.LogsRequest, stream grpc.ServerStreamingServer[pb.LogEntry],
|
||||||
) error {
|
) error {
|
||||||
// Stream context is cancelled when the client has disconnected or the stream has ended.
|
// Stream context is cancelled when the client has disconnected or the stream has ended.
|
||||||
ctx := stream.Context()
|
ctx := stream.Context()
|
||||||
|
|
||||||
opts := ContainerLogsOptions{
|
opts := api.ServiceLogsOptions{
|
||||||
ContainerID: req.ContainerId,
|
|
||||||
Follow: req.Follow,
|
Follow: req.Follow,
|
||||||
Tail: int(req.Tail),
|
Tail: int(req.Tail),
|
||||||
Since: req.Since,
|
Since: req.Since,
|
||||||
Until: req.Until,
|
Until: req.Until,
|
||||||
}
|
}
|
||||||
|
|
||||||
logsCh, err := s.service.ContainerLogs(ctx, opts)
|
logsCh, err := s.service.ContainerLogs(ctx, req.Id, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
return status.Error(codes.NotFound, err.Error())
|
return status.Error(codes.NotFound, err.Error())
|
||||||
@@ -1099,7 +1098,7 @@ func (s *Server) ContainerLogs(
|
|||||||
return status.Errorf(codes.Internal, "get container logs: %v", err)
|
return status.Errorf(codes.Internal, "get container logs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log := slog.With("container_id", req.ContainerId, "stream_id", fmt.Sprintf("%p", stream)[2:])
|
log := slog.With("container_id", req.Id, "stream_id", fmt.Sprintf("%p", stream)[2:])
|
||||||
log.Debug("Starting container logs streaming.",
|
log.Debug("Starting container logs streaming.",
|
||||||
"follow", req.Follow, "tail", req.Tail, "since", req.Since, "until", req.Until)
|
"follow", req.Follow, "tail", req.Tail, "since", req.Since, "until", req.Until)
|
||||||
|
|
||||||
@@ -1127,7 +1126,7 @@ func (s *Server) ContainerLogs(
|
|||||||
return status.Error(codes.Internal, entry.Err.Error())
|
return status.Error(codes.Internal, entry.Err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
pbEntry := &pb.ContainerLogEntry{
|
pbEntry := &pb.LogEntry{
|
||||||
Stream: api.LogStreamTypeToProto(entry.Stream),
|
Stream: api.LogStreamTypeToProto(entry.Stream),
|
||||||
Timestamp: timestamppb.New(entry.Timestamp),
|
Timestamp: timestamppb.New(entry.Timestamp),
|
||||||
Message: entry.Message,
|
Message: entry.Message,
|
||||||
@@ -1148,8 +1147,8 @@ func (s *Server) ContainerLogs(
|
|||||||
// Use the timestamp one heartbeat in the past to be conservative. This reduces the chance of sending
|
// Use the timestamp one heartbeat in the past to be conservative. This reduces the chance of sending
|
||||||
// a timestamp that is greater than a log entry currently being parsed but not yet sent, which would
|
// a timestamp that is greater than a log entry currently being parsed but not yet sent, which would
|
||||||
// cause the client to incorrectly believe it has received all logs up to that point.
|
// cause the client to incorrectly believe it has received all logs up to that point.
|
||||||
heartbeat := &pb.ContainerLogEntry{
|
heartbeat := &pb.LogEntry{
|
||||||
Stream: pb.ContainerLogEntry_HEARTBEAT,
|
Stream: pb.LogEntry_HEARTBEAT,
|
||||||
Timestamp: timestamppb.New(now.Add(-logsHeartbeatInterval)),
|
Timestamp: timestamppb.New(now.Add(-logsHeartbeatInterval)),
|
||||||
}
|
}
|
||||||
if err = stream.Send(heartbeat); err != nil {
|
if err = stream.Send(heartbeat); err != nil {
|
||||||
|
|||||||
@@ -155,18 +155,9 @@ func (s *Service) ListImages(ctx context.Context, opts image.ListOptions) (Image
|
|||||||
return imagesResp, nil
|
return imagesResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerLogsOptions specifies parameters for ContainerLogs.
|
|
||||||
type ContainerLogsOptions struct {
|
|
||||||
ContainerID string
|
|
||||||
Follow bool
|
|
||||||
Tail int
|
|
||||||
Since string
|
|
||||||
Until string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerLogs streams logs from a container and returns demultiplexed entries via a channel.
|
// ContainerLogs streams logs from a container and returns demultiplexed entries via a channel.
|
||||||
// The channel is closed when streaming completes or context is cancelled.
|
// The channel is closed when streaming completes or context is cancelled.
|
||||||
func (s *Service) ContainerLogs(ctx context.Context, opts ContainerLogsOptions) (<-chan api.ContainerLogEntry, error) {
|
func (s *Service) ContainerLogs(ctx context.Context, containerID string, opts api.ServiceLogsOptions) (<-chan api.LogEntry, error) {
|
||||||
dockerOpts := container.LogsOptions{
|
dockerOpts := container.LogsOptions{
|
||||||
ShowStdout: true,
|
ShowStdout: true,
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
@@ -177,12 +168,12 @@ func (s *Service) ContainerLogs(ctx context.Context, opts ContainerLogsOptions)
|
|||||||
Timestamps: true,
|
Timestamps: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := s.Client.ContainerLogs(ctx, opts.ContainerID, dockerOpts)
|
reader, err := s.Client.ContainerLogs(ctx, containerID, dockerOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
outCh := make(chan api.ContainerLogEntry)
|
outCh := make(chan api.LogEntry)
|
||||||
stdoutWriter := &logsChannelWriter{ctx: ctx, ch: outCh, isStderr: false}
|
stdoutWriter := &logsChannelWriter{ctx: ctx, ch: outCh, isStderr: false}
|
||||||
stderrWriter := &logsChannelWriter{ctx: ctx, ch: outCh, isStderr: true}
|
stderrWriter := &logsChannelWriter{ctx: ctx, ch: outCh, isStderr: true}
|
||||||
|
|
||||||
@@ -198,7 +189,7 @@ func (s *Service) ContainerLogs(ctx context.Context, opts ContainerLogsOptions)
|
|||||||
if _, err := stdcopy.StdCopy(stdoutWriter, stderrWriter, reader); err != nil {
|
if _, err := stdcopy.StdCopy(stdoutWriter, stderrWriter, reader); err != nil {
|
||||||
// Send error as the last entry.
|
// Send error as the last entry.
|
||||||
select {
|
select {
|
||||||
case outCh <- api.ContainerLogEntry{Err: fmt.Errorf("demultiplex container logs: %w", err)}:
|
case outCh <- api.LogEntry{Err: fmt.Errorf("demultiplex container logs: %w", err)}:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,7 +207,7 @@ func (s *Service) ContainerLogs(ctx context.Context, opts ContainerLogsOptions)
|
|||||||
// logsChannelWriter is a writer for stdcopy.StdCopy that sends demultiplexed container logs to a channel.
|
// logsChannelWriter is a writer for stdcopy.StdCopy that sends demultiplexed container logs to a channel.
|
||||||
type logsChannelWriter struct {
|
type logsChannelWriter struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
ch chan<- api.ContainerLogEntry
|
ch chan<- api.LogEntry
|
||||||
isStderr bool
|
isStderr bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +227,7 @@ func (w *logsChannelWriter) Write(data []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := api.ContainerLogEntry{
|
entry := api.LogEntry{
|
||||||
Timestamp: timestamp,
|
Timestamp: timestamp,
|
||||||
// Clone is required because message is a slice into data, which stdcopy.StdCopy may reuse
|
// Clone is required because message is a slice into data, which stdcopy.StdCopy may reuse
|
||||||
// after Write returns but before the entry is consumed from the channel.
|
// after Write returns but before the entry is consumed from the channel.
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/errdefs"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/psviderski/uncloud/internal/corrosion"
|
"github.com/psviderski/uncloud/internal/corrosion"
|
||||||
"github.com/psviderski/uncloud/internal/docker"
|
"github.com/psviderski/uncloud/internal/docker"
|
||||||
"github.com/psviderski/uncloud/internal/fs"
|
"github.com/psviderski/uncloud/internal/fs"
|
||||||
|
"github.com/psviderski/uncloud/internal/journal"
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
apiproxy "github.com/psviderski/uncloud/internal/machine/api/proxy"
|
apiproxy "github.com/psviderski/uncloud/internal/machine/api/proxy"
|
||||||
"github.com/psviderski/uncloud/internal/machine/caddyconfig"
|
"github.com/psviderski/uncloud/internal/machine/caddyconfig"
|
||||||
@@ -30,6 +33,7 @@ import (
|
|||||||
machinedocker "github.com/psviderski/uncloud/internal/machine/docker"
|
machinedocker "github.com/psviderski/uncloud/internal/machine/docker"
|
||||||
"github.com/psviderski/uncloud/internal/machine/network"
|
"github.com/psviderski/uncloud/internal/machine/network"
|
||||||
"github.com/psviderski/uncloud/internal/machine/store"
|
"github.com/psviderski/uncloud/internal/machine/store"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/unregistry"
|
"github.com/psviderski/unregistry"
|
||||||
"github.com/siderolabs/grpc-proxy/proxy"
|
"github.com/siderolabs/grpc-proxy/proxy"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -1073,3 +1077,93 @@ func (m *Machine) InspectService(
|
|||||||
}
|
}
|
||||||
return &pb.InspectServiceResponse{Service: svc}, nil
|
return &pb.InspectServiceResponse{Service: svc}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logsHeartbeatInterval is the interval at which heartbeat entries are sent when there are no logs to stream.
|
||||||
|
const logsHeartbeatInterval = 200 * time.Millisecond
|
||||||
|
|
||||||
|
// MachineLogs streams logs from a systemd service.
|
||||||
|
func (s *Machine) MachineLogs(
|
||||||
|
req *pb.LogsRequest, stream grpc.ServerStreamingServer[pb.LogEntry],
|
||||||
|
) error {
|
||||||
|
// TODO(miek): almost duplicate of docker/server.ContainerLogs
|
||||||
|
ctx := stream.Context()
|
||||||
|
|
||||||
|
opts := api.ServiceLogsOptions{
|
||||||
|
Follow: req.Follow,
|
||||||
|
Tail: int(req.Tail),
|
||||||
|
Since: req.Since,
|
||||||
|
Until: req.Until,
|
||||||
|
}
|
||||||
|
|
||||||
|
logsCh, err := journal.Logs(ctx, req.Id, opts)
|
||||||
|
if err != nil {
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
return status.Error(codes.NotFound, err.Error())
|
||||||
|
}
|
||||||
|
return status.Errorf(codes.Internal, "get journal logs: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := slog.With("unit", req.Id, "stream_id", fmt.Sprintf("%p", stream)[2:])
|
||||||
|
log.Debug("Starting systemd service logs streaming.",
|
||||||
|
"follow", req.Follow, "tail", req.Tail, "since", req.Since, "until", req.Until)
|
||||||
|
|
||||||
|
// Heartbeats are needed only when following logs to let the client know when there are no new log entries
|
||||||
|
// to allow it to advance the watermark of last received log timestamp.
|
||||||
|
var heartbeatCh <-chan time.Time
|
||||||
|
if req.Follow {
|
||||||
|
heartbeatTicker := time.NewTicker(logsHeartbeatInterval)
|
||||||
|
defer heartbeatTicker.Stop()
|
||||||
|
heartbeatCh = heartbeatTicker.C
|
||||||
|
}
|
||||||
|
|
||||||
|
started := time.Now()
|
||||||
|
lastSent := time.Time{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case entry, ok := <-logsCh:
|
||||||
|
if !ok {
|
||||||
|
// Channel closed, no more log entries.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Err != nil {
|
||||||
|
return status.Error(codes.Internal, entry.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pbEntry := &pb.LogEntry{
|
||||||
|
Stream: api.LogStreamTypeToProto(entry.Stream),
|
||||||
|
Timestamp: timestamppb.New(entry.Timestamp),
|
||||||
|
Message: entry.Message,
|
||||||
|
}
|
||||||
|
if err = stream.Send(pbEntry); err != nil {
|
||||||
|
return status.Errorf(codes.Internal, "send log entry: %v", err)
|
||||||
|
}
|
||||||
|
lastSent = entry.Timestamp
|
||||||
|
|
||||||
|
case now := <-heartbeatCh:
|
||||||
|
// Only send heartbeat if no log entries have been sent since the last heartbeat interval or
|
||||||
|
// if no log entries have been sent at all for at least a heartbeat interval since starting.
|
||||||
|
if now.Sub(lastSent) < logsHeartbeatInterval ||
|
||||||
|
(lastSent.IsZero() && now.Sub(started) < logsHeartbeatInterval) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the timestamp one heartbeat in the past to be conservative. This reduces the chance of sending
|
||||||
|
// a timestamp that is greater than a log entry currently being parsed but not yet sent, which would
|
||||||
|
// cause the client to incorrectly believe it has received all logs up to that point.
|
||||||
|
heartbeat := &pb.LogEntry{
|
||||||
|
Stream: pb.LogEntry_HEARTBEAT,
|
||||||
|
Timestamp: timestamppb.New(now.Add(-logsHeartbeatInterval)),
|
||||||
|
}
|
||||||
|
if err = stream.Send(heartbeat); err != nil {
|
||||||
|
return status.Errorf(codes.Internal, "send log stream heartbeat: %v", err)
|
||||||
|
}
|
||||||
|
lastSent = heartbeat.Timestamp.AsTime()
|
||||||
|
log.Debug("Sent log stream heartbeat.", "timestamp", lastSent)
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return status.Error(codes.Canceled, ctx.Err().Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+14
-14
@@ -18,31 +18,31 @@ const (
|
|||||||
|
|
||||||
type LogStreamType int
|
type LogStreamType int
|
||||||
|
|
||||||
// LogStreamTypeFromProto converts a protobuf ContainerLogEntry.StreamType to the internal LogStreamType.
|
// LogStreamTypeFromProto converts a protobuf LogEntry.StreamType to the internal LogStreamType.
|
||||||
func LogStreamTypeFromProto(s pb.ContainerLogEntry_StreamType) LogStreamType {
|
func LogStreamTypeFromProto(s pb.LogEntry_StreamType) LogStreamType {
|
||||||
switch s {
|
switch s {
|
||||||
case pb.ContainerLogEntry_STDOUT:
|
case pb.LogEntry_STDOUT:
|
||||||
return LogStreamStdout
|
return LogStreamStdout
|
||||||
case pb.ContainerLogEntry_STDERR:
|
case pb.LogEntry_STDERR:
|
||||||
return LogStreamStderr
|
return LogStreamStderr
|
||||||
case pb.ContainerLogEntry_HEARTBEAT:
|
case pb.LogEntry_HEARTBEAT:
|
||||||
return LogStreamHeartbeat
|
return LogStreamHeartbeat
|
||||||
default:
|
default:
|
||||||
return LogStreamUnknown
|
return LogStreamUnknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogStreamTypeToProto converts LogStreamType to protobuf ContainerLogEntry.StreamType.
|
// LogStreamTypeToProto converts LogStreamType to protobuf LogEntry.StreamType.
|
||||||
func LogStreamTypeToProto(s LogStreamType) pb.ContainerLogEntry_StreamType {
|
func LogStreamTypeToProto(s LogStreamType) pb.LogEntry_StreamType {
|
||||||
switch s {
|
switch s {
|
||||||
case LogStreamStdout:
|
case LogStreamStdout:
|
||||||
return pb.ContainerLogEntry_STDOUT
|
return pb.LogEntry_STDOUT
|
||||||
case LogStreamStderr:
|
case LogStreamStderr:
|
||||||
return pb.ContainerLogEntry_STDERR
|
return pb.LogEntry_STDERR
|
||||||
case LogStreamHeartbeat:
|
case LogStreamHeartbeat:
|
||||||
return pb.ContainerLogEntry_HEARTBEAT
|
return pb.LogEntry_HEARTBEAT
|
||||||
default:
|
default:
|
||||||
return pb.ContainerLogEntry_UNKNOWN
|
return pb.LogEntry_UNKNOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ type ServiceLogsOptions struct {
|
|||||||
type ServiceLogEntry struct {
|
type ServiceLogEntry struct {
|
||||||
// Metadata may not be set if an error occurred (Err is not nil).
|
// Metadata may not be set if an error occurred (Err is not nil).
|
||||||
Metadata ServiceLogEntryMetadata
|
Metadata ServiceLogEntryMetadata
|
||||||
ContainerLogEntry
|
LogEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceLogEntryMetadata contains metadata about the source of a log entry.
|
// ServiceLogEntryMetadata contains metadata about the source of a log entry.
|
||||||
@@ -73,8 +73,8 @@ type ServiceLogEntryMetadata struct {
|
|||||||
MachineName string
|
MachineName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerLogEntry represents a single log entry from a container.
|
// LogEntry represents a single log entry from a container or a service.
|
||||||
type ContainerLogEntry struct {
|
type LogEntry struct {
|
||||||
Stream LogStreamType
|
Stream LogStreamType
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
Message []byte
|
Message []byte
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ func (m *LogMerger) run() {
|
|||||||
|
|
||||||
for _, s := range stalled {
|
for _, s := range stalled {
|
||||||
errEntry := api.ServiceLogEntry{
|
errEntry := api.ServiceLogEntry{
|
||||||
ContainerLogEntry: api.ContainerLogEntry{
|
LogEntry: api.LogEntry{
|
||||||
Err: api.ErrLogStreamStalled,
|
Err: api.ErrLogStreamStalled,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
// testEntry creates a ServiceLogEntry for testing.
|
// testEntry creates a ServiceLogEntry for testing.
|
||||||
func testEntry(stream api.LogStreamType, ts time.Time, msg string) api.ServiceLogEntry {
|
func testEntry(stream api.LogStreamType, ts time.Time, msg string) api.ServiceLogEntry {
|
||||||
return api.ServiceLogEntry{
|
return api.ServiceLogEntry{
|
||||||
ContainerLogEntry: api.ContainerLogEntry{
|
LogEntry: api.LogEntry{
|
||||||
Stream: stream,
|
Stream: stream,
|
||||||
Timestamp: ts,
|
Timestamp: ts,
|
||||||
Message: []byte(msg),
|
Message: []byte(msg),
|
||||||
@@ -91,7 +91,7 @@ func TestLogMerger_PreservesData(t *testing.T) {
|
|||||||
|
|
||||||
e := api.ServiceLogEntry{
|
e := api.ServiceLogEntry{
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
ContainerLogEntry: api.ContainerLogEntry{
|
LogEntry: api.LogEntry{
|
||||||
Stream: api.LogStreamStdout,
|
Stream: api.LogStreamStdout,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
Message: []byte("test"),
|
Message: []byte("test"),
|
||||||
@@ -199,7 +199,7 @@ func TestLogMerger_ErrorForwarding(t *testing.T) {
|
|||||||
ch1 <- testEntry(api.LogStreamStdout, t1, "ch1-first")
|
ch1 <- testEntry(api.LogStreamStdout, t1, "ch1-first")
|
||||||
// Send an error entry.
|
// Send an error entry.
|
||||||
ch1 <- api.ServiceLogEntry{
|
ch1 <- api.ServiceLogEntry{
|
||||||
ContainerLogEntry: api.ContainerLogEntry{
|
LogEntry: api.LogEntry{
|
||||||
Err: assert.AnError,
|
Err: assert.AnError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -81,14 +81,14 @@ func (cli *Client) ServiceLogs(
|
|||||||
// ContainerLogs streams log entries from a single container on a specified machine.
|
// ContainerLogs streams log entries from a single container on a specified machine.
|
||||||
func (cli *Client) ContainerLogs(
|
func (cli *Client) ContainerLogs(
|
||||||
ctx context.Context, machineNameOrID string, containerID string, opts api.ServiceLogsOptions,
|
ctx context.Context, machineNameOrID string, containerID string, opts api.ServiceLogsOptions,
|
||||||
) (<-chan api.ContainerLogEntry, error) {
|
) (<-chan api.LogEntry, error) {
|
||||||
proxyCtx, _, err := cli.ProxyMachinesContext(ctx, []string{machineNameOrID})
|
proxyCtx, _, err := cli.ProxyMachinesContext(ctx, []string{machineNameOrID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create request context to proxy to machine '%s': %w", machineNameOrID, err)
|
return nil, fmt.Errorf("create request context to proxy to machine '%s': %w", machineNameOrID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &pb.ContainerLogsRequest{
|
req := &pb.LogsRequest{
|
||||||
ContainerId: containerID,
|
Id: containerID,
|
||||||
Follow: opts.Follow,
|
Follow: opts.Follow,
|
||||||
Tail: int32(opts.Tail),
|
Tail: int32(opts.Tail),
|
||||||
Since: opts.Since,
|
Since: opts.Since,
|
||||||
@@ -105,7 +105,7 @@ func (cli *Client) ContainerLogs(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan api.ContainerLogEntry)
|
ch := make(chan api.LogEntry)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(ch)
|
defer close(ch)
|
||||||
@@ -116,13 +116,13 @@ func (cli *Client) ContainerLogs(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- api.ContainerLogEntry{
|
ch <- api.LogEntry{
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := api.ContainerLogEntry{
|
entry := api.LogEntry{
|
||||||
Stream: api.LogStreamTypeFromProto(pbEntry.Stream),
|
Stream: api.LogStreamTypeFromProto(pbEntry.Stream),
|
||||||
Message: pbEntry.Message,
|
Message: pbEntry.Message,
|
||||||
Timestamp: pbEntry.Timestamp.AsTime(),
|
Timestamp: pbEntry.Timestamp.AsTime(),
|
||||||
@@ -141,7 +141,7 @@ func (cli *Client) ContainerLogs(
|
|||||||
|
|
||||||
// logsStreamWithServiceMetadata wraps a container logs stream and enriches each log entry with service metadata.
|
// logsStreamWithServiceMetadata wraps a container logs stream and enriches each log entry with service metadata.
|
||||||
func logsStreamWithServiceMetadata(
|
func logsStreamWithServiceMetadata(
|
||||||
stream <-chan api.ContainerLogEntry, metadata api.ServiceLogEntryMetadata,
|
stream <-chan api.LogEntry, metadata api.ServiceLogEntryMetadata,
|
||||||
) <-chan api.ServiceLogEntry {
|
) <-chan api.ServiceLogEntry {
|
||||||
out := make(chan api.ServiceLogEntry)
|
out := make(chan api.ServiceLogEntry)
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ func logsStreamWithServiceMetadata(
|
|||||||
for entry := range stream {
|
for entry := range stream {
|
||||||
out <- api.ServiceLogEntry{
|
out <- api.ServiceLogEntry{
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
ContainerLogEntry: entry,
|
LogEntry: entry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(out)
|
close(out)
|
||||||
|
|||||||
Reference in New Issue
Block a user