Report custom labels set by agent admins back (#4141)

This commit is contained in:
6543 2024-10-06 17:13:41 +02:00 committed by GitHub
parent 0f7b607a40
commit f8cfda1ea9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 441 additions and 234 deletions

View file

@ -480,12 +480,15 @@ func (c *client) sendLogs(ctx context.Context, entries []*proto.LogEntry) error
return nil
}
func (c *client) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int) (int64, error) {
func (c *client) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) {
req := new(proto.RegisterAgentRequest)
req.Platform = platform
req.Backend = backend
req.Version = version
req.Capacity = int32(capacity)
req.Info = &proto.AgentInfo{
Platform: info.Platform,
Backend: info.Backend,
Version: info.Version,
Capacity: int32(info.Capacity),
CustomLabels: info.CustomLabels,
}
res, err := c.client.RegisterAgent(ctx, req)
return res.GetAgentId(), err

View file

@ -20,6 +20,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"maps"
"net/http"
"os"
"strings"
@ -198,7 +199,22 @@ func run(ctx context.Context, c *cli.Command, backends []types.Backend) error {
log.Debug().Msgf("loaded %s backend engine", backendEngine.Name())
maxWorkflows := int(c.Int("max-workflows"))
agentConfig.AgentID, err = client.RegisterAgent(grpcCtx, engInfo.Platform, backendEngine.Name(), version.String(), maxWorkflows) //nolint:contextcheck
customLabels := make(map[string]string)
if err := stringSliceAddToMap(c.StringSlice("labels"), customLabels); err != nil {
return err
}
if len(customLabels) != 0 {
log.Debug().Msgf("custom labels detected: %#v", customLabels)
}
agentConfig.AgentID, err = client.RegisterAgent(grpcCtx, rpc.AgentInfo{ //nolint:contextcheck
Version: version.String(),
Backend: backendEngine.Name(),
Platform: engInfo.Platform,
Capacity: maxWorkflows,
CustomLabels: customLabels,
})
if err != nil {
return err
}
@ -210,7 +226,7 @@ func run(ctx context.Context, c *cli.Command, backends []types.Backend) error {
<-agentCtx.Done()
// Remove stateless agents from server
if !agentConfigPersisted.Load() {
log.Debug().Msg("unregistering agent from server ...")
log.Debug().Msg("unregister agent from server ...")
// we want to run it explicit run when context got canceled so run it in background
err := client.UnregisterAgent(grpcClientCtx)
if err != nil {
@ -228,16 +244,15 @@ func run(ctx context.Context, c *cli.Command, backends []types.Backend) error {
}
}
// set default labels ...
labels := map[string]string{
"hostname": hostname,
"platform": engInfo.Platform,
"backend": backendEngine.Name(),
"repo": "*", // allow all repos by default
}
if err := stringSliceAddToMap(c.StringSlice("filter"), labels); err != nil {
return err
}
// ... and let it overwrite by custom ones
maps.Copy(labels, customLabels)
log.Debug().Any("labels", labels).Msgf("agent configured with labels")

View file

@ -60,8 +60,9 @@ var flags = []cli.Flag{
Value: "/etc/woodpecker/agent.conf",
},
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_FILTER_LABELS"),
Name: "filter",
Sources: cli.EnvVars("WOODPECKER_AGENT_LABELS", "WOODPECKER_FILTER_LABELS"), // remove WOODPECKER_FILTER_LABELS in v4.x
Name: "labels",
Aliases: []string{"filter"}, // remove in v4.x
Usage: "List of labels to filter tasks on. An agent must be assigned every tag listed in a task to be selected.",
},
&cli.IntFlag{

View file

@ -4589,6 +4589,12 @@ const docTemplate = `{
"created": {
"type": "integer"
},
"custom_labels": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"id": {
"type": "integer"
},

View file

@ -610,7 +610,7 @@ For more details check the [matrix build docs](./30-matrix-workflows.md).
You can set labels for your workflow to select an agent to execute the workflow on. An agent will pick up and run a workflow when **every** label assigned to it matches the agents labels.
To set additional agent labels, check the [agent configuration options](../30-administration/15-agent-config.md#woodpecker_filter_labels). Agents will have at least four default labels: `platform=agent-os/agent-arch`, `hostname=my-agent`, `backend=docker` (type of the agent backend) and `repo=*`. Agents can use a `*` as a wildcard for a label. For example `repo=*` will match every repo.
To set additional agent labels, check the [agent configuration options](../30-administration/15-agent-config.md#woodpecker_agent_labels). Agents will have at least four default labels: `platform=agent-os/agent-arch`, `hostname=my-agent`, `backend=docker` (type of the agent backend) and `repo=*`. Agents can use a `*` as a wildcard for a label. For example `repo=*` will match every repo.
Workflow labels with an empty value will be ignored.
By default, each workflow has at least the `repo=your-user/your-repo-name` label. If you have set the [platform attribute](#platform) for your workflow it will have a label like `platform=your-os/your-arch` as well.

View file

@ -120,11 +120,14 @@ Configures the path of the agent config file.
Configures the number of parallel workflows.
### `WOODPECKER_FILTER_LABELS`
### `WOODPECKER_AGENT_LABELS`
> Default: empty
Configures labels to filter pipeline pick up. Use a list of key-value pairs like `key=value,second-key=*`. `*` can be used as a wildcard. By default, agents provide three additional labels `platform=os/arch`, `hostname=my-agent` and `repo=*` which can be overwritten if needed. To learn how labels work, check out the [pipeline syntax page](../20-usage/20-workflow-syntax.md#labels).
Configures custom labels for the agent, to let workflows filter by it.
Use a list of key-value pairs like `key=value,second-key=*`. `*` can be used as a wildcard.
By default, agents provide three additional labels `platform=os/arch`, `hostname=my-agent` and `repo=*` which can be overwritten if needed.
To learn how labels work, check out the [pipeline syntax page](../20-usage/20-workflow-syntax.md#labels).
### `WOODPECKER_HEALTHCHECK`

View file

@ -4,6 +4,7 @@ Some versions need some changes to the server configuration or the pipeline conf
## `next`
- Deprecate `WOODPECKER_FILTER_LABELS` use `WOODPECKER_AGENT_LABELS`
- Removed built-in environment variables:
- `CI_COMMIT_URL` use `CI_PIPELINE_FORGE_URL`
- `CI_STEP_FINISHED` as empty during execution

View file

@ -106,9 +106,9 @@ func (_m *Peer) Next(c context.Context, f rpc.Filter) (*rpc.Workflow, error) {
return r0, r1
}
// RegisterAgent provides a mock function with given fields: ctx, platform, backend, version, capacity
func (_m *Peer) RegisterAgent(ctx context.Context, platform string, backend string, version string, capacity int) (int64, error) {
ret := _m.Called(ctx, platform, backend, version, capacity)
// RegisterAgent provides a mock function with given fields: ctx, info
func (_m *Peer) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) {
ret := _m.Called(ctx, info)
if len(ret) == 0 {
panic("no return value specified for RegisterAgent")
@ -116,17 +116,17 @@ func (_m *Peer) RegisterAgent(ctx context.Context, platform string, backend stri
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, int) (int64, error)); ok {
return rf(ctx, platform, backend, version, capacity)
if rf, ok := ret.Get(0).(func(context.Context, rpc.AgentInfo) (int64, error)); ok {
return rf(ctx, info)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, int) int64); ok {
r0 = rf(ctx, platform, backend, version, capacity)
if rf, ok := ret.Get(0).(func(context.Context, rpc.AgentInfo) int64); ok {
r0 = rf(ctx, info)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, int) error); ok {
r1 = rf(ctx, platform, backend, version, capacity)
if rf, ok := ret.Get(1).(func(context.Context, rpc.AgentInfo) error); ok {
r1 = rf(ctx, info)
} else {
r1 = ret.Error(1)
}

View file

@ -55,6 +55,15 @@ type (
GrpcVersion int32 `json:"grpc_version,omitempty"`
ServerVersion string `json:"server_version,omitempty"`
}
// AgentInfo represents all the metadata that should be known about an agent.
AgentInfo struct {
Version string `json:"version"`
Platform string `json:"platform"`
Backend string `json:"backend"`
Capacity int `json:"capacity"`
CustomLabels map[string]string `json:"custom_labels"`
}
)
//go:generate mockery --name Peer --output mocks --case underscore --note "+build test"
@ -86,7 +95,7 @@ type Peer interface {
EnqueueLog(logEntry *LogEntry)
// RegisterAgent register our agent to the server
RegisterAgent(ctx context.Context, platform, backend, version string, capacity int) (int64, error)
RegisterAgent(ctx context.Context, info AgentInfo) (int64, error)
// UnregisterAgent unregister our agent from the server
UnregisterAgent(ctx context.Context) error

View file

@ -16,4 +16,4 @@ package proto
// Version is the version of the woodpecker.proto file,
// IMPORTANT: increased by 1 each time it get changed.
const Version int32 = 10
const Version int32 = 11

View file

@ -812,21 +812,97 @@ func (x *ReportHealthRequest) GetStatus() string {
return ""
}
type AgentInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"`
Capacity int32 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"`
Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"`
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
CustomLabels map[string]string `protobuf:"bytes,5,rep,name=customLabels,proto3" json:"customLabels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *AgentInfo) Reset() {
*x = AgentInfo{}
if protoimpl.UnsafeEnabled {
mi := &file_woodpecker_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AgentInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AgentInfo) ProtoMessage() {}
func (x *AgentInfo) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[14]
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 AgentInfo.ProtoReflect.Descriptor instead.
func (*AgentInfo) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{14}
}
func (x *AgentInfo) GetPlatform() string {
if x != nil {
return x.Platform
}
return ""
}
func (x *AgentInfo) GetCapacity() int32 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *AgentInfo) GetBackend() string {
if x != nil {
return x.Backend
}
return ""
}
func (x *AgentInfo) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
func (x *AgentInfo) GetCustomLabels() map[string]string {
if x != nil {
return x.CustomLabels
}
return nil
}
type RegisterAgentRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"`
Capacity int32 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"`
Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"`
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
Info *AgentInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"`
}
func (x *RegisterAgentRequest) Reset() {
*x = RegisterAgentRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_woodpecker_proto_msgTypes[14]
mi := &file_woodpecker_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -839,7 +915,7 @@ func (x *RegisterAgentRequest) String() string {
func (*RegisterAgentRequest) ProtoMessage() {}
func (x *RegisterAgentRequest) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[14]
mi := &file_woodpecker_proto_msgTypes[15]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -852,35 +928,14 @@ func (x *RegisterAgentRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RegisterAgentRequest.ProtoReflect.Descriptor instead.
func (*RegisterAgentRequest) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{14}
return file_woodpecker_proto_rawDescGZIP(), []int{15}
}
func (x *RegisterAgentRequest) GetPlatform() string {
func (x *RegisterAgentRequest) GetInfo() *AgentInfo {
if x != nil {
return x.Platform
return x.Info
}
return ""
}
func (x *RegisterAgentRequest) GetCapacity() int32 {
if x != nil {
return x.Capacity
}
return 0
}
func (x *RegisterAgentRequest) GetBackend() string {
if x != nil {
return x.Backend
}
return ""
}
func (x *RegisterAgentRequest) GetVersion() string {
if x != nil {
return x.Version
}
return ""
return nil
}
type VersionResponse struct {
@ -895,7 +950,7 @@ type VersionResponse struct {
func (x *VersionResponse) Reset() {
*x = VersionResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_woodpecker_proto_msgTypes[15]
mi := &file_woodpecker_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -908,7 +963,7 @@ func (x *VersionResponse) String() string {
func (*VersionResponse) ProtoMessage() {}
func (x *VersionResponse) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[15]
mi := &file_woodpecker_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -921,7 +976,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead.
func (*VersionResponse) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{15}
return file_woodpecker_proto_rawDescGZIP(), []int{16}
}
func (x *VersionResponse) GetGrpcVersion() int32 {
@ -949,7 +1004,7 @@ type NextResponse struct {
func (x *NextResponse) Reset() {
*x = NextResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_woodpecker_proto_msgTypes[16]
mi := &file_woodpecker_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -962,7 +1017,7 @@ func (x *NextResponse) String() string {
func (*NextResponse) ProtoMessage() {}
func (x *NextResponse) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[16]
mi := &file_woodpecker_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -975,7 +1030,7 @@ func (x *NextResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use NextResponse.ProtoReflect.Descriptor instead.
func (*NextResponse) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{16}
return file_woodpecker_proto_rawDescGZIP(), []int{17}
}
func (x *NextResponse) GetWorkflow() *Workflow {
@ -996,7 +1051,7 @@ type RegisterAgentResponse struct {
func (x *RegisterAgentResponse) Reset() {
*x = RegisterAgentResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_woodpecker_proto_msgTypes[17]
mi := &file_woodpecker_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1009,7 +1064,7 @@ func (x *RegisterAgentResponse) String() string {
func (*RegisterAgentResponse) ProtoMessage() {}
func (x *RegisterAgentResponse) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[17]
mi := &file_woodpecker_proto_msgTypes[18]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1022,7 +1077,7 @@ func (x *RegisterAgentResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RegisterAgentResponse.ProtoReflect.Descriptor instead.
func (*RegisterAgentResponse) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{17}
return file_woodpecker_proto_rawDescGZIP(), []int{18}
}
func (x *RegisterAgentResponse) GetAgentId() int64 {
@ -1044,7 +1099,7 @@ type AuthRequest struct {
func (x *AuthRequest) Reset() {
*x = AuthRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_woodpecker_proto_msgTypes[18]
mi := &file_woodpecker_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1057,7 +1112,7 @@ func (x *AuthRequest) String() string {
func (*AuthRequest) ProtoMessage() {}
func (x *AuthRequest) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[18]
mi := &file_woodpecker_proto_msgTypes[19]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1070,7 +1125,7 @@ func (x *AuthRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthRequest.ProtoReflect.Descriptor instead.
func (*AuthRequest) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{18}
return file_woodpecker_proto_rawDescGZIP(), []int{19}
}
func (x *AuthRequest) GetAgentToken() string {
@ -1100,7 +1155,7 @@ type AuthResponse struct {
func (x *AuthResponse) Reset() {
*x = AuthResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_woodpecker_proto_msgTypes[19]
mi := &file_woodpecker_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -1113,7 +1168,7 @@ func (x *AuthResponse) String() string {
func (*AuthResponse) ProtoMessage() {}
func (x *AuthResponse) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[19]
mi := &file_woodpecker_proto_msgTypes[20]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1126,7 +1181,7 @@ func (x *AuthResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthResponse.ProtoReflect.Descriptor instead.
func (*AuthResponse) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{19}
return file_woodpecker_proto_rawDescGZIP(), []int{20}
}
func (x *AuthResponse) GetStatus() string {
@ -1220,83 +1275,95 @@ var file_woodpecker_proto_rawDesc = []byte{
0x22, 0x2d, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
0x82, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74,
0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74,
0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x70, 0x63, 0x5f,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x67,
0x72, 0x70, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x22, 0x3b, 0x0a, 0x0c, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x2b, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x6f, 0x72, 0x6b,
0x66, 0x6c, 0x6f, 0x77, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x32,
0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x49, 0x64, 0x22, 0x49, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x64, 0x0a,
0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64,
0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f,
0x6b, 0x65, 0x6e, 0x32, 0xbb, 0x04, 0x0a, 0x0a, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b,
0x65, 0x72, 0x12, 0x31, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0c, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x04, 0x4e, 0x65, 0x78, 0x74, 0x12, 0x12, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74,
0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04, 0x57, 0x61, 0x69, 0x74, 0x12, 0x12, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x80, 0x02, 0x0a, 0x09, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a,
0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70,
0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70,
0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12,
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x0c, 0x63, 0x75, 0x73,
0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66,
0x6f, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c,
0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
0x38, 0x01, 0x22, 0x3c, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x69, 0x6e,
0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f,
0x22, 0x5b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x67, 0x72, 0x70, 0x63, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a,
0x0c, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a,
0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x32, 0x0a, 0x15, 0x52, 0x65,
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x49,
0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x19,
0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x64, 0x0a, 0x0c, 0x41, 0x75, 0x74,
0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c,
0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32,
0xbb, 0x04, 0x0a, 0x0a, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x31,
0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x12, 0x31, 0x0a, 0x04, 0x4e, 0x65, 0x78, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x12, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,
0x12, 0x2a, 0x0a, 0x04, 0x44, 0x6f, 0x6e, 0x65, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x44, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06,
0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45,
0x78, 0x74, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x28, 0x0a, 0x03,
0x4c, 0x6f, 0x67, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x67,
0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x0f, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d,
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48,
0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65,
0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
0x00, 0x32, 0x43, 0x0a, 0x0e, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x41,
0x75, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x12, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x6f, 0x2e, 0x77, 0x6f, 0x6f,
0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x2d, 0x63, 0x69, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77,
0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x69, 0x70,
0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x12, 0x2a, 0x0a, 0x04, 0x57, 0x61, 0x69, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04,
0x44, 0x6f, 0x6e, 0x65, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x6f, 0x6e,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06, 0x45, 0x78, 0x74, 0x65,
0x6e, 0x64, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e,
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x28, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12,
0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x2f, 0x0a, 0x0f, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74,
0x68, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x43, 0x0a,
0x0e, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x12,
0x31, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x6f, 0x2e, 0x77, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63,
0x6b, 0x65, 0x72, 0x2d, 0x63, 0x69, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x6f, 0x6f, 0x64, 0x70,
0x65, 0x63, 0x6b, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e,
0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
@ -1311,7 +1378,7 @@ func file_woodpecker_proto_rawDescGZIP() []byte {
return file_woodpecker_proto_rawDescData
}
var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
var file_woodpecker_proto_goTypes = []interface{}{
(*StepState)(nil), // 0: proto.StepState
(*WorkflowState)(nil), // 1: proto.WorkflowState
@ -1327,51 +1394,55 @@ var file_woodpecker_proto_goTypes = []interface{}{
(*LogRequest)(nil), // 11: proto.LogRequest
(*Empty)(nil), // 12: proto.Empty
(*ReportHealthRequest)(nil), // 13: proto.ReportHealthRequest
(*RegisterAgentRequest)(nil), // 14: proto.RegisterAgentRequest
(*VersionResponse)(nil), // 15: proto.VersionResponse
(*NextResponse)(nil), // 16: proto.NextResponse
(*RegisterAgentResponse)(nil), // 17: proto.RegisterAgentResponse
(*AuthRequest)(nil), // 18: proto.AuthRequest
(*AuthResponse)(nil), // 19: proto.AuthResponse
nil, // 20: proto.Filter.LabelsEntry
(*AgentInfo)(nil), // 14: proto.AgentInfo
(*RegisterAgentRequest)(nil), // 15: proto.RegisterAgentRequest
(*VersionResponse)(nil), // 16: proto.VersionResponse
(*NextResponse)(nil), // 17: proto.NextResponse
(*RegisterAgentResponse)(nil), // 18: proto.RegisterAgentResponse
(*AuthRequest)(nil), // 19: proto.AuthRequest
(*AuthResponse)(nil), // 20: proto.AuthResponse
nil, // 21: proto.Filter.LabelsEntry
nil, // 22: proto.AgentInfo.CustomLabelsEntry
}
var file_woodpecker_proto_depIdxs = []int32{
20, // 0: proto.Filter.labels:type_name -> proto.Filter.LabelsEntry
21, // 0: proto.Filter.labels:type_name -> proto.Filter.LabelsEntry
3, // 1: proto.NextRequest.filter:type_name -> proto.Filter
1, // 2: proto.InitRequest.state:type_name -> proto.WorkflowState
1, // 3: proto.DoneRequest.state:type_name -> proto.WorkflowState
0, // 4: proto.UpdateRequest.state:type_name -> proto.StepState
2, // 5: proto.LogRequest.logEntries:type_name -> proto.LogEntry
4, // 6: proto.NextResponse.workflow:type_name -> proto.Workflow
12, // 7: proto.Woodpecker.Version:input_type -> proto.Empty
5, // 8: proto.Woodpecker.Next:input_type -> proto.NextRequest
6, // 9: proto.Woodpecker.Init:input_type -> proto.InitRequest
7, // 10: proto.Woodpecker.Wait:input_type -> proto.WaitRequest
8, // 11: proto.Woodpecker.Done:input_type -> proto.DoneRequest
9, // 12: proto.Woodpecker.Extend:input_type -> proto.ExtendRequest
10, // 13: proto.Woodpecker.Update:input_type -> proto.UpdateRequest
11, // 14: proto.Woodpecker.Log:input_type -> proto.LogRequest
14, // 15: proto.Woodpecker.RegisterAgent:input_type -> proto.RegisterAgentRequest
12, // 16: proto.Woodpecker.UnregisterAgent:input_type -> proto.Empty
13, // 17: proto.Woodpecker.ReportHealth:input_type -> proto.ReportHealthRequest
18, // 18: proto.WoodpeckerAuth.Auth:input_type -> proto.AuthRequest
15, // 19: proto.Woodpecker.Version:output_type -> proto.VersionResponse
16, // 20: proto.Woodpecker.Next:output_type -> proto.NextResponse
12, // 21: proto.Woodpecker.Init:output_type -> proto.Empty
12, // 22: proto.Woodpecker.Wait:output_type -> proto.Empty
12, // 23: proto.Woodpecker.Done:output_type -> proto.Empty
12, // 24: proto.Woodpecker.Extend:output_type -> proto.Empty
12, // 25: proto.Woodpecker.Update:output_type -> proto.Empty
12, // 26: proto.Woodpecker.Log:output_type -> proto.Empty
17, // 27: proto.Woodpecker.RegisterAgent:output_type -> proto.RegisterAgentResponse
12, // 28: proto.Woodpecker.UnregisterAgent:output_type -> proto.Empty
12, // 29: proto.Woodpecker.ReportHealth:output_type -> proto.Empty
19, // 30: proto.WoodpeckerAuth.Auth:output_type -> proto.AuthResponse
19, // [19:31] is the sub-list for method output_type
7, // [7:19] is the sub-list for method input_type
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
22, // 6: proto.AgentInfo.customLabels:type_name -> proto.AgentInfo.CustomLabelsEntry
14, // 7: proto.RegisterAgentRequest.info:type_name -> proto.AgentInfo
4, // 8: proto.NextResponse.workflow:type_name -> proto.Workflow
12, // 9: proto.Woodpecker.Version:input_type -> proto.Empty
5, // 10: proto.Woodpecker.Next:input_type -> proto.NextRequest
6, // 11: proto.Woodpecker.Init:input_type -> proto.InitRequest
7, // 12: proto.Woodpecker.Wait:input_type -> proto.WaitRequest
8, // 13: proto.Woodpecker.Done:input_type -> proto.DoneRequest
9, // 14: proto.Woodpecker.Extend:input_type -> proto.ExtendRequest
10, // 15: proto.Woodpecker.Update:input_type -> proto.UpdateRequest
11, // 16: proto.Woodpecker.Log:input_type -> proto.LogRequest
15, // 17: proto.Woodpecker.RegisterAgent:input_type -> proto.RegisterAgentRequest
12, // 18: proto.Woodpecker.UnregisterAgent:input_type -> proto.Empty
13, // 19: proto.Woodpecker.ReportHealth:input_type -> proto.ReportHealthRequest
19, // 20: proto.WoodpeckerAuth.Auth:input_type -> proto.AuthRequest
16, // 21: proto.Woodpecker.Version:output_type -> proto.VersionResponse
17, // 22: proto.Woodpecker.Next:output_type -> proto.NextResponse
12, // 23: proto.Woodpecker.Init:output_type -> proto.Empty
12, // 24: proto.Woodpecker.Wait:output_type -> proto.Empty
12, // 25: proto.Woodpecker.Done:output_type -> proto.Empty
12, // 26: proto.Woodpecker.Extend:output_type -> proto.Empty
12, // 27: proto.Woodpecker.Update:output_type -> proto.Empty
12, // 28: proto.Woodpecker.Log:output_type -> proto.Empty
18, // 29: proto.Woodpecker.RegisterAgent:output_type -> proto.RegisterAgentResponse
12, // 30: proto.Woodpecker.UnregisterAgent:output_type -> proto.Empty
12, // 31: proto.Woodpecker.ReportHealth:output_type -> proto.Empty
20, // 32: proto.WoodpeckerAuth.Auth:output_type -> proto.AuthResponse
21, // [21:33] is the sub-list for method output_type
9, // [9:21] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
}
func init() { file_woodpecker_proto_init() }
@ -1549,7 +1620,7 @@ func file_woodpecker_proto_init() {
}
}
file_woodpecker_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RegisterAgentRequest); i {
switch v := v.(*AgentInfo); i {
case 0:
return &v.state
case 1:
@ -1561,7 +1632,7 @@ func file_woodpecker_proto_init() {
}
}
file_woodpecker_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*VersionResponse); i {
switch v := v.(*RegisterAgentRequest); i {
case 0:
return &v.state
case 1:
@ -1573,7 +1644,7 @@ func file_woodpecker_proto_init() {
}
}
file_woodpecker_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*NextResponse); i {
switch v := v.(*VersionResponse); i {
case 0:
return &v.state
case 1:
@ -1585,7 +1656,7 @@ func file_woodpecker_proto_init() {
}
}
file_woodpecker_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RegisterAgentResponse); i {
switch v := v.(*NextResponse); i {
case 0:
return &v.state
case 1:
@ -1597,7 +1668,7 @@ func file_woodpecker_proto_init() {
}
}
file_woodpecker_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthRequest); i {
switch v := v.(*RegisterAgentResponse); i {
case 0:
return &v.state
case 1:
@ -1609,6 +1680,18 @@ func file_woodpecker_proto_init() {
}
}
file_woodpecker_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_woodpecker_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuthResponse); i {
case 0:
return &v.state
@ -1627,7 +1710,7 @@ func file_woodpecker_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_woodpecker_proto_rawDesc,
NumEnums: 0,
NumMessages: 21,
NumMessages: 23,
NumExtensions: 0,
NumServices: 2,
},

View file

@ -116,11 +116,16 @@ message ReportHealthRequest {
string status = 1;
}
message RegisterAgentRequest {
message AgentInfo {
string platform = 1;
int32 capacity = 2;
string backend = 3;
string version = 4;
map<string, string> customLabels = 5;
}
message RegisterAgentRequest {
AgentInfo info = 1;
}
//

View file

@ -441,7 +441,7 @@ func (s *RPC) Log(c context.Context, stepUUID string, rpcLogEntries []*rpc.LogEn
return nil
}
func (s *RPC) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int32) (int64, error) {
func (s *RPC) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) {
agent, err := s.getAgentFromContext(ctx)
if err != nil {
return -1, err
@ -453,10 +453,11 @@ func (s *RPC) RegisterAgent(ctx context.Context, platform, backend, version stri
}
}
agent.Backend = backend
agent.Platform = platform
agent.Capacity = capacity
agent.Version = version
agent.Backend = info.Backend
agent.Platform = info.Platform
agent.Capacity = int32(info.Capacity)
agent.Version = info.Version
agent.CustomLabels = info.CustomLabels
err = s.store.AgentUpdate(agent)
if err != nil {

View file

@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/mock"
"google.golang.org/grpc/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
)
@ -52,15 +53,19 @@ func TestRegisterAgent(t *testing.T) {
store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil)
store.On("AgentUpdate", &updatedAgent).Once().Return(nil)
rpc := RPC{
grpc := RPC{
store: store,
}
ctx := metadata.NewIncomingContext(
context.Background(),
metadata.Pairs("hostname", "hostname", "agent_id", "1337"),
)
capacity := int32(2)
agentID, err := rpc.RegisterAgent(ctx, "platform", "backend", "version", capacity)
agentID, err := grpc.RegisterAgent(ctx, rpc.AgentInfo{
Version: "version",
Platform: "platform",
Backend: "backend",
Capacity: 2,
})
if !assert.NoError(t, err) {
return
}
@ -92,15 +97,19 @@ func TestRegisterAgent(t *testing.T) {
store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil)
store.On("AgentUpdate", &updatedAgent).Once().Return(nil)
rpc := RPC{
grpc := RPC{
store: store,
}
ctx := metadata.NewIncomingContext(
context.Background(),
metadata.Pairs("hostname", "newHostname", "agent_id", "1337"),
)
capacity := int32(2)
agentID, err := rpc.RegisterAgent(ctx, "platform", "backend", "version", capacity)
agentID, err := grpc.RegisterAgent(ctx, rpc.AgentInfo{
Version: "version",
Platform: "platform",
Backend: "backend",
Capacity: 2,
})
if !assert.NoError(t, err) {
return
}

View file

@ -172,7 +172,14 @@ func (s *WoodpeckerServer) Log(c context.Context, req *proto.LogRequest) (*proto
func (s *WoodpeckerServer) RegisterAgent(c context.Context, req *proto.RegisterAgentRequest) (*proto.RegisterAgentResponse, error) {
res := new(proto.RegisterAgentResponse)
agentID, err := s.peer.RegisterAgent(c, req.GetPlatform(), req.GetBackend(), req.GetVersion(), req.GetCapacity())
agentInfo := req.GetInfo()
agentID, err := s.peer.RegisterAgent(c, rpc.AgentInfo{
Version: agentInfo.GetVersion(),
Platform: agentInfo.GetPlatform(),
Backend: agentInfo.GetBackend(),
Capacity: int(agentInfo.GetCapacity()),
CustomLabels: agentInfo.GetCustomLabels(),
})
res.AgentId = agentID
return res, err
}

View file

@ -22,19 +22,20 @@ import (
)
type Agent struct {
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
Created int64 `json:"created" xorm:"created"`
Updated int64 `json:"updated" xorm:"updated"`
Name string `json:"name" xorm:"name"`
OwnerID int64 `json:"owner_id" xorm:"'owner_id'"`
Token string `json:"token" xorm:"token"`
LastContact int64 `json:"last_contact" xorm:"last_contact"`
LastWork int64 `json:"last_work" xorm:"last_work"` // last time the agent did something, this value is used to determine if the agent is still doing work used by the autoscaler
Platform string `json:"platform" xorm:"VARCHAR(100) 'platform'"`
Backend string `json:"backend" xorm:"VARCHAR(100) 'backend'"`
Capacity int32 `json:"capacity" xorm:"capacity"`
Version string `json:"version" xorm:"'version'"`
NoSchedule bool `json:"no_schedule" xorm:"no_schedule"`
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
Created int64 `json:"created" xorm:"created"`
Updated int64 `json:"updated" xorm:"updated"`
Name string `json:"name" xorm:"name"`
OwnerID int64 `json:"owner_id" xorm:"'owner_id'"`
Token string `json:"token" xorm:"token"`
LastContact int64 `json:"last_contact" xorm:"last_contact"`
LastWork int64 `json:"last_work" xorm:"last_work"` // last time the agent did something, this value is used to determine if the agent is still doing work used by the autoscaler
Platform string `json:"platform" xorm:"VARCHAR(100) 'platform'"`
Backend string `json:"backend" xorm:"VARCHAR(100) 'backend'"`
Capacity int32 `json:"capacity" xorm:"capacity"`
Version string `json:"version" xorm:"'version'"`
NoSchedule bool `json:"no_schedule" xorm:"no_schedule"`
CustomLabels map[string]string `json:"custom_labels" xorm:"JSON 'custom_labels'"`
// OrgID is counted as unset if set to -1, this is done to ensure a new(Agent) still enforce the OrgID check by default
OrgID int64 `json:"org_id" xorm:"INDEX 'org_id'"`
} // @name Agent

View file

@ -0,0 +1,41 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package migration
import (
"fmt"
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
type agentV016 struct {
ID int64 `xorm:"pk autoincr 'id'"`
CustomLabels map[string]string `xorm:"JSON 'custom_labels'"`
}
func (agentV016) TableName() string {
return "agents"
}
var addCustomLabelsToAgent = xormigrate.Migration{
ID: "add-custom-labels-to-agent",
MigrateSession: func(sess *xorm.Session) (err error) {
if err := sess.Sync(new(agentV016)); err != nil {
return fmt.Errorf("sync models failed: %w", err)
}
return nil
},
}

View file

@ -44,6 +44,7 @@ var migrationTasks = []*xormigrate.Migration{
&fixV31Registries,
&removeOldMigrationsOfV1,
&addOrgAgents,
&addCustomLabelsToAgent,
}
var allBeans = []any{

View file

@ -316,6 +316,10 @@
"desc": "The max amount of parallel pipelines executed by this agent.",
"badge": "capacity"
},
"custom_labels": {
"custom_labels": "Custom Labels",
"desc": "The custom labels set by the agent admin on agent startup."
},
"org": {
"badge": "org"
},

View file

@ -33,6 +33,15 @@
<TextField :id="id" v-model="agent.platform" disabled />
</InputField>
<InputField
v-if="agent.custom_labels && Object.keys(agent.custom_labels).length > 0"
v-slot="{ id }"
:label="$t('admin.settings.agents.custom_labels.custom_labels')"
>
<span class="text-wp-text-alt-100">{{ $t('admin.settings.agents.custom_labels.desc') }}</span>
<TextField :id="id" :model-value="formatCustomLabels(agent.custom_labels)" disabled />
</InputField>
<InputField
v-slot="{ id }"
:label="$t('admin.settings.agents.capacity.capacity')"
@ -101,4 +110,10 @@ const agent = computed({
function updateAgent(newValues: Partial<Agent>) {
emit('update:modelValue', { ...agent.value, ...newValues });
}
function formatCustomLabels(labels: Record<string, string>): string {
return Object.entries(labels)
.map(([key, value]) => `${key}=${value}`)
.join(', ');
}
</script>

View file

@ -12,4 +12,5 @@ export interface Agent {
capacity: number;
version: string;
no_schedule: boolean;
custom_labels: Record<string, string>;
}

View file

@ -228,20 +228,21 @@ type (
// Agent is the JSON data for an agent.
Agent struct {
ID int64 `json:"id"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Name string `json:"name"`
OwnerID int64 `json:"owner_id"`
OrgID int64 `json:"org_id"`
Token string `json:"token"`
LastContact int64 `json:"last_contact"`
LastWork int64 `json:"last_work"`
Platform string `json:"platform"`
Backend string `json:"backend"`
Capacity int32 `json:"capacity"`
Version string `json:"version"`
NoSchedule bool `json:"no_schedule"`
ID int64 `json:"id"`
Created int64 `json:"created"`
Updated int64 `json:"updated"`
Name string `json:"name"`
OwnerID int64 `json:"owner_id"`
OrgID int64 `json:"org_id"`
Token string `json:"token"`
LastContact int64 `json:"last_contact"`
LastWork int64 `json:"last_work"`
Platform string `json:"platform"`
Backend string `json:"backend"`
Capacity int32 `json:"capacity"`
Version string `json:"version"`
NoSchedule bool `json:"no_schedule"`
CustomLabels map[string]string `json:"custom_labels"`
}
// Task is the JSON data for a task.