From 191cdd108f7ae7893239f05e17e2d54e4e1dd10f Mon Sep 17 00:00:00 2001 From: Sergey Vanyushkin Date: Sat, 16 May 2026 13:09:07 +0300 Subject: [PATCH] feat: add Timeweb Cloud provider for Woodpecker CI autoscaler - Implement timewebcloud provider with DeployAgent, RemoveAgent, ListDeployedAgentNames - Add minimal HTTP API client for Timeweb Cloud (create/list/delete servers) - Register provider in main.go with CLI flags - Add timeweb-list and timeweb-tester utilities - Include Dockerfile and docker-compose.yml for deployment - Update DEPLOY.md with verified OS/preset IDs --- .gitignore | 8 + CHANGELOG.md | 743 ++++ Dockerfile | 17 + LICENSE | 201 + Makefile | 133 + README.md | 105 + agents.md | 151 + checkmake.ini | 2 + config/config.go | 19 + docker-compose.yml | 32 + docker/Dockerfile | 18 + docker/Dockerfile.make | 27 + docs/DEPLOY.md | 92 + engine/autoscaler.go | 400 ++ engine/autoscaler_test.go | 546 +++ engine/const.go | 9 + engine/inits/cloudinit/cloudinit.go | 116 + engine/inits/cloudinit/cloudinit_test.go | 65 + engine/types/mocks/mock_Provider.go | 215 + engine/types/provider.go | 13 + go.mod | 28 + go.sum | 44 + providers/timewebcloud/api/client.go | 222 + providers/timewebcloud/flags.go | 47 + providers/timewebcloud/provider.go | 122 + release-config.ts | 3 + renovate.json | 4 + server/client.go | 79 + server/mocks/mock_Client.go | 4982 ++++++++++++++++++++++ utils/random.go | 18 + utils/random_test.go | 42 + utils/stringmaps.go | 39 + utils/stringmaps_test.go | 96 + version/version.go | 13 + 34 files changed, 8651 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 agents.md create mode 100644 checkmake.ini create mode 100644 config/config.go create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/Dockerfile.make create mode 100644 docs/DEPLOY.md create mode 100644 engine/autoscaler.go create mode 100644 engine/autoscaler_test.go create mode 100644 engine/const.go create mode 100644 engine/inits/cloudinit/cloudinit.go create mode 100644 engine/inits/cloudinit/cloudinit_test.go create mode 100644 engine/types/mocks/mock_Provider.go create mode 100644 engine/types/provider.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 providers/timewebcloud/api/client.go create mode 100644 providers/timewebcloud/flags.go create mode 100644 providers/timewebcloud/provider.go create mode 100644 release-config.ts create mode 100644 renovate.json create mode 100644 server/client.go create mode 100644 server/mocks/mock_Client.go create mode 100644 utils/random.go create mode 100644 utils/random_test.go create mode 100644 utils/stringmaps.go create mode 100644 utils/stringmaps_test.go create mode 100644 version/version.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5860d1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +woodpecker-autoscaler +timeweb-list +timeweb-tester +timeweb-debug +*.exe +*.test +*.out +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..98c5586 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,743 @@ +# Changelog + +## [1.4.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.4.0) - 2026-04-29 + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @mendarb + +### 📈 Enhancement + +- Make agent extra labels an explicit option [[#584](https://github.com/woodpecker-ci/autoscaler/pull/584)] +- Move code in subpackages [[#585](https://github.com/woodpecker-ci/autoscaler/pull/585)] + +### 🐛 Bug Fixes + +- Propagate tags to EBS volumes on AWS instances [[#568](https://github.com/woodpecker-ci/autoscaler/pull/568)] + +### 📦️ Dependency + +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.39.0 [[#599](https://github.com/woodpecker-ci/autoscaler/pull/599)] +- fix(deps): update golang deps non-major [[#591](https://github.com/woodpecker-ci/autoscaler/pull/591)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.31.1 [[#589](https://github.com/woodpecker-ci/autoscaler/pull/589)] + +## [1.3.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.3.0) - 2026-04-21 + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @BnMcG + +### 📈 Enhancement + +- Surface regex compile error [[#586](https://github.com/woodpecker-ci/autoscaler/pull/586)] + +### 🐛 Bug Fixes + +- fix(scaleway): tidy up scaleway volumes [[#559](https://github.com/woodpecker-ci/autoscaler/pull/559)] + +### 📦️ Dependency + +- fix(deps): update golang deps non-major [[#588](https://github.com/woodpecker-ci/autoscaler/pull/588)] +- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v4.1.1 [[#587](https://github.com/woodpecker-ci/autoscaler/pull/587)] +- fix(deps): update golang deps non-major [[#578](https://github.com/woodpecker-ci/autoscaler/pull/578)] +- fix(deps): update golang.org/x/exp digest to 746e56f [[#579](https://github.com/woodpecker-ci/autoscaler/pull/579)] +- chore(deps): update dependency golangci/golangci-lint to v2.11.4 [[#554](https://github.com/woodpecker-ci/autoscaler/pull/554)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.30.0 [[#576](https://github.com/woodpecker-ci/autoscaler/pull/576)] +- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v4 [[#556](https://github.com/woodpecker-ci/autoscaler/pull/556)] +- fix(deps): update golang.org/x/exp digest to 7ab1446 [[#560](https://github.com/woodpecker-ci/autoscaler/pull/560)] +- fix(deps): update golang deps non-major [[#575](https://github.com/woodpecker-ci/autoscaler/pull/575)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/config to v1.32.14 [[#574](https://github.com/woodpecker-ci/autoscaler/pull/574)] +- fix(deps): update golang deps non-major [[#573](https://github.com/woodpecker-ci/autoscaler/pull/573)] +- fix(deps): update golang deps non-major [[#572](https://github.com/woodpecker-ci/autoscaler/pull/572)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.4 [[#569](https://github.com/woodpecker-ci/autoscaler/pull/569)] +- fix(deps): update golang deps non-major [[#571](https://github.com/woodpecker-ci/autoscaler/pull/571)] +- fix(deps): update module github.com/urfave/cli/v3 to v3.8.0 [[#570](https://github.com/woodpecker-ci/autoscaler/pull/570)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.296.0 [[#562](https://github.com/woodpecker-ci/autoscaler/pull/562)] +- fix(deps): update golang deps non-major [[#561](https://github.com/woodpecker-ci/autoscaler/pull/561)] +- fix(deps): update golang deps non-major [[#557](https://github.com/woodpecker-ci/autoscaler/pull/557)] +- fix(deps): update golang.org/x/exp digest to 3dfff04 [[#548](https://github.com/woodpecker-ci/autoscaler/pull/548)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.11.3 [[#555](https://github.com/woodpecker-ci/autoscaler/pull/555)] +- chore(deps): update dependency go to v1.26.1 [[#553](https://github.com/woodpecker-ci/autoscaler/pull/553)] +- chore(deps): update dependency golangci/golangci-lint to v2.11.0 [[#543](https://github.com/woodpecker-ci/autoscaler/pull/543)] +- fix(deps): update golang deps non-major [[#552](https://github.com/woodpecker-ci/autoscaler/pull/552)] +- fix(deps): update golang deps non-major [[#551](https://github.com/woodpecker-ci/autoscaler/pull/551)] +- fix(deps): update golang deps non-major [[#541](https://github.com/woodpecker-ci/autoscaler/pull/541)] +- chore(deps): update docker.io/golang docker tag to v1.26 [[#544](https://github.com/woodpecker-ci/autoscaler/pull/544)] +- chore(deps): update pre-commit non-major [[#549](https://github.com/woodpecker-ci/autoscaler/pull/549)] +- fix(deps): update golang.org/x/exp digest to 81e46e3 [[#542](https://github.com/woodpecker-ci/autoscaler/pull/542)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.9.0 [[#546](https://github.com/woodpecker-ci/autoscaler/pull/546)] +- chore(deps): update golang docker tag to v1.26 [[#545](https://github.com/woodpecker-ci/autoscaler/pull/545)] +- fix(deps): update module golang.org/x/oauth2 to v0.35.0 [[#540](https://github.com/woodpecker-ci/autoscaler/pull/540)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.27.0 [[#536](https://github.com/woodpecker-ci/autoscaler/pull/536)] +- chore(deps): update dependency go to v1.25.7 [[#535](https://github.com/woodpecker-ci/autoscaler/pull/535)] +- fix(deps): update module github.com/linode/linodego to v1.65.0 [[#533](https://github.com/woodpecker-ci/autoscaler/pull/533)] + +## [1.2.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.2.0) - 2026-01-30 + +### ❤️ Thanks to all contributors! ❤️ + +@jooola, @xoxys + +### 📈 Enhancement + +- feat: add version flag [[#523](https://github.com/woodpecker-ci/autoscaler/pull/523)] +- feat: add Woodpecker to Hetzner Cloud client user agent [[#519](https://github.com/woodpecker-ci/autoscaler/pull/519)] + +### 📦️ Dependency + +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.285.0 [[#527](https://github.com/woodpecker-ci/autoscaler/pull/527)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.284.0 [[#526](https://github.com/woodpecker-ci/autoscaler/pull/526)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.283.0 [[#525](https://github.com/woodpecker-ci/autoscaler/pull/525)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.282.0 [[#524](https://github.com/woodpecker-ci/autoscaler/pull/524)] +- fix(deps): update golang deps non-major [[#521](https://github.com/woodpecker-ci/autoscaler/pull/521)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.280.0 [[#520](https://github.com/woodpecker-ci/autoscaler/pull/520)] +- fix(deps): update golang.org/x/exp digest to 716be56 [[#515](https://github.com/woodpecker-ci/autoscaler/pull/515)] +- fix(deps): update module github.com/urfave/cli/v3 to v3.6.2 [[#518](https://github.com/woodpecker-ci/autoscaler/pull/518)] +- chore(deps): update pre-commit hook adrienverge/yamllint to v1.38.0 [[#516](https://github.com/woodpecker-ci/autoscaler/pull/516)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.35.0 [[#517](https://github.com/woodpecker-ci/autoscaler/pull/517)] +- fix(deps): update golang deps non-major [[#514](https://github.com/woodpecker-ci/autoscaler/pull/514)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v3 to v3.13.0 [[#512](https://github.com/woodpecker-ci/autoscaler/pull/512)] + +## [1.1.5](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.1.5) - 2026-01-13 + +### ❤️ Thanks to all contributors! ❤️ + +@qwerty287 + +### 🐛 Bug Fixes + +- Fix log format messages [[#502](https://github.com/woodpecker-ci/autoscaler/pull/502)] + +### 📦️ Dependency + +- chore(deps): update pre-commit non-major [[#511](https://github.com/woodpecker-ci/autoscaler/pull/511)] +- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v3.4.1 [[#509](https://github.com/woodpecker-ci/autoscaler/pull/509)] +- fix(deps): update golang deps non-major [[#507](https://github.com/woodpecker-ci/autoscaler/pull/507)] +- chore(deps): update docker.io/woodpeckerci/plugin-editorconfig-checker docker tag to v0.3.3 [[#506](https://github.com/woodpecker-ci/autoscaler/pull/506)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v6.0.4 [[#508](https://github.com/woodpecker-ci/autoscaler/pull/508)] +- chore(deps): update dependency golangci/golangci-lint to v2.8.0 [[#510](https://github.com/woodpecker-ci/autoscaler/pull/510)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.34.0 [[#505](https://github.com/woodpecker-ci/autoscaler/pull/505)] +- fix(deps): update module github.com/linode/linodego to v1.64.0 [[#504](https://github.com/woodpecker-ci/autoscaler/pull/504)] + +## [1.1.4](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.1.4) - 2025-12-23 + +### ❤️ Thanks to all contributors! ❤️ + +@mossylion + +### 🐛 Bug Fixes + +- Allow setting Scaleway storage type and default to l_ssd [[#501](https://github.com/woodpecker-ci/autoscaler/pull/501)] + +### 📦️ Dependency + +- fix(deps): update golang.org/x/exp digest to 944ab1f [[#499](https://github.com/woodpecker-ci/autoscaler/pull/499)] +- fix(deps): update golang deps non-major [[#500](https://github.com/woodpecker-ci/autoscaler/pull/500)] +- fix(deps): update golang deps non-major [[#498](https://github.com/woodpecker-ci/autoscaler/pull/498)] +- fix(deps): update golang deps non-major [[#496](https://github.com/woodpecker-ci/autoscaler/pull/496)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.277.0 [[#495](https://github.com/woodpecker-ci/autoscaler/pull/495)] +- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.47.0 [[#494](https://github.com/woodpecker-ci/autoscaler/pull/494)] +- fix(deps): update golang.org/x/exp digest to 8475f28 [[#493](https://github.com/woodpecker-ci/autoscaler/pull/493)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.26.0 [[#492](https://github.com/woodpecker-ci/autoscaler/pull/492)] +- fix(deps): update golang deps non-major [[#491](https://github.com/woodpecker-ci/autoscaler/pull/491)] +- chore(deps): update dependency golangci/golangci-lint to v2.7.2 [[#489](https://github.com/woodpecker-ci/autoscaler/pull/489)] +- fix(deps): update golang deps non-major [[#490](https://github.com/woodpecker-ci/autoscaler/pull/490)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.2 [[#487](https://github.com/woodpecker-ci/autoscaler/pull/487)] + +## [1.1.3](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.1.3) - 2025-12-06 + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @xoxys + +### 🐛 Bug Fixes + +- Ensure latest qemu packages are installed [[#484](https://github.com/woodpecker-ci/autoscaler/pull/484)] + +### 📦️ Dependency + +- chore(deps): update dependency golangci/golangci-lint to v2.7.1 [[#485](https://github.com/woodpecker-ci/autoscaler/pull/485)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.1 [[#486](https://github.com/woodpecker-ci/autoscaler/pull/486)] +- fix(deps): update golang deps non-major [[#483](https://github.com/woodpecker-ci/autoscaler/pull/483)] +- chore(deps): update dependency golangci/golangci-lint to v2.6.2 [[#471](https://github.com/woodpecker-ci/autoscaler/pull/471)] +- fix(deps): update golang.org/x/exp digest to 87e1e73 [[#482](https://github.com/woodpecker-ci/autoscaler/pull/482)] +- fix(deps): update golang deps non-major [[#481](https://github.com/woodpecker-ci/autoscaler/pull/481)] +- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.46.0 [[#479](https://github.com/woodpecker-ci/autoscaler/pull/479)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/config to v1.32.1 [[#480](https://github.com/woodpecker-ci/autoscaler/pull/480)] +- fix(deps): update golang deps non-major [[#478](https://github.com/woodpecker-ci/autoscaler/pull/478)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.273.0 [[#477](https://github.com/woodpecker-ci/autoscaler/pull/477)] +- fix(deps): update golang deps non-major [[#476](https://github.com/woodpecker-ci/autoscaler/pull/476)] +- fix(deps): update golang deps non-major [[#475](https://github.com/woodpecker-ci/autoscaler/pull/475)] +- fix(deps): update module github.com/urfave/cli/v3 to v3.6.1 [[#474](https://github.com/woodpecker-ci/autoscaler/pull/474)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.269.0 [[#473](https://github.com/woodpecker-ci/autoscaler/pull/473)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.2 [[#472](https://github.com/woodpecker-ci/autoscaler/pull/472)] +- fix(deps): update golang.org/x/exp digest to e25ba8c [[#470](https://github.com/woodpecker-ci/autoscaler/pull/470)] +- fix(deps): update golang deps non-major [[#469](https://github.com/woodpecker-ci/autoscaler/pull/469)] +- fix(deps): update golang deps non-major [[#468](https://github.com/woodpecker-ci/autoscaler/pull/468)] +- fix(deps): update module github.com/urfave/cli/v3 to v3.6.0 [[#467](https://github.com/woodpecker-ci/autoscaler/pull/467)] +- chore(deps): update dependency golangci/golangci-lint to v2.6.1 [[#464](https://github.com/woodpecker-ci/autoscaler/pull/464)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.1 [[#465](https://github.com/woodpecker-ci/autoscaler/pull/465)] +- fix(deps): update golang deps non-major [[#466](https://github.com/woodpecker-ci/autoscaler/pull/466)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.263.0 [[#463](https://github.com/woodpecker-ci/autoscaler/pull/463)] +- fix(deps): update golang deps non-major [[#462](https://github.com/woodpecker-ci/autoscaler/pull/462)] +- fix(deps): update golang deps non-major [[#461](https://github.com/woodpecker-ci/autoscaler/pull/461)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.261.0 [[#460](https://github.com/woodpecker-ci/autoscaler/pull/460)] +- chore(deps): update node.js to v24 [[#458](https://github.com/woodpecker-ci/autoscaler/pull/458)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.6.0 [[#457](https://github.com/woodpecker-ci/autoscaler/pull/457)] +- chore(deps): update dependency golangci/golangci-lint to v2.6.0 [[#456](https://github.com/woodpecker-ci/autoscaler/pull/456)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.260.0 [[#459](https://github.com/woodpecker-ci/autoscaler/pull/459)] +- fix(deps): update golang deps non-major [[#455](https://github.com/woodpecker-ci/autoscaler/pull/455)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.259.0 [[#454](https://github.com/woodpecker-ci/autoscaler/pull/454)] +- fix(deps): update golang.org/x/exp digest to a4bb9ff [[#451](https://github.com/woodpecker-ci/autoscaler/pull/451)] +- chore(deps): update dependency mvdan/gofumpt to v0.9.2 [[#452](https://github.com/woodpecker-ci/autoscaler/pull/452)] +- fix(deps): update golang deps non-major [[#453](https://github.com/woodpecker-ci/autoscaler/pull/453)] +- fix(deps): update golang deps non-major [[#450](https://github.com/woodpecker-ci/autoscaler/pull/450)] +- Use our own editorconfig checker plugin [[#447](https://github.com/woodpecker-ci/autoscaler/pull/447)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v3 to v3.11.0 [[#448](https://github.com/woodpecker-ci/autoscaler/pull/448)] + +## [1.1.2](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.1.2) - 2025-10-19 + +### ❤️ Thanks to all contributors! ❤️ + +@xoxys + +### 📦️ Dependency + +- fix(deps): update golang.org/x/exp digest to 90e834f [[#444](https://github.com/woodpecker-ci/autoscaler/pull/444)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.257.2 [[#445](https://github.com/woodpecker-ci/autoscaler/pull/445)] +- fix(deps): update golang deps non-major [[#443](https://github.com/woodpecker-ci/autoscaler/pull/443)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.257.0 [[#442](https://github.com/woodpecker-ci/autoscaler/pull/442)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.256.0 [[#441](https://github.com/woodpecker-ci/autoscaler/pull/441)] +- fix(deps): update golang deps non-major [[#440](https://github.com/woodpecker-ci/autoscaler/pull/440)] +- chore(deps): update docker.io/mstruebing/editorconfig-checker docker tag to v3.4.1 [[#439](https://github.com/woodpecker-ci/autoscaler/pull/439)] +- fix(deps): update golang.org/x/exp digest to d2f985d [[#438](https://github.com/woodpecker-ci/autoscaler/pull/438)] +- fix(deps): update golang deps non-major [[#437](https://github.com/woodpecker-ci/autoscaler/pull/437)] +- fix(deps): update golang.org/x/exp digest to 27f1f14 [[#434](https://github.com/woodpecker-ci/autoscaler/pull/434)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.27.0 [[#436](https://github.com/woodpecker-ci/autoscaler/pull/436)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v6.0.3 [[#435](https://github.com/woodpecker-ci/autoscaler/pull/435)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/config to v1.31.12 [[#433](https://github.com/woodpecker-ci/autoscaler/pull/433)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v3 to v3.10.0 [[#432](https://github.com/woodpecker-ci/autoscaler/pull/432)] +- chore(deps): update pre-commit hook hadolint/hadolint to v2.14.0 [[#431](https://github.com/woodpecker-ci/autoscaler/pull/431)] +- fix(deps): update golang deps non-major [[#430](https://github.com/woodpecker-ci/autoscaler/pull/430)] +- fix(deps): update golang deps non-major [[#429](https://github.com/woodpecker-ci/autoscaler/pull/429)] +- fix(deps): update golang deps non-major [[#427](https://github.com/woodpecker-ci/autoscaler/pull/427)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/config to v1.31.9 [[#426](https://github.com/woodpecker-ci/autoscaler/pull/426)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.5.0 [[#425](https://github.com/woodpecker-ci/autoscaler/pull/425)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.24.0 [[#424](https://github.com/woodpecker-ci/autoscaler/pull/424)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.24.0 [[#423](https://github.com/woodpecker-ci/autoscaler/pull/423)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.253.0 [[#422](https://github.com/woodpecker-ci/autoscaler/pull/422)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.252.0 [[#421](https://github.com/woodpecker-ci/autoscaler/pull/421)] +- fix(deps): update golang deps non-major [[#420](https://github.com/woodpecker-ci/autoscaler/pull/420)] +- chore(deps): update docker.io/mstruebing/editorconfig-checker docker tag to v3.4.0 [[#419](https://github.com/woodpecker-ci/autoscaler/pull/419)] +- fix(deps): update golang.org/x/exp digest to df92998 [[#418](https://github.com/woodpecker-ci/autoscaler/pull/418)] +- fix(deps): update golang deps non-major [[#417](https://github.com/woodpecker-ci/autoscaler/pull/417)] +- fix(deps): update golang deps non-major [[#416](https://github.com/woodpecker-ci/autoscaler/pull/416)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.245.0 [[#414](https://github.com/woodpecker-ci/autoscaler/pull/414)] + +### Misc + +- Regenerate woodpecker-client mocks [[#446](https://github.com/woodpecker-ci/autoscaler/pull/446)] +- Migrate mockery to v3 [[#428](https://github.com/woodpecker-ci/autoscaler/pull/428)] + +## [1.1.1](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.1.1) - 2025-08-18 + +### ❤️ Thanks to all contributors! ❤️ + +@xoxys + +### 🐛 Bug Fixes + +- Improve logging for fallback server types in hcloud provider [[#413](https://github.com/woodpecker-ci/autoscaler/pull/413)] + +### 📦️ Dependency + +- chore(deps): update golang docker tag to v1.25 [[#409](https://github.com/woodpecker-ci/autoscaler/pull/409)] +- fix(deps): update golang.org/x/exp digest to 42675ad [[#407](https://github.com/woodpecker-ci/autoscaler/pull/407)] +- chore(deps): update docker.io/golang docker tag to v1.25 [[#408](https://github.com/woodpecker-ci/autoscaler/pull/408)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.4.0 [[#411](https://github.com/woodpecker-ci/autoscaler/pull/411)] +- fix(deps): update golang deps non-major [[#406](https://github.com/woodpecker-ci/autoscaler/pull/406)] +- fix(deps): update golang deps non-major [[#405](https://github.com/woodpecker-ci/autoscaler/pull/405)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.4.0 [[#402](https://github.com/woodpecker-ci/autoscaler/pull/402)] +- chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v6 [[#403](https://github.com/woodpecker-ci/autoscaler/pull/403)] +- fix(deps): update golang deps non-major [[#404](https://github.com/woodpecker-ci/autoscaler/pull/404)] +- fix(deps): update golang.org/x/exp digest to 51f8813 [[#401](https://github.com/woodpecker-ci/autoscaler/pull/401)] +- fix(deps): update module golang.org/x/net to v0.43.0 [[#400](https://github.com/woodpecker-ci/autoscaler/pull/400)] +- fix(deps): update golang deps non-major [[#399](https://github.com/woodpecker-ci/autoscaler/pull/399)] +- fix(deps): update golang deps non-major [[#398](https://github.com/woodpecker-ci/autoscaler/pull/398)] +- fix(deps): update golang deps non-major [[#395](https://github.com/woodpecker-ci/autoscaler/pull/395)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.236.0 [[#394](https://github.com/woodpecker-ci/autoscaler/pull/394)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.3.0 [[#393](https://github.com/woodpecker-ci/autoscaler/pull/393)] +- fix(deps): update golang.org/x/exp digest to 645b1fa [[#389](https://github.com/woodpecker-ci/autoscaler/pull/389)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.235.0 [[#390](https://github.com/woodpecker-ci/autoscaler/pull/390)] +- fix(deps): update golang deps non-major [[#388](https://github.com/woodpecker-ci/autoscaler/pull/388)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.21.1 [[#387](https://github.com/woodpecker-ci/autoscaler/pull/387)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.232.0 [[#385](https://github.com/woodpecker-ci/autoscaler/pull/385)] + +### Misc + +- Use list format for pipelines [[#412](https://github.com/woodpecker-ci/autoscaler/pull/412)] +- [pre-commit.ci] pre-commit autoupdate [[#397](https://github.com/woodpecker-ci/autoscaler/pull/397)] + +## [1.1.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.1.0) - 2025-07-13 + +### ❤️ Thanks to all contributors! ❤️ + +@xoxys + +### 📈 Enhancement + +- Introduce global user-data flag [[#337](https://github.com/woodpecker-ci/autoscaler/pull/337)] + +### 📦️ Dependency + +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.2.2 [[#383](https://github.com/woodpecker-ci/autoscaler/pull/383)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.3.0 [[#377](https://github.com/woodpecker-ci/autoscaler/pull/377)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v6.0.2 [[#376](https://github.com/woodpecker-ci/autoscaler/pull/376)] +- fix(deps): update golang.org/x/exp digest to 6ae5c78 [[#382](https://github.com/woodpecker-ci/autoscaler/pull/382)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.22.0 [[#384](https://github.com/woodpecker-ci/autoscaler/pull/384)] +- fix(deps): update module golang.org/x/net to v0.42.0 [[#381](https://github.com/woodpecker-ci/autoscaler/pull/381)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.231.0 [[#380](https://github.com/woodpecker-ci/autoscaler/pull/380)] +- fix(deps): update golang deps non-major [[#379](https://github.com/woodpecker-ci/autoscaler/pull/379)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v3 to v3.8.0 [[#378](https://github.com/woodpecker-ci/autoscaler/pull/378)] +- fix(deps): update golang deps non-major [[#375](https://github.com/woodpecker-ci/autoscaler/pull/375)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.229.0 [[#374](https://github.com/woodpecker-ci/autoscaler/pull/374)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.228.0 [[#373](https://github.com/woodpecker-ci/autoscaler/pull/373)] +- fix(deps): update module github.com/linode/linodego to v1.52.2 [[#372](https://github.com/woodpecker-ci/autoscaler/pull/372)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.2.1 [[#371](https://github.com/woodpecker-ci/autoscaler/pull/371)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.2.0 [[#370](https://github.com/woodpecker-ci/autoscaler/pull/370)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.227.0 [[#369](https://github.com/woodpecker-ci/autoscaler/pull/369)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.226.0 [[#368](https://github.com/woodpecker-ci/autoscaler/pull/368)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.21.0 [[#367](https://github.com/woodpecker-ci/autoscaler/pull/367)] +- fix(deps): update golang.org/x/exp digest to b7579e2 [[#366](https://github.com/woodpecker-ci/autoscaler/pull/366)] +- fix(deps): update golang deps non-major [[#365](https://github.com/woodpecker-ci/autoscaler/pull/365)] +- fix(deps): update module github.com/urfave/cli/v3 to v3.3.8 [[#364](https://github.com/woodpecker-ci/autoscaler/pull/364)] +- fix(deps): update golang deps non-major [[#363](https://github.com/woodpecker-ci/autoscaler/pull/363)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.225.0 [[#362](https://github.com/woodpecker-ci/autoscaler/pull/362)] +- fix(deps): update golang deps non-major [[#361](https://github.com/woodpecker-ci/autoscaler/pull/361)] +- fix(deps): update golang.org/x/exp digest to dcc06ee [[#360](https://github.com/woodpecker-ci/autoscaler/pull/360)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.21.1 [[#359](https://github.com/woodpecker-ci/autoscaler/pull/359)] +- fix(deps): update module github.com/linode/linodego to v1.52.1 [[#358](https://github.com/woodpecker-ci/autoscaler/pull/358)] +- fix(deps): update golang.org/x/exp digest to b6e5de4 [[#357](https://github.com/woodpecker-ci/autoscaler/pull/357)] +- fix(deps): update golang.org/x/exp digest to 65e9200 [[#356](https://github.com/woodpecker-ci/autoscaler/pull/356)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.224.0 [[#355](https://github.com/woodpecker-ci/autoscaler/pull/355)] +- fix(deps): update golang deps non-major [[#354](https://github.com/woodpecker-ci/autoscaler/pull/354)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.222.0 [[#353](https://github.com/woodpecker-ci/autoscaler/pull/353)] +- fix(deps): update golang deps non-major [[#352](https://github.com/woodpecker-ci/autoscaler/pull/352)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.220.0 [[#351](https://github.com/woodpecker-ci/autoscaler/pull/351)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.219.0 [[#350](https://github.com/woodpecker-ci/autoscaler/pull/350)] +- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.45.0 [[#349](https://github.com/woodpecker-ci/autoscaler/pull/349)] +- fix(deps): update golang.org/x/exp digest to ce4c2cf [[#345](https://github.com/woodpecker-ci/autoscaler/pull/345)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.218.0 [[#348](https://github.com/woodpecker-ci/autoscaler/pull/348)] +- chore(deps): update mstruebing/editorconfig-checker docker tag to v3.3.0 [[#346](https://github.com/woodpecker-ci/autoscaler/pull/346)] +- fix(deps): update golang deps non-major [[#347](https://github.com/woodpecker-ci/autoscaler/pull/347)] +- fix(deps): update golang deps non-major [[#344](https://github.com/woodpecker-ci/autoscaler/pull/344)] +- fix(deps): update golang deps non-major [[#343](https://github.com/woodpecker-ci/autoscaler/pull/343)] +- fix(deps): update golang deps non-major [[#342](https://github.com/woodpecker-ci/autoscaler/pull/342)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v6 [[#340](https://github.com/woodpecker-ci/autoscaler/pull/340)] +- chore(deps): update pre-commit non-major [[#341](https://github.com/woodpecker-ci/autoscaler/pull/341)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.213.0 [[#335](https://github.com/woodpecker-ci/autoscaler/pull/335)] +- fix(deps): update module github.com/urfave/cli/v3 to v3.3.2 [[#334](https://github.com/woodpecker-ci/autoscaler/pull/334)] +- fix(deps): update module github.com/urfave/cli/v3 to v3.3.1 [[#333](https://github.com/woodpecker-ci/autoscaler/pull/333)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.1.5 [[#331](https://github.com/woodpecker-ci/autoscaler/pull/331)] + +## [1.0.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/1.0.0) - 2025-04-24 + +### ❤️ Thanks to all contributors! ❤️ + +@gsaslis, @xoxys + +### 💥 Breaking changes + +- fix(deps): update module github.com/urfave/cli/v2 to v3 [[#317](https://github.com/woodpecker-ci/autoscaler/pull/317)] + +### 📚 Documentation + +- Fix link to caddy docs [[#326](https://github.com/woodpecker-ci/autoscaler/pull/326)] + +### 📦️ Dependency + +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.212.0 [[#330](https://github.com/woodpecker-ci/autoscaler/pull/330)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.2.0 [[#328](https://github.com/woodpecker-ci/autoscaler/pull/328)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.1.2 [[#327](https://github.com/woodpecker-ci/autoscaler/pull/327)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v3 to v3.5.2 [[#325](https://github.com/woodpecker-ci/autoscaler/pull/325)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.1.1 [[#324](https://github.com/woodpecker-ci/autoscaler/pull/324)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2.1.0 [[#323](https://github.com/woodpecker-ci/autoscaler/pull/323)] +- fix(deps): update golang.org/x/exp digest to 7e4ce0a [[#322](https://github.com/woodpecker-ci/autoscaler/pull/322)] +- fix(deps): update golang deps non-major [[#321](https://github.com/woodpecker-ci/autoscaler/pull/321)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.19.1 [[#320](https://github.com/woodpecker-ci/autoscaler/pull/320)] +- fix(deps): update golang deps non-major [[#319](https://github.com/woodpecker-ci/autoscaler/pull/319)] +- fix(deps): update module golang.org/x/oauth2 to v0.29.0 [[#318](https://github.com/woodpecker-ci/autoscaler/pull/318)] +- fix(deps): update golang deps non-major [[#316](https://github.com/woodpecker-ci/autoscaler/pull/316)] +- fix(deps): update golang deps non-major [[#315](https://github.com/woodpecker-ci/autoscaler/pull/315)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v3 to v3.5.0 [[#314](https://github.com/woodpecker-ci/autoscaler/pull/314)] +- chore(deps): update dependency go to v1.24.2 [[#313](https://github.com/woodpecker-ci/autoscaler/pull/313)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.211.0 [[#312](https://github.com/woodpecker-ci/autoscaler/pull/312)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v2 [[#309](https://github.com/woodpecker-ci/autoscaler/pull/309)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.1.4 [[#307](https://github.com/woodpecker-ci/autoscaler/pull/307)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.18.0 [[#308](https://github.com/woodpecker-ci/autoscaler/pull/308)] +- fix(deps): update golang deps non-major [[#306](https://github.com/woodpecker-ci/autoscaler/pull/306)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/config to v1.29.11 [[#305](https://github.com/woodpecker-ci/autoscaler/pull/305)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v5.2.2 [[#302](https://github.com/woodpecker-ci/autoscaler/pull/302)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/config to v1.29.10 [[#304](https://github.com/woodpecker-ci/autoscaler/pull/304)] +- chore(deps): update pre-commit hook adrienverge/yamllint to v1.37.0 [[#303](https://github.com/woodpecker-ci/autoscaler/pull/303)] +- chore(deps): update pre-commit non-major [[#300](https://github.com/woodpecker-ci/autoscaler/pull/300)] +- fix(deps): update module github.com/rs/zerolog to v1.34.0 [[#301](https://github.com/woodpecker-ci/autoscaler/pull/301)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.20.1 [[#299](https://github.com/woodpecker-ci/autoscaler/pull/299)] +- fix(deps): update golang deps non-major [[#298](https://github.com/woodpecker-ci/autoscaler/pull/298)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v3 to v3.4.0 [[#297](https://github.com/woodpecker-ci/autoscaler/pull/297)] +- chore(deps): update mstruebing/editorconfig-checker docker tag to v3.2.1 [[#296](https://github.com/woodpecker-ci/autoscaler/pull/296)] +- chore(deps): update pre-commit hook adrienverge/yamllint to v1.36.1 [[#295](https://github.com/woodpecker-ci/autoscaler/pull/295)] +- chore(deps): update pre-commit non-major [[#294](https://github.com/woodpecker-ci/autoscaler/pull/294)] +- fix(deps): update golang deps non-major [[#293](https://github.com/woodpecker-ci/autoscaler/pull/293)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.209.0 [[#292](https://github.com/woodpecker-ci/autoscaler/pull/292)] +- fix(deps): update module github.com/vultr/govultr/v3 to v3.16.1 [[#291](https://github.com/woodpecker-ci/autoscaler/pull/291)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.208.0 [[#290](https://github.com/woodpecker-ci/autoscaler/pull/290)] +- fix(deps): update golang.org/x/exp digest to 054e65f [[#289](https://github.com/woodpecker-ci/autoscaler/pull/289)] +- fix(deps): update golang deps non-major [[#288](https://github.com/woodpecker-ci/autoscaler/pull/288)] +- fix(deps): update golang deps non-major [[#287](https://github.com/woodpecker-ci/autoscaler/pull/287)] +- fix(deps): update golang deps non-major [[#286](https://github.com/woodpecker-ci/autoscaler/pull/286)] +- fix(deps): update golang.org/x/exp digest to dead583 [[#284](https://github.com/woodpecker-ci/autoscaler/pull/284)] +- fix(deps): update golang deps non-major [[#282](https://github.com/woodpecker-ci/autoscaler/pull/282)] + +### Misc + +- Bump golangci-lint to v2 [[#311](https://github.com/woodpecker-ci/autoscaler/pull/311)] +- Run tests also on makefile changes [[#310](https://github.com/woodpecker-ci/autoscaler/pull/310)] +- [pre-commit.ci] pre-commit autoupdate [[#285](https://github.com/woodpecker-ci/autoscaler/pull/285)] + +## [0.6.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.6.0) - 2025-02-27 + +### ❤️ Thanks to all contributors! ❤️ + +@xoxys + +### 📈 Enhancement + +- Add hcloud server type fallback list [[#275](https://github.com/woodpecker-ci/autoscaler/pull/275)] + +### 📦️ Dependency + +- fix(deps): update golang deps non-major [[#281](https://github.com/woodpecker-ci/autoscaler/pull/281)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.204.0 [[#280](https://github.com/woodpecker-ci/autoscaler/pull/280)] +- fix(deps): update module golang.org/x/oauth2 to v0.27.0 [[#279](https://github.com/woodpecker-ci/autoscaler/pull/279)] +- fix(deps): update golang.org/x/exp digest to aa4b98e [[#276](https://github.com/woodpecker-ci/autoscaler/pull/276)] + +## [0.5.1](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.5.1) - 2025-02-21 + +### ❤️ Thanks to all contributors! ❤️ + +@henkka + +### 📚 Documentation + +- docs: fix typos [[#259](https://github.com/woodpecker-ci/autoscaler/pull/259)] + +### 📦️ Dependency + +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v2 to v3 [[#266](https://github.com/woodpecker-ci/autoscaler/pull/266)] +- fix(deps): update golang deps non-major [[#265](https://github.com/woodpecker-ci/autoscaler/pull/265)] +- chore(deps): update golang docker tag to v1.24 [[#273](https://github.com/woodpecker-ci/autoscaler/pull/273)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.64.5 [[#271](https://github.com/woodpecker-ci/autoscaler/pull/271)] +- fix(deps): update golang.org/x/exp digest to eff6e97 [[#274](https://github.com/woodpecker-ci/autoscaler/pull/274)] +- fix(deps): update golang.org/x/exp digest to 939b2ce [[#270](https://github.com/woodpecker-ci/autoscaler/pull/270)] +- chore(deps): update docker.io/golang docker tag to v1.24 [[#272](https://github.com/woodpecker-ci/autoscaler/pull/272)] +- chore(deps): update pre-commit non-major [[#264](https://github.com/woodpecker-ci/autoscaler/pull/264)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v5.2.1 [[#263](https://github.com/woodpecker-ci/autoscaler/pull/263)] +- chore(deps): update mstruebing/editorconfig-checker docker tag to v3.2.0 [[#269](https://github.com/woodpecker-ci/autoscaler/pull/269)] +- fix(deps): update golang.org/x/exp digest to f9890c6 [[#267](https://github.com/woodpecker-ci/autoscaler/pull/267)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.1.3 [[#262](https://github.com/woodpecker-ci/autoscaler/pull/262)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.201.0 [[#261](https://github.com/woodpecker-ci/autoscaler/pull/261)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.18.0 [[#260](https://github.com/woodpecker-ci/autoscaler/pull/260)] +- fix(deps): update golang deps non-major [[#257](https://github.com/woodpecker-ci/autoscaler/pull/257)] + +### Misc + +- [pre-commit.ci] pre-commit autoupdate [[#268](https://github.com/woodpecker-ci/autoscaler/pull/268)] + +## [0.5.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.5.0) - 2025-01-17 + +### ❤️ Thanks to all contributors! ❤️ + +@xoxys + +### ✨ Features + +- Add Scaleway provider [[#252](https://github.com/woodpecker-ci/autoscaler/pull/252)] +- Add Vultr provider [[#251](https://github.com/woodpecker-ci/autoscaler/pull/251)] + +### 📦️ Dependency + +- chore(deps): update dependency go to v1.23.5 [[#255](https://github.com/woodpecker-ci/autoscaler/pull/255)] + +## [0.4.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.4.0) - 2025-01-16 + +### ❤️ Thanks to all contributors! ❤️ + +@keslerm, @pat-s, @xoxys + +### 📈 Enhancement + +- Wait for AWS instance availablity before returning [[#227](https://github.com/woodpecker-ci/autoscaler/pull/227)] + +### 📦️ Dependency + +- fix(deps): update golang deps non-major [[#254](https://github.com/woodpecker-ci/autoscaler/pull/254)] +- fix(deps): update golang deps non-major [[#253](https://github.com/woodpecker-ci/autoscaler/pull/253)] +- fix(deps): update golang deps non-major [[#248](https://github.com/woodpecker-ci/autoscaler/pull/248)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v2 to v2.8.3 [[#247](https://github.com/woodpecker-ci/autoscaler/pull/247)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/config to v1.28.10 [[#245](https://github.com/woodpecker-ci/autoscaler/pull/245)] +- fix(deps): update golang.org/x/exp digest to 7588d65 [[#244](https://github.com/woodpecker-ci/autoscaler/pull/244)] +- chore(deps): update mstruebing/editorconfig-checker docker tag to v3.1.2 [[#246](https://github.com/woodpecker-ci/autoscaler/pull/246)] +- fix(deps): update golang deps non-major [[#243](https://github.com/woodpecker-ci/autoscaler/pull/243)] +- fix(deps): update golang deps non-major [[#242](https://github.com/woodpecker-ci/autoscaler/pull/242)] +- fix(deps): update golang deps non-major [[#241](https://github.com/woodpecker-ci/autoscaler/pull/241)] +- fix(deps): update module golang.org/x/oauth2 to v0.25.0 [[#239](https://github.com/woodpecker-ci/autoscaler/pull/239)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.63.4 [[#238](https://github.com/woodpecker-ci/autoscaler/pull/238)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v2 to v2.8.2 [[#236](https://github.com/woodpecker-ci/autoscaler/pull/236)] +- fix(deps): update golang.org/x/exp digest to 7d7fa50 [[#235](https://github.com/woodpecker-ci/autoscaler/pull/235)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.1.1 [[#237](https://github.com/woodpecker-ci/autoscaler/pull/237)] +- fix(deps): update golang deps non-major [[#234](https://github.com/woodpecker-ci/autoscaler/pull/234)] +- fix(deps): update golang deps non-major [[#233](https://github.com/woodpecker-ci/autoscaler/pull/233)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.17.1 [[#232](https://github.com/woodpecker-ci/autoscaler/pull/232)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.198.0 [[#231](https://github.com/woodpecker-ci/autoscaler/pull/231)] +- fix(deps): update golang deps non-major [[#229](https://github.com/woodpecker-ci/autoscaler/pull/229)] +- fix(deps): update golang.org/x/exp digest to 4a55095 [[#228](https://github.com/woodpecker-ci/autoscaler/pull/228)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.196.0 [[#221](https://github.com/woodpecker-ci/autoscaler/pull/221)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3.1.0 [[#220](https://github.com/woodpecker-ci/autoscaler/pull/220)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v5.1.0 [[#219](https://github.com/woodpecker-ci/autoscaler/pull/219)] +- fix(deps): update golang.org/x/exp digest to 43b7b7c [[#218](https://github.com/woodpecker-ci/autoscaler/pull/218)] +- fix(deps): update module golang.org/x/net to v0.32.0 [[#217](https://github.com/woodpecker-ci/autoscaler/pull/217)] +- chore(deps): update dependency go to v1.23.4 [[#216](https://github.com/woodpecker-ci/autoscaler/pull/216)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v3 [[#215](https://github.com/woodpecker-ci/autoscaler/pull/215)] +- fix(deps): update golang deps non-major [[#213](https://github.com/woodpecker-ci/autoscaler/pull/213)] + +### Misc + +- Fix deprecated editorconfig filename [[#250](https://github.com/woodpecker-ci/autoscaler/pull/250)] +- Include tags from AWS config in instance creation [[#223](https://github.com/woodpecker-ci/autoscaler/pull/223)] +- Make sure to use the AWS Region when specified [[#224](https://github.com/woodpecker-ci/autoscaler/pull/224)] +- Rename linter [[#240](https://github.com/woodpecker-ci/autoscaler/pull/240)] + +## [0.3.1](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.3.1) - 2024-11-30 + +### ❤️ Thanks to all contributors! ❤️ + +@henkka, @pat-s, @xoxys + +### 📦️ Dependency + +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.62.2 [[#211](https://github.com/woodpecker-ci/autoscaler/pull/211)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v2 to v2.8.0 [[#210](https://github.com/woodpecker-ci/autoscaler/pull/210)] +- fix(deps): update module github.com/aws/aws-sdk-go-v2/service/ec2 to v1.194.0 [[#208](https://github.com/woodpecker-ci/autoscaler/pull/208)] +- fix(deps): update module github.com/stretchr/testify to v1.10.0 [[#207](https://github.com/woodpecker-ci/autoscaler/pull/207)] +- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.43.0 [[#206](https://github.com/woodpecker-ci/autoscaler/pull/206)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.17.0 [[#205](https://github.com/woodpecker-ci/autoscaler/pull/205)] +- fix(deps): update golang deps non-major [[#203](https://github.com/woodpecker-ci/autoscaler/pull/203)] +- fix(deps): update golang deps non-major [[#201](https://github.com/woodpecker-ci/autoscaler/pull/201)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.62.0 [[#200](https://github.com/woodpecker-ci/autoscaler/pull/200)] +- fix(deps): update golang deps non-major [[#197](https://github.com/woodpecker-ci/autoscaler/pull/197)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v2.1.1 [[#199](https://github.com/woodpecker-ci/autoscaler/pull/199)] +- fix(deps): update golang.org/x/exp digest to 2d47ceb [[#198](https://github.com/woodpecker-ci/autoscaler/pull/198)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v2 to v2.7.2 [[#196](https://github.com/woodpecker-ci/autoscaler/pull/196)] +- chore(deps): update node.js to v22 [[#193](https://github.com/woodpecker-ci/autoscaler/pull/193)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.15.0 [[#194](https://github.com/woodpecker-ci/autoscaler/pull/194)] +- fix(deps): update golang.org/x/exp digest to f66d83c [[#188](https://github.com/woodpecker-ci/autoscaler/pull/188)] +- chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v5 [[#187](https://github.com/woodpecker-ci/autoscaler/pull/187)] +- fix(deps): update golang deps non-major [[#186](https://github.com/woodpecker-ci/autoscaler/pull/186)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v2 [[#185](https://github.com/woodpecker-ci/autoscaler/pull/185)] +- chore(deps): update golang docker tag to v1.23 [[#181](https://github.com/woodpecker-ci/autoscaler/pull/181)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v1.2.0 [[#182](https://github.com/woodpecker-ci/autoscaler/pull/182)] +- fix(deps): update golang deps non-major [[#183](https://github.com/woodpecker-ci/autoscaler/pull/183)] +- chore(deps): update pre-commit non-major [[#184](https://github.com/woodpecker-ci/autoscaler/pull/184)] + +### Misc + +- docs: add AWS as supported cloud provider [[#209](https://github.com/woodpecker-ci/autoscaler/pull/209)] +- ci: remove `renovate` branch triggers [[#204](https://github.com/woodpecker-ci/autoscaler/pull/204)] +- [pre-commit.ci] pre-commit autoupdate [[#195](https://github.com/woodpecker-ci/autoscaler/pull/195)] +- Bump buildx plugin image for pipeline [[#192](https://github.com/woodpecker-ci/autoscaler/pull/192)] +- [pre-commit.ci] pre-commit autoupdate [[#176](https://github.com/woodpecker-ci/autoscaler/pull/176)] + +## [0.3.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.3.0) - 2024-09-20 + +### ❤️ Thanks to all contributors! ❤️ + +@anbraten, @hhamalai, @qwerty287, @xoxys + +### ✨ Features + +- Add AWS provider [[#118](https://github.com/woodpecker-ci/autoscaler/pull/118)] +- Reactivate agents and prevent draining recently active agents [[#163](https://github.com/woodpecker-ci/autoscaler/pull/163)] +- Add agent idle timeout [[#162](https://github.com/woodpecker-ci/autoscaler/pull/162)] +- Allow to filter for specific tasks [[#134](https://github.com/woodpecker-ci/autoscaler/pull/134)] + +### 🐛 Bug Fixes + +- Fix drain agents [[#156](https://github.com/woodpecker-ci/autoscaler/pull/156)] +- Return error on unknown server types [[#151](https://github.com/woodpecker-ci/autoscaler/pull/151)] + +### 📈 Enhancement + +- Allow to remove an agent as soon as it connected once, but has no more tasks left [[#92](https://github.com/woodpecker-ci/autoscaler/pull/92)] +- Improve error handling [[#155](https://github.com/woodpecker-ci/autoscaler/pull/155)] +- Use docker gpg key from download.docker.com [[#154](https://github.com/woodpecker-ci/autoscaler/pull/154)] + +### Misc + +- fix(deps): update golang.org/x/exp digest to 701f63a [[#178](https://github.com/woodpecker-ci/autoscaler/pull/178)] +- chore(deps): update mstruebing/editorconfig-checker docker tag to v3.0.3 [[#179](https://github.com/woodpecker-ci/autoscaler/pull/179)] +- fix(deps): update golang deps non-major [[#173](https://github.com/woodpecker-ci/autoscaler/pull/173)] +- fix(deps): update golang.org/x/exp digest to 778ce7b [[#174](https://github.com/woodpecker-ci/autoscaler/pull/174)] +- chore(deps): update golang deps non-major [[#170](https://github.com/woodpecker-ci/autoscaler/pull/170)] +- fix(deps): update go.woodpecker-ci.org/woodpecker/v2 digest to 987c201 [[#169](https://github.com/woodpecker-ci/autoscaler/pull/169)] +- [pre-commit.ci] pre-commit autoupdate [[#160](https://github.com/woodpecker-ci/autoscaler/pull/160)] +- fix(deps): update module github.com/linode/linodego to v1.36.1 [[#161](https://github.com/woodpecker-ci/autoscaler/pull/161)] +- fix(deps): update golang deps non-major [[#159](https://github.com/woodpecker-ci/autoscaler/pull/159)] +- fix(deps): update golang.org/x/exp digest to 7f521ea [[#158](https://github.com/woodpecker-ci/autoscaler/pull/158)] +- fix(deps): update golang deps non-major [[#150](https://github.com/woodpecker-ci/autoscaler/pull/150)] +- [pre-commit.ci] pre-commit autoupdate [[#149](https://github.com/woodpecker-ci/autoscaler/pull/149)] +- Fix deprecations and run on renovate branches [[#147](https://github.com/woodpecker-ci/autoscaler/pull/147)] +- chore(deps): update mstruebing/editorconfig-checker docker tag to v3 [[#138](https://github.com/woodpecker-ci/autoscaler/pull/138)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v4 [[#143](https://github.com/woodpecker-ci/autoscaler/pull/143)] +- fix(deps): update golang deps non-major [[#142](https://github.com/woodpecker-ci/autoscaler/pull/142)] +- chore(deps): update golang docker tag to v1.22.3 [[#144](https://github.com/woodpecker-ci/autoscaler/pull/144)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.58.1 [[#145](https://github.com/woodpecker-ci/autoscaler/pull/145)] +- chore(deps): update pre-commit non-major [[#139](https://github.com/woodpecker-ci/autoscaler/pull/139)] +- fix(deps): update module golang.org/x/oauth2 to v0.20.0 [[#140](https://github.com/woodpecker-ci/autoscaler/pull/140)] +- fix(deps): update module github.com/urfave/cli/v2 to v2.27.2 [[#136](https://github.com/woodpecker-ci/autoscaler/pull/136)] +- fix(deps): update module github.com/linode/linodego to v1.33.0 [[#135](https://github.com/woodpecker-ci/autoscaler/pull/135)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.7.2 [[#132](https://github.com/woodpecker-ci/autoscaler/pull/132)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v1.1.1 [[#131](https://github.com/woodpecker-ci/autoscaler/pull/131)] +- fix(deps): update golang.org/x/exp digest to fe59bbe [[#130](https://github.com/woodpecker-ci/autoscaler/pull/130)] +- chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v4.6.0 [[#128](https://github.com/woodpecker-ci/autoscaler/pull/128)] +- chore(deps): update golang docker tag to v1.22.2 [[#127](https://github.com/woodpecker-ci/autoscaler/pull/127)] +- fix(deps): update golang.org/x/exp digest to c0f41cb [[#126](https://github.com/woodpecker-ci/autoscaler/pull/126)] +- fix(deps): update golang deps non-major [[#125](https://github.com/woodpecker-ci/autoscaler/pull/125)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v3.2.1 [[#124](https://github.com/woodpecker-ci/autoscaler/pull/124)] +- fix(deps): update golang.org/x/exp digest to a685a6e [[#122](https://github.com/woodpecker-ci/autoscaler/pull/122)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.57.2 [[#123](https://github.com/woodpecker-ci/autoscaler/pull/123)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.7.0 [[#121](https://github.com/woodpecker-ci/autoscaler/pull/121)] +- chore(deps): update pre-commit non-major [[#120](https://github.com/woodpecker-ci/autoscaler/pull/120)] +- fix(deps): update golang.org/x/exp digest to a85f2c6 [[#119](https://github.com/woodpecker-ci/autoscaler/pull/119)] +- fix(deps): update golang deps non-major [[#116](https://github.com/woodpecker-ci/autoscaler/pull/116)] + +## [0.2.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.2.0) - 2024-03-17 + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @guisea, @maltejur, @pat-s, @qwerty287, @xoxys + +### ✨ Features + +- Add linode provider [[#15](https://github.com/woodpecker-ci/autoscaler/pull/15)] + +### 📚 Documentation + +- Document `WOODPECKER_PROVIDER` [[#19](https://github.com/woodpecker-ci/autoscaler/pull/19)] + +### 📈 Enhancement + +- Ignore WaitingOnDeps for agent calculation [[#14](https://github.com/woodpecker-ci/autoscaler/pull/14)] +- don't require amd64 CPU architecture [[#10](https://github.com/woodpecker-ci/autoscaler/pull/10)] + +### 🐛 Bug Fixes + +- Update hetznercloud provider [[#12](https://github.com/woodpecker-ci/autoscaler/pull/12)] + +### Misc + +- Temp disable linode provider [[#115](https://github.com/woodpecker-ci/autoscaler/pull/115)] +- Enable more linters and cleanup code [[#114](https://github.com/woodpecker-ci/autoscaler/pull/114)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v3.2.0 [[#113](https://github.com/woodpecker-ci/autoscaler/pull/113)] +- fix(deps): update golang.org/x/exp digest to c7f7c64 [[#112](https://github.com/woodpecker-ci/autoscaler/pull/112)] +- fix(deps): update module github.com/linode/linodego to v1.30.0 [[#111](https://github.com/woodpecker-ci/autoscaler/pull/111)] +- chore(deps): update golang docker tag to v1.22.1 [[#110](https://github.com/woodpecker-ci/autoscaler/pull/110)] +- fix(deps): update golang deps non-major [[#109](https://github.com/woodpecker-ci/autoscaler/pull/109)] +- fix(deps): update golang.org/x/exp digest to 814bf88 [[#108](https://github.com/woodpecker-ci/autoscaler/pull/108)] +- fix(deps): update module github.com/linode/linodego to v1.29.0 [[#107](https://github.com/woodpecker-ci/autoscaler/pull/107)] +- fix(deps): update golang.org/x/exp digest to ec58324 [[#106](https://github.com/woodpecker-ci/autoscaler/pull/106)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v3.1.0 [[#105](https://github.com/woodpecker-ci/autoscaler/pull/105)] +- chore(deps): update golang docker tag [[#101](https://github.com/woodpecker-ci/autoscaler/pull/101)] +- fix(deps): update golang.org/x/exp digest to 2c58cdc [[#100](https://github.com/woodpecker-ci/autoscaler/pull/100)] +- fix(deps): update golang deps non-major [[#99](https://github.com/woodpecker-ci/autoscaler/pull/99)] +- fix(deps): update module github.com/rs/zerolog to v1.32.0 [[#98](https://github.com/woodpecker-ci/autoscaler/pull/98)] +- [pre-commit.ci] pre-commit autoupdate [[#97](https://github.com/woodpecker-ci/autoscaler/pull/97)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v3.0.1 [[#96](https://github.com/woodpecker-ci/autoscaler/pull/96)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v2 to v2.3.0 [[#95](https://github.com/woodpecker-ci/autoscaler/pull/95)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v1.1.0 [[#89](https://github.com/woodpecker-ci/autoscaler/pull/89)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker/v2 to v2.2.2 [[#87](https://github.com/woodpecker-ci/autoscaler/pull/87)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v3 [[#85](https://github.com/woodpecker-ci/autoscaler/pull/85)] +- fix(deps): update golang.org/x/exp digest to 1b97071 [[#86](https://github.com/woodpecker-ci/autoscaler/pull/86)] +- Use cleartext username [[#84](https://github.com/woodpecker-ci/autoscaler/pull/84)] +- chore(deps): update golang docker tag to v1.21.6 [[#83](https://github.com/woodpecker-ci/autoscaler/pull/83)] +- fix(deps): update golang.org/x/exp digest to db7319d [[#82](https://github.com/woodpecker-ci/autoscaler/pull/82)] +- fix(deps): update golang deps non-major [[#81](https://github.com/woodpecker-ci/autoscaler/pull/81)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v2.3.0 [[#80](https://github.com/woodpecker-ci/autoscaler/pull/80)] +- fix(deps): update golang.org/x/exp digest to be819d1 [[#79](https://github.com/woodpecker-ci/autoscaler/pull/79)] +- fix(deps): update module github.com/urfave/cli/v2 to v2.27.1 [[#78](https://github.com/woodpecker-ci/autoscaler/pull/78)] +- [pre-commit.ci] pre-commit autoupdate [[#77](https://github.com/woodpecker-ci/autoscaler/pull/77)] +- fix(deps): update golang.org/x/exp digest to 02704c9 [[#76](https://github.com/woodpecker-ci/autoscaler/pull/76)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker to v2 [[#75](https://github.com/woodpecker-ci/autoscaler/pull/75)] +- fix(deps): update module github.com/urfave/cli/v2 to v2.27.0 [[#74](https://github.com/woodpecker-ci/autoscaler/pull/74)] +- fix(deps): update golang.org/x/exp digest to dc181d7 [[#71](https://github.com/woodpecker-ci/autoscaler/pull/71)] +- fix(deps): update golang.org/x/exp digest to aacd6d4 [[#69](https://github.com/woodpecker-ci/autoscaler/pull/69)] +- fix(deps): update module github.com/hetznercloud/hcloud-go/v2 to v2.5.1 [[#68](https://github.com/woodpecker-ci/autoscaler/pull/68)] +- chore(deps): update golang docker tag to v1.21.5 [[#67](https://github.com/woodpecker-ci/autoscaler/pull/67)] +- fix(deps): update golang.org/x/exp digest to f3f8817 [[#66](https://github.com/woodpecker-ci/autoscaler/pull/66)] +- fix(deps): update module github.com/urfave/cli/v2 to v2.26.0 [[#65](https://github.com/woodpecker-ci/autoscaler/pull/65)] +- fix(deps): update golang.org/x/exp digest to 6522937 [[#64](https://github.com/woodpecker-ci/autoscaler/pull/64)] +- fix(deps): update golang deps non-major [[#63](https://github.com/woodpecker-ci/autoscaler/pull/63)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v1.0.3 [[#62](https://github.com/woodpecker-ci/autoscaler/pull/62)] +- chore(deps): update woodpeckerci/plugin-docker-buildx docker tag to v2.2.1 [[#61](https://github.com/woodpecker-ci/autoscaler/pull/61)] +- fix(deps): update golang deps non-major [[#60](https://github.com/woodpecker-ci/autoscaler/pull/60)] +- chore(deps): update golang docker tag to v1.21.4 [[#59](https://github.com/woodpecker-ci/autoscaler/pull/59)] +- fix(deps): update golang.org/x/exp digest to 9a3e603 [[#58](https://github.com/woodpecker-ci/autoscaler/pull/58)] +- Add linters and `pre-commit` [[#57](https://github.com/woodpecker-ci/autoscaler/pull/57)] +- fix(deps): update module go.woodpecker-ci.org/woodpecker to v1 [[#54](https://github.com/woodpecker-ci/autoscaler/pull/54)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v1 [[#53](https://github.com/woodpecker-ci/autoscaler/pull/53)] +- Go vanity urls for autoscaler [[#48](https://github.com/woodpecker-ci/autoscaler/pull/48)] +- fix(deps): update module github.com/woodpecker-ci/woodpecker to v1.0.4 [[#47](https://github.com/woodpecker-ci/autoscaler/pull/47)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v0.7.0 [[#45](https://github.com/woodpecker-ci/autoscaler/pull/45)] +- renovate: use org config [[#43](https://github.com/woodpecker-ci/autoscaler/pull/43)] +- Fix indentation in cloudconfig multiline string [[#42](https://github.com/woodpecker-ci/autoscaler/pull/42)] +- Update woodpeckerci/plugin-docker-buildx Docker tag to v2.2.0 [[#41](https://github.com/woodpecker-ci/autoscaler/pull/41)] +- Update Node.js to v21 [[#40](https://github.com/woodpecker-ci/autoscaler/pull/40)] +- Update module github.com/woodpecker-ci/woodpecker to v1.0.3 [[#39](https://github.com/woodpecker-ci/autoscaler/pull/39)] +- Update module github.com/hetznercloud/hcloud-go/v2 to v2.4.0 [[#38](https://github.com/woodpecker-ci/autoscaler/pull/38)] +- Update golang Docker tag to v1.21.3 [[#37](https://github.com/woodpecker-ci/autoscaler/pull/37)] +- Update module golang.org/x/net to v0.17.0 [[#36](https://github.com/woodpecker-ci/autoscaler/pull/36)] +- Update mstruebing/editorconfig-checker Docker tag to v2.7.2 [[#35](https://github.com/woodpecker-ci/autoscaler/pull/35)] +- Update module github.com/woodpecker-ci/woodpecker to v1 [[#34](https://github.com/woodpecker-ci/autoscaler/pull/34)] +- Update Node.js to v20 [[#33](https://github.com/woodpecker-ci/autoscaler/pull/33)] +- Update module golang.org/x/oauth2 to v0.13.0 [[#32](https://github.com/woodpecker-ci/autoscaler/pull/32)] +- Update module github.com/hetznercloud/hcloud-go/v2 to v2.3.0 [[#27](https://github.com/woodpecker-ci/autoscaler/pull/27)] +- Update golang Docker tag to v1.21.2 [[#30](https://github.com/woodpecker-ci/autoscaler/pull/30)] +- Update golang.org/x/exp digest to 7918f67 [[#29](https://github.com/woodpecker-ci/autoscaler/pull/29)] +- Update module golang.org/x/net to v0.16.0 [[#31](https://github.com/woodpecker-ci/autoscaler/pull/31)] +- Update module github.com/rs/zerolog to v1.31.0 [[#28](https://github.com/woodpecker-ci/autoscaler/pull/28)] +- Update golang Docker tag [[#26](https://github.com/woodpecker-ci/autoscaler/pull/26)] +- Update module github.com/urfave/cli/v2 to v2.25.7 [[#22](https://github.com/woodpecker-ci/autoscaler/pull/22)] +- Update golang.org/x/exp digest to 9212866 [[#21](https://github.com/woodpecker-ci/autoscaler/pull/21)] +- Add renovate [[#20](https://github.com/woodpecker-ci/autoscaler/pull/20)] + +## [0.1.0](https://github.com/woodpecker-ci/autoscaler/releases/tag/0.1.0) - 2023-07-28 + +### ❤️ Thanks to all contributors! ❤️ + +@anbraten, @xoxys + +### ✨ Features + +- Refactor project structure and improve agent calculation [[#3](https://github.com/woodpecker-ci/autoscaler/pull/3)] + +### 📈 Enhancement + +- Renamings and failing on parsing errors [[#9](https://github.com/woodpecker-ci/autoscaler/pull/9)] +- Add release helper [[#7](https://github.com/woodpecker-ci/autoscaler/pull/7)] +- Add hetznercloud network, firewall and ssh-key options [[#4](https://github.com/woodpecker-ci/autoscaler/pull/4)] + +### Misc + +- Allow to set agent min-time-alive as user [[#5](https://github.com/woodpecker-ci/autoscaler/pull/5)] +- Add ci test workflow [[#6](https://github.com/woodpecker-ci/autoscaler/pull/6)] +- Add container image [[#1](https://github.com/woodpecker-ci/autoscaler/pull/1)] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1423bda --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM golang:1.23-alpine AS builder + +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN GOMAXPROCS=1 go build -p 1 -o woodpecker-autoscaler ./cmd/woodpecker-autoscaler + +FROM alpine:latest + +RUN apk add --no-cache ca-certificates + +WORKDIR /app +COPY --from=builder /build/woodpecker-autoscaler /usr/local/bin/ + +ENTRYPOINT ["woodpecker-autoscaler"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..34b46d2 --- /dev/null +++ b/Makefile @@ -0,0 +1,133 @@ +# renovate: datasource=github-releases depName=mvdan/gofumpt +GOFUMPT_VERSION := v0.10.0 +# renovate: datasource=github-releases depName=golangci/golangci-lint +GOLANGCI_LINT_VERSION := v2.12.2 + +GO_PACKAGES ?= $(shell go list ./... | grep -v /vendor/) + +TARGETOS ?= linux +TARGETARCH ?= amd64 + +VERSION ?= next +CI_COMMIT_SHA ?= $(shell git rev-parse HEAD) + +# it's a tagged release +ifneq ($(CI_COMMIT_TAG),) + VERSION := $(CI_COMMIT_TAG:v%=%) +else + # append commit-sha to next version + ifeq ($(VERSION),next) + VERSION := $(shell echo "next-$(shell echo ${CI_COMMIT_SHA} | cut -c -10)") + endif + # append commit-sha to release branch version + ifeq ($(shell echo ${CI_COMMIT_BRANCH} | cut -c -9),release/v) + VERSION := $(shell echo "$(shell echo ${CI_COMMIT_BRANCH} | cut -c 10-)-$(shell echo ${CI_COMMIT_SHA} | cut -c -10)") + endif +endif + +LDFLAGS := -s -w -extldflags "-static" -X go.woodpecker-ci.org/autoscaler/version.Version=${VERSION} +CGO_ENABLED := 0 + +HAS_GO = $(shell hash go > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) +ifeq ($(HAS_GO),GO) + CGO_CFLAGS ?= $(shell go env CGO_CFLAGS) +endif +CGO_CFLAGS ?= + +# If the first argument is "in_docker"... +ifeq (in_docker,$(firstword $(MAKECMDGOALS))) + # use the rest as arguments for "in_docker" + MAKE_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + # Ignore the next args + $(eval $(MAKE_ARGS):;@:) + + in_docker: + @[ "1" -eq "$(shell docker image ls woodpecker/make:local -a | wc -l)" ] && docker buildx build -f ./docker/Dockerfile.make -t woodpecker/make:local --load . || echo reuse existing docker image + @echo run in docker: + @docker run -it \ + --user $(shell id -u):$(shell id -g) \ + -e VERSION="$(VERSION)" \ + -e CI_COMMIT_SHA="$(CI_COMMIT_SHA)" \ + -e TARGETOS="$(TARGETOS)" \ + -e TARGETARCH="$(TARGETARCH)" \ + -e CGO_ENABLED="$(CGO_ENABLED)" \ + -e GOPATH=/tmp/go \ + -e HOME=/tmp/home \ + -v $(PWD):/build --rm woodpecker/make:local make $(MAKE_ARGS) +else + +# Proceed with normal make + +##@ General + +.PHONY: all +all: help + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +.PHONY: version +version: ## Print the current version + @echo ${VERSION} + +format: install-tools ## Format source code + @gofumpt -extra -w . + +.PHONY: clean +clean: ## Clean build artifacts + go clean -i ./... + rm -rf build + @[ "1" != "$(shell docker image ls woodpecker/make:local -a | wc -l)" ] && docker image rm woodpecker/make:local || echo no docker image to clean + + +install-tools: ## Install development tools + @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION); \ + fi ; \ + hash lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go install github.com/rs/zerolog/cmd/lint@latest; \ + fi ; \ + hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION); \ + fi ; \ + hash mockery > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go install github.com/vektra/mockery/v3@latest; \ + fi ; \ + +##@ Test + +.PHONY: lint +lint: install-tools ## Lint code + @echo "Running golangci-lint" + golangci-lint run + @echo "Running zerolog linter" + lint go.woodpecker-ci.org/autoscaler/cmd/woodpecker-autoscaler + +test-autoscaler: ## Test autoscaler code + go test -race -cover -coverprofile autoscaler-coverage.out -timeout 30s ${GO_PACKAGES} + +.PHONY: test +test: test-autoscaler ## Run all tests + +.PHONY: generate +generate: + mockery + +##@ Build + +build: + CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -o dist/woodpecker-autoscaler go.woodpecker-ci.org/autoscaler/cmd/woodpecker-autoscaler + +endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..f241421 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# Timeweb Cloud Autoscaler Provider for Woodpecker CI + +Динамический autoscaler для Woodpecker CI с поддержкой хостера Timeweb Cloud. + +## Архитектура + +``` +┌──────────────────────┐ +│ Woodpecker Server │◄──── постоянно запущен на VDS +│ (gRPC + Web UI) │ +└──────────┬───────────┘ + │ + ▼ gRPC +┌──────────────────────┐ +│ Woodpecker Agent │◄──── постоянно запущен на VDS (можно оставить 1 статический) +│ (Docker Compose) │ +└──────────────────────┘ + +┌──────────────────────────────────────────┐ +│ Woodpecker Autoscaler │◄──── постоянно запущен +│ (форк woodpecker-ci/autoscaler + │ +│ провайдер timewebcloud) │ +└──────────┬───────────────────────────────┘ + │ + ▼ HTTP / JWT +┌──────────────────────────────────────────┐ +│ Timeweb Cloud API │ +│ api.timeweb.cloud │ +│ - CreateServer │ +│ - DeleteServer │ +│ - ListServers │ +└──────────────────────────────────────────┘ + +Flow: +1. Появляется задача в очереди Woodpecker +2. Autoscaler создает новый VDS через Timeweb API +3. VDS загружается, cloud-init устанавливает Docker и запускает Agent +4. Agent подключается к Server по gRPC и забирает задачу +5. По завершении задачи и истечении idle-timeout, autoscaler удаляет VDS +``` + +## Сборка + +Требования: Go 1.23+ + +```bash +go build -o woodpecker-autoscaler ./cmd/woodpecker-autoscaler +``` + +При сборке на машине с ограниченной памятью используйте: +```bash +GOMAXPROCS=1 go build -p 1 -o woodpecker-autoscaler ./cmd/woodpecker-autoscaler +``` + +## Запуск + +```bash +export WOODPECKER_SERVER=https://your-woodpecker-server +export WOODPECKER_TOKEN=your-token +export WOODPECKER_PROVIDER=timewebcloud +export WOODPECKER_TIMEWEBCLOUD_API_TOKEN=your-jwt-token +export WOODPECKER_TIMEWEBCLOUD_OS_ID=123 +export WOODPECKER_TIMEWEBCLOUD_PRESET_ID=456 +export WOODPECKER_TIMEWEBCLOUD_AVAILABILITY_ZONE=msk-1 + +./woodpecker-autoscaler +``` + +## Структура проекта + +``` +twcloud-scaler/ +├── agents.md # Контекст для AI-агентов +├── README.md # Этот файл +├── go.mod # Модуль Go +├── woodpecker-autoscaler # Собранный бинарник +├── cmd/ +│ └── woodpecker-autoscaler/ +│ ├── main.go # Точка входа +│ └── flags.go # Глобальные CLI-флаги +├── providers/ +│ └── timewebcloud/ +│ ├── provider.go # Реализация Provider interface +│ ├── flags.go # CLI-флаги провайдера +│ └── api/ +│ └── client.go # Минимальный HTTP-клиент для Timeweb API +├── engine/ # Ядро autoscaler (из upstream) +├── config/ # Конфигурация (из upstream) +├── server/ # Woodpecker API client (из upstream) +└── utils/ # Утилиты (из upstream) +``` + +## Что реализовано + +- [x] Провайдер `timewebcloud` для Woodpecker Autoscaler +- [x] Создание VDS (`DeployAgent`) с cloud-init +- [x] Удаление VDS (`RemoveAgent`) по имени агента +- [x] Список развернутых агентов (`ListDeployedAgentNames`) с фильтрацией по префиксу +- [x] Минимальный HTTP-клиент для Timeweb Cloud API (вместо сломанного SDK) +- [x] Интеграция в main.go +- [x] Успешная сборка бинарника + +## Ссылки +- [Woodpecker Autoscaler](https://github.com/woodpecker-ci/autoscaler) +- [Timeweb Cloud API Docs](https://timeweb.cloud/api-docs) diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..a1b7f62 --- /dev/null +++ b/agents.md @@ -0,0 +1,151 @@ +# Project: Woodpecker CI Autoscaler — Timeweb Cloud Provider + +## Goal +Add a Timeweb Cloud provider to the Woodpecker CI autoscaler so that: +1. The Woodpecker server runs permanently on one VDS. +2. When a CI job appears, the autoscaler dynamically creates a new VDS on Timeweb Cloud. +3. The VDS is bootstrapped via cloud-init, connects to the server as an agent, and runs the job. +4. After the job finishes and the idle timeout expires, the VDS is destroyed. + +## Background + +### Current Setup +- Woodpecker server and agent run permanently on a single VDS via Docker Compose. +- The goal is to move to a dynamic model where agents are created on demand. + +### Woodpecker CI Autoscaler Architecture +- **Repository**: `woodpecker-ci/autoscaler` (separate from the main `woodpecker-ci/woodpecker` repo). +- **Language**: Go. +- **Provider Interface** (3 methods): + ```go + type Provider interface { + DeployAgent(context.Context, *woodpecker.Agent) error + RemoveAgent(context.Context, *woodpecker.Agent) error + ListDeployedAgentNames(context.Context) ([]string, error) + } + ``` +- **Provisioning Flow**: + 1. Autoscaler monitors the Woodpecker queue. + 2. When pending tasks exceed capacity, it calls `AgentCreate()` to get a token, then `DeployAgent()`. + 3. `DeployAgent` creates a VM and passes cloud-init user-data. + 4. The VM boots, installs Docker, and runs the Woodpecker agent container via docker compose. + 5. The agent connects to the server via gRPC using the provided token. + 6. On scale-down, `RemoveAgent()` terminates the VM, and the agent is deleted from Woodpecker. +- **Cloud-init**: The autoscaler generates a cloud-init YAML that installs Docker and starts the agent. Custom templates are supported via `WOODPECKER_PROVIDER_USERDATA` / `WOODPECKER_PROVIDER_USERDATA_FILE`. +- **Agent Environment Variables** (set in cloud-init): + - `WOODPECKER_SERVER` — gRPC address of the server. + - `WOODPECKER_AGENT_SECRET` — token generated by `AgentCreate()`. + - `WOODPECKER_MAX_WORKFLOWS` — parallelism per agent. + - `WOODPECKER_GRPC_SECURE` — TLS flag. +- **Configuration**: The autoscaler uses `urfave/cli` for CLI flags. Providers define their own flags (e.g., `--hetznercloud-api-token`). +- **Registration**: To add a new provider, you must: + 1. Implement the `Provider` interface in a new package under `providers//`. + 2. Create a `flags.go` file with CLI flags. + 3. Import the package and add a case in `cmd/woodpecker-autoscaler/main.go`. + 4. Append the provider's flags to the global app flags. + +### Timeweb Cloud API +- **Public API**: Yes — `https://api.timeweb.cloud`. +- **Official Go SDK**: `github.com/timeweb-cloud/sdk-go` (OpenAPI-generated). +- **Authentication**: JWT Bearer token (`Authorization: Bearer `). +- **VDS Lifecycle Endpoints**: + - Create: `POST /api/v1/servers` + - Delete: `DELETE /api/v1/servers/{server_id}` + - Get: `GET /api/v1/servers/{server_id}` + - List: `GET /api/v1/servers` + - Start: `POST /api/v1/servers/{server_id}/start` + - Shutdown: `POST /api/v1/servers/{server_id}/shutdown` + - Clone: `POST /api/v1/servers/{server_id}/clone` +- **Create Server Parameters**: + - `name` (required) + - `os_id` or `image_id` + - `preset_id` or `configuration` (CPU, RAM, disk) + - `ssh_keys_ids` + - `cloud_init` — **this is critical** for passing user-data. + - `availability_zone` + - `hostname` +- **Rate Limit**: 20 requests per second per endpoint. +- **Tags/Labels**: The API does not seem to have a native "label" or "tag" system for servers. We may need to track pool association by server name prefix or by storing state locally. **This is an open question.** + +## Implementation Plan + +### Phase 1: Project Setup +1. Fork / vendor `woodpecker-ci/autoscaler` as the base. +2. Add `github.com/timeweb-cloud/sdk-go` as a dependency. +3. Create the provider package: `providers/timewebcloud/`. + +### Phase 2: Provider Implementation +1. **Struct & Constructor** (`provider.go`): + - Fields: API client, config, pool ID, default image/preset/zone. + - `New(ctx, cli.Command, *config.Config) (types.Provider, error)`. +2. **Flags** (`flags.go`): + - `--timewebcloud-api-token` (env: `WOODPECKER_TIMEWEBCLOUD_API_TOKEN`) + - `--timewebcloud-os-id` / `--timewebcloud-image-id` + - `--timewebcloud-preset-id` / `--timewebcloud-configuration` + - `--timewebcloud-availability-zone` + - `--timewebcloud-ssh-key-id` + - `--timewebcloud-hostname-prefix` +3. **DeployAgent**: + - Generate cloud-init user-data via `cloudinit.RenderUserDataTemplate()`. + - Call `CreateServer` with the agent name and user-data. + - Store the mapping `agent.Name -> server_id` (in memory or via naming convention). +4. **RemoveAgent**: + - Find server by agent name (list all servers and filter by name, or use a stored mapping). + - Call `DeleteServer`. + - Handle "not found" gracefully. +5. **ListDeployedAgentNames**: + - List all servers. + - Filter by name prefix (e.g., `pool--agent-`). + - Return matching names. + +### Phase 3: Integration +1. Import the provider in `main.go`. +2. Add `case "timewebcloud":` to `setupProvider()`. +3. Append `timewebcloud.ProviderFlags` to the global flags. + +### Phase 4: Testing & Deployment +1. Build the binary. +2. Test locally or on a staging VDS: + - Start the autoscaler with `--provider=timewebcloud`. + - Trigger a CI job. + - Verify VDS creation, agent connection, job execution, and cleanup. +3. Update Docker Compose / deployment docs. + +## Key Technical Decisions + +### 1. How to Track Agent-to-Server Mapping? +**Options**: +- **A. Name Prefix Convention**: Name servers as `wp--`. `ListDeployedAgentNames` filters by prefix. Simple, no state needed. +- **B. In-Memory Map**: Store `map[string]int` (agent name -> server ID) in the provider struct. Lost on restart. +- **C. Local State File**: Persist the map to disk. Survives restart. +- **D. API Metadata**: If Timeweb API supports tags/labels, use them. (Currently unclear.) + +**Recommendation**: Start with **A** (name prefix) as the simplest and most robust approach. If Timeweb adds tags later, migrate to **D**. + +### 2. How to Handle Server Readiness? +**Question**: After `CreateServer`, the server may take time to boot. Does `DeployAgent` need to wait? +**Answer**: No. The autoscaler engine only requires that the VM creation is initiated. The agent will connect when ready. The engine has `AgentInactivityTimeout` (default 10m) to clean up agents that never connect. + +### 3. OS Image Selection +**Question**: What base image should be used for the agent VMs? +**Answer**: Ubuntu 22.04 LTS or Debian 12 (stable, good Docker support). The `os_id` must be fetched from Timeweb's API (`GetOsList`). Alternatively, a custom image with Docker pre-installed could speed up boot time. + +### 4. SSH Keys +**Question**: Are SSH keys needed if we use cloud-init? +**Answer**: Cloud-init handles everything. SSH keys are optional but useful for debugging. The provider should allow configuring `ssh_keys_ids`. + +## Open Questions +1. Does Timeweb Cloud API support assigning custom tags/labels to servers? (Affects `ListDeployedAgentNames` implementation.) +2. What is the typical boot time for a new VDS? (Affects `AgentInactivityTimeout` tuning.) +3. Does the `cloud_init` field in `CreateServer` accept standard cloud-init YAML? (Needs testing.) +4. Is there a way to use a custom image (snapshot) to pre-install Docker and reduce boot time? +5. What are the `os_id` values for Ubuntu/Debian? (Need to call `GetOsList`.) +6. Does Timeweb charge for stopped (but not deleted) servers? (Affects whether we should stop vs. delete.) + +## References +- Woodpecker Autoscaler Repo: `https://github.com/woodpecker-ci/autoscaler` +- Provider Interface: `engine/types/provider.go` +- Hetzner Provider (reference): `providers/hetznercloud/` +- Cloud-init Render: `engine/inits/cloudinit/cloudinit.go` +- Timeweb Cloud Go SDK: `https://github.com/timeweb-cloud/sdk-go` +- Timeweb Cloud API Docs: `https://timeweb.cloud/api-docs` diff --git a/checkmake.ini b/checkmake.ini new file mode 100644 index 0000000..5e5fdb8 --- /dev/null +++ b/checkmake.ini @@ -0,0 +1,2 @@ +[maxbodylength] +maxBodyLength = 12 diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..e611b4e --- /dev/null +++ b/config/config.go @@ -0,0 +1,19 @@ +package config + +import "time" + +type Config struct { + MinAgents int + MaxAgents int + WorkflowsPerAgent int + PoolID string + Image string + Environment map[string]string + GRPCAddress string + GRPCSecure bool + AgentInactivityTimeout time.Duration + AgentIdleTimeout time.Duration + UserData string + FilterLabels string + ExtraAgentLabels map[string]string +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b68d253 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +version: "3.8" + +services: + woodpecker-server: + image: woodpeckerci/woodpecker-server:next + ports: + - "8000:8000" + volumes: + - woodpecker-server-data:/var/lib/woodpecker/ + environment: + - WOODPECKER_OPEN=true + - WOODPECKER_ADMIN=pi3c83 + - WOODPECKER_GRPC_ADDR=:9000 + - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET} + restart: unless-stopped + + woodpecker-autoscaler: + build: + context: . + dockerfile: Dockerfile + env_file: .env + restart: unless-stopped + depends_on: + - woodpecker-server + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +volumes: + woodpecker-server-data: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..376b4fd --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,18 @@ +FROM --platform=$BUILDPLATFORM golang:1.26 AS build + +WORKDIR /src +COPY . . +ARG TARGETOS TARGETARCH +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/go/pkg \ + make build + +FROM --platform=$BUILDPLATFORM scratch +ENV GODEBUG=netdns=go + +# copy certs from build image +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +# copy agent binary +COPY --from=build /src/dist/woodpecker-autoscaler /bin/ + +ENTRYPOINT ["/bin/woodpecker-autoscaler"] diff --git a/docker/Dockerfile.make b/docker/Dockerfile.make new file mode 100644 index 0000000..8705fc9 --- /dev/null +++ b/docker/Dockerfile.make @@ -0,0 +1,27 @@ +# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local . +FROM docker.io/golang:1.26-alpine AS golang_image +FROM docker.io/node:24-alpine + +RUN apk add --no-cache --update make gcc binutils-gold musl-dev && \ + apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main protoc && \ + corepack enable + +# Build packages. +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 +ENV COREPACK_ENABLE_AUTO_PIN=0 + +# Cache tools +RUN GOBIN=/usr/local/go/bin make install-tools && \ + rm -rf /Makefile + +ENV GOPATH=/tmp/go +ENV HOME=/tmp/home +ENV PATH=$PATH:/usr/local/go/bin:/tmp/go/bin + +WORKDIR /build +RUN chmod -R 777 /root + +CMD [ "/bin/sh" ] diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md new file mode 100644 index 0000000..7259aec --- /dev/null +++ b/docs/DEPLOY.md @@ -0,0 +1,92 @@ +# Инструкция по развертыванию + +## 1. Получение API-токена Timeweb Cloud + +1. Войдите в панель управления Timeweb Cloud: https://timeweb.cloud +2. Перейдите в раздел **"API и Terraform"**. +3. Создайте новый токен: + - Задайте имя (например, `woodpecker-autoscaler`). + - Выберите права: управление серверами (Servers). + - Можно отключить подтверждение через Telegram для автоматизации. + - Сохраните токен — он отображается только один раз. + +## 2. Определение параметров сервера + +Проверенные параметры для CI-агента: + +| Параметр | Значение | Описание | +|----------|----------|----------| +| OS ID | `79` | Ubuntu 22.04 LTS | +| Preset ID | `2451` | Cloud-40 (2 CPU, 2GB RAM, 40GB NVMe, 800 руб/мес) | +| Availability Zone | `spb-1` | Санкт-Петербург (ru-1) | + +Другие варианты: +- Ubuntu 24.04: `os_id=99` +- Debian 12: `os_id=95` +- Москва: `zone=msk-1`, `preset_id=4799` (Cloud MSK 40) +- Новосибирск: `zone=nsk-1`, `preset_id=4241` (Cloud NSK 40) + +Для получения полного списка: +```bash +./timeweb-list +``` + +## 3. Настройка Woodpecker Autoscaler + +Создайте `.env` файл: + +```bash +# Woodpecker Server +WOODPECKER_SERVER=http://woodpecker-server:8000 +WOODPECKER_TOKEN=your-woodpecker-admin-token +WOODPECKER_GRPC_ADDR=woodpecker-server:9000 +WOODPECKER_GRPC_SECURE=false + +# Autoscaler +WOODPECKER_POOL_ID=twcloud +WOODPECKER_MIN_AGENTS=0 +WOODPECKER_MAX_AGENTS=5 +WOODPECKER_WORKFLOWS_PER_AGENT=2 +WOODPECKER_AGENT_IDLE_TIMEOUT=10m +WOODPECKER_RECONCILIATION_INTERVAL=1m +WOODPECKER_PROVIDER=timewebcloud + +# Timeweb Cloud +WOODPECKER_TIMEWEBCLOUD_API_TOKEN=your-jwt-token +WOODPECKER_TIMEWEBCLOUD_OS_ID=79 +WOODPECKER_TIMEWEBCLOUD_PRESET_ID=2451 +WOODPECKER_TIMEWEBCLOUD_AVAILABILITY_ZONE=spb-1 +# Опционально: SSH-ключи для доступа к агентам +# WOODPECKER_TIMEWEBCLOUD_SSH_KEY_IDS=1234 +``` + +## 4. Запуск через Docker Compose + +```bash +docker-compose up -d +``` + +Просмотр логов: +```bash +docker-compose logs -f woodpecker-autoscaler +``` + +## 5. Проверка + +1. Запушьте коммит в репозиторий, подключенный к Woodpecker. +2. В логах autoscaler должно появиться `create agent` и `create server`. +3. В панели Timeweb Cloud должен создаться новый сервер с именем `pool-twcloud-agent-`. +4. Через 2-3 минуты агент подключится к серверу и заберет задачу. +5. После завершения задачи и истечения `AGENT_IDLE_TIMEOUT` сервер будет удален. + +## 6. Тестирование API (опционально) + +Для проверки корректности API-клиента: + +```bash +# Список ОС и тарифов +./timeweb-list + +# Тест создания/удаления сервера +TIMEWEB_API_TOKEN=your-token ./timeweb-tester +``` diff --git a/engine/autoscaler.go b/engine/autoscaler.go new file mode 100644 index 0000000..8cbb697 --- /dev/null +++ b/engine/autoscaler.go @@ -0,0 +1,400 @@ +package engine + +import ( + "context" + "fmt" + "math" + "regexp" + "strings" + "time" + + "github.com/rs/zerolog/log" + + "go.woodpecker-ci.org/autoscaler/config" + "go.woodpecker-ci.org/autoscaler/engine/types" + "go.woodpecker-ci.org/autoscaler/server" + "go.woodpecker-ci.org/autoscaler/utils" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +type Autoscaler struct { + client server.Client + agents []*woodpecker.Agent + config *config.Config + provider types.Provider +} + +// NewAutoscaler creates a new Autoscaler instance. +// It takes in a Provider, Client and Config, and returns a configured +// Autoscaler struct. +func NewAutoscaler(p types.Provider, client server.Client, config *config.Config) Autoscaler { + return Autoscaler{ + provider: p, + client: client, + config: config, + } +} + +func (a *Autoscaler) loadAgents(_ context.Context) error { + a.agents = []*woodpecker.Agent{} + + agents, err := a.client.AgentList() + if err != nil { + return fmt.Errorf("client.AgentList: %w", err) + } + r, err := regexp.Compile(fmt.Sprintf("pool-%s-agent-.*?", a.config.PoolID)) + if err != nil { + return fmt.Errorf("could not create regex matcher for agent names by pool ID: %w", err) + } + + for _, agent := range agents { + if r.MatchString(agent.Name) { + a.agents = append(a.agents, agent) + } + } + + return nil +} + +func (a *Autoscaler) getPoolAgents(excludeNoSchedule bool) []*woodpecker.Agent { + agents := make([]*woodpecker.Agent, 0) + for _, agent := range a.agents { + if excludeNoSchedule && agent.NoSchedule { + continue + } + agents = append(agents, agent) + } + return agents +} + +func (a *Autoscaler) createAgents(ctx context.Context, amount int) error { + suffixLength := 4 + + reactivatedAgents := 0 + + // try to re-activate agents that are in no-schedule state + for i := 0; i < amount; i++ { + for _, agent := range a.agents { + if agent.NoSchedule { + log.Info().Str("agent", agent.Name).Msg("reactivate agent") + agent.NoSchedule = false + _, err := a.client.AgentUpdate(agent) + if err != nil { + return fmt.Errorf("client.AgentUpdate: %w", err) + } + reactivatedAgents++ + } + } + } + + // create new agents + for i := 0; i < amount-reactivatedAgents; i++ { + agent, err := a.client.AgentCreate(&woodpecker.Agent{ + Name: fmt.Sprintf("pool-%s-agent-%s", a.config.PoolID, utils.RandomString(suffixLength)), + }) + if err != nil { + return fmt.Errorf("client.AgentCreate: %w", err) + } + + log.Info().Str("agent", agent.Name).Msg("deploying agent") + + err = a.provider.DeployAgent(ctx, agent) + if err != nil { + return fmt.Errorf("types.DeployAgent: %w", err) + } + + a.agents = append(a.agents, agent) + } + + return nil +} + +func (a *Autoscaler) drainAgents(_ context.Context, amount int) error { + for i := 0; i < amount; i++ { + for _, agent := range a.agents { + // agent is already marked for draining + if agent.NoSchedule { + continue + } + + // agent has recently done work => not ready for draining + if time.Since(time.Unix(agent.LastWork, 0)) < a.config.AgentIdleTimeout { + continue + } + + // agent has never contacted the server => not ready for draining + if agent.LastContact == 0 { + continue + } + + log.Info().Str("agent", agent.Name).Msg("drain agent") + agent.NoSchedule = true + _, err := a.client.AgentUpdate(agent) + if err != nil { + return fmt.Errorf("client.AgentUpdate: %w", err) + } + break + } + } + + return nil +} + +func (a *Autoscaler) isAgentIdle(agent *woodpecker.Agent) (bool, error) { + tasks, err := a.client.AgentTasksList(agent.ID) + if err != nil { + return false, fmt.Errorf("client.AgentTasksList: %w", err) + } + + // agent still has tasks => not idle + if len(tasks) > 0 { + return false, nil + } + + // agent has done work recently => not idle + if time.Since(time.Unix(agent.LastWork, 0)) < a.config.AgentIdleTimeout { + return false, nil + } + + return true, nil +} + +func (a *Autoscaler) removeAgent(ctx context.Context, agent *woodpecker.Agent, reason string) error { + isIdle, err := a.isAgentIdle(agent) + if err != nil { + return err + } + if !isIdle { + log.Info().Str("agent", agent.Name).Msg("agent is still processing workload") + return nil + } + + log.Info().Str("agent", agent.Name).Str("reason", reason).Msgf("removing agent") + + err = a.provider.RemoveAgent(ctx, agent) + if err != nil { + return err + } + + err = a.client.AgentDelete(agent.ID) + if err != nil { + return fmt.Errorf("client.AgentDelete: %w", err) + } + + filteredAgents := make([]*woodpecker.Agent, 0) + for _, a := range a.agents { + if a.ID != agent.ID { + filteredAgents = append(filteredAgents, a) + } + } + a.agents = filteredAgents + + return nil +} + +func (a *Autoscaler) removeDrainedAgents(ctx context.Context) error { + for _, agent := range a.getPoolAgents(false) { + if !agent.NoSchedule { + continue + } + + err := a.removeAgent(ctx, agent, "was drained") + if err != nil { + return err + } + } + + return nil +} + +func (a *Autoscaler) cleanupDanglingAgents(ctx context.Context) error { + woodpeckerAgents := a.getPoolAgents(false) + providerAgentNames, err := a.provider.ListDeployedAgentNames(ctx) + if err != nil { + return err + } + + // remove agents that are not in the woodpecker agent list anymore + for _, agentName := range providerAgentNames { + found := false + for _, agent := range woodpeckerAgents { + if agent.Name == agentName { + found = true + break + } + } + + if !found { + log.Info().Str("agent", agentName).Str("reason", "not found on woodpecker").Msg("remove agent") + if err := a.provider.RemoveAgent(ctx, &woodpecker.Agent{Name: agentName}); err != nil { + return fmt.Errorf("types.RemoveAgent: %w", err) + } + + // remove agent from providerAgentNames + _providerAgentNames := make([]string, 0) + for _, a := range providerAgentNames { + if a != agentName { + _providerAgentNames = append(_providerAgentNames, a) + } + } + providerAgentNames = _providerAgentNames + } + } + + // remove agents that do not exist on the provider anymore + for _, agent := range woodpeckerAgents { + found := false + for _, agentName := range providerAgentNames { + if agent.Name == agentName { + found = true + break + } + } + + if !found { + log.Info().Str("agent", agent.Name).Str("reason", "not found on provider").Msg("remove agent") + if err = a.client.AgentDelete(agent.ID); err != nil { + return fmt.Errorf("client.AgentDelete: %w", err) + } + + // remove agent from woodpeckerAgents + _woodpeckerAgents := make([]*woodpecker.Agent, 0) + for _, a := range a.agents { + if a.Name != agent.Name { + woodpeckerAgents = append(woodpeckerAgents, a) + } + } + a.agents = _woodpeckerAgents + } + } + + return nil +} + +func (a *Autoscaler) cleanupStaleAgents(ctx context.Context) error { + // remove agents that haven't contacted the server for a while (including agents that never contacted the server) + for _, agent := range a.getPoolAgents(false) { + if agent.NoSchedule { + continue + } + + lastContact := agent.LastContact + + // if agent has never contacted the server, use the creation time + if lastContact == 0 { + lastContact = agent.Created + } + + if time.Since(time.Unix(lastContact, 0)) > a.config.AgentInactivityTimeout { + err := a.removeAgent(ctx, agent, "hasn't connected to the server for a while") + if err != nil { + return err + } + } + } + + return nil +} + +func (a *Autoscaler) getQueueInfo(_ context.Context) (freeTasks, runningTasks, pendingTasks int, err error) { + queueInfo, err := a.client.QueueInfo() + if err != nil { + return 0, 0, 0, fmt.Errorf("error from QueueInfo: %s", err.Error()) + } + + if a.config.FilterLabels == "" { + return queueInfo.Stats.Workers, queueInfo.Stats.Running, queueInfo.Stats.Pending, nil + } + + labelFilterKey, labelFilterValue, ok := strings.Cut(a.config.FilterLabels, "=") + if !ok { + return 0, 0, 0, fmt.Errorf("invalid labels filter: %s", a.config.FilterLabels) + } + + running := countTasksByLabel(queueInfo.Running, labelFilterKey, labelFilterValue) + pending := countTasksByLabel(queueInfo.Pending, labelFilterKey, labelFilterValue) + + return queueInfo.Stats.Workers, running, pending, nil +} + +func (a *Autoscaler) calcAgents(ctx context.Context) (float64, error) { + freeTasks, runningTasks, pendingTasks, err := a.getQueueInfo(ctx) + if err != nil { + return 0, err + } + + log.Debug().Msgf("queue info: freeTasks = %v runningTasks = %v pendingTasks = %v", freeTasks, runningTasks, pendingTasks) + availableAgents := math.Ceil(float64(freeTasks+runningTasks) / float64((a.config.WorkflowsPerAgent))) + reqAgents := math.Ceil(float64(pendingTasks+runningTasks) / float64(a.config.WorkflowsPerAgent)) + + availablePoolAgents := len(a.getPoolAgents(true)) + maxUp := float64(a.config.MaxAgents - availablePoolAgents) + maxDown := float64(availablePoolAgents - a.config.MinAgents) + + reqPoolAgents := math.Ceil(reqAgents - (availableAgents + float64(availablePoolAgents))) + reqPoolAgents = math.Max(reqPoolAgents, -maxDown) + reqPoolAgents = math.Min(reqPoolAgents, maxUp) + + log.Debug().Msgf("capacity info: agents = %v/%v pool = %v/%v limits = %v/%v", availableAgents, reqAgents, availablePoolAgents, reqPoolAgents, maxUp, maxDown) + + return reqPoolAgents, nil +} + +// Reconcile periodically checks the status of the agent pool and adjusts it to match +// the desired capacity based on the current queue state. +func (a *Autoscaler) Reconcile(ctx context.Context) error { + if err := a.loadAgents(ctx); err != nil { + return fmt.Errorf("loading agents failed: %w", err) + } + + reqPoolAgents, err := a.calcAgents(ctx) + if err != nil { + return fmt.Errorf("calculating agents failed: %w", err) + } + + if reqPoolAgents > 0 { + num := int(math.Abs(reqPoolAgents)) + log.Debug().Msgf("starting %d additional agents", num) + + if err := a.createAgents(ctx, num); err != nil { + return fmt.Errorf("creating agents failed: %w", err) + } + } + + if reqPoolAgents < 0 { + num := int(math.Abs(reqPoolAgents)) + + log.Debug().Msgf("checking %d agents if ready for draining", num) + if err := a.drainAgents(ctx, num); err != nil { + return fmt.Errorf("draining agents failed: %w", err) + } + } + + // cleanup agents that are only present at the provider or woodpecker + if err := a.cleanupDanglingAgents(ctx); err != nil { + return fmt.Errorf("cleaning up dangling agents failed: %w", err) + } + + // cleanup agents that haven't contacted the server for a while + if err := a.cleanupStaleAgents(ctx); err != nil { + return fmt.Errorf("cleaning up stale agents failed: %w", err) + } + + // remove agents that are drained + if err := a.removeDrainedAgents(ctx); err != nil { + return fmt.Errorf("removing drained agents failed: %w", err) + } + + return nil +} + +func countTasksByLabel(jobs []woodpecker.Task, labelKey, labelValue string) int { + count := 0 + for _, job := range jobs { + val, exists := job.Labels[labelKey] + if exists && val == labelValue { + count++ + } + } + return count +} diff --git a/engine/autoscaler_test.go b/engine/autoscaler_test.go new file mode 100644 index 0000000..5c77095 --- /dev/null +++ b/engine/autoscaler_test.go @@ -0,0 +1,546 @@ +package engine + +import ( + "testing" + "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "go.woodpecker-ci.org/autoscaler/config" + mocks_provider "go.woodpecker-ci.org/autoscaler/engine/types/mocks" + mocks_server "go.woodpecker-ci.org/autoscaler/server/mocks" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +type MockClient struct { + workers int + running int + pending int + waitingOnDeps int + woodpecker.Client +} + +func (m MockClient) QueueInfo() (*woodpecker.Info, error) { + info := &woodpecker.Info{} + + info.Stats.Workers = m.workers + info.Stats.Running = m.running + info.Stats.Pending = m.pending + info.Stats.WaitingOnDeps = m.waitingOnDeps + + info.Pending = []woodpecker.Task{ + { + Labels: map[string]string{ + "arch": "amd64", + }, + }, + } + info.Running = []woodpecker.Task{ + { + Labels: map[string]string{ + "arch": "amd64", + }, + }, + } + + return info, nil +} + +func Test_calcAgents(t *testing.T) { + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + + t.Run("should create new agent (MinAgents)", func(t *testing.T) { + autoscaler := Autoscaler{client: &MockClient{ + pending: 0, + }, config: &config.Config{ + WorkflowsPerAgent: 1, + MaxAgents: 2, + MinAgents: 1, + }} + + value, _ := autoscaler.calcAgents(t.Context()) + assert.Equal(t, float64(1), value) + }) + + t.Run("should create single agent", func(t *testing.T) { + autoscaler := Autoscaler{client: &MockClient{ + pending: 2, + }, config: &config.Config{ + WorkflowsPerAgent: 5, + MaxAgents: 3, + }} + + value, _ := autoscaler.calcAgents(t.Context()) + assert.Equal(t, float64(1), value) + }) + + t.Run("should create multiple agents", func(t *testing.T) { + autoscaler := Autoscaler{client: &MockClient{ + pending: 6, + }, config: &config.Config{ + WorkflowsPerAgent: 5, + MaxAgents: 3, + }} + + value, _ := autoscaler.calcAgents(t.Context()) + assert.Equal(t, float64(2), value) + }) + + t.Run("should create new agent (MaxAgents)", func(t *testing.T) { + autoscaler := Autoscaler{client: &MockClient{ + pending: 2, + }, config: &config.Config{ + WorkflowsPerAgent: 1, + MaxAgents: 2, + }, agents: []*woodpecker.Agent{ + {Name: "pool-1-agent-1234"}, + }} + + value, _ := autoscaler.calcAgents(t.Context()) + assert.Equal(t, float64(1), value) + }) + + t.Run("should not create new agent (availableAgents)", func(t *testing.T) { + autoscaler := Autoscaler{client: &MockClient{ + workers: 2, + pending: 2, + }, config: &config.Config{ + WorkflowsPerAgent: 1, + MaxAgents: 2, + }} + + value, _ := autoscaler.calcAgents(t.Context()) + assert.Equal(t, float64(0), value) + }) +} + +func Test_getQueueInfo(t *testing.T) { + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + t.Run("should not filter", func(t *testing.T) { + autoscaler := Autoscaler{ + client: &MockClient{ + pending: 2, + }, + config: &config.Config{}, + } + + free, running, pending, _ := autoscaler.getQueueInfo(t.Context()) + assert.Equal(t, 0, free) + assert.Equal(t, 0, running) + assert.Equal(t, 2, pending) + }) + + t.Run("should filter one by label", func(t *testing.T) { + autoscaler := Autoscaler{ + client: &MockClient{ + pending: 2, + }, + config: &config.Config{ + FilterLabels: "arch=amd64", + }, + } + + free, running, pending, _ := autoscaler.getQueueInfo(t.Context()) + assert.Equal(t, 0, free) + assert.Equal(t, 1, running) + assert.Equal(t, 1, pending) + }) + + t.Run("should filter all by label", func(t *testing.T) { + autoscaler := Autoscaler{ + client: &MockClient{ + pending: 2, + }, + config: &config.Config{ + FilterLabels: "arch=arm64", + }, + } + + free, running, pending, _ := autoscaler.getQueueInfo(t.Context()) + assert.Equal(t, 0, free) + assert.Equal(t, 0, running) + assert.Equal(t, 0, pending) + }) +} + +func Test_getPoolAgents(t *testing.T) { + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + {ID: 1, Name: "pool-1-agent-1", NoSchedule: false}, + {ID: 2, Name: "pool-1-agent-2", NoSchedule: true}, + {ID: 3, Name: "pool-1-agent-3", NoSchedule: false}, + }, + } + + agents := autoscaler.getPoolAgents(false) + assert.Equal(t, 3, len(agents)) + + agents = autoscaler.getPoolAgents(true) + assert.Equal(t, 2, len(agents)) +} + +func Test_createAgents(t *testing.T) { + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + + t.Run("should create a new agent", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + client: client, + provider: provider, + config: &config.Config{ + PoolID: "1", + }, + } + + client.On("AgentCreate", mock.Anything).Return(&woodpecker.Agent{Name: "pool-1-agent-1"}, nil) + provider.On("DeployAgent", ctx, mock.Anything).Return(nil) + + err := autoscaler.createAgents(ctx, 1) + assert.NoError(t, err) + }) + + t.Run("should reuse an no-schedule agent first before creating a new one", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + client: client, + provider: provider, + agents: []*woodpecker.Agent{ + { + ID: 1, + NoSchedule: true, + }, + }, + config: &config.Config{ + PoolID: "1", + }, + } + + client.On("AgentUpdate", mock.MatchedBy(func(agent *woodpecker.Agent) bool { + return agent.ID == 1 && agent.NoSchedule == false + })).Return(nil, nil) + client.On("AgentCreate", mock.Anything).Return(&woodpecker.Agent{Name: "pool-1-agent-1"}, nil) + provider.On("DeployAgent", ctx, mock.Anything).Return(nil) + + err := autoscaler.createAgents(ctx, 2) + assert.NoError(t, err) + }) +} + +func Test_cleanupDanglingAgents(t *testing.T) { + t.Run("should remove agent that is only present on woodpecker (not provider)", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + {ID: 1, Name: "pool-1-agent-1", NoSchedule: false}, + }, + provider: provider, + client: client, + } + + provider.On("ListDeployedAgentNames", mock.Anything).Return(nil, nil) + client.On("AgentDelete", int64(1)).Return(nil) + + err := autoscaler.cleanupDanglingAgents(ctx) + assert.NoError(t, err) + }) + + t.Run("should remove agent that is only present on provider (not woodpecker)", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + {ID: 1, Name: "pool-1-agent-1", NoSchedule: false}, + }, + provider: provider, + client: client, + } + + provider.On("ListDeployedAgentNames", mock.Anything).Return([]string{"pool-1-agent-1", "pool-1-agent-2"}, nil) + provider.On("RemoveAgent", mock.Anything, mock.MatchedBy(func(agent *woodpecker.Agent) bool { + return agent.Name == "pool-1-agent-2" + })).Return(nil) + + err := autoscaler.cleanupDanglingAgents(ctx) + assert.NoError(t, err) + }) +} + +func Test_cleanupStaleAgents(t *testing.T) { + t.Run("should remove agent that never connected (last contact = 0) in over 15 minutes", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + { + ID: 1, + Name: "active agent", + NoSchedule: false, + Created: time.Now().Add(-time.Minute * 20).Unix(), // created 20 minutes ago + LastContact: time.Now().Add(-time.Minute * 5).Unix(), // last contact 5 minutes ago + }, + { + ID: 2, + Name: "never contacted agent", + NoSchedule: false, + Created: time.Now().Add(-time.Minute * 20).Unix(), // created 20 minutes ago + LastContact: 0, // never contacted + }, + }, + provider: provider, + client: client, + config: &config.Config{ + AgentInactivityTimeout: time.Minute * 15, + }, + } + + client.On("AgentTasksList", int64(2)).Return(nil, nil) + client.On("AgentDelete", int64(2)).Return(nil) + provider.On("RemoveAgent", mock.Anything, mock.MatchedBy(func(agent *woodpecker.Agent) bool { + return agent.ID == 2 + })).Return(nil) + + err := autoscaler.cleanupStaleAgents(ctx) + assert.NoError(t, err) + }) + + t.Run("should remove agent that has lost connection for more than 15 minutes", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + { + ID: 1, + Name: "active agent", + NoSchedule: false, + Created: time.Now().Add(-time.Minute * 20).Unix(), // created 20 minutes ago + LastContact: time.Now().Add(-time.Minute * 5).Unix(), // last contact 5 minutes ago + }, + { + ID: 2, + Name: "stale agent", + NoSchedule: false, + Created: time.Now().Add(-time.Minute * 20).Unix(), // created 20 minutes ago + LastContact: time.Now().Add(-time.Minute * 20).Unix(), // last contact 20 minutes ago + }, + }, + provider: provider, + client: client, + config: &config.Config{ + AgentInactivityTimeout: time.Minute * 15, + }, + } + + client.On("AgentTasksList", int64(2)).Return(nil, nil) + client.On("AgentDelete", int64(2)).Return(nil) + provider.On("RemoveAgent", mock.Anything, mock.MatchedBy(func(agent *woodpecker.Agent) bool { + return agent.ID == 2 + })).Return(nil) + + err := autoscaler.cleanupStaleAgents(ctx) + assert.NoError(t, err) + }) +} + +func Test_isAgentIdle(t *testing.T) { + t.Run("should return false if agent has tasks", func(t *testing.T) { + client := mocks_server.NewMockClient(t) + autoscaler := Autoscaler{ + client: client, + config: &config.Config{ + AgentIdleTimeout: time.Minute * 15, + }, + } + + client.On("AgentTasksList", int64(1)).Return([]*woodpecker.Task{ + {ID: "1"}, + }, nil) + + idle, err := autoscaler.isAgentIdle(&woodpecker.Agent{ + ID: 1, + Name: "pool-1-agent-1", + NoSchedule: false, + }) + assert.NoError(t, err) + assert.False(t, idle) + }) + + t.Run("should return false if agent has done work recently", func(t *testing.T) { + client := mocks_server.NewMockClient(t) + autoscaler := Autoscaler{ + client: client, + config: &config.Config{ + AgentIdleTimeout: time.Minute * 15, + }, + } + + client.On("AgentTasksList", int64(1)).Return(nil, nil) + + idle, err := autoscaler.isAgentIdle(&woodpecker.Agent{ + ID: 1, + Name: "pool-1-agent-1", + NoSchedule: false, + LastWork: time.Now().Add(-time.Minute * 10).Unix(), + }) + assert.NoError(t, err) + assert.False(t, idle) + }) + + t.Run("should return true if agent is idle", func(t *testing.T) { + client := mocks_server.NewMockClient(t) + autoscaler := Autoscaler{ + client: client, + config: &config.Config{ + AgentIdleTimeout: time.Minute * 15, + }, + } + + client.On("AgentTasksList", int64(1)).Return(nil, nil) // no tasks + + idle, err := autoscaler.isAgentIdle(&woodpecker.Agent{ + ID: 1, + Name: "pool-1-agent-1", + NoSchedule: false, + LastWork: time.Now().Add(-time.Minute * 20).Unix(), // last work 20 minutes ago + }) + assert.NoError(t, err) + assert.True(t, idle) + }) +} + +func Test_drainAgents(t *testing.T) { + t.Run("should drain agents and skip no-schedule ones", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + {ID: 1, Name: "pool-1-agent-1", NoSchedule: false, LastContact: time.Now().Add(-time.Minute * 2).Unix()}, + {ID: 2, Name: "pool-1-agent-2", NoSchedule: true, LastContact: time.Now().Add(-time.Minute * 2).Unix()}, + {ID: 3, Name: "pool-1-agent-3", NoSchedule: true, LastContact: time.Now().Add(-time.Minute * 2).Unix()}, + {ID: 4, Name: "pool-1-agent-4", NoSchedule: false, LastContact: time.Now().Add(-time.Minute * 2).Unix()}, + }, + provider: provider, + client: client, + config: &config.Config{ + AgentIdleTimeout: time.Minute * 15, + }, + } + + client.On("AgentUpdate", mock.MatchedBy(func(agent *woodpecker.Agent) bool { + return (agent.ID == 1 || agent.ID == 4) && agent.NoSchedule == true + })).Return(nil, nil) + + err := autoscaler.drainAgents(ctx, 2) + assert.NoError(t, err) + assert.True(t, autoscaler.agents[0].NoSchedule) + assert.True(t, autoscaler.agents[3].NoSchedule) + }) + + t.Run("should not remove an agent that never connected", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + {ID: 1, Name: "pool-1-agent-1", NoSchedule: false, LastContact: 0}, + }, + provider: provider, + client: client, + config: &config.Config{ + AgentIdleTimeout: time.Minute * 15, + }, + } + + err := autoscaler.drainAgents(ctx, 1) + assert.NoError(t, err) + assert.False(t, autoscaler.agents[0].NoSchedule) + }) + + t.Run("should not remove an agent that has recently done some work", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + { + ID: 1, + Name: "pool-1-agent-1", + NoSchedule: false, + LastContact: time.Now().Add(-time.Minute * 2).Unix(), // last contact 2 minutes ago + LastWork: time.Now().Add(-time.Minute * 5).Unix(), // last work 5 minutes ago + }, + }, + provider: provider, + client: client, + config: &config.Config{ + AgentIdleTimeout: time.Minute * 15, + }, + } + + err := autoscaler.drainAgents(ctx, 1) + assert.NoError(t, err) + assert.False(t, autoscaler.agents[0].NoSchedule) + }) +} + +func Test_removeDrainedAgents(t *testing.T) { + t.Run("should remove agent", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + {ID: 1, Name: "pool-1-agent-1", NoSchedule: false}, + {ID: 2, Name: "pool-1-agent-2", NoSchedule: true}, + {ID: 3, Name: "pool-1-agent-3", NoSchedule: false}, + }, + provider: provider, + client: client, + config: &config.Config{ + AgentIdleTimeout: time.Minute * 15, + }, + } + + client.On("AgentTasksList", int64(2)).Return(nil, nil) + provider.On("RemoveAgent", mock.Anything, mock.MatchedBy(func(agent *woodpecker.Agent) bool { + return agent.ID == 2 + })).Return(nil) + client.On("AgentDelete", int64(2)).Return(nil) + + err := autoscaler.removeDrainedAgents(ctx) + assert.NoError(t, err) + }) + + t.Run("should not remove agent with tasks", func(t *testing.T) { + ctx := t.Context() + client := mocks_server.NewMockClient(t) + provider := mocks_provider.NewMockProvider(t) + autoscaler := Autoscaler{ + agents: []*woodpecker.Agent{ + {ID: 1, Name: "pool-1-agent-1", NoSchedule: false}, + {ID: 2, Name: "pool-1-agent-2", NoSchedule: true}, + {ID: 3, Name: "pool-1-agent-3", NoSchedule: false}, + }, + provider: provider, + client: client, + } + + client.On("AgentTasksList", int64(2)).Return([]*woodpecker.Task{ + {ID: "1"}, + }, nil) + + err := autoscaler.removeDrainedAgents(ctx) + assert.NoError(t, err) + }) +} diff --git a/engine/const.go b/engine/const.go new file mode 100644 index 0000000..58f76ce --- /dev/null +++ b/engine/const.go @@ -0,0 +1,9 @@ +package engine + +import "fmt" + +var ( + LabelPrefix = "wp.autoscaler/" + LabelPool = fmt.Sprintf("%spool", LabelPrefix) + LabelImage = fmt.Sprintf("%simage", LabelPrefix) +) diff --git a/engine/inits/cloudinit/cloudinit.go b/engine/inits/cloudinit/cloudinit.go new file mode 100644 index 0000000..27f61d0 --- /dev/null +++ b/engine/inits/cloudinit/cloudinit.go @@ -0,0 +1,116 @@ +package cloudinit + +import ( + "bytes" + "fmt" + "strings" + "text/template" + + "go.woodpecker-ci.org/autoscaler/config" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +// RenderUserDataTemplate renders the user data template for an Agent +// using the provided configuration. +func RenderUserDataTemplate(config *config.Config, agent *woodpecker.Agent, tmpl *template.Template) (string, error) { + var err error + + switch { + case tmpl != nil: + case config.UserData != "": + tmpl, err = template.New("user-data").Parse(config.UserData) + default: + tmpl, err = template.New("user-data").Parse(CloudInitUserDataUbuntuDefault) + } + + if err != nil { + return "", fmt.Errorf("template.New.Parse %w", err) + } + + params := struct { + Image string + Environment map[string]string + }{ + Image: config.Image, + Environment: map[string]string{ + "WOODPECKER_SERVER": config.GRPCAddress, + "WOODPECKER_AGENT_SECRET": agent.Token, + "WOODPECKER_MAX_WORKFLOWS": fmt.Sprintf("%d", config.WorkflowsPerAgent), + }, + } + + if config.GRPCSecure { + params.Environment["WOODPECKER_GRPC_SECURE"] = "true" + } + + for key, value := range config.Environment { + params.Environment[key] = value + } + + params.Environment["WOODPECKER_AGENT_LABELS"] = genExtraAgentLabels(config.ExtraAgentLabels) + + var userData bytes.Buffer + if err := tmpl.Execute(&userData, params); err != nil { + return "", err + } + + return userData.String(), nil +} + +func genExtraAgentLabels(conf map[string]string) string { + out := make([]string, 0, len(conf)) + for k, v := range conf { + out = append(out, fmt.Sprintf("%s=%s", k, v)) + } + return strings.Join(out, ",") +} + +// editorconfig-checker-disable +var CloudInitUserDataUbuntuDefault = ` +#cloud-config + +package_reboot_if_required: false +package_update: true +package_upgrade: false + +groups: + - docker + +system_info: + default_user: + groups: [ docker ] + +apt: + sources: + docker.list: + keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 + keyserver: https://download.docker.com/linux/ubuntu/gpg + source: deb [signed-by=$KEY_FILE] https://download.docker.com/linux/ubuntu $RELEASE stable + +packages: + - docker-ce + - docker-compose-plugin + - binfmt-support + - qemu-user-static + +write_files: +- path: /root/docker-compose.yml + content: | + # docker-compose.yml + version: '3' + services: + woodpecker-agent: + image: {{ .Image }} + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + {{- range $key, $value := .Environment }} + - {{ $key }}={{ $value }} + {{- end }} + +runcmd: + - sh -xc "cd /root; docker compose up -d" + +final_message: "The system is finally up, after $UPTIME seconds" +` // editorconfig-checker-enable diff --git a/engine/inits/cloudinit/cloudinit_test.go b/engine/inits/cloudinit/cloudinit_test.go new file mode 100644 index 0000000..beae5f5 --- /dev/null +++ b/engine/inits/cloudinit/cloudinit_test.go @@ -0,0 +1,65 @@ +package cloudinit_test + +import ( + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + + "go.woodpecker-ci.org/autoscaler/config" + "go.woodpecker-ci.org/autoscaler/engine/inits/cloudinit" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var testUserDataStr = ` +image: {{ .Image }} +environment: + {{- range $key, $value := .Environment }} + - {{ $key }}={{ $value }} + {{- end }} +` + +var testUserDataTmpl = template.Must(template.New("test").Parse(testUserDataStr)) + +func TestRenderUserDataTemplate(t *testing.T) { + config := &config.Config{ + Image: "test-image", + GRPCAddress: "test-address", + GRPCSecure: false, + Environment: map[string]string{ + "FOO": "bar", + }, + } + agent := &woodpecker.Agent{ + Token: "test-token", + } + + userData, err := cloudinit.RenderUserDataTemplate(config, agent, testUserDataTmpl) + + assert.NoError(t, err) + assert.Contains(t, userData, "test-image") + assert.Contains(t, userData, "bar") + assert.Contains(t, userData, "WOODPECKER_SERVER=test-address") + assert.Contains(t, userData, "WOODPECKER_AGENT_SECRET=test-token") +} + +func TestRenderUserDataTemplate_Secure(t *testing.T) { + config := &config.Config{ + GRPCSecure: true, + } + agent := &woodpecker.Agent{} + + userData, err := cloudinit.RenderUserDataTemplate(config, agent, testUserDataTmpl) + + assert.NoError(t, err) + assert.Contains(t, userData, "WOODPECKER_GRPC_SECURE=true") +} + +func TestRenderUserDataTemplate_Error(t *testing.T) { + config := &config.Config{} + agent := &woodpecker.Agent{} + tmpl := template.Must(template.New("test").Parse("{{.Missing}}")) + + _, err := cloudinit.RenderUserDataTemplate(config, agent, tmpl) + assert.Error(t, err) +} diff --git a/engine/types/mocks/mock_Provider.go b/engine/types/mocks/mock_Provider.go new file mode 100644 index 0000000..d52a166 --- /dev/null +++ b/engine/types/mocks/mock_Provider.go @@ -0,0 +1,215 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + mock "github.com/stretchr/testify/mock" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +// NewMockProvider creates a new instance of MockProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *MockProvider { + mock := &MockProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockProvider is an autogenerated mock type for the Provider type +type MockProvider struct { + mock.Mock +} + +type MockProvider_Expecter struct { + mock *mock.Mock +} + +func (_m *MockProvider) EXPECT() *MockProvider_Expecter { + return &MockProvider_Expecter{mock: &_m.Mock} +} + +// DeployAgent provides a mock function for the type MockProvider +func (_mock *MockProvider) DeployAgent(context1 context.Context, agent *woodpecker.Agent) error { + ret := _mock.Called(context1, agent) + + if len(ret) == 0 { + panic("no return value specified for DeployAgent") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *woodpecker.Agent) error); ok { + r0 = returnFunc(context1, agent) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockProvider_DeployAgent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeployAgent' +type MockProvider_DeployAgent_Call struct { + *mock.Call +} + +// DeployAgent is a helper method to define mock.On call +// - context1 context.Context +// - agent *woodpecker.Agent +func (_e *MockProvider_Expecter) DeployAgent(context1 interface{}, agent interface{}) *MockProvider_DeployAgent_Call { + return &MockProvider_DeployAgent_Call{Call: _e.mock.On("DeployAgent", context1, agent)} +} + +func (_c *MockProvider_DeployAgent_Call) Run(run func(context1 context.Context, agent *woodpecker.Agent)) *MockProvider_DeployAgent_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *woodpecker.Agent + if args[1] != nil { + arg1 = args[1].(*woodpecker.Agent) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockProvider_DeployAgent_Call) Return(err error) *MockProvider_DeployAgent_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockProvider_DeployAgent_Call) RunAndReturn(run func(context1 context.Context, agent *woodpecker.Agent) error) *MockProvider_DeployAgent_Call { + _c.Call.Return(run) + return _c +} + +// ListDeployedAgentNames provides a mock function for the type MockProvider +func (_mock *MockProvider) ListDeployedAgentNames(context1 context.Context) ([]string, error) { + ret := _mock.Called(context1) + + if len(ret) == 0 { + panic("no return value specified for ListDeployedAgentNames") + } + + var r0 []string + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { + return returnFunc(context1) + } + if returnFunc, ok := ret.Get(0).(func(context.Context) []string); ok { + r0 = returnFunc(context1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(context1) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockProvider_ListDeployedAgentNames_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListDeployedAgentNames' +type MockProvider_ListDeployedAgentNames_Call struct { + *mock.Call +} + +// ListDeployedAgentNames is a helper method to define mock.On call +// - context1 context.Context +func (_e *MockProvider_Expecter) ListDeployedAgentNames(context1 interface{}) *MockProvider_ListDeployedAgentNames_Call { + return &MockProvider_ListDeployedAgentNames_Call{Call: _e.mock.On("ListDeployedAgentNames", context1)} +} + +func (_c *MockProvider_ListDeployedAgentNames_Call) Run(run func(context1 context.Context)) *MockProvider_ListDeployedAgentNames_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockProvider_ListDeployedAgentNames_Call) Return(strings []string, err error) *MockProvider_ListDeployedAgentNames_Call { + _c.Call.Return(strings, err) + return _c +} + +func (_c *MockProvider_ListDeployedAgentNames_Call) RunAndReturn(run func(context1 context.Context) ([]string, error)) *MockProvider_ListDeployedAgentNames_Call { + _c.Call.Return(run) + return _c +} + +// RemoveAgent provides a mock function for the type MockProvider +func (_mock *MockProvider) RemoveAgent(context1 context.Context, agent *woodpecker.Agent) error { + ret := _mock.Called(context1, agent) + + if len(ret) == 0 { + panic("no return value specified for RemoveAgent") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, *woodpecker.Agent) error); ok { + r0 = returnFunc(context1, agent) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockProvider_RemoveAgent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveAgent' +type MockProvider_RemoveAgent_Call struct { + *mock.Call +} + +// RemoveAgent is a helper method to define mock.On call +// - context1 context.Context +// - agent *woodpecker.Agent +func (_e *MockProvider_Expecter) RemoveAgent(context1 interface{}, agent interface{}) *MockProvider_RemoveAgent_Call { + return &MockProvider_RemoveAgent_Call{Call: _e.mock.On("RemoveAgent", context1, agent)} +} + +func (_c *MockProvider_RemoveAgent_Call) Run(run func(context1 context.Context, agent *woodpecker.Agent)) *MockProvider_RemoveAgent_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 *woodpecker.Agent + if args[1] != nil { + arg1 = args[1].(*woodpecker.Agent) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockProvider_RemoveAgent_Call) Return(err error) *MockProvider_RemoveAgent_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockProvider_RemoveAgent_Call) RunAndReturn(run func(context1 context.Context, agent *woodpecker.Agent) error) *MockProvider_RemoveAgent_Call { + _c.Call.Return(run) + return _c +} diff --git a/engine/types/provider.go b/engine/types/provider.go new file mode 100644 index 0000000..cac219e --- /dev/null +++ b/engine/types/provider.go @@ -0,0 +1,13 @@ +package types + +import ( + "context" + + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +type Provider interface { + DeployAgent(context.Context, *woodpecker.Agent) error + RemoveAgent(context.Context, *woodpecker.Agent) error + ListDeployedAgentNames(context.Context) ([]string, error) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..19ca9ce --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module go.woodpecker-ci.org/autoscaler + +go 1.26.0 + +toolchain go1.26.3 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/rs/zerolog v1.35.1 + github.com/stretchr/testify v1.11.1 + github.com/urfave/cli/v3 v3.9.0 + go.woodpecker-ci.org/woodpecker/v3 v3.14.1 + golang.org/x/net v0.54.0 + golang.org/x/oauth2 v0.36.0 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + golang.org/x/sys v0.44.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d4597eb --- /dev/null +++ b/go.sum @@ -0,0 +1,44 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c= +github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +go.woodpecker-ci.org/woodpecker/v3 v3.14.1 h1:/HrTfGQOJxVHx5+ReCAf/4BBZe+HoE7s/rYgOzZlRi0= +go.woodpecker-ci.org/woodpecker/v3 v3.14.1/go.mod h1:b0Rz7zEMUG1T2C0djxV1euWkNlSDH5uqJB/5fCQtvBE= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/providers/timewebcloud/api/client.go b/providers/timewebcloud/api/client.go new file mode 100644 index 0000000..09f506a --- /dev/null +++ b/providers/timewebcloud/api/client.go @@ -0,0 +1,222 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "time" +) + +const defaultBaseURL = "https://api.timeweb.cloud" + +// Client is a minimal HTTP client for the Timeweb Cloud API. +type Client struct { + baseURL string + token string + client *http.Client +} + +// NewClient creates a new Timeweb Cloud API client. +func NewClient(token string) *Client { + return &Client{ + baseURL: defaultBaseURL, + token: token, + client: &http.Client{Timeout: 30 * time.Second}, + } +} + +func (c *Client) request(ctx context.Context, method, path string, body []byte) (*http.Response, error) { + url := c.baseURL + path + var bodyReader io.Reader + if body != nil { + bodyReader = bytes.NewReader(body) + } + + req, err := http.NewRequestWithContext(ctx, method, url, bodyReader) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+c.token) + req.Header.Set("Content-Type", "application/json") + + return c.client.Do(req) +} + +// CreateServerRequest is the payload for creating a server. +type CreateServerRequest struct { + Name string `json:"name"` + OsID int32 `json:"os_id,omitempty"` + ImageID string `json:"image_id,omitempty"` + PresetID int32 `json:"preset_id,omitempty"` + Bandwidth int32 `json:"bandwidth,omitempty"` + SSHKeysIds []int32 `json:"ssh_keys_ids,omitempty"` + CloudInit string `json:"cloud_init,omitempty"` + AvailabilityZone string `json:"availability_zone,omitempty"` + Hostname string `json:"hostname,omitempty"` + Comment string `json:"comment,omitempty"` +} + +// CreateServerResponse is the response from creating a server. +type CreateServerResponse struct { + Server Server `json:"server"` +} + +// Server represents a Timeweb Cloud VDS. +type Server struct { + ID int32 `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Location string `json:"location"` +} + +// CreateServer creates a new cloud server. +func (c *Client) CreateServer(ctx context.Context, req CreateServerRequest) (*CreateServerResponse, error) { + payload, err := json.Marshal(req) + if err != nil { + return nil, err + } + + resp, err := c.request(ctx, http.MethodPost, "/api/v1/servers", payload) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) + } + + var result CreateServerResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + return &result, nil +} + +// DeleteServer deletes a cloud server by ID. +func (c *Client) DeleteServer(ctx context.Context, id int32) error { + path := "/api/v1/servers/" + strconv.FormatInt(int64(id), 10) + resp, err := c.request(ctx, http.MethodDelete, path, nil) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) + } + + return nil +} + +// GetServersResponse is the response for listing servers. +type GetServersResponse struct { + Meta Meta `json:"meta"` + Servers []Server `json:"servers"` +} + +// Meta contains pagination info. +type Meta struct { + Total int32 `json:"total"` +} + +// GetServers lists all cloud servers. +func (c *Client) GetServers(ctx context.Context) (*GetServersResponse, error) { + resp, err := c.request(ctx, http.MethodGet, "/api/v1/servers", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) + } + + var result GetServersResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + return &result, nil +} + +type OS struct { + ID int32 `json:"id"` + Family string `json:"family"` + Name string `json:"name"` + Version string `json:"version"` + VersionCodename string `json:"version_codename"` + Description string `json:"description"` +} + +type GetOSListResponse struct { + OsList []OS `json:"servers_os"` +} + +func (c *Client) GetOSList(ctx context.Context) (*GetOSListResponse, error) { + resp, err := c.request(ctx, http.MethodGet, "/api/v1/os/servers", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) + } + + var result GetOSListResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + return &result, nil +} + +type Preset struct { + ID int32 `json:"id"` + Location string `json:"location"` + Price float64 `json:"price"` + CPU int32 `json:"cpu"` + CPUFrequency string `json:"cpu_frequency"` + RAM int32 `json:"ram"` + Disk int32 `json:"disk"` + DiskType string `json:"disk_type"` + Bandwidth int32 `json:"bandwidth"` + DescriptionShort string `json:"description_short"` + IsDedicatedCPU bool `json:"is_dedicated_cpu"` + Tags []string `json:"tags"` +} + +type GetPresetsResponse struct { + Presets []Preset `json:"server_presets"` +} + +func (c *Client) GetServerPresets(ctx context.Context) (*GetPresetsResponse, error) { + resp, err := c.request(ctx, http.MethodGet, "/api/v1/presets/servers", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body)) + } + + var result GetPresetsResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + return &result, nil +} diff --git a/providers/timewebcloud/flags.go b/providers/timewebcloud/flags.go new file mode 100644 index 0000000..7a2df63 --- /dev/null +++ b/providers/timewebcloud/flags.go @@ -0,0 +1,47 @@ +package timewebcloud + +import ( + "github.com/urfave/cli/v3" +) + +const category = "Timeweb Cloud" + +var ProviderFlags = []cli.Flag{ + &cli.StringFlag{ + Name: "timewebcloud-api-token", + Usage: "Timeweb Cloud API JWT token", + Sources: cli.EnvVars("WOODPECKER_TIMEWEBCLOUD_API_TOKEN"), + Category: category, + }, + &cli.IntFlag{ + Name: "timewebcloud-os-id", + Usage: "OS image ID to use for new servers (from GetOsList)", + Sources: cli.EnvVars("WOODPECKER_TIMEWEBCLOUD_OS_ID"), + Category: category, + }, + &cli.IntFlag{ + Name: "timewebcloud-preset-id", + Usage: "Pricing preset ID (from GetServersPresets)", + Sources: cli.EnvVars("WOODPECKER_TIMEWEBCLOUD_PRESET_ID"), + Category: category, + }, + &cli.StringFlag{ + Name: "timewebcloud-availability-zone", + Usage: "Availability zone for new servers", + Sources: cli.EnvVars("WOODPECKER_TIMEWEBCLOUD_AVAILABILITY_ZONE"), + Category: category, + }, + &cli.StringSliceFlag{ + Name: "timewebcloud-ssh-key-ids", + Usage: "SSH key IDs to inject into new servers", + Sources: cli.EnvVars("WOODPECKER_TIMEWEBCLOUD_SSH_KEY_IDS"), + Category: category, + }, + // TODO: Deprecated remove in v2.0 + &cli.StringFlag{ + Name: "timewebcloud-user-data", + Usage: "timeweb cloud userdata template (deprecated)", + Sources: cli.EnvVars("WOODPECKER_TIMEWEBCLOUD_USERDATA"), + Category: category, + }, +} diff --git a/providers/timewebcloud/provider.go b/providers/timewebcloud/provider.go new file mode 100644 index 0000000..9d14561 --- /dev/null +++ b/providers/timewebcloud/provider.go @@ -0,0 +1,122 @@ +package timewebcloud + +import ( + "context" + "fmt" + "strconv" + "strings" + "text/template" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/autoscaler/config" + "go.woodpecker-ci.org/autoscaler/engine/inits/cloudinit" + "go.woodpecker-ci.org/autoscaler/engine/types" + "go.woodpecker-ci.org/autoscaler/providers/timewebcloud/api" + woodpecker "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +type Provider struct { + name string + config *config.Config + client *api.Client + osID int32 + presetID int32 + availabilityZone string + sshKeyIDs []int32 + userDataTemplate *template.Template +} + +func New(_ context.Context, c *cli.Command, config *config.Config) (types.Provider, error) { + p := &Provider{ + name: "timewebcloud", + config: config, + client: api.NewClient(c.String("timewebcloud-api-token")), + osID: int32(c.Int("timewebcloud-os-id")), + presetID: int32(c.Int("timewebcloud-preset-id")), + availabilityZone: c.String("timewebcloud-availability-zone"), + } + + for _, raw := range c.StringSlice("timewebcloud-ssh-key-ids") { + id, err := strconv.ParseInt(raw, 10, 32) + if err != nil { + return nil, fmt.Errorf("%s: invalid ssh key id: %s", p.name, raw) + } + p.sshKeyIDs = append(p.sshKeyIDs, int32(id)) + } + + if u := c.String("timewebcloud-user-data"); u != "" { + log.Warn().Msg("timewebcloud-user-data is deprecated, please use provider-user-data instead") + tmpl, err := template.New("user-data").Parse(u) + if err != nil { + return nil, fmt.Errorf("%s: template.New.Parse %w", p.name, err) + } + p.userDataTemplate = tmpl + } + + return p, nil +} + +func (p *Provider) DeployAgent(ctx context.Context, agent *woodpecker.Agent) error { + userData, err := cloudinit.RenderUserDataTemplate(p.config, agent, p.userDataTemplate) + if err != nil { + return fmt.Errorf("%s: cloudinit.RenderUserDataTemplate: %w", p.name, err) + } + + req := api.CreateServerRequest{ + Name: agent.Name, + OsID: p.osID, + PresetID: p.presetID, + CloudInit: userData, + AvailabilityZone: p.availabilityZone, + SSHKeysIds: p.sshKeyIDs, + Comment: fmt.Sprintf("woodpecker autoscaler agent for pool %s", p.config.PoolID), + } + + log.Info().Str("agent", agent.Name).Msg("create agent") + + _, err = p.client.CreateServer(ctx, req) + if err != nil { + return fmt.Errorf("%s: CreateServer: %w", p.name, err) + } + + return nil +} + +func (p *Provider) RemoveAgent(ctx context.Context, agent *woodpecker.Agent) error { + servers, err := p.client.GetServers(ctx) + if err != nil { + return fmt.Errorf("%s: GetServers: %w", p.name, err) + } + + for _, server := range servers.Servers { + if server.Name == agent.Name { + log.Info().Str("agent", agent.Name).Int32("server_id", server.ID).Msg("delete agent") + if err := p.client.DeleteServer(ctx, server.ID); err != nil { + return fmt.Errorf("%s: DeleteServer: %w", p.name, err) + } + return nil + } + } + + log.Warn().Str("agent", agent.Name).Msg("agent not found, skipping deletion") + return nil +} + +func (p *Provider) ListDeployedAgentNames(ctx context.Context) ([]string, error) { + servers, err := p.client.GetServers(ctx) + if err != nil { + return nil, fmt.Errorf("%s: GetServers: %w", p.name, err) + } + + prefix := fmt.Sprintf("pool-%s-agent-", p.config.PoolID) + var names []string + for _, server := range servers.Servers { + if strings.HasPrefix(server.Name, prefix) { + names = append(names, server.Name) + } + } + + return names, nil +} diff --git a/release-config.ts b/release-config.ts new file mode 100644 index 0000000..13d36fd --- /dev/null +++ b/release-config.ts @@ -0,0 +1,3 @@ +export default { + commentOnReleasedPullRequests: false, +}; diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..09cc639 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>woodpecker-ci/renovate-config"] +} diff --git a/server/client.go b/server/client.go new file mode 100644 index 0000000..476f9ed --- /dev/null +++ b/server/client.go @@ -0,0 +1,79 @@ +package server + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + "strings" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v3" + "golang.org/x/net/proxy" + "golang.org/x/oauth2" + + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +type Client interface { + woodpecker.Client +} + +// NewClient returns a new client from the CLI context. +func NewClient(ctx context.Context, c *cli.Command) (Client, error) { + var ( + skip = c.Bool("skip-verify") + socks = c.String("socks-proxy") + socksoff = c.Bool("socks-proxy-off") + serverToken = c.String("server-token") + serverURL = c.String("server-url") + ) + serverURL = strings.TrimRight(serverURL, "/") + + if len(serverURL) == 0 { + return nil, fmt.Errorf("please provide the woodpecker server address") + } + if len(serverToken) == 0 { + return nil, fmt.Errorf("please provide a woodpecker access token") + } + + // attempt to find system CA certs + certs, err := x509.SystemCertPool() + if err != nil { + log.Error().Err(err).Msg("ca certs not found") + } + tlsConfig := &tls.Config{ + RootCAs: certs, + InsecureSkipVerify: skip, + } + + config := new(oauth2.Config) + client := config.Client( + ctx, + &oauth2.Token{ + AccessToken: serverToken, + }, + ) + + trans, _ := client.Transport.(*oauth2.Transport) + + if len(socks) != 0 && !socksoff { + dialer, err := proxy.SOCKS5("tcp", socks, nil, proxy.Direct) + if err != nil { + return nil, err + } + trans.Base = &http.Transport{ + TLSClientConfig: tlsConfig, + Proxy: http.ProxyFromEnvironment, + Dial: dialer.Dial, + } + } else { + trans.Base = &http.Transport{ + TLSClientConfig: tlsConfig, + Proxy: http.ProxyFromEnvironment, + } + } + + return woodpecker.NewClient(serverURL, client), nil +} diff --git a/server/mocks/mock_Client.go b/server/mocks/mock_Client.go new file mode 100644 index 0000000..60df56d --- /dev/null +++ b/server/mocks/mock_Client.go @@ -0,0 +1,4982 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "net/http" + + mock "github.com/stretchr/testify/mock" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// Agent provides a mock function for the type MockClient +func (_mock *MockClient) Agent(n int64) (*woodpecker.Agent, error) { + ret := _mock.Called(n) + + if len(ret) == 0 { + panic("no return value specified for Agent") + } + + var r0 *woodpecker.Agent + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64) (*woodpecker.Agent, error)); ok { + return returnFunc(n) + } + if returnFunc, ok := ret.Get(0).(func(int64) *woodpecker.Agent); ok { + r0 = returnFunc(n) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Agent) + } + } + if returnFunc, ok := ret.Get(1).(func(int64) error); ok { + r1 = returnFunc(n) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Agent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Agent' +type MockClient_Agent_Call struct { + *mock.Call +} + +// Agent is a helper method to define mock.On call +// - n int64 +func (_e *MockClient_Expecter) Agent(n interface{}) *MockClient_Agent_Call { + return &MockClient_Agent_Call{Call: _e.mock.On("Agent", n)} +} + +func (_c *MockClient_Agent_Call) Run(run func(n int64)) *MockClient_Agent_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_Agent_Call) Return(agent *woodpecker.Agent, err error) *MockClient_Agent_Call { + _c.Call.Return(agent, err) + return _c +} + +func (_c *MockClient_Agent_Call) RunAndReturn(run func(n int64) (*woodpecker.Agent, error)) *MockClient_Agent_Call { + _c.Call.Return(run) + return _c +} + +// AgentCreate provides a mock function for the type MockClient +func (_mock *MockClient) AgentCreate(agent *woodpecker.Agent) (*woodpecker.Agent, error) { + ret := _mock.Called(agent) + + if len(ret) == 0 { + panic("no return value specified for AgentCreate") + } + + var r0 *woodpecker.Agent + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Agent) (*woodpecker.Agent, error)); ok { + return returnFunc(agent) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Agent) *woodpecker.Agent); ok { + r0 = returnFunc(agent) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Agent) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.Agent) error); ok { + r1 = returnFunc(agent) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_AgentCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AgentCreate' +type MockClient_AgentCreate_Call struct { + *mock.Call +} + +// AgentCreate is a helper method to define mock.On call +// - agent *woodpecker.Agent +func (_e *MockClient_Expecter) AgentCreate(agent interface{}) *MockClient_AgentCreate_Call { + return &MockClient_AgentCreate_Call{Call: _e.mock.On("AgentCreate", agent)} +} + +func (_c *MockClient_AgentCreate_Call) Run(run func(agent *woodpecker.Agent)) *MockClient_AgentCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.Agent + if args[0] != nil { + arg0 = args[0].(*woodpecker.Agent) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_AgentCreate_Call) Return(agent1 *woodpecker.Agent, err error) *MockClient_AgentCreate_Call { + _c.Call.Return(agent1, err) + return _c +} + +func (_c *MockClient_AgentCreate_Call) RunAndReturn(run func(agent *woodpecker.Agent) (*woodpecker.Agent, error)) *MockClient_AgentCreate_Call { + _c.Call.Return(run) + return _c +} + +// AgentDelete provides a mock function for the type MockClient +func (_mock *MockClient) AgentDelete(n int64) error { + ret := _mock.Called(n) + + if len(ret) == 0 { + panic("no return value specified for AgentDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64) error); ok { + r0 = returnFunc(n) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_AgentDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AgentDelete' +type MockClient_AgentDelete_Call struct { + *mock.Call +} + +// AgentDelete is a helper method to define mock.On call +// - n int64 +func (_e *MockClient_Expecter) AgentDelete(n interface{}) *MockClient_AgentDelete_Call { + return &MockClient_AgentDelete_Call{Call: _e.mock.On("AgentDelete", n)} +} + +func (_c *MockClient_AgentDelete_Call) Run(run func(n int64)) *MockClient_AgentDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_AgentDelete_Call) Return(err error) *MockClient_AgentDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_AgentDelete_Call) RunAndReturn(run func(n int64) error) *MockClient_AgentDelete_Call { + _c.Call.Return(run) + return _c +} + +// AgentList provides a mock function for the type MockClient +func (_mock *MockClient) AgentList() ([]*woodpecker.Agent, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for AgentList") + } + + var r0 []*woodpecker.Agent + var r1 error + if returnFunc, ok := ret.Get(0).(func() ([]*woodpecker.Agent, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() []*woodpecker.Agent); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Agent) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_AgentList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AgentList' +type MockClient_AgentList_Call struct { + *mock.Call +} + +// AgentList is a helper method to define mock.On call +func (_e *MockClient_Expecter) AgentList() *MockClient_AgentList_Call { + return &MockClient_AgentList_Call{Call: _e.mock.On("AgentList")} +} + +func (_c *MockClient_AgentList_Call) Run(run func()) *MockClient_AgentList_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_AgentList_Call) Return(agents []*woodpecker.Agent, err error) *MockClient_AgentList_Call { + _c.Call.Return(agents, err) + return _c +} + +func (_c *MockClient_AgentList_Call) RunAndReturn(run func() ([]*woodpecker.Agent, error)) *MockClient_AgentList_Call { + _c.Call.Return(run) + return _c +} + +// AgentTasksList provides a mock function for the type MockClient +func (_mock *MockClient) AgentTasksList(n int64) ([]*woodpecker.Task, error) { + ret := _mock.Called(n) + + if len(ret) == 0 { + panic("no return value specified for AgentTasksList") + } + + var r0 []*woodpecker.Task + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64) ([]*woodpecker.Task, error)); ok { + return returnFunc(n) + } + if returnFunc, ok := ret.Get(0).(func(int64) []*woodpecker.Task); ok { + r0 = returnFunc(n) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Task) + } + } + if returnFunc, ok := ret.Get(1).(func(int64) error); ok { + r1 = returnFunc(n) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_AgentTasksList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AgentTasksList' +type MockClient_AgentTasksList_Call struct { + *mock.Call +} + +// AgentTasksList is a helper method to define mock.On call +// - n int64 +func (_e *MockClient_Expecter) AgentTasksList(n interface{}) *MockClient_AgentTasksList_Call { + return &MockClient_AgentTasksList_Call{Call: _e.mock.On("AgentTasksList", n)} +} + +func (_c *MockClient_AgentTasksList_Call) Run(run func(n int64)) *MockClient_AgentTasksList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_AgentTasksList_Call) Return(tasks []*woodpecker.Task, err error) *MockClient_AgentTasksList_Call { + _c.Call.Return(tasks, err) + return _c +} + +func (_c *MockClient_AgentTasksList_Call) RunAndReturn(run func(n int64) ([]*woodpecker.Task, error)) *MockClient_AgentTasksList_Call { + _c.Call.Return(run) + return _c +} + +// AgentUpdate provides a mock function for the type MockClient +func (_mock *MockClient) AgentUpdate(agent *woodpecker.Agent) (*woodpecker.Agent, error) { + ret := _mock.Called(agent) + + if len(ret) == 0 { + panic("no return value specified for AgentUpdate") + } + + var r0 *woodpecker.Agent + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Agent) (*woodpecker.Agent, error)); ok { + return returnFunc(agent) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Agent) *woodpecker.Agent); ok { + r0 = returnFunc(agent) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Agent) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.Agent) error); ok { + r1 = returnFunc(agent) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_AgentUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AgentUpdate' +type MockClient_AgentUpdate_Call struct { + *mock.Call +} + +// AgentUpdate is a helper method to define mock.On call +// - agent *woodpecker.Agent +func (_e *MockClient_Expecter) AgentUpdate(agent interface{}) *MockClient_AgentUpdate_Call { + return &MockClient_AgentUpdate_Call{Call: _e.mock.On("AgentUpdate", agent)} +} + +func (_c *MockClient_AgentUpdate_Call) Run(run func(agent *woodpecker.Agent)) *MockClient_AgentUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.Agent + if args[0] != nil { + arg0 = args[0].(*woodpecker.Agent) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_AgentUpdate_Call) Return(agent1 *woodpecker.Agent, err error) *MockClient_AgentUpdate_Call { + _c.Call.Return(agent1, err) + return _c +} + +func (_c *MockClient_AgentUpdate_Call) RunAndReturn(run func(agent *woodpecker.Agent) (*woodpecker.Agent, error)) *MockClient_AgentUpdate_Call { + _c.Call.Return(run) + return _c +} + +// CronCreate provides a mock function for the type MockClient +func (_mock *MockClient) CronCreate(repoID int64, cron *woodpecker.Cron) (*woodpecker.Cron, error) { + ret := _mock.Called(repoID, cron) + + if len(ret) == 0 { + panic("no return value specified for CronCreate") + } + + var r0 *woodpecker.Cron + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Cron) (*woodpecker.Cron, error)); ok { + return returnFunc(repoID, cron) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Cron) *woodpecker.Cron); ok { + r0 = returnFunc(repoID, cron) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Cron) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Cron) error); ok { + r1 = returnFunc(repoID, cron) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_CronCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CronCreate' +type MockClient_CronCreate_Call struct { + *mock.Call +} + +// CronCreate is a helper method to define mock.On call +// - repoID int64 +// - cron *woodpecker.Cron +func (_e *MockClient_Expecter) CronCreate(repoID interface{}, cron interface{}) *MockClient_CronCreate_Call { + return &MockClient_CronCreate_Call{Call: _e.mock.On("CronCreate", repoID, cron)} +} + +func (_c *MockClient_CronCreate_Call) Run(run func(repoID int64, cron *woodpecker.Cron)) *MockClient_CronCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Cron + if args[1] != nil { + arg1 = args[1].(*woodpecker.Cron) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_CronCreate_Call) Return(cron1 *woodpecker.Cron, err error) *MockClient_CronCreate_Call { + _c.Call.Return(cron1, err) + return _c +} + +func (_c *MockClient_CronCreate_Call) RunAndReturn(run func(repoID int64, cron *woodpecker.Cron) (*woodpecker.Cron, error)) *MockClient_CronCreate_Call { + _c.Call.Return(run) + return _c +} + +// CronDelete provides a mock function for the type MockClient +func (_mock *MockClient) CronDelete(repoID int64, cronID int64) error { + ret := _mock.Called(repoID, cronID) + + if len(ret) == 0 { + panic("no return value specified for CronDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) error); ok { + r0 = returnFunc(repoID, cronID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_CronDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CronDelete' +type MockClient_CronDelete_Call struct { + *mock.Call +} + +// CronDelete is a helper method to define mock.On call +// - repoID int64 +// - cronID int64 +func (_e *MockClient_Expecter) CronDelete(repoID interface{}, cronID interface{}) *MockClient_CronDelete_Call { + return &MockClient_CronDelete_Call{Call: _e.mock.On("CronDelete", repoID, cronID)} +} + +func (_c *MockClient_CronDelete_Call) Run(run func(repoID int64, cronID int64)) *MockClient_CronDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_CronDelete_Call) Return(err error) *MockClient_CronDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_CronDelete_Call) RunAndReturn(run func(repoID int64, cronID int64) error) *MockClient_CronDelete_Call { + _c.Call.Return(run) + return _c +} + +// CronGet provides a mock function for the type MockClient +func (_mock *MockClient) CronGet(repoID int64, cronID int64) (*woodpecker.Cron, error) { + ret := _mock.Called(repoID, cronID) + + if len(ret) == 0 { + panic("no return value specified for CronGet") + } + + var r0 *woodpecker.Cron + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) (*woodpecker.Cron, error)); ok { + return returnFunc(repoID, cronID) + } + if returnFunc, ok := ret.Get(0).(func(int64, int64) *woodpecker.Cron); ok { + r0 = returnFunc(repoID, cronID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Cron) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int64) error); ok { + r1 = returnFunc(repoID, cronID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_CronGet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CronGet' +type MockClient_CronGet_Call struct { + *mock.Call +} + +// CronGet is a helper method to define mock.On call +// - repoID int64 +// - cronID int64 +func (_e *MockClient_Expecter) CronGet(repoID interface{}, cronID interface{}) *MockClient_CronGet_Call { + return &MockClient_CronGet_Call{Call: _e.mock.On("CronGet", repoID, cronID)} +} + +func (_c *MockClient_CronGet_Call) Run(run func(repoID int64, cronID int64)) *MockClient_CronGet_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_CronGet_Call) Return(cron *woodpecker.Cron, err error) *MockClient_CronGet_Call { + _c.Call.Return(cron, err) + return _c +} + +func (_c *MockClient_CronGet_Call) RunAndReturn(run func(repoID int64, cronID int64) (*woodpecker.Cron, error)) *MockClient_CronGet_Call { + _c.Call.Return(run) + return _c +} + +// CronList provides a mock function for the type MockClient +func (_mock *MockClient) CronList(repoID int64, opt woodpecker.CronListOptions) ([]*woodpecker.Cron, error) { + ret := _mock.Called(repoID, opt) + + if len(ret) == 0 { + panic("no return value specified for CronList") + } + + var r0 []*woodpecker.Cron + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.CronListOptions) ([]*woodpecker.Cron, error)); ok { + return returnFunc(repoID, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.CronListOptions) []*woodpecker.Cron); ok { + r0 = returnFunc(repoID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Cron) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, woodpecker.CronListOptions) error); ok { + r1 = returnFunc(repoID, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_CronList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CronList' +type MockClient_CronList_Call struct { + *mock.Call +} + +// CronList is a helper method to define mock.On call +// - repoID int64 +// - opt woodpecker.CronListOptions +func (_e *MockClient_Expecter) CronList(repoID interface{}, opt interface{}) *MockClient_CronList_Call { + return &MockClient_CronList_Call{Call: _e.mock.On("CronList", repoID, opt)} +} + +func (_c *MockClient_CronList_Call) Run(run func(repoID int64, opt woodpecker.CronListOptions)) *MockClient_CronList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.CronListOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.CronListOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_CronList_Call) Return(crons []*woodpecker.Cron, err error) *MockClient_CronList_Call { + _c.Call.Return(crons, err) + return _c +} + +func (_c *MockClient_CronList_Call) RunAndReturn(run func(repoID int64, opt woodpecker.CronListOptions) ([]*woodpecker.Cron, error)) *MockClient_CronList_Call { + _c.Call.Return(run) + return _c +} + +// CronUpdate provides a mock function for the type MockClient +func (_mock *MockClient) CronUpdate(repoID int64, cron *woodpecker.Cron) (*woodpecker.Cron, error) { + ret := _mock.Called(repoID, cron) + + if len(ret) == 0 { + panic("no return value specified for CronUpdate") + } + + var r0 *woodpecker.Cron + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Cron) (*woodpecker.Cron, error)); ok { + return returnFunc(repoID, cron) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Cron) *woodpecker.Cron); ok { + r0 = returnFunc(repoID, cron) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Cron) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Cron) error); ok { + r1 = returnFunc(repoID, cron) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_CronUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CronUpdate' +type MockClient_CronUpdate_Call struct { + *mock.Call +} + +// CronUpdate is a helper method to define mock.On call +// - repoID int64 +// - cron *woodpecker.Cron +func (_e *MockClient_Expecter) CronUpdate(repoID interface{}, cron interface{}) *MockClient_CronUpdate_Call { + return &MockClient_CronUpdate_Call{Call: _e.mock.On("CronUpdate", repoID, cron)} +} + +func (_c *MockClient_CronUpdate_Call) Run(run func(repoID int64, cron *woodpecker.Cron)) *MockClient_CronUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Cron + if args[1] != nil { + arg1 = args[1].(*woodpecker.Cron) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_CronUpdate_Call) Return(cron1 *woodpecker.Cron, err error) *MockClient_CronUpdate_Call { + _c.Call.Return(cron1, err) + return _c +} + +func (_c *MockClient_CronUpdate_Call) RunAndReturn(run func(repoID int64, cron *woodpecker.Cron) (*woodpecker.Cron, error)) *MockClient_CronUpdate_Call { + _c.Call.Return(run) + return _c +} + +// Deploy provides a mock function for the type MockClient +func (_mock *MockClient) Deploy(repoID int64, pipeline int64, opt woodpecker.DeployOptions) (*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, pipeline, opt) + + if len(ret) == 0 { + panic("no return value specified for Deploy") + } + + var r0 *woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int64, woodpecker.DeployOptions) (*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, pipeline, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, int64, woodpecker.DeployOptions) *woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, pipeline, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int64, woodpecker.DeployOptions) error); ok { + r1 = returnFunc(repoID, pipeline, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Deploy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Deploy' +type MockClient_Deploy_Call struct { + *mock.Call +} + +// Deploy is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +// - opt woodpecker.DeployOptions +func (_e *MockClient_Expecter) Deploy(repoID interface{}, pipeline interface{}, opt interface{}) *MockClient_Deploy_Call { + return &MockClient_Deploy_Call{Call: _e.mock.On("Deploy", repoID, pipeline, opt)} +} + +func (_c *MockClient_Deploy_Call) Run(run func(repoID int64, pipeline int64, opt woodpecker.DeployOptions)) *MockClient_Deploy_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + var arg2 woodpecker.DeployOptions + if args[2] != nil { + arg2 = args[2].(woodpecker.DeployOptions) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockClient_Deploy_Call) Return(pipeline1 *woodpecker.Pipeline, err error) *MockClient_Deploy_Call { + _c.Call.Return(pipeline1, err) + return _c +} + +func (_c *MockClient_Deploy_Call) RunAndReturn(run func(repoID int64, pipeline int64, opt woodpecker.DeployOptions) (*woodpecker.Pipeline, error)) *MockClient_Deploy_Call { + _c.Call.Return(run) + return _c +} + +// GlobalRegistry provides a mock function for the type MockClient +func (_mock *MockClient) GlobalRegistry(registry string) (*woodpecker.Registry, error) { + ret := _mock.Called(registry) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistry") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(string) (*woodpecker.Registry, error)); ok { + return returnFunc(registry) + } + if returnFunc, ok := ret.Get(0).(func(string) *woodpecker.Registry); ok { + r0 = returnFunc(registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(string) error); ok { + r1 = returnFunc(registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalRegistry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalRegistry' +type MockClient_GlobalRegistry_Call struct { + *mock.Call +} + +// GlobalRegistry is a helper method to define mock.On call +// - registry string +func (_e *MockClient_Expecter) GlobalRegistry(registry interface{}) *MockClient_GlobalRegistry_Call { + return &MockClient_GlobalRegistry_Call{Call: _e.mock.On("GlobalRegistry", registry)} +} + +func (_c *MockClient_GlobalRegistry_Call) Run(run func(registry string)) *MockClient_GlobalRegistry_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalRegistry_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_GlobalRegistry_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_GlobalRegistry_Call) RunAndReturn(run func(registry string) (*woodpecker.Registry, error)) *MockClient_GlobalRegistry_Call { + _c.Call.Return(run) + return _c +} + +// GlobalRegistryCreate provides a mock function for the type MockClient +func (_mock *MockClient) GlobalRegistryCreate(registry *woodpecker.Registry) (*woodpecker.Registry, error) { + ret := _mock.Called(registry) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryCreate") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Registry) (*woodpecker.Registry, error)); ok { + return returnFunc(registry) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Registry) *woodpecker.Registry); ok { + r0 = returnFunc(registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.Registry) error); ok { + r1 = returnFunc(registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalRegistryCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalRegistryCreate' +type MockClient_GlobalRegistryCreate_Call struct { + *mock.Call +} + +// GlobalRegistryCreate is a helper method to define mock.On call +// - registry *woodpecker.Registry +func (_e *MockClient_Expecter) GlobalRegistryCreate(registry interface{}) *MockClient_GlobalRegistryCreate_Call { + return &MockClient_GlobalRegistryCreate_Call{Call: _e.mock.On("GlobalRegistryCreate", registry)} +} + +func (_c *MockClient_GlobalRegistryCreate_Call) Run(run func(registry *woodpecker.Registry)) *MockClient_GlobalRegistryCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.Registry + if args[0] != nil { + arg0 = args[0].(*woodpecker.Registry) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalRegistryCreate_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_GlobalRegistryCreate_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_GlobalRegistryCreate_Call) RunAndReturn(run func(registry *woodpecker.Registry) (*woodpecker.Registry, error)) *MockClient_GlobalRegistryCreate_Call { + _c.Call.Return(run) + return _c +} + +// GlobalRegistryDelete provides a mock function for the type MockClient +func (_mock *MockClient) GlobalRegistryDelete(registry string) error { + ret := _mock.Called(registry) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(string) error); ok { + r0 = returnFunc(registry) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_GlobalRegistryDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalRegistryDelete' +type MockClient_GlobalRegistryDelete_Call struct { + *mock.Call +} + +// GlobalRegistryDelete is a helper method to define mock.On call +// - registry string +func (_e *MockClient_Expecter) GlobalRegistryDelete(registry interface{}) *MockClient_GlobalRegistryDelete_Call { + return &MockClient_GlobalRegistryDelete_Call{Call: _e.mock.On("GlobalRegistryDelete", registry)} +} + +func (_c *MockClient_GlobalRegistryDelete_Call) Run(run func(registry string)) *MockClient_GlobalRegistryDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalRegistryDelete_Call) Return(err error) *MockClient_GlobalRegistryDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_GlobalRegistryDelete_Call) RunAndReturn(run func(registry string) error) *MockClient_GlobalRegistryDelete_Call { + _c.Call.Return(run) + return _c +} + +// GlobalRegistryList provides a mock function for the type MockClient +func (_mock *MockClient) GlobalRegistryList(opt woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error) { + ret := _mock.Called(opt) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryList") + } + + var r0 []*woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error)); ok { + return returnFunc(opt) + } + if returnFunc, ok := ret.Get(0).(func(woodpecker.RegistryListOptions) []*woodpecker.Registry); ok { + r0 = returnFunc(opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(woodpecker.RegistryListOptions) error); ok { + r1 = returnFunc(opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalRegistryList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalRegistryList' +type MockClient_GlobalRegistryList_Call struct { + *mock.Call +} + +// GlobalRegistryList is a helper method to define mock.On call +// - opt woodpecker.RegistryListOptions +func (_e *MockClient_Expecter) GlobalRegistryList(opt interface{}) *MockClient_GlobalRegistryList_Call { + return &MockClient_GlobalRegistryList_Call{Call: _e.mock.On("GlobalRegistryList", opt)} +} + +func (_c *MockClient_GlobalRegistryList_Call) Run(run func(opt woodpecker.RegistryListOptions)) *MockClient_GlobalRegistryList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 woodpecker.RegistryListOptions + if args[0] != nil { + arg0 = args[0].(woodpecker.RegistryListOptions) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalRegistryList_Call) Return(registrys []*woodpecker.Registry, err error) *MockClient_GlobalRegistryList_Call { + _c.Call.Return(registrys, err) + return _c +} + +func (_c *MockClient_GlobalRegistryList_Call) RunAndReturn(run func(opt woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error)) *MockClient_GlobalRegistryList_Call { + _c.Call.Return(run) + return _c +} + +// GlobalRegistryUpdate provides a mock function for the type MockClient +func (_mock *MockClient) GlobalRegistryUpdate(registry *woodpecker.Registry) (*woodpecker.Registry, error) { + ret := _mock.Called(registry) + + if len(ret) == 0 { + panic("no return value specified for GlobalRegistryUpdate") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Registry) (*woodpecker.Registry, error)); ok { + return returnFunc(registry) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Registry) *woodpecker.Registry); ok { + r0 = returnFunc(registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.Registry) error); ok { + r1 = returnFunc(registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalRegistryUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalRegistryUpdate' +type MockClient_GlobalRegistryUpdate_Call struct { + *mock.Call +} + +// GlobalRegistryUpdate is a helper method to define mock.On call +// - registry *woodpecker.Registry +func (_e *MockClient_Expecter) GlobalRegistryUpdate(registry interface{}) *MockClient_GlobalRegistryUpdate_Call { + return &MockClient_GlobalRegistryUpdate_Call{Call: _e.mock.On("GlobalRegistryUpdate", registry)} +} + +func (_c *MockClient_GlobalRegistryUpdate_Call) Run(run func(registry *woodpecker.Registry)) *MockClient_GlobalRegistryUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.Registry + if args[0] != nil { + arg0 = args[0].(*woodpecker.Registry) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalRegistryUpdate_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_GlobalRegistryUpdate_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_GlobalRegistryUpdate_Call) RunAndReturn(run func(registry *woodpecker.Registry) (*woodpecker.Registry, error)) *MockClient_GlobalRegistryUpdate_Call { + _c.Call.Return(run) + return _c +} + +// GlobalSecret provides a mock function for the type MockClient +func (_mock *MockClient) GlobalSecret(secret string) (*woodpecker.Secret, error) { + ret := _mock.Called(secret) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecret") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(string) (*woodpecker.Secret, error)); ok { + return returnFunc(secret) + } + if returnFunc, ok := ret.Get(0).(func(string) *woodpecker.Secret); ok { + r0 = returnFunc(secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(string) error); ok { + r1 = returnFunc(secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalSecret' +type MockClient_GlobalSecret_Call struct { + *mock.Call +} + +// GlobalSecret is a helper method to define mock.On call +// - secret string +func (_e *MockClient_Expecter) GlobalSecret(secret interface{}) *MockClient_GlobalSecret_Call { + return &MockClient_GlobalSecret_Call{Call: _e.mock.On("GlobalSecret", secret)} +} + +func (_c *MockClient_GlobalSecret_Call) Run(run func(secret string)) *MockClient_GlobalSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalSecret_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_GlobalSecret_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_GlobalSecret_Call) RunAndReturn(run func(secret string) (*woodpecker.Secret, error)) *MockClient_GlobalSecret_Call { + _c.Call.Return(run) + return _c +} + +// GlobalSecretCreate provides a mock function for the type MockClient +func (_mock *MockClient) GlobalSecretCreate(secret *woodpecker.Secret) (*woodpecker.Secret, error) { + ret := _mock.Called(secret) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretCreate") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Secret) (*woodpecker.Secret, error)); ok { + return returnFunc(secret) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Secret) *woodpecker.Secret); ok { + r0 = returnFunc(secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.Secret) error); ok { + r1 = returnFunc(secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalSecretCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalSecretCreate' +type MockClient_GlobalSecretCreate_Call struct { + *mock.Call +} + +// GlobalSecretCreate is a helper method to define mock.On call +// - secret *woodpecker.Secret +func (_e *MockClient_Expecter) GlobalSecretCreate(secret interface{}) *MockClient_GlobalSecretCreate_Call { + return &MockClient_GlobalSecretCreate_Call{Call: _e.mock.On("GlobalSecretCreate", secret)} +} + +func (_c *MockClient_GlobalSecretCreate_Call) Run(run func(secret *woodpecker.Secret)) *MockClient_GlobalSecretCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.Secret + if args[0] != nil { + arg0 = args[0].(*woodpecker.Secret) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalSecretCreate_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_GlobalSecretCreate_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_GlobalSecretCreate_Call) RunAndReturn(run func(secret *woodpecker.Secret) (*woodpecker.Secret, error)) *MockClient_GlobalSecretCreate_Call { + _c.Call.Return(run) + return _c +} + +// GlobalSecretDelete provides a mock function for the type MockClient +func (_mock *MockClient) GlobalSecretDelete(secret string) error { + ret := _mock.Called(secret) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(string) error); ok { + r0 = returnFunc(secret) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_GlobalSecretDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalSecretDelete' +type MockClient_GlobalSecretDelete_Call struct { + *mock.Call +} + +// GlobalSecretDelete is a helper method to define mock.On call +// - secret string +func (_e *MockClient_Expecter) GlobalSecretDelete(secret interface{}) *MockClient_GlobalSecretDelete_Call { + return &MockClient_GlobalSecretDelete_Call{Call: _e.mock.On("GlobalSecretDelete", secret)} +} + +func (_c *MockClient_GlobalSecretDelete_Call) Run(run func(secret string)) *MockClient_GlobalSecretDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalSecretDelete_Call) Return(err error) *MockClient_GlobalSecretDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_GlobalSecretDelete_Call) RunAndReturn(run func(secret string) error) *MockClient_GlobalSecretDelete_Call { + _c.Call.Return(run) + return _c +} + +// GlobalSecretList provides a mock function for the type MockClient +func (_mock *MockClient) GlobalSecretList(opt woodpecker.SecretListOptions) ([]*woodpecker.Secret, error) { + ret := _mock.Called(opt) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretList") + } + + var r0 []*woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(woodpecker.SecretListOptions) ([]*woodpecker.Secret, error)); ok { + return returnFunc(opt) + } + if returnFunc, ok := ret.Get(0).(func(woodpecker.SecretListOptions) []*woodpecker.Secret); ok { + r0 = returnFunc(opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(woodpecker.SecretListOptions) error); ok { + r1 = returnFunc(opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalSecretList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalSecretList' +type MockClient_GlobalSecretList_Call struct { + *mock.Call +} + +// GlobalSecretList is a helper method to define mock.On call +// - opt woodpecker.SecretListOptions +func (_e *MockClient_Expecter) GlobalSecretList(opt interface{}) *MockClient_GlobalSecretList_Call { + return &MockClient_GlobalSecretList_Call{Call: _e.mock.On("GlobalSecretList", opt)} +} + +func (_c *MockClient_GlobalSecretList_Call) Run(run func(opt woodpecker.SecretListOptions)) *MockClient_GlobalSecretList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 woodpecker.SecretListOptions + if args[0] != nil { + arg0 = args[0].(woodpecker.SecretListOptions) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalSecretList_Call) Return(secrets []*woodpecker.Secret, err error) *MockClient_GlobalSecretList_Call { + _c.Call.Return(secrets, err) + return _c +} + +func (_c *MockClient_GlobalSecretList_Call) RunAndReturn(run func(opt woodpecker.SecretListOptions) ([]*woodpecker.Secret, error)) *MockClient_GlobalSecretList_Call { + _c.Call.Return(run) + return _c +} + +// GlobalSecretUpdate provides a mock function for the type MockClient +func (_mock *MockClient) GlobalSecretUpdate(secret *woodpecker.Secret) (*woodpecker.Secret, error) { + ret := _mock.Called(secret) + + if len(ret) == 0 { + panic("no return value specified for GlobalSecretUpdate") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Secret) (*woodpecker.Secret, error)); ok { + return returnFunc(secret) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.Secret) *woodpecker.Secret); ok { + r0 = returnFunc(secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.Secret) error); ok { + r1 = returnFunc(secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GlobalSecretUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GlobalSecretUpdate' +type MockClient_GlobalSecretUpdate_Call struct { + *mock.Call +} + +// GlobalSecretUpdate is a helper method to define mock.On call +// - secret *woodpecker.Secret +func (_e *MockClient_Expecter) GlobalSecretUpdate(secret interface{}) *MockClient_GlobalSecretUpdate_Call { + return &MockClient_GlobalSecretUpdate_Call{Call: _e.mock.On("GlobalSecretUpdate", secret)} +} + +func (_c *MockClient_GlobalSecretUpdate_Call) Run(run func(secret *woodpecker.Secret)) *MockClient_GlobalSecretUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.Secret + if args[0] != nil { + arg0 = args[0].(*woodpecker.Secret) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_GlobalSecretUpdate_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_GlobalSecretUpdate_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_GlobalSecretUpdate_Call) RunAndReturn(run func(secret *woodpecker.Secret) (*woodpecker.Secret, error)) *MockClient_GlobalSecretUpdate_Call { + _c.Call.Return(run) + return _c +} + +// LogLevel provides a mock function for the type MockClient +func (_mock *MockClient) LogLevel() (*woodpecker.LogLevel, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for LogLevel") + } + + var r0 *woodpecker.LogLevel + var r1 error + if returnFunc, ok := ret.Get(0).(func() (*woodpecker.LogLevel, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() *woodpecker.LogLevel); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.LogLevel) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_LogLevel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LogLevel' +type MockClient_LogLevel_Call struct { + *mock.Call +} + +// LogLevel is a helper method to define mock.On call +func (_e *MockClient_Expecter) LogLevel() *MockClient_LogLevel_Call { + return &MockClient_LogLevel_Call{Call: _e.mock.On("LogLevel")} +} + +func (_c *MockClient_LogLevel_Call) Run(run func()) *MockClient_LogLevel_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_LogLevel_Call) Return(logLevel *woodpecker.LogLevel, err error) *MockClient_LogLevel_Call { + _c.Call.Return(logLevel, err) + return _c +} + +func (_c *MockClient_LogLevel_Call) RunAndReturn(run func() (*woodpecker.LogLevel, error)) *MockClient_LogLevel_Call { + _c.Call.Return(run) + return _c +} + +// LogsPurge provides a mock function for the type MockClient +func (_mock *MockClient) LogsPurge(repoID int64, pipeline int64) error { + ret := _mock.Called(repoID, pipeline) + + if len(ret) == 0 { + panic("no return value specified for LogsPurge") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) error); ok { + r0 = returnFunc(repoID, pipeline) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_LogsPurge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LogsPurge' +type MockClient_LogsPurge_Call struct { + *mock.Call +} + +// LogsPurge is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +func (_e *MockClient_Expecter) LogsPurge(repoID interface{}, pipeline interface{}) *MockClient_LogsPurge_Call { + return &MockClient_LogsPurge_Call{Call: _e.mock.On("LogsPurge", repoID, pipeline)} +} + +func (_c *MockClient_LogsPurge_Call) Run(run func(repoID int64, pipeline int64)) *MockClient_LogsPurge_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_LogsPurge_Call) Return(err error) *MockClient_LogsPurge_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_LogsPurge_Call) RunAndReturn(run func(repoID int64, pipeline int64) error) *MockClient_LogsPurge_Call { + _c.Call.Return(run) + return _c +} + +// Org provides a mock function for the type MockClient +func (_mock *MockClient) Org(orgID int64) (*woodpecker.Org, error) { + ret := _mock.Called(orgID) + + if len(ret) == 0 { + panic("no return value specified for Org") + } + + var r0 *woodpecker.Org + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64) (*woodpecker.Org, error)); ok { + return returnFunc(orgID) + } + if returnFunc, ok := ret.Get(0).(func(int64) *woodpecker.Org); ok { + r0 = returnFunc(orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Org) + } + } + if returnFunc, ok := ret.Get(1).(func(int64) error); ok { + r1 = returnFunc(orgID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Org_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Org' +type MockClient_Org_Call struct { + *mock.Call +} + +// Org is a helper method to define mock.On call +// - orgID int64 +func (_e *MockClient_Expecter) Org(orgID interface{}) *MockClient_Org_Call { + return &MockClient_Org_Call{Call: _e.mock.On("Org", orgID)} +} + +func (_c *MockClient_Org_Call) Run(run func(orgID int64)) *MockClient_Org_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_Org_Call) Return(org *woodpecker.Org, err error) *MockClient_Org_Call { + _c.Call.Return(org, err) + return _c +} + +func (_c *MockClient_Org_Call) RunAndReturn(run func(orgID int64) (*woodpecker.Org, error)) *MockClient_Org_Call { + _c.Call.Return(run) + return _c +} + +// OrgList provides a mock function for the type MockClient +func (_mock *MockClient) OrgList(opt woodpecker.ListOptions) ([]*woodpecker.Org, error) { + ret := _mock.Called(opt) + + if len(ret) == 0 { + panic("no return value specified for OrgList") + } + + var r0 []*woodpecker.Org + var r1 error + if returnFunc, ok := ret.Get(0).(func(woodpecker.ListOptions) ([]*woodpecker.Org, error)); ok { + return returnFunc(opt) + } + if returnFunc, ok := ret.Get(0).(func(woodpecker.ListOptions) []*woodpecker.Org); ok { + r0 = returnFunc(opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Org) + } + } + if returnFunc, ok := ret.Get(1).(func(woodpecker.ListOptions) error); ok { + r1 = returnFunc(opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgList' +type MockClient_OrgList_Call struct { + *mock.Call +} + +// OrgList is a helper method to define mock.On call +// - opt woodpecker.ListOptions +func (_e *MockClient_Expecter) OrgList(opt interface{}) *MockClient_OrgList_Call { + return &MockClient_OrgList_Call{Call: _e.mock.On("OrgList", opt)} +} + +func (_c *MockClient_OrgList_Call) Run(run func(opt woodpecker.ListOptions)) *MockClient_OrgList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 woodpecker.ListOptions + if args[0] != nil { + arg0 = args[0].(woodpecker.ListOptions) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_OrgList_Call) Return(orgs []*woodpecker.Org, err error) *MockClient_OrgList_Call { + _c.Call.Return(orgs, err) + return _c +} + +func (_c *MockClient_OrgList_Call) RunAndReturn(run func(opt woodpecker.ListOptions) ([]*woodpecker.Org, error)) *MockClient_OrgList_Call { + _c.Call.Return(run) + return _c +} + +// OrgLookup provides a mock function for the type MockClient +func (_mock *MockClient) OrgLookup(orgName string) (*woodpecker.Org, error) { + ret := _mock.Called(orgName) + + if len(ret) == 0 { + panic("no return value specified for OrgLookup") + } + + var r0 *woodpecker.Org + var r1 error + if returnFunc, ok := ret.Get(0).(func(string) (*woodpecker.Org, error)); ok { + return returnFunc(orgName) + } + if returnFunc, ok := ret.Get(0).(func(string) *woodpecker.Org); ok { + r0 = returnFunc(orgName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Org) + } + } + if returnFunc, ok := ret.Get(1).(func(string) error); ok { + r1 = returnFunc(orgName) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgLookup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgLookup' +type MockClient_OrgLookup_Call struct { + *mock.Call +} + +// OrgLookup is a helper method to define mock.On call +// - orgName string +func (_e *MockClient_Expecter) OrgLookup(orgName interface{}) *MockClient_OrgLookup_Call { + return &MockClient_OrgLookup_Call{Call: _e.mock.On("OrgLookup", orgName)} +} + +func (_c *MockClient_OrgLookup_Call) Run(run func(orgName string)) *MockClient_OrgLookup_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_OrgLookup_Call) Return(org *woodpecker.Org, err error) *MockClient_OrgLookup_Call { + _c.Call.Return(org, err) + return _c +} + +func (_c *MockClient_OrgLookup_Call) RunAndReturn(run func(orgName string) (*woodpecker.Org, error)) *MockClient_OrgLookup_Call { + _c.Call.Return(run) + return _c +} + +// OrgRegistry provides a mock function for the type MockClient +func (_mock *MockClient) OrgRegistry(orgID int64, registry string) (*woodpecker.Registry, error) { + ret := _mock.Called(orgID, registry) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistry") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, string) (*woodpecker.Registry, error)); ok { + return returnFunc(orgID, registry) + } + if returnFunc, ok := ret.Get(0).(func(int64, string) *woodpecker.Registry); ok { + r0 = returnFunc(orgID, registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, string) error); ok { + r1 = returnFunc(orgID, registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgRegistry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgRegistry' +type MockClient_OrgRegistry_Call struct { + *mock.Call +} + +// OrgRegistry is a helper method to define mock.On call +// - orgID int64 +// - registry string +func (_e *MockClient_Expecter) OrgRegistry(orgID interface{}, registry interface{}) *MockClient_OrgRegistry_Call { + return &MockClient_OrgRegistry_Call{Call: _e.mock.On("OrgRegistry", orgID, registry)} +} + +func (_c *MockClient_OrgRegistry_Call) Run(run func(orgID int64, registry string)) *MockClient_OrgRegistry_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgRegistry_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_OrgRegistry_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_OrgRegistry_Call) RunAndReturn(run func(orgID int64, registry string) (*woodpecker.Registry, error)) *MockClient_OrgRegistry_Call { + _c.Call.Return(run) + return _c +} + +// OrgRegistryCreate provides a mock function for the type MockClient +func (_mock *MockClient) OrgRegistryCreate(orgID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error) { + ret := _mock.Called(orgID, registry) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryCreate") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) (*woodpecker.Registry, error)); ok { + return returnFunc(orgID, registry) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) *woodpecker.Registry); ok { + r0 = returnFunc(orgID, registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Registry) error); ok { + r1 = returnFunc(orgID, registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgRegistryCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgRegistryCreate' +type MockClient_OrgRegistryCreate_Call struct { + *mock.Call +} + +// OrgRegistryCreate is a helper method to define mock.On call +// - orgID int64 +// - registry *woodpecker.Registry +func (_e *MockClient_Expecter) OrgRegistryCreate(orgID interface{}, registry interface{}) *MockClient_OrgRegistryCreate_Call { + return &MockClient_OrgRegistryCreate_Call{Call: _e.mock.On("OrgRegistryCreate", orgID, registry)} +} + +func (_c *MockClient_OrgRegistryCreate_Call) Run(run func(orgID int64, registry *woodpecker.Registry)) *MockClient_OrgRegistryCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Registry + if args[1] != nil { + arg1 = args[1].(*woodpecker.Registry) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgRegistryCreate_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_OrgRegistryCreate_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_OrgRegistryCreate_Call) RunAndReturn(run func(orgID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error)) *MockClient_OrgRegistryCreate_Call { + _c.Call.Return(run) + return _c +} + +// OrgRegistryDelete provides a mock function for the type MockClient +func (_mock *MockClient) OrgRegistryDelete(orgID int64, registry string) error { + ret := _mock.Called(orgID, registry) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, string) error); ok { + r0 = returnFunc(orgID, registry) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_OrgRegistryDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgRegistryDelete' +type MockClient_OrgRegistryDelete_Call struct { + *mock.Call +} + +// OrgRegistryDelete is a helper method to define mock.On call +// - orgID int64 +// - registry string +func (_e *MockClient_Expecter) OrgRegistryDelete(orgID interface{}, registry interface{}) *MockClient_OrgRegistryDelete_Call { + return &MockClient_OrgRegistryDelete_Call{Call: _e.mock.On("OrgRegistryDelete", orgID, registry)} +} + +func (_c *MockClient_OrgRegistryDelete_Call) Run(run func(orgID int64, registry string)) *MockClient_OrgRegistryDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgRegistryDelete_Call) Return(err error) *MockClient_OrgRegistryDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_OrgRegistryDelete_Call) RunAndReturn(run func(orgID int64, registry string) error) *MockClient_OrgRegistryDelete_Call { + _c.Call.Return(run) + return _c +} + +// OrgRegistryList provides a mock function for the type MockClient +func (_mock *MockClient) OrgRegistryList(orgID int64, opt woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error) { + ret := _mock.Called(orgID, opt) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryList") + } + + var r0 []*woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error)); ok { + return returnFunc(orgID, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.RegistryListOptions) []*woodpecker.Registry); ok { + r0 = returnFunc(orgID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, woodpecker.RegistryListOptions) error); ok { + r1 = returnFunc(orgID, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgRegistryList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgRegistryList' +type MockClient_OrgRegistryList_Call struct { + *mock.Call +} + +// OrgRegistryList is a helper method to define mock.On call +// - orgID int64 +// - opt woodpecker.RegistryListOptions +func (_e *MockClient_Expecter) OrgRegistryList(orgID interface{}, opt interface{}) *MockClient_OrgRegistryList_Call { + return &MockClient_OrgRegistryList_Call{Call: _e.mock.On("OrgRegistryList", orgID, opt)} +} + +func (_c *MockClient_OrgRegistryList_Call) Run(run func(orgID int64, opt woodpecker.RegistryListOptions)) *MockClient_OrgRegistryList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.RegistryListOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.RegistryListOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgRegistryList_Call) Return(registrys []*woodpecker.Registry, err error) *MockClient_OrgRegistryList_Call { + _c.Call.Return(registrys, err) + return _c +} + +func (_c *MockClient_OrgRegistryList_Call) RunAndReturn(run func(orgID int64, opt woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error)) *MockClient_OrgRegistryList_Call { + _c.Call.Return(run) + return _c +} + +// OrgRegistryUpdate provides a mock function for the type MockClient +func (_mock *MockClient) OrgRegistryUpdate(orgID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error) { + ret := _mock.Called(orgID, registry) + + if len(ret) == 0 { + panic("no return value specified for OrgRegistryUpdate") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) (*woodpecker.Registry, error)); ok { + return returnFunc(orgID, registry) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) *woodpecker.Registry); ok { + r0 = returnFunc(orgID, registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Registry) error); ok { + r1 = returnFunc(orgID, registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgRegistryUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgRegistryUpdate' +type MockClient_OrgRegistryUpdate_Call struct { + *mock.Call +} + +// OrgRegistryUpdate is a helper method to define mock.On call +// - orgID int64 +// - registry *woodpecker.Registry +func (_e *MockClient_Expecter) OrgRegistryUpdate(orgID interface{}, registry interface{}) *MockClient_OrgRegistryUpdate_Call { + return &MockClient_OrgRegistryUpdate_Call{Call: _e.mock.On("OrgRegistryUpdate", orgID, registry)} +} + +func (_c *MockClient_OrgRegistryUpdate_Call) Run(run func(orgID int64, registry *woodpecker.Registry)) *MockClient_OrgRegistryUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Registry + if args[1] != nil { + arg1 = args[1].(*woodpecker.Registry) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgRegistryUpdate_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_OrgRegistryUpdate_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_OrgRegistryUpdate_Call) RunAndReturn(run func(orgID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error)) *MockClient_OrgRegistryUpdate_Call { + _c.Call.Return(run) + return _c +} + +// OrgSecret provides a mock function for the type MockClient +func (_mock *MockClient) OrgSecret(orgID int64, secret string) (*woodpecker.Secret, error) { + ret := _mock.Called(orgID, secret) + + if len(ret) == 0 { + panic("no return value specified for OrgSecret") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, string) (*woodpecker.Secret, error)); ok { + return returnFunc(orgID, secret) + } + if returnFunc, ok := ret.Get(0).(func(int64, string) *woodpecker.Secret); ok { + r0 = returnFunc(orgID, secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, string) error); ok { + r1 = returnFunc(orgID, secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgSecret' +type MockClient_OrgSecret_Call struct { + *mock.Call +} + +// OrgSecret is a helper method to define mock.On call +// - orgID int64 +// - secret string +func (_e *MockClient_Expecter) OrgSecret(orgID interface{}, secret interface{}) *MockClient_OrgSecret_Call { + return &MockClient_OrgSecret_Call{Call: _e.mock.On("OrgSecret", orgID, secret)} +} + +func (_c *MockClient_OrgSecret_Call) Run(run func(orgID int64, secret string)) *MockClient_OrgSecret_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgSecret_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_OrgSecret_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_OrgSecret_Call) RunAndReturn(run func(orgID int64, secret string) (*woodpecker.Secret, error)) *MockClient_OrgSecret_Call { + _c.Call.Return(run) + return _c +} + +// OrgSecretCreate provides a mock function for the type MockClient +func (_mock *MockClient) OrgSecretCreate(orgID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error) { + ret := _mock.Called(orgID, secret) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretCreate") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) (*woodpecker.Secret, error)); ok { + return returnFunc(orgID, secret) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) *woodpecker.Secret); ok { + r0 = returnFunc(orgID, secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Secret) error); ok { + r1 = returnFunc(orgID, secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgSecretCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgSecretCreate' +type MockClient_OrgSecretCreate_Call struct { + *mock.Call +} + +// OrgSecretCreate is a helper method to define mock.On call +// - orgID int64 +// - secret *woodpecker.Secret +func (_e *MockClient_Expecter) OrgSecretCreate(orgID interface{}, secret interface{}) *MockClient_OrgSecretCreate_Call { + return &MockClient_OrgSecretCreate_Call{Call: _e.mock.On("OrgSecretCreate", orgID, secret)} +} + +func (_c *MockClient_OrgSecretCreate_Call) Run(run func(orgID int64, secret *woodpecker.Secret)) *MockClient_OrgSecretCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Secret + if args[1] != nil { + arg1 = args[1].(*woodpecker.Secret) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgSecretCreate_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_OrgSecretCreate_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_OrgSecretCreate_Call) RunAndReturn(run func(orgID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error)) *MockClient_OrgSecretCreate_Call { + _c.Call.Return(run) + return _c +} + +// OrgSecretDelete provides a mock function for the type MockClient +func (_mock *MockClient) OrgSecretDelete(orgID int64, secret string) error { + ret := _mock.Called(orgID, secret) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, string) error); ok { + r0 = returnFunc(orgID, secret) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_OrgSecretDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgSecretDelete' +type MockClient_OrgSecretDelete_Call struct { + *mock.Call +} + +// OrgSecretDelete is a helper method to define mock.On call +// - orgID int64 +// - secret string +func (_e *MockClient_Expecter) OrgSecretDelete(orgID interface{}, secret interface{}) *MockClient_OrgSecretDelete_Call { + return &MockClient_OrgSecretDelete_Call{Call: _e.mock.On("OrgSecretDelete", orgID, secret)} +} + +func (_c *MockClient_OrgSecretDelete_Call) Run(run func(orgID int64, secret string)) *MockClient_OrgSecretDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgSecretDelete_Call) Return(err error) *MockClient_OrgSecretDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_OrgSecretDelete_Call) RunAndReturn(run func(orgID int64, secret string) error) *MockClient_OrgSecretDelete_Call { + _c.Call.Return(run) + return _c +} + +// OrgSecretList provides a mock function for the type MockClient +func (_mock *MockClient) OrgSecretList(orgID int64, opt woodpecker.SecretListOptions) ([]*woodpecker.Secret, error) { + ret := _mock.Called(orgID, opt) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretList") + } + + var r0 []*woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.SecretListOptions) ([]*woodpecker.Secret, error)); ok { + return returnFunc(orgID, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.SecretListOptions) []*woodpecker.Secret); ok { + r0 = returnFunc(orgID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, woodpecker.SecretListOptions) error); ok { + r1 = returnFunc(orgID, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgSecretList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgSecretList' +type MockClient_OrgSecretList_Call struct { + *mock.Call +} + +// OrgSecretList is a helper method to define mock.On call +// - orgID int64 +// - opt woodpecker.SecretListOptions +func (_e *MockClient_Expecter) OrgSecretList(orgID interface{}, opt interface{}) *MockClient_OrgSecretList_Call { + return &MockClient_OrgSecretList_Call{Call: _e.mock.On("OrgSecretList", orgID, opt)} +} + +func (_c *MockClient_OrgSecretList_Call) Run(run func(orgID int64, opt woodpecker.SecretListOptions)) *MockClient_OrgSecretList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.SecretListOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.SecretListOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgSecretList_Call) Return(secrets []*woodpecker.Secret, err error) *MockClient_OrgSecretList_Call { + _c.Call.Return(secrets, err) + return _c +} + +func (_c *MockClient_OrgSecretList_Call) RunAndReturn(run func(orgID int64, opt woodpecker.SecretListOptions) ([]*woodpecker.Secret, error)) *MockClient_OrgSecretList_Call { + _c.Call.Return(run) + return _c +} + +// OrgSecretUpdate provides a mock function for the type MockClient +func (_mock *MockClient) OrgSecretUpdate(orgID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error) { + ret := _mock.Called(orgID, secret) + + if len(ret) == 0 { + panic("no return value specified for OrgSecretUpdate") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) (*woodpecker.Secret, error)); ok { + return returnFunc(orgID, secret) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) *woodpecker.Secret); ok { + r0 = returnFunc(orgID, secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Secret) error); ok { + r1 = returnFunc(orgID, secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_OrgSecretUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OrgSecretUpdate' +type MockClient_OrgSecretUpdate_Call struct { + *mock.Call +} + +// OrgSecretUpdate is a helper method to define mock.On call +// - orgID int64 +// - secret *woodpecker.Secret +func (_e *MockClient_Expecter) OrgSecretUpdate(orgID interface{}, secret interface{}) *MockClient_OrgSecretUpdate_Call { + return &MockClient_OrgSecretUpdate_Call{Call: _e.mock.On("OrgSecretUpdate", orgID, secret)} +} + +func (_c *MockClient_OrgSecretUpdate_Call) Run(run func(orgID int64, secret *woodpecker.Secret)) *MockClient_OrgSecretUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Secret + if args[1] != nil { + arg1 = args[1].(*woodpecker.Secret) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_OrgSecretUpdate_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_OrgSecretUpdate_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_OrgSecretUpdate_Call) RunAndReturn(run func(orgID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error)) *MockClient_OrgSecretUpdate_Call { + _c.Call.Return(run) + return _c +} + +// Pipeline provides a mock function for the type MockClient +func (_mock *MockClient) Pipeline(repoID int64, pipeline int64) (*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, pipeline) + + if len(ret) == 0 { + panic("no return value specified for Pipeline") + } + + var r0 *woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) (*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, pipeline) + } + if returnFunc, ok := ret.Get(0).(func(int64, int64) *woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, pipeline) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int64) error); ok { + r1 = returnFunc(repoID, pipeline) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Pipeline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Pipeline' +type MockClient_Pipeline_Call struct { + *mock.Call +} + +// Pipeline is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +func (_e *MockClient_Expecter) Pipeline(repoID interface{}, pipeline interface{}) *MockClient_Pipeline_Call { + return &MockClient_Pipeline_Call{Call: _e.mock.On("Pipeline", repoID, pipeline)} +} + +func (_c *MockClient_Pipeline_Call) Run(run func(repoID int64, pipeline int64)) *MockClient_Pipeline_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_Pipeline_Call) Return(pipeline1 *woodpecker.Pipeline, err error) *MockClient_Pipeline_Call { + _c.Call.Return(pipeline1, err) + return _c +} + +func (_c *MockClient_Pipeline_Call) RunAndReturn(run func(repoID int64, pipeline int64) (*woodpecker.Pipeline, error)) *MockClient_Pipeline_Call { + _c.Call.Return(run) + return _c +} + +// PipelineApprove provides a mock function for the type MockClient +func (_mock *MockClient) PipelineApprove(repoID int64, pipeline int64) (*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, pipeline) + + if len(ret) == 0 { + panic("no return value specified for PipelineApprove") + } + + var r0 *woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) (*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, pipeline) + } + if returnFunc, ok := ret.Get(0).(func(int64, int64) *woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, pipeline) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int64) error); ok { + r1 = returnFunc(repoID, pipeline) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineApprove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineApprove' +type MockClient_PipelineApprove_Call struct { + *mock.Call +} + +// PipelineApprove is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +func (_e *MockClient_Expecter) PipelineApprove(repoID interface{}, pipeline interface{}) *MockClient_PipelineApprove_Call { + return &MockClient_PipelineApprove_Call{Call: _e.mock.On("PipelineApprove", repoID, pipeline)} +} + +func (_c *MockClient_PipelineApprove_Call) Run(run func(repoID int64, pipeline int64)) *MockClient_PipelineApprove_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineApprove_Call) Return(pipeline1 *woodpecker.Pipeline, err error) *MockClient_PipelineApprove_Call { + _c.Call.Return(pipeline1, err) + return _c +} + +func (_c *MockClient_PipelineApprove_Call) RunAndReturn(run func(repoID int64, pipeline int64) (*woodpecker.Pipeline, error)) *MockClient_PipelineApprove_Call { + _c.Call.Return(run) + return _c +} + +// PipelineCreate provides a mock function for the type MockClient +func (_mock *MockClient) PipelineCreate(repoID int64, opts *woodpecker.PipelineOptions) (*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, opts) + + if len(ret) == 0 { + panic("no return value specified for PipelineCreate") + } + + var r0 *woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.PipelineOptions) (*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, opts) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.PipelineOptions) *woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.PipelineOptions) error); ok { + r1 = returnFunc(repoID, opts) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineCreate' +type MockClient_PipelineCreate_Call struct { + *mock.Call +} + +// PipelineCreate is a helper method to define mock.On call +// - repoID int64 +// - opts *woodpecker.PipelineOptions +func (_e *MockClient_Expecter) PipelineCreate(repoID interface{}, opts interface{}) *MockClient_PipelineCreate_Call { + return &MockClient_PipelineCreate_Call{Call: _e.mock.On("PipelineCreate", repoID, opts)} +} + +func (_c *MockClient_PipelineCreate_Call) Run(run func(repoID int64, opts *woodpecker.PipelineOptions)) *MockClient_PipelineCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.PipelineOptions + if args[1] != nil { + arg1 = args[1].(*woodpecker.PipelineOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineCreate_Call) Return(pipeline *woodpecker.Pipeline, err error) *MockClient_PipelineCreate_Call { + _c.Call.Return(pipeline, err) + return _c +} + +func (_c *MockClient_PipelineCreate_Call) RunAndReturn(run func(repoID int64, opts *woodpecker.PipelineOptions) (*woodpecker.Pipeline, error)) *MockClient_PipelineCreate_Call { + _c.Call.Return(run) + return _c +} + +// PipelineDecline provides a mock function for the type MockClient +func (_mock *MockClient) PipelineDecline(repoID int64, pipeline int64) (*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, pipeline) + + if len(ret) == 0 { + panic("no return value specified for PipelineDecline") + } + + var r0 *woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) (*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, pipeline) + } + if returnFunc, ok := ret.Get(0).(func(int64, int64) *woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, pipeline) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int64) error); ok { + r1 = returnFunc(repoID, pipeline) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineDecline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineDecline' +type MockClient_PipelineDecline_Call struct { + *mock.Call +} + +// PipelineDecline is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +func (_e *MockClient_Expecter) PipelineDecline(repoID interface{}, pipeline interface{}) *MockClient_PipelineDecline_Call { + return &MockClient_PipelineDecline_Call{Call: _e.mock.On("PipelineDecline", repoID, pipeline)} +} + +func (_c *MockClient_PipelineDecline_Call) Run(run func(repoID int64, pipeline int64)) *MockClient_PipelineDecline_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineDecline_Call) Return(pipeline1 *woodpecker.Pipeline, err error) *MockClient_PipelineDecline_Call { + _c.Call.Return(pipeline1, err) + return _c +} + +func (_c *MockClient_PipelineDecline_Call) RunAndReturn(run func(repoID int64, pipeline int64) (*woodpecker.Pipeline, error)) *MockClient_PipelineDecline_Call { + _c.Call.Return(run) + return _c +} + +// PipelineDelete provides a mock function for the type MockClient +func (_mock *MockClient) PipelineDelete(repoID int64, pipeline int64) error { + ret := _mock.Called(repoID, pipeline) + + if len(ret) == 0 { + panic("no return value specified for PipelineDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) error); ok { + r0 = returnFunc(repoID, pipeline) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_PipelineDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineDelete' +type MockClient_PipelineDelete_Call struct { + *mock.Call +} + +// PipelineDelete is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +func (_e *MockClient_Expecter) PipelineDelete(repoID interface{}, pipeline interface{}) *MockClient_PipelineDelete_Call { + return &MockClient_PipelineDelete_Call{Call: _e.mock.On("PipelineDelete", repoID, pipeline)} +} + +func (_c *MockClient_PipelineDelete_Call) Run(run func(repoID int64, pipeline int64)) *MockClient_PipelineDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineDelete_Call) Return(err error) *MockClient_PipelineDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_PipelineDelete_Call) RunAndReturn(run func(repoID int64, pipeline int64) error) *MockClient_PipelineDelete_Call { + _c.Call.Return(run) + return _c +} + +// PipelineLast provides a mock function for the type MockClient +func (_mock *MockClient) PipelineLast(repoID int64, opt woodpecker.PipelineLastOptions) (*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, opt) + + if len(ret) == 0 { + panic("no return value specified for PipelineLast") + } + + var r0 *woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.PipelineLastOptions) (*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.PipelineLastOptions) *woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, woodpecker.PipelineLastOptions) error); ok { + r1 = returnFunc(repoID, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineLast_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineLast' +type MockClient_PipelineLast_Call struct { + *mock.Call +} + +// PipelineLast is a helper method to define mock.On call +// - repoID int64 +// - opt woodpecker.PipelineLastOptions +func (_e *MockClient_Expecter) PipelineLast(repoID interface{}, opt interface{}) *MockClient_PipelineLast_Call { + return &MockClient_PipelineLast_Call{Call: _e.mock.On("PipelineLast", repoID, opt)} +} + +func (_c *MockClient_PipelineLast_Call) Run(run func(repoID int64, opt woodpecker.PipelineLastOptions)) *MockClient_PipelineLast_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.PipelineLastOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.PipelineLastOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineLast_Call) Return(pipeline *woodpecker.Pipeline, err error) *MockClient_PipelineLast_Call { + _c.Call.Return(pipeline, err) + return _c +} + +func (_c *MockClient_PipelineLast_Call) RunAndReturn(run func(repoID int64, opt woodpecker.PipelineLastOptions) (*woodpecker.Pipeline, error)) *MockClient_PipelineLast_Call { + _c.Call.Return(run) + return _c +} + +// PipelineList provides a mock function for the type MockClient +func (_mock *MockClient) PipelineList(repoID int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, opt) + + if len(ret) == 0 { + panic("no return value specified for PipelineList") + } + + var r0 []*woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.PipelineListOptions) []*woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, woodpecker.PipelineListOptions) error); ok { + r1 = returnFunc(repoID, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineList' +type MockClient_PipelineList_Call struct { + *mock.Call +} + +// PipelineList is a helper method to define mock.On call +// - repoID int64 +// - opt woodpecker.PipelineListOptions +func (_e *MockClient_Expecter) PipelineList(repoID interface{}, opt interface{}) *MockClient_PipelineList_Call { + return &MockClient_PipelineList_Call{Call: _e.mock.On("PipelineList", repoID, opt)} +} + +func (_c *MockClient_PipelineList_Call) Run(run func(repoID int64, opt woodpecker.PipelineListOptions)) *MockClient_PipelineList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.PipelineListOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.PipelineListOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineList_Call) Return(pipelines []*woodpecker.Pipeline, err error) *MockClient_PipelineList_Call { + _c.Call.Return(pipelines, err) + return _c +} + +func (_c *MockClient_PipelineList_Call) RunAndReturn(run func(repoID int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error)) *MockClient_PipelineList_Call { + _c.Call.Return(run) + return _c +} + +// PipelineMetadata provides a mock function for the type MockClient +func (_mock *MockClient) PipelineMetadata(repoID int64, pipelineNumber int) ([]byte, error) { + ret := _mock.Called(repoID, pipelineNumber) + + if len(ret) == 0 { + panic("no return value specified for PipelineMetadata") + } + + var r0 []byte + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int) ([]byte, error)); ok { + return returnFunc(repoID, pipelineNumber) + } + if returnFunc, ok := ret.Get(0).(func(int64, int) []byte); ok { + r0 = returnFunc(repoID, pipelineNumber) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int) error); ok { + r1 = returnFunc(repoID, pipelineNumber) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineMetadata_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineMetadata' +type MockClient_PipelineMetadata_Call struct { + *mock.Call +} + +// PipelineMetadata is a helper method to define mock.On call +// - repoID int64 +// - pipelineNumber int +func (_e *MockClient_Expecter) PipelineMetadata(repoID interface{}, pipelineNumber interface{}) *MockClient_PipelineMetadata_Call { + return &MockClient_PipelineMetadata_Call{Call: _e.mock.On("PipelineMetadata", repoID, pipelineNumber)} +} + +func (_c *MockClient_PipelineMetadata_Call) Run(run func(repoID int64, pipelineNumber int)) *MockClient_PipelineMetadata_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int + if args[1] != nil { + arg1 = args[1].(int) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineMetadata_Call) Return(bytes []byte, err error) *MockClient_PipelineMetadata_Call { + _c.Call.Return(bytes, err) + return _c +} + +func (_c *MockClient_PipelineMetadata_Call) RunAndReturn(run func(repoID int64, pipelineNumber int) ([]byte, error)) *MockClient_PipelineMetadata_Call { + _c.Call.Return(run) + return _c +} + +// PipelineQueue provides a mock function for the type MockClient +func (_mock *MockClient) PipelineQueue() ([]*woodpecker.Feed, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for PipelineQueue") + } + + var r0 []*woodpecker.Feed + var r1 error + if returnFunc, ok := ret.Get(0).(func() ([]*woodpecker.Feed, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() []*woodpecker.Feed); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Feed) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineQueue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineQueue' +type MockClient_PipelineQueue_Call struct { + *mock.Call +} + +// PipelineQueue is a helper method to define mock.On call +func (_e *MockClient_Expecter) PipelineQueue() *MockClient_PipelineQueue_Call { + return &MockClient_PipelineQueue_Call{Call: _e.mock.On("PipelineQueue")} +} + +func (_c *MockClient_PipelineQueue_Call) Run(run func()) *MockClient_PipelineQueue_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_PipelineQueue_Call) Return(feeds []*woodpecker.Feed, err error) *MockClient_PipelineQueue_Call { + _c.Call.Return(feeds, err) + return _c +} + +func (_c *MockClient_PipelineQueue_Call) RunAndReturn(run func() ([]*woodpecker.Feed, error)) *MockClient_PipelineQueue_Call { + _c.Call.Return(run) + return _c +} + +// PipelineStart provides a mock function for the type MockClient +func (_mock *MockClient) PipelineStart(repoID int64, num int64, opt woodpecker.PipelineStartOptions) (*woodpecker.Pipeline, error) { + ret := _mock.Called(repoID, num, opt) + + if len(ret) == 0 { + panic("no return value specified for PipelineStart") + } + + var r0 *woodpecker.Pipeline + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int64, woodpecker.PipelineStartOptions) (*woodpecker.Pipeline, error)); ok { + return returnFunc(repoID, num, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, int64, woodpecker.PipelineStartOptions) *woodpecker.Pipeline); ok { + r0 = returnFunc(repoID, num, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Pipeline) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int64, woodpecker.PipelineStartOptions) error); ok { + r1 = returnFunc(repoID, num, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_PipelineStart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineStart' +type MockClient_PipelineStart_Call struct { + *mock.Call +} + +// PipelineStart is a helper method to define mock.On call +// - repoID int64 +// - num int64 +// - opt woodpecker.PipelineStartOptions +func (_e *MockClient_Expecter) PipelineStart(repoID interface{}, num interface{}, opt interface{}) *MockClient_PipelineStart_Call { + return &MockClient_PipelineStart_Call{Call: _e.mock.On("PipelineStart", repoID, num, opt)} +} + +func (_c *MockClient_PipelineStart_Call) Run(run func(repoID int64, num int64, opt woodpecker.PipelineStartOptions)) *MockClient_PipelineStart_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + var arg2 woodpecker.PipelineStartOptions + if args[2] != nil { + arg2 = args[2].(woodpecker.PipelineStartOptions) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockClient_PipelineStart_Call) Return(pipeline *woodpecker.Pipeline, err error) *MockClient_PipelineStart_Call { + _c.Call.Return(pipeline, err) + return _c +} + +func (_c *MockClient_PipelineStart_Call) RunAndReturn(run func(repoID int64, num int64, opt woodpecker.PipelineStartOptions) (*woodpecker.Pipeline, error)) *MockClient_PipelineStart_Call { + _c.Call.Return(run) + return _c +} + +// PipelineStop provides a mock function for the type MockClient +func (_mock *MockClient) PipelineStop(repoID int64, pipeline int64) error { + ret := _mock.Called(repoID, pipeline) + + if len(ret) == 0 { + panic("no return value specified for PipelineStop") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, int64) error); ok { + r0 = returnFunc(repoID, pipeline) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_PipelineStop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PipelineStop' +type MockClient_PipelineStop_Call struct { + *mock.Call +} + +// PipelineStop is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +func (_e *MockClient_Expecter) PipelineStop(repoID interface{}, pipeline interface{}) *MockClient_PipelineStop_Call { + return &MockClient_PipelineStop_Call{Call: _e.mock.On("PipelineStop", repoID, pipeline)} +} + +func (_c *MockClient_PipelineStop_Call) Run(run func(repoID int64, pipeline int64)) *MockClient_PipelineStop_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_PipelineStop_Call) Return(err error) *MockClient_PipelineStop_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_PipelineStop_Call) RunAndReturn(run func(repoID int64, pipeline int64) error) *MockClient_PipelineStop_Call { + _c.Call.Return(run) + return _c +} + +// QueueInfo provides a mock function for the type MockClient +func (_mock *MockClient) QueueInfo() (*woodpecker.Info, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for QueueInfo") + } + + var r0 *woodpecker.Info + var r1 error + if returnFunc, ok := ret.Get(0).(func() (*woodpecker.Info, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() *woodpecker.Info); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Info) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_QueueInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueueInfo' +type MockClient_QueueInfo_Call struct { + *mock.Call +} + +// QueueInfo is a helper method to define mock.On call +func (_e *MockClient_Expecter) QueueInfo() *MockClient_QueueInfo_Call { + return &MockClient_QueueInfo_Call{Call: _e.mock.On("QueueInfo")} +} + +func (_c *MockClient_QueueInfo_Call) Run(run func()) *MockClient_QueueInfo_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_QueueInfo_Call) Return(info *woodpecker.Info, err error) *MockClient_QueueInfo_Call { + _c.Call.Return(info, err) + return _c +} + +func (_c *MockClient_QueueInfo_Call) RunAndReturn(run func() (*woodpecker.Info, error)) *MockClient_QueueInfo_Call { + _c.Call.Return(run) + return _c +} + +// Registry provides a mock function for the type MockClient +func (_mock *MockClient) Registry(repoID int64, hostname string) (*woodpecker.Registry, error) { + ret := _mock.Called(repoID, hostname) + + if len(ret) == 0 { + panic("no return value specified for Registry") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, string) (*woodpecker.Registry, error)); ok { + return returnFunc(repoID, hostname) + } + if returnFunc, ok := ret.Get(0).(func(int64, string) *woodpecker.Registry); ok { + r0 = returnFunc(repoID, hostname) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, string) error); ok { + r1 = returnFunc(repoID, hostname) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Registry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Registry' +type MockClient_Registry_Call struct { + *mock.Call +} + +// Registry is a helper method to define mock.On call +// - repoID int64 +// - hostname string +func (_e *MockClient_Expecter) Registry(repoID interface{}, hostname interface{}) *MockClient_Registry_Call { + return &MockClient_Registry_Call{Call: _e.mock.On("Registry", repoID, hostname)} +} + +func (_c *MockClient_Registry_Call) Run(run func(repoID int64, hostname string)) *MockClient_Registry_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_Registry_Call) Return(registry *woodpecker.Registry, err error) *MockClient_Registry_Call { + _c.Call.Return(registry, err) + return _c +} + +func (_c *MockClient_Registry_Call) RunAndReturn(run func(repoID int64, hostname string) (*woodpecker.Registry, error)) *MockClient_Registry_Call { + _c.Call.Return(run) + return _c +} + +// RegistryCreate provides a mock function for the type MockClient +func (_mock *MockClient) RegistryCreate(repoID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error) { + ret := _mock.Called(repoID, registry) + + if len(ret) == 0 { + panic("no return value specified for RegistryCreate") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) (*woodpecker.Registry, error)); ok { + return returnFunc(repoID, registry) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) *woodpecker.Registry); ok { + r0 = returnFunc(repoID, registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Registry) error); ok { + r1 = returnFunc(repoID, registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RegistryCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegistryCreate' +type MockClient_RegistryCreate_Call struct { + *mock.Call +} + +// RegistryCreate is a helper method to define mock.On call +// - repoID int64 +// - registry *woodpecker.Registry +func (_e *MockClient_Expecter) RegistryCreate(repoID interface{}, registry interface{}) *MockClient_RegistryCreate_Call { + return &MockClient_RegistryCreate_Call{Call: _e.mock.On("RegistryCreate", repoID, registry)} +} + +func (_c *MockClient_RegistryCreate_Call) Run(run func(repoID int64, registry *woodpecker.Registry)) *MockClient_RegistryCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Registry + if args[1] != nil { + arg1 = args[1].(*woodpecker.Registry) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_RegistryCreate_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_RegistryCreate_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_RegistryCreate_Call) RunAndReturn(run func(repoID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error)) *MockClient_RegistryCreate_Call { + _c.Call.Return(run) + return _c +} + +// RegistryDelete provides a mock function for the type MockClient +func (_mock *MockClient) RegistryDelete(repoID int64, hostname string) error { + ret := _mock.Called(repoID, hostname) + + if len(ret) == 0 { + panic("no return value specified for RegistryDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, string) error); ok { + r0 = returnFunc(repoID, hostname) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_RegistryDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegistryDelete' +type MockClient_RegistryDelete_Call struct { + *mock.Call +} + +// RegistryDelete is a helper method to define mock.On call +// - repoID int64 +// - hostname string +func (_e *MockClient_Expecter) RegistryDelete(repoID interface{}, hostname interface{}) *MockClient_RegistryDelete_Call { + return &MockClient_RegistryDelete_Call{Call: _e.mock.On("RegistryDelete", repoID, hostname)} +} + +func (_c *MockClient_RegistryDelete_Call) Run(run func(repoID int64, hostname string)) *MockClient_RegistryDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_RegistryDelete_Call) Return(err error) *MockClient_RegistryDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_RegistryDelete_Call) RunAndReturn(run func(repoID int64, hostname string) error) *MockClient_RegistryDelete_Call { + _c.Call.Return(run) + return _c +} + +// RegistryList provides a mock function for the type MockClient +func (_mock *MockClient) RegistryList(repoID int64, opt woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error) { + ret := _mock.Called(repoID, opt) + + if len(ret) == 0 { + panic("no return value specified for RegistryList") + } + + var r0 []*woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error)); ok { + return returnFunc(repoID, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.RegistryListOptions) []*woodpecker.Registry); ok { + r0 = returnFunc(repoID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, woodpecker.RegistryListOptions) error); ok { + r1 = returnFunc(repoID, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RegistryList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegistryList' +type MockClient_RegistryList_Call struct { + *mock.Call +} + +// RegistryList is a helper method to define mock.On call +// - repoID int64 +// - opt woodpecker.RegistryListOptions +func (_e *MockClient_Expecter) RegistryList(repoID interface{}, opt interface{}) *MockClient_RegistryList_Call { + return &MockClient_RegistryList_Call{Call: _e.mock.On("RegistryList", repoID, opt)} +} + +func (_c *MockClient_RegistryList_Call) Run(run func(repoID int64, opt woodpecker.RegistryListOptions)) *MockClient_RegistryList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.RegistryListOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.RegistryListOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_RegistryList_Call) Return(registrys []*woodpecker.Registry, err error) *MockClient_RegistryList_Call { + _c.Call.Return(registrys, err) + return _c +} + +func (_c *MockClient_RegistryList_Call) RunAndReturn(run func(repoID int64, opt woodpecker.RegistryListOptions) ([]*woodpecker.Registry, error)) *MockClient_RegistryList_Call { + _c.Call.Return(run) + return _c +} + +// RegistryUpdate provides a mock function for the type MockClient +func (_mock *MockClient) RegistryUpdate(repoID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error) { + ret := _mock.Called(repoID, registry) + + if len(ret) == 0 { + panic("no return value specified for RegistryUpdate") + } + + var r0 *woodpecker.Registry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) (*woodpecker.Registry, error)); ok { + return returnFunc(repoID, registry) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Registry) *woodpecker.Registry); ok { + r0 = returnFunc(repoID, registry) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Registry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Registry) error); ok { + r1 = returnFunc(repoID, registry) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RegistryUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegistryUpdate' +type MockClient_RegistryUpdate_Call struct { + *mock.Call +} + +// RegistryUpdate is a helper method to define mock.On call +// - repoID int64 +// - registry *woodpecker.Registry +func (_e *MockClient_Expecter) RegistryUpdate(repoID interface{}, registry interface{}) *MockClient_RegistryUpdate_Call { + return &MockClient_RegistryUpdate_Call{Call: _e.mock.On("RegistryUpdate", repoID, registry)} +} + +func (_c *MockClient_RegistryUpdate_Call) Run(run func(repoID int64, registry *woodpecker.Registry)) *MockClient_RegistryUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Registry + if args[1] != nil { + arg1 = args[1].(*woodpecker.Registry) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_RegistryUpdate_Call) Return(registry1 *woodpecker.Registry, err error) *MockClient_RegistryUpdate_Call { + _c.Call.Return(registry1, err) + return _c +} + +func (_c *MockClient_RegistryUpdate_Call) RunAndReturn(run func(repoID int64, registry *woodpecker.Registry) (*woodpecker.Registry, error)) *MockClient_RegistryUpdate_Call { + _c.Call.Return(run) + return _c +} + +// Repo provides a mock function for the type MockClient +func (_mock *MockClient) Repo(repoID int64) (*woodpecker.Repo, error) { + ret := _mock.Called(repoID) + + if len(ret) == 0 { + panic("no return value specified for Repo") + } + + var r0 *woodpecker.Repo + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64) (*woodpecker.Repo, error)); ok { + return returnFunc(repoID) + } + if returnFunc, ok := ret.Get(0).(func(int64) *woodpecker.Repo); ok { + r0 = returnFunc(repoID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Repo) + } + } + if returnFunc, ok := ret.Get(1).(func(int64) error); ok { + r1 = returnFunc(repoID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Repo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Repo' +type MockClient_Repo_Call struct { + *mock.Call +} + +// Repo is a helper method to define mock.On call +// - repoID int64 +func (_e *MockClient_Expecter) Repo(repoID interface{}) *MockClient_Repo_Call { + return &MockClient_Repo_Call{Call: _e.mock.On("Repo", repoID)} +} + +func (_c *MockClient_Repo_Call) Run(run func(repoID int64)) *MockClient_Repo_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_Repo_Call) Return(repo *woodpecker.Repo, err error) *MockClient_Repo_Call { + _c.Call.Return(repo, err) + return _c +} + +func (_c *MockClient_Repo_Call) RunAndReturn(run func(repoID int64) (*woodpecker.Repo, error)) *MockClient_Repo_Call { + _c.Call.Return(run) + return _c +} + +// RepoChown provides a mock function for the type MockClient +func (_mock *MockClient) RepoChown(repoID int64) (*woodpecker.Repo, error) { + ret := _mock.Called(repoID) + + if len(ret) == 0 { + panic("no return value specified for RepoChown") + } + + var r0 *woodpecker.Repo + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64) (*woodpecker.Repo, error)); ok { + return returnFunc(repoID) + } + if returnFunc, ok := ret.Get(0).(func(int64) *woodpecker.Repo); ok { + r0 = returnFunc(repoID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Repo) + } + } + if returnFunc, ok := ret.Get(1).(func(int64) error); ok { + r1 = returnFunc(repoID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RepoChown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoChown' +type MockClient_RepoChown_Call struct { + *mock.Call +} + +// RepoChown is a helper method to define mock.On call +// - repoID int64 +func (_e *MockClient_Expecter) RepoChown(repoID interface{}) *MockClient_RepoChown_Call { + return &MockClient_RepoChown_Call{Call: _e.mock.On("RepoChown", repoID)} +} + +func (_c *MockClient_RepoChown_Call) Run(run func(repoID int64)) *MockClient_RepoChown_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_RepoChown_Call) Return(repo *woodpecker.Repo, err error) *MockClient_RepoChown_Call { + _c.Call.Return(repo, err) + return _c +} + +func (_c *MockClient_RepoChown_Call) RunAndReturn(run func(repoID int64) (*woodpecker.Repo, error)) *MockClient_RepoChown_Call { + _c.Call.Return(run) + return _c +} + +// RepoDel provides a mock function for the type MockClient +func (_mock *MockClient) RepoDel(repoID int64) error { + ret := _mock.Called(repoID) + + if len(ret) == 0 { + panic("no return value specified for RepoDel") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64) error); ok { + r0 = returnFunc(repoID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_RepoDel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoDel' +type MockClient_RepoDel_Call struct { + *mock.Call +} + +// RepoDel is a helper method to define mock.On call +// - repoID int64 +func (_e *MockClient_Expecter) RepoDel(repoID interface{}) *MockClient_RepoDel_Call { + return &MockClient_RepoDel_Call{Call: _e.mock.On("RepoDel", repoID)} +} + +func (_c *MockClient_RepoDel_Call) Run(run func(repoID int64)) *MockClient_RepoDel_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_RepoDel_Call) Return(err error) *MockClient_RepoDel_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_RepoDel_Call) RunAndReturn(run func(repoID int64) error) *MockClient_RepoDel_Call { + _c.Call.Return(run) + return _c +} + +// RepoList provides a mock function for the type MockClient +func (_mock *MockClient) RepoList(opt woodpecker.RepoListOptions) ([]*woodpecker.Repo, error) { + ret := _mock.Called(opt) + + if len(ret) == 0 { + panic("no return value specified for RepoList") + } + + var r0 []*woodpecker.Repo + var r1 error + if returnFunc, ok := ret.Get(0).(func(woodpecker.RepoListOptions) ([]*woodpecker.Repo, error)); ok { + return returnFunc(opt) + } + if returnFunc, ok := ret.Get(0).(func(woodpecker.RepoListOptions) []*woodpecker.Repo); ok { + r0 = returnFunc(opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Repo) + } + } + if returnFunc, ok := ret.Get(1).(func(woodpecker.RepoListOptions) error); ok { + r1 = returnFunc(opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RepoList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoList' +type MockClient_RepoList_Call struct { + *mock.Call +} + +// RepoList is a helper method to define mock.On call +// - opt woodpecker.RepoListOptions +func (_e *MockClient_Expecter) RepoList(opt interface{}) *MockClient_RepoList_Call { + return &MockClient_RepoList_Call{Call: _e.mock.On("RepoList", opt)} +} + +func (_c *MockClient_RepoList_Call) Run(run func(opt woodpecker.RepoListOptions)) *MockClient_RepoList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 woodpecker.RepoListOptions + if args[0] != nil { + arg0 = args[0].(woodpecker.RepoListOptions) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_RepoList_Call) Return(repos []*woodpecker.Repo, err error) *MockClient_RepoList_Call { + _c.Call.Return(repos, err) + return _c +} + +func (_c *MockClient_RepoList_Call) RunAndReturn(run func(opt woodpecker.RepoListOptions) ([]*woodpecker.Repo, error)) *MockClient_RepoList_Call { + _c.Call.Return(run) + return _c +} + +// RepoLookup provides a mock function for the type MockClient +func (_mock *MockClient) RepoLookup(repoFullName string) (*woodpecker.Repo, error) { + ret := _mock.Called(repoFullName) + + if len(ret) == 0 { + panic("no return value specified for RepoLookup") + } + + var r0 *woodpecker.Repo + var r1 error + if returnFunc, ok := ret.Get(0).(func(string) (*woodpecker.Repo, error)); ok { + return returnFunc(repoFullName) + } + if returnFunc, ok := ret.Get(0).(func(string) *woodpecker.Repo); ok { + r0 = returnFunc(repoFullName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Repo) + } + } + if returnFunc, ok := ret.Get(1).(func(string) error); ok { + r1 = returnFunc(repoFullName) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RepoLookup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoLookup' +type MockClient_RepoLookup_Call struct { + *mock.Call +} + +// RepoLookup is a helper method to define mock.On call +// - repoFullName string +func (_e *MockClient_Expecter) RepoLookup(repoFullName interface{}) *MockClient_RepoLookup_Call { + return &MockClient_RepoLookup_Call{Call: _e.mock.On("RepoLookup", repoFullName)} +} + +func (_c *MockClient_RepoLookup_Call) Run(run func(repoFullName string)) *MockClient_RepoLookup_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_RepoLookup_Call) Return(repo *woodpecker.Repo, err error) *MockClient_RepoLookup_Call { + _c.Call.Return(repo, err) + return _c +} + +func (_c *MockClient_RepoLookup_Call) RunAndReturn(run func(repoFullName string) (*woodpecker.Repo, error)) *MockClient_RepoLookup_Call { + _c.Call.Return(run) + return _c +} + +// RepoMove provides a mock function for the type MockClient +func (_mock *MockClient) RepoMove(repoID int64, opt woodpecker.RepoMoveOptions) error { + ret := _mock.Called(repoID, opt) + + if len(ret) == 0 { + panic("no return value specified for RepoMove") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.RepoMoveOptions) error); ok { + r0 = returnFunc(repoID, opt) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_RepoMove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoMove' +type MockClient_RepoMove_Call struct { + *mock.Call +} + +// RepoMove is a helper method to define mock.On call +// - repoID int64 +// - opt woodpecker.RepoMoveOptions +func (_e *MockClient_Expecter) RepoMove(repoID interface{}, opt interface{}) *MockClient_RepoMove_Call { + return &MockClient_RepoMove_Call{Call: _e.mock.On("RepoMove", repoID, opt)} +} + +func (_c *MockClient_RepoMove_Call) Run(run func(repoID int64, opt woodpecker.RepoMoveOptions)) *MockClient_RepoMove_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.RepoMoveOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.RepoMoveOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_RepoMove_Call) Return(err error) *MockClient_RepoMove_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_RepoMove_Call) RunAndReturn(run func(repoID int64, opt woodpecker.RepoMoveOptions) error) *MockClient_RepoMove_Call { + _c.Call.Return(run) + return _c +} + +// RepoPatch provides a mock function for the type MockClient +func (_mock *MockClient) RepoPatch(repoID int64, repo *woodpecker.RepoPatch) (*woodpecker.Repo, error) { + ret := _mock.Called(repoID, repo) + + if len(ret) == 0 { + panic("no return value specified for RepoPatch") + } + + var r0 *woodpecker.Repo + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.RepoPatch) (*woodpecker.Repo, error)); ok { + return returnFunc(repoID, repo) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.RepoPatch) *woodpecker.Repo); ok { + r0 = returnFunc(repoID, repo) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Repo) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.RepoPatch) error); ok { + r1 = returnFunc(repoID, repo) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RepoPatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoPatch' +type MockClient_RepoPatch_Call struct { + *mock.Call +} + +// RepoPatch is a helper method to define mock.On call +// - repoID int64 +// - repo *woodpecker.RepoPatch +func (_e *MockClient_Expecter) RepoPatch(repoID interface{}, repo interface{}) *MockClient_RepoPatch_Call { + return &MockClient_RepoPatch_Call{Call: _e.mock.On("RepoPatch", repoID, repo)} +} + +func (_c *MockClient_RepoPatch_Call) Run(run func(repoID int64, repo *woodpecker.RepoPatch)) *MockClient_RepoPatch_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.RepoPatch + if args[1] != nil { + arg1 = args[1].(*woodpecker.RepoPatch) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_RepoPatch_Call) Return(repo1 *woodpecker.Repo, err error) *MockClient_RepoPatch_Call { + _c.Call.Return(repo1, err) + return _c +} + +func (_c *MockClient_RepoPatch_Call) RunAndReturn(run func(repoID int64, repo *woodpecker.RepoPatch) (*woodpecker.Repo, error)) *MockClient_RepoPatch_Call { + _c.Call.Return(run) + return _c +} + +// RepoPost provides a mock function for the type MockClient +func (_mock *MockClient) RepoPost(opt woodpecker.RepoPostOptions) (*woodpecker.Repo, error) { + ret := _mock.Called(opt) + + if len(ret) == 0 { + panic("no return value specified for RepoPost") + } + + var r0 *woodpecker.Repo + var r1 error + if returnFunc, ok := ret.Get(0).(func(woodpecker.RepoPostOptions) (*woodpecker.Repo, error)); ok { + return returnFunc(opt) + } + if returnFunc, ok := ret.Get(0).(func(woodpecker.RepoPostOptions) *woodpecker.Repo); ok { + r0 = returnFunc(opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Repo) + } + } + if returnFunc, ok := ret.Get(1).(func(woodpecker.RepoPostOptions) error); ok { + r1 = returnFunc(opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_RepoPost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoPost' +type MockClient_RepoPost_Call struct { + *mock.Call +} + +// RepoPost is a helper method to define mock.On call +// - opt woodpecker.RepoPostOptions +func (_e *MockClient_Expecter) RepoPost(opt interface{}) *MockClient_RepoPost_Call { + return &MockClient_RepoPost_Call{Call: _e.mock.On("RepoPost", opt)} +} + +func (_c *MockClient_RepoPost_Call) Run(run func(opt woodpecker.RepoPostOptions)) *MockClient_RepoPost_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 woodpecker.RepoPostOptions + if args[0] != nil { + arg0 = args[0].(woodpecker.RepoPostOptions) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_RepoPost_Call) Return(repo *woodpecker.Repo, err error) *MockClient_RepoPost_Call { + _c.Call.Return(repo, err) + return _c +} + +func (_c *MockClient_RepoPost_Call) RunAndReturn(run func(opt woodpecker.RepoPostOptions) (*woodpecker.Repo, error)) *MockClient_RepoPost_Call { + _c.Call.Return(run) + return _c +} + +// RepoRepair provides a mock function for the type MockClient +func (_mock *MockClient) RepoRepair(repoID int64) error { + ret := _mock.Called(repoID) + + if len(ret) == 0 { + panic("no return value specified for RepoRepair") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64) error); ok { + r0 = returnFunc(repoID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_RepoRepair_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RepoRepair' +type MockClient_RepoRepair_Call struct { + *mock.Call +} + +// RepoRepair is a helper method to define mock.On call +// - repoID int64 +func (_e *MockClient_Expecter) RepoRepair(repoID interface{}) *MockClient_RepoRepair_Call { + return &MockClient_RepoRepair_Call{Call: _e.mock.On("RepoRepair", repoID)} +} + +func (_c *MockClient_RepoRepair_Call) Run(run func(repoID int64)) *MockClient_RepoRepair_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_RepoRepair_Call) Return(err error) *MockClient_RepoRepair_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_RepoRepair_Call) RunAndReturn(run func(repoID int64) error) *MockClient_RepoRepair_Call { + _c.Call.Return(run) + return _c +} + +// Secret provides a mock function for the type MockClient +func (_mock *MockClient) Secret(repoID int64, secret string) (*woodpecker.Secret, error) { + ret := _mock.Called(repoID, secret) + + if len(ret) == 0 { + panic("no return value specified for Secret") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, string) (*woodpecker.Secret, error)); ok { + return returnFunc(repoID, secret) + } + if returnFunc, ok := ret.Get(0).(func(int64, string) *woodpecker.Secret); ok { + r0 = returnFunc(repoID, secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, string) error); ok { + r1 = returnFunc(repoID, secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Secret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Secret' +type MockClient_Secret_Call struct { + *mock.Call +} + +// Secret is a helper method to define mock.On call +// - repoID int64 +// - secret string +func (_e *MockClient_Expecter) Secret(repoID interface{}, secret interface{}) *MockClient_Secret_Call { + return &MockClient_Secret_Call{Call: _e.mock.On("Secret", repoID, secret)} +} + +func (_c *MockClient_Secret_Call) Run(run func(repoID int64, secret string)) *MockClient_Secret_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_Secret_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_Secret_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_Secret_Call) RunAndReturn(run func(repoID int64, secret string) (*woodpecker.Secret, error)) *MockClient_Secret_Call { + _c.Call.Return(run) + return _c +} + +// SecretCreate provides a mock function for the type MockClient +func (_mock *MockClient) SecretCreate(repoID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error) { + ret := _mock.Called(repoID, secret) + + if len(ret) == 0 { + panic("no return value specified for SecretCreate") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) (*woodpecker.Secret, error)); ok { + return returnFunc(repoID, secret) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) *woodpecker.Secret); ok { + r0 = returnFunc(repoID, secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Secret) error); ok { + r1 = returnFunc(repoID, secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_SecretCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SecretCreate' +type MockClient_SecretCreate_Call struct { + *mock.Call +} + +// SecretCreate is a helper method to define mock.On call +// - repoID int64 +// - secret *woodpecker.Secret +func (_e *MockClient_Expecter) SecretCreate(repoID interface{}, secret interface{}) *MockClient_SecretCreate_Call { + return &MockClient_SecretCreate_Call{Call: _e.mock.On("SecretCreate", repoID, secret)} +} + +func (_c *MockClient_SecretCreate_Call) Run(run func(repoID int64, secret *woodpecker.Secret)) *MockClient_SecretCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Secret + if args[1] != nil { + arg1 = args[1].(*woodpecker.Secret) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_SecretCreate_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_SecretCreate_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_SecretCreate_Call) RunAndReturn(run func(repoID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error)) *MockClient_SecretCreate_Call { + _c.Call.Return(run) + return _c +} + +// SecretDelete provides a mock function for the type MockClient +func (_mock *MockClient) SecretDelete(repoID int64, secret string) error { + ret := _mock.Called(repoID, secret) + + if len(ret) == 0 { + panic("no return value specified for SecretDelete") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, string) error); ok { + r0 = returnFunc(repoID, secret) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_SecretDelete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SecretDelete' +type MockClient_SecretDelete_Call struct { + *mock.Call +} + +// SecretDelete is a helper method to define mock.On call +// - repoID int64 +// - secret string +func (_e *MockClient_Expecter) SecretDelete(repoID interface{}, secret interface{}) *MockClient_SecretDelete_Call { + return &MockClient_SecretDelete_Call{Call: _e.mock.On("SecretDelete", repoID, secret)} +} + +func (_c *MockClient_SecretDelete_Call) Run(run func(repoID int64, secret string)) *MockClient_SecretDelete_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_SecretDelete_Call) Return(err error) *MockClient_SecretDelete_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_SecretDelete_Call) RunAndReturn(run func(repoID int64, secret string) error) *MockClient_SecretDelete_Call { + _c.Call.Return(run) + return _c +} + +// SecretList provides a mock function for the type MockClient +func (_mock *MockClient) SecretList(repoID int64, opt woodpecker.SecretListOptions) ([]*woodpecker.Secret, error) { + ret := _mock.Called(repoID, opt) + + if len(ret) == 0 { + panic("no return value specified for SecretList") + } + + var r0 []*woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.SecretListOptions) ([]*woodpecker.Secret, error)); ok { + return returnFunc(repoID, opt) + } + if returnFunc, ok := ret.Get(0).(func(int64, woodpecker.SecretListOptions) []*woodpecker.Secret); ok { + r0 = returnFunc(repoID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, woodpecker.SecretListOptions) error); ok { + r1 = returnFunc(repoID, opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_SecretList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SecretList' +type MockClient_SecretList_Call struct { + *mock.Call +} + +// SecretList is a helper method to define mock.On call +// - repoID int64 +// - opt woodpecker.SecretListOptions +func (_e *MockClient_Expecter) SecretList(repoID interface{}, opt interface{}) *MockClient_SecretList_Call { + return &MockClient_SecretList_Call{Call: _e.mock.On("SecretList", repoID, opt)} +} + +func (_c *MockClient_SecretList_Call) Run(run func(repoID int64, opt woodpecker.SecretListOptions)) *MockClient_SecretList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 woodpecker.SecretListOptions + if args[1] != nil { + arg1 = args[1].(woodpecker.SecretListOptions) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_SecretList_Call) Return(secrets []*woodpecker.Secret, err error) *MockClient_SecretList_Call { + _c.Call.Return(secrets, err) + return _c +} + +func (_c *MockClient_SecretList_Call) RunAndReturn(run func(repoID int64, opt woodpecker.SecretListOptions) ([]*woodpecker.Secret, error)) *MockClient_SecretList_Call { + _c.Call.Return(run) + return _c +} + +// SecretUpdate provides a mock function for the type MockClient +func (_mock *MockClient) SecretUpdate(repoID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error) { + ret := _mock.Called(repoID, secret) + + if len(ret) == 0 { + panic("no return value specified for SecretUpdate") + } + + var r0 *woodpecker.Secret + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) (*woodpecker.Secret, error)); ok { + return returnFunc(repoID, secret) + } + if returnFunc, ok := ret.Get(0).(func(int64, *woodpecker.Secret) *woodpecker.Secret); ok { + r0 = returnFunc(repoID, secret) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.Secret) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, *woodpecker.Secret) error); ok { + r1 = returnFunc(repoID, secret) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_SecretUpdate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SecretUpdate' +type MockClient_SecretUpdate_Call struct { + *mock.Call +} + +// SecretUpdate is a helper method to define mock.On call +// - repoID int64 +// - secret *woodpecker.Secret +func (_e *MockClient_Expecter) SecretUpdate(repoID interface{}, secret interface{}) *MockClient_SecretUpdate_Call { + return &MockClient_SecretUpdate_Call{Call: _e.mock.On("SecretUpdate", repoID, secret)} +} + +func (_c *MockClient_SecretUpdate_Call) Run(run func(repoID int64, secret *woodpecker.Secret)) *MockClient_SecretUpdate_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 *woodpecker.Secret + if args[1] != nil { + arg1 = args[1].(*woodpecker.Secret) + } + run( + arg0, + arg1, + ) + }) + return _c +} + +func (_c *MockClient_SecretUpdate_Call) Return(secret1 *woodpecker.Secret, err error) *MockClient_SecretUpdate_Call { + _c.Call.Return(secret1, err) + return _c +} + +func (_c *MockClient_SecretUpdate_Call) RunAndReturn(run func(repoID int64, secret *woodpecker.Secret) (*woodpecker.Secret, error)) *MockClient_SecretUpdate_Call { + _c.Call.Return(run) + return _c +} + +// Self provides a mock function for the type MockClient +func (_mock *MockClient) Self() (*woodpecker.User, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Self") + } + + var r0 *woodpecker.User + var r1 error + if returnFunc, ok := ret.Get(0).(func() (*woodpecker.User, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() *woodpecker.User); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.User) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_Self_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Self' +type MockClient_Self_Call struct { + *mock.Call +} + +// Self is a helper method to define mock.On call +func (_e *MockClient_Expecter) Self() *MockClient_Self_Call { + return &MockClient_Self_Call{Call: _e.mock.On("Self")} +} + +func (_c *MockClient_Self_Call) Run(run func()) *MockClient_Self_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockClient_Self_Call) Return(user *woodpecker.User, err error) *MockClient_Self_Call { + _c.Call.Return(user, err) + return _c +} + +func (_c *MockClient_Self_Call) RunAndReturn(run func() (*woodpecker.User, error)) *MockClient_Self_Call { + _c.Call.Return(run) + return _c +} + +// SetAddress provides a mock function for the type MockClient +func (_mock *MockClient) SetAddress(s string) { + _mock.Called(s) + return +} + +// MockClient_SetAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetAddress' +type MockClient_SetAddress_Call struct { + *mock.Call +} + +// SetAddress is a helper method to define mock.On call +// - s string +func (_e *MockClient_Expecter) SetAddress(s interface{}) *MockClient_SetAddress_Call { + return &MockClient_SetAddress_Call{Call: _e.mock.On("SetAddress", s)} +} + +func (_c *MockClient_SetAddress_Call) Run(run func(s string)) *MockClient_SetAddress_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_SetAddress_Call) Return() *MockClient_SetAddress_Call { + _c.Call.Return() + return _c +} + +func (_c *MockClient_SetAddress_Call) RunAndReturn(run func(s string)) *MockClient_SetAddress_Call { + _c.Run(run) + return _c +} + +// SetClient provides a mock function for the type MockClient +func (_mock *MockClient) SetClient(client *http.Client) { + _mock.Called(client) + return +} + +// MockClient_SetClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetClient' +type MockClient_SetClient_Call struct { + *mock.Call +} + +// SetClient is a helper method to define mock.On call +// - client *http.Client +func (_e *MockClient_Expecter) SetClient(client interface{}) *MockClient_SetClient_Call { + return &MockClient_SetClient_Call{Call: _e.mock.On("SetClient", client)} +} + +func (_c *MockClient_SetClient_Call) Run(run func(client *http.Client)) *MockClient_SetClient_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *http.Client + if args[0] != nil { + arg0 = args[0].(*http.Client) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_SetClient_Call) Return() *MockClient_SetClient_Call { + _c.Call.Return() + return _c +} + +func (_c *MockClient_SetClient_Call) RunAndReturn(run func(client *http.Client)) *MockClient_SetClient_Call { + _c.Run(run) + return _c +} + +// SetLogLevel provides a mock function for the type MockClient +func (_mock *MockClient) SetLogLevel(logLevel *woodpecker.LogLevel) (*woodpecker.LogLevel, error) { + ret := _mock.Called(logLevel) + + if len(ret) == 0 { + panic("no return value specified for SetLogLevel") + } + + var r0 *woodpecker.LogLevel + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.LogLevel) (*woodpecker.LogLevel, error)); ok { + return returnFunc(logLevel) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.LogLevel) *woodpecker.LogLevel); ok { + r0 = returnFunc(logLevel) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.LogLevel) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.LogLevel) error); ok { + r1 = returnFunc(logLevel) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_SetLogLevel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetLogLevel' +type MockClient_SetLogLevel_Call struct { + *mock.Call +} + +// SetLogLevel is a helper method to define mock.On call +// - logLevel *woodpecker.LogLevel +func (_e *MockClient_Expecter) SetLogLevel(logLevel interface{}) *MockClient_SetLogLevel_Call { + return &MockClient_SetLogLevel_Call{Call: _e.mock.On("SetLogLevel", logLevel)} +} + +func (_c *MockClient_SetLogLevel_Call) Run(run func(logLevel *woodpecker.LogLevel)) *MockClient_SetLogLevel_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.LogLevel + if args[0] != nil { + arg0 = args[0].(*woodpecker.LogLevel) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_SetLogLevel_Call) Return(logLevel1 *woodpecker.LogLevel, err error) *MockClient_SetLogLevel_Call { + _c.Call.Return(logLevel1, err) + return _c +} + +func (_c *MockClient_SetLogLevel_Call) RunAndReturn(run func(logLevel *woodpecker.LogLevel) (*woodpecker.LogLevel, error)) *MockClient_SetLogLevel_Call { + _c.Call.Return(run) + return _c +} + +// StepLogEntries provides a mock function for the type MockClient +func (_mock *MockClient) StepLogEntries(repoID int64, pipeline int64, stepID int64) ([]*woodpecker.LogEntry, error) { + ret := _mock.Called(repoID, pipeline, stepID) + + if len(ret) == 0 { + panic("no return value specified for StepLogEntries") + } + + var r0 []*woodpecker.LogEntry + var r1 error + if returnFunc, ok := ret.Get(0).(func(int64, int64, int64) ([]*woodpecker.LogEntry, error)); ok { + return returnFunc(repoID, pipeline, stepID) + } + if returnFunc, ok := ret.Get(0).(func(int64, int64, int64) []*woodpecker.LogEntry); ok { + r0 = returnFunc(repoID, pipeline, stepID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.LogEntry) + } + } + if returnFunc, ok := ret.Get(1).(func(int64, int64, int64) error); ok { + r1 = returnFunc(repoID, pipeline, stepID) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_StepLogEntries_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StepLogEntries' +type MockClient_StepLogEntries_Call struct { + *mock.Call +} + +// StepLogEntries is a helper method to define mock.On call +// - repoID int64 +// - pipeline int64 +// - stepID int64 +func (_e *MockClient_Expecter) StepLogEntries(repoID interface{}, pipeline interface{}, stepID interface{}) *MockClient_StepLogEntries_Call { + return &MockClient_StepLogEntries_Call{Call: _e.mock.On("StepLogEntries", repoID, pipeline, stepID)} +} + +func (_c *MockClient_StepLogEntries_Call) Run(run func(repoID int64, pipeline int64, stepID int64)) *MockClient_StepLogEntries_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + var arg2 int64 + if args[2] != nil { + arg2 = args[2].(int64) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockClient_StepLogEntries_Call) Return(logEntrys []*woodpecker.LogEntry, err error) *MockClient_StepLogEntries_Call { + _c.Call.Return(logEntrys, err) + return _c +} + +func (_c *MockClient_StepLogEntries_Call) RunAndReturn(run func(repoID int64, pipeline int64, stepID int64) ([]*woodpecker.LogEntry, error)) *MockClient_StepLogEntries_Call { + _c.Call.Return(run) + return _c +} + +// StepLogsPurge provides a mock function for the type MockClient +func (_mock *MockClient) StepLogsPurge(repoID int64, pipelineNumber int64, stepID int64) error { + ret := _mock.Called(repoID, pipelineNumber, stepID) + + if len(ret) == 0 { + panic("no return value specified for StepLogsPurge") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int64, int64, int64) error); ok { + r0 = returnFunc(repoID, pipelineNumber, stepID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_StepLogsPurge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StepLogsPurge' +type MockClient_StepLogsPurge_Call struct { + *mock.Call +} + +// StepLogsPurge is a helper method to define mock.On call +// - repoID int64 +// - pipelineNumber int64 +// - stepID int64 +func (_e *MockClient_Expecter) StepLogsPurge(repoID interface{}, pipelineNumber interface{}, stepID interface{}) *MockClient_StepLogsPurge_Call { + return &MockClient_StepLogsPurge_Call{Call: _e.mock.On("StepLogsPurge", repoID, pipelineNumber, stepID)} +} + +func (_c *MockClient_StepLogsPurge_Call) Run(run func(repoID int64, pipelineNumber int64, stepID int64)) *MockClient_StepLogsPurge_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 int64 + if args[0] != nil { + arg0 = args[0].(int64) + } + var arg1 int64 + if args[1] != nil { + arg1 = args[1].(int64) + } + var arg2 int64 + if args[2] != nil { + arg2 = args[2].(int64) + } + run( + arg0, + arg1, + arg2, + ) + }) + return _c +} + +func (_c *MockClient_StepLogsPurge_Call) Return(err error) *MockClient_StepLogsPurge_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_StepLogsPurge_Call) RunAndReturn(run func(repoID int64, pipelineNumber int64, stepID int64) error) *MockClient_StepLogsPurge_Call { + _c.Call.Return(run) + return _c +} + +// User provides a mock function for the type MockClient +func (_mock *MockClient) User(login string, forgeID ...int64) (*woodpecker.User, error) { + var tmpRet mock.Arguments + if len(forgeID) > 0 { + tmpRet = _mock.Called(login, forgeID) + } else { + tmpRet = _mock.Called(login) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for User") + } + + var r0 *woodpecker.User + var r1 error + if returnFunc, ok := ret.Get(0).(func(string, ...int64) (*woodpecker.User, error)); ok { + return returnFunc(login, forgeID...) + } + if returnFunc, ok := ret.Get(0).(func(string, ...int64) *woodpecker.User); ok { + r0 = returnFunc(login, forgeID...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.User) + } + } + if returnFunc, ok := ret.Get(1).(func(string, ...int64) error); ok { + r1 = returnFunc(login, forgeID...) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_User_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'User' +type MockClient_User_Call struct { + *mock.Call +} + +// User is a helper method to define mock.On call +// - login string +// - forgeID ...int64 +func (_e *MockClient_Expecter) User(login interface{}, forgeID ...interface{}) *MockClient_User_Call { + return &MockClient_User_Call{Call: _e.mock.On("User", + append([]interface{}{login}, forgeID...)...)} +} + +func (_c *MockClient_User_Call) Run(run func(login string, forgeID ...int64)) *MockClient_User_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []int64 + var variadicArgs []int64 + if len(args) > 1 { + variadicArgs = args[1].([]int64) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *MockClient_User_Call) Return(user *woodpecker.User, err error) *MockClient_User_Call { + _c.Call.Return(user, err) + return _c +} + +func (_c *MockClient_User_Call) RunAndReturn(run func(login string, forgeID ...int64) (*woodpecker.User, error)) *MockClient_User_Call { + _c.Call.Return(run) + return _c +} + +// UserDel provides a mock function for the type MockClient +func (_mock *MockClient) UserDel(login string, forgeID ...int64) error { + var tmpRet mock.Arguments + if len(forgeID) > 0 { + tmpRet = _mock.Called(login, forgeID) + } else { + tmpRet = _mock.Called(login) + } + ret := tmpRet + + if len(ret) == 0 { + panic("no return value specified for UserDel") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(string, ...int64) error); ok { + r0 = returnFunc(login, forgeID...) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_UserDel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserDel' +type MockClient_UserDel_Call struct { + *mock.Call +} + +// UserDel is a helper method to define mock.On call +// - login string +// - forgeID ...int64 +func (_e *MockClient_Expecter) UserDel(login interface{}, forgeID ...interface{}) *MockClient_UserDel_Call { + return &MockClient_UserDel_Call{Call: _e.mock.On("UserDel", + append([]interface{}{login}, forgeID...)...)} +} + +func (_c *MockClient_UserDel_Call) Run(run func(login string, forgeID ...int64)) *MockClient_UserDel_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 []int64 + var variadicArgs []int64 + if len(args) > 1 { + variadicArgs = args[1].([]int64) + } + arg1 = variadicArgs + run( + arg0, + arg1..., + ) + }) + return _c +} + +func (_c *MockClient_UserDel_Call) Return(err error) *MockClient_UserDel_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_UserDel_Call) RunAndReturn(run func(login string, forgeID ...int64) error) *MockClient_UserDel_Call { + _c.Call.Return(run) + return _c +} + +// UserList provides a mock function for the type MockClient +func (_mock *MockClient) UserList(opt woodpecker.UserListOptions) ([]*woodpecker.User, error) { + ret := _mock.Called(opt) + + if len(ret) == 0 { + panic("no return value specified for UserList") + } + + var r0 []*woodpecker.User + var r1 error + if returnFunc, ok := ret.Get(0).(func(woodpecker.UserListOptions) ([]*woodpecker.User, error)); ok { + return returnFunc(opt) + } + if returnFunc, ok := ret.Get(0).(func(woodpecker.UserListOptions) []*woodpecker.User); ok { + r0 = returnFunc(opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*woodpecker.User) + } + } + if returnFunc, ok := ret.Get(1).(func(woodpecker.UserListOptions) error); ok { + r1 = returnFunc(opt) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_UserList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserList' +type MockClient_UserList_Call struct { + *mock.Call +} + +// UserList is a helper method to define mock.On call +// - opt woodpecker.UserListOptions +func (_e *MockClient_Expecter) UserList(opt interface{}) *MockClient_UserList_Call { + return &MockClient_UserList_Call{Call: _e.mock.On("UserList", opt)} +} + +func (_c *MockClient_UserList_Call) Run(run func(opt woodpecker.UserListOptions)) *MockClient_UserList_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 woodpecker.UserListOptions + if args[0] != nil { + arg0 = args[0].(woodpecker.UserListOptions) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_UserList_Call) Return(users []*woodpecker.User, err error) *MockClient_UserList_Call { + _c.Call.Return(users, err) + return _c +} + +func (_c *MockClient_UserList_Call) RunAndReturn(run func(opt woodpecker.UserListOptions) ([]*woodpecker.User, error)) *MockClient_UserList_Call { + _c.Call.Return(run) + return _c +} + +// UserPatch provides a mock function for the type MockClient +func (_mock *MockClient) UserPatch(user *woodpecker.User) (*woodpecker.User, error) { + ret := _mock.Called(user) + + if len(ret) == 0 { + panic("no return value specified for UserPatch") + } + + var r0 *woodpecker.User + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.User) (*woodpecker.User, error)); ok { + return returnFunc(user) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.User) *woodpecker.User); ok { + r0 = returnFunc(user) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.User) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.User) error); ok { + r1 = returnFunc(user) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_UserPatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserPatch' +type MockClient_UserPatch_Call struct { + *mock.Call +} + +// UserPatch is a helper method to define mock.On call +// - user *woodpecker.User +func (_e *MockClient_Expecter) UserPatch(user interface{}) *MockClient_UserPatch_Call { + return &MockClient_UserPatch_Call{Call: _e.mock.On("UserPatch", user)} +} + +func (_c *MockClient_UserPatch_Call) Run(run func(user *woodpecker.User)) *MockClient_UserPatch_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.User + if args[0] != nil { + arg0 = args[0].(*woodpecker.User) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_UserPatch_Call) Return(user1 *woodpecker.User, err error) *MockClient_UserPatch_Call { + _c.Call.Return(user1, err) + return _c +} + +func (_c *MockClient_UserPatch_Call) RunAndReturn(run func(user *woodpecker.User) (*woodpecker.User, error)) *MockClient_UserPatch_Call { + _c.Call.Return(run) + return _c +} + +// UserPost provides a mock function for the type MockClient +func (_mock *MockClient) UserPost(user *woodpecker.User) (*woodpecker.User, error) { + ret := _mock.Called(user) + + if len(ret) == 0 { + panic("no return value specified for UserPost") + } + + var r0 *woodpecker.User + var r1 error + if returnFunc, ok := ret.Get(0).(func(*woodpecker.User) (*woodpecker.User, error)); ok { + return returnFunc(user) + } + if returnFunc, ok := ret.Get(0).(func(*woodpecker.User) *woodpecker.User); ok { + r0 = returnFunc(user) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*woodpecker.User) + } + } + if returnFunc, ok := ret.Get(1).(func(*woodpecker.User) error); ok { + r1 = returnFunc(user) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_UserPost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserPost' +type MockClient_UserPost_Call struct { + *mock.Call +} + +// UserPost is a helper method to define mock.On call +// - user *woodpecker.User +func (_e *MockClient_Expecter) UserPost(user interface{}) *MockClient_UserPost_Call { + return &MockClient_UserPost_Call{Call: _e.mock.On("UserPost", user)} +} + +func (_c *MockClient_UserPost_Call) Run(run func(user *woodpecker.User)) *MockClient_UserPost_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *woodpecker.User + if args[0] != nil { + arg0 = args[0].(*woodpecker.User) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockClient_UserPost_Call) Return(user1 *woodpecker.User, err error) *MockClient_UserPost_Call { + _c.Call.Return(user1, err) + return _c +} + +func (_c *MockClient_UserPost_Call) RunAndReturn(run func(user *woodpecker.User) (*woodpecker.User, error)) *MockClient_UserPost_Call { + _c.Call.Return(run) + return _c +} diff --git a/utils/random.go b/utils/random.go new file mode 100644 index 0000000..74cc0d9 --- /dev/null +++ b/utils/random.go @@ -0,0 +1,18 @@ +package utils + +import ( + "math/rand" + "time" +) + +// RandomString generates a random string of length n using alphanumeric characters. +func RandomString(n int) string { + letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rng.Intn(len(letterRunes))] + } + return string(b) +} diff --git a/utils/random_test.go b/utils/random_test.go new file mode 100644 index 0000000..57219fe --- /dev/null +++ b/utils/random_test.go @@ -0,0 +1,42 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.woodpecker-ci.org/autoscaler/utils" +) + +func TestRandomString(t *testing.T) { + tests := []struct { + name string + n int + want int + }{ + { + name: "zero length", + n: 0, + want: 0, + }, + { + name: "length 10", + n: 10, + want: 10, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + str := utils.RandomString(tt.n) + assert.Equal(t, tt.want, len(str)) + }) + + t.Run("alphanumeric", func(t *testing.T) { + str1 := utils.RandomString(10) + for _, r := range str1 { + assert.Contains(t, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", string(r)) + } + }) + } +} diff --git a/utils/stringmaps.go b/utils/stringmaps.go new file mode 100644 index 0000000..ff991dc --- /dev/null +++ b/utils/stringmaps.go @@ -0,0 +1,39 @@ +package utils + +import ( + "fmt" + "strings" +) + +// SliceToMap converts a slice of strings in the format "key=value" +// into a string map, using the provided delimiter to split the pieces. +// Returns a map and nil error on success, or nil and an error if a +// slice element does not contain the delimiter. +func SliceToMap(list []string, del string) (map[string]string, error) { + m := make(map[string]string) + for _, e := range list { + before, after, _ := strings.Cut(e, del) + if before == "" || after == "" { + return nil, fmt.Errorf("could not split '%s' into key value pair with '=' delimiter", e) + } + m[strings.TrimSpace(before)] = strings.TrimSpace(after) + } + + return m, nil +} + +// MergeMaps merges two string maps m1 and m2 into a new map. +// It copies all key-value pairs from m1 into the result. +// It then copies all key-value pairs from m2 into the result, +// overwriting any keys that are present in both m1 and m2. +// The merged map is returned. +func MergeMaps(m1, m2 map[string]string) map[string]string { + merged := make(map[string]string) + for k, v := range m1 { + merged[k] = v + } + for key, value := range m2 { + merged[key] = value + } + return merged +} diff --git a/utils/stringmaps_test.go b/utils/stringmaps_test.go new file mode 100644 index 0000000..3db3806 --- /dev/null +++ b/utils/stringmaps_test.go @@ -0,0 +1,96 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.woodpecker-ci.org/autoscaler/utils" +) + +func TestSliceToMap(t *testing.T) { + testCases := []struct { + name string + input []string + del string + want map[string]string + wantErr error + }{ + { + name: "basic", + input: []string{"key1=value1", "key2=value2"}, + del: "=", + want: map[string]string{"key1": "value1", "key2": "value2"}, + wantErr: nil, + }, + { + name: "whitespace", + input: []string{"key1 = value1", "key2=value2"}, + del: "=", + want: map[string]string{"key1": "value1", "key2": "value2"}, + wantErr: nil, + }, + { + name: "missing delimiter", + input: []string{"key1", "key2=value2"}, + del: "=", + want: nil, + wantErr: assert.AnError, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + actual, err := utils.SliceToMap(tt.input, tt.del) + if tt.wantErr != nil { + assert.Error(t, err) + + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, actual) + }) + } +} + +func TestMergeMaps(t *testing.T) { + testCases := []struct { + name string + m1 map[string]string + m2 map[string]string + want map[string]string + }{ + { + name: "nil maps", + m1: nil, + m2: nil, + want: map[string]string{}, + }, + { + name: "empty maps", + m1: map[string]string{}, + m2: map[string]string{}, + want: map[string]string{}, + }, + { + name: "overwrite", + m1: map[string]string{"key1": "value1", "key2": "value2"}, + m2: map[string]string{"key2": "newvalue2", "key3": "value3"}, + want: map[string]string{"key1": "value1", "key2": "newvalue2", "key3": "value3"}, + }, + { + name: "no overwrite", + m1: map[string]string{"key1": "value1", "key2": "value2"}, + m2: map[string]string{"key3": "value3", "key4": "value4"}, + want: map[string]string{"key1": "value1", "key2": "value2", "key3": "value3", "key4": "value4"}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + merged := utils.MergeMaps(tt.m1, tt.m2) + assert.Equal(t, tt.want, merged) + }) + } +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..e219d4c --- /dev/null +++ b/version/version.go @@ -0,0 +1,13 @@ +package version + +// Version of Woodpecker Autoscaler, set with ldflags, from Git tag. +var Version string + +// String returns the Version set at build time or "dev". +func String() string { + if Version == "" { + return "dev" + } + + return Version +}