mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-05-13 03:32:38 +00:00
Compare commits
12 commits
ae1cfd2ecd
...
64e5f0f13d
Author | SHA1 | Date | |
---|---|---|---|
64e5f0f13d | |||
c6b2cd8a48 | |||
325b1b5e57 | |||
4b1ff6d1a7 | |||
2c3cd83402 | |||
a230e88c3a | |||
2d66cfcce2 | |||
daf673a857 | |||
d0057736f1 | |||
9972c24924 | |||
b5bc1cf48a | |||
b2cfa37682 |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -13,7 +13,7 @@
|
|||
*.so
|
||||
*.dylib
|
||||
vendor/
|
||||
__debug_bin
|
||||
__debug_bin*
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
|
|
@ -3,7 +3,7 @@ when:
|
|||
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22.2'
|
||||
- &node_image 'docker.io/node:21-alpine'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.1'
|
||||
- &xgo_version 'go-1.21.2'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22.2'
|
||||
- &node_image 'docker.io/node:21-alpine'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.1'
|
||||
- &xgo_version 'go-1.21.2'
|
||||
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:3.2.1'
|
||||
|
|
|
@ -13,7 +13,7 @@ steps:
|
|||
branch: renovate/*
|
||||
|
||||
- name: spellcheck
|
||||
image: docker.io/node:21-alpine
|
||||
image: docker.io/node:22-alpine
|
||||
depends_on: []
|
||||
commands:
|
||||
- corepack enable
|
||||
|
|
|
@ -6,7 +6,7 @@ when:
|
|||
- renovate/*
|
||||
|
||||
variables:
|
||||
- &node_image 'docker.io/node:21-alpine'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
- &when
|
||||
path:
|
||||
# related config files
|
||||
|
|
4
Makefile
4
Makefile
|
@ -306,6 +306,10 @@ bundle-cli: bundle-prepare ## Create bundles for cli
|
|||
.PHONY: bundle
|
||||
bundle: bundle-agent bundle-server bundle-cli ## Create all bundles
|
||||
|
||||
.PHONY: spellcheck
|
||||
spellcheck:
|
||||
pnpx cspell lint --no-progress --gitignore '{**,.*}/{*,.*}'
|
||||
|
||||
##@ Docs
|
||||
.PHONY: docs
|
||||
docs: ## Generate docs (currently only for the cli)
|
||||
|
|
|
@ -2174,6 +2174,18 @@ const docTemplate = `{
|
|||
"description": "for response pagination, max items per page",
|
||||
"name": "perPage",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "only return pipelines before this RFC3339 date",
|
||||
"name": "before",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "only return pipelines after this RFC3339 date",
|
||||
"name": "after",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -2327,6 +2339,44 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Delete pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the repository id",
|
||||
"name": "repo_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the number of the pipeline",
|
||||
"name": "number",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/pipelines/{number}/approve": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local .
|
||||
FROM docker.io/golang:1.22-alpine3.19 as golang_image
|
||||
FROM docker.io/node:21-alpine3.19
|
||||
FROM docker.io/node:22-alpine3.19
|
||||
|
||||
# renovate: datasource=repology depName=alpine_3_19/make versioning=loose
|
||||
ENV MAKE_VERSION="4.4.1-r2"
|
||||
|
@ -20,6 +20,7 @@ RUN apk add --no-cache --update make=${MAKE_VERSION} gcc=${GCC_VERSION} binutils
|
|||
COPY --from=golang_image /usr/local/go /usr/local/go
|
||||
COPY Makefile /
|
||||
ENV PATH=$PATH:/usr/local/go/bin
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
# Cache tools
|
||||
RUN GOBIN=/usr/local/go/bin make install-tools && \
|
||||
|
|
|
@ -359,20 +359,6 @@ when:
|
|||
- platform: [linux/*, windows/amd64]
|
||||
```
|
||||
|
||||
<!-- markdownlint-disable no-duplicate-heading -->
|
||||
|
||||
#### `environment`
|
||||
|
||||
<!-- markdownlint-enable no-duplicate-heading -->
|
||||
|
||||
Execute a step for deployment events matching the target deployment environment:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- environment: production
|
||||
- event: deployment
|
||||
```
|
||||
|
||||
#### `matrix`
|
||||
|
||||
Execute a step for a single matrix permutation:
|
||||
|
@ -758,7 +744,7 @@ Workflows that should run even on failure should set the `runs_on` tag. See [her
|
|||
Woodpecker gives the ability to configure privileged mode in the YAML. You can use this parameter to launch containers with escalated capabilities.
|
||||
|
||||
:::info
|
||||
Privileged mode is only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./71-project-settings.md#trusted) to enable trusted mode.
|
||||
Privileged mode is only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./75-project-settings.md#trusted) to enable trusted mode.
|
||||
:::
|
||||
|
||||
```diff
|
||||
|
|
|
@ -6,7 +6,7 @@ In case there is a single configuration in `.woodpecker.yaml` Woodpecker will cr
|
|||
|
||||
By placing the configurations in a folder which is by default named `.woodpecker/` Woodpecker will create a pipeline with multiple workflows each named by the file they are defined in. Only `.yml` and `.yaml` files will be used and files in any subfolders like `.woodpecker/sub-folder/test.yaml` will be ignored.
|
||||
|
||||
You can also set some custom path like `.my-ci/pipelines/` instead of `.woodpecker/` in the [project settings](./71-project-settings.md).
|
||||
You can also set some custom path like `.my-ci/pipelines/` instead of `.woodpecker/` in the [project settings](./75-project-settings.md).
|
||||
|
||||
## Benefits of using workflows
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Woodpecker gives the ability to define Docker volumes in the YAML. You can use this parameter to mount files or folders on the host machine into your containers.
|
||||
|
||||
:::note
|
||||
Volumes are only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./71-project-settings.md#trusted) to enable trusted mode.
|
||||
Volumes are only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./75-project-settings.md#trusted) to enable trusted mode.
|
||||
:::
|
||||
|
||||
```diff
|
||||
|
|
62
docs/docs/20-usage/72-linter.md
Normal file
62
docs/docs/20-usage/72-linter.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Linter
|
||||
|
||||
Woodpecker automatically lints your workflow files for errors, deprecations and bad habits. Errors and warnings are shown in the UI for any pipelines.
|
||||
|
||||
![errors and warnings in UI](./linter-warnings-errors.png)
|
||||
|
||||
## Running the linter from CLI
|
||||
|
||||
You can run the linter also manually from the CLI:
|
||||
|
||||
```shell
|
||||
woodpecker-cli lint <workflow files>
|
||||
```
|
||||
|
||||
## Bad habit warnings
|
||||
|
||||
Woodpecker warns you if your configuration contains some bad habits.
|
||||
|
||||
### Event filter for all steps
|
||||
|
||||
All your items in `when` blocks should have an `event` filter, so no step runs on all events. This is recommended because if new events are added, your steps probably shouldn't run on those as well.
|
||||
|
||||
Examples of an **incorrect** config for this rule:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- branch: main
|
||||
- event: tag
|
||||
```
|
||||
|
||||
This will trigger the warning because the first item (`branch: main`) does not filter with an event.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: test
|
||||
when:
|
||||
branch: main
|
||||
|
||||
- name: deploy
|
||||
when:
|
||||
event: tag
|
||||
```
|
||||
|
||||
Examples of a **correct** config for this rule:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- branch: main
|
||||
event: push
|
||||
- event: tag
|
||||
```
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: test
|
||||
when:
|
||||
event: [tag, push]
|
||||
|
||||
- name: deploy
|
||||
when:
|
||||
- event: tag
|
||||
```
|
BIN
docs/docs/20-usage/linter-warnings-errors.png
Normal file
BIN
docs/docs/20-usage/linter-warnings-errors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
|
@ -1,6 +1,6 @@
|
|||
# Addon forges
|
||||
|
||||
If the forge you're using does not comply with [Woodpecker's requirements](../../92-development/02-core-ideas.md#forge) or your setup is too specific to be added to Woodpecker's core, you can write your own forge using an addon forge.
|
||||
If the forge you're using does not comply with [Woodpecker's requirements](../../92-development/02-core-ideas.md#forges) or your setup is too specific to be added to Woodpecker's core, you can write your own forge using an addon forge.
|
||||
|
||||
:::warning
|
||||
Addon forges are still experimental. Their implementation can change and break at any time.
|
||||
|
|
|
@ -11,6 +11,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
- Deprecated uppercasing all secret env vars, instead, the value of the `secrets` property is used. [Read more](./20-usage/40-secrets.md#use-secrets-in-commands)
|
||||
- Deprecated alternative names for secrets, use `environment` with `from_secret`
|
||||
- Deprecated slice definition for env vars
|
||||
- Deprecated `environment` filter, use `when.evaluate`
|
||||
|
||||
## 2.0.0
|
||||
|
||||
|
@ -66,7 +67,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
|
||||
Only projects created after updating will have an empty value by default. Existing projects will stick to the current pipeline path which is `.drone.yml` in most cases.
|
||||
|
||||
Read more about it at the [Project Settings](./20-usage/71-project-settings.md#pipeline-path)
|
||||
Read more about it at the [Project Settings](./20-usage/75-project-settings.md#pipeline-path)
|
||||
|
||||
- From version `0.15.0` ongoing there will be three types of docker images: `latest`, `next` and `x.x.x` with an alpine variant for each type like `latest-alpine`.
|
||||
If you used `latest` before to try pre-release features you should switch to `next` after this release.
|
||||
|
|
|
@ -10,6 +10,7 @@ const config: Config = {
|
|||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
onBrokenAnchors: 'throw',
|
||||
onDuplicateRoutes: 'throw',
|
||||
organizationName: 'woodpecker-ci',
|
||||
projectName: 'woodpecker-ci.github.io',
|
||||
|
|
|
@ -53,8 +53,8 @@
|
|||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"trim": "^0.0.3",
|
||||
"got": "^11.8.5"
|
||||
"trim": "^1.0.0",
|
||||
"got": "^14.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1045
docs/pnpm-lock.yaml
1045
docs/pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -19,6 +19,12 @@ type DeprecationErrorData struct {
|
|||
Docs string `json:"docs"`
|
||||
}
|
||||
|
||||
type BadHabitErrorData struct {
|
||||
File string `json:"file"`
|
||||
Field string `json:"field"`
|
||||
Docs string `json:"docs"`
|
||||
}
|
||||
|
||||
func GetLinterData(e *types.PipelineError) *LinterErrorData {
|
||||
if e.Type != types.PipelineErrorTypeLinter {
|
||||
return nil
|
||||
|
|
|
@ -305,7 +305,39 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
Field: fmt.Sprintf("steps.%s.secrets[%d]", step.Name, i),
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#event",
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/secrets#use-secrets-in-settings-and-environment",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, c := range parsed.When.Constraints {
|
||||
if !c.Environment.IsEmpty() {
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
Field: fmt.Sprintf("when[%d].environment", i),
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, step := range parsed.Steps.ContainerList {
|
||||
for i, c := range step.When.Constraints {
|
||||
if !c.Environment.IsEmpty() {
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
Field: fmt.Sprintf("steps.%s.when[%d].environment", step.Name, i),
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
|
@ -351,10 +383,11 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) {
|
|||
if field != "" {
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeBadHabit,
|
||||
Message: "Please set an event filter on all when branches",
|
||||
Data: errors.LinterErrorData{
|
||||
Message: "Please set an event filter for all steps or the whole workflow on all items of the when block",
|
||||
Data: errors.BadHabitErrorData{
|
||||
File: config.File,
|
||||
Field: field,
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/linter#event-filter-for-all-steps",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
|
|
|
@ -189,11 +189,11 @@ func TestBadHabits(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
from: "steps: { build: { image: golang } }",
|
||||
want: "Please set an event filter on all when branches",
|
||||
want: "Please set an event filter for all steps or the whole workflow on all items of the when block",
|
||||
},
|
||||
{
|
||||
from: "when: [{branch: xyz}, {event: push}]\nsteps: { build: { image: golang } }",
|
||||
want: "Please set an event filter on all when branches",
|
||||
want: "Please set an event filter for all steps or the whole workflow on all items of the when block",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ func GetCC(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
pipelines, err := _store.GetPipelineList(repo, &model.ListOptions{Page: 1, PerPage: 1})
|
||||
pipelines, err := _store.GetPipelineList(repo, &model.ListOptions{Page: 1, PerPage: 1}, nil)
|
||||
if err != nil && !errors.Is(err, types.RecordNotExist) {
|
||||
log.Warn().Err(err).Msg("could not get pipeline list")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
|
|
|
@ -64,3 +64,14 @@ func refreshUserToken(c *gin.Context, user *model.User) {
|
|||
}
|
||||
forge.Refresh(c, _forge, _store, user)
|
||||
}
|
||||
|
||||
// pipelineDeleteAllowed checks if the given pipeline can be deleted based on its status.
|
||||
// It returns a bool indicating if delete is allowed, and the pipeline's status.
|
||||
func pipelineDeleteAllowed(pl *model.Pipeline) bool {
|
||||
switch pl.Status {
|
||||
case model.StatusRunning, model.StatusPending, model.StatusBlocked:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -109,10 +109,34 @@ func createTmpPipeline(event model.WebhookEvent, commit *model.Commit, user *mod
|
|||
// @Param repo_id path int true "the repository id"
|
||||
// @Param page query int false "for response pagination, page offset number" default(1)
|
||||
// @Param perPage query int false "for response pagination, max items per page" default(50)
|
||||
// @Param before query string false "only return pipelines before this RFC3339 date"
|
||||
// @Param after query string false "only return pipelines after this RFC3339 date"
|
||||
func GetPipelines(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
before := c.Query("before")
|
||||
after := c.Query("after")
|
||||
|
||||
pipelines, err := store.FromContext(c).GetPipelineList(repo, session.Pagination(c))
|
||||
filter := new(model.PipelineFilter)
|
||||
|
||||
if before != "" {
|
||||
beforeDt, err := time.Parse(time.RFC3339, before)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
filter.Before = beforeDt.Unix()
|
||||
}
|
||||
|
||||
if after != "" {
|
||||
afterDt, err := time.Parse(time.RFC3339, after)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
filter.After = afterDt.Unix()
|
||||
}
|
||||
|
||||
pipelines, err := store.FromContext(c).GetPipelineList(repo, session.Pagination(c), filter)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -120,6 +144,46 @@ func GetPipelines(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, pipelines)
|
||||
}
|
||||
|
||||
// DeletePipeline
|
||||
//
|
||||
// @Summary Delete pipeline
|
||||
// @Router /repos/{repo_id}/pipelines/{number} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Pipelines
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param repo_id path int true "the repository id"
|
||||
// @Param number path int true "the number of the pipeline"
|
||||
func DeletePipeline(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
repo := session.Repo(c)
|
||||
num, err := strconv.ParseInt(c.Param("number"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
pl, err := _store.GetPipelineNumber(repo, num)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if ok := pipelineDeleteAllowed(pl); !ok {
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete pipeline with status %s", pl.Status)
|
||||
return
|
||||
}
|
||||
|
||||
err = store.FromContext(c).DeletePipeline(pl)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error deleting pipeline. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetPipeline
|
||||
//
|
||||
// @Summary Pipeline information by number
|
||||
|
@ -550,9 +614,8 @@ func DeletePipelineLogs(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
switch pl.Status {
|
||||
case model.StatusRunning, model.StatusPending:
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete logs for a pending or running pipeline")
|
||||
if ok := pipelineDeleteAllowed(pl); !ok {
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete logs for pipeline with status %s", pl.Status)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -562,7 +625,7 @@ func DeletePipelineLogs(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "There was a problem deleting your logs. %s", err)
|
||||
c.String(http.StatusInternalServerError, "Error deleting pipeline logs. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
129
server/api/pipeline_test.go
Normal file
129
server/api/pipeline_test.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
|
||||
)
|
||||
|
||||
var fakePipeline = &model.Pipeline{
|
||||
Status: model.StatusSuccess,
|
||||
}
|
||||
|
||||
func TestGetPipelines(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Pipeline", func() {
|
||||
g.It("should get pipelines", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineList", mock.Anything, mock.Anything, mock.Anything)
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should not parse pipeline filter", func() {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("DELETE", "/?before=2023-01-16&after=2023-01-15", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should parse pipeline filter", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Request, _ = http.NewRequest("DELETE", "/?2023-01-16T15:00:00Z&after=2023-01-15T15:00:00Z", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should parse pipeline filter with tz offset", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Request, _ = http.NewRequest("DELETE", "/?before=2023-01-16T15:00:00%2B01:00&after=2023-01-15T15:00:00%2B01:00", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeletePipeline(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Pipeline", func() {
|
||||
g.It("should delete pipeline", func() {
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil)
|
||||
mockStore.On("DeletePipeline", mock.Anything).Return(nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "number", Value: "1"}}
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusNoContent, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should not delete without pipeline number", func() {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should not delete pending", func() {
|
||||
fakePipeline.Status = model.StatusPending
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "number", Value: "1"}}
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertNotCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, c.Writer.Status())
|
||||
})
|
||||
})
|
||||
}
|
|
@ -53,6 +53,11 @@ type Pipeline struct {
|
|||
IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"`
|
||||
} // @name Pipeline
|
||||
|
||||
type PipelineFilter struct {
|
||||
Before int64
|
||||
After int64
|
||||
}
|
||||
|
||||
// TableName return database table name for xorm
|
||||
func (Pipeline) TableName() string {
|
||||
return "pipelines"
|
||||
|
|
|
@ -94,6 +94,7 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
|
||||
repo.GET("/pipelines", api.GetPipelines)
|
||||
repo.POST("/pipelines", session.MustPush, api.CreatePipeline)
|
||||
repo.DELETE("/pipelines/:number", session.MustRepoAdmin(), api.DeletePipeline)
|
||||
repo.GET("/pipelines/:number", api.GetPipeline)
|
||||
repo.GET("/pipelines/:number/config", api.GetPipelineConfig)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
|
|
@ -52,9 +52,22 @@ func (s storage) GetPipelineLastBefore(repo *model.Repo, branch string, num int6
|
|||
Get(pipeline))
|
||||
}
|
||||
|
||||
func (s storage) GetPipelineList(repo *model.Repo, p *model.ListOptions) ([]*model.Pipeline, error) {
|
||||
func (s storage) GetPipelineList(repo *model.Repo, p *model.ListOptions, f *model.PipelineFilter) ([]*model.Pipeline, error) {
|
||||
pipelines := make([]*model.Pipeline, 0, 16)
|
||||
return pipelines, s.paginate(p).Where("pipeline_repo_id = ?", repo.ID).
|
||||
|
||||
cond := builder.NewCond().And(builder.Eq{"pipeline_repo_id": repo.ID})
|
||||
|
||||
if f != nil {
|
||||
if f.After != 0 {
|
||||
cond = cond.And(builder.Gt{"pipeline_created": f.After})
|
||||
}
|
||||
|
||||
if f.Before != 0 {
|
||||
cond = cond.And(builder.Lt{"pipeline_created": f.Before})
|
||||
}
|
||||
}
|
||||
|
||||
return pipelines, s.paginate(p).Where(cond).
|
||||
Desc("pipeline_number").
|
||||
Find(&pipelines)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package datastore
|
|||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -221,13 +222,33 @@ func TestPipelines(t *testing.T) {
|
|||
g.Assert(err1).IsNil()
|
||||
err2 := store.CreatePipeline(pipeline2, []*model.Step{}...)
|
||||
g.Assert(err2).IsNil()
|
||||
pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50})
|
||||
pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, nil)
|
||||
g.Assert(err3).IsNil()
|
||||
g.Assert(len(pipelines)).Equal(2)
|
||||
g.Assert(pipelines[0].ID).Equal(pipeline2.ID)
|
||||
g.Assert(pipelines[0].RepoID).Equal(pipeline2.RepoID)
|
||||
g.Assert(pipelines[0].Status).Equal(pipeline2.Status)
|
||||
})
|
||||
|
||||
g.It("Should get filtered pipelines", func() {
|
||||
pipeline1 := &model.Pipeline{
|
||||
RepoID: repo.ID,
|
||||
}
|
||||
pipeline2 := &model.Pipeline{
|
||||
RepoID: repo.ID,
|
||||
}
|
||||
err1 := store.CreatePipeline(pipeline1, []*model.Step{}...)
|
||||
g.Assert(err1).IsNil()
|
||||
time.Sleep(1 * time.Second)
|
||||
before := time.Now().Unix()
|
||||
err2 := store.CreatePipeline(pipeline2, []*model.Step{}...)
|
||||
g.Assert(err2).IsNil()
|
||||
pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, &model.PipelineFilter{Before: before})
|
||||
g.Assert(err3).IsNil()
|
||||
g.Assert(len(pipelines)).Equal(1)
|
||||
g.Assert(pipelines[0].ID).Equal(pipeline1.ID)
|
||||
g.Assert(pipelines[0].RepoID).Equal(pipeline1.RepoID)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -543,6 +543,10 @@ func (_m *Store) DeleteUser(_a0 *model.User) error {
|
|||
func (_m *Store) ForgeCreate(_a0 *model.Forge) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ForgeCreate")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Forge) error); ok {
|
||||
r0 = rf(_a0)
|
||||
|
@ -557,6 +561,10 @@ func (_m *Store) ForgeCreate(_a0 *model.Forge) error {
|
|||
func (_m *Store) ForgeDelete(_a0 *model.Forge) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ForgeDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Forge) error); ok {
|
||||
r0 = rf(_a0)
|
||||
|
@ -567,62 +575,14 @@ func (_m *Store) ForgeDelete(_a0 *model.Forge) error {
|
|||
return r0
|
||||
}
|
||||
|
||||
// ForgeFindByRepo provides a mock function with given fields: _a0
|
||||
func (_m *Store) ForgeFindByRepo(_a0 *model.Repo) (*model.Forge, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
var r0 *model.Forge
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo) (*model.Forge, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo) *model.Forge); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Forge)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Repo) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ForgeFindByUser provides a mock function with given fields: _a0
|
||||
func (_m *Store) ForgeFindByUser(_a0 *model.User) (*model.Forge, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
var r0 *model.Forge
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.User) (*model.Forge, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.User) *model.Forge); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Forge)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ForgeGet provides a mock function with given fields: _a0
|
||||
func (_m *Store) ForgeGet(_a0 int64) (*model.Forge, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ForgeGet")
|
||||
}
|
||||
|
||||
var r0 *model.Forge
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(int64) (*model.Forge, error)); ok {
|
||||
|
@ -649,6 +609,10 @@ func (_m *Store) ForgeGet(_a0 int64) (*model.Forge, error) {
|
|||
func (_m *Store) ForgeList(p *model.ListOptions) ([]*model.Forge, error) {
|
||||
ret := _m.Called(p)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ForgeList")
|
||||
}
|
||||
|
||||
var r0 []*model.Forge
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.ListOptions) ([]*model.Forge, error)); ok {
|
||||
|
@ -675,6 +639,10 @@ func (_m *Store) ForgeList(p *model.ListOptions) ([]*model.Forge, error) {
|
|||
func (_m *Store) ForgeUpdate(_a0 *model.Forge) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ForgeUpdate")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Forge) error); ok {
|
||||
r0 = rf(_a0)
|
||||
|
@ -833,9 +801,9 @@ func (_m *Store) GetPipelineLastBefore(_a0 *model.Repo, _a1 string, _a2 int64) (
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPipelineList provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) GetPipelineList(_a0 *model.Repo, _a1 *model.ListOptions) ([]*model.Pipeline, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
// GetPipelineList provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *Store) GetPipelineList(_a0 *model.Repo, _a1 *model.ListOptions, _a2 *model.PipelineFilter) ([]*model.Pipeline, error) {
|
||||
ret := _m.Called(_a0, _a1, _a2)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPipelineList")
|
||||
|
@ -843,19 +811,19 @@ func (_m *Store) GetPipelineList(_a0 *model.Repo, _a1 *model.ListOptions) ([]*mo
|
|||
|
||||
var r0 []*model.Pipeline
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) ([]*model.Pipeline, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions, *model.PipelineFilter) ([]*model.Pipeline, error)); ok {
|
||||
return rf(_a0, _a1, _a2)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions) []*model.Pipeline); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, *model.ListOptions, *model.PipelineFilter) []*model.Pipeline); ok {
|
||||
r0 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Pipeline)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Repo, *model.ListOptions) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
if rf, ok := ret.Get(1).(func(*model.Repo, *model.ListOptions, *model.PipelineFilter) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ type Store interface {
|
|||
// GetPipelineLastBefore gets the last pipeline before pipeline number N.
|
||||
GetPipelineLastBefore(*model.Repo, string, int64) (*model.Pipeline, error)
|
||||
// GetPipelineList gets a list of pipelines for the repository
|
||||
GetPipelineList(*model.Repo, *model.ListOptions) ([]*model.Pipeline, error)
|
||||
GetPipelineList(*model.Repo, *model.ListOptions, *model.PipelineFilter) ([]*model.Pipeline, error)
|
||||
// GetActivePipelineList gets a list of the active pipelines for the repository
|
||||
GetActivePipelineList(repo *model.Repo) ([]*model.Pipeline, error)
|
||||
// GetPipelineQueue gets a list of pipelines in queue.
|
||||
|
|
|
@ -11,13 +11,16 @@
|
|||
}"
|
||||
/>
|
||||
<span>[{{ error.type }}]</span>
|
||||
<span v-if="isLinterError(error) || isDeprecationError(error)" class="whitespace-nowrap">
|
||||
<span
|
||||
v-if="isLinterError(error) || isDeprecationError(error) || isBadHabitError(error)"
|
||||
class="whitespace-nowrap"
|
||||
>
|
||||
<span v-if="error.data?.file" class="font-bold">{{ error.data?.file }}: </span>
|
||||
<span>{{ error.data?.field }}</span>
|
||||
</span>
|
||||
<span v-else />
|
||||
<a
|
||||
v-if="isDeprecationError(error)"
|
||||
v-if="isDeprecationError(error) || isBadHabitError(error)"
|
||||
:href="error.data?.docs"
|
||||
target="_blank"
|
||||
class="underline col-span-full col-start-2 md:col-span-auto md:col-start-auto"
|
||||
|
@ -52,6 +55,10 @@ function isDeprecationError(
|
|||
): error is PipelineError<{ file: string; field: string; docs: string }> {
|
||||
return error.type === 'deprecation';
|
||||
}
|
||||
|
||||
function isBadHabitError(error: PipelineError): error is PipelineError<{ file?: string; field: string; docs: string }> {
|
||||
return error.type === 'bad_habit';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
50
woodpecker-go/woodpecker/agent.go
Normal file
50
woodpecker-go/woodpecker/agent.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
pathAgents = "%s/api/agents"
|
||||
pathAgent = "%s/api/agents/%d"
|
||||
pathAgentTasks = "%s/api/agents/%d/tasks"
|
||||
)
|
||||
|
||||
// AgentCreate creates a new agent.
|
||||
func (c *client) AgentCreate(in *Agent) (*Agent, error) {
|
||||
out := new(Agent)
|
||||
uri := fmt.Sprintf(pathAgents, c.addr)
|
||||
return out, c.post(uri, in, out)
|
||||
}
|
||||
|
||||
// AgentList returns a list of all registered agents.
|
||||
func (c *client) AgentList() ([]*Agent, error) {
|
||||
out := make([]*Agent, 0, 5)
|
||||
uri := fmt.Sprintf(pathAgents, c.addr)
|
||||
return out, c.get(uri, &out)
|
||||
}
|
||||
|
||||
// Agent returns an agent by id.
|
||||
func (c *client) Agent(agentID int64) (*Agent, error) {
|
||||
out := new(Agent)
|
||||
uri := fmt.Sprintf(pathAgent, c.addr, agentID)
|
||||
return out, c.get(uri, out)
|
||||
}
|
||||
|
||||
// AgentUpdate updates the agent with the provided Agent struct.
|
||||
func (c *client) AgentUpdate(in *Agent) (*Agent, error) {
|
||||
out := new(Agent)
|
||||
uri := fmt.Sprintf(pathAgent, c.addr, in.ID)
|
||||
return out, c.patch(uri, in, out)
|
||||
}
|
||||
|
||||
// AgentDelete deletes the agent with the given id.
|
||||
func (c *client) AgentDelete(agentID int64) error {
|
||||
uri := fmt.Sprintf(pathAgent, c.addr, agentID)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// AgentTasksList returns a list of all tasks for the agent with the given id.
|
||||
func (c *client) AgentTasksList(agentID int64) ([]*Task, error) {
|
||||
out := make([]*Task, 0, 5)
|
||||
uri := fmt.Sprintf(pathAgentTasks, c.addr, agentID)
|
||||
return out, c.get(uri, &out)
|
||||
}
|
511
woodpecker-go/woodpecker/agent_test.go
Normal file
511
woodpecker-go/woodpecker/agent_test.go
Normal file
|
@ -0,0 +1,511 @@
|
|||
package woodpecker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClient_AgentCreate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
input *Agent
|
||||
expected *Agent
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, err := fmt.Fprint(w, `{"id":1,"name":"new_agent","backend":"local","capacity":2,"version":"1.0.0"}`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
input: &Agent{Name: "new_agent", Backend: "local", Capacity: 2, Version: "1.0.0"},
|
||||
expected: &Agent{ID: 1, Name: "new_agent", Backend: "local", Capacity: 2, Version: "1.0.0"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid input",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
},
|
||||
input: &Agent{},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
input: &Agent{Name: "new_agent", Backend: "local", Capacity: 2, Version: "1.0.0"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
agent, err := client.AgentCreate(tt.input)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, agent, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AgentList(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
expected []*Agent
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "agent-1",
|
||||
"backend": "local",
|
||||
"capacity": 2,
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "agent-2",
|
||||
"backend": "kubernetes",
|
||||
"capacity": 4,
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
expected: []*Agent{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "agent-1",
|
||||
Backend: "local",
|
||||
Capacity: 2,
|
||||
Version: "1.0.0",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "agent-2",
|
||||
Backend: "kubernetes",
|
||||
Capacity: 4,
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid response",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `invalid json`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
agents, err := client.AgentList()
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, agents)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Agent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
agentID int64
|
||||
expected *Agent
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `{"id":1,"name":"agent-1","backend":"local","capacity":2,"version":"1.0.0"}`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
agentID: 1,
|
||||
expected: &Agent{ID: 1, Name: "agent-1", Backend: "local", Capacity: 2, Version: "1.0.0"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
},
|
||||
agentID: 999,
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
agentID: 1,
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid response",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `invalid json`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
agentID: 1,
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
agent, err := client.Agent(tt.agentID)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, agent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AgentUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
input *Agent
|
||||
expected *Agent
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `{"id":1,"name":"updated_agent"}`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
input: &Agent{ID: 1, Name: "existing_agent"},
|
||||
expected: &Agent{ID: 1, Name: "updated_agent"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
},
|
||||
input: &Agent{ID: 999, Name: "nonexistent_agent"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid input",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
},
|
||||
input: &Agent{},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
input: &Agent{ID: 1, Name: "existing_agent"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
agent, err := client.AgentUpdate(tt.input)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, agent, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AgentDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
agentID int64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
agentID: 1,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
},
|
||||
agentID: 999,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
agentID: 1,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
err := client.AgentDelete(tt.agentID)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_AgentTasksList(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
agentID int64
|
||||
expected []*Task
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `[
|
||||
{
|
||||
"id": "4696",
|
||||
"data": "",
|
||||
"labels": {
|
||||
"platform": "linux/amd64",
|
||||
"repo": "woodpecker-ci/woodpecker"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4697",
|
||||
"data": "",
|
||||
"labels": {
|
||||
"platform": "linux/arm64",
|
||||
"repo": "woodpecker-ci/woodpecker"
|
||||
}
|
||||
}
|
||||
]`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
agentID: 1,
|
||||
expected: []*Task{
|
||||
{
|
||||
ID: "4696",
|
||||
Data: []byte{},
|
||||
Labels: map[string]string{
|
||||
"platform": "linux/amd64",
|
||||
"repo": "woodpecker-ci/woodpecker",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "4697",
|
||||
Data: []byte{},
|
||||
Labels: map[string]string{
|
||||
"platform": "linux/arm64",
|
||||
"repo": "woodpecker-ci/woodpecker",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
},
|
||||
agentID: 999,
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
agentID: 1,
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid response",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `invalid json`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
agentID: 1,
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
tasks, err := client.AgentTasksList(tt.agentID)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, tasks)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -26,41 +26,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
pathSelf = "%s/api/user"
|
||||
pathRepos = "%s/api/user/repos"
|
||||
pathRepoPost = "%s/api/repos?forge_remote_id=%d"
|
||||
pathRepo = "%s/api/repos/%d"
|
||||
pathRepoLookup = "%s/api/repos/lookup/%s"
|
||||
pathRepoMove = "%s/api/repos/%d/move?to=%s"
|
||||
pathChown = "%s/api/repos/%d/chown"
|
||||
pathRepair = "%s/api/repos/%d/repair"
|
||||
pathPipelines = "%s/api/repos/%d/pipelines"
|
||||
pathPipeline = "%s/api/repos/%d/pipelines/%v"
|
||||
pathPipelineLogs = "%s/api/repos/%d/logs/%d"
|
||||
pathStepLogs = "%s/api/repos/%d/logs/%d/%d"
|
||||
pathApprove = "%s/api/repos/%d/pipelines/%d/approve"
|
||||
pathDecline = "%s/api/repos/%d/pipelines/%d/decline"
|
||||
pathStop = "%s/api/repos/%d/pipelines/%d/cancel"
|
||||
pathRepoSecrets = "%s/api/repos/%d/secrets"
|
||||
pathRepoSecret = "%s/api/repos/%d/secrets/%s"
|
||||
pathRepoRegistries = "%s/api/repos/%d/registry"
|
||||
pathRepoRegistry = "%s/api/repos/%d/registry/%s"
|
||||
pathRepoCrons = "%s/api/repos/%d/cron"
|
||||
pathRepoCron = "%s/api/repos/%d/cron/%d"
|
||||
pathOrg = "%s/api/orgs/%d"
|
||||
pathOrgLookup = "%s/api/orgs/lookup/%s"
|
||||
pathOrgSecrets = "%s/api/orgs/%d/secrets"
|
||||
pathOrgSecret = "%s/api/orgs/%d/secrets/%s"
|
||||
pathGlobalSecrets = "%s/api/secrets"
|
||||
pathGlobalSecret = "%s/api/secrets/%s"
|
||||
pathUsers = "%s/api/users"
|
||||
pathUser = "%s/api/users/%s"
|
||||
pathPipelineQueue = "%s/api/pipelines"
|
||||
pathQueue = "%s/api/queue"
|
||||
pathLogLevel = "%s/api/log-level"
|
||||
pathAgents = "%s/api/agents"
|
||||
pathAgent = "%s/api/agents/%d"
|
||||
pathAgentTasks = "%s/api/agents/%d/tasks"
|
||||
pathLogLevel = "%s/api/log-level"
|
||||
|
||||
// TODO: implement endpoints
|
||||
// pathFeed = "%s/api/user/feed"
|
||||
// pathVersion = "%s/version"
|
||||
|
@ -91,422 +58,6 @@ func (c *client) SetAddress(addr string) {
|
|||
c.addr = addr
|
||||
}
|
||||
|
||||
// Self returns the currently authenticated user.
|
||||
func (c *client) Self() (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathSelf, c.addr)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// User returns a user by login.
|
||||
func (c *client) User(login string) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUser, c.addr, login)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserList returns a list of all registered users.
|
||||
func (c *client) UserList() ([]*User, error) {
|
||||
var out []*User
|
||||
uri := fmt.Sprintf(pathUsers, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserPost creates a new user account.
|
||||
func (c *client) UserPost(in *User) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUsers, c.addr)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserPatch updates a user account.
|
||||
func (c *client) UserPatch(in *User) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUser, c.addr, in.Login)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserDel deletes a user account.
|
||||
func (c *client) UserDel(login string) error {
|
||||
uri := fmt.Sprintf(pathUser, c.addr, login)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// Repo returns a repository by id.
|
||||
func (c *client) Repo(repoID int64) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoLookup returns a repository by name.
|
||||
func (c *client) RepoLookup(fullName string) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepoLookup, c.addr, fullName)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoList returns a list of all repositories to which
|
||||
// the user has explicit access in the host system.
|
||||
func (c *client) RepoList() ([]*Repo, error) {
|
||||
var out []*Repo
|
||||
uri := fmt.Sprintf(pathRepos, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoListOpts returns a list of all repositories to which
|
||||
// the user has explicit access in the host system.
|
||||
func (c *client) RepoListOpts(all bool) ([]*Repo, error) {
|
||||
var out []*Repo
|
||||
uri := fmt.Sprintf(pathRepos+"?all=%v", c.addr, all)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoPost activates a repository.
|
||||
func (c *client) RepoPost(forgeRemoteID int64) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepoPost, c.addr, forgeRemoteID)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoChown updates a repository owner.
|
||||
func (c *client) RepoChown(repoID int64) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathChown, c.addr, repoID)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoRepair repairs the repository hooks.
|
||||
func (c *client) RepoRepair(repoID int64) error {
|
||||
uri := fmt.Sprintf(pathRepair, c.addr, repoID)
|
||||
return c.post(uri, nil, nil)
|
||||
}
|
||||
|
||||
// RepoPatch updates a repository.
|
||||
func (c *client) RepoPatch(repoID int64, in *RepoPatch) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoDel deletes a repository.
|
||||
func (c *client) RepoDel(repoID int64) error {
|
||||
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// RepoMove moves a repository
|
||||
func (c *client) RepoMove(repoID int64, newFullName string) error {
|
||||
uri := fmt.Sprintf(pathRepoMove, c.addr, repoID, newFullName)
|
||||
return c.post(uri, nil, nil)
|
||||
}
|
||||
|
||||
// Pipeline returns a repository pipeline by pipeline-id.
|
||||
func (c *client) Pipeline(repoID, pipeline int64) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Pipeline returns the latest repository pipeline by branch.
|
||||
func (c *client) PipelineLast(repoID int64, branch string) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, "latest")
|
||||
if len(branch) != 0 {
|
||||
uri += "?branch=" + branch
|
||||
}
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineList returns a list of recent pipelines for the
|
||||
// the specified repository.
|
||||
func (c *client) PipelineList(repoID int64) ([]*Pipeline, error) {
|
||||
var out []*Pipeline
|
||||
uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *client) PipelineCreate(repoID int64, options *PipelineOptions) (*Pipeline, error) {
|
||||
var out *Pipeline
|
||||
uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
|
||||
err := c.post(uri, options, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineQueue returns a list of enqueued pipelines.
|
||||
func (c *client) PipelineQueue() ([]*Feed, error) {
|
||||
var out []*Feed
|
||||
uri := fmt.Sprintf(pathPipelineQueue, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineStart re-starts a stopped pipeline.
|
||||
func (c *client) PipelineStart(repoID, pipeline int64, params map[string]string) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
val := mapValues(params)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.post(uri+"?"+val.Encode(), nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineStop cancels the running step.
|
||||
func (c *client) PipelineStop(repoID, pipeline int64) error {
|
||||
uri := fmt.Sprintf(pathStop, c.addr, repoID, pipeline)
|
||||
err := c.post(uri, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// PipelineApprove approves a blocked pipeline.
|
||||
func (c *client) PipelineApprove(repoID, pipeline int64) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathApprove, c.addr, repoID, pipeline)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineDecline declines a blocked pipeline.
|
||||
func (c *client) PipelineDecline(repoID, pipeline int64) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathDecline, c.addr, repoID, pipeline)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineKill force kills the running pipeline.
|
||||
func (c *client) PipelineKill(repoID, pipeline int64) error {
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// LogsPurge purges the pipeline all steps logs for the specified pipeline.
|
||||
func (c *client) LogsPurge(repoID, pipeline int64) error {
|
||||
uri := fmt.Sprintf(pathPipelineLogs, c.addr, repoID, pipeline)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// StepLogEntries returns the pipeline logs for the specified step.
|
||||
func (c *client) StepLogEntries(repoID, num, step int64) ([]*LogEntry, error) {
|
||||
uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, num, step)
|
||||
var out []*LogEntry
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// StepLogsPurge purges the pipeline logs for the specified step.
|
||||
func (c *client) StepLogsPurge(repoID, pipelineNumber, stepID int64) error {
|
||||
uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, pipelineNumber, stepID)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// Deploy triggers a deployment for an existing pipeline using the
|
||||
// specified target environment.
|
||||
func (c *client) Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
val := mapValues(params)
|
||||
val.Set("event", EventDeploy)
|
||||
val.Set("deploy_to", env)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.post(uri+"?"+val.Encode(), nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Registry returns a registry by hostname.
|
||||
func (c *client) Registry(repoID int64, hostname string) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryList returns a list of all repository registries.
|
||||
func (c *client) RegistryList(repoID int64) ([]*Registry, error) {
|
||||
var out []*Registry
|
||||
uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryCreate creates a registry.
|
||||
func (c *client) RegistryCreate(repoID int64, in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryUpdate updates a registry.
|
||||
func (c *client) RegistryUpdate(repoID int64, in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, in.Address)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryDelete deletes a registry.
|
||||
func (c *client) RegistryDelete(repoID int64, hostname string) error {
|
||||
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// Secret returns a secret by name.
|
||||
func (c *client) Secret(repoID int64, secret string) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretList returns a list of all repository secrets.
|
||||
func (c *client) SecretList(repoID int64) ([]*Secret, error) {
|
||||
var out []*Secret
|
||||
uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretCreate creates a secret.
|
||||
func (c *client) SecretCreate(repoID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretUpdate updates a secret.
|
||||
func (c *client) SecretUpdate(repoID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, in.Name)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretDelete deletes a secret.
|
||||
func (c *client) SecretDelete(repoID int64, secret string) error {
|
||||
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// Org returns an organization by id.
|
||||
func (c *client) Org(orgID int64) (*Org, error) {
|
||||
out := new(Org)
|
||||
uri := fmt.Sprintf(pathOrg, c.addr, orgID)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgLookup returns a organization by its name.
|
||||
func (c *client) OrgLookup(name string) (*Org, error) {
|
||||
out := new(Org)
|
||||
uri := fmt.Sprintf(pathOrgLookup, c.addr, name)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecret returns an organization secret by name.
|
||||
func (c *client) OrgSecret(orgID int64, secret string) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretList returns a list of all organization secrets.
|
||||
func (c *client) OrgSecretList(orgID int64) ([]*Secret, error) {
|
||||
var out []*Secret
|
||||
uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretCreate creates an organization secret.
|
||||
func (c *client) OrgSecretCreate(orgID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretUpdate updates an organization secret.
|
||||
func (c *client) OrgSecretUpdate(orgID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, in.Name)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretDelete deletes an organization secret.
|
||||
func (c *client) OrgSecretDelete(orgID int64, secret string) error {
|
||||
uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// GlobalOrgSecret returns an global secret by name.
|
||||
func (c *client) GlobalSecret(secret string) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretList returns a list of all global secrets.
|
||||
func (c *client) GlobalSecretList() ([]*Secret, error) {
|
||||
var out []*Secret
|
||||
uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretCreate creates a global secret.
|
||||
func (c *client) GlobalSecretCreate(in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretUpdate updates a global secret.
|
||||
func (c *client) GlobalSecretUpdate(in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathGlobalSecret, c.addr, in.Name)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretDelete deletes a global secret.
|
||||
func (c *client) GlobalSecretDelete(secret string) error {
|
||||
uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// QueueInfo returns queue info
|
||||
func (c *client) QueueInfo() (*Info, error) {
|
||||
out := new(Info)
|
||||
uri := fmt.Sprintf(pathQueue+"/info", c.addr)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// LogLevel returns the current logging level
|
||||
func (c *client) LogLevel() (*LogLevel, error) {
|
||||
out := new(LogLevel)
|
||||
|
@ -523,96 +74,31 @@ func (c *client) SetLogLevel(in *LogLevel) (*LogLevel, error) {
|
|||
return out, err
|
||||
}
|
||||
|
||||
func (c *client) CronList(repoID int64) ([]*Cron, error) {
|
||||
out := make([]*Cron, 0, 5)
|
||||
uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
|
||||
return out, c.get(uri, &out)
|
||||
}
|
||||
|
||||
func (c *client) CronCreate(repoID int64, in *Cron) (*Cron, error) {
|
||||
out := new(Cron)
|
||||
uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
|
||||
return out, c.post(uri, in, out)
|
||||
}
|
||||
|
||||
func (c *client) CronUpdate(repoID int64, in *Cron) (*Cron, error) {
|
||||
out := new(Cron)
|
||||
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, in.ID)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *client) CronDelete(repoID, cronID int64) error {
|
||||
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
func (c *client) CronGet(repoID, cronID int64) (*Cron, error) {
|
||||
out := new(Cron)
|
||||
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
|
||||
return out, c.get(uri, out)
|
||||
}
|
||||
|
||||
func (c *client) AgentList() ([]*Agent, error) {
|
||||
out := make([]*Agent, 0, 5)
|
||||
uri := fmt.Sprintf(pathAgents, c.addr)
|
||||
return out, c.get(uri, &out)
|
||||
}
|
||||
|
||||
func (c *client) Agent(agentID int64) (*Agent, error) {
|
||||
out := new(Agent)
|
||||
uri := fmt.Sprintf(pathAgent, c.addr, agentID)
|
||||
return out, c.get(uri, out)
|
||||
}
|
||||
|
||||
func (c *client) AgentCreate(in *Agent) (*Agent, error) {
|
||||
out := new(Agent)
|
||||
uri := fmt.Sprintf(pathAgents, c.addr)
|
||||
return out, c.post(uri, in, out)
|
||||
}
|
||||
|
||||
func (c *client) AgentUpdate(in *Agent) (*Agent, error) {
|
||||
out := new(Agent)
|
||||
uri := fmt.Sprintf(pathAgent, c.addr, in.ID)
|
||||
return out, c.patch(uri, in, out)
|
||||
}
|
||||
|
||||
func (c *client) AgentDelete(agentID int64) error {
|
||||
uri := fmt.Sprintf(pathAgent, c.addr, agentID)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
func (c *client) AgentTasksList(agentID int64) ([]*Task, error) {
|
||||
out := make([]*Task, 0, 5)
|
||||
uri := fmt.Sprintf(pathAgentTasks, c.addr, agentID)
|
||||
return out, c.get(uri, &out)
|
||||
}
|
||||
|
||||
//
|
||||
// http request helper functions
|
||||
//
|
||||
|
||||
// helper function for making an http GET request.
|
||||
// Helper function for making an http GET request.
|
||||
func (c *client) get(rawurl string, out any) error {
|
||||
return c.do(rawurl, http.MethodGet, nil, out)
|
||||
}
|
||||
|
||||
// helper function for making an http POST request.
|
||||
// Helper function for making an http POST request.
|
||||
func (c *client) post(rawurl string, in, out any) error {
|
||||
return c.do(rawurl, http.MethodPost, in, out)
|
||||
}
|
||||
|
||||
// helper function for making an http PATCH request.
|
||||
// Helper function for making an http PATCH request.
|
||||
func (c *client) patch(rawurl string, in, out any) error {
|
||||
return c.do(rawurl, http.MethodPatch, in, out)
|
||||
}
|
||||
|
||||
// helper function for making an http DELETE request.
|
||||
// Helper function for making an http DELETE request.
|
||||
func (c *client) delete(rawurl string) error {
|
||||
return c.do(rawurl, http.MethodDelete, nil, nil)
|
||||
}
|
||||
|
||||
// helper function to make an http request
|
||||
// Helper function to make an http request.
|
||||
func (c *client) do(rawurl, method string, in, out any) error {
|
||||
body, err := c.open(rawurl, method, in)
|
||||
if err != nil {
|
||||
|
@ -625,7 +111,7 @@ func (c *client) do(rawurl, method string, in, out any) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// helper function to open an http request
|
||||
// Helper function to open an http request.
|
||||
func (c *client) open(rawurl, method string, in any) (io.ReadCloser, error) {
|
||||
uri, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
|
|
|
@ -25,44 +25,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_QueueInfo(t *testing.T) {
|
||||
fixtureHandler := func(w http.ResponseWriter, _ *http.Request) {
|
||||
fmt.Fprint(w, `{
|
||||
"pending": null,
|
||||
"running": [
|
||||
{
|
||||
"id": "4696",
|
||||
"data": "",
|
||||
"labels": {
|
||||
"platform": "linux/amd64",
|
||||
"repo": "woodpecker-ci/woodpecker"
|
||||
},
|
||||
"Dependencies": [],
|
||||
"DepStatus": {},
|
||||
"RunOn": null
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"worker_count": 3,
|
||||
"pending_count": 0,
|
||||
"waiting_on_deps_count": 0,
|
||||
"running_count": 1,
|
||||
"completed_count": 0
|
||||
},
|
||||
"Paused": false
|
||||
}`)
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(fixtureHandler))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
|
||||
info, err := client.QueueInfo()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, info.Stats.Workers)
|
||||
}
|
||||
|
||||
func Test_LogLevel(t *testing.T) {
|
||||
logLevel := "warn"
|
||||
fixtureHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -49,7 +49,7 @@ const (
|
|||
LogEntryProgress
|
||||
)
|
||||
|
||||
// StepType identifies the type of step
|
||||
// StepType identifies the type of step.
|
||||
type StepType string
|
||||
|
||||
const (
|
||||
|
|
46
woodpecker-go/woodpecker/global_secret.go
Normal file
46
woodpecker-go/woodpecker/global_secret.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
pathGlobalSecrets = "%s/api/secrets"
|
||||
pathGlobalSecret = "%s/api/secrets/%s"
|
||||
)
|
||||
|
||||
// GlobalOrgSecret returns an global secret by name.
|
||||
func (c *client) GlobalSecret(secret string) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretList returns a list of all global secrets.
|
||||
func (c *client) GlobalSecretList() ([]*Secret, error) {
|
||||
var out []*Secret
|
||||
uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretCreate creates a global secret.
|
||||
func (c *client) GlobalSecretCreate(in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathGlobalSecrets, c.addr)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretUpdate updates a global secret.
|
||||
func (c *client) GlobalSecretUpdate(in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathGlobalSecret, c.addr, in.Name)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GlobalSecretDelete deletes a global secret.
|
||||
func (c *client) GlobalSecretDelete(secret string) error {
|
||||
uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret)
|
||||
return c.delete(uri)
|
||||
}
|
|
@ -190,42 +190,42 @@ type Client interface {
|
|||
// QueueInfo returns the queue state.
|
||||
QueueInfo() (*Info, error)
|
||||
|
||||
// LogLevel returns the current logging level
|
||||
// LogLevel returns the current logging level.
|
||||
LogLevel() (*LogLevel, error)
|
||||
|
||||
// SetLogLevel sets the server's logging level
|
||||
// SetLogLevel sets the server's logging level.
|
||||
SetLogLevel(logLevel *LogLevel) (*LogLevel, error)
|
||||
|
||||
// CronList list all cron jobs of a repo
|
||||
// CronList list all cron jobs of a repo.
|
||||
CronList(repoID int64) ([]*Cron, error)
|
||||
|
||||
// CronGet get a specific cron job of a repo by id
|
||||
// CronGet get a specific cron job of a repo by id.
|
||||
CronGet(repoID, cronID int64) (*Cron, error)
|
||||
|
||||
// CronDelete delete a specific cron job of a repo by id
|
||||
// CronDelete delete a specific cron job of a repo by id.
|
||||
CronDelete(repoID, cronID int64) error
|
||||
|
||||
// CronCreate create a new cron job in a repo
|
||||
// CronCreate create a new cron job in a repo.
|
||||
CronCreate(repoID int64, cron *Cron) (*Cron, error)
|
||||
|
||||
// CronUpdate update an existing cron job of a repo
|
||||
// CronUpdate update an existing cron job of a repo.
|
||||
CronUpdate(repoID int64, cron *Cron) (*Cron, error)
|
||||
|
||||
// AgentList returns a list of all registered agents
|
||||
// AgentList returns a list of all registered agents.
|
||||
AgentList() ([]*Agent, error)
|
||||
|
||||
// Agent returns an agent by id
|
||||
// Agent returns an agent by id.
|
||||
Agent(int64) (*Agent, error)
|
||||
|
||||
// AgentCreate creates a new agent
|
||||
// AgentCreate creates a new agent.
|
||||
AgentCreate(*Agent) (*Agent, error)
|
||||
|
||||
// AgentUpdate updates an existing agent
|
||||
// AgentUpdate updates an existing agent.
|
||||
AgentUpdate(*Agent) (*Agent, error)
|
||||
|
||||
// AgentDelete deletes an agent
|
||||
// AgentDelete deletes an agent.
|
||||
AgentDelete(int64) error
|
||||
|
||||
// AgentTasksList returns a list of all tasks executed by an agent
|
||||
// AgentTasksList returns a list of all tasks executed by an agent.
|
||||
AgentTasksList(int64) ([]*Task, error)
|
||||
}
|
||||
|
|
64
woodpecker-go/woodpecker/org.go
Normal file
64
woodpecker-go/woodpecker/org.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
pathOrg = "%s/api/orgs/%d"
|
||||
pathOrgLookup = "%s/api/orgs/lookup/%s"
|
||||
pathOrgSecrets = "%s/api/orgs/%d/secrets"
|
||||
pathOrgSecret = "%s/api/orgs/%d/secrets/%s"
|
||||
)
|
||||
|
||||
// Org returns an organization by id.
|
||||
func (c *client) Org(orgID int64) (*Org, error) {
|
||||
out := new(Org)
|
||||
uri := fmt.Sprintf(pathOrg, c.addr, orgID)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgLookup returns a organization by its name.
|
||||
func (c *client) OrgLookup(name string) (*Org, error) {
|
||||
out := new(Org)
|
||||
uri := fmt.Sprintf(pathOrgLookup, c.addr, name)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecret returns an organization secret by name.
|
||||
func (c *client) OrgSecret(orgID int64, secret string) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretList returns a list of all organization secrets.
|
||||
func (c *client) OrgSecretList(orgID int64) ([]*Secret, error) {
|
||||
var out []*Secret
|
||||
uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretCreate creates an organization secret.
|
||||
func (c *client) OrgSecretCreate(orgID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretUpdate updates an organization secret.
|
||||
func (c *client) OrgSecretUpdate(orgID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, in.Name)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OrgSecretDelete deletes an organization secret.
|
||||
func (c *client) OrgSecretDelete(orgID int64, secret string) error {
|
||||
uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret)
|
||||
return c.delete(uri)
|
||||
}
|
13
woodpecker-go/woodpecker/pipeline.go
Normal file
13
woodpecker-go/woodpecker/pipeline.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const pathPipelineQueue = "%s/api/pipelines"
|
||||
|
||||
// PipelineQueue returns a list of enqueued pipelines.
|
||||
func (c *client) PipelineQueue() ([]*Feed, error) {
|
||||
var out []*Feed
|
||||
uri := fmt.Sprintf(pathPipelineQueue, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
13
woodpecker-go/woodpecker/queue.go
Normal file
13
woodpecker-go/woodpecker/queue.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const pathQueue = "%s/api/queue"
|
||||
|
||||
// QueueInfo returns queue info.
|
||||
func (c *client) QueueInfo() (*Info, error) {
|
||||
out := new(Info)
|
||||
uri := fmt.Sprintf(pathQueue+"/info", c.addr)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
116
woodpecker-go/woodpecker/queue_test.go
Normal file
116
woodpecker-go/woodpecker/queue_test.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package woodpecker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClient_QueueInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
expected *Info
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `{
|
||||
"pending": null,
|
||||
"running": [
|
||||
{
|
||||
"id": "4696",
|
||||
"data": "",
|
||||
"labels": {
|
||||
"platform": "linux/amd64",
|
||||
"repo": "woodpecker-ci/woodpecker"
|
||||
},
|
||||
"Dependencies": [],
|
||||
"DepStatus": {},
|
||||
"RunOn": null
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"worker_count": 2,
|
||||
"pending_count": 0,
|
||||
"waiting_on_deps_count": 0,
|
||||
"running_count": 0,
|
||||
"completed_count": 0
|
||||
},
|
||||
"Paused": false
|
||||
}`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
expected: &Info{
|
||||
Running: []Task{
|
||||
{
|
||||
ID: "4696",
|
||||
Data: []byte{},
|
||||
Labels: map[string]string{
|
||||
"platform": "linux/amd64",
|
||||
"repo": "woodpecker-ci/woodpecker",
|
||||
},
|
||||
Dependencies: []string{},
|
||||
DepStatus: nil,
|
||||
RunOn: nil,
|
||||
},
|
||||
},
|
||||
Stats: struct {
|
||||
Workers int `json:"worker_count"`
|
||||
Pending int `json:"pending_count"`
|
||||
WaitingOnDeps int `json:"waiting_on_deps_count"`
|
||||
Running int `json:"running_count"`
|
||||
Complete int `json:"completed_count"`
|
||||
}{
|
||||
Workers: 2,
|
||||
Pending: 0,
|
||||
WaitingOnDeps: 0,
|
||||
Running: 0,
|
||||
Complete: 0,
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid response",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `invalid json`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
info, err := client.QueueInfo()
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, info)
|
||||
})
|
||||
}
|
||||
}
|
304
woodpecker-go/woodpecker/repo.go
Normal file
304
woodpecker-go/woodpecker/repo.go
Normal file
|
@ -0,0 +1,304 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
pathRepoPost = "%s/api/repos?forge_remote_id=%d"
|
||||
pathRepo = "%s/api/repos/%d"
|
||||
pathRepoLookup = "%s/api/repos/lookup/%s"
|
||||
pathRepoMove = "%s/api/repos/%d/move?to=%s"
|
||||
pathChown = "%s/api/repos/%d/chown"
|
||||
pathRepair = "%s/api/repos/%d/repair"
|
||||
pathPipelines = "%s/api/repos/%d/pipelines"
|
||||
pathPipeline = "%s/api/repos/%d/pipelines/%v"
|
||||
pathPipelineLogs = "%s/api/repos/%d/logs/%d"
|
||||
pathStepLogs = "%s/api/repos/%d/logs/%d/%d"
|
||||
pathApprove = "%s/api/repos/%d/pipelines/%d/approve"
|
||||
pathDecline = "%s/api/repos/%d/pipelines/%d/decline"
|
||||
pathStop = "%s/api/repos/%d/pipelines/%d/cancel"
|
||||
pathRepoSecrets = "%s/api/repos/%d/secrets"
|
||||
pathRepoSecret = "%s/api/repos/%d/secrets/%s"
|
||||
pathRepoRegistries = "%s/api/repos/%d/registry"
|
||||
pathRepoRegistry = "%s/api/repos/%d/registry/%s"
|
||||
pathRepoCrons = "%s/api/repos/%d/cron"
|
||||
pathRepoCron = "%s/api/repos/%d/cron/%d"
|
||||
)
|
||||
|
||||
// Repo returns a repository by id.
|
||||
func (c *client) Repo(repoID int64) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoLookup returns a repository by name.
|
||||
func (c *client) RepoLookup(fullName string) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepoLookup, c.addr, fullName)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoPost activates a repository.
|
||||
func (c *client) RepoPost(forgeRemoteID int64) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepoPost, c.addr, forgeRemoteID)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoChown updates a repository owner.
|
||||
func (c *client) RepoChown(repoID int64) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathChown, c.addr, repoID)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoRepair repairs the repository hooks.
|
||||
func (c *client) RepoRepair(repoID int64) error {
|
||||
uri := fmt.Sprintf(pathRepair, c.addr, repoID)
|
||||
return c.post(uri, nil, nil)
|
||||
}
|
||||
|
||||
// RepoPatch updates a repository.
|
||||
func (c *client) RepoPatch(repoID int64, in *RepoPatch) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoDel deletes a repository.
|
||||
func (c *client) RepoDel(repoID int64) error {
|
||||
uri := fmt.Sprintf(pathRepo, c.addr, repoID)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// RepoMove moves a repository.
|
||||
func (c *client) RepoMove(repoID int64, newFullName string) error {
|
||||
uri := fmt.Sprintf(pathRepoMove, c.addr, repoID, newFullName)
|
||||
return c.post(uri, nil, nil)
|
||||
}
|
||||
|
||||
// Registry returns a registry by hostname.
|
||||
func (c *client) Registry(repoID int64, hostname string) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryList returns a list of all repository registries.
|
||||
func (c *client) RegistryList(repoID int64) ([]*Registry, error) {
|
||||
var out []*Registry
|
||||
uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryCreate creates a registry.
|
||||
func (c *client) RegistryCreate(repoID int64, in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryUpdate updates a registry.
|
||||
func (c *client) RegistryUpdate(repoID int64, in *Registry) (*Registry, error) {
|
||||
out := new(Registry)
|
||||
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, in.Address)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RegistryDelete deletes a registry.
|
||||
func (c *client) RegistryDelete(repoID int64, hostname string) error {
|
||||
uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// Secret returns a secret by name.
|
||||
func (c *client) Secret(repoID int64, secret string) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretList returns a list of all repository secrets.
|
||||
func (c *client) SecretList(repoID int64) ([]*Secret, error) {
|
||||
var out []*Secret
|
||||
uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretCreate creates a secret.
|
||||
func (c *client) SecretCreate(repoID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretUpdate updates a secret.
|
||||
func (c *client) SecretUpdate(repoID int64, in *Secret) (*Secret, error) {
|
||||
out := new(Secret)
|
||||
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, in.Name)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// SecretDelete deletes a secret.
|
||||
func (c *client) SecretDelete(repoID int64, secret string) error {
|
||||
uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// CronList returns a list of cronjobs for the specified repository.
|
||||
func (c *client) CronList(repoID int64) ([]*Cron, error) {
|
||||
out := make([]*Cron, 0, 5)
|
||||
uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
|
||||
return out, c.get(uri, &out)
|
||||
}
|
||||
|
||||
// CronCreate creates a new cron job for the specified repository.
|
||||
func (c *client) CronCreate(repoID int64, in *Cron) (*Cron, error) {
|
||||
out := new(Cron)
|
||||
uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID)
|
||||
return out, c.post(uri, in, out)
|
||||
}
|
||||
|
||||
// CronUpdate updates an existing cron job for the specified repository.
|
||||
func (c *client) CronUpdate(repoID int64, in *Cron) (*Cron, error) {
|
||||
out := new(Cron)
|
||||
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, in.ID)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// CronDelete deletes a cron job by cron-id for the specified repository.
|
||||
func (c *client) CronDelete(repoID, cronID int64) error {
|
||||
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
|
||||
return c.delete(uri)
|
||||
}
|
||||
|
||||
// CronGet returns a cron job by cron-id for the specified repository.
|
||||
func (c *client) CronGet(repoID, cronID int64) (*Cron, error) {
|
||||
out := new(Cron)
|
||||
uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID)
|
||||
return out, c.get(uri, out)
|
||||
}
|
||||
|
||||
// Pipeline returns a repository pipeline by pipeline-id.
|
||||
func (c *client) Pipeline(repoID, pipeline int64) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Pipeline returns the latest repository pipeline by branch.
|
||||
func (c *client) PipelineLast(repoID int64, branch string) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, "latest")
|
||||
if len(branch) != 0 {
|
||||
uri += "?branch=" + branch
|
||||
}
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineList returns a list of recent pipelines for the
|
||||
// the specified repository.
|
||||
func (c *client) PipelineList(repoID int64) ([]*Pipeline, error) {
|
||||
var out []*Pipeline
|
||||
uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineCreate creates a new pipeline for the specified repository.
|
||||
func (c *client) PipelineCreate(repoID int64, options *PipelineOptions) (*Pipeline, error) {
|
||||
var out *Pipeline
|
||||
uri := fmt.Sprintf(pathPipelines, c.addr, repoID)
|
||||
err := c.post(uri, options, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineStart re-starts a stopped pipeline.
|
||||
func (c *client) PipelineStart(repoID, pipeline int64, params map[string]string) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
val := mapValues(params)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.post(uri+"?"+val.Encode(), nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineStop cancels the running step.
|
||||
func (c *client) PipelineStop(repoID, pipeline int64) error {
|
||||
uri := fmt.Sprintf(pathStop, c.addr, repoID, pipeline)
|
||||
err := c.post(uri, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// PipelineApprove approves a blocked pipeline.
|
||||
func (c *client) PipelineApprove(repoID, pipeline int64) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathApprove, c.addr, repoID, pipeline)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineDecline declines a blocked pipeline.
|
||||
func (c *client) PipelineDecline(repoID, pipeline int64) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
uri := fmt.Sprintf(pathDecline, c.addr, repoID, pipeline)
|
||||
err := c.post(uri, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PipelineKill force kills the running pipeline.
|
||||
func (c *client) PipelineKill(repoID, pipeline int64) error {
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// LogsPurge purges the pipeline all steps logs for the specified pipeline.
|
||||
func (c *client) LogsPurge(repoID, pipeline int64) error {
|
||||
uri := fmt.Sprintf(pathPipelineLogs, c.addr, repoID, pipeline)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// Deploy triggers a deployment for an existing pipeline using the
|
||||
// specified target environment.
|
||||
func (c *client) Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) {
|
||||
out := new(Pipeline)
|
||||
val := mapValues(params)
|
||||
val.Set("event", EventDeploy)
|
||||
val.Set("deploy_to", env)
|
||||
uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)
|
||||
err := c.post(uri+"?"+val.Encode(), nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// StepLogEntries returns the pipeline logs for the specified step.
|
||||
func (c *client) StepLogEntries(repoID, num, step int64) ([]*LogEntry, error) {
|
||||
uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, num, step)
|
||||
var out []*LogEntry
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// StepLogsPurge purges the pipeline logs for the specified step.
|
||||
func (c *client) StepLogsPurge(repoID, pipelineNumber, stepID int64) error {
|
||||
uri := fmt.Sprintf(pathStepLogs, c.addr, repoID, pipelineNumber, stepID)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
|
@ -181,12 +181,22 @@ type (
|
|||
Commit string `json:"commit,omitempty"`
|
||||
}
|
||||
|
||||
// QueueStats struct {
|
||||
// Workers int `json:"worker_count"`
|
||||
// Pending int `json:"pending_count"`
|
||||
// WaitingOnDeps int `json:"waiting_on_deps_count"`
|
||||
// Running int `json:"running_count"`
|
||||
// Complete int `json:"completed_count"`
|
||||
// }
|
||||
|
||||
// Info provides queue stats.
|
||||
Info struct {
|
||||
Pending []Task `json:"pending"`
|
||||
WaitingOnDeps []Task `json:"waiting_on_deps"`
|
||||
Running []Task `json:"running"`
|
||||
Stats struct {
|
||||
// TODO use dedicated struct in 3.x
|
||||
// Stats QueueStats `json:"stats"`
|
||||
Stats struct {
|
||||
Workers int `json:"worker_count"`
|
||||
Pending int `json:"pending_count"`
|
||||
WaitingOnDeps int `json:"waiting_on_deps_count"`
|
||||
|
@ -196,7 +206,7 @@ type (
|
|||
Paused bool `json:"paused,omitempty"`
|
||||
}
|
||||
|
||||
// LogLevel is for checking/setting logging level
|
||||
// LogLevel is for checking/setting logging level.
|
||||
LogLevel struct {
|
||||
Level string `json:"log-level"`
|
||||
}
|
||||
|
@ -211,7 +221,7 @@ type (
|
|||
Type LogEntryType `json:"type"`
|
||||
}
|
||||
|
||||
// Cron is the JSON data of a cron job
|
||||
// Cron is the JSON data of a cron job.
|
||||
Cron struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
@ -223,13 +233,13 @@ type (
|
|||
Branch string `json:"branch"`
|
||||
}
|
||||
|
||||
// PipelineOptions is the JSON data for creating a new pipeline
|
||||
// PipelineOptions is the JSON data for creating a new pipeline.
|
||||
PipelineOptions struct {
|
||||
Branch string `json:"branch"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
}
|
||||
|
||||
// Agent is the JSON data for an agent
|
||||
// Agent is the JSON data for an agent.
|
||||
Agent struct {
|
||||
ID int64 `json:"id"`
|
||||
Created int64 `json:"created"`
|
||||
|
@ -245,7 +255,7 @@ type (
|
|||
NoSchedule bool `json:"no_schedule"`
|
||||
}
|
||||
|
||||
// Task is the JSON data for a task
|
||||
// Task is the JSON data for a task.
|
||||
Task struct {
|
||||
ID string `json:"id"`
|
||||
Data []byte `json:"data"`
|
||||
|
@ -256,7 +266,7 @@ type (
|
|||
AgentID int64 `json:"agent_id"`
|
||||
}
|
||||
|
||||
// Org is the JSON data for an organization
|
||||
// Org is the JSON data for an organization.
|
||||
Org struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
75
woodpecker-go/woodpecker/user.go
Normal file
75
woodpecker-go/woodpecker/user.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package woodpecker
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
pathSelf = "%s/api/user"
|
||||
pathRepos = "%s/api/user/repos"
|
||||
pathUsers = "%s/api/users"
|
||||
pathUser = "%s/api/users/%s"
|
||||
)
|
||||
|
||||
// Self returns the currently authenticated user.
|
||||
func (c *client) Self() (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathSelf, c.addr)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// User returns a user by login.
|
||||
func (c *client) User(login string) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUser, c.addr, login)
|
||||
err := c.get(uri, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserList returns a list of all registered users.
|
||||
func (c *client) UserList() ([]*User, error) {
|
||||
var out []*User
|
||||
uri := fmt.Sprintf(pathUsers, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserPost creates a new user account.
|
||||
func (c *client) UserPost(in *User) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUsers, c.addr)
|
||||
err := c.post(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserPatch updates a user account.
|
||||
func (c *client) UserPatch(in *User) (*User, error) {
|
||||
out := new(User)
|
||||
uri := fmt.Sprintf(pathUser, c.addr, in.Login)
|
||||
err := c.patch(uri, in, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// UserDel deletes a user account.
|
||||
func (c *client) UserDel(login string) error {
|
||||
uri := fmt.Sprintf(pathUser, c.addr, login)
|
||||
err := c.delete(uri)
|
||||
return err
|
||||
}
|
||||
|
||||
// RepoList returns a list of all repositories to which
|
||||
// the user has explicit access in the host system.
|
||||
func (c *client) RepoList() ([]*Repo, error) {
|
||||
var out []*Repo
|
||||
uri := fmt.Sprintf(pathRepos, c.addr)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// RepoListOpts returns a list of all repositories to which
|
||||
// the user has explicit access in the host system.
|
||||
func (c *client) RepoListOpts(all bool) ([]*Repo, error) {
|
||||
var out []*Repo
|
||||
uri := fmt.Sprintf(pathRepos+"?all=%v", c.addr, all)
|
||||
err := c.get(uri, &out)
|
||||
return out, err
|
||||
}
|
267
woodpecker-go/woodpecker/user_test.go
Normal file
267
woodpecker-go/woodpecker/user_test.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package woodpecker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClient_UserList(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
expected []*User
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `[{"id":1,"login":"user1"},{"id":2,"login":"user2"}]`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
expected: []*User{{ID: 1, Login: "user1"}, {ID: 2, Login: "user2"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty response",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `[]`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
expected: []*User{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
users, err := client.UserList()
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, users, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_UserPost(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
input *User
|
||||
expected *User
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
_, err := fmt.Fprint(w, `{"id":1,"login":"new_user"}`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
input: &User{Login: "new_user"},
|
||||
expected: &User{ID: 1, Login: "new_user"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid input",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
},
|
||||
input: &User{},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
input: &User{Login: "new_user"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
user, err := client.UserPost(tt.input)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_UserPatch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
input *User
|
||||
expected *User
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprint(w, `{"id":1,"login":"updated_user"}`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
input: &User{ID: 1, Login: "existing_user"},
|
||||
expected: &User{ID: 1, Login: "updated_user"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
},
|
||||
input: &User{ID: 999, Login: "nonexistent_user"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid input",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
},
|
||||
input: &User{},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
input: &User{ID: 1, Login: "existing_user"},
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
user, err := client.UserPatch(tt.input)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_UserDel(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler http.HandlerFunc
|
||||
login string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
login: "existing_user",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
},
|
||||
login: "nonexistent_user",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
login: "existing_user",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts := httptest.NewServer(tt.handler)
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(ts.URL, http.DefaultClient)
|
||||
err := client.UserDel(tt.login)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue