mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-27 19:00:35 +00:00
commit
6bae1b6c83
46 changed files with 427 additions and 181 deletions
|
@ -15,7 +15,7 @@ pipeline:
|
|||
- go get -u golang.org/x/net/context/ctxhttp
|
||||
- go get -u github.com/golang/protobuf/proto
|
||||
- go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
- go test -cover $(go list ./... | grep -v /vendor/)
|
||||
- go test -cover -timeout 30s $(go list ./... | grep -v /vendor/)
|
||||
|
||||
test_postgres:
|
||||
image: golang:1.12.4
|
||||
|
@ -23,7 +23,7 @@ pipeline:
|
|||
- DATABASE_DRIVER=postgres
|
||||
- DATABASE_CONFIG=host=postgres user=postgres dbname=postgres sslmode=disable
|
||||
commands:
|
||||
- go test github.com/laszlocph/woodpecker/store/datastore
|
||||
- go test -timeout 30s github.com/laszlocph/woodpecker/store/datastore
|
||||
|
||||
test_mysql:
|
||||
image: golang:1.12.4
|
||||
|
@ -31,7 +31,7 @@ pipeline:
|
|||
- DATABASE_DRIVER=mysql
|
||||
- DATABASE_CONFIG=root@tcp(mysql:3306)/test?parseTime=true
|
||||
commands:
|
||||
- go test github.com/laszlocph/woodpecker/store/datastore
|
||||
- go test -timeout 30s github.com/laszlocph/woodpecker/store/datastore
|
||||
|
||||
build:
|
||||
image: golang:1.12.4
|
||||
|
|
13
BUILDING
13
BUILDING
|
@ -1,15 +1,6 @@
|
|||
1. Install go 1.9 or later
|
||||
2. Install dependencies
|
||||
|
||||
go get -u golang.org/x/net/context
|
||||
go get -u golang.org/x/net/context/ctxhttp
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
3. Install binaries to $GOPATH/bin
|
||||
|
||||
go install github.com/laszlocph/woodpecker/cmd/drone-agent
|
||||
go install github.com/laszlocph/woodpecker/cmd/drone-server
|
||||
2. Execute `make deps` to download dependencies
|
||||
3. Execute `make install` to compile project and install binaries to `GOPATH`
|
||||
|
||||
---
|
||||
|
||||
|
|
46
Makefile
Normal file
46
Makefile
Normal file
|
@ -0,0 +1,46 @@
|
|||
export GO111MODULE=off
|
||||
|
||||
GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./.git/*")
|
||||
|
||||
all: deps build
|
||||
|
||||
deps:
|
||||
go get -u golang.org/x/net/context
|
||||
go get -u golang.org/x/net/context/ctxhttp
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
formatcheck:
|
||||
([ -z "$(shell gofmt -d $(GOFILES_NOVENDOR))" ]) || (echo "Source is unformatted"; exit 1)
|
||||
|
||||
format:
|
||||
@gofmt -w ${GOFILES_NOVENDOR}
|
||||
|
||||
test-agent:
|
||||
go test -timeout 30s github.com/laszlocph/woodpecker/cmd/drone-agent $(go list ./... | grep -v /vendor/)
|
||||
|
||||
test-server:
|
||||
ifneq ($(shell uname), "Linux")
|
||||
$(error Target OS is not Linux drone-server build skipped)
|
||||
endif
|
||||
go test -timeout 30s github.com/laszlocph/woodpecker/cmd/drone-server
|
||||
|
||||
test-lib:
|
||||
go test -timeout 30s $(shell go list ./... | grep -v '/cmd/')
|
||||
|
||||
test: test-lib test-agent test-server
|
||||
|
||||
build-agent:
|
||||
go build -o build/drone-agent github.com/laszlocph/woodpecker/cmd/drone-agent
|
||||
|
||||
build-server:
|
||||
ifneq ($(shell uname), "Linux")
|
||||
$(error Target OS is not Linux drone-server build skipped)
|
||||
endif
|
||||
go build -o build/drone-server github.com/laszlocph/woodpecker/cmd/drone-server
|
||||
|
||||
build: build-agent build-server
|
||||
|
||||
install:
|
||||
go install github.com/laszlocph/woodpecker/cmd/drone-agent
|
||||
go install github.com/laszlocph/woodpecker/cmd/drone-server
|
2
build/.gitignore
vendored
Normal file
2
build/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
drone-agent
|
||||
drone-server
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drone/envsubst"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/backend"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/backend/docker"
|
||||
|
@ -20,7 +21,6 @@ import (
|
|||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/frontend/yaml/linter"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/interrupt"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/multipart"
|
||||
"github.com/drone/envsubst"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
@ -275,7 +275,7 @@ var Command = cli.Command{
|
|||
EnvVar: "DRONE_JOB_NUMBER",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "env, e",
|
||||
Name: "env, e",
|
||||
EnvVar: "DRONE_ENV",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,4 +12,4 @@ pipeline:
|
|||
test:
|
||||
image: golang:1.8
|
||||
commands:
|
||||
- go test -cover github.com/laszlocph/woodpecker/cncd/pipeline/...
|
||||
- go test -cover -timeout 30s github.com/laszlocph/woodpecker/cncd/pipeline/...
|
||||
|
|
|
@ -3,9 +3,9 @@ package yaml
|
|||
import (
|
||||
"path/filepath"
|
||||
|
||||
libcompose "github.com/docker/libcompose/yaml"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/frontend"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/frontend/yaml/types"
|
||||
libcompose "github.com/docker/libcompose/yaml"
|
||||
)
|
||||
|
||||
type (
|
||||
|
|
|
@ -25,4 +25,4 @@ func (b *BoolTrue) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
// Bool returns the bool value.
|
||||
func (b BoolTrue) Bool() bool {
|
||||
return !b.value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package rpc
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/backend"
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/rpc/proto"
|
||||
|
|
|
@ -6,6 +6,6 @@ import (
|
|||
|
||||
// Health defines a health-check connection.
|
||||
type Health interface {
|
||||
// Check returns if server is healthy or not
|
||||
// Check returns if server is healthy or not
|
||||
Check(c context.Context) (bool, error)
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ package bitbucketserver
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/laszlocph/woodpecker/remote/bitbucketserver/internal"
|
||||
"github.com/franela/goblin"
|
||||
"github.com/mrjones/oauth"
|
||||
)
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/laszlocph/woodpecker/remote/gitea/fixtures"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_parse(t *testing.T) {
|
||||
|
|
|
@ -69,13 +69,13 @@ type pullRequestHook struct {
|
|||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar_url"`
|
||||
} `json:"user"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
State string `json:"state"`
|
||||
URL string `json:"html_url"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
Merged bool `json:"merged"`
|
||||
MergeBase string `json:"merge_base"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
State string `json:"state"`
|
||||
URL string `json:"html_url"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
Merged bool `json:"merged"`
|
||||
MergeBase string `json:"merge_base"`
|
||||
Base struct {
|
||||
Label string `json:"label"`
|
||||
Ref string `json:"ref"`
|
||||
|
|
|
@ -17,8 +17,8 @@ package github
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/laszlocph/woodpecker/remote/github/fixtures"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_parser(t *testing.T) {
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/laszlocph/woodpecker/remote/gitlab/testdata"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_Gitlab(t *testing.T) {
|
||||
|
|
6
remote/gitlab/testdata/hooks.go
vendored
6
remote/gitlab/testdata/hooks.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab/testdata/oauth.go
vendored
6
remote/gitlab/testdata/oauth.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab/testdata/projects.go
vendored
6
remote/gitlab/testdata/projects.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab/testdata/testdata.go
vendored
6
remote/gitlab/testdata/testdata.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab/testdata/users.go
vendored
6
remote/gitlab/testdata/users.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/laszlocph/woodpecker/remote/gitlab3/testdata"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_Gitlab(t *testing.T) {
|
||||
|
|
6
remote/gitlab3/testdata/hooks.go
vendored
6
remote/gitlab3/testdata/hooks.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab3/testdata/oauth.go
vendored
6
remote/gitlab3/testdata/oauth.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab3/testdata/projects.go
vendored
6
remote/gitlab3/testdata/projects.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab3/testdata/testdata.go
vendored
6
remote/gitlab3/testdata/testdata.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
6
remote/gitlab3/testdata/users.go
vendored
6
remote/gitlab3/testdata/users.go
vendored
|
@ -1,11 +1,11 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
//
|
||||
// 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.
|
||||
|
|
|
@ -22,8 +22,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/gogits/go-gogs-client"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
)
|
||||
|
||||
// helper function that converts a Gogs repository to a Drone repository.
|
||||
|
|
|
@ -68,14 +68,14 @@ type pullRequestHook struct {
|
|||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar_url"`
|
||||
} `json:"user"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
State string `json:"state"`
|
||||
URL string `json:"html_url"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
Merged bool `json:"merged"`
|
||||
MergeBase string `json:"merge_base"`
|
||||
BaseBranch string `json:"base_branch"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
State string `json:"state"`
|
||||
URL string `json:"html_url"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
Merged bool `json:"merged"`
|
||||
MergeBase string `json:"merge_base"`
|
||||
BaseBranch string `json:"base_branch"`
|
||||
Base struct {
|
||||
Label string `json:"label"`
|
||||
Ref string `json:"ref"`
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/woodpecker/version"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/woodpecker/version"
|
||||
)
|
||||
|
||||
// NoCache is a middleware function that appends headers
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/laszlocph/woodpecker/remote"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/woodpecker/remote"
|
||||
)
|
||||
|
||||
// Remote is a middleware function that initializes the Remote and attaches to
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"github.com/laszlocph/woodpecker/shared/token"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/woodpecker/shared/token"
|
||||
)
|
||||
|
||||
// AuthorizeAgent authorizes requsts from build agents to access the queue.
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/laszlocph/woodpecker/version"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/woodpecker/version"
|
||||
)
|
||||
|
||||
// Version is a middleware function that appends the Drone version information
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
@ -185,14 +186,10 @@ func DeleteBuild(c *gin.Context) {
|
|||
continue
|
||||
}
|
||||
|
||||
proc.State = model.StatusKilled
|
||||
proc.Stopped = time.Now().Unix()
|
||||
if proc.Started == 0 {
|
||||
proc.Started = proc.Stopped
|
||||
}
|
||||
proc.ExitCode = 137
|
||||
// TODO cancel child procs
|
||||
store.FromContext(c).ProcUpdate(proc)
|
||||
if _, err = UpdateProcToStatusKilled(store.FromContext(c), *proc); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d state: %s", proc.ID, err)
|
||||
}
|
||||
|
||||
Config.Services.Queue.Error(context.Background(), fmt.Sprint(proc.ID), queue.ErrCancel)
|
||||
cancelled = true
|
||||
|
@ -235,23 +232,19 @@ func ZombieKill(c *gin.Context) {
|
|||
|
||||
for _, proc := range procs {
|
||||
if proc.Running() {
|
||||
proc.State = model.StatusKilled
|
||||
proc.ExitCode = 137
|
||||
proc.Stopped = time.Now().Unix()
|
||||
if proc.Started == 0 {
|
||||
proc.Started = proc.Stopped
|
||||
if _, err := UpdateProcToStatusKilled(store.FromContext(c), *proc); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d state: %s", proc.ID, err)
|
||||
}
|
||||
} else {
|
||||
store.FromContext(c).ProcUpdate(proc)
|
||||
}
|
||||
}
|
||||
|
||||
for _, proc := range procs {
|
||||
store.FromContext(c).ProcUpdate(proc)
|
||||
Config.Services.Queue.Error(context.Background(), fmt.Sprint(proc.ID), queue.ErrCancel)
|
||||
}
|
||||
|
||||
build.Status = model.StatusKilled
|
||||
build.Finished = time.Now().Unix()
|
||||
store.FromContext(c).UpdateBuild(build)
|
||||
if _, err := UpdateToStatusKilled(store.FromContext(c), *build); err != nil {
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.String(204, "")
|
||||
}
|
||||
|
@ -275,9 +268,6 @@ func PostApproval(c *gin.Context) {
|
|||
c.String(500, "cannot decline a build with status %s", build.Status)
|
||||
return
|
||||
}
|
||||
build.Status = model.StatusPending
|
||||
build.Reviewed = time.Now().Unix()
|
||||
build.Reviewer = user.Login
|
||||
|
||||
// fetch the build file from the database
|
||||
configs, err := Config.Storage.Config.ConfigsForBuild(build.ID)
|
||||
|
@ -289,12 +279,12 @@ func PostApproval(c *gin.Context) {
|
|||
|
||||
netrc, err := remote_.Netrc(user, repo)
|
||||
if err != nil {
|
||||
c.String(500, "Failed to generate netrc file. %s", err)
|
||||
c.String(500, "failed to generate netrc file. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if uerr := store.UpdateBuild(c, build); err != nil {
|
||||
c.String(500, "error updating build. %s", uerr)
|
||||
if build, err = UpdateToStatusPending(store.FromContext(c), *build, user.Login); err != nil {
|
||||
c.String(500, "error updating build. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -337,11 +327,9 @@ func PostApproval(c *gin.Context) {
|
|||
}
|
||||
buildItems, err := b.Build()
|
||||
if err != nil {
|
||||
build.Status = model.StatusError
|
||||
build.Started = time.Now().Unix()
|
||||
build.Finished = build.Started
|
||||
build.Error = err.Error()
|
||||
store.UpdateBuild(c, build)
|
||||
if _, err = UpdateToStatusError(store.FromContext(c), *build, err); err != nil {
|
||||
logrus.Errorf("Error setting error status of build for %s#%d. %s", repo.FullName, build.Number, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
build = setBuildStepsOnBuild(b.Curr, buildItems)
|
||||
|
@ -388,12 +376,8 @@ func PostDecline(c *gin.Context) {
|
|||
c.String(500, "cannot decline a build with status %s", build.Status)
|
||||
return
|
||||
}
|
||||
build.Status = model.StatusDeclined
|
||||
build.Reviewed = time.Now().Unix()
|
||||
build.Reviewer = user.Login
|
||||
|
||||
err = store.UpdateBuild(c, build)
|
||||
if err != nil {
|
||||
if _, err = UpdateToStatusDeclined(store.FromContext(c), *build, user.Login); err != nil {
|
||||
c.String(500, "error updating build. %s", err)
|
||||
return
|
||||
}
|
||||
|
|
120
server/buildStatus_test.go
Normal file
120
server/buildStatus_test.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2019 mhmxs.
|
||||
//
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
)
|
||||
|
||||
const TIMEOUT = 3 * time.Second
|
||||
|
||||
type mockUpdateBuildStore struct {
|
||||
}
|
||||
|
||||
func (m *mockUpdateBuildStore) UpdateBuild(build *model.Build) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestUpdateToStatusRunning(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
build, _ := UpdateToStatusRunning(&mockUpdateBuildStore{}, model.Build{}, int64(1))
|
||||
|
||||
if model.StatusRunning != build.Status {
|
||||
t.Errorf("Build status not equals '%s' != '%s'", model.StatusRunning, build.Status)
|
||||
} else if int64(1) != build.Started {
|
||||
t.Errorf("Build started not equals 1 != %d", build.Started)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateToStatusPending(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
build, _ := UpdateToStatusPending(&mockUpdateBuildStore{}, model.Build{}, "Reviewer")
|
||||
|
||||
if model.StatusPending != build.Status {
|
||||
t.Errorf("Build status not equals '%s' != '%s'", model.StatusPending, build.Status)
|
||||
} else if "Reviewer" != build.Reviewer {
|
||||
t.Errorf("Reviewer not equals 'Reviewer' != '%s'", build.Reviewer)
|
||||
} else if now > build.Reviewed {
|
||||
t.Errorf("Reviewed not updated %d !< %d", now, build.Reviewed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateToStatusDeclined(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
build, _ := UpdateToStatusDeclined(&mockUpdateBuildStore{}, model.Build{}, "Reviewer")
|
||||
|
||||
if model.StatusDeclined != build.Status {
|
||||
t.Errorf("Build status not equals '%s' != '%s'", model.StatusDeclined, build.Status)
|
||||
} else if "Reviewer" != build.Reviewer {
|
||||
t.Errorf("Reviewer not equals 'Reviewer' != '%s'", build.Reviewer)
|
||||
} else if now > build.Reviewed {
|
||||
t.Errorf("Reviewed not updated %d !< %d", now, build.Reviewed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateToStatusToDone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
build, _ := UpdateStatusToDone(&mockUpdateBuildStore{}, model.Build{}, "status", int64(1))
|
||||
|
||||
if "status" != build.Status {
|
||||
t.Errorf("Build status not equals 'status' != '%s'", build.Status)
|
||||
} else if int64(1) != build.Finished {
|
||||
t.Errorf("Build finished not equals 1 != %d", build.Finished)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateToStatusError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
build, _ := UpdateToStatusError(&mockUpdateBuildStore{}, model.Build{}, errors.New("error"))
|
||||
|
||||
if "error" != build.Error {
|
||||
t.Errorf("Build error not equals 'error' != '%s'", build.Error)
|
||||
} else if model.StatusError != build.Status {
|
||||
t.Errorf("Build status not equals '%s' != '%s'", model.StatusError, build.Status)
|
||||
} else if now > build.Started {
|
||||
t.Errorf("Started not updated %d !< %d", now, build.Started)
|
||||
} else if build.Started != build.Finished {
|
||||
t.Errorf("Build started and finished not equals %d != %d", build.Started, build.Finished)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateToStatusKilled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().Unix()
|
||||
|
||||
build, _ := UpdateToStatusKilled(&mockUpdateBuildStore{}, model.Build{})
|
||||
|
||||
if model.StatusKilled != build.Status {
|
||||
t.Errorf("Build status not equals '%s' != '%s'", model.StatusKilled, build.Status)
|
||||
} else if now > build.Finished {
|
||||
t.Errorf("Finished not updated %d !< %d", now, build.Finished)
|
||||
}
|
||||
}
|
65
server/builsStatus.go
Normal file
65
server/builsStatus.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2019 mhmxs.
|
||||
//
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
)
|
||||
|
||||
type UpdateBuildStore interface {
|
||||
UpdateBuild(*model.Build) error
|
||||
}
|
||||
|
||||
func UpdateToStatusRunning(store UpdateBuildStore, build model.Build, started int64) (*model.Build, error) {
|
||||
build.Status = model.StatusRunning
|
||||
build.Started = started
|
||||
return &build, store.UpdateBuild(&build)
|
||||
}
|
||||
|
||||
func UpdateToStatusPending(store UpdateBuildStore, build model.Build, reviewer string) (*model.Build, error) {
|
||||
build.Reviewer = reviewer
|
||||
build.Status = model.StatusPending
|
||||
build.Reviewed = time.Now().Unix()
|
||||
return &build, store.UpdateBuild(&build)
|
||||
}
|
||||
|
||||
func UpdateToStatusDeclined(store UpdateBuildStore, build model.Build, reviewer string) (*model.Build, error) {
|
||||
build.Reviewer = reviewer
|
||||
build.Status = model.StatusDeclined
|
||||
build.Reviewed = time.Now().Unix()
|
||||
return &build, store.UpdateBuild(&build)
|
||||
}
|
||||
|
||||
func UpdateStatusToDone(store UpdateBuildStore, build model.Build, status string, stopped int64) (*model.Build, error) {
|
||||
build.Status = status
|
||||
build.Finished = stopped
|
||||
return &build, store.UpdateBuild(&build)
|
||||
}
|
||||
|
||||
func UpdateToStatusError(store UpdateBuildStore, build model.Build, err error) (*model.Build, error) {
|
||||
build.Error = err.Error()
|
||||
build.Status = model.StatusError
|
||||
build.Started = time.Now().Unix()
|
||||
build.Finished = build.Started
|
||||
return &build, store.UpdateBuild(&build)
|
||||
}
|
||||
|
||||
func UpdateToStatusKilled(store UpdateBuildStore, build model.Build) (*model.Build, error) {
|
||||
build.Status = model.StatusKilled
|
||||
build.Finished = time.Now().Unix()
|
||||
return &build, store.UpdateBuild(&build)
|
||||
}
|
|
@ -20,9 +20,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/woodpecker/router/middleware/session"
|
||||
"github.com/laszlocph/woodpecker/store"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// FileList gets a list file by build.
|
||||
|
|
|
@ -259,11 +259,9 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
buildItems, err := b.Build()
|
||||
if err != nil {
|
||||
build.Status = model.StatusError
|
||||
build.Started = time.Now().Unix()
|
||||
build.Finished = build.Started
|
||||
build.Error = err.Error()
|
||||
store.UpdateBuild(c, build)
|
||||
if _, err = UpdateToStatusError(store.FromContext(c), *build, err); err != nil {
|
||||
logrus.Errorf("Error setting error status of build for %s#%d. %s", repo.FullName, build.Number, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
build = setBuildStepsOnBuild(b.Curr, buildItems)
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/laszlocph/woodpecker/server"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/woodpecker/server"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
|
|
89
server/procStatus.go
Normal file
89
server/procStatus.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2019 mhmxs.
|
||||
//
|
||||
// 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 server
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/woodpecker/cncd/pipeline/pipeline/rpc"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
)
|
||||
|
||||
type UpdateProcStore interface {
|
||||
ProcUpdate(*model.Proc) error
|
||||
}
|
||||
|
||||
func UpdateProcStatus(store UpdateProcStore, proc model.Proc, state rpc.State, started int64) (*model.Proc, error) {
|
||||
if state.Exited {
|
||||
proc.Stopped = state.Finished
|
||||
proc.ExitCode = state.ExitCode
|
||||
proc.Error = state.Error
|
||||
proc.State = model.StatusSuccess
|
||||
if state.ExitCode != 0 || state.Error != "" {
|
||||
proc.State = model.StatusFailure
|
||||
}
|
||||
if state.ExitCode == 137 {
|
||||
proc.State = model.StatusKilled
|
||||
}
|
||||
} else {
|
||||
proc.Started = state.Started
|
||||
proc.State = model.StatusRunning
|
||||
}
|
||||
|
||||
if proc.Started == 0 && proc.Stopped != 0 {
|
||||
proc.Started = started
|
||||
}
|
||||
return &proc, store.ProcUpdate(&proc)
|
||||
}
|
||||
|
||||
func UpdateProcToStatusStarted(store UpdateProcStore, proc model.Proc, state rpc.State) (*model.Proc, error) {
|
||||
proc.Started = state.Started
|
||||
proc.State = model.StatusRunning
|
||||
return &proc, store.ProcUpdate(&proc)
|
||||
}
|
||||
|
||||
func UpdateProcToStatusSkipped(store UpdateProcStore, proc model.Proc, stopped int64) (*model.Proc, error) {
|
||||
proc.State = model.StatusSkipped
|
||||
if proc.Started != 0 {
|
||||
proc.State = model.StatusSuccess // for deamons that are killed
|
||||
proc.Stopped = stopped
|
||||
}
|
||||
return &proc, store.ProcUpdate(&proc)
|
||||
}
|
||||
|
||||
func UpdateProcStatusToDone(store UpdateProcStore, proc model.Proc, state rpc.State) (*model.Proc, error) {
|
||||
proc.Stopped = state.Finished
|
||||
proc.Error = state.Error
|
||||
proc.ExitCode = state.ExitCode
|
||||
if state.Started == 0 {
|
||||
proc.State = model.StatusSkipped
|
||||
} else {
|
||||
proc.State = model.StatusSuccess
|
||||
}
|
||||
if proc.ExitCode != 0 || proc.Error != "" {
|
||||
proc.State = model.StatusFailure
|
||||
}
|
||||
return &proc, store.ProcUpdate(&proc)
|
||||
}
|
||||
|
||||
func UpdateProcToStatusKilled(store UpdateProcStore, proc model.Proc) (*model.Proc, error) {
|
||||
proc.State = model.StatusKilled
|
||||
proc.Stopped = time.Now().Unix()
|
||||
if proc.Started == 0 {
|
||||
proc.Started = proc.Stopped
|
||||
}
|
||||
proc.ExitCode = 137
|
||||
return &proc, store.ProcUpdate(&proc)
|
||||
}
|
|
@ -179,27 +179,7 @@ func (s *RPC) Update(c context.Context, id string, state rpc.State) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if state.Exited {
|
||||
proc.Stopped = state.Finished
|
||||
proc.ExitCode = state.ExitCode
|
||||
proc.Error = state.Error
|
||||
proc.State = model.StatusSuccess
|
||||
if state.ExitCode != 0 || state.Error != "" {
|
||||
proc.State = model.StatusFailure
|
||||
}
|
||||
if state.ExitCode == 137 {
|
||||
proc.State = model.StatusKilled
|
||||
}
|
||||
} else {
|
||||
proc.Started = state.Started
|
||||
proc.State = model.StatusRunning
|
||||
}
|
||||
|
||||
if proc.Started == 0 && proc.Stopped != 0 {
|
||||
proc.Started = build.Started
|
||||
}
|
||||
|
||||
if err := s.store.ProcUpdate(proc); err != nil {
|
||||
if proc, err = UpdateProcStatus(s.store, *proc, state, build.Started); err != nil {
|
||||
log.Printf("error: rpc.update: cannot update proc: %s", err)
|
||||
}
|
||||
|
||||
|
@ -326,9 +306,7 @@ func (s *RPC) Init(c context.Context, id string, state rpc.State) error {
|
|||
}
|
||||
|
||||
if build.Status == model.StatusPending {
|
||||
build.Status = model.StatusRunning
|
||||
build.Started = state.Started
|
||||
if err := s.store.UpdateBuild(build); err != nil {
|
||||
if build, err = UpdateToStatusRunning(s.store, *build, state.Started); err != nil {
|
||||
log.Printf("error: init: cannot update build_id %d state: %s", build.ID, err)
|
||||
}
|
||||
}
|
||||
|
@ -348,9 +326,8 @@ func (s *RPC) Init(c context.Context, id string, state rpc.State) error {
|
|||
s.pubsub.Publish(c, "topic/events", message)
|
||||
}()
|
||||
|
||||
proc.Started = state.Started
|
||||
proc.State = model.StatusRunning
|
||||
return s.store.ProcUpdate(proc)
|
||||
_, err = UpdateProcToStatusStarted(s.store, *proc, state)
|
||||
return err
|
||||
}
|
||||
|
||||
// Done implements the rpc.Done function
|
||||
|
@ -378,7 +355,9 @@ func (s *RPC) Done(c context.Context, id string, state rpc.State) error {
|
|||
return err
|
||||
}
|
||||
|
||||
s.updateProcState(proc, state)
|
||||
if proc, err = UpdateProcStatusToDone(s.store, *proc, state); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d state: %s", proc.ID, err)
|
||||
}
|
||||
|
||||
var queueErr error
|
||||
if proc.Failing() {
|
||||
|
@ -394,9 +373,7 @@ func (s *RPC) Done(c context.Context, id string, state rpc.State) error {
|
|||
s.completeChildrenIfParentCompleted(procs, proc)
|
||||
|
||||
if !isThereRunningStage(procs) {
|
||||
build.Status = buildStatus(procs)
|
||||
build.Finished = proc.Stopped
|
||||
if err := s.store.UpdateBuild(build); err != nil {
|
||||
if build, err = UpdateStatusToDone(s.store, *build, buildStatus(procs), proc.Stopped); err != nil {
|
||||
log.Printf("error: done: cannot update build_id %d final state: %s", build.ID, err)
|
||||
}
|
||||
|
||||
|
@ -444,32 +421,10 @@ func (s *RPC) Log(c context.Context, id string, line *rpc.Line) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *RPC) updateProcState(proc *model.Proc, state rpc.State) {
|
||||
proc.Stopped = state.Finished
|
||||
proc.Error = state.Error
|
||||
proc.ExitCode = state.ExitCode
|
||||
if state.Started == 0 {
|
||||
proc.State = model.StatusSkipped
|
||||
} else {
|
||||
proc.State = model.StatusSuccess
|
||||
}
|
||||
if proc.ExitCode != 0 || proc.Error != "" {
|
||||
proc.State = model.StatusFailure
|
||||
}
|
||||
if err := s.store.ProcUpdate(proc); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d state: %s", proc.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RPC) completeChildrenIfParentCompleted(procs []*model.Proc, completedProc *model.Proc) {
|
||||
for _, p := range procs {
|
||||
if p.Running() && p.PPID == completedProc.PID {
|
||||
p.State = model.StatusSkipped
|
||||
if p.Started != 0 {
|
||||
p.State = model.StatusSuccess // for deamons that are killed
|
||||
p.Stopped = completedProc.Stopped
|
||||
}
|
||||
if err := s.store.ProcUpdate(p); err != nil {
|
||||
if _, err := UpdateProcToStatusSkipped(s.store, *p, completedProc.Stopped); err != nil {
|
||||
log.Printf("error: done: cannot update proc_id %d child state: %s", p.ID, err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,10 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/laszlocph/woodpecker-ui/dist"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/laszlocph/woodpecker/shared/token"
|
||||
"github.com/laszlocph/woodpecker/version"
|
||||
"github.com/laszlocph/woodpecker-ui/dist"
|
||||
|
||||
"github.com/dimfeld/httptreemux"
|
||||
)
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/laszlocph/woodpecker/store"
|
||||
"github.com/laszlocph/woodpecker/version"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Health endpoint returns a 500 if the server state is unhealthy.
|
||||
|
|
|
@ -17,8 +17,8 @@ package datastore
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
)
|
||||
|
||||
func TestRepos(t *testing.T) {
|
||||
|
|
|
@ -17,8 +17,8 @@ package datastore
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
"github.com/franela/goblin"
|
||||
"github.com/laszlocph/woodpecker/model"
|
||||
)
|
||||
|
||||
func TestUsers(t *testing.T) {
|
||||
|
|
|
@ -249,7 +249,3 @@ func GetBuildQueue(c context.Context) ([]*model.Feed, error) {
|
|||
func CreateBuild(c context.Context, build *model.Build, procs ...*model.Proc) error {
|
||||
return FromContext(c).CreateBuild(build, procs...)
|
||||
}
|
||||
|
||||
func UpdateBuild(c context.Context, build *model.Build) error {
|
||||
return FromContext(c).UpdateBuild(build)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue