You've already forked woodpecker
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:
@@ -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).
|
||||
|
||||
---
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user