commit 191cdd108f7ae7893239f05e17e2d54e4e1dd10f Author: Sergey Vanyushkin Date: Sat May 16 13:09:07 2026 +0300 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 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 +}