1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2026-06-03 16:35:37 +02:00

Allow agents to require labels on workflows (#5633)

This commit is contained in:
Marcus Ramberg
2025-10-13 12:47:03 +02:00
committed by GitHub
parent 4da66b32d4
commit 05bf8d17e5
4 changed files with 60 additions and 2 deletions
@@ -155,7 +155,8 @@ Configures the number of parallel workflows.
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.
If you use `!` as key prefix it is mandatory for the workflow to have that label set (without !) set and matched.
By default, agents provide four additional labels `platform=os/arch`, `hostname=my-agent`, `backend=my-backend` 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).
---
+16
View File
@@ -29,6 +29,10 @@ func createFilterFunc(agentFilter rpc.Filter) queue.FilterFn {
// Create a copy of the labels for filtering to avoid modifying the original task
labels := maps.Clone(task.Labels)
if requiredLabelsMissing(labels, agentFilter.Labels) {
return false, 0
}
// ignore internal labels for filtering
for k := range labels {
if strings.HasPrefix(k, pipelineConsts.InternalLabelPrefix) {
@@ -64,3 +68,15 @@ func createFilterFunc(agentFilter rpc.Filter) queue.FilterFn {
return true, score
}
}
func requiredLabelsMissing(taskLabels, agentLabels map[string]string) bool {
for label, value := range agentLabels {
if len(label) > 0 && label[0] == '!' {
val, ok := taskLabels[label[1:]]
if !ok || val != value {
return true
}
}
}
return false
}
+41
View File
@@ -131,3 +131,44 @@ func TestCreateFilterFunc(t *testing.T) {
})
}
}
func TestMissingRequiredLabels(t *testing.T) {
t.Parallel()
testdata := []struct {
taskLabels map[string]string
requiredLabels map[string]string
want bool
}{
// Required label present and matches
{
taskLabels: map[string]string{"os": "linux"},
requiredLabels: map[string]string{"!os": "linux", "platform": "arm64"},
want: false,
},
// Required label present but does not match
{
taskLabels: map[string]string{"os": "windows"},
requiredLabels: map[string]string{"!os": "linux", "platform": "amd64"},
want: true,
},
// Required label missing
{
taskLabels: map[string]string{"arch": "amd64"},
requiredLabels: map[string]string{"!os": "linux"},
want: true,
},
// No agent labels
{
taskLabels: map[string]string{"os": "linux"},
requiredLabels: map[string]string{},
want: false,
},
}
for _, tt := range testdata {
if got := requiredLabelsMissing(tt.taskLabels, tt.requiredLabels); got != tt.want {
t.Errorf("requiredLabelsMissing(%v, %v) = %v, want %v", tt.taskLabels, tt.requiredLabels, got, tt.want)
}
}
}
+1 -1
View File
@@ -67,7 +67,7 @@ func (t *InfoT) String() string {
return sb.String()
}
// Filter filters tasks in the queue. If the Filter returns false,
// FilterFn filters tasks in the queue. If the Filter returns false,
// the Task is skipped and not returned to the subscriber.
// The int return value represents the matching score (higher is better).
type FilterFn func(*model.Task) (bool, int)