Add agent tagging / filtering for pipelines (#902)

Officially support labels for pipelines and agents to improve pipeline picking. 

* add pipeline labels
* update, improve docs  and add migration
* update proto file

---
closes #304 & #860
This commit is contained in:
Anbraten 2022-05-31 01:12:18 +02:00 committed by GitHub
parent 56a55842f6
commit e79ad00826
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 385 additions and 1218 deletions

View file

@ -19,6 +19,7 @@ import (
"encoding/json"
"io"
"io/ioutil"
"runtime"
"strconv"
"sync"
"time"
@ -283,7 +284,7 @@ func (r *Runner) Run(ctx context.Context) error {
state.Pipeline.Step.Environment = map[string]string{}
}
// TODO: find better way to update this state
// TODO: find better way to update this state and move it to pipeline to have the same env in cli-exec
state.Pipeline.Step.Environment["CI_MACHINE"] = r.hostname
state.Pipeline.Step.Environment["CI_BUILD_STATUS"] = "success"
state.Pipeline.Step.Environment["CI_BUILD_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
@ -293,6 +294,8 @@ func (r *Runner) Run(ctx context.Context) error {
state.Pipeline.Step.Environment["CI_JOB_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
state.Pipeline.Step.Environment["CI_JOB_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
state.Pipeline.Step.Environment["CI_SYSTEM_ARCH"] = runtime.GOOS + "/" + runtime.GOARCH
if state.Pipeline.Error != nil {
state.Pipeline.Step.Environment["CI_BUILD_STATUS"] = "failure"
state.Pipeline.Step.Environment["CI_JOB_STATUS"] = "failure"

View file

@ -206,6 +206,11 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
// return the metadata from the cli context.
func metadataFromContext(c *cli.Context, axis matrix.Axis) frontend.Metadata {
platform := c.String("system-platform")
if platform == "" {
platform = runtime.GOOS + "/" + runtime.GOARCH
}
return frontend.Metadata{
Repo: frontend.Repo{
Name: c.String("repo-name"),
@ -262,9 +267,9 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) frontend.Metadata {
Matrix: axis,
},
Sys: frontend.System{
Name: c.String("system-name"),
Link: c.String("system-link"),
Arch: c.String("system-arch"),
Name: c.String("system-name"),
Link: c.String("system-link"),
Platform: platform,
},
}
}

View file

@ -101,9 +101,8 @@ var flags = []cli.Flag{
// metadata parameters
//
&cli.StringFlag{
EnvVars: []string{"CI_SYSTEM_ARCH"},
Name: "system-arch",
Value: "linux/amd64",
EnvVars: []string{"CI_SYSTEM_PLATFORM"},
Name: "system-platform",
},
&cli.StringFlag{
EnvVars: []string{"CI_SYSTEM_NAME"},

View file

@ -20,6 +20,7 @@ import (
"net/http"
"os"
"runtime"
"strings"
"sync"
"github.com/rs/zerolog"
@ -39,18 +40,26 @@ import (
)
func loop(c *cli.Context) error {
filter := rpc.Filter{
Labels: map[string]string{
"platform": runtime.GOOS + "/" + runtime.GOARCH,
},
Expr: c.String("filter"),
}
hostname := c.String("hostname")
if len(hostname) == 0 {
hostname, _ = os.Hostname()
}
labels := map[string]string{
"hostname": hostname,
"platform": runtime.GOOS + "/" + runtime.GOARCH,
"repo": "*", // allow all repos by default
}
for _, v := range c.StringSlice("filter-labels") {
parts := strings.SplitN(v, "=", 2)
labels[parts[0]] = parts[1]
}
filter := rpc.Filter{
Labels: labels,
}
if c.Bool("pretty") {
log.Logger = log.Output(
zerolog.ConsoleWriter{

View file

@ -72,10 +72,10 @@ var flags = []cli.Flag{
Name: "hostname",
Usage: "agent hostname",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_FILTER"},
&cli.StringSliceFlag{
EnvVars: []string{"WOODPECKER_FILTER_LABELS"},
Name: "filter",
Usage: "filter expression to restrict builds by label",
Usage: "List of labels to filter tasks on. An agent must be assigned every tag listed in a task to be selected.",
},
&cli.IntFlag{
EnvVars: []string{"WOODPECKER_MAX_PROCS"},

View file

@ -96,26 +96,6 @@ pipeline:
+ exclude: [ develop, feature/* ]
```
### `platform`
To configure your pipeline to only be executed on an agent with a specific platform, you can use the `platform` key.
Have a look at the official [go docs](https://go.dev/doc/install/source) for the available platforms. The syntax of the platform is `GOOS/GOARCH` like `linux/arm64` or `linux/amd64`.
Example:
Assuming we have two agents, one `arm` and one `amd64`. Previously this pipeline would have executed on **either agent**, as Woodpecker is not fussy about where it runs the pipelines. By setting the following option it will only be executed on an agent with the platform `linux/arm64`.
```diff
+platform: linux/arm64
pipeline:
build:
image: golang
commands:
- go build
- go test
```
### Skip Commits
Woodpecker gives the ability to skip individual commits by adding `[CI SKIP]` to the commit message. Note this is case-insensitive.
@ -401,6 +381,10 @@ pipeline:
#### `platform`
:::note
This condition should be used in conjunction with a [matrix](/docs/usage/matrix-pipelines#example-matrix-pipeline-using-multiple-platforms) pipeline as a regular pipeline will only executed by a single agent which only has one arch.
:::
Execute a step for a specific platform:
```diff
@ -448,8 +432,8 @@ when:
#### `path`
:::info
Path conditions are applied only to **push** and **pull_request** events.
It is currently **only available** for GitHub, GitLab.
Path conditions are applied only to **push** and **pull_request** events.
It is currently **only available** for GitHub, GitLab.
Gitea only support **push** at the moment ([go-gitea/gitea#18228](https://github.com/go-gitea/gitea/pull/18228)).
:::
@ -586,7 +570,52 @@ git clone https://github.com/octocat/hello-world \
Woodpecker has integrated support for matrix builds. Woodpecker executes a separate build task for each combination in the matrix, allowing you to build and test a single commit against multiple configurations.
For more details check the [matrix build docs](/docs/usage/matrix-builds/).
For more details check the [matrix build docs](/docs/usage/matrix-pipelines/).
## `platform`
To configure your pipeline to only be executed on an agent with a specific platform, you can use the `platform` key.
Have a look at the official [go docs](https://go.dev/doc/install/source) for the available platforms. The syntax of the platform is `GOOS/GOARCH` like `linux/arm64` or `linux/amd64`.
Example:
Assuming we have two agents, one `arm` and one `amd64`. Previously this pipeline would have executed on **either agent**, as Woodpecker is not fussy about where it runs the pipelines. By setting the following option it will only be executed on an agent with the platform `linux/arm64`.
```diff
+platform: linux/arm64
pipeline:
build:
image: golang
commands:
- go build
- go test
```
## `labels`
You can set labels for your pipeline to select an agent to execute the pipeline on. An agent will pick up and run a pipeline when **every** label assigned to a pipeline matches the agents labels.
To set additional agent labels check the [agent configuration options](/docs/administration/agent-config#woodpecker_filter_labels). Agents will have at least three default labels: `platform=agent-os/agent-arch`, `hostname=my-agent` and `repo=*`. Agents can use a `*` as a wildcard for a label. For example `repo=*` will match every repo.
Pipeline labels with an empty value will be ignored.
By default each pipeline has at least the `repo=your-user/your-repo-name` label. If you have set the [platform attribute](#platform) for your pipeline it will have a label like `platform=your-os/your-arch` as well.
You can add additional labels as a key value map:
```diff
+labels:
+ location: europe # only agents with `location=europe` or `location=*` will be used
+ weather: sun
+ hostname: "" # this label will be ignored as it is empty
pipeline:
build:
image: golang
commands:
- go build
- go test
```
## `clone`

View file

@ -1,6 +1,6 @@
# Matrix builds
# Matrix pipelines
Woodpecker has integrated support for matrix builds. Woodpecker executes a separate build task for each combination in the matrix, allowing you to build and test a single commit against multiple configurations.
Woodpecker has integrated support for matrix pipeline. Woodpecker executes a separate pipeline for each combination in the matrix, allowing you to build and test a single commit against multiple configurations.
Example matrix definition:
@ -33,6 +33,15 @@ matrix:
Matrix variables are interpolated in the yaml using the `${VARIABLE}` syntax, before the yaml is parsed. This is an example yaml file before interpolating matrix parameters:
```yaml
matrix:
GO_VERSION:
- 1.4
- 1.3
DATABASE:
- mysql:5.5
- mysql:6.5
- mariadb:10.1
pipeline:
build:
image: golang:${GO_VERSION}
@ -44,15 +53,6 @@ pipeline:
services:
database:
image: ${DATABASE}
matrix:
GO_VERSION:
- 1.4
- 1.3
DATABASE:
- mysql:5.5
- mysql:6.5
- mariadb:10.1
```
Example YAML file after injecting the matrix parameters:
@ -78,36 +78,61 @@ services:
## Examples
Example matrix build based on Docker image tag:
### Example matrix pipeline based on Docker image tag
```yaml
matrix:
TAG:
- 1.7
- 1.8
- latest
pipeline:
build:
image: golang:${TAG}
commands:
- go build
- go test
matrix:
TAG:
- 1.7
- 1.8
- latest
```
Example matrix build based on Docker image:
### Example matrix pipeline based on container image
```yaml
matrix:
IMAGE:
- golang:1.7
- golang:1.8
- golang:latest
pipeline:
build:
image: ${IMAGE}
commands:
- go build
- go test
matrix:
IMAGE:
- golang:1.7
- golang:1.8
- golang:latest
```
### Example matrix pipeline using multiple platforms
```yaml
matrix:
platform:
- linux/amd64
- linux/arm64
platform: ${platform}
pipeline:
test:
image: alpine
commands:
- echo "I am running on ${platform}"
test-arm-only:
image: alpine
commands:
- echo "I am running on ${platform}"
- echo "Arm is cool!"
when:
platform: linux/arm*
```

View file

@ -12,7 +12,6 @@ services:
environment:
+ - WOODPECKER_SERVER=localhost:9000
+ - WOODPECKER_AGENT_SECRET="your-shared-secret-goes-here"
```
The following are automatically set and can be overridden:
@ -37,45 +36,6 @@ services:
+ - WOODPECKER_MAX_PROCS=4
```
## Filtering agents
When building your pipelines as long as you have set the platform or filter, builds can be made to only run code on certain agents.
```
- WOODPECKER_HOSTNAME=mycompany-ci-01.example.com
- WOODPECKER_FILTER=
```
### Filter on Platform
Only want certain pipelines or steps to run on certain agents with specific platforms? Such as arm vs amd64?
```yaml
# .woodpecker.yml
pipeline:
build:
image: golang
commands:
- go build
- go test
when:
platform: linux/amd64
testing:
image: golang
commands:
- go build
- go test
when:
platform: linux/arm*
```
See [Conditionals Pipeline](/docs/usage/pipeline-syntax#step-when---conditional-execution) syntax for more
## All agent configuration options
Here is the full list of configuration options and their default variables.
@ -125,6 +85,11 @@ Configures the agent hostname.
Configures the number of parallel builds.
### `WOODPECKER_FILTER_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](/docs/usage/pipeline-syntax#labels).
### `WOODPECKER_HEALTHCHECK`
> Default: `true`

View file

@ -2,6 +2,11 @@
Some versions need some changes to the server configuration or the pipeline configuration files.
## 1.0.0
- Refactored support of old agent filter labels and expression. Learn how to use the new [filter](/docs/usage/pipeline-syntax#labels).
- Renamed step environment variable `CI_SYSTEM_ARCH` to `CI_SYSTEM_PLATFORM`. Same applies for the cli exec variable.
## 0.15.0
- Default value for custom pipeline path is now empty / un-set which results in following resolution:

1
go.mod
View file

@ -44,7 +44,6 @@ require (
github.com/tevino/abool v1.2.0
github.com/ugorji/go v1.2.7 // indirect
github.com/urfave/cli/v2 v2.5.1
github.com/woodpecker-ci/expr v0.0.0-20210628233344-164b8b3d0915
github.com/xanzy/go-gitlab v0.64.0
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonschema v1.2.0

2
go.sum
View file

@ -1413,8 +1413,6 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/woodpecker-ci/expr v0.0.0-20210628233344-164b8b3d0915 h1:9zBOoKSR9CBeYoKQv6LFIuImg8lorCjh8XzK72bJMRg=
github.com/woodpecker-ci/expr v0.0.0-20210628233344-164b8b3d0915/go.mod h1:PbzlZ93HrA1cf16OUP1vckAPq57gtF+ccnwZeDkmC9s=
github.com/xanzy/go-gitlab v0.64.0 h1:rMgQdW9S1w3qvNAH2LYpFd2xh7KNLk+JWJd7sorNuTc=
github.com/xanzy/go-gitlab v0.64.0/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=

View file

@ -87,11 +87,11 @@ type (
// System defines runtime metadata for a ci/cd system.
System struct {
Name string `json:"name,omitempty"`
Host string `json:"host,omitempty"`
Link string `json:"link,omitempty"`
Arch string `json:"arch,omitempty"`
Version string `json:"version,omitempty"`
Name string `json:"name,omitempty"`
Host string `json:"host,omitempty"`
Link string `json:"link,omitempty"`
Platform string `json:"arch,omitempty"`
Version string `json:"version,omitempty"`
}
)
@ -179,11 +179,13 @@ func (m *Metadata) Environ() map[string]string {
"CI_PREV_BUILD_STARTED": strconv.FormatInt(m.Prev.Started, 10),
"CI_PREV_BUILD_FINISHED": strconv.FormatInt(m.Prev.Finished, 10),
"CI_SYSTEM_NAME": m.Sys.Name,
"CI_SYSTEM_LINK": m.Sys.Link,
"CI_SYSTEM_HOST": m.Sys.Host,
"CI_SYSTEM_ARCH": m.Sys.Arch,
"CI_SYSTEM_VERSION": version.Version,
"CI_SYSTEM_NAME": m.Sys.Name,
"CI_SYSTEM_LINK": m.Sys.Link,
"CI_SYSTEM_HOST": m.Sys.Host,
"CI_SYSTEM_PLATFORM": m.Sys.Platform, // will be set by pipeline platform option or by agent
"CI_SYSTEM_VERSION": version.Version,
"CI_SYSTEM_ARCH": m.Sys.Platform, // TODO: remove after next version
}
if m.Curr.Event == EventTag {
params["CI_COMMIT_TAG"] = strings.TrimPrefix(m.Curr.Commit.Ref, "refs/tags/")
@ -198,5 +200,5 @@ func (m *Metadata) Environ() map[string]string {
var pullRegexp = regexp.MustCompile(`\d+`)
func (m *Metadata) SetPlatform(platform string) {
m.Sys.Arch = platform
m.Sys.Platform = platform
}

View file

@ -92,7 +92,7 @@ func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
})
// create a default network
if strings.HasPrefix(c.metadata.Sys.Arch, windowsPrefix) {
if strings.HasPrefix(c.metadata.Sys.Platform, windowsPrefix) {
config.Networks = append(config.Networks, &backend.Network{
Name: fmt.Sprintf("%s_default", c.prefix),
Driver: networkDriverNAT,

View file

@ -78,7 +78,7 @@ func (c *Compiler) createProcess(name string, container *yaml.Container, section
}
if len(container.Commands) != 0 {
if c.metadata.Sys.Arch == "windows/amd64" {
if c.metadata.Sys.Platform == "windows/amd64" {
entrypoint = []string{"powershell", "-noprofile", "-noninteractive", "-command"}
command = []string{"[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"}
environment["CI_SCRIPT"] = generateScriptWindows(container.Commands)

View file

@ -50,7 +50,7 @@ type (
// Match returns true if all constraints match the given input. If a single
// constraint fails a false value is returned.
func (c *Constraints) Match(metadata frontend.Metadata) bool {
match := c.Platform.Match(metadata.Sys.Arch) &&
match := c.Platform.Match(metadata.Sys.Platform) &&
c.Environment.Match(metadata.Curr.Target) &&
c.Event.Match(metadata.Curr.Event) &&
c.Repo.Match(metadata.Repo.Name) &&

View file

@ -440,12 +440,12 @@ func TestConstraints(t *testing.T) {
// platform constraint
{
conf: "{ platform: linux/amd64 }",
with: frontend.Metadata{Sys: frontend.System{Arch: "linux/amd64"}},
with: frontend.Metadata{Sys: frontend.System{Platform: "linux/amd64"}},
want: true,
},
{
conf: "{ repo: linux/amd64 }",
with: frontend.Metadata{Sys: frontend.System{Arch: "windows/amd64"}},
with: frontend.Metadata{Sys: frontend.System{Platform: "windows/amd64"}},
want: false,
},
// instance constraint

View file

@ -40,7 +40,6 @@ func (c *client) Next(ctx context.Context, f Filter) (*Pipeline, error) {
var err error
req := new(proto.NextRequest)
req.Filter = new(proto.Filter)
req.Filter.Expr = f.Expr
req.Filter.Labels = f.Labels
for {
res, err = c.client.Next(ctx, req)

View file

@ -10,7 +10,6 @@ type (
// Filter defines filters for fetching items from the queue.
Filter struct {
Labels map[string]string `json:"labels"`
Expr string `json:"expr"`
}
// State defines the pipeline state.

View file

@ -17,6 +17,7 @@ package proto
//go:generate protoc --go_out=paths=source_relative:. woodpecker.proto
//go:generate protoc --go-grpc_out=paths=source_relative:. woodpecker.proto
// get needed binary's:
// go install google.golang.org/protobuf/cmd/protoc-gen-go
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
// install protoc: https://grpc.io/docs/protoc-installation/
// and get needed binary's:
// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.1
// protoc-gen-go v1.28.0
// protoc v3.12.4
// source: woodpecker.proto
package proto
@ -328,7 +328,6 @@ type Filter struct {
unknownFields protoimpl.UnknownFields
Labels map[string]string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Expr string `protobuf:"bytes,2,opt,name=expr,proto3" json:"expr,omitempty"`
}
func (x *Filter) Reset() {
@ -370,13 +369,6 @@ func (x *Filter) GetLabels() map[string]string {
return nil
}
func (x *Filter) GetExpr() string {
if x != nil {
return x.Expr
}
return ""
}
type Pipeline struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -1069,98 +1061,96 @@ var file_woodpecker_proto_rawDesc = []byte{
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x10, 0x0a,
0x03, 0x70, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x6f, 0x73, 0x12,
0x10, 0x0a, 0x03, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f, 0x75,
0x74, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x06,
0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x4c, 0x61, 0x62, 0x65,
0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12,
0x12, 0x0a, 0x04, 0x65, 0x78, 0x70, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x65,
0x78, 0x70, 0x72, 0x1a, 0x39, 0x0a, 0x0b, 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, 0x4e,
0x0a, 0x08, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69,
0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d,
0x65, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x2e,
0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x93,
0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48,
0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3a, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76,
0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b,
0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e,
0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49,
0x4e, 0x47, 0x10, 0x02, 0x22, 0x34, 0x0a, 0x0b, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x74,
0x65, 0x72, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x38, 0x0a, 0x09, 0x4e, 0x65,
0x78, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2b, 0x0a, 0x08, 0x70, 0x69, 0x70, 0x65, 0x6c,
0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x50, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x70, 0x69, 0x70, 0x65,
0x6c, 0x69, 0x6e, 0x65, 0x22, 0x41, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x02, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65,
0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x1d, 0x0a, 0x0b, 0x57, 0x61, 0x69, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x41, 0x0a, 0x0b, 0x44, 0x6f, 0x6e, 0x65, 0x52, 0x65,
0x74, 0x22, 0x76, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x31, 0x0a, 0x06, 0x6c,
0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c,
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39,
0x0a, 0x0b, 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, 0x4e, 0x0a, 0x08, 0x50, 0x69, 0x70,
0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12,
0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61,
0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x13, 0x48, 0x65,
0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x40, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68,
0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65,
0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61,
0x74, 0x75, 0x73, 0x22, 0x3a, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74,
0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f,
0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x22,
0x34, 0x0a, 0x0b, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25,
0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66,
0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x38, 0x0a, 0x09, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x12, 0x2b, 0x0a, 0x08, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x70,
0x65, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x22,
0x41, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e,
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22,
0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61,
0x74, 0x65, 0x22, 0x1d, 0x0a, 0x0b, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x22, 0x41, 0x0a, 0x0b, 0x44, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
0x12, 0x22, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73,
0x74, 0x61, 0x74, 0x65, 0x22, 0x1f, 0x0a, 0x0d, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61,
0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x1f, 0x0a, 0x0d, 0x45, 0x78, 0x74,
0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x40, 0x0a, 0x0d, 0x55, 0x70,
0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x04, 0x66,
0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x43, 0x0a, 0x0d,
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22, 0x0a,
0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74,
0x65, 0x22, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x1f, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65,
0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0xfa, 0x02, 0x0a, 0x0a, 0x57, 0x6f,
0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x2e, 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, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78,
0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 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,
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, 0x2e, 0x0a, 0x06,
0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55,
0x70, 0x6c, 0x6f, 0x61, 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, 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, 0x32, 0x48, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68,
0x12, 0x3e, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61,
0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x42, 0x38, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x77,
0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x2d, 0x63, 0x69, 0x2f, 0x77, 0x6f, 0x6f,
0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 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,
0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x40, 0x0a, 0x0d, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c,
0x65, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x43, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3d, 0x0a, 0x0a,
0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x04, 0x6c, 0x69,
0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x07, 0x0a, 0x05, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x32, 0xfa, 0x02, 0x0a, 0x0a, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63,
0x6b, 0x65, 0x72, 0x12, 0x2e, 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,
0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x70, 0x6c,
0x79, 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, 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, 0x2e, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x6f, 0x61,
0x64, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 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, 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, 0x32, 0x48, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x3e, 0x0a, 0x05, 0x43,
0x68, 0x65, 0x63, 0x6b, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61,
0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68,
0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x38, 0x5a, 0x36, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x77, 0x6f, 0x6f, 0x64, 0x70, 0x65,
0x63, 0x6b, 0x65, 0x72, 0x2d, 0x63, 0x69, 0x2f, 0x77, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b,
0x65, 0x72, 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 (

View file

@ -31,7 +31,6 @@ message Line {
message Filter {
map<string, string> labels = 1;
string expr = 2;
}
message Pipeline {

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.1.0
// - protoc v3.18.1
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.12.4
// source: woodpecker.proto
package proto

View file

@ -0,0 +1,10 @@
labels:
location: europe
weather: sun
hostname: ""
pipeline:
build:
image: golang:latest
commands:
- go test

View file

@ -0,0 +1,7 @@
platform: linux/amd64
pipeline:
build:
image: golang:latest
commands:
- go test

View file

@ -17,6 +17,8 @@
"services": { "$ref": "#/definitions/services" },
"workspace": { "$ref": "#/definitions/workspace" },
"matrix": { "$ref": "#/definitions/matrix" },
"platform": { "$ref": "#/definitions/platform" },
"labels": { "$ref": "#/definitions/labels" },
"skip_clone": { "type": "boolean" },
"depends_on": {
"type": "array",
@ -270,7 +272,7 @@
"oneOf": [
{ "type": "string" },
{
"type" :"array",
"type": "array",
"items": {
"type": "string"
}
@ -311,7 +313,7 @@
"additionalProperties": true
},
"matrix": {
"description": "TODO Read more: https://woodpecker-ci.org/docs/usage/matrix-builds",
"description": "Execute pipeline for each matrix combination. Read more: https://woodpecker-ci.org/docs/usage/matrix-pipelines",
"type": "object",
"properties": {
"include": {
@ -329,6 +331,18 @@
},
"minLength": 1
}
},
"platform": {
"description": "Configures the platform the pipeline will be executed on. Read more: https://woodpecker-ci.org/docs/usage/pipeline-syntax#platform",
"type": "string",
"additionalProperties": false
},
"labels": {
"description": "Configures the labels used for the agent selection. Read more: https://woodpecker-ci.org/docs/usage/pipeline-syntax#clone",
"type": "object",
"additionalProperties": {
"type": ["boolean", "string", "number"]
}
}
}
}

View file

@ -70,6 +70,14 @@ func TestSchema(t *testing.T) {
name: "Workspace",
testFile: ".woodpecker/test-workspace.yml",
},
{
name: "Platform",
testFile: ".woodpecker/test-platform.yml",
},
{
name: "Labels",
testFile: ".woodpecker/test-labels.yml",
},
{
name: "Broken Config",
testFile: ".woodpecker/test-broken.yml",

View file

@ -15,36 +15,30 @@
package grpc
import (
"github.com/woodpecker-ci/expr"
"github.com/woodpecker-ci/woodpecker/pipeline/rpc"
"github.com/woodpecker-ci/woodpecker/server/queue"
)
func createFilterFunc(filter rpc.Filter) (queue.Filter, error) {
var st *expr.Selector
var err error
if filter.Expr != "" {
st, err = expr.ParseString(filter.Expr)
if err != nil {
return nil, err
}
}
func createFilterFunc(agentFilter rpc.Filter) (queue.FilterFn, error) {
return func(task *queue.Task) bool {
if st != nil {
match, _ := st.Eval(expr.NewRow(task.Labels))
return match
}
for k, v := range filter.Labels {
// if platform is not set ignore that filter
if k == "platform" && task.Labels[k] == "" {
for taskLabel, taskLabelValue := range task.Labels {
// if a task label is empty it will be ignored
if taskLabelValue == "" {
continue
}
if task.Labels[k] != v {
agentLabelValue, ok := agentFilter.Labels[taskLabel]
if !ok {
return false
}
// if agent label has a wildcard
if agentLabelValue == "*" {
continue
}
if taskLabelValue != agentLabelValue {
return false
}
}

View file

@ -26,72 +26,70 @@ import (
func TestCreateFilterFunc(t *testing.T) {
t.Parallel()
type filterTests struct {
tsk queue.Task
exp bool
}
tests := []struct {
struc rpc.Filter
ft []filterTests
}{{
struc: rpc.Filter{},
ft: []filterTests{{
tsk: queue.Task{
Labels: map[string]string{"platform": "", "repo": "test/woodpecker"},
name string
agentLabels map[string]string
task queue.Task
exp bool
}{
{
name: "agent with missing labels",
agentLabels: map[string]string{"repo": "test/woodpecker"},
task: queue.Task{
Labels: map[string]string{"platform": "linux/amd64", "repo": "test/woodpecker"},
},
exp: false,
},
{
name: "agent with wrong labels",
agentLabels: map[string]string{"platform": "linux/arm64"},
task: queue.Task{
Labels: map[string]string{"platform": "linux/amd64"},
},
exp: false,
},
{
name: "agent with correct labels",
agentLabels: map[string]string{"platform": "linux/amd64", "location": "europe"},
task: queue.Task{
Labels: map[string]string{"platform": "linux/amd64", "location": "europe"},
},
exp: true,
}, {
tsk: queue.Task{
},
{
name: "agent with additional labels",
agentLabels: map[string]string{"platform": "linux/amd64", "location": "europe"},
task: queue.Task{
Labels: map[string]string{"platform": "linux/amd64"},
},
exp: true,
},
{
name: "agent with wildcard label",
agentLabels: map[string]string{"platform": "linux/amd64", "location": "*"},
task: queue.Task{
Labels: map[string]string{"platform": "linux/amd64", "location": "america"},
},
exp: true,
},
{
name: "agent with platform label and task without",
agentLabels: map[string]string{"platform": "linux/amd64"},
task: queue.Task{
Labels: map[string]string{"platform": ""},
},
exp: true,
}},
}, {
struc: rpc.Filter{
Labels: map[string]string{"platform": "abc"},
},
ft: []filterTests{{
tsk: queue.Task{
Labels: map[string]string{"platform": "def"},
},
exp: false,
}, {
tsk: queue.Task{
Labels: map[string]string{"platform": ""},
},
exp: true,
}},
}, {
struc: rpc.Filter{
Expr: "platform = 'abc' OR repo = 'test/woodpecker'",
},
ft: []filterTests{{
tsk: queue.Task{
Labels: map[string]string{"platform": "", "repo": "test/woodpecker"},
},
exp: true,
}, {
tsk: queue.Task{
Labels: map[string]string{"platform": "abc", "repo": "else"},
},
exp: true,
}, {
tsk: queue.Task{
Labels: map[string]string{"platform": "also", "repo": "else"},
},
exp: false,
}},
}}
}
for _, test := range tests {
fn, err := createFilterFunc(test.struc)
if !assert.NoError(t, err) {
t.Fail()
}
t.Run(test.name, func(t *testing.T) {
fn, err := createFilterFunc(rpc.Filter{Labels: test.agentLabels})
if !assert.NoError(t, err) {
t.Fail()
}
for _, ft := range test.ft {
assert.EqualValues(t, ft.exp, fn(&ft.tsk))
}
assert.EqualValues(t, test.exp, fn(&test.task))
})
}
}

View file

@ -52,7 +52,7 @@ type RPC struct {
}
// Next implements the rpc.Next function
func (s *RPC) Next(c context.Context, filter rpc.Filter) (*rpc.Pipeline, error) {
func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Pipeline, error) {
metadata, ok := grpcMetadata.FromIncomingContext(c)
if ok {
hostname, ok := metadata["hostname"]
@ -61,7 +61,7 @@ func (s *RPC) Next(c context.Context, filter rpc.Filter) (*rpc.Pipeline, error)
}
}
fn, err := createFilterFunc(filter)
fn, err := createFilterFunc(agentFilter)
if err != nil {
return nil, err
}

View file

@ -63,7 +63,6 @@ func NewWoodpeckerServer(remote remote.Remote, queue queue.Queue, logger logging
func (s *WoodpeckerServer) Next(c context.Context, req *proto.NextRequest) (*proto.NextReply, error) {
filter := rpc.Filter{
Labels: req.GetFilter().GetLabels(),
Expr: req.GetFilter().GetExpr(),
}
res := new(proto.NextReply)

View file

@ -26,7 +26,7 @@ type entry struct {
}
type worker struct {
filter Filter
filter FilterFn
channel chan *Task
}
@ -74,7 +74,7 @@ func (q *fifo) PushAtOnce(c context.Context, tasks []*Task) error {
}
// Poll retrieves and removes the head of this queue.
func (q *fifo) Poll(c context.Context, f Filter) (*Task, error) {
func (q *fifo) Poll(c context.Context, f FilterFn) (*Task, error) {
q.Lock()
w := &worker{
channel: make(chan *Task, 1),

View file

@ -96,7 +96,7 @@ func (q *persistentQueue) PushAtOnce(c context.Context, tasks []*Task) error {
}
// Poll retrieves and removes a task head of this queue.
func (q *persistentQueue) Poll(c context.Context, f Filter) (*Task, error) {
func (q *persistentQueue) Poll(c context.Context, f FilterFn) (*Task, error) {
task, err := q.Queue.Poll(c, f)
if task != nil {
log.Debug().Msgf("pull queue item: %s: remove from backup", task.ID)

View file

@ -25,7 +25,7 @@ type Task struct {
// Data is the actual data in the entry.
Data []byte `json:"data"`
// Labels represents the key-value pairs the entry is lebeled with.
// Labels represents the key-value pairs the entry is labeled with.
Labels map[string]string `json:"labels,omitempty"`
// Task IDs this task depend
@ -128,7 +128,7 @@ func (t *InfoT) String() string {
// Filter filters tasks in the queue. If the Filter returns false,
// the Task is skipped and not returned to the subscriber.
type Filter func(*Task) bool
type FilterFn func(*Task) bool
// Queue defines a task queue for scheduling tasks among
// a pool of workers.
@ -140,7 +140,7 @@ type Queue interface {
PushAtOnce(c context.Context, tasks []*Task) error
// Poll retrieves and removes a task head of this queue.
Poll(c context.Context, f Filter) (*Task, error)
Poll(c context.Context, f FilterFn) (*Task, error)
// Extend extends the deadline for a task.
Extend(c context.Context, id string) error

View file

@ -110,8 +110,6 @@ func (b *ProcBuilder) Build() ([]*BuildItem, error) {
proc.State = model.StatusSkipped
}
metadata.SetPlatform(parsed.Platform)
ir := b.toInternalRepresentation(parsed, environ, metadata, proc.ID)
if len(ir.Stages) == 0 {
@ -124,7 +122,7 @@ func (b *ProcBuilder) Build() ([]*BuildItem, error) {
Labels: parsed.Labels,
DependsOn: parsed.DependsOn,
RunsOn: parsed.RunsOn,
Platform: metadata.Sys.Arch,
Platform: parsed.Platform,
}
if item.Labels == nil {
item.Labels = map[string]string{}
@ -365,10 +363,10 @@ func metadataFromStruct(repo *model.Repo, build, last *model.Build, proc *model.
Matrix: proc.Environ,
},
Sys: frontend.System{
Name: "woodpecker",
Link: link,
Host: host,
Arch: "linux/amd64",
Name: "woodpecker",
Link: link,
Host: host,
Platform: "", // will be set by pipeline platform option or by agent
},
}
}

View file

@ -1,2 +0,0 @@
*.out
*.txt

View file

@ -1,5 +0,0 @@
Go package for parsing and evaluating SQL expressions.
Documentation:
https://pkg.go.dev/github.com/woodpecker-ci/expr

View file

@ -1,158 +0,0 @@
package expr
import (
"bytes"
"path/filepath"
"regexp"
"github.com/woodpecker-ci/expr/parse"
)
// state represents the state of an execution. It's not part of the
// statement so that multiple executions of the same statement
// can execute in parallel.
type state struct {
node parse.Node
vars Row
}
// at marks the state to be on node n, for error reporting.
func (s *state) at(node parse.Node) {
s.node = node
}
// Walk functions step through the major pieces of the template structure,
// generating output as they go.
func (s *state) walk(node parse.BoolExpr) bool {
s.at(node)
switch node := node.(type) {
case *parse.ComparisonExpr:
return s.eval(node)
case *parse.AndExpr:
return s.walk(node.Left) && s.walk(node.Right)
case *parse.OrExpr:
return s.walk(node.Left) || s.walk(node.Right)
case *parse.NotExpr:
return !s.walk(node.Expr)
case *parse.ParenBoolExpr:
return s.walk(node.Expr)
default:
panic("invalid node type")
}
}
func (s *state) eval(node *parse.ComparisonExpr) bool {
switch node.Operator {
case parse.OperatorEq:
return s.evalEq(node)
case parse.OperatorGt:
return s.evalGt(node)
case parse.OperatorGte:
return s.evalGte(node)
case parse.OperatorLt:
return s.evalLt(node)
case parse.OperatorLte:
return s.evalLte(node)
case parse.OperatorNeq:
return !s.evalEq(node)
case parse.OperatorGlob:
return s.evalGlob(node)
case parse.OperatorNotGlob:
return !s.evalGlob(node)
case parse.OperatorRe:
return s.evalRegexp(node)
case parse.OperatorNotRe:
return !s.evalRegexp(node)
case parse.OperatorIn:
return s.evalIn(node)
case parse.OperatorNotIn:
return !s.evalIn(node)
default:
panic("inalid operator type")
}
}
func (s *state) evalEq(node *parse.ComparisonExpr) bool {
return bytes.Equal(
s.toValue(node.Left),
s.toValue(node.Right),
)
}
func (s *state) evalGt(node *parse.ComparisonExpr) bool {
return bytes.Compare(
s.toValue(node.Left),
s.toValue(node.Right),
) == 1
}
func (s *state) evalGte(node *parse.ComparisonExpr) bool {
return bytes.Compare(
s.toValue(node.Left),
s.toValue(node.Right),
) >= 0
}
func (s *state) evalLt(node *parse.ComparisonExpr) bool {
return bytes.Compare(
s.toValue(node.Left),
s.toValue(node.Right),
) == -1
}
func (s *state) evalLte(node *parse.ComparisonExpr) bool {
return bytes.Compare(
s.toValue(node.Left),
s.toValue(node.Right),
) <= 0
}
func (s *state) evalGlob(node *parse.ComparisonExpr) bool {
match, _ := filepath.Match(
string(s.toValue(node.Right)),
string(s.toValue(node.Left)),
)
return match
}
func (s *state) evalRegexp(node *parse.ComparisonExpr) bool {
match, _ := regexp.Match(
string(s.toValue(node.Right)),
s.toValue(node.Left),
)
return match
}
func (s *state) evalIn(node *parse.ComparisonExpr) bool {
left := s.toValue(node.Left)
right, ok := node.Right.(*parse.ArrayLit)
if !ok {
panic("expected array literal")
}
for _, expr := range right.Values {
if bytes.Equal(left, s.toValue(expr)) {
return true
}
}
return false
}
func (s *state) toValue(expr parse.ValExpr) []byte {
switch node := expr.(type) {
case *parse.Field:
return s.vars.Field(node.Name)
case *parse.BasicLit:
return node.Value
default:
panic("invalid expression type")
}
}
// errRecover is the handler that turns panics into returns.
func errRecover(err *error) {
if e := recover(); e != nil {
*err = e.(error)
}
}

View file

@ -1,5 +0,0 @@
module github.com/woodpecker-ci/expr
go 1.16
require github.com/kr/pretty v0.2.1

View file

@ -1,5 +0,0 @@
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=

View file

@ -1,268 +0,0 @@
package parse
import (
"unicode"
"unicode/utf8"
)
// token is a lexical token.
type token uint
// list of lexical tokens.
const (
// special tokens
tokenIllegal token = iota
tokenEOF
// identifiers and basic type literals
tokenIdent
tokenText
tokenReal
tokenInteger
// operators and delimiters
tokenEq // ==
tokenLt // <
tokenLte // <=
tokenGt // >
tokenGte // >=
tokenNeq // !=
tokenComma // ,
tokenLparen // (
tokenRparen // )
// keywords
tokenNot
tokenAnd
tokenOr
tokenIn
tokenGlob
tokenRegexp
tokenBetween
tokenTrue
tokenFalse
)
// lexer implements a lexical scanner that reads unicode characters
// and tokens from a byte buffer.
type lexer struct {
buf []byte
pos int
start int
width int
}
// scan reads the next token or Unicode character from source and
// returns it. It returns EOF at the end of the source.
func (l *lexer) scan() token {
l.start = l.pos
l.skipWhitespace()
r := l.read()
switch {
case isIdent(r):
l.unread()
return l.scanIdent()
case isQuote(r):
l.unread()
return l.scanQuote()
case isNumeric(r):
l.unread()
return l.scanNumber()
case isCompare(r):
l.unread()
return l.scanCompare()
}
switch r {
case eof:
return tokenEOF
case '(':
return tokenLparen
case ')':
return tokenRparen
case ',':
return tokenComma
}
return tokenIllegal
}
// peek reads the next token or Unicode character from source and
// returns it without advancing the scanner.
func (l *lexer) peek() token {
var (
pos = l.pos
start = l.start
width = l.width
)
tok := l.scan()
l.pos = pos
l.start = start
l.width = width
return tok
}
// bytes returns the bytes corresponding to the most recently scanned
// token. Valid after calling Scan().
func (l *lexer) bytes() []byte {
return l.buf[l.start:l.pos]
}
// string returns the string corresponding to the most recently scanned
// token. Valid after calling Scan().
func (l *lexer) string() string {
return string(l.bytes())
}
// init initializes a scanner with a new buffer.
func (l *lexer) init(buf []byte) {
l.buf = buf
l.pos = 0
l.start = 0
l.width = 0
}
func (l *lexer) scanIdent() token {
for {
if r := l.read(); r == eof {
break
} else if !isIdent(r) {
l.unread()
break
}
}
ident := l.bytes()
switch string(ident) {
case "NOT", "not":
return tokenNot
case "AND", "and":
return tokenAnd
case "OR", "or":
return tokenOr
case "IN", "in":
return tokenIn
case "GLOB", "glob":
return tokenGlob
case "REGEXP", "regexp":
return tokenRegexp
case "BETWEEN", "between":
return tokenBetween
case "TRUE", "true":
return tokenTrue
case "FALSE", "false":
return tokenFalse
}
return tokenIdent
}
func (l *lexer) scanQuote() (tok token) {
l.read() // consume first quote
for {
if r := l.read(); r == eof {
return tokenIllegal
} else if isQuote(r) {
break
}
}
return tokenText
}
func (l *lexer) scanNumber() token {
for {
if r := l.read(); r == eof {
break
} else if !isNumeric(r) {
l.unread()
break
}
}
return tokenInteger
}
func (l *lexer) scanCompare() (tok token) {
switch l.read() {
case '=':
tok = tokenEq
case '!':
tok = tokenNeq
case '>':
tok = tokenGt
case '<':
tok = tokenLt
}
r := l.read()
switch {
case tok == tokenGt && r == '=':
tok = tokenGte
case tok == tokenLt && r == '=':
tok = tokenLte
case tok == tokenEq && r == '=':
tok = tokenEq
case tok == tokenNeq && r == '=':
tok = tokenNeq
case tok == tokenNeq && r != '=':
tok = tokenIllegal
default:
l.unread()
}
return
}
func (l *lexer) skipWhitespace() {
for {
if r := l.read(); r == eof {
break
} else if !isWhitespace(r) {
l.unread()
break
}
}
l.ignore()
}
func (l *lexer) read() rune {
if l.pos >= len(l.buf) {
l.width = 0
return eof
}
r, w := utf8.DecodeRune(l.buf[l.pos:])
l.width = w
l.pos += l.width
return r
}
func (l *lexer) unread() {
l.pos -= l.width
}
func (l *lexer) ignore() {
l.start = l.pos
}
// eof rune sent when end of file is reached
var eof = rune(0)
func isWhitespace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n'
}
func isNumeric(r rune) bool {
return unicode.IsDigit(r) || r == '.'
}
func isQuote(r rune) bool {
return r == '\''
}
func isCompare(r rune) bool {
return r == '=' || r == '!' || r == '>' || r == '<'
}
func isIdent(r rune) bool {
return unicode.IsLetter(r) || r == '_' || r == '-'
}

View file

@ -1,119 +0,0 @@
package parse
// Node is an element in the parse tree.
type Node interface {
node()
}
// ValExpr defines a value expression.
type ValExpr interface {
Node
value()
}
// BoolExpr defines a boolean expression.
type BoolExpr interface {
Node
bool()
}
// An expression is represented by a tree consisting of one
// or more of the following concrete expression nodes.
//
type (
// ComparisonExpr represents a two-value comparison expression.
ComparisonExpr struct {
Operator Operator
Left, Right ValExpr
}
// AndExpr represents an AND expression.
AndExpr struct {
Left, Right BoolExpr
}
// OrExpr represents an OR expression.
OrExpr struct {
Left, Right BoolExpr
}
// NotExpr represents a NOT expression.
NotExpr struct {
Expr BoolExpr
}
// ParenBoolExpr represents a parenthesized boolean expression.
ParenBoolExpr struct {
Expr BoolExpr
}
// BasicLit represents a basic literal.
BasicLit struct {
Kind Literal // INT, REAL, TEXT
Value []byte
}
// ArrayLit represents an array literal.
ArrayLit struct {
Values []ValExpr
}
// Field represents a value lookup by name.
Field struct {
Name []byte
}
)
// Operator identifies the type of operator.
type Operator int
// Comparison operators.
const (
OperatorEq Operator = iota
OperatorLt
OperatorLte
OperatorGt
OperatorGte
OperatorNeq
OperatorIn
OperatorRe
OperatorGlob
OperatorNotIn
OperatorNotRe
OperatorNotGlob
OperatorBetween
OperatorNotBetween
)
// Literal identifies the type of literal.
type Literal int
// The list of possible literal kinds.
const (
LiteralBool Literal = iota
LiteralInt
LiteralReal
LiteralText
)
// node() defines the node in a parse tree
func (x *ComparisonExpr) node() {}
func (x *AndExpr) node() {}
func (x *OrExpr) node() {}
func (x *NotExpr) node() {}
func (x *ParenBoolExpr) node() {}
func (x *BasicLit) node() {}
func (x *ArrayLit) node() {}
func (x *Field) node() {}
// bool() defines the node as a boolean expression.
func (x *ComparisonExpr) bool() {}
func (x *AndExpr) bool() {}
func (x *OrExpr) bool() {}
func (x *NotExpr) bool() {}
func (x *ParenBoolExpr) bool() {}
// value() defines the node as a value expression.
func (x *BasicLit) value() {}
func (x *ArrayLit) value() {}
func (x *Field) value() {}

View file

@ -1,272 +0,0 @@
package parse
import (
"bytes"
"fmt"
)
// Tree is the representation of a single parsed SQL statement.
type Tree struct {
Root BoolExpr
lex *lexer
depth int
}
// Parse parses the SQL statement and returns a Tree.
func Parse(buf []byte) (*Tree, error) {
t := new(Tree)
t.lex = new(lexer)
return t.Parse(buf)
}
// Parse parses the SQL statement buffer to construct an ast
// representation for execution.
func (t *Tree) Parse(buf []byte) (tree *Tree, err error) {
defer t.recover(&err)
t.lex.init(buf)
t.Root = t.parseExpr()
return t, nil
}
// recover is the handler that turns panics into returns.
func (t *Tree) recover(err *error) {
if e := recover(); e != nil {
*err = e.(error)
}
}
// errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...interface{}) {
t.Root = nil
format = fmt.Sprintf("selector: parse error:%d: %s", t.lex.start, format)
panic(fmt.Errorf(format, args...))
}
func (t *Tree) parseExpr() BoolExpr {
switch t.lex.peek() {
case tokenLparen:
t.lex.scan()
return t.parseGroup()
case tokenNot:
t.lex.scan()
return t.parseNot()
}
left := t.parseVal()
node := t.parseComparison(left)
switch t.lex.scan() {
case tokenOr:
return t.parseOr(node)
case tokenAnd:
return t.parseAnd(node)
case tokenRparen:
if t.depth == 0 {
t.errorf("unexpected token")
return nil
}
return node
default:
return node
}
}
func (t *Tree) parseGroup() BoolExpr {
t.depth++
node := t.parseExpr()
t.depth--
switch t.lex.scan() {
case tokenOr:
return t.parseOr(node)
case tokenAnd:
return t.parseAnd(node)
case tokenEOF:
return node
default:
t.errorf("unexpected token")
return nil
}
}
func (t *Tree) parseAnd(left BoolExpr) BoolExpr {
node := new(AndExpr)
node.Left = left
node.Right = t.parseExpr()
return node
}
func (t *Tree) parseOr(left BoolExpr) BoolExpr {
node := new(OrExpr)
node.Left = left
node.Right = t.parseExpr()
return node
}
func (t *Tree) parseNot() BoolExpr {
node := new(NotExpr)
node.Expr = t.parseExpr()
return node
}
func (t *Tree) parseComparison(left ValExpr) BoolExpr {
var negate bool
if t.lex.peek() == tokenNot {
t.lex.scan()
negate = true
}
op := t.parseOperator()
if negate {
switch op {
case OperatorIn:
op = OperatorNotIn
case OperatorGlob:
op = OperatorNotGlob
case OperatorRe:
op = OperatorNotRe
case OperatorBetween:
op = OperatorNotBetween
}
}
switch op {
case OperatorBetween:
return t.parseBetween(left)
case OperatorNotBetween:
return t.parseNotBetween(left)
}
node := new(ComparisonExpr)
node.Left = left
node.Operator = op
switch node.Operator {
case OperatorIn, OperatorNotIn:
node.Right = t.parseList()
case OperatorRe, OperatorNotRe:
// TODO we should use a custom regexp node here that parses and
// compiles the regexp, insteam of recompiling on every evaluation.
node.Right = t.parseVal()
default:
node.Right = t.parseVal()
}
return node
}
func (t *Tree) parseNotBetween(value ValExpr) BoolExpr {
node := new(NotExpr)
node.Expr = t.parseBetween(value)
return node
}
func (t *Tree) parseBetween(value ValExpr) BoolExpr {
left := new(ComparisonExpr)
left.Left = value
left.Operator = OperatorGte
left.Right = t.parseVal()
if t.lex.scan() != tokenAnd {
t.errorf("unexpected token, expecting AND")
return nil
}
right := new(ComparisonExpr)
right.Left = value
right.Operator = OperatorLte
right.Right = t.parseVal()
node := new(AndExpr)
node.Left = left
node.Right = right
return node
}
func (t *Tree) parseOperator() (op Operator) {
switch t.lex.scan() {
case tokenEq:
return OperatorEq
case tokenGt:
return OperatorGt
case tokenGte:
return OperatorGte
case tokenLt:
return OperatorLt
case tokenLte:
return OperatorLte
case tokenNeq:
return OperatorNeq
case tokenIn:
return OperatorIn
case tokenRegexp:
return OperatorRe
case tokenGlob:
return OperatorGlob
case tokenBetween:
return OperatorBetween
default:
t.errorf("illegal operator")
return
}
}
func (t *Tree) parseVal() ValExpr {
switch t.lex.scan() {
case tokenIdent:
node := new(Field)
node.Name = t.lex.bytes()
return node
case tokenText:
return t.parseText()
case tokenReal, tokenInteger, tokenTrue, tokenFalse:
node := new(BasicLit)
node.Value = t.lex.bytes()
return node
default:
t.errorf("illegal value expression")
return nil
}
}
func (t *Tree) parseList() ValExpr {
if t.lex.scan() != tokenLparen {
t.errorf("unexpected token, expecting (")
return nil
}
node := new(ArrayLit)
for {
next := t.lex.peek()
switch next {
case tokenEOF:
t.errorf("unexpected eof, expecting )")
case tokenComma:
t.lex.scan()
case tokenRparen:
t.lex.scan()
return node
default:
child := t.parseVal()
node.Values = append(node.Values, child)
}
}
}
func (t *Tree) parseText() ValExpr {
node := new(BasicLit)
node.Value = t.lex.bytes()
// this is where we strip the starting and ending quote
// and unescape the string. On the surface this might look
// like it is subject to index out of bounds errors but
// it is safe because it is already verified by the lexer.
node.Value = node.Value[1 : len(node.Value)-1]
node.Value = bytes.Replace(node.Value, quoteEscaped, quoteUnescaped, -1)
return node
}
var (
quoteEscaped = []byte("\\'")
quoteUnescaped = []byte("'")
)

View file

@ -1,50 +0,0 @@
package expr
import "github.com/woodpecker-ci/expr/parse"
// Selector reprents a parsed SQL selector statement.
type Selector struct {
*parse.Tree
}
// Parse parses the SQL statement and returns a new Statement object.
func Parse(b []byte) (selector *Selector, err error) {
selector = new(Selector)
selector.Tree, err = parse.Parse(b)
return
}
// ParseString parses the SQL statement and returns a new Statement object.
func ParseString(s string) (selector *Selector, err error) {
return Parse([]byte(s))
}
// Eval evaluates the SQL statement using the provided data and returns true
// if all conditions are satisfied. If a runtime error is experiences a false
// value is returned along with an error message.
func (s *Selector) Eval(row Row) (match bool, err error) {
defer errRecover(&err)
state := &state{vars: row}
match = state.walk(s.Root)
return
}
// Row defines a row of columnar data.
//
// Note that the field name and field values are represented as []byte
// since stomp header names and values are represented as []byte to avoid
// extra allocations when converting from []byte to string.
type Row interface {
Field([]byte) []byte
}
// NewRow return a Row bound to a map of key value strings.
func NewRow(m map[string]string) Row {
return mapRow(m)
}
type mapRow map[string]string
func (m mapRow) Field(name []byte) []byte {
return []byte(m[string(name)])
}

4
vendor/modules.txt vendored
View file

@ -606,10 +606,6 @@ github.com/ultraware/whitespace
github.com/urfave/cli/v2
# github.com/uudashr/gocognit v1.0.5
github.com/uudashr/gocognit
# github.com/woodpecker-ci/expr v0.0.0-20210628233344-164b8b3d0915
## explicit
github.com/woodpecker-ci/expr
github.com/woodpecker-ci/expr/parse
# github.com/xanzy/go-gitlab v0.64.0
## explicit
github.com/xanzy/go-gitlab