diff --git a/agent/rpc/client_grpc.go b/agent/rpc/client_grpc.go index ff6d6d0ec..74114f8dc 100644 --- a/agent/rpc/client_grpc.go +++ b/agent/rpc/client_grpc.go @@ -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 diff --git a/cmd/agent/core/agent.go b/cmd/agent/core/agent.go index 582669f1a..cba6249b1 100644 --- a/cmd/agent/core/agent.go +++ b/cmd/agent/core/agent.go @@ -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") diff --git a/cmd/agent/core/flags.go b/cmd/agent/core/flags.go index 6770e36bd..5afcb0e6b 100644 --- a/cmd/agent/core/flags.go +++ b/cmd/agent/core/flags.go @@ -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{ diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index e91133386..8e00e7afc 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -4589,6 +4589,12 @@ const docTemplate = `{ "created": { "type": "integer" }, + "custom_labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "id": { "type": "integer" }, diff --git a/docs/docs/20-usage/20-workflow-syntax.md b/docs/docs/20-usage/20-workflow-syntax.md index 22de53e7d..5c13bff04 100644 --- a/docs/docs/20-usage/20-workflow-syntax.md +++ b/docs/docs/20-usage/20-workflow-syntax.md @@ -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. diff --git a/docs/docs/30-administration/15-agent-config.md b/docs/docs/30-administration/15-agent-config.md index 15792b38b..4b2e46bb4 100644 --- a/docs/docs/30-administration/15-agent-config.md +++ b/docs/docs/30-administration/15-agent-config.md @@ -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` diff --git a/docs/docs/91-migrations.md b/docs/docs/91-migrations.md index e0cc57c83..221358035 100644 --- a/docs/docs/91-migrations.md +++ b/docs/docs/91-migrations.md @@ -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 diff --git a/pipeline/rpc/mocks/peer.go b/pipeline/rpc/mocks/peer.go index c98456284..baac63d6e 100644 --- a/pipeline/rpc/mocks/peer.go +++ b/pipeline/rpc/mocks/peer.go @@ -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) } diff --git a/pipeline/rpc/peer.go b/pipeline/rpc/peer.go index 050a2ab11..c47104210 100644 --- a/pipeline/rpc/peer.go +++ b/pipeline/rpc/peer.go @@ -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 diff --git a/pipeline/rpc/proto/version.go b/pipeline/rpc/proto/version.go index 8f79192ce..9305354c4 100644 --- a/pipeline/rpc/proto/version.go +++ b/pipeline/rpc/proto/version.go @@ -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 diff --git a/pipeline/rpc/proto/woodpecker.pb.go b/pipeline/rpc/proto/woodpecker.pb.go index dd3423175..d7fca2aa8 100644 --- a/pipeline/rpc/proto/woodpecker.pb.go +++ b/pipeline/rpc/proto/woodpecker.pb.go @@ -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, }, diff --git a/pipeline/rpc/proto/woodpecker.proto b/pipeline/rpc/proto/woodpecker.proto index ea6c987b9..f9fef191b 100644 --- a/pipeline/rpc/proto/woodpecker.proto +++ b/pipeline/rpc/proto/woodpecker.proto @@ -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 customLabels = 5; +} + +message RegisterAgentRequest { + AgentInfo info = 1; } // diff --git a/server/grpc/rpc.go b/server/grpc/rpc.go index 614c4eb7d..9297f3502 100644 --- a/server/grpc/rpc.go +++ b/server/grpc/rpc.go @@ -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 { diff --git a/server/grpc/rpc_test.go b/server/grpc/rpc_test.go index 01c57676b..60bd8193a 100644 --- a/server/grpc/rpc_test.go +++ b/server/grpc/rpc_test.go @@ -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 } diff --git a/server/grpc/server.go b/server/grpc/server.go index 633a7ee99..2cfa8548a 100644 --- a/server/grpc/server.go +++ b/server/grpc/server.go @@ -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 } diff --git a/server/model/agent.go b/server/model/agent.go index 9a360cec4..b57c56baa 100644 --- a/server/model/agent.go +++ b/server/model/agent.go @@ -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 diff --git a/server/store/datastore/migration/016_add_custom_labels_to_agent.go b/server/store/datastore/migration/016_add_custom_labels_to_agent.go new file mode 100644 index 000000000..64ef286c9 --- /dev/null +++ b/server/store/datastore/migration/016_add_custom_labels_to_agent.go @@ -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 + }, +} diff --git a/server/store/datastore/migration/migration.go b/server/store/datastore/migration/migration.go index 9a8855617..7ba1f435c 100644 --- a/server/store/datastore/migration/migration.go +++ b/server/store/datastore/migration/migration.go @@ -44,6 +44,7 @@ var migrationTasks = []*xormigrate.Migration{ &fixV31Registries, &removeOldMigrationsOfV1, &addOrgAgents, + &addCustomLabelsToAgent, } var allBeans = []any{ diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json index 26c04e5af..3a1ec3313 100644 --- a/web/src/assets/locales/en.json +++ b/web/src/assets/locales/en.json @@ -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" }, diff --git a/web/src/components/agent/AgentForm.vue b/web/src/components/agent/AgentForm.vue index b64a0c43a..42c30fde0 100644 --- a/web/src/components/agent/AgentForm.vue +++ b/web/src/components/agent/AgentForm.vue @@ -33,6 +33,15 @@ + + {{ $t('admin.settings.agents.custom_labels.desc') }} + + + ) { emit('update:modelValue', { ...agent.value, ...newValues }); } + +function formatCustomLabels(labels: Record): string { + return Object.entries(labels) + .map(([key, value]) => `${key}=${value}`) + .join(', '); +} diff --git a/web/src/lib/api/types/agent.ts b/web/src/lib/api/types/agent.ts index 62868a609..0afbf971f 100644 --- a/web/src/lib/api/types/agent.ts +++ b/web/src/lib/api/types/agent.ts @@ -12,4 +12,5 @@ export interface Agent { capacity: number; version: string; no_schedule: boolean; + custom_labels: Record; } diff --git a/woodpecker-go/woodpecker/types.go b/woodpecker-go/woodpecker/types.go index 006424b61..aa30137f0 100644 --- a/woodpecker-go/woodpecker/types.go +++ b/woodpecker-go/woodpecker/types.go @@ -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.