Fix pipeline cancellation status handling and step state synchronization (#6011)

Co-authored-by: pnkcaht <samzoovsk19@gmail.com>
Co-authored-by: qwerty287 <80460567+qwerty287@users.noreply.github.com>
Co-authored-by: Lauris B <lauris@nix.lv>
This commit is contained in:
6543
2026-02-05 21:41:05 +01:00
committed by GitHub
parent 1af1ef562c
commit 8a8f9ad3aa
26 changed files with 956 additions and 387 deletions

View File

@@ -623,20 +623,29 @@ func (_c *MockPeer_Version_Call) RunAndReturn(run func(c context.Context) (*rpc.
}
// Wait provides a mock function for the type MockPeer
func (_mock *MockPeer) Wait(c context.Context, workflowID string) error {
func (_mock *MockPeer) Wait(c context.Context, workflowID string) (bool, error) {
ret := _mock.Called(c, workflowID)
if len(ret) == 0 {
panic("no return value specified for Wait")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
var r0 bool
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok {
return returnFunc(c, workflowID)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, string) bool); ok {
r0 = returnFunc(c, workflowID)
} else {
r0 = ret.Error(0)
r0 = ret.Get(0).(bool)
}
return r0
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = returnFunc(c, workflowID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockPeer_Wait_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Wait'
@@ -669,12 +678,12 @@ func (_c *MockPeer_Wait_Call) Run(run func(c context.Context, workflowID string)
return _c
}
func (_c *MockPeer_Wait_Call) Return(err error) *MockPeer_Wait_Call {
_c.Call.Return(err)
func (_c *MockPeer_Wait_Call) Return(canceled bool, err error) *MockPeer_Wait_Call {
_c.Call.Return(canceled, err)
return _c
}
func (_c *MockPeer_Wait_Call) RunAndReturn(run func(c context.Context, workflowID string) error) *MockPeer_Wait_Call {
func (_c *MockPeer_Wait_Call) RunAndReturn(run func(c context.Context, workflowID string) (bool, error)) *MockPeer_Wait_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -15,89 +15,291 @@
package rpc
import (
"context"
import "context"
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
)
type (
// Filter defines filters for fetching items from the queue.
Filter struct {
Labels map[string]string `json:"labels"`
}
// StepState defines the step state.
StepState struct {
StepUUID string `json:"step_uuid"`
Started int64 `json:"started"`
Finished int64 `json:"finished"`
Exited bool `json:"exited"`
ExitCode int `json:"exit_code"`
Error string `json:"error"`
}
// WorkflowState defines the workflow state.
WorkflowState struct {
Started int64 `json:"started"`
Finished int64 `json:"finished"`
Error string `json:"error"`
}
// Workflow defines the workflow execution details.
Workflow struct {
ID string `json:"id"`
Config *backend.Config `json:"config"`
Timeout int64 `json:"timeout"`
}
Version struct {
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"`
}
)
// Peer defines a peer-to-peer connection.
// Peer defines the bidirectional communication interface between Woodpecker agents and servers.
//
// # Architecture and Implementations
//
// The Peer interface is implemented differently on each side of the communication:
//
// - Agent side: Implemented by agent/rpc/client_grpc.go's client struct, which wraps
// a gRPC client connection to make RPC calls to the server.
//
// - Server side: Implemented by server/rpc/rpc.go's RPC struct, which contains the
// business logic and is wrapped by server/rpc/server.go's WoodpeckerServer struct
// to handle incoming gRPC requests.
//
// # Thread Safety and Concurrency
//
// - Implementations must be safe for concurrent calls across different workflows
// - The same Peer instance may be called concurrently from multiple goroutines
// - Each workflow is identified by a unique workflowID string
// - Implementations must properly isolate workflow state using workflowID
//
// # Error Handling Conventions
//
// - Methods return errors for communication failures, validation errors, or server-side issues
// - Errors should not be used for business logic
// - Network/transport errors should be retried by the caller when appropriate
// - Nil error indicates successful operation
// - Context cancellation should return nil or context.Canceled, not a custom error
// - Business logic errors (e.g., workflow not found) return specific error types
//
// # Intended Execution Flow
//
// 1. Agent Lifecycle:
// - Version() checks compatibility with server
// - RegisterAgent() announces agent availability
// - ReportHealth() periodically confirms agent is alive
// - UnregisterAgent() gracefully disconnects agent
//
// 2. Workflow Execution (may happen concurrently for multiple workflows):
// - Next() blocks until server assigns a workflow
// - Init() signals workflow execution has started
// - Wait() (in background goroutine) monitors for cancellation signals
// - Update() reports step state changes as workflow progresses
// - EnqueueLog() streams log output from steps
// - Extend() extends workflow timeout if needed so queue does not reschedule it as retry
// - Done() signals workflow has completed
//
// 3. Cancellation Flow:
// - Server can cancel workflow by releasing Wait() with canceled=true
// - Agent detects cancellation from Wait() return value
// - Agent stops workflow execution and calls Done() with canceled state
type Peer interface {
// Version returns the server- & grpc-version
// Version returns the server and gRPC protocol version information.
//
// This is typically called once during agent initialization to verify
// compatibility between agent and server versions.
//
// Returns:
// - Version with server version string and gRPC protocol version number
// - Error if communication fails or server is unreachable
Version(c context.Context) (*Version, error)
// Next returns the next workflow in the queue
// Next blocks until the server provides the next workflow to execute from the queue.
//
// This is the primary work-polling mechanism. Agents call this repeatedly in a loop,
// and it blocks until either:
// 1. A workflow matching the filter becomes available
// 2. The context is canceled (agent shutdown, network timeout, etc.)
//
// The filter allows agents to specify capabilities via labels (e.g., platform,
// backend type) so the server only assigns compatible workflows.
//
// Context Handling:
// - This is a long-polling operation that may block for extended periods
// - Implementations MUST check context regularly (not just at entry)
// - When context is canceled, must return nil workflow and nil error
// - Server may send keep-alive signals or periodically return nil to allow reconnection
//
// Returns:
// - Workflow object with ID, Config, and Workflow.Timeout if work is available
// - nil, nil if context is canceled or no work available (retry expected)
// - nil, error if a non-retryable error occurs
Next(c context.Context, f Filter) (*Workflow, error)
// Wait blocks until the workflow is complete
Wait(c context.Context, workflowID string) error
// Wait blocks until the workflow with the given ID completes or is canceled by the server.
//
// This is used by agents to monitor for server-side cancellation signals. Typically
// called in a background goroutine immediately after Init(), running concurrently
// with workflow execution.
//
// The method serves two purposes:
// 1. Signals when server wants to cancel workflow (canceled=true)
// 2. Unblocks when workflow completes normally on agent (canceled=false)
//
// Context Handling:
// - This is a long-running blocking operation for the workflow duration
// - Context cancellation indicates shutdown, not workflow cancellation
// - When context is canceled, should return (false, nil) or (false, ctx.Err())
// - Must not confuse context cancellation with workflow cancellation signal
//
// Cancellation Flow:
// - Server releases Wait() with canceled=true → agent should stop workflow
// - Agent completes workflow normally → Done() is called → server releases Wait() with canceled=false
// - Agent context canceled → Wait() returns immediately, workflow may continue on agent
//
// Returns:
// - canceled=true, err=nil: Server initiated cancellation, agent should stop workflow
// - canceled=false, err=nil: Workflow completed normally (Wait unblocked by Done call)
// - canceled=false, err!=nil: Communication error, agent should retry or handle error
Wait(c context.Context, workflowID string) (canceled bool, err error)
// Init signals the workflow is initialized
// Init signals to the server that the workflow has been initialized and execution has started.
//
// This is called once per workflow immediately after the agent accepts it from Next()
// and before starting step execution. It allows the server to track workflow start time
// and update workflow status to "running".
//
// The WorkflowState should have:
// - Started: Unix timestamp when execution began
// - Finished: 0 (not finished yet)
// - Error: empty string (no error yet)
// - Canceled: false (not canceled yet)
//
// Returns:
// - nil on success
// - error if communication fails or server rejects the state
Init(c context.Context, workflowID string, state WorkflowState) error
// Done signals the workflow is complete
// Done signals to the server that the workflow has completed execution.
//
// This is called once per workflow after all steps have finished (or workflow was canceled).
// It provides the final workflow state including completion time, any errors, and
// cancellation status.
//
// The WorkflowState should have:
// - Started: Unix timestamp when execution began (same as Init)
// - Finished: Unix timestamp when execution completed
// - Error: Error message if workflow failed, empty if successful
// - Canceled: true if workflow was canceled, false otherwise
//
// After Done() is called:
// - Server updates final workflow status in database
// - Server releases any Wait() calls for this workflow
// - Server removes workflow from active queue
// - Server notifies forge of workflow completion
//
// Context Handling:
// - MUST attempt to complete even if workflow context is canceled
// - Often called with a shutdown/cleanup context rather than workflow context
// - Critical for proper cleanup - should retry on transient failures
//
// Returns:
// - nil on success
// - error if communication fails or server rejects the state
Done(c context.Context, workflowID string, state WorkflowState) error
// Extend extends the workflow deadline
// Extend extends the timeout for the workflow with the given ID in the task queue.
//
// Agents must call Extend() regularly (e.g., every constant.TaskTimeout / 3) to signal
// that the workflow is still actively executing and prevent premature timeout.
//
// If agents don't call Extend periodically, the workflow will be rescheduled to a new
// agent after the timeout period expires (specified in constant.TaskTimeout).
//
// This acts as a heartbeat mechanism to detect stuck workflow executions. If an agent
// dies or becomes unresponsive, the server will eventually timeout the workflow and
// reassign it.
//
// IMPORTANT: Don't confuse this with Workflow.Timeout returned by Next() - they serve
// different purposes!
//
// Returns:
// - nil on success (timeout was extended)
// - error if communication fails or workflow is not found
Extend(c context.Context, workflowID string) error
// Update updates the step state
// Update reports step state changes to the server as the workflow progresses.
//
// This is called multiple times per step:
// 1. When step starts (Exited=false, Finished=0)
// 2. When step completes (Exited=true, Finished and ExitCode set)
// 3. Potentially on progress updates if step has long-running operations
//
// The server uses these updates to:
// - Track step execution progress
// - Update UI with real-time status
// - Store step results in database
// - Calculate workflow completion
//
// Context Handling:
// - Failures should be logged but not block workflow execution
//
// Returns:
// - nil on success
// - error if communication fails or server rejects the state
Update(c context.Context, workflowID string, state StepState) error
// EnqueueLog queues the step log entry for delayed sending
// EnqueueLog queues a log entry for delayed batch sending to the server.
//
// Log entries are produced continuously during step execution and need to be
// transmitted efficiently. This method adds logs to an internal queue that
// batches and sends them periodically to reduce network overhead.
//
// The implementation should:
// - Queue the log entry in a memory buffer
// - Batch multiple entries together
// - Send batches periodically (e.g., every second) or when buffer fills
// - Handle backpressure if server is slow or network is congested
//
// Unlike other methods, EnqueueLog:
// - Does NOT take a context parameter (fire-and-forget)
// - Does NOT return an error (never blocks the caller)
// - Does NOT guarantee immediate transmission
//
// Thread Safety:
// - MUST be safe to call concurrently from multiple goroutines
// - May be called concurrently from different steps/workflows
// - Internal queue must be properly synchronized
EnqueueLog(logEntry *LogEntry)
// RegisterAgent register our agent to the server
// RegisterAgent announces this agent to the server and returns an agent ID.
//
// This is called once during agent startup to:
// - Create an agent record in the server database
// - Obtain a unique agent ID for subsequent requests
// - Declare agent capabilities (platform, backend, capacity, labels)
// - Enable server-side agent tracking and monitoring
//
// The AgentInfo should specify:
// - Version: Agent version string (e.g., "v2.0.0")
// - Platform: OS/architecture (e.g., "linux/amd64")
// - Backend: Execution backend (e.g., "docker", "kubernetes")
// - Capacity: Maximum concurrent workflows (e.g., 2)
// - CustomLabels: Additional key-value labels for filtering
//
// Context Handling:
// - Context cancellation indicates agent is aborting startup
// - Should not retry indefinitely - fail fast on persistent errors
//
// Returns:
// - agentID: Unique identifier for this agent (use in subsequent calls)
// - error: If registration fails
RegisterAgent(ctx context.Context, info AgentInfo) (int64, error)
// UnregisterAgent unregister our agent from the server
// UnregisterAgent removes this agent from the server's registry.
//
// This is called during graceful agent shutdown to:
// - Mark agent as offline in server database
// - Allow server to stop assigning workflows to this agent
// - Clean up any agent-specific server resources
// - Provide clean shutdown signal to monitoring systems
//
// After UnregisterAgent:
// - Agent should stop calling Next() for new work
// - Agent should complete any in-progress workflows
// - Agent may call Done() to finish existing workflows
// - Agent should close network connections
//
// Context Handling:
// - MUST attempt to complete even during forced shutdown
// - Often called with a shutdown context (limited time)
// - Failure is logged but should not prevent agent exit
//
// Returns:
// - nil on success
// - error if communication fails
UnregisterAgent(ctx context.Context) error
// ReportHealth reports health status of the agent to the server
// ReportHealth sends a periodic health status update to the server.
//
// This is called regularly (e.g., every 30 seconds) during agent operation to:
// - Prove agent is still alive and responsive
// - Allow server to detect dead or stuck agents
// - Update agent's "last seen" timestamp in database
// - Provide application-level keepalive beyond network keep-alive signals
//
// Health reporting helps the server:
// - Mark unresponsive agents as offline
// - Redistribute work from dead agents
// - Display accurate agent status in UI
// - Trigger alerts for infrastructure issues
//
// Returns:
// - nil on success
// - error if communication fails
ReportHealth(c 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 = 14
const Version int32 = 15

View File

@@ -44,6 +44,7 @@ type StepState struct {
Exited bool `protobuf:"varint,4,opt,name=exited,proto3" json:"exited,omitempty"`
ExitCode int32 `protobuf:"varint,5,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"`
Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"`
Canceled bool `protobuf:"varint,7,opt,name=canceled,proto3" json:"canceled,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -120,11 +121,19 @@ func (x *StepState) GetError() string {
return ""
}
func (x *StepState) GetCanceled() bool {
if x != nil {
return x.Canceled
}
return false
}
type WorkflowState struct {
state protoimpl.MessageState `protogen:"open.v1"`
Started int64 `protobuf:"varint,4,opt,name=started,proto3" json:"started,omitempty"`
Finished int64 `protobuf:"varint,5,opt,name=finished,proto3" json:"finished,omitempty"`
Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"`
Started int64 `protobuf:"varint,1,opt,name=started,proto3" json:"started,omitempty"`
Finished int64 `protobuf:"varint,2,opt,name=finished,proto3" json:"finished,omitempty"`
Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
Canceled bool `protobuf:"varint,4,opt,name=canceled,proto3" json:"canceled,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -180,6 +189,13 @@ func (x *WorkflowState) GetError() string {
return ""
}
func (x *WorkflowState) GetCanceled() bool {
if x != nil {
return x.Canceled
}
return false
}
type LogEntry struct {
state protoimpl.MessageState `protogen:"open.v1"`
StepUuid string `protobuf:"bytes,1,opt,name=step_uuid,json=stepUuid,proto3" json:"step_uuid,omitempty"`
@@ -1032,6 +1048,50 @@ func (x *RegisterAgentResponse) GetAgentId() int64 {
return 0
}
type WaitResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Canceled bool `protobuf:"varint,1,opt,name=canceled,proto3" json:"canceled,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WaitResponse) Reset() {
*x = WaitResponse{}
mi := &file_woodpecker_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WaitResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WaitResponse) ProtoMessage() {}
func (x *WaitResponse) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WaitResponse.ProtoReflect.Descriptor instead.
func (*WaitResponse) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{19}
}
func (x *WaitResponse) GetCanceled() bool {
if x != nil {
return x.Canceled
}
return false
}
type AuthRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
AgentToken string `protobuf:"bytes,1,opt,name=agent_token,json=agentToken,proto3" json:"agent_token,omitempty"`
@@ -1042,7 +1102,7 @@ type AuthRequest struct {
func (x *AuthRequest) Reset() {
*x = AuthRequest{}
mi := &file_woodpecker_proto_msgTypes[19]
mi := &file_woodpecker_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1054,7 +1114,7 @@ func (x *AuthRequest) String() string {
func (*AuthRequest) ProtoMessage() {}
func (x *AuthRequest) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[19]
mi := &file_woodpecker_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1067,7 +1127,7 @@ func (x *AuthRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthRequest.ProtoReflect.Descriptor instead.
func (*AuthRequest) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{19}
return file_woodpecker_proto_rawDescGZIP(), []int{20}
}
func (x *AuthRequest) GetAgentToken() string {
@@ -1095,7 +1155,7 @@ type AuthResponse struct {
func (x *AuthResponse) Reset() {
*x = AuthResponse{}
mi := &file_woodpecker_proto_msgTypes[20]
mi := &file_woodpecker_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1107,7 +1167,7 @@ func (x *AuthResponse) String() string {
func (*AuthResponse) ProtoMessage() {}
func (x *AuthResponse) ProtoReflect() protoreflect.Message {
mi := &file_woodpecker_proto_msgTypes[20]
mi := &file_woodpecker_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1120,7 +1180,7 @@ func (x *AuthResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthResponse.ProtoReflect.Descriptor instead.
func (*AuthResponse) Descriptor() ([]byte, []int) {
return file_woodpecker_proto_rawDescGZIP(), []int{20}
return file_woodpecker_proto_rawDescGZIP(), []int{21}
}
func (x *AuthResponse) GetStatus() string {
@@ -1148,18 +1208,20 @@ var File_woodpecker_proto protoreflect.FileDescriptor
const file_woodpecker_proto_rawDesc = "" +
"\n" +
"\x10woodpecker.proto\x12\x05proto\"\xa9\x01\n" +
"\x10woodpecker.proto\x12\x05proto\"\xc5\x01\n" +
"\tStepState\x12\x1b\n" +
"\tstep_uuid\x18\x01 \x01(\tR\bstepUuid\x12\x18\n" +
"\astarted\x18\x02 \x01(\x03R\astarted\x12\x1a\n" +
"\bfinished\x18\x03 \x01(\x03R\bfinished\x12\x16\n" +
"\x06exited\x18\x04 \x01(\bR\x06exited\x12\x1b\n" +
"\texit_code\x18\x05 \x01(\x05R\bexitCode\x12\x14\n" +
"\x05error\x18\x06 \x01(\tR\x05error\"[\n" +
"\x05error\x18\x06 \x01(\tR\x05error\x12\x1a\n" +
"\bcanceled\x18\a \x01(\bR\bcanceled\"w\n" +
"\rWorkflowState\x12\x18\n" +
"\astarted\x18\x04 \x01(\x03R\astarted\x12\x1a\n" +
"\bfinished\x18\x05 \x01(\x03R\bfinished\x12\x14\n" +
"\x05error\x18\x06 \x01(\tR\x05error\"w\n" +
"\astarted\x18\x01 \x01(\x03R\astarted\x12\x1a\n" +
"\bfinished\x18\x02 \x01(\x03R\bfinished\x12\x14\n" +
"\x05error\x18\x03 \x01(\tR\x05error\x12\x1a\n" +
"\bcanceled\x18\x04 \x01(\bR\bcanceled\"w\n" +
"\bLogEntry\x12\x1b\n" +
"\tstep_uuid\x18\x01 \x01(\tR\bstepUuid\x12\x12\n" +
"\x04time\x18\x02 \x01(\x03R\x04time\x12\x12\n" +
@@ -1215,7 +1277,9 @@ const file_woodpecker_proto_rawDesc = "" +
"\fNextResponse\x12+\n" +
"\bworkflow\x18\x01 \x01(\v2\x0f.proto.WorkflowR\bworkflow\"2\n" +
"\x15RegisterAgentResponse\x12\x19\n" +
"\bagent_id\x18\x01 \x01(\x03R\aagentId\"I\n" +
"\bagent_id\x18\x01 \x01(\x03R\aagentId\"*\n" +
"\fWaitResponse\x12\x1a\n" +
"\bcanceled\x18\x01 \x01(\bR\bcanceled\"I\n" +
"\vAuthRequest\x12\x1f\n" +
"\vagent_token\x18\x01 \x01(\tR\n" +
"agentToken\x12\x19\n" +
@@ -1223,13 +1287,13 @@ const file_woodpecker_proto_rawDesc = "" +
"\fAuthResponse\x12\x16\n" +
"\x06status\x18\x01 \x01(\tR\x06status\x12\x19\n" +
"\bagent_id\x18\x02 \x01(\x03R\aagentId\x12!\n" +
"\faccess_token\x18\x03 \x01(\tR\vaccessToken2\xbb\x04\n" +
"\faccess_token\x18\x03 \x01(\tR\vaccessToken2\xc2\x04\n" +
"\n" +
"Woodpecker\x121\n" +
"\aVersion\x12\f.proto.Empty\x1a\x16.proto.VersionResponse\"\x00\x121\n" +
"\x04Next\x12\x12.proto.NextRequest\x1a\x13.proto.NextResponse\"\x00\x12*\n" +
"\x04Init\x12\x12.proto.InitRequest\x1a\f.proto.Empty\"\x00\x12*\n" +
"\x04Wait\x12\x12.proto.WaitRequest\x1a\f.proto.Empty\"\x00\x12*\n" +
"\x04Init\x12\x12.proto.InitRequest\x1a\f.proto.Empty\"\x00\x121\n" +
"\x04Wait\x12\x12.proto.WaitRequest\x1a\x13.proto.WaitResponse\"\x00\x12*\n" +
"\x04Done\x12\x12.proto.DoneRequest\x1a\f.proto.Empty\"\x00\x12.\n" +
"\x06Extend\x12\x14.proto.ExtendRequest\x1a\f.proto.Empty\"\x00\x12.\n" +
"\x06Update\x12\x14.proto.UpdateRequest\x1a\f.proto.Empty\"\x00\x12(\n" +
@@ -1252,7 +1316,7 @@ func file_woodpecker_proto_rawDescGZIP() []byte {
return file_woodpecker_proto_rawDescData
}
var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
var file_woodpecker_proto_goTypes = []any{
(*StepState)(nil), // 0: proto.StepState
(*WorkflowState)(nil), // 1: proto.WorkflowState
@@ -1273,19 +1337,20 @@ var file_woodpecker_proto_goTypes = []any{
(*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
(*WaitResponse)(nil), // 19: proto.WaitResponse
(*AuthRequest)(nil), // 20: proto.AuthRequest
(*AuthResponse)(nil), // 21: proto.AuthResponse
nil, // 22: proto.Filter.LabelsEntry
nil, // 23: proto.AgentInfo.CustomLabelsEntry
}
var file_woodpecker_proto_depIdxs = []int32{
21, // 0: proto.Filter.labels:type_name -> proto.Filter.LabelsEntry
22, // 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
22, // 6: proto.AgentInfo.customLabels:type_name -> proto.AgentInfo.CustomLabelsEntry
23, // 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
@@ -1299,11 +1364,11 @@ var file_woodpecker_proto_depIdxs = []int32{
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
20, // 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
19, // 24: proto.Woodpecker.Wait:output_type -> proto.WaitResponse
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
@@ -1311,7 +1376,7 @@ var file_woodpecker_proto_depIdxs = []int32{
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, // 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
@@ -1330,7 +1395,7 @@ func file_woodpecker_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_woodpecker_proto_rawDesc), len(file_woodpecker_proto_rawDesc)),
NumEnums: 0,
NumMessages: 23,
NumMessages: 24,
NumExtensions: 0,
NumServices: 2,
},

View File

@@ -27,7 +27,7 @@ service Woodpecker {
rpc Version (Empty) returns (VersionResponse) {}
rpc Next (NextRequest) returns (NextResponse) {}
rpc Init (InitRequest) returns (Empty) {}
rpc Wait (WaitRequest) returns (Empty) {}
rpc Wait (WaitRequest) returns (WaitResponse) {}
rpc Done (DoneRequest) returns (Empty) {}
rpc Extend (ExtendRequest) returns (Empty) {}
rpc Update (UpdateRequest) returns (Empty) {}
@@ -48,12 +48,14 @@ message StepState {
bool exited = 4;
int32 exit_code = 5;
string error = 6;
bool canceled = 7;
}
message WorkflowState {
int64 started = 4;
int64 finished = 5;
string error = 6;
int64 started = 1;
int64 finished = 2;
string error = 3;
bool canceled = 4;
}
message LogEntry {
@@ -145,6 +147,10 @@ message RegisterAgentResponse {
int64 agent_id = 1;
}
message WaitResponse {
bool canceled = 1;
};
// Woodpecker auth service is a simple service to authenticate agents and acquire a token
service WoodpeckerAuth {

View File

@@ -56,7 +56,7 @@ type WoodpeckerClient interface {
Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionResponse, error)
Next(ctx context.Context, in *NextRequest, opts ...grpc.CallOption) (*NextResponse, error)
Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*Empty, error)
Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*Empty, error)
Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*WaitResponse, error)
Done(ctx context.Context, in *DoneRequest, opts ...grpc.CallOption) (*Empty, error)
Extend(ctx context.Context, in *ExtendRequest, opts ...grpc.CallOption) (*Empty, error)
Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Empty, error)
@@ -104,9 +104,9 @@ func (c *woodpeckerClient) Init(ctx context.Context, in *InitRequest, opts ...gr
return out, nil
}
func (c *woodpeckerClient) Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*Empty, error) {
func (c *woodpeckerClient) Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*WaitResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
out := new(WaitResponse)
err := c.cc.Invoke(ctx, Woodpecker_Wait_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
@@ -193,7 +193,7 @@ type WoodpeckerServer interface {
Version(context.Context, *Empty) (*VersionResponse, error)
Next(context.Context, *NextRequest) (*NextResponse, error)
Init(context.Context, *InitRequest) (*Empty, error)
Wait(context.Context, *WaitRequest) (*Empty, error)
Wait(context.Context, *WaitRequest) (*WaitResponse, error)
Done(context.Context, *DoneRequest) (*Empty, error)
Extend(context.Context, *ExtendRequest) (*Empty, error)
Update(context.Context, *UpdateRequest) (*Empty, error)
@@ -220,7 +220,7 @@ func (UnimplementedWoodpeckerServer) Next(context.Context, *NextRequest) (*NextR
func (UnimplementedWoodpeckerServer) Init(context.Context, *InitRequest) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method Init not implemented")
}
func (UnimplementedWoodpeckerServer) Wait(context.Context, *WaitRequest) (*Empty, error) {
func (UnimplementedWoodpeckerServer) Wait(context.Context, *WaitRequest) (*WaitResponse, error) {
return nil, status.Error(codes.Unimplemented, "method Wait not implemented")
}
func (UnimplementedWoodpeckerServer) Done(context.Context, *DoneRequest) (*Empty, error) {

66
rpc/types.go Normal file
View File

@@ -0,0 +1,66 @@
// Copyright 2025 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 rpc
import (
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
)
type (
// Filter defines filters for fetching items from the queue.
Filter struct {
Labels map[string]string `json:"labels"`
}
// StepState defines the step state.
StepState struct {
StepUUID string `json:"step_uuid"`
Started int64 `json:"started"`
Finished int64 `json:"finished"`
Exited bool `json:"exited"`
ExitCode int `json:"exit_code"`
Error string `json:"error"`
Canceled bool `json:"canceled"`
}
// WorkflowState defines the workflow state.
WorkflowState struct {
Started int64 `json:"started"`
Finished int64 `json:"finished"`
Error string `json:"error"`
Canceled bool `json:"canceled"`
}
// Workflow defines the workflow execution details.
Workflow struct {
ID string `json:"id"`
Config *backend.Config `json:"config"`
Timeout int64 `json:"timeout"`
}
Version struct {
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"`
}
)