Compare commits

...

98 commits

Author SHA1 Message Date
6608d0aa8c
refactor(params): ♻️ Simplify ForgeURL logic by removing redundant switch case
Some checks failed
CodeQL / Analyze (push) Failing after 2s
Go Tests / Linters (push) Successful in 3m26s
Go Tests / go-tests (push) Successful in 6m41s
Streamlines the ForgeURL method by removing an unnecessary nested switch case
on ForgeType, consolidating the logic to rely solely on EntityType. This change
reduces complexity and improves code readability without altering functionality.
2025-08-27 16:04:56 +02:00
Gabriel
29f2e2a2b9
Merge pull request #499 from gabriel-samfira/better-cli-log-formatting
Make the debug-log command more useful
2025-08-27 16:27:33 +03:00
Gabriel Adrian Samfira
0faeee703d Make the debug-log command more useful
The debug-log command now supports log level filtering and attribute
filtering. The log level filtering will only be able to set the minimum
log level as low as the server is configured to stream. If the server has
its log level set as INFO, then setting the log level in the CLI to DEBUG
will have no effect.

But anything above what the server sends, is within the control of the client
to filter. This is all done client side.

Attribute filters are useful if you need to watch the logs for a particular
worker, entity, etc.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-27 13:21:56 +00:00
Gabriel
e3a9fe7026
Merge pull request #498 from gabriel-samfira/preload-more-info
Preload more info for entity pools
2025-08-27 12:27:04 +03:00
Gabriel Adrian Samfira
9ef4566cae Preload more info for entity pools
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-27 09:14:45 +00:00
Gabriel
e79a5c1d50
Merge pull request #496 from cloudbase/dependabot/go_modules/github.com/stretchr/testify-1.11.0
Bump github.com/stretchr/testify from 1.10.0 to 1.11.0
2025-08-25 19:35:17 +03:00
Gabriel
89fecd9dcd
Merge pull request #497 from gabriel-samfira/fix-double-pool
Fix double creation of pools
2025-08-25 19:18:07 +03:00
Gabriel Adrian Samfira
6fee10c737 Fix double creation of pools
This change fixes the creation of pools though the UI. Both the modal and
the page were sending a request to create the pool, leading to double pool.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-25 16:12:01 +00:00
dependabot[bot]
642b3bbf92
Bump github.com/stretchr/testify from 1.10.0 to 1.11.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-25 11:54:27 +00:00
Gabriel
1fceec374d
Merge pull request #495 from gabriel-samfira/caching-and-fixes
Attempt to use the scalset API and caching
2025-08-25 01:45:46 +03:00
Gabriel Adrian Samfira
d05df36868 Attempt to use the scalset API and caching
On github, attempt to use the scaleset API to list all runners without
pagination. This will avoid missing runners and accidentally removing them.

Fall back to paginated API if we can't use the scaleset API.

Add ability to retrieve all instances from cache, for an entity.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-24 22:36:44 +00:00
Gabriel
5c703f310b
Merge pull request #494 from gabriel-samfira/small-fixes
Small fixes
2025-08-24 11:53:21 +03:00
Gabriel Adrian Samfira
86a0b0cf4f Small fixes
* Shut down the web server first to prevent errors caused by clients trying
to use functionality that has already been shut down, causing errors and
potentially delaying the shutdown process.
* remove write timeout from the websocket Write() function.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-24 08:35:17 +00:00
Gabriel
5a26614acf
Merge pull request #493 from gabriel-samfira/ensure-scale-set
Ensure scale set exists
2025-08-23 22:12:30 +03:00
Gabriel Adrian Samfira
39003f006a Ensure scale set exists
Github will remove inactive scale sets after 7 days. This change
ensures the scale set exists in github before spinning up the listener.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-23 18:55:08 +00:00
Gabriel
c48bb50f2a
Merge pull request #492 from gabriel-samfira/add-webui-tests
Add webui tests
2025-08-22 00:18:57 +03:00
Gabriel Adrian Samfira
54c6571ccd Update test workflow
Add steps to test the Web UI and to run go generate.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-21 21:00:27 +00:00
Gabriel Adrian Samfira
8d5c6b6738 Bump go version to 1.24.6 and run go generate
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-21 20:36:50 +00:00
Gabriel Adrian Samfira
48769587bb Add web UI tests
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-21 20:36:50 +00:00
Gabriel
9aa2e297b9
Merge pull request #491 from igrikus/main
fix(jobs): Correctly handle workflow job IDs from webhooks
2025-08-21 22:04:35 +03:00
Gabriel Adrian Samfira
4341b4869c Fix erroneous calls to Job related functions
Some functions were left behind when we added WorkflowJobID.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-21 18:57:42 +00:00
igrikus
8bda81d6cc fix(jobs): Correctly handle workflow job IDs from webhooks 2025-08-21 14:54:38 +02:00
Gabriel
004c03962f
Merge pull request #488 from gabriel-samfira/remove-deprecated
Replace ${base} with resolve()
2025-08-18 16:09:32 +03:00
Gabriel Adrian Samfira
69a2c509a7 Replace ${base} with resolve()
This removes some deprecated code.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-18 13:08:58 +00:00
Gabriel
b8ff6d9e14
Merge pull request #487 from gabriel-samfira/small-fixes
Fix href for entities
2025-08-18 15:03:24 +03:00
Gabriel Adrian Samfira
9028ef64b1 Fix href for entities
The URL from scale sets and pools to the entity to which they belong
was not being properly resolved.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-18 12:02:08 +00:00
Gabriel
eeed5ae508
Merge pull request #486 from gabriel-samfira/small-fixes
Fix endpoint type name and update cookie
2025-08-18 01:28:47 +03:00
Gabriel Adrian Samfira
04d1042a4c Fix endpoint type name and update cookie
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-17 22:27:17 +00:00
Gabriel
102c430e1d
Merge pull request #485 from gabriel-samfira/ui-fixes
Slightly better error handling
2025-08-17 10:42:33 +03:00
Gabriel Adrian Samfira
7f647941f6 Slightly better error handling
Extract error details we get from the API when status code > 2xx.
Also, use toast messages to display the error, properly close delete
modals and prevent full page display of error messages.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-17 07:34:40 +00:00
Gabriel
8cf0b0a56e
Merge pull request #484 from gabriel-samfira/remove-all-flag-scalesets
Remove the --all flag for scalesets
2025-08-17 02:20:16 +03:00
Gabriel Adrian Samfira
f805123a85 Remove the --all flag for scalesets
Display all scalesets by default, similar to runners and pools.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-16 23:13:47 +00:00
Gabriel
6b49c21206
Merge pull request #483 from gabriel-samfira/fix-inconsistency
Allow referencing runners by ID
2025-08-17 02:08:02 +03:00
Gabriel Adrian Samfira
31ad45eeb6 Allow referencing runners by ID
Although runner names are unique, we still have an ID on the model
which is used as a primary key. We should allow using that ID to
reference a runner in the API.

This change allows users to specify ID or runner name.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-16 23:00:55 +00:00
Gabriel
b4113048bb
Merge pull request #482 from gabriel-samfira/switch-to-fmt-errorf
Switch to fmt.Errorf
2025-08-17 01:28:22 +03:00
Gabriel Adrian Samfira
118319c7c1 Switch to fmt.Errorf
Replace all instances of errors.Wrap() with fmt.Errorf.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-16 22:19:05 +00:00
Gabriel
10dcbec954
Merge pull request #481 from cloudbase/dependabot/npm_and_yarn/webapp/npm_and_yarn-2ea0fb2c37
Bump the npm_and_yarn group across 1 directory with 2 updates
2025-08-16 14:31:17 +03:00
dependabot[bot]
b58bf4c895
Bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the /webapp directory: [tmp](https://github.com/raszi/node-tmp) and [@openapitools/openapi-generator-cli](https://github.com/OpenAPITools/openapi-generator-cli).


Removes `tmp`

Updates `@openapitools/openapi-generator-cli` from 2.21.4 to 2.22.0
- [Release notes](https://github.com/OpenAPITools/openapi-generator-cli/releases)
- [Changelog](https://github.com/OpenAPITools/openapi-generator-cli/blob/master/.releaserc)
- [Commits](https://github.com/OpenAPITools/openapi-generator-cli/compare/v2.21.4...v2.22.0)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: "@openapitools/openapi-generator-cli"
  dependency-version: 2.22.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-16 10:25:06 +00:00
Gabriel
8cc96dbcd1
Merge pull request #479 from gabriel-samfira/spa-webapp
Spa webapp
2025-08-16 13:23:42 +03:00
Gabriel Adrian Samfira
eec158b32c Add SPA UI for GARM
This change adds a single page application front-end to GARM. It uses
a generated REST client, built from the swagger definitions, the websocket
interface for live updates of entities and eager loading of everything
except runners, as users may have many runners and we don't want to load
hundreds of runners in memory.

Proper pagination should be implemented in the API, in future commits,
to avoid loading lots of elements for no reason.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-16 09:09:13 +00:00
Gabriel Adrian Samfira
a811d129d0 Update .gitignore
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-12 12:48:48 +00:00
Gabriel Adrian Samfira
98a769b8d1 Allow cookie login to API endpoints
This change considers cookies as a source for the JWT token.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-12 12:48:48 +00:00
Gabriel Adrian Samfira
5a6ac12118 Fix for gitea tools and scale set cleanup
Filter out gitea tools to only consider archived downloads. This
should help in situations where bandwidth is more important than
CPU time used to unarchive the tools.

Also a drive by fix for scale sets cleanup.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-12 12:48:48 +00:00
Gabriel Adrian Samfira
325bca4af3 Add swagger annotations and updates
Add swagger annotations to models to allow generating a full swagger
definition. This will help generate clients in other languages if needed.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-12 12:48:48 +00:00
Gabriel Adrian Samfira
b2dee1d844 Preload missing resources
There are some inconsistencies in the way the API returns some
values for pools and scale sets. This is due to not preloading
the appropriate relations.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-12 12:48:48 +00:00
Gabriel
9f2764f614
Merge pull request #478 from cloudbase/dependabot/go_modules/github.com/go-openapi/errors-0.22.2
Bump github.com/go-openapi/errors from 0.22.1 to 0.22.2
2025-08-11 21:20:33 +03:00
dependabot[bot]
37eba0fed9
Bump github.com/go-openapi/errors from 0.22.1 to 0.22.2
Bumps [github.com/go-openapi/errors](https://github.com/go-openapi/errors) from 0.22.1 to 0.22.2.
- [Commits](https://github.com/go-openapi/errors/compare/v0.22.1...v0.22.2)

---
updated-dependencies:
- dependency-name: github.com/go-openapi/errors
  dependency-version: 0.22.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 10:34:11 +00:00
Gabriel
389a8538af
Merge pull request #476 from cloudbase/dependabot/go_modules/golang.org/x/crypto-0.41.0
Bump golang.org/x/crypto from 0.40.0 to 0.41.0
2025-08-08 12:54:56 +03:00
dependabot[bot]
f24a22d537
Bump golang.org/x/crypto from 0.40.0 to 0.41.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/crypto/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 09:47:38 +00:00
Gabriel
dfa9848999
Merge pull request #477 from cloudbase/dependabot/go_modules/golang.org/x/mod-0.27.0
Bump golang.org/x/mod from 0.26.0 to 0.27.0
2025-08-08 12:46:12 +03:00
dependabot[bot]
3f51046279
Bump golang.org/x/mod from 0.26.0 to 0.27.0
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/mod/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-08 06:49:58 +00:00
Gabriel
256521ac38
Merge pull request #475 from cloudbase/dependabot/go_modules/github.com/cloudbase/garm-provider-common-0.1.7
Bump github.com/cloudbase/garm-provider-common from 0.1.6 to 0.1.7
2025-08-07 15:34:27 +03:00
dependabot[bot]
e2169865a1
Bump github.com/cloudbase/garm-provider-common from 0.1.6 to 0.1.7
Bumps [github.com/cloudbase/garm-provider-common](https://github.com/cloudbase/garm-provider-common) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/cloudbase/garm-provider-common/releases)
- [Commits](https://github.com/cloudbase/garm-provider-common/compare/v0.1.6...v0.1.7)

---
updated-dependencies:
- dependency-name: github.com/cloudbase/garm-provider-common
  dependency-version: 0.1.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-07 07:05:40 +00:00
Gabriel
fd9a4d544a
Merge pull request #474 from gabriel-samfira/fix-image-tag
Get the image tag from supplied ref
2025-08-07 00:13:51 +03:00
Gabriel Adrian Samfira
20a16d923c
Get the image tag from supplied ref
We need to pass the ref used in the workflow. If we supply a tag,
we should just get that same tag. If we supply a branch, we should
get the latest release from that branch.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-07 00:12:35 +03:00
Gabriel
212f6fff42
Merge pull request #473 from gabriel-samfira/fix-build-image
Use the Dockerfile from the main branch
2025-08-06 23:11:29 +03:00
Gabriel Adrian Samfira
2ee2fca8ae
Use the Dockerfile from the main branch
There is no way to change the Dockerfile in a tag. We need to
use the Dockerfile in the main branch. So even if we're buildin
the image for a stable version, we need to check out the main branch.

The Dockerfile will take care of checking out the proper tags.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-06 23:09:01 +03:00
Gabriel Adrian Samfira
5915107446
WiP
Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-06 23:03:45 +03:00
Gabriel
e43acd5b65
Merge pull request #472 from cloudbase/dependabot/go_modules/github.com/prometheus/client_golang-1.23.0
Bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0
2025-08-01 10:10:35 +03:00
dependabot[bot]
f85fe3d63f
Bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.22.0 to 1.23.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.23.0/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.22.0...v1.23.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-01 06:26:29 +00:00
Gabriel
8082e1a30e
Merge pull request #456 from chickenkiller/feat/refactor-builds
Refactor CI/CD builds
2025-07-31 10:58:16 +03:00
Gabriel
daf2a13e48
Merge pull request #471 from cloudbase/dependabot/go_modules/github.com/golang-jwt/jwt/v5-5.3.0
Bump github.com/golang-jwt/jwt/v5 from 5.2.3 to 5.3.0
2025-07-31 10:14:59 +03:00
dependabot[bot]
4ad7d8e856
Bump github.com/golang-jwt/jwt/v5 from 5.2.3 to 5.3.0
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.3 to 5.3.0.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.3...v5.3.0)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-31 07:04:47 +00:00
Lionel ORRY
22f655f48d fixes after testing 2025-07-31 08:42:16 +02:00
Lionel ORRY
158b35db06 simplify workflows 2025-07-30 14:35:01 +02:00
Lionel ORRY
eb07ed3774 remove obsolete tech debt 2025-07-30 12:12:03 +02:00
Lionel ORRY
0f4f98dd03 put a better git version in providers 2025-07-30 12:12:02 +02:00
Lionel ORRY
f6f22cb686 small fixes 2025-07-30 12:09:34 +02:00
Lionel ORRY
af1c090db5 transfer providers branch computation inside Dockerfile 2025-07-30 12:09:32 +02:00
Lionel ORRY
be3026e87c fix branch names 2025-07-30 12:04:02 +02:00
Lionel ORRY
5152bab1b8 fix branch names 2025-07-30 12:04:02 +02:00
Lionel ORRY
3687c7fea4 activate release/v2 nightly build 2025-07-30 12:04:02 +02:00
Lionel ORRY
1e8d0d79a6 improvements after review comments 2025-07-30 12:04:02 +02:00
Lionel ORRY
97ef92706b refactor workflows to enable multiple docker images 2025-07-30 12:04:00 +02:00
Gabriel
53cdfd70a1
Merge pull request #470 from cloudbase/dependabot/go_modules/github.com/jedib0t/go-pretty/v6-6.6.8
Bump github.com/jedib0t/go-pretty/v6 from 6.6.7 to 6.6.8
2025-07-28 12:30:47 +03:00
dependabot[bot]
7817d20516
Bump github.com/jedib0t/go-pretty/v6 from 6.6.7 to 6.6.8
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.6.7 to 6.6.8.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.6.7...v6.6.8)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-version: 6.6.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 08:04:00 +00:00
Gabriel
daadad682b
Merge pull request #468 from cloudbase/dependabot/go_modules/gorm.io/gorm-1.30.1
Bump gorm.io/gorm from 1.30.0 to 1.30.1
2025-07-24 09:55:44 +03:00
dependabot[bot]
f4892be193
Bump gorm.io/gorm from 1.30.0 to 1.30.1
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.30.0 to 1.30.1.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.30.0...v1.30.1)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-version: 1.30.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-24 06:47:05 +00:00
Gabriel
e687565671
Merge pull request #467 from cloudbase/dependabot/go_modules/github.com/cloudbase/garm-provider-common-0.1.6
Bump github.com/cloudbase/garm-provider-common from 0.1.5 to 0.1.6
2025-07-21 11:45:24 +03:00
dependabot[bot]
567c465ad7
Bump github.com/cloudbase/garm-provider-common from 0.1.5 to 0.1.6
Bumps [github.com/cloudbase/garm-provider-common](https://github.com/cloudbase/garm-provider-common) from 0.1.5 to 0.1.6.
- [Release notes](https://github.com/cloudbase/garm-provider-common/releases)
- [Commits](https://github.com/cloudbase/garm-provider-common/compare/v0.1.5...v0.1.6)

---
updated-dependencies:
- dependency-name: github.com/cloudbase/garm-provider-common
  dependency-version: 0.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 07:58:08 +00:00
Gabriel
f1fed3c462
Merge pull request #466 from gabriel-samfira/update-docs-and-defaults
Update docs and deprecate the --all flag
2025-07-20 23:38:50 +03:00
Gabriel Adrian Samfira
80735ac2eb Update docs and deprecate the --all flag
Update the docs to reflect the latest stable version and deprecate the
--all flag for runner list and pool list.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-07-20 20:31:56 +00:00
Gabriel
bb45324dcd
Merge pull request #459 from gabriel-samfira/account-for-job-id
Handle new jobID for scale sets
2025-07-18 11:39:06 +03:00
Gabriel Adrian Samfira
a984782fd7 Handle new jobID for scale sets
There seems to be a change in the scale set message. It now includes
a jobID and sets the runner request ID to 0. This change adds separate
job ID fields for workflow jobs and scaleset jobs.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-07-18 08:20:50 +00:00
Gabriel
d26973da2a
Merge pull request #458 from gabriel-samfira/fix-scaleset-param
Fix scale set param
2025-07-17 20:06:14 +03:00
Gabriel Adrian Samfira
69779a0a7d Fix scale set param
Do not look for a name when composing the scale set. Preload may not
have been called on an entity, but we still have the ID, which is the
only thing needed when GetEntity() is called.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-07-17 17:01:20 +00:00
Gabriel
c95252547e
Merge pull request #454 from gabriel-samfira/fix-encoding
Handle query args
2025-07-16 17:52:02 +03:00
Gabriel
d17b168b4e
Merge pull request #453 from cloudbase/dependabot/go_modules/github.com/golang-jwt/jwt/v5-5.2.3
Bump github.com/golang-jwt/jwt/v5 from 5.2.2 to 5.2.3
2025-07-16 10:36:26 +03:00
dependabot[bot]
a46c474640
Bump github.com/golang-jwt/jwt/v5 from 5.2.2 to 5.2.3
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.2 to 5.2.3.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.2...v5.2.3)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-version: 5.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-16 06:41:38 +00:00
Gabriel Adrian Samfira
65d6d1ae87 Handle query args
Merge any query args from both the GH url and the supplied URL.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-07-16 06:39:46 +00:00
Gabriel
78547fefaa
Merge pull request #450 from cloudbase/dependabot/go_modules/golang.org/x/mod-0.26.0
Bump golang.org/x/mod from 0.25.0 to 0.26.0
2025-07-11 14:48:26 +03:00
dependabot[bot]
0cc51e48ef
Bump golang.org/x/mod from 0.25.0 to 0.26.0
---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-version: 0.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 09:34:59 +00:00
Gabriel
86b8ac9e94
Merge pull request #451 from cloudbase/dependabot/go_modules/golang.org/x/crypto-0.40.0
Bump golang.org/x/crypto from 0.39.0 to 0.40.0
2025-07-11 12:33:37 +03:00
dependabot[bot]
f9b1b26f80
Bump golang.org/x/crypto from 0.39.0 to 0.40.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/crypto/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 06:28:17 +00:00
Gabriel
19e025c2be
Merge pull request #448 from gabriel-samfira/fix-sleep
Fix sleepWithCancel and ensure closed channel
2025-07-07 08:02:32 +03:00
Gabriel Adrian Samfira
b23bca73bc Fix sleepWithCancel and ensure closed channel
* time.NewTicker will panic if the duration is 0. Make it return
early if duration is 0.
* Return a pre-closed channel in Wait() instead of nil. Ensures receiver
will not block forever.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-07-07 04:54:10 +00:00
Gabriel
20d4e68fd4
Merge pull request #447 from gabriel-samfira/fix-makefile
Use the -v flag on podman only
2025-07-07 00:26:46 +03:00
Gabriel Adrian Samfira
6ae3b25b4d Use the -v flag on podman only
Docker does not support the --volume flag at build time. This needs
to be done in the Dockerfile directly on the RUN stanza. Will update
in a future PR, until then, just set the flag for podman.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-07-06 21:17:31 +00:00
504 changed files with 75483 additions and 8039 deletions

View file

@ -1,17 +1,17 @@
name: "Build GARM images"
name: "Build and push GARM images"
on:
workflow_dispatch:
workflow_call:
inputs:
push_to_project:
description: "Project to build images for"
required: true
required: false
type: string
default: "ghcr.io/cloudbase"
ref:
description: "Ref to build"
required: true
required: false
type: string
default: "main"
schedule:
- cron: "0 2 * * *"
permissions:
contents: read
@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: src/github.com/cloudbase/garm
fetch-depth: 0
@ -39,35 +39,16 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
- name: Build and push image
env:
IMAGE_REGISTRY: ${{ inputs.push_to_project }}
GH_REF: ${{ inputs.ref }}
working-directory: src/github.com/cloudbase/garm
run: |
set -x
REGISTRY_INPUT="${{ github.event.inputs.push_to_project }}"
REF_INPUT="${{ github.event.inputs.ref }}"
PUSH_TO_PROJECT="${REGISTRY_INPUT:-ghcr.io/cloudbase}"
GH_REF="${REF_INPUT:-main}"
cd src/github.com/cloudbase/garm && git checkout "${GH_REF}"
VERSION=$(git describe --tags --match='v[0-9]*' --always)
AZURE_REF=v0.1.0
OPENSTACK_REF=v0.1.0
LXD_REF=v0.1.0
INCUS_REF=v0.1.0
AWS_REF=v0.1.0
GCP_REF=v0.1.0
EQUINIX_REF=v0.1.0
K8S_REF=v0.3.2
if [ "$GH_REF" == "main" ]; then
AZURE_REF="main"
OPENSTACK_REF="main"
LXD_REF="main"
INCUS_REF="main"
AWS_REF="main"
GCP_REF="main"
EQUINIX_REF="main"
K8S_REF="main"
VERSION="nightly"
IMAGE_TAG="nightly"
else
IMAGE_TAG=$(git describe --tags --match='v[0-9]*' --always ${GH_REF})
fi
docker buildx build \
--provenance=false \
@ -76,13 +57,5 @@ jobs:
--label "org.opencontainers.image.description=GARM ${GH_REF}" \
--label "org.opencontainers.image.licenses=Apache 2.0" \
--build-arg="GARM_REF=${GH_REF}" \
--build-arg="AZURE_REF=${AZURE_REF}" \
--build-arg="OPENSTACK_REF=${OPENSTACK_REF}" \
--build-arg="LXD_REF=${LXD_REF}" \
--build-arg="INCUS_REF=${INCUS_REF}" \
--build-arg="AWS_REF=${AWS_REF}" \
--build-arg="GCP_REF=${GCP_REF}" \
--build-arg="EQUINIX_REF=${EQUINIX_REF}" \
--build-arg="K8S_REF=${K8S_REF}" \
-t ${PUSH_TO_PROJECT}/garm:"${VERSION}" \
-t ${IMAGE_REGISTRY}/garm:"${IMAGE_TAG}" \
--push .

View file

@ -28,10 +28,11 @@ jobs:
sudo apt-get update
sudo apt-get install -y libbtrfs-dev build-essential apg jq
- uses: actions/checkout@v3
- uses: actions/setup-go@v5
with:
go-version: '^1.22.3'
- uses: actions/checkout@v3
go-version-file: go.mod
- name: make lint
run: make golangci-lint && GOLANGCI_LINT_EXTRA_ARGS="--timeout=8m --build-tags=testing,integration" make lint
- name: Verify go vendor, go modules and gofmt
@ -43,15 +44,39 @@ jobs:
runs-on: ubuntu-latest
needs: [linters]
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libbtrfs-dev build-essential apg jq default-jre
- uses: actions/setup-node@v4
with:
node-version: '>=v24.5.0'
- name: Set up openapi-generator-cli
run: |
mkdir -p $HOME/openapi-generator
cd $HOME/openapi-generator
npm install @openapitools/openapi-generator-cli
echo "$HOME/openapi-generator/node_modules/.bin" >> $GITHUB_PATH
- name: Checkout
uses: actions/checkout@v3
- name: Setup Golang
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go version
- name: Run go generate
run: |
GOTOOLCHAIN=go1.24.6 make generate
- name: Run GARM Go Tests
run: make go-test
- name: Run web UI tests
run: |
make webui-test

19
.github/workflows/trigger-manual.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: Manual build of GARM images
on:
workflow_dispatch:
inputs:
push_to_project:
description: "Project to build images for"
required: true
default: "ghcr.io/cloudbase"
ref:
description: "Ref to build"
required: true
default: "main"
jobs:
call-build-and-push:
uses: ./.github/workflows/build-and-push.yml
with:
push_to_project: ${{ inputs.push_to_project }}
ref: ${{ inputs.ref }}

10
.github/workflows/trigger-nightly.yml vendored Normal file
View file

@ -0,0 +1,10 @@
name: Nightly build of GARM images
on:
schedule:
- cron: "0 2 * * *"
jobs:
call-build-and-push:
uses: ./.github/workflows/build-and-push.yml
with:
ref: "main"

6
.gitignore vendored
View file

@ -19,3 +19,9 @@ bin/
cmd/temp
build/
release/
node_modules/
.svelte-kit/
debug.html
git_push.sh
webapp/src/lib/api/generated/docs
.env

27
.mockery.yaml Normal file
View file

@ -0,0 +1,27 @@
with-expecter: true
dir: "mocks"
mockname: "{{ .InterfaceName }}"
outpkg: "mocks"
filename: "{{ .InterfaceName }}.go"
# V3 compatibility settings
resolve-type-alias: false
disable-version-string: true
issue-845-fix: true
packages:
# Database store interfaces
github.com/cloudbase/garm/database/common:
interfaces:
Store:
config:
dir: "{{ .InterfaceDir }}/mocks"
# Runner interfaces
github.com/cloudbase/garm/runner:
interfaces:
PoolManagerController:
config:
dir: "{{ .InterfaceDir }}/mocks"
# Runner common interfaces (generate all interfaces in this package)
github.com/cloudbase/garm/runner/common:
config:
dir: "{{ .InterfaceDir }}/mocks"
all: true

View file

@ -1,49 +1,63 @@
FROM docker.io/golang:alpine AS builder
ARG GARM_REF
ARG AZURE_REF=v0.1.0
ARG OPENSTACK_REF=v0.1.0
ARG LXD_REF=v0.1.0
ARG INCUS_REF=v0.1.0
ARG AWS_REF=v0.1.0
ARG GCP_REF=v0.1.0
ARG EQUINIX_REF=v0.1.0
ARG K8S_REF=v0.3.2
LABEL stage=builder
RUN apk add musl-dev gcc libtool m4 autoconf g++ make libblkid util-linux-dev git linux-headers upx
RUN git config --global --add safe.directory /build
RUN apk add --no-cache musl-dev gcc libtool m4 autoconf g++ make libblkid util-linux-dev git linux-headers upx curl jq
RUN git config --global --add safe.directory /build && git config --global --add advice.detachedHead false
RUN echo ${GARM_REF}
ADD . /build/garm
RUN cd /build/garm && git checkout ${GARM_REF}
RUN git clone --depth 1 --branch ${AZURE_REF} https://github.com/cloudbase/garm-provider-azure /build/garm-provider-azure
RUN git clone --depth 1 --branch ${OPENSTACK_REF} https://github.com/cloudbase/garm-provider-openstack /build/garm-provider-openstack
RUN git clone --depth 1 --branch ${LXD_REF} https://github.com/cloudbase/garm-provider-lxd /build/garm-provider-lxd
RUN git clone --depth 1 --branch ${INCUS_REF} https://github.com/cloudbase/garm-provider-incus /build/garm-provider-incus
RUN git clone --depth 1 --branch ${AWS_REF} https://github.com/cloudbase/garm-provider-aws /build/garm-provider-aws
RUN git clone --depth 1 --branch ${GCP_REF} https://github.com/cloudbase/garm-provider-gcp /build/garm-provider-gcp
RUN git clone --depth 1 --branch ${EQUINIX_REF} https://github.com/cloudbase/garm-provider-equinix /build/garm-provider-equinix
RUN git clone --depth 1 --branch ${K8S_REF} https://github.com/mercedes-benz/garm-provider-k8s /build/garm-provider-k8s
RUN cd /build/garm && go build -o /bin/garm \
-tags osusergo,netgo,sqlite_omit_load_extension \
-ldflags "-linkmode external -extldflags '-static' -s -w -X github.com/cloudbase/garm/util/appdefaults.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" \
/build/garm/cmd/garm && upx /bin/garm
RUN cd /build/garm/cmd/garm-cli && go build -o /bin/garm-cli \
-tags osusergo,netgo,sqlite_omit_load_extension \
-ldflags "-linkmode external -extldflags '-static' -s -w -X github.com/cloudbase/garm/util/appdefaults.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" \
. && upx /bin/garm-cli
RUN mkdir -p /opt/garm/providers.d
RUN cd /build/garm-provider-azure && go build -ldflags="-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" -o /opt/garm/providers.d/garm-provider-azure . && upx /opt/garm/providers.d/garm-provider-azure
RUN cd /build/garm-provider-openstack && go build -ldflags="-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" -o /opt/garm/providers.d/garm-provider-openstack . && upx /opt/garm/providers.d/garm-provider-openstack
RUN cd /build/garm-provider-lxd && go build -ldflags="-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" -o /opt/garm/providers.d/garm-provider-lxd . && upx /opt/garm/providers.d/garm-provider-lxd
RUN cd /build/garm-provider-incus && go build -ldflags="-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" -o /opt/garm/providers.d/garm-provider-incus . && upx /opt/garm/providers.d/garm-provider-incus
RUN cd /build/garm-provider-aws && go build -ldflags="-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" -o /opt/garm/providers.d/garm-provider-aws . && upx /opt/garm/providers.d/garm-provider-aws
RUN cd /build/garm-provider-gcp && go build -ldflags="-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" -o /opt/garm/providers.d/garm-provider-gcp . && upx /opt/garm/providers.d/garm-provider-gcp
RUN cd /build/garm-provider-equinix && go build -ldflags="-linkmode external -extldflags '-static' -s -w -X main.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" -o /opt/garm/providers.d/garm-provider-equinix . && upx /opt/garm/providers.d/garm-provider-equinix
RUN cd /build/garm-provider-k8s/cmd/garm-provider-k8s && go build -ldflags="-linkmode external -extldflags '-static' -s -w" -o /opt/garm/providers.d/garm-provider-k8s . && upx /opt/garm/providers.d/garm-provider-k8s
RUN git -C /build/garm checkout ${GARM_REF}
RUN cd /build/garm \
&& go build -o /bin/garm \
-tags osusergo,netgo,sqlite_omit_load_extension \
-ldflags "-linkmode external -extldflags '-static' -s -w -X github.com/cloudbase/garm/util/appdefaults.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" \
/build/garm/cmd/garm && upx /bin/garm
RUN cd /build/garm/cmd/garm-cli \
&& go build -o /bin/garm-cli \
-tags osusergo,netgo,sqlite_omit_load_extension \
-ldflags "-linkmode external -extldflags '-static' -s -w -X github.com/cloudbase/garm/util/appdefaults.Version=$(git describe --tags --match='v[0-9]*' --dirty --always)" \
. && upx /bin/garm-cli
RUN set -ex; \
mkdir -p /opt/garm/providers.d; \
for repo in \
cloudbase/garm-provider-azure \
cloudbase/garm-provider-openstack \
cloudbase/garm-provider-lxd \
cloudbase/garm-provider-incus \
cloudbase/garm-provider-aws \
cloudbase/garm-provider-gcp \
cloudbase/garm-provider-equinix \
flatcar/garm-provider-linode \
mercedes-benz/garm-provider-k8s; \
do \
export PROVIDER_NAME="$(basename $repo)"; \
export PROVIDER_SUBDIR=""; \
if [ "$GARM_REF" == "main" ]; then \
export PROVIDER_REF="main"; \
else \
export PROVIDER_REF="$(curl -s -L https://api.github.com/repos/$repo/releases/latest | jq -r '.tag_name')"; \
fi; \
git clone --branch "$PROVIDER_REF" "https://github.com/$repo" "/build/$PROVIDER_NAME"; \
case $PROVIDER_NAME in \
"garm-provider-k8s") \
export PROVIDER_SUBDIR="cmd/garm-provider-k8s"; \
export PROVIDER_LDFLAGS="-linkmode external -extldflags \"-static\" -s -w"; \
;; \
"garm-provider-linode") \
export PROVIDER_LDFLAGS="-linkmode external -extldflags \"-static\" -s -w"; \
;; \
*) \
export PROVIDER_VERSION=$(git -C /build/$PROVIDER_NAME describe --tags --match='v[0-9]*' --dirty --always); \
export PROVIDER_LDFLAGS="-linkmode external -extldflags \"-static\" -s -w -X main.Version=$PROVIDER_VERSION"; \
;; \
esac; \
cd "/build/$PROVIDER_NAME/$PROVIDER_SUBDIR" \
&& go build -ldflags="$PROVIDER_LDFLAGS" -o /opt/garm/providers.d/$PROVIDER_NAME . \
&& upx /opt/garm/providers.d/$PROVIDER_NAME; \
done
FROM busybox

View file

@ -7,8 +7,9 @@ GEN_PASSWORD=$(shell (/usr/bin/apg -n1 -m32))
IMAGE_TAG = garm-build
IMAGE_BUILDER=$(shell (which docker || which podman))
USER_ID=$(shell (($(IMAGE_BUILDER) --version | grep -q podman) && echo "0" || id -u))
USER_GROUP=$(shell (($(IMAGE_BUILDER) --version | grep -q podman) && echo "0" || id -g))
IS_PODMAN=$(shell (($(IMAGE_BUILDER) --version | grep -q podman) && echo "yes" || echo "no"))
USER_ID=$(if $(filter yes,$(IS_PODMAN)),0,$(shell id -u))
USER_GROUP=$(if $(filter yes,$(IS_PODMAN)),0,$(shell id -g))
ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
GOPATH ?= $(shell go env GOPATH)
VERSION ?= $(shell git describe --tags --match='v[0-9]*' --dirty --always)
@ -21,6 +22,11 @@ export CREDENTIALS_NAME ?= test-garm-creds
export WORKFLOW_FILE_NAME ?= test.yml
export GARM_ADMIN_USERNAME ?= admin
ifeq ($(IS_PODMAN),yes)
EXTRA_ARGS := -v /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt
endif
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
@ -33,7 +39,7 @@ default: build
.PHONY : build-static test install-lint-deps lint go-test fmt fmtcheck verify-vendor verify create-release-files release
build-static: ## Build garm statically
@echo Building garm
$(IMAGE_BUILDER) build -v /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt --tag $(IMAGE_TAG) -f Dockerfile.build-static .
$(IMAGE_BUILDER) build $(EXTRA_ARGS) --tag $(IMAGE_TAG) -f Dockerfile.build-static .
mkdir -p build
$(IMAGE_BUILDER) run --rm -e USER_ID=$(USER_ID) -e GARM_REF=$(GARM_REF) -e USER_GROUP=$(USER_GROUP) -v $(PWD)/build:/build/output:z $(IMAGE_TAG) /build-static.sh
@echo Binaries are available in $(PWD)/build
@ -49,6 +55,20 @@ build: ## Build garm
@$(GO) build -ldflags "-s -w -X github.com/cloudbase/garm/util/appdefaults.Version=${VERSION}" -tags osusergo,netgo,sqlite_omit_load_extension -o bin/garm-cli ./cmd/garm-cli
@echo Binaries are available in $(PWD)/bin
.PHONY: build-webui
build-webui:
@echo Building GARM web ui
./build-webapp.sh
rm -rf webapp/assets/_app
cp -r webapp/build/* webapp/assets/
.PHONY: generate
generate: ## Run go generate after checking required tools are in PATH
@echo Checking required tools...
@which openapi-generator-cli > /dev/null || (echo "Error: openapi-generator-cli not found in PATH" && exit 1)
@echo Running go generate
@$(GO) generate ./...
test: verify go-test ## Run tests
##@ Release
@ -95,6 +115,9 @@ go-test: ## Run tests
fmt: ## Run go fmt against code.
@$(GO) fmt $$(go list ./...)
webui-test:
(cd webapp && npm install)
(cd webapp && npm run test:run)
##@ Build Dependencies

View file

@ -18,6 +18,7 @@
- [Installing on Kubernetes](#installing-on-kubernetes)
- [Configuring GARM for GHES](#configuring-garm-for-ghes)
- [Configuring GARM for Gitea](#configuring-garm-for-gitea)
- [Enabling the web UI](#enabling-the-web-ui)
- [Using GARM](#using-garm)
- [Supported providers](#supported-providers)
- [Installing external providers](#installing-external-providers)
@ -48,9 +49,11 @@ Here is a brief architectural diagram of how pools work and how GARM reacts to w
**Scale sets** work differently. While pools (as they are defined in GARM) rely on webhooks to know when a job was started and GARM needs to internally make the right decission in terms of which pool should handle that runner, scale sets have a lot of the scheduling and decission making logic done in GitHub itself.
:warning: **Important note**: The README and documentation in the `main` branch are relevant to the not yet released code that is present in `main`. Following the documentation from the `main` branch for a stable release of GARM, may lead to errors. To view the documentation for the latest stable release, please switch to the appropriate tag. For information about setting up `v0.1.5`, please refer to the [v0.1.5 tag](https://github.com/cloudbase/garm/tree/v0.1.5).
> [!IMPORTANT]
> The README and documentation in the `main` branch are relevant to the not yet released code that is present in `main`. Following the documentation from the `main` branch for a stable release of GARM, may lead to errors. To view the documentation for the latest stable release, please switch to the appropriate tag. For information about setting up `v0.1.6`, please refer to the [v0.1.6 tag](https://github.com/cloudbase/garm/tree/v0.1.6).
:warning: **Important note**: The `main` branch holds the latest code and is not guaranteed to be stable. If you are looking for a stable release, please check the releases page. If you plan to use the `main` branch, please do so on a new instance. Do not upgrade from a stable release to `main`.
> [!CAUTION]
> The `main` branch holds the latest code and is not guaranteed to be stable. If you are looking for a stable release, please check the releases page. If you plan to use the `main` branch, please do so on a new instance. Do not upgrade from a stable release to `main`.
## Join us on slack
@ -76,6 +79,17 @@ GARM supports creating pools and scale sets in either GitHub itself or in your o
GARM now has support for Gitea (>=1.24.0). For information on getting started with Gitea, see the [Gitea quickstart](/doc/gitea.md) document.
## Enabling the web UI
GARM now ships with a single page application. To enable it, add the following to your GARM config:
```toml
[apiserver.webui]
enable = true
```
Check the [README.md](/webapp/README.md) file for details on the web UI.
## Using GARM
GARM is designed with simplicity in mind. At least we try to keep it as simple as possible. We're aware that adding a new tool in your workflow can be painful, especially when you already have to deal with so many. The cognitive load for OPS has reached a level where it feels overwhelming at times to even wrap your head around a new tool. As such, we believe that tools should be simple, should take no more than a few hours to understand and set up and if you absolutely need to interact with the tool, it should be as intuitive as possible. Although we try our best to make this happen, we're aware that GARM has some rough edges, especially for new users. If you encounter issues or feel like the setup process was too complicated, please let us know. We're always looking to improve the user experience.

View file

@ -17,30 +17,57 @@ package controllers
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"strings"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
gErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm-provider-common/util"
"github.com/cloudbase/garm/apiserver/events"
"github.com/cloudbase/garm/apiserver/params"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/metrics"
runnerParams "github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner" //nolint:typecheck
garmUtil "github.com/cloudbase/garm/util"
wsWriter "github.com/cloudbase/garm/websocket"
"github.com/cloudbase/garm/workers/websocket/events"
)
func NewAPIController(r *runner.Runner, authenticator *auth.Authenticator, hub *wsWriter.Hub) (*APIController, error) {
func NewAPIController(r *runner.Runner, authenticator *auth.Authenticator, hub *wsWriter.Hub, apiCfg config.APIServer) (*APIController, error) {
controllerInfo, err := r.GetControllerInfo(auth.GetAdminContext(context.Background()))
if err != nil {
return nil, errors.Wrap(err, "failed to get controller info")
return nil, fmt.Errorf("failed to get controller info: %w", err)
}
var checkOrigin func(r *http.Request) bool
if len(apiCfg.CORSOrigins) > 0 {
checkOrigin = func(r *http.Request) bool {
origin := r.Header["Origin"]
if len(origin) == 0 {
return true
}
u, err := url.Parse(origin[0])
if err != nil {
return false
}
for _, val := range apiCfg.CORSOrigins {
corsVal, err := url.Parse(val)
if err != nil {
continue
}
if garmUtil.ASCIIEqualFold(u.Host, corsVal.Host) {
return true
}
}
return false
}
}
return &APIController{
r: r,
@ -49,6 +76,7 @@ func NewAPIController(r *runner.Runner, authenticator *auth.Authenticator, hub *
upgrader: websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 16384,
CheckOrigin: checkOrigin,
},
controllerID: controllerInfo.ControllerID.String(),
}, nil
@ -64,24 +92,22 @@ type APIController struct {
func handleError(ctx context.Context, w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json")
origErr := errors.Cause(err)
apiErr := params.APIErrorResponse{
Details: origErr.Error(),
Details: err.Error(),
}
switch origErr.(type) {
case *gErrors.NotFoundError:
switch {
case errors.Is(err, gErrors.ErrNotFound):
w.WriteHeader(http.StatusNotFound)
apiErr.Error = "Not Found"
case *gErrors.UnauthorizedError:
case errors.Is(err, gErrors.ErrUnauthorized):
w.WriteHeader(http.StatusUnauthorized)
apiErr.Error = "Not Authorized"
// Don't include details on 401 errors.
apiErr.Details = ""
case *gErrors.BadRequestError:
case errors.Is(err, gErrors.ErrBadRequest):
w.WriteHeader(http.StatusBadRequest)
apiErr.Error = "Bad Request"
case *gErrors.DuplicateUserError, *gErrors.ConflictError:
case errors.Is(err, gErrors.ErrDuplicateEntity), errors.Is(err, &gErrors.ConflictError{}):
w.WriteHeader(http.StatusConflict)
apiErr.Error = "Conflict"
default:

View file

@ -14,6 +14,7 @@
package params
// swagger:model APIErrorResponse
// APIErrorResponse holds information about an error, returned by the API
type APIErrorResponse struct {
Error string `json:"error"`

View file

@ -57,6 +57,8 @@ import (
"github.com/cloudbase/garm/apiserver/controllers"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/config"
spaAssets "github.com/cloudbase/garm/webapp/assets"
)
func WithMetricsRouter(parentRouter *mux.Router, disableAuth bool, metricsMiddlerware auth.Middleware) *mux.Router {
@ -82,6 +84,30 @@ func WithDebugServer(parentRouter *mux.Router) *mux.Router {
return parentRouter
}
func WithWebUI(parentRouter *mux.Router, apiConfig config.APIServer) *mux.Router {
if parentRouter == nil {
return nil
}
if apiConfig.WebUI.EnableWebUI {
slog.Info("WebUI is enabled, adding webapp routes")
webappPath := apiConfig.WebUI.GetWebappPath()
slog.Info("Using webapp path", "path", webappPath)
// Accessing / should redirect to the UI
parentRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, webappPath, http.StatusMovedPermanently) // 301
})
// Serve the SPA with dynamic path
parentRouter.PathPrefix(webappPath).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
spaAssets.ServeSPAWithPath(w, r, webappPath)
}).Methods("GET")
} else {
slog.Info("WebUI is disabled, skipping webapp routes")
}
return parentRouter
}
func requestLogger(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// gathers metrics from the upstream handlers
@ -505,7 +531,7 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/ws/events/", http.HandlerFunc(han.EventsHandler)).Methods("GET")
apiRouter.Handle("/ws/events", http.HandlerFunc(han.EventsHandler)).Methods("GET")
// NotFound handler
// NotFound handler - this should be last
apiRouter.PathPrefix("/").HandlerFunc(han.NotFoundHandler).Methods("GET", "POST", "PUT", "DELETE", "OPTIONS")
return router
}

View file

@ -16,11 +16,12 @@ package auth
import (
"context"
"errors"
"fmt"
"time"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/nbutton23/zxcvbn-go"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -49,7 +50,7 @@ func (a *Authenticator) IsInitialized() bool {
func (a *Authenticator) GetJWTToken(ctx context.Context) (string, error) {
tokenID, err := util.GetRandomString(16)
if err != nil {
return "", errors.Wrap(err, "generating random string")
return "", fmt.Errorf("error generating random string: %w", err)
}
expireToken := time.Now().Add(a.cfg.TimeToLive.Duration())
expires := &jwt.NumericDate{
@ -72,7 +73,7 @@ func (a *Authenticator) GetJWTToken(ctx context.Context) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(a.cfg.Secret))
if err != nil {
return "", errors.Wrap(err, "fetching token string")
return "", fmt.Errorf("error fetching token string: %w", err)
}
return tokenString, nil
@ -87,7 +88,7 @@ func (a *Authenticator) GetJWTMetricsToken(ctx context.Context) (string, error)
tokenID, err := util.GetRandomString(16)
if err != nil {
return "", errors.Wrap(err, "generating random string")
return "", fmt.Errorf("error generating random string: %w", err)
}
// nolint:golangci-lint,godox
// TODO: currently this is the same TTL as the normal Token
@ -111,7 +112,7 @@ func (a *Authenticator) GetJWTMetricsToken(ctx context.Context) (string, error)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(a.cfg.Secret))
if err != nil {
return "", errors.Wrap(err, "fetching token string")
return "", fmt.Errorf("error fetching token string: %w", err)
}
return tokenString, nil
@ -121,7 +122,7 @@ func (a *Authenticator) InitController(ctx context.Context, param params.NewUser
_, err := a.store.ControllerInfo()
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return params.User{}, errors.Wrap(err, "initializing controller")
return params.User{}, fmt.Errorf("error initializing controller: %w", err)
}
}
if a.store.HasAdminUser(ctx) {
@ -151,7 +152,7 @@ func (a *Authenticator) InitController(ctx context.Context, param params.NewUser
hashed, err := util.PaswsordToBcrypt(param.Password)
if err != nil {
return params.User{}, errors.Wrap(err, "creating user")
return params.User{}, fmt.Errorf("error creating user: %w", err)
}
param.Password = hashed
@ -169,7 +170,7 @@ func (a *Authenticator) AuthenticateUser(ctx context.Context, info params.Passwo
if errors.Is(err, runnerErrors.ErrNotFound) {
return ctx, runnerErrors.ErrUnauthorized
}
return ctx, errors.Wrap(err, "authenticating")
return ctx, fmt.Errorf("error authenticating: %w", err)
}
if !user.Enabled {

View file

@ -38,8 +38,8 @@ type initRequired struct {
func (i *initRequired) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctrlInfo, err := i.store.ControllerInfo()
if err != nil || ctrlInfo.ControllerID.String() == "" {
if !i.store.HasAdminUser(ctx) {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusConflict)
if err := json.NewEncoder(w).Encode(params.InitializationRequired); err != nil {

View file

@ -24,7 +24,6 @@ import (
"time"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params"
@ -91,7 +90,7 @@ func (i *instanceToken) NewInstanceJWTToken(instance params.Instance, entity par
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(i.jwtSecret))
if err != nil {
return "", errors.Wrap(err, "signing token")
return "", fmt.Errorf("error signing token: %w", err)
}
return tokenString, nil
@ -121,7 +120,7 @@ func (amw *instanceMiddleware) claimsToContext(ctx context.Context, claims *Inst
return nil, runnerErrors.ErrUnauthorized
}
instanceInfo, err := amw.store.GetInstanceByName(ctx, claims.Name)
instanceInfo, err := amw.store.GetInstance(ctx, claims.Name)
if err != nil {
return ctx, runnerErrors.ErrUnauthorized
}

View file

@ -97,26 +97,37 @@ func invalidAuthResponse(ctx context.Context, w http.ResponseWriter) {
}
}
func (amw *jwtMiddleware) getTokenFromRequest(r *http.Request) (string, error) {
authorizationHeader := r.Header.Get("authorization")
if authorizationHeader == "" {
cookie, err := r.Cookie("garm_token")
if err != nil {
return "", fmt.Errorf("failed to get cookie: %w", err)
}
return cookie.Value, nil
}
bearerToken := strings.Split(authorizationHeader, " ")
if len(bearerToken) != 2 {
return "", fmt.Errorf("invalid auth header")
}
return bearerToken[1], nil
}
// Middleware implements the middleware interface
func (amw *jwtMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// nolint:golangci-lint,godox
// TODO: Log error details when authentication fails
ctx := r.Context()
authorizationHeader := r.Header.Get("authorization")
if authorizationHeader == "" {
authToken, err := amw.getTokenFromRequest(r)
if err != nil {
slog.ErrorContext(ctx, "failed to get auth token", "error", err)
invalidAuthResponse(ctx, w)
return
}
bearerToken := strings.Split(authorizationHeader, " ")
if len(bearerToken) != 2 {
invalidAuthResponse(ctx, w)
return
}
claims := &JWTClaims{}
token, err := jwt.ParseWithClaims(bearerToken[1], claims, func(token *jwt.Token) (interface{}, error) {
token, err := jwt.ParseWithClaims(authToken, claims, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("invalid signing method")
}

16
build-webapp.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
set -e
echo "Building GARM SPA (SvelteKit)..."
# Navigate to webapp directory
cd webapp
# Install dependencies if node_modules doesn't exist
npm install
# Build the SPA
echo "Building SPA..."
npm run build
echo "SPA built successfully!"

56
cache/entity_cache.go vendored
View file

@ -15,6 +15,7 @@ package cache
import (
"sync"
"time"
"github.com/cloudbase/garm/params"
)
@ -28,10 +29,16 @@ func init() {
entityCache = ghEntityCache
}
type RunnerGroupEntry struct {
RunnerGroupID int64
time time.Time
}
type EntityItem struct {
Entity params.ForgeEntity
Pools map[string]params.Pool
ScaleSets map[uint]params.ScaleSet
Entity params.ForgeEntity
Pools map[string]params.Pool
ScaleSets map[uint]params.ScaleSet
RunnerGroups map[string]RunnerGroupEntry
}
type EntityCache struct {
@ -80,9 +87,10 @@ func (e *EntityCache) SetEntity(entity params.ForgeEntity) {
cache, ok := e.entities[entity.ID]
if !ok {
e.entities[entity.ID] = EntityItem{
Entity: entity,
Pools: make(map[string]params.Pool),
ScaleSets: make(map[uint]params.ScaleSet),
Entity: entity,
Pools: make(map[string]params.Pool),
ScaleSets: make(map[uint]params.ScaleSet),
RunnerGroups: make(map[string]RunnerGroupEntry),
}
return
}
@ -314,6 +322,42 @@ func (e *EntityCache) GetAllScaleSets() []params.ScaleSet {
return scaleSets
}
func (e *EntityCache) SetEntityRunnerGroup(entityID, runnerGroupName string, runnerGroupID int64) {
e.mux.Lock()
defer e.mux.Unlock()
if _, ok := e.entities[entityID]; ok {
e.entities[entityID].RunnerGroups[runnerGroupName] = RunnerGroupEntry{
RunnerGroupID: runnerGroupID,
time: time.Now().UTC(),
}
}
}
func (e *EntityCache) GetEntityRunnerGroup(entityID, runnerGroupName string) (int64, bool) {
e.mux.Lock()
defer e.mux.Unlock()
if _, ok := e.entities[entityID]; ok {
if runnerGroup, ok := e.entities[entityID].RunnerGroups[runnerGroupName]; ok {
if time.Now().UTC().After(runnerGroup.time.Add(1 * time.Hour)) {
delete(e.entities[entityID].RunnerGroups, runnerGroupName)
return 0, false
}
return runnerGroup.RunnerGroupID, true
}
}
return 0, false
}
func SetEntityRunnerGroup(entityID, runnerGroupName string, runnerGroupID int64) {
entityCache.SetEntityRunnerGroup(entityID, runnerGroupName, runnerGroupID)
}
func GetEntityRunnerGroup(entityID, runnerGroupName string) (int64, bool) {
return entityCache.GetEntityRunnerGroup(entityID, runnerGroupName)
}
func GetEntity(entityID string) (params.ForgeEntity, bool) {
return entityCache.GetEntity(entityID)
}

View file

@ -98,6 +98,22 @@ func (i *InstanceCache) GetInstancesForScaleSet(scaleSetID uint) []params.Instan
return filteredInstances
}
func (i *InstanceCache) GetEntityInstances(entityID string) []params.Instance {
pools := GetEntityPools(entityID)
poolsAsMap := map[string]bool{}
for _, pool := range pools {
poolsAsMap[pool.ID] = true
}
ret := []params.Instance{}
for _, val := range i.GetAllInstances() {
if _, ok := poolsAsMap[val.PoolID]; ok {
ret = append(ret, val)
}
}
return ret
}
func SetInstanceCache(instance params.Instance) {
instanceCache.SetInstance(instance)
}
@ -121,3 +137,7 @@ func GetInstancesForPool(poolID string) []params.Instance {
func GetInstancesForScaleSet(scaleSetID uint) []params.Instance {
return instanceCache.GetInstancesForScaleSet(scaleSetID)
}
func GetEntityInstances(entityID string) []params.Instance {
return instanceCache.GetEntityInstances(entityID)
}

View file

@ -21,7 +21,6 @@ import (
openapiRuntimeClient "github.com/go-openapi/runtime/client"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/pkg/errors"
"github.com/spf13/cobra"
apiClientController "github.com/cloudbase/garm/client/controller"
@ -80,7 +79,7 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
response, err := apiCli.FirstRun.FirstRun(newUserReq, authToken)
if err != nil {
return errors.Wrap(err, "initializing manager")
return fmt.Errorf("error initializing manager: %w", err)
}
newLoginParamsReq := apiClientLogin.NewLoginParams()
@ -91,7 +90,7 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
token, err := apiCli.Login.Login(newLoginParamsReq, authToken)
if err != nil {
return errors.Wrap(err, "authenticating")
return fmt.Errorf("error authenticating: %w", err)
}
cfg.Managers = append(cfg.Managers, config.Manager{
@ -104,7 +103,7 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
cfg.ActiveManager = loginProfileName
if err := cfg.SaveConfig(); err != nil {
return errors.Wrap(err, "saving config")
return fmt.Errorf("error saving config: %w", err)
}
updateUrlsReq := apiClientController.NewUpdateControllerParams()

View file

@ -16,6 +16,7 @@ package cmd
import (
"context"
"os/signal"
"strings"
"github.com/spf13/cobra"
@ -23,7 +24,12 @@ import (
"github.com/cloudbase/garm/cmd/garm-cli/common"
)
var eventsFilters string
var (
eventsFilters string
logLevel string
filters []string
enableColor bool
)
var logCmd = &cobra.Command{
Use: "debug-log",
@ -34,7 +40,19 @@ var logCmd = &cobra.Command{
ctx, stop := signal.NotifyContext(context.Background(), signals...)
defer stop()
reader, err := garmWs.NewReader(ctx, mgr.BaseURL, "/api/v1/ws/logs", mgr.Token, common.PrintWebsocketMessage)
// Parse filters into map
attributeFilters := make(map[string]string)
for _, filter := range filters {
parts := strings.SplitN(filter, "=", 2)
if len(parts) == 2 {
attributeFilters[parts[0]] = parts[1]
}
}
// Create log formatter with filters
logFormatter := common.NewLogFormatter(logLevel, attributeFilters, enableColor)
reader, err := garmWs.NewReader(ctx, mgr.BaseURL, "/api/v1/ws/logs", mgr.Token, logFormatter.FormatWebsocketMessage)
if err != nil {
return err
}
@ -49,5 +67,9 @@ var logCmd = &cobra.Command{
}
func init() {
logCmd.Flags().StringVar(&logLevel, "log-level", "", "Minimum log level to display (DEBUG, INFO, WARN, ERROR)")
logCmd.Flags().StringArrayVar(&filters, "filter", []string{}, "Filter logs by attribute (format: key=value) or message content (msg=text). You can specify this option multiple times. The filter will return true for any of the attributes you set.")
logCmd.Flags().BoolVar(&enableColor, "enable-color", true, "Enable color logging (auto-detects terminal support)")
rootCmd.AddCommand(logCmd)
}

View file

@ -21,7 +21,6 @@ import (
"strings"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/pkg/errors"
"github.com/spf13/cobra"
commonParams "github.com/cloudbase/garm-provider-common/params"
@ -128,12 +127,9 @@ Example:
listEnterprisePoolsReq := apiClientEnterprises.NewListEnterprisePoolsParams()
listEnterprisePoolsReq.EnterpriseID = poolEnterprise
response, err = apiCli.Enterprises.ListEnterprisePools(listEnterprisePoolsReq, authToken)
} else if cmd.Flags().Changed("all") {
} else {
listPoolsReq := apiClientPools.NewListPoolsParams()
response, err = apiCli.Pools.ListPools(listPoolsReq, authToken)
} else {
cmd.Help() //nolint
os.Exit(0)
}
default:
cmd.Help() //nolint
@ -409,11 +405,12 @@ func init() {
poolListCmd.Flags().StringVarP(&poolRepository, "repo", "r", "", "List all pools within this repository.")
poolListCmd.Flags().StringVarP(&poolOrganization, "org", "o", "", "List all pools within this organization.")
poolListCmd.Flags().StringVarP(&poolEnterprise, "enterprise", "e", "", "List all pools within this enterprise.")
poolListCmd.Flags().BoolVarP(&poolAll, "all", "a", false, "List all pools, regardless of org or repo.")
poolListCmd.Flags().BoolVarP(&poolAll, "all", "a", true, "List all pools, regardless of org or repo.")
poolListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
poolListCmd.Flags().StringVar(&endpointName, "endpoint", "", "When using the name of an entity, the endpoint must be specified when multiple entities with the same name exist.")
poolListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all", "enterprise")
poolListCmd.Flags().MarkDeprecated("all", "all pools are listed by default in the absence of --repo, --org or --enterprise.")
poolListCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise", "all")
poolUpdateCmd.Flags().StringVar(&poolImage, "image", "", "The provider-specific image name to use for runners in this pool.")
poolUpdateCmd.Flags().UintVar(&priority, "priority", 0, "When multiple pools match the same labels, priority dictates the order by which they are returned, in descending order.")
@ -473,7 +470,7 @@ func init() {
func extraSpecsFromFile(specsFile string) (json.RawMessage, error) {
data, err := os.ReadFile(specsFile)
if err != nil {
return nil, errors.Wrap(err, "opening specs file")
return nil, fmt.Errorf("error opening specs file: %w", err)
}
return asRawMessage(data)
}
@ -483,14 +480,14 @@ func asRawMessage(data []byte) (json.RawMessage, error) {
// have a valid json.
var unmarshaled interface{}
if err := json.Unmarshal(data, &unmarshaled); err != nil {
return nil, errors.Wrap(err, "decoding extra specs")
return nil, fmt.Errorf("error decoding extra specs: %w", err)
}
var asRawJSON json.RawMessage
var err error
asRawJSON, err = json.Marshal(unmarshaled)
if err != nil {
return nil, errors.Wrap(err, "marshaling json")
return nil, fmt.Errorf("error marshaling json: %w", err)
}
return asRawJSON, nil
}

View file

@ -104,23 +104,32 @@ Example:
response, err = apiCli.Instances.ListPoolInstances(listPoolInstancesReq, authToken)
case 0:
if cmd.Flags().Changed("repo") {
runnerRepo, resErr := resolveRepository(runnerRepository, endpointName)
if resErr != nil {
return resErr
}
listRepoInstancesReq := apiClientRepos.NewListRepoInstancesParams()
listRepoInstancesReq.RepoID = runnerRepository
listRepoInstancesReq.RepoID = runnerRepo
response, err = apiCli.Repositories.ListRepoInstances(listRepoInstancesReq, authToken)
} else if cmd.Flags().Changed("org") {
runnerOrg, resErr := resolveOrganization(runnerOrganization, endpointName)
if resErr != nil {
return resErr
}
listOrgInstancesReq := apiClientOrgs.NewListOrgInstancesParams()
listOrgInstancesReq.OrgID = runnerOrganization
listOrgInstancesReq.OrgID = runnerOrg
response, err = apiCli.Organizations.ListOrgInstances(listOrgInstancesReq, authToken)
} else if cmd.Flags().Changed("enterprise") {
runnerEnt, resErr := resolveEnterprise(runnerEnterprise, endpointName)
if resErr != nil {
return resErr
}
listEnterpriseInstancesReq := apiClientEnterprises.NewListEnterpriseInstancesParams()
listEnterpriseInstancesReq.EnterpriseID = runnerEnterprise
listEnterpriseInstancesReq.EnterpriseID = runnerEnt
response, err = apiCli.Enterprises.ListEnterpriseInstances(listEnterpriseInstancesReq, authToken)
} else if cmd.Flags().Changed("all") {
} else {
listInstancesReq := apiClientInstances.NewListInstancesParams()
response, err = apiCli.Instances.ListInstances(listInstancesReq, authToken)
} else {
cmd.Help() //nolint
os.Exit(0)
}
default:
cmd.Help() //nolint
@ -205,9 +214,12 @@ func init() {
runnerListCmd.Flags().StringVarP(&runnerRepository, "repo", "r", "", "List all runners from all pools within this repository.")
runnerListCmd.Flags().StringVarP(&runnerOrganization, "org", "o", "", "List all runners from all pools within this organization.")
runnerListCmd.Flags().StringVarP(&runnerEnterprise, "enterprise", "e", "", "List all runners from all pools within this enterprise.")
runnerListCmd.Flags().BoolVarP(&runnerAll, "all", "a", false, "List all runners, regardless of org or repo.")
runnerListCmd.Flags().BoolVarP(&runnerAll, "all", "a", true, "List all runners, regardless of org or repo. (deprecated)")
runnerListCmd.Flags().BoolVarP(&long, "long", "l", false, "Include additional info.")
runnerListCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise", "all")
runnerListCmd.Flags().StringVar(&endpointName, "endpoint", "", "When using the name of an entity, the endpoint must be specified when multiple entities with the same name exist.")
runnerListCmd.Flags().MarkDeprecated("all", "all runners are listed by default in the absence of --repo, --org or --enterprise.")
runnerDeleteCmd.Flags().BoolVarP(&forceRemove, "force-remove-runner", "f", false, "Forcefully remove a runner. If set to true, GARM will ignore provider errors when removing the runner.")
runnerDeleteCmd.Flags().BoolVarP(&bypassGHUnauthorized, "bypass-github-unauthorized", "b", false, "Ignore Unauthorized errors from GitHub and proceed with removing runner from provider and DB. This is useful when credentials are no longer valid and you want to remove your runners. Warning, this has the potential to leave orphaned runners in GitHub. You will need to update your credentials to properly consolidate.")

View file

@ -47,7 +47,6 @@ var (
scalesetEnterprise string
scalesetExtraSpecsFile string
scalesetExtraSpecs string
scalesetAll bool
scalesetGitHubRunnerGroup string
)
@ -128,12 +127,9 @@ Example:
listEnterpriseScaleSetsReq := apiClientEnterprises.NewListEnterpriseScaleSetsParams()
listEnterpriseScaleSetsReq.EnterpriseID = scalesetEnterprise
response, err = apiCli.Enterprises.ListEnterpriseScaleSets(listEnterpriseScaleSetsReq, authToken)
} else if cmd.Flags().Changed("all") {
} else {
listScaleSetsReq := apiClientScaleSets.NewListScalesetsParams()
response, err = apiCli.Scalesets.ListScalesets(listScaleSetsReq, authToken)
} else {
cmd.Help() //nolint
os.Exit(0)
}
default:
cmd.Help() //nolint
@ -400,8 +396,7 @@ func init() {
scalesetListCmd.Flags().StringVarP(&scalesetRepository, "repo", "r", "", "List all scale sets within this repository.")
scalesetListCmd.Flags().StringVarP(&scalesetOrganization, "org", "o", "", "List all scale sets within this organization.")
scalesetListCmd.Flags().StringVarP(&scalesetEnterprise, "enterprise", "e", "", "List all scale sets within this enterprise.")
scalesetListCmd.Flags().BoolVarP(&scalesetAll, "all", "a", false, "List all scale sets, regardless of org or repo.")
scalesetListCmd.MarkFlagsMutuallyExclusive("repo", "org", "all", "enterprise")
scalesetListCmd.MarkFlagsMutuallyExclusive("repo", "org", "enterprise")
scalesetListCmd.Flags().StringVar(&endpointName, "endpoint", "", "When using the name of an entity, the endpoint must be specified when multiple entities with the same name exist.")
scaleSetUpdateCmd.Flags().StringVar(&scalesetImage, "image", "", "The provider-specific image name to use for runners in this scale set.")

View file

@ -15,8 +15,14 @@
package common
import (
"encoding/json"
"errors"
"fmt"
"os"
"runtime"
"sort"
"strings"
"time"
"github.com/manifoldco/promptui"
"github.com/nbutton23/zxcvbn-go"
@ -74,3 +80,246 @@ func PrintWebsocketMessage(_ int, msg []byte) error {
fmt.Println(util.SanitizeLogEntry(string(msg)))
return nil
}
type LogFormatter struct {
MinLevel string
AttributeFilters map[string]string
EnableColor bool
}
type LogRecord struct {
Time string `json:"time"`
Level string `json:"level"`
Msg string `json:"msg"`
Attrs map[string]interface{} `json:",inline"`
}
// Color codes for different log levels
const (
ColorReset = "\033[0m"
ColorRed = "\033[31m"
ColorYellow = "\033[33m"
ColorBlue = "\033[34m"
ColorMagenta = "\033[35m"
ColorCyan = "\033[36m"
ColorWhite = "\033[37m"
ColorGray = "\033[90m"
)
func (lf *LogFormatter) colorizeLevel(level string) string {
if !lf.EnableColor {
return level
}
levelUpper := strings.TrimSpace(strings.ToUpper(level))
switch levelUpper {
case "ERROR":
return ColorRed + level + ColorReset
case "WARN", "WARNING":
return ColorYellow + level + ColorReset
case "INFO":
return ColorBlue + level + ColorReset
case "DEBUG":
return ColorMagenta + level + ColorReset
default:
return level
}
}
func (lf *LogFormatter) shouldFilterLevel(level string) bool {
if lf.MinLevel == "" {
return false
}
levelMap := map[string]int{
"DEBUG": 0,
"INFO": 1,
"WARN": 2,
"ERROR": 3,
}
minLevelNum, exists := levelMap[strings.ToUpper(lf.MinLevel)]
if !exists {
return false
}
currentLevelNum, exists := levelMap[strings.ToUpper(level)]
if !exists {
return false
}
return currentLevelNum < minLevelNum
}
func (lf *LogFormatter) matchesAttributeFilters(attrs map[string]interface{}, msg string) bool {
if len(lf.AttributeFilters) == 0 {
return true
}
for key, expectedValue := range lf.AttributeFilters {
// Special handling for message filtering
if key == "msg" {
if strings.Contains(msg, expectedValue) {
return true
}
}
// Regular attribute filtering
actualValue, exists := attrs[key]
if exists {
actualStr := fmt.Sprintf("%v", actualValue)
if actualStr == expectedValue {
return true
}
}
}
return false
}
func (lf *LogFormatter) FormatWebsocketMessage(_ int, msg []byte) error {
// Try to parse as JSON log record
var logRecord LogRecord
err := json.Unmarshal(msg, &logRecord)
if err != nil {
// If it's not JSON, print as-is (sanitized)
_, err = fmt.Println(util.SanitizeLogEntry(string(msg)))
return err
}
// Apply level filtering
if lf.shouldFilterLevel(logRecord.Level) {
return nil
}
// Parse additional attributes from the JSON
var fullRecord map[string]interface{}
if err := json.Unmarshal(msg, &fullRecord); err == nil {
// Remove standard fields and keep only attributes
delete(fullRecord, "time")
delete(fullRecord, "level")
delete(fullRecord, "msg")
logRecord.Attrs = fullRecord
}
// Apply attribute filtering
if !lf.matchesAttributeFilters(logRecord.Attrs, logRecord.Msg) {
return nil
}
// Format timestamp to fixed width
timeStr := logRecord.Time
if t, err := time.Parse(time.RFC3339Nano, logRecord.Time); err == nil {
timeStr = t.Format("2006-01-02 15:04:05.000")
}
// Format log level to fixed width (5 characters)
levelStr := lf.colorizeLevel(fmt.Sprintf("%-5s", strings.ToUpper(logRecord.Level)))
// Highlight message if it matches a msg filter
msgStr := logRecord.Msg
if msgFilter, hasMsgFilter := lf.AttributeFilters["msg"]; hasMsgFilter {
if strings.Contains(msgStr, msgFilter) && lf.EnableColor {
msgStr = ColorYellow + msgStr + ColorReset
}
}
output := fmt.Sprintf("%s [%s] %s", timeStr, levelStr, msgStr)
// Add attributes if any
if len(logRecord.Attrs) > 0 {
// Get sorted keys for consistent output
var keys []string
for k := range logRecord.Attrs {
keys = append(keys, k)
}
sort.Strings(keys)
var attrPairs []string
for _, k := range keys {
v := logRecord.Attrs[k]
attrStr := fmt.Sprintf("%s=%v", k, v)
// Highlight filtered attributes
if filterValue, isFiltered := lf.AttributeFilters[k]; isFiltered && fmt.Sprintf("%v", v) == filterValue {
if lf.EnableColor {
attrStr = ColorYellow + attrStr + ColorGray
}
} else if lf.EnableColor {
attrStr = ColorGray + attrStr
}
attrPairs = append(attrPairs, attrStr)
}
if len(attrPairs) > 0 {
if lf.EnableColor {
output += " " + strings.Join(attrPairs, " ") + ColorReset
} else {
output += " " + strings.Join(attrPairs, " ")
}
}
}
fmt.Println(output)
return nil
}
// supportsColor checks if the current terminal/environment supports ANSI colors.
// This is best effort. There is no reliable way to determine if a terminal supports
// color. Set NO_COLOR=1 to disable color if your terminal doesn't support it, but this
// function returns true.
func supportsColor() bool {
// Check NO_COLOR environment variable (universal standard)
if os.Getenv("NO_COLOR") != "" {
return false
}
// Check FORCE_COLOR environment variable
if os.Getenv("FORCE_COLOR") != "" {
return true
}
// On Windows, check for modern terminal support
if runtime.GOOS == "windows" {
// Check for Windows Terminal
if os.Getenv("WT_SESSION") != "" {
return true
}
// Check for ConEmu
if os.Getenv("ConEmuANSI") == "ON" {
return true
}
// Check for other modern terminals
term := os.Getenv("TERM")
if strings.Contains(term, "color") || term == "xterm-256color" || term == "screen-256color" {
return true
}
// Modern PowerShell and cmd.exe with VT processing
if os.Getenv("TERM_PROGRAM") != "" {
return true
}
// Default to false for older Windows cmd.exe
return false
}
// On Unix-like systems, check TERM
term := os.Getenv("TERM")
if term == "" || term == "dumb" {
return false
}
return true
}
func NewLogFormatter(minLevel string, attributeFilters map[string]string, color bool) *LogFormatter {
var enableColor bool
if color && supportsColor() {
enableColor = true
}
return &LogFormatter{
MinLevel: minLevel,
AttributeFilters: attributeFilters,
EnableColor: enableColor,
}
}

View file

@ -15,13 +15,13 @@
package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"sync"
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
)
@ -34,11 +34,11 @@ const (
func getConfigFilePath() (string, error) {
configDir, err := getHomeDir()
if err != nil {
return "", errors.Wrap(err, "fetching home folder")
return "", fmt.Errorf("error fetching home folder: %w", err)
}
if err := ensureHomeDir(configDir); err != nil {
return "", errors.Wrap(err, "ensuring config dir")
return "", fmt.Errorf("error ensuring config dir: %w", err)
}
cfgFile := filepath.Join(configDir, DefaultConfigFileName)
@ -48,7 +48,7 @@ func getConfigFilePath() (string, error) {
func LoadConfig() (*Config, error) {
cfgFile, err := getConfigFilePath()
if err != nil {
return nil, errors.Wrap(err, "fetching config")
return nil, fmt.Errorf("error fetching config: %w", err)
}
if _, err := os.Stat(cfgFile); err != nil {
@ -56,12 +56,12 @@ func LoadConfig() (*Config, error) {
// return empty config
return &Config{}, nil
}
return nil, errors.Wrap(err, "accessing config file")
return nil, fmt.Errorf("error accessing config file: %w", err)
}
var config Config
if _, err := toml.DecodeFile(cfgFile, &config); err != nil {
return nil, errors.Wrap(err, "decoding toml")
return nil, fmt.Errorf("error decoding toml: %w", err)
}
return &config, nil
@ -157,17 +157,17 @@ func (c *Config) SaveConfig() error {
cfgFile, err := getConfigFilePath()
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return errors.Wrap(err, "getting config")
return fmt.Errorf("error getting config: %w", err)
}
}
cfgHandle, err := os.Create(cfgFile)
if err != nil {
return errors.Wrap(err, "getting file handle")
return fmt.Errorf("error getting file handle: %w", err)
}
encoder := toml.NewEncoder(cfgHandle)
if err := encoder.Encode(c); err != nil {
return errors.Wrap(err, "saving config")
return fmt.Errorf("error saving config: %w", err)
}
return nil

View file

@ -15,19 +15,19 @@
package config
import (
"errors"
"fmt"
"os"
"github.com/pkg/errors"
)
func ensureHomeDir(folder string) error {
if _, err := os.Stat(folder); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return errors.Wrap(err, "checking home dir")
return fmt.Errorf("error checking home dir: %w", err)
}
if err := os.MkdirAll(folder, 0o710); err != nil {
return errors.Wrapf(err, "creating %s", folder)
return fmt.Errorf("error creating %s: %w", folder, err)
}
}

View file

@ -17,16 +17,15 @@
package config
import (
"fmt"
"os"
"path/filepath"
"github.com/pkg/errors"
)
func getHomeDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", errors.Wrap(err, "fetching home dir")
return "", fmt.Errorf("error fetching home dir: %w", err)
}
return filepath.Join(home, ".local", "share", DefaultAppFolder), nil

View file

@ -18,7 +18,6 @@ import (
"context"
"flag"
"fmt"
"io"
"log"
"log/slog"
"net"
@ -31,7 +30,6 @@ import (
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/pkg/errors"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
"github.com/cloudbase/garm-provider-common/util"
@ -73,7 +71,7 @@ func maybeInitController(db common.Store) (params.ControllerInfo, error) {
info, err := db.InitController()
if err != nil {
return params.ControllerInfo{}, errors.Wrap(err, "initializing controller")
return params.ControllerInfo{}, fmt.Errorf("error initializing controller: %w", err)
}
return info, nil
@ -105,16 +103,6 @@ func setupLogging(ctx context.Context, logCfg config.Logging, hub *websocket.Hub
}
}()
writers := []io.Writer{
logWriter,
}
if hub != nil {
writers = append(writers, hub)
}
wr := io.MultiWriter(writers...)
var logLevel slog.Level
switch logCfg.LogLevel {
case config.LevelDebug:
@ -135,16 +123,25 @@ func setupLogging(ctx context.Context, logCfg config.Logging, hub *websocket.Hub
Level: logLevel,
}
var han slog.Handler
var fileHan slog.Handler
switch logCfg.LogFormat {
case config.FormatJSON:
han = slog.NewJSONHandler(wr, &opts)
fileHan = slog.NewJSONHandler(logWriter, &opts)
default:
han = slog.NewTextHandler(wr, &opts)
fileHan = slog.NewTextHandler(logWriter, &opts)
}
wrapped := garmUtil.ContextHandler{
Handler: han,
handlers := []slog.Handler{
fileHan,
}
if hub != nil {
wsHan := slog.NewJSONHandler(hub, &opts)
handlers = append(handlers, wsHan)
}
wrapped := &garmUtil.SlogMultiHandler{
Handlers: handlers,
}
slog.SetDefault(slog.New(wrapped))
}
@ -152,7 +149,7 @@ func setupLogging(ctx context.Context, logCfg config.Logging, hub *websocket.Hub
func maybeUpdateURLsFromConfig(cfg config.Config, store common.Store) error {
info, err := store.ControllerInfo()
if err != nil {
return errors.Wrap(err, "fetching controller info")
return fmt.Errorf("error fetching controller info: %w", err)
}
var updateParams params.UpdateControllerParams
@ -176,7 +173,7 @@ func maybeUpdateURLsFromConfig(cfg config.Config, store common.Store) error {
_, err = store.UpdateController(updateParams)
if err != nil {
return errors.Wrap(err, "updating controller info")
return fmt.Errorf("error updating controller info: %w", err)
}
return nil
}
@ -283,7 +280,7 @@ func main() {
}
authenticator := auth.NewAuthenticator(cfg.JWTAuth, db)
controller, err := controllers.NewAPIController(runner, authenticator, hub)
controller, err := controllers.NewAPIController(runner, authenticator, hub, cfg.APIServer)
if err != nil {
log.Fatalf("failed to create controller: %+v", err)
}
@ -315,6 +312,9 @@ func main() {
router := routers.NewAPIRouter(controller, jwtMiddleware, initMiddleware, urlsRequiredMiddleware, instanceMiddleware, cfg.Default.EnableWebhookManagement)
// Add WebUI routes
router = routers.WithWebUI(router, cfg.APIServer)
// start the metrics collector
if cfg.Metrics.Enable {
slog.InfoContext(ctx, "setting up metric routes")
@ -370,6 +370,13 @@ func main() {
<-ctx.Done()
slog.InfoContext(ctx, "shutting down http server")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 60*time.Second)
defer shutdownCancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "graceful api server shutdown failed")
}
if err := cacheWorker.Stop(); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to stop credentials worker")
}
@ -384,13 +391,6 @@ func main() {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to stop provider worker")
}
slog.InfoContext(ctx, "shutting down http server")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 60*time.Second)
defer shutdownCancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "graceful api server shutdown failed")
}
slog.With(slog.Any("error", err)).InfoContext(ctx, "waiting for runner to stop")
if err := runner.Wait(); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to shutdown workers")

View file

@ -31,7 +31,6 @@ import (
"github.com/BurntSushi/toml"
"github.com/bradleyfalzon/ghinstallation/v2"
zxcvbn "github.com/nbutton23/zxcvbn-go"
"github.com/pkg/errors"
"golang.org/x/oauth2"
"github.com/cloudbase/garm/params"
@ -84,10 +83,10 @@ const (
func NewConfig(cfgFile string) (*Config, error) {
var config Config
if _, err := toml.DecodeFile(cfgFile, &config); err != nil {
return nil, errors.Wrap(err, "decoding toml")
return nil, fmt.Errorf("error decoding toml: %w", err)
}
if err := config.Validate(); err != nil {
return nil, errors.Wrap(err, "validating config")
return nil, fmt.Errorf("error validating config: %w", err)
}
return &config, nil
}
@ -496,19 +495,19 @@ type Database struct {
// GormParams returns the database type and connection URI
func (d *Database) GormParams() (dbType DBBackendType, uri string, err error) {
if err := d.Validate(); err != nil {
return "", "", errors.Wrap(err, "validating database config")
return "", "", fmt.Errorf("error validating database config: %w", err)
}
dbType = d.DbBackend
switch dbType {
case MySQLBackend:
uri, err = d.MySQL.ConnectionString()
if err != nil {
return "", "", errors.Wrap(err, "fetching mysql connection string")
return "", "", fmt.Errorf("error fetching mysql connection string: %w", err)
}
case SQLiteBackend:
uri, err = d.SQLite.ConnectionString()
if err != nil {
return "", "", errors.Wrap(err, "fetching sqlite3 connection string")
return "", "", fmt.Errorf("error fetching sqlite3 connection string: %w", err)
}
default:
return "", "", fmt.Errorf("invalid database backend: %s", dbType)
@ -663,6 +662,21 @@ func (m *Metrics) Duration() time.Duration {
return duration
}
// WebUI holds configuration for the web UI
type WebUI struct {
EnableWebUI bool `toml:"enable" json:"enable"`
}
// Validate validates the WebUI config
func (w *WebUI) Validate() error {
return nil
}
// GetWebappPath returns the webapp path with proper formatting
func (w *WebUI) GetWebappPath() string {
return "/ui/"
}
// APIServer holds configuration for the API server
// worker
type APIServer struct {
@ -671,6 +685,7 @@ type APIServer struct {
UseTLS bool `toml:"use_tls" json:"use-tls"`
TLSConfig TLSConfig `toml:"tls" json:"tls"`
CORSOrigins []string `toml:"cors_origins" json:"cors-origins"`
WebUI WebUI `toml:"webui" json:"webui"`
}
// BindAddress returns a host:port string.
@ -696,6 +711,11 @@ func (a *APIServer) Validate() error {
// when we try to bind to it.
return fmt.Errorf("invalid IP address")
}
if err := a.WebUI.Validate(); err != nil {
return fmt.Errorf("invalid webui config: %w", err)
}
return nil
}

View file

@ -517,7 +517,6 @@ func TestJWTAuthConfig(t *testing.T) {
func TestTimeToLiveDuration(t *testing.T) {
cfg := JWTAuth{
Secret: EncryptionPassphrase,
TimeToLive: "48h",
}

File diff suppressed because it is too large Load diff

View file

@ -75,7 +75,6 @@ type PoolStore interface {
ListPoolInstances(ctx context.Context, poolID string) ([]params.Instance, error)
PoolInstanceCount(ctx context.Context, poolID string) (int64, error)
GetPoolInstanceByName(ctx context.Context, poolID string, instanceName string) (params.Instance, error)
FindPoolsMatchingAllTags(ctx context.Context, entityType params.ForgeEntityType, entityID string, tags []string) ([]params.Pool, error)
}
@ -91,9 +90,9 @@ type UserStore interface {
type InstanceStore interface {
CreateInstance(ctx context.Context, poolID string, param params.CreateInstanceParams) (params.Instance, error)
DeleteInstance(ctx context.Context, poolID string, instanceName string) error
DeleteInstance(ctx context.Context, poolID string, instanceNameOrID string) error
DeleteInstanceByName(ctx context.Context, instanceName string) error
UpdateInstance(ctx context.Context, instanceName string, param params.UpdateInstanceParams) (params.Instance, error)
UpdateInstance(ctx context.Context, instanceNameOrID string, param params.UpdateInstanceParams) (params.Instance, error)
// Probably a bad idea without some king of filter or at least pagination
//
@ -101,8 +100,8 @@ type InstanceStore interface {
// TODO: add filter/pagination
ListAllInstances(ctx context.Context) ([]params.Instance, error)
GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error)
AddInstanceEvent(ctx context.Context, instanceName string, event params.EventType, eventLevel params.EventLevel, eventMessage string) error
GetInstance(ctx context.Context, instanceNameOrID string) (params.Instance, error)
AddInstanceEvent(ctx context.Context, instanceNameOrID string, event params.EventType, eventLevel params.EventLevel, eventMessage string) error
}
type JobsStore interface {
@ -169,7 +168,7 @@ type GiteaCredentialsStore interface {
DeleteGiteaCredentials(ctx context.Context, id uint) (err error)
}
//go:generate mockery --name=Store
//go:generate go run github.com/vektra/mockery/v2@latest
type Store interface {
RepoStore
OrgStore

View file

@ -15,10 +15,11 @@
package sql
import (
"errors"
"fmt"
"net/url"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -30,7 +31,7 @@ import (
func dbControllerToCommonController(dbInfo ControllerInfo) (params.ControllerInfo, error) {
url, err := url.JoinPath(dbInfo.WebhookBaseURL, dbInfo.ControllerID.String())
if err != nil {
return params.ControllerInfo{}, errors.Wrap(err, "joining webhook URL")
return params.ControllerInfo{}, fmt.Errorf("error joining webhook URL: %w", err)
}
return params.ControllerInfo{
@ -49,14 +50,14 @@ func (s *sqlDatabase) ControllerInfo() (params.ControllerInfo, error) {
q := s.conn.Model(&ControllerInfo{}).First(&info)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return params.ControllerInfo{}, errors.Wrap(runnerErrors.ErrNotFound, "fetching controller info")
return params.ControllerInfo{}, fmt.Errorf("error fetching controller info: %w", runnerErrors.ErrNotFound)
}
return params.ControllerInfo{}, errors.Wrap(q.Error, "fetching controller info")
return params.ControllerInfo{}, fmt.Errorf("error fetching controller info: %w", q.Error)
}
paramInfo, err := dbControllerToCommonController(info)
if err != nil {
return params.ControllerInfo{}, errors.Wrap(err, "converting controller info")
return params.ControllerInfo{}, fmt.Errorf("error converting controller info: %w", err)
}
return paramInfo, nil
@ -69,7 +70,7 @@ func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
newID, err := uuid.NewRandom()
if err != nil {
return params.ControllerInfo{}, errors.Wrap(err, "generating UUID")
return params.ControllerInfo{}, fmt.Errorf("error generating UUID: %w", err)
}
newInfo := ControllerInfo{
@ -79,7 +80,7 @@ func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
q := s.conn.Save(&newInfo)
if q.Error != nil {
return params.ControllerInfo{}, errors.Wrap(q.Error, "saving controller info")
return params.ControllerInfo{}, fmt.Errorf("error saving controller info: %w", q.Error)
}
return params.ControllerInfo{
@ -98,13 +99,13 @@ func (s *sqlDatabase) UpdateController(info params.UpdateControllerParams) (para
q := tx.Model(&ControllerInfo{}).First(&dbInfo)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "fetching controller info")
return fmt.Errorf("error fetching controller info: %w", runnerErrors.ErrNotFound)
}
return errors.Wrap(q.Error, "fetching controller info")
return fmt.Errorf("error fetching controller info: %w", q.Error)
}
if err := info.Validate(); err != nil {
return errors.Wrap(err, "validating controller info")
return fmt.Errorf("error validating controller info: %w", err)
}
if info.MetadataURL != nil {
@ -125,17 +126,17 @@ func (s *sqlDatabase) UpdateController(info params.UpdateControllerParams) (para
q = tx.Save(&dbInfo)
if q.Error != nil {
return errors.Wrap(q.Error, "saving controller info")
return fmt.Errorf("error saving controller info: %w", q.Error)
}
return nil
})
if err != nil {
return params.ControllerInfo{}, errors.Wrap(err, "updating controller info")
return params.ControllerInfo{}, fmt.Errorf("error updating controller info: %w", err)
}
paramInfo, err = dbControllerToCommonController(dbInfo)
if err != nil {
return params.ControllerInfo{}, errors.Wrap(err, "converting controller info")
return params.ControllerInfo{}, fmt.Errorf("error converting controller info: %w", err)
}
return paramInfo, nil
}

View file

@ -16,10 +16,11 @@ package sql
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -33,12 +34,12 @@ func (s *sqlDatabase) CreateEnterprise(ctx context.Context, name string, credent
return params.Enterprise{}, errors.New("creating enterprise: missing secret")
}
if credentials.ForgeType != params.GithubEndpointType {
return params.Enterprise{}, errors.Wrap(runnerErrors.ErrBadRequest, "enterprises are not supported on this forge type")
return params.Enterprise{}, fmt.Errorf("enterprises are not supported on this forge type: %w", runnerErrors.ErrBadRequest)
}
secret, err := util.Seal([]byte(webhookSecret), []byte(s.cfg.Passphrase))
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "encoding secret")
return params.Enterprise{}, fmt.Errorf("error encoding secret: %w", err)
}
defer func() {
@ -57,22 +58,22 @@ func (s *sqlDatabase) CreateEnterprise(ctx context.Context, name string, credent
q := tx.Create(&newEnterprise)
if q.Error != nil {
return errors.Wrap(q.Error, "creating enterprise")
return fmt.Errorf("error creating enterprise: %w", q.Error)
}
newEnterprise, err = s.getEnterpriseByID(ctx, tx, newEnterprise.ID.String(), "Pools", "Credentials", "Endpoint", "Credentials.Endpoint")
if err != nil {
return errors.Wrap(err, "creating enterprise")
return fmt.Errorf("error creating enterprise: %w", err)
}
return nil
})
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
return params.Enterprise{}, fmt.Errorf("error creating enterprise: %w", err)
}
ret, err := s.GetEnterpriseByID(ctx, newEnterprise.ID.String())
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
return params.Enterprise{}, fmt.Errorf("error creating enterprise: %w", err)
}
return ret, nil
@ -81,12 +82,12 @@ func (s *sqlDatabase) CreateEnterprise(ctx context.Context, name string, credent
func (s *sqlDatabase) GetEnterprise(ctx context.Context, name, endpointName string) (params.Enterprise, error) {
enterprise, err := s.getEnterprise(ctx, name, endpointName)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
return params.Enterprise{}, fmt.Errorf("error fetching enterprise: %w", err)
}
param, err := s.sqlToCommonEnterprise(enterprise, true)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
return params.Enterprise{}, fmt.Errorf("error fetching enterprise: %w", err)
}
return param, nil
}
@ -101,12 +102,12 @@ func (s *sqlDatabase) GetEnterpriseByID(ctx context.Context, enterpriseID string
}
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID, preloadList...)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
return params.Enterprise{}, fmt.Errorf("error fetching enterprise: %w", err)
}
param, err := s.sqlToCommonEnterprise(enterprise, true)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
return params.Enterprise{}, fmt.Errorf("error fetching enterprise: %w", err)
}
return param, nil
}
@ -125,7 +126,7 @@ func (s *sqlDatabase) ListEnterprises(_ context.Context, filter params.Enterpris
}
q = q.Find(&enterprises)
if q.Error != nil {
return []params.Enterprise{}, errors.Wrap(q.Error, "fetching enterprises")
return []params.Enterprise{}, fmt.Errorf("error fetching enterprises: %w", q.Error)
}
ret := make([]params.Enterprise, len(enterprises))
@ -133,7 +134,7 @@ func (s *sqlDatabase) ListEnterprises(_ context.Context, filter params.Enterpris
var err error
ret[idx], err = s.sqlToCommonEnterprise(val, true)
if err != nil {
return nil, errors.Wrap(err, "fetching enterprises")
return nil, fmt.Errorf("error fetching enterprises: %w", err)
}
}
@ -143,7 +144,7 @@ func (s *sqlDatabase) ListEnterprises(_ context.Context, filter params.Enterpris
func (s *sqlDatabase) DeleteEnterprise(ctx context.Context, enterpriseID string) error {
enterprise, err := s.getEnterpriseByID(ctx, s.conn, enterpriseID, "Endpoint", "Credentials", "Credentials.Endpoint")
if err != nil {
return errors.Wrap(err, "fetching enterprise")
return fmt.Errorf("error fetching enterprise: %w", err)
}
defer func(ent Enterprise) {
@ -159,7 +160,7 @@ func (s *sqlDatabase) DeleteEnterprise(ctx context.Context, enterpriseID string)
q := s.conn.Unscoped().Delete(&enterprise)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting enterprise")
return fmt.Errorf("error deleting enterprise: %w", q.Error)
}
return nil
@ -177,31 +178,31 @@ func (s *sqlDatabase) UpdateEnterprise(ctx context.Context, enterpriseID string,
var err error
enterprise, err = s.getEnterpriseByID(ctx, tx, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise")
return fmt.Errorf("error fetching enterprise: %w", err)
}
if enterprise.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "enterprise has no endpoint")
return fmt.Errorf("error enterprise has no endpoint: %w", runnerErrors.ErrUnprocessable)
}
if param.CredentialsName != "" {
creds, err = s.getGithubCredentialsByName(ctx, tx, param.CredentialsName, false)
if err != nil {
return errors.Wrap(err, "fetching credentials")
return fmt.Errorf("error fetching credentials: %w", err)
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
return fmt.Errorf("error credentials have no endpoint: %w", runnerErrors.ErrUnprocessable)
}
if *creds.EndpointName != *enterprise.EndpointName {
return errors.Wrap(runnerErrors.ErrBadRequest, "endpoint mismatch")
return fmt.Errorf("error endpoint mismatch: %w", runnerErrors.ErrBadRequest)
}
enterprise.CredentialsID = &creds.ID
}
if param.WebhookSecret != "" {
secret, err := util.Seal([]byte(param.WebhookSecret), []byte(s.cfg.Passphrase))
if err != nil {
return errors.Wrap(err, "encoding secret")
return fmt.Errorf("error encoding secret: %w", err)
}
enterprise.WebhookSecret = secret
}
@ -212,22 +213,22 @@ func (s *sqlDatabase) UpdateEnterprise(ctx context.Context, enterpriseID string,
q := tx.Save(&enterprise)
if q.Error != nil {
return errors.Wrap(q.Error, "saving enterprise")
return fmt.Errorf("error saving enterprise: %w", q.Error)
}
return nil
})
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
return params.Enterprise{}, fmt.Errorf("error updating enterprise: %w", err)
}
enterprise, err = s.getEnterpriseByID(ctx, s.conn, enterpriseID, "Endpoint", "Credentials", "Credentials.Endpoint")
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
return params.Enterprise{}, fmt.Errorf("error updating enterprise: %w", err)
}
newParams, err = s.sqlToCommonEnterprise(enterprise, true)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
return params.Enterprise{}, fmt.Errorf("error updating enterprise: %w", err)
}
return newParams, nil
}
@ -244,7 +245,7 @@ func (s *sqlDatabase) getEnterprise(_ context.Context, name, endpointName string
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Enterprise{}, runnerErrors.ErrNotFound
}
return Enterprise{}, errors.Wrap(q.Error, "fetching enterprise from database")
return Enterprise{}, fmt.Errorf("error fetching enterprise from database: %w", q.Error)
}
return enterprise, nil
}
@ -252,7 +253,7 @@ func (s *sqlDatabase) getEnterprise(_ context.Context, name, endpointName string
func (s *sqlDatabase) getEnterpriseByID(_ context.Context, tx *gorm.DB, id string, preload ...string) (Enterprise, error) {
u, err := uuid.Parse(id)
if err != nil {
return Enterprise{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return Enterprise{}, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
var enterprise Enterprise
@ -268,7 +269,7 @@ func (s *sqlDatabase) getEnterpriseByID(_ context.Context, tx *gorm.DB, id strin
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Enterprise{}, runnerErrors.ErrNotFound
}
return Enterprise{}, errors.Wrap(q.Error, "fetching enterprise from database")
return Enterprise{}, fmt.Errorf("error fetching enterprise from database: %w", q.Error)
}
return enterprise, nil
}

View file

@ -218,7 +218,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseInvalidDBPassphrase() {
params.PoolBalancerTypeRoundRobin)
s.Require().NotNil(err)
s.Require().Equal("encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
}
func (s *EnterpriseTestSuite) TestCreateEnterpriseDBCreateErr() {
@ -236,7 +236,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseDBCreateErr() {
params.PoolBalancerTypeRoundRobin)
s.Require().NotNil(err)
s.Require().Equal("creating enterprise: creating enterprise: creating enterprise mock error", err.Error())
s.Require().Equal("error creating enterprise: error creating enterprise: creating enterprise mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -259,7 +259,7 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseNotFound() {
_, err := s.Store.GetEnterprise(s.adminCtx, "dummy-name", "github.com")
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: not found", err.Error())
s.Require().Equal("error fetching enterprise: not found", err.Error())
}
func (s *EnterpriseTestSuite) TestGetEnterpriseDBDecryptingErr() {
@ -271,7 +271,7 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseDBDecryptingErr() {
_, err := s.StoreSQLMocked.GetEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].Name, s.Fixtures.Enterprises[0].Endpoint.Name)
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: missing secret", err.Error())
s.Require().Equal("error fetching enterprise: missing secret", err.Error())
s.assertSQLMockExpectations()
}
@ -341,7 +341,7 @@ func (s *EnterpriseTestSuite) TestListEnterprisesDBFetchErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching enterprises: fetching user from database mock error", err.Error())
s.Require().Equal("error fetching enterprises: fetching user from database mock error", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterprise() {
@ -350,14 +350,14 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprise() {
s.Require().Nil(err)
_, err = s.Store.GetEnterpriseByID(s.adminCtx, s.Fixtures.Enterprises[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: not found", err.Error())
s.Require().Equal("error fetching enterprise: not found", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterpriseInvalidEnterpriseID() {
err := s.Store.DeleteEnterprise(s.adminCtx, "dummy-enterprise-id")
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching enterprise: error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterpriseDBDeleteErr() {
@ -375,7 +375,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterpriseDBDeleteErr() {
err := s.StoreSQLMocked.DeleteEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID)
s.Require().NotNil(err)
s.Require().Equal("deleting enterprise: mocked delete enterprise error", err.Error())
s.Require().Equal("error deleting enterprise: mocked delete enterprise error", err.Error())
s.assertSQLMockExpectations()
}
@ -391,7 +391,7 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseInvalidEnterpriseID() {
_, err := s.Store.UpdateEnterprise(s.adminCtx, "dummy-enterprise-id", s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("updating enterprise: fetching enterprise: parsing id: invalid request", err.Error())
s.Require().Equal("error updating enterprise: error fetching enterprise: error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBEncryptErr() {
@ -416,7 +416,7 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBEncryptErr() {
_, err := s.StoreSQLMocked.UpdateEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("updating enterprise: encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error updating enterprise: error encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
@ -444,7 +444,7 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBSaveErr() {
_, err := s.StoreSQLMocked.UpdateEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("updating enterprise: saving enterprise: saving enterprise mock error", err.Error())
s.Require().Equal("error updating enterprise: error saving enterprise: saving enterprise mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -472,7 +472,7 @@ func (s *EnterpriseTestSuite) TestUpdateEnterpriseDBDecryptingErr() {
_, err := s.StoreSQLMocked.UpdateEnterprise(s.adminCtx, s.Fixtures.Enterprises[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("updating enterprise: encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error updating enterprise: error encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
@ -487,7 +487,7 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseByIDInvalidEnterpriseID() {
_, err := s.Store.GetEnterpriseByID(s.adminCtx, "dummy-enterprise-id")
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching enterprise: error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestGetEnterpriseByIDDBDecryptingErr() {
@ -508,7 +508,7 @@ func (s *EnterpriseTestSuite) TestGetEnterpriseByIDDBDecryptingErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching enterprise: missing secret", err.Error())
s.Require().Equal("error fetching enterprise: missing secret", err.Error())
}
func (s *EnterpriseTestSuite) TestCreateEnterprisePool() {
@ -547,7 +547,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolInvalidEnterpriseID() {
_, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
s.Require().Equal("error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBFetchTagErr() {
@ -565,7 +565,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBFetchTagErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating tag: fetching tag from database: mocked fetching tag error", err.Error())
s.Require().Equal("error creating tag: error fetching tag from database: mocked fetching tag error", err.Error())
s.assertSQLMockExpectations()
}
@ -592,7 +592,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBAddingPoolErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating pool: mocked adding pool error", err.Error())
s.Require().Equal("error creating pool: mocked adding pool error", err.Error())
s.assertSQLMockExpectations()
}
@ -623,7 +623,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBSaveTagErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("associating tags: mocked saving tag error", err.Error())
s.Require().Equal("error associating tags: mocked saving tag error", err.Error())
s.assertSQLMockExpectations()
}
@ -663,7 +663,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolDBFetchPoolErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: not found", err.Error())
s.Require().Equal("error fetching pool by ID: not found", err.Error())
s.assertSQLMockExpectations()
}
@ -694,7 +694,7 @@ func (s *EnterpriseTestSuite) TestListEnterprisePoolsInvalidEnterpriseID() {
_, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pools: error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestGetEnterprisePool() {
@ -719,7 +719,7 @@ func (s *EnterpriseTestSuite) TestGetEnterprisePoolInvalidEnterpriseID() {
_, err := s.Store.GetEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("fetching pool: error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterprisePool() {
@ -734,7 +734,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePool() {
s.Require().Nil(err)
_, err = s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
s.Require().Equal("fetching pool: error finding pool: not found", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolInvalidEnterpriseID() {
@ -745,7 +745,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolInvalidEnterpriseID() {
err := s.Store.DeleteEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
s.Require().Equal("error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolDBDeleteErr() {
@ -765,7 +765,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolDBDeleteErr() {
err = s.StoreSQLMocked.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().NotNil(err)
s.Require().Equal("removing pool: mocked deleting pool error", err.Error())
s.Require().Equal("error removing pool: mocked deleting pool error", err.Error())
s.assertSQLMockExpectations()
}
@ -800,7 +800,7 @@ func (s *EnterpriseTestSuite) TestListEnterpriseInstancesInvalidEnterpriseID() {
_, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching entity: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching entity: error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestUpdateEnterprisePool() {
@ -828,7 +828,7 @@ func (s *EnterpriseTestSuite) TestUpdateEnterprisePoolInvalidEnterpriseID() {
_, err := s.Store.UpdateEntityPool(s.adminCtx, entity, "dummy-pool-id", s.Fixtures.UpdatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool: error parsing id: invalid request", err.Error())
}
func (s *EnterpriseTestSuite) TestAddRepoEntityEvent() {

View file

@ -16,9 +16,10 @@ package sql
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -36,7 +37,7 @@ func (s *sqlDatabase) CreateGiteaEndpoint(_ context.Context, param params.Create
var endpoint GithubEndpoint
err = s.conn.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("name = ?", param.Name).First(&endpoint).Error; err == nil {
return errors.Wrap(runnerErrors.ErrDuplicateEntity, "gitea endpoint already exists")
return fmt.Errorf("gitea endpoint already exists: %w", runnerErrors.ErrDuplicateEntity)
}
endpoint = GithubEndpoint{
Name: param.Name,
@ -48,16 +49,16 @@ func (s *sqlDatabase) CreateGiteaEndpoint(_ context.Context, param params.Create
}
if err := tx.Create(&endpoint).Error; err != nil {
return errors.Wrap(err, "creating gitea endpoint")
return fmt.Errorf("error creating gitea endpoint: %w", err)
}
return nil
})
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "creating gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error creating gitea endpoint: %w", err)
}
ghEndpoint, err = s.sqlToCommonGithubEndpoint(endpoint)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "converting gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error converting gitea endpoint: %w", err)
}
return ghEndpoint, nil
}
@ -66,14 +67,14 @@ func (s *sqlDatabase) ListGiteaEndpoints(_ context.Context) ([]params.ForgeEndpo
var endpoints []GithubEndpoint
err := s.conn.Where("endpoint_type = ?", params.GiteaEndpointType).Find(&endpoints).Error
if err != nil {
return nil, errors.Wrap(err, "fetching gitea endpoints")
return nil, fmt.Errorf("error fetching gitea endpoints: %w", err)
}
var ret []params.ForgeEndpoint
for _, ep := range endpoints {
commonEp, err := s.sqlToCommonGithubEndpoint(ep)
if err != nil {
return nil, errors.Wrap(err, "converting gitea endpoint")
return nil, fmt.Errorf("error converting gitea endpoint: %w", err)
}
ret = append(ret, commonEp)
}
@ -90,19 +91,19 @@ func (s *sqlDatabase) UpdateGiteaEndpoint(_ context.Context, name string, param
err = s.conn.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("name = ? and endpoint_type = ?", name, params.GiteaEndpointType).First(&endpoint).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "gitea endpoint not found")
return runnerErrors.NewNotFoundError("gitea endpoint %q not found", name)
}
return errors.Wrap(err, "fetching gitea endpoint")
return fmt.Errorf("error fetching gitea endpoint: %w", err)
}
var credsCount int64
if err := tx.Model(&GiteaCredentials{}).Where("endpoint_name = ?", endpoint.Name).Count(&credsCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching gitea credentials")
return fmt.Errorf("error fetching gitea credentials: %w", err)
}
}
if credsCount > 0 && (param.APIBaseURL != nil || param.BaseURL != nil) {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update endpoint URLs with existing credentials")
return runnerErrors.NewBadRequestError("cannot update endpoint URLs with existing credentials")
}
if param.APIBaseURL != nil {
@ -122,17 +123,17 @@ func (s *sqlDatabase) UpdateGiteaEndpoint(_ context.Context, name string, param
}
if err := tx.Save(&endpoint).Error; err != nil {
return errors.Wrap(err, "updating gitea endpoint")
return fmt.Errorf("error updating gitea endpoint: %w", err)
}
return nil
})
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "updating gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error updating gitea endpoint: %w", err)
}
ghEndpoint, err = s.sqlToCommonGithubEndpoint(endpoint)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "converting gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error converting gitea endpoint: %w", err)
}
return ghEndpoint, nil
}
@ -142,9 +143,9 @@ func (s *sqlDatabase) GetGiteaEndpoint(_ context.Context, name string) (params.F
err := s.conn.Where("name = ? and endpoint_type = ?", name, params.GiteaEndpointType).First(&endpoint).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return params.ForgeEndpoint{}, errors.Wrap(runnerErrors.ErrNotFound, "gitea endpoint not found")
return params.ForgeEndpoint{}, runnerErrors.NewNotFoundError("gitea endpoint %q not found", name)
}
return params.ForgeEndpoint{}, errors.Wrap(err, "fetching gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error fetching gitea endpoint: %w", err)
}
return s.sqlToCommonGithubEndpoint(endpoint)
@ -162,41 +163,41 @@ func (s *sqlDatabase) DeleteGiteaEndpoint(_ context.Context, name string) (err e
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(err, "fetching gitea endpoint")
return fmt.Errorf("error fetching gitea endpoint: %w", err)
}
var credsCount int64
if err := tx.Model(&GiteaCredentials{}).Where("endpoint_name = ?", endpoint.Name).Count(&credsCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching gitea credentials")
return fmt.Errorf("error fetching gitea credentials: %w", err)
}
}
var repoCnt int64
if err := tx.Model(&Repository{}).Where("endpoint_name = ?", endpoint.Name).Count(&repoCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching gitea repositories")
return fmt.Errorf("error fetching gitea repositories: %w", err)
}
}
var orgCnt int64
if err := tx.Model(&Organization{}).Where("endpoint_name = ?", endpoint.Name).Count(&orgCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching gitea organizations")
return fmt.Errorf("error fetching gitea organizations: %w", err)
}
}
if credsCount > 0 || repoCnt > 0 || orgCnt > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete endpoint with associated entities")
return runnerErrors.NewBadRequestError("cannot delete endpoint with associated entities")
}
if err := tx.Unscoped().Delete(&endpoint).Error; err != nil {
return errors.Wrap(err, "deleting gitea endpoint")
return fmt.Errorf("error deleting gitea endpoint: %w", err)
}
return nil
})
if err != nil {
return errors.Wrap(err, "deleting gitea endpoint")
return fmt.Errorf("error deleting gitea endpoint: %w", err)
}
return nil
}
@ -204,10 +205,10 @@ func (s *sqlDatabase) DeleteGiteaEndpoint(_ context.Context, name string) (err e
func (s *sqlDatabase) CreateGiteaCredentials(ctx context.Context, param params.CreateGiteaCredentialsParams) (gtCreds params.ForgeCredentials, err error) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "creating gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error creating gitea credentials: %w", err)
}
if param.Endpoint == "" {
return params.ForgeCredentials{}, errors.Wrap(runnerErrors.ErrBadRequest, "endpoint name is required")
return params.ForgeCredentials{}, runnerErrors.NewBadRequestError("endpoint name is required")
}
defer func() {
@ -220,13 +221,13 @@ func (s *sqlDatabase) CreateGiteaCredentials(ctx context.Context, param params.C
var endpoint GithubEndpoint
if err := tx.Where("name = ? and endpoint_type = ?", param.Endpoint, params.GiteaEndpointType).First(&endpoint).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "gitea endpoint not found")
return runnerErrors.NewNotFoundError("gitea endpoint %q not found", param.Endpoint)
}
return errors.Wrap(err, "fetching gitea endpoint")
return fmt.Errorf("error fetching gitea endpoint: %w", err)
}
if err := tx.Where("name = ? and user_id = ?", param.Name, userID).First(&creds).Error; err == nil {
return errors.Wrap(runnerErrors.ErrDuplicateEntity, "gitea credentials already exists")
return fmt.Errorf("gitea credentials already exists: %w", runnerErrors.ErrDuplicateEntity)
}
var data []byte
@ -235,10 +236,10 @@ func (s *sqlDatabase) CreateGiteaCredentials(ctx context.Context, param params.C
case params.ForgeAuthTypePAT:
data, err = s.marshalAndSeal(param.PAT)
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid auth type")
return runnerErrors.NewBadRequestError("invalid auth type %q", param.AuthType)
}
if err != nil {
return errors.Wrap(err, "marshaling and sealing credentials")
return fmt.Errorf("error marshaling and sealing credentials: %w", err)
}
creds = GiteaCredentials{
@ -251,7 +252,7 @@ func (s *sqlDatabase) CreateGiteaCredentials(ctx context.Context, param params.C
}
if err := tx.Create(&creds).Error; err != nil {
return errors.Wrap(err, "creating gitea credentials")
return fmt.Errorf("error creating gitea credentials: %w", err)
}
// Skip making an extra query.
creds.Endpoint = endpoint
@ -259,11 +260,11 @@ func (s *sqlDatabase) CreateGiteaCredentials(ctx context.Context, param params.C
return nil
})
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "creating gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error creating gitea credentials: %w", err)
}
gtCreds, err = s.sqlGiteaToCommonForgeCredentials(creds)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error converting gitea credentials: %w", err)
}
return gtCreds, nil
}
@ -284,16 +285,16 @@ func (s *sqlDatabase) getGiteaCredentialsByName(ctx context.Context, tx *gorm.DB
userID, err := getUIDFromContext(ctx)
if err != nil {
return GiteaCredentials{}, errors.Wrap(err, "fetching gitea credentials")
return GiteaCredentials{}, fmt.Errorf("error fetching gitea credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
err = q.Where("name = ?", name).First(&creds).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return GiteaCredentials{}, errors.Wrap(runnerErrors.ErrNotFound, "gitea credentials not found")
return GiteaCredentials{}, runnerErrors.NewNotFoundError("gitea credentials %q not found", name)
}
return GiteaCredentials{}, errors.Wrap(err, "fetching gitea credentials")
return GiteaCredentials{}, fmt.Errorf("error fetching gitea credentials: %w", err)
}
return creds, nil
@ -302,7 +303,7 @@ func (s *sqlDatabase) getGiteaCredentialsByName(ctx context.Context, tx *gorm.DB
func (s *sqlDatabase) GetGiteaCredentialsByName(ctx context.Context, name string, detailed bool) (params.ForgeCredentials, error) {
creds, err := s.getGiteaCredentialsByName(ctx, s.conn, name, detailed)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "fetching gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching gitea credentials: %w", err)
}
return s.sqlGiteaToCommonForgeCredentials(creds)
@ -325,7 +326,7 @@ func (s *sqlDatabase) GetGiteaCredentials(ctx context.Context, id uint, detailed
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "fetching gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching gitea credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
@ -333,9 +334,9 @@ func (s *sqlDatabase) GetGiteaCredentials(ctx context.Context, id uint, detailed
err := q.Where("id = ?", id).First(&creds).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return params.ForgeCredentials{}, errors.Wrap(runnerErrors.ErrNotFound, "gitea credentials not found")
return params.ForgeCredentials{}, runnerErrors.NewNotFoundError("gitea credentials not found")
}
return params.ForgeCredentials{}, errors.Wrap(err, "fetching gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching gitea credentials: %w", err)
}
return s.sqlGiteaToCommonForgeCredentials(creds)
@ -346,7 +347,7 @@ func (s *sqlDatabase) ListGiteaCredentials(ctx context.Context) ([]params.ForgeC
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching gitea credentials")
return nil, fmt.Errorf("error fetching gitea credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
@ -354,14 +355,14 @@ func (s *sqlDatabase) ListGiteaCredentials(ctx context.Context) ([]params.ForgeC
var creds []GiteaCredentials
err := q.Preload("Endpoint").Find(&creds).Error
if err != nil {
return nil, errors.Wrap(err, "fetching gitea credentials")
return nil, fmt.Errorf("error fetching gitea credentials: %w", err)
}
var ret []params.ForgeCredentials
for _, c := range creds {
commonCreds, err := s.sqlGiteaToCommonForgeCredentials(c)
if err != nil {
return nil, errors.Wrap(err, "converting gitea credentials")
return nil, fmt.Errorf("error converting gitea credentials: %w", err)
}
ret = append(ret, commonCreds)
}
@ -380,16 +381,16 @@ func (s *sqlDatabase) UpdateGiteaCredentials(ctx context.Context, id uint, param
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return errors.Wrap(err, "updating gitea credentials")
return fmt.Errorf("error updating gitea credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
if err := q.Where("id = ?", id).First(&creds).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "gitea credentials not found")
return runnerErrors.NewNotFoundError("gitea credentials not found")
}
return errors.Wrap(err, "fetching gitea credentials")
return fmt.Errorf("error fetching gitea credentials: %w", err)
}
if param.Name != nil {
@ -407,28 +408,28 @@ func (s *sqlDatabase) UpdateGiteaCredentials(ctx context.Context, id uint, param
data, err = s.marshalAndSeal(param.PAT)
}
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid auth type")
return runnerErrors.NewBadRequestError("invalid auth type %q", creds.AuthType)
}
if err != nil {
return errors.Wrap(err, "marshaling and sealing credentials")
return fmt.Errorf("error marshaling and sealing credentials: %w", err)
}
if len(data) > 0 {
creds.Payload = data
}
if err := tx.Save(&creds).Error; err != nil {
return errors.Wrap(err, "updating gitea credentials")
return fmt.Errorf("error updating gitea credentials: %w", err)
}
return nil
})
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "updating gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error updating gitea credentials: %w", err)
}
gtCreds, err = s.sqlGiteaToCommonForgeCredentials(creds)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error converting gitea credentials: %w", err)
}
return gtCreds, nil
}
@ -454,7 +455,7 @@ func (s *sqlDatabase) DeleteGiteaCredentials(ctx context.Context, id uint) (err
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return errors.Wrap(err, "deleting gitea credentials")
return fmt.Errorf("error deleting gitea credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
@ -464,22 +465,22 @@ func (s *sqlDatabase) DeleteGiteaCredentials(ctx context.Context, id uint) (err
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(err, "fetching gitea credentials")
return fmt.Errorf("error fetching gitea credentials: %w", err)
}
if len(creds.Repositories) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with repositories")
return runnerErrors.NewBadRequestError("cannot delete credentials with repositories")
}
if len(creds.Organizations) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with organizations")
return runnerErrors.NewBadRequestError("cannot delete credentials with organizations")
}
if err := tx.Unscoped().Delete(&creds).Error; err != nil {
return errors.Wrap(err, "deleting gitea credentials")
return fmt.Errorf("error deleting gitea credentials: %w", err)
}
return nil
})
if err != nil {
return errors.Wrap(err, "deleting gitea credentials")
return fmt.Errorf("error deleting gitea credentials: %w", err)
}
return nil
}

View file

@ -236,7 +236,7 @@ func (s *GiteaTestSuite) TestCreateCredentialsFailsWhenEndpointDoesNotExist() {
_, err := s.db.CreateGiteaCredentials(ctx, params.CreateGiteaCredentialsParams{Endpoint: "non-existing"})
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrNotFound)
s.Require().Regexp("endpoint not found", err.Error())
s.Require().Regexp("error creating gitea credentials: gitea endpoint \"non-existing\" not found", err.Error())
}
func (s *GiteaTestSuite) TestCreateCredentialsFailsWhenAuthTypeIsInvalid() {
@ -807,7 +807,7 @@ func (s *GiteaTestSuite) TestUpdateEndpointURLsFailsIfCredentialsAreAssociated()
_, err = s.db.UpdateGiteaEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating gitea endpoint: cannot update endpoint URLs with existing credentials: invalid request")
s.Require().EqualError(err, "error updating gitea endpoint: cannot update endpoint URLs with existing credentials")
updateEpParams = params.UpdateGiteaEndpointParams{
APIBaseURL: &newAPIBaseURL,
@ -815,7 +815,7 @@ func (s *GiteaTestSuite) TestUpdateEndpointURLsFailsIfCredentialsAreAssociated()
_, err = s.db.UpdateGiteaEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating gitea endpoint: cannot update endpoint URLs with existing credentials: invalid request")
s.Require().EqualError(err, "error updating gitea endpoint: cannot update endpoint URLs with existing credentials")
updateEpParams = params.UpdateGiteaEndpointParams{
Description: &newDescription,

View file

@ -16,8 +16,9 @@ package sql
import (
"context"
"errors"
"fmt"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -35,7 +36,7 @@ func (s *sqlDatabase) CreateGithubEndpoint(_ context.Context, param params.Creat
var endpoint GithubEndpoint
err = s.conn.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("name = ?", param.Name).First(&endpoint).Error; err == nil {
return errors.Wrap(runnerErrors.ErrDuplicateEntity, "github endpoint already exists")
return fmt.Errorf("error github endpoint already exists: %w", runnerErrors.ErrDuplicateEntity)
}
endpoint = GithubEndpoint{
Name: param.Name,
@ -48,16 +49,16 @@ func (s *sqlDatabase) CreateGithubEndpoint(_ context.Context, param params.Creat
}
if err := tx.Create(&endpoint).Error; err != nil {
return errors.Wrap(err, "creating github endpoint")
return fmt.Errorf("error creating github endpoint: %w", err)
}
return nil
})
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "creating github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error creating github endpoint: %w", err)
}
ghEndpoint, err = s.sqlToCommonGithubEndpoint(endpoint)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "converting github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error converting github endpoint: %w", err)
}
return ghEndpoint, nil
}
@ -66,14 +67,14 @@ func (s *sqlDatabase) ListGithubEndpoints(_ context.Context) ([]params.ForgeEndp
var endpoints []GithubEndpoint
err := s.conn.Where("endpoint_type = ?", params.GithubEndpointType).Find(&endpoints).Error
if err != nil {
return nil, errors.Wrap(err, "fetching github endpoints")
return nil, fmt.Errorf("error fetching github endpoints: %w", err)
}
var ret []params.ForgeEndpoint
for _, ep := range endpoints {
commonEp, err := s.sqlToCommonGithubEndpoint(ep)
if err != nil {
return nil, errors.Wrap(err, "converting github endpoint")
return nil, fmt.Errorf("error converting github endpoint: %w", err)
}
ret = append(ret, commonEp)
}
@ -90,19 +91,19 @@ func (s *sqlDatabase) UpdateGithubEndpoint(_ context.Context, name string, param
err = s.conn.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("name = ? and endpoint_type = ?", name, params.GithubEndpointType).First(&endpoint).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "github endpoint not found")
return fmt.Errorf("error github endpoint not found: %w", runnerErrors.ErrNotFound)
}
return errors.Wrap(err, "fetching github endpoint")
return fmt.Errorf("error fetching github endpoint: %w", err)
}
var credsCount int64
if err := tx.Model(&GithubCredentials{}).Where("endpoint_name = ?", endpoint.Name).Count(&credsCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github credentials")
return fmt.Errorf("error fetching github credentials: %w", err)
}
}
if credsCount > 0 && (param.APIBaseURL != nil || param.BaseURL != nil || param.UploadBaseURL != nil) {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update endpoint URLs with existing credentials")
return fmt.Errorf("cannot update endpoint URLs with existing credentials: %w", runnerErrors.ErrBadRequest)
}
if param.APIBaseURL != nil {
@ -126,17 +127,17 @@ func (s *sqlDatabase) UpdateGithubEndpoint(_ context.Context, name string, param
}
if err := tx.Save(&endpoint).Error; err != nil {
return errors.Wrap(err, "updating github endpoint")
return fmt.Errorf("error updating github endpoint: %w", err)
}
return nil
})
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "updating github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error updating github endpoint: %w", err)
}
ghEndpoint, err = s.sqlToCommonGithubEndpoint(endpoint)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "converting github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error converting github endpoint: %w", err)
}
return ghEndpoint, nil
}
@ -147,9 +148,9 @@ func (s *sqlDatabase) GetGithubEndpoint(_ context.Context, name string) (params.
err := s.conn.Where("name = ? and endpoint_type = ?", name, params.GithubEndpointType).First(&endpoint).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return params.ForgeEndpoint{}, errors.Wrap(runnerErrors.ErrNotFound, "github endpoint not found")
return params.ForgeEndpoint{}, fmt.Errorf("github endpoint not found: %w", runnerErrors.ErrNotFound)
}
return params.ForgeEndpoint{}, errors.Wrap(err, "fetching github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("error fetching github endpoint: %w", err)
}
return s.sqlToCommonGithubEndpoint(endpoint)
@ -167,48 +168,48 @@ func (s *sqlDatabase) DeleteGithubEndpoint(_ context.Context, name string) (err
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(err, "fetching github endpoint")
return fmt.Errorf("error fetching github endpoint: %w", err)
}
var credsCount int64
if err := tx.Model(&GithubCredentials{}).Where("endpoint_name = ?", endpoint.Name).Count(&credsCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github credentials")
return fmt.Errorf("error fetching github credentials: %w", err)
}
}
var repoCnt int64
if err := tx.Model(&Repository{}).Where("endpoint_name = ?", endpoint.Name).Count(&repoCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github repositories")
return fmt.Errorf("error fetching github repositories: %w", err)
}
}
var orgCnt int64
if err := tx.Model(&Organization{}).Where("endpoint_name = ?", endpoint.Name).Count(&orgCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github organizations")
return fmt.Errorf("error fetching github organizations: %w", err)
}
}
var entCnt int64
if err := tx.Model(&Enterprise{}).Where("endpoint_name = ?", endpoint.Name).Count(&entCnt).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "fetching github enterprises")
return fmt.Errorf("error fetching github enterprises: %w", err)
}
}
if credsCount > 0 || repoCnt > 0 || orgCnt > 0 || entCnt > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete endpoint with associated entities")
return fmt.Errorf("cannot delete endpoint with associated entities: %w", runnerErrors.ErrBadRequest)
}
if err := tx.Unscoped().Delete(&endpoint).Error; err != nil {
return errors.Wrap(err, "deleting github endpoint")
return fmt.Errorf("error deleting github endpoint: %w", err)
}
return nil
})
if err != nil {
return errors.Wrap(err, "deleting github endpoint")
return fmt.Errorf("error deleting github endpoint: %w", err)
}
return nil
}
@ -216,10 +217,10 @@ func (s *sqlDatabase) DeleteGithubEndpoint(_ context.Context, name string) (err
func (s *sqlDatabase) CreateGithubCredentials(ctx context.Context, param params.CreateGithubCredentialsParams) (ghCreds params.ForgeCredentials, err error) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "creating github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error creating github credentials: %w", err)
}
if param.Endpoint == "" {
return params.ForgeCredentials{}, errors.Wrap(runnerErrors.ErrBadRequest, "endpoint name is required")
return params.ForgeCredentials{}, fmt.Errorf("endpoint name is required: %w", runnerErrors.ErrBadRequest)
}
defer func() {
@ -232,13 +233,13 @@ func (s *sqlDatabase) CreateGithubCredentials(ctx context.Context, param params.
var endpoint GithubEndpoint
if err := tx.Where("name = ? and endpoint_type = ?", param.Endpoint, params.GithubEndpointType).First(&endpoint).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "github endpoint not found")
return fmt.Errorf("github endpoint not found: %w", runnerErrors.ErrNotFound)
}
return errors.Wrap(err, "fetching github endpoint")
return fmt.Errorf("error fetching github endpoint: %w", err)
}
if err := tx.Where("name = ? and user_id = ?", param.Name, userID).First(&creds).Error; err == nil {
return errors.Wrap(runnerErrors.ErrDuplicateEntity, "github credentials already exists")
return fmt.Errorf("github credentials already exists: %w", runnerErrors.ErrDuplicateEntity)
}
var data []byte
@ -249,10 +250,10 @@ func (s *sqlDatabase) CreateGithubCredentials(ctx context.Context, param params.
case params.ForgeAuthTypeApp:
data, err = s.marshalAndSeal(param.App)
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid auth type")
return fmt.Errorf("invalid auth type: %w", runnerErrors.ErrBadRequest)
}
if err != nil {
return errors.Wrap(err, "marshaling and sealing credentials")
return fmt.Errorf("error marshaling and sealing credentials: %w", err)
}
creds = GithubCredentials{
@ -265,7 +266,7 @@ func (s *sqlDatabase) CreateGithubCredentials(ctx context.Context, param params.
}
if err := tx.Create(&creds).Error; err != nil {
return errors.Wrap(err, "creating github credentials")
return fmt.Errorf("error creating github credentials: %w", err)
}
// Skip making an extra query.
creds.Endpoint = endpoint
@ -273,11 +274,11 @@ func (s *sqlDatabase) CreateGithubCredentials(ctx context.Context, param params.
return nil
})
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "creating github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error creating github credentials: %w", err)
}
ghCreds, err = s.sqlToCommonForgeCredentials(creds)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error converting github credentials: %w", err)
}
return ghCreds, nil
}
@ -298,16 +299,16 @@ func (s *sqlDatabase) getGithubCredentialsByName(ctx context.Context, tx *gorm.D
userID, err := getUIDFromContext(ctx)
if err != nil {
return GithubCredentials{}, errors.Wrap(err, "fetching github credentials")
return GithubCredentials{}, fmt.Errorf("error fetching github credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
err = q.Where("name = ?", name).First(&creds).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return GithubCredentials{}, errors.Wrap(runnerErrors.ErrNotFound, "github credentials not found")
return GithubCredentials{}, fmt.Errorf("github credentials not found: %w", runnerErrors.ErrNotFound)
}
return GithubCredentials{}, errors.Wrap(err, "fetching github credentials")
return GithubCredentials{}, fmt.Errorf("error fetching github credentials: %w", err)
}
return creds, nil
@ -316,7 +317,7 @@ func (s *sqlDatabase) getGithubCredentialsByName(ctx context.Context, tx *gorm.D
func (s *sqlDatabase) GetGithubCredentialsByName(ctx context.Context, name string, detailed bool) (params.ForgeCredentials, error) {
creds, err := s.getGithubCredentialsByName(ctx, s.conn, name, detailed)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "fetching github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching github credentials: %w", err)
}
return s.sqlToCommonForgeCredentials(creds)
}
@ -338,7 +339,7 @@ func (s *sqlDatabase) GetGithubCredentials(ctx context.Context, id uint, detaile
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "fetching github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching github credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
@ -346,9 +347,9 @@ func (s *sqlDatabase) GetGithubCredentials(ctx context.Context, id uint, detaile
err := q.Where("id = ?", id).First(&creds).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return params.ForgeCredentials{}, errors.Wrap(runnerErrors.ErrNotFound, "github credentials not found")
return params.ForgeCredentials{}, fmt.Errorf("github credentials not found: %w", runnerErrors.ErrNotFound)
}
return params.ForgeCredentials{}, errors.Wrap(err, "fetching github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching github credentials: %w", err)
}
return s.sqlToCommonForgeCredentials(creds)
@ -359,7 +360,7 @@ func (s *sqlDatabase) ListGithubCredentials(ctx context.Context) ([]params.Forge
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching github credentials")
return nil, fmt.Errorf("error fetching github credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
@ -367,14 +368,14 @@ func (s *sqlDatabase) ListGithubCredentials(ctx context.Context) ([]params.Forge
var creds []GithubCredentials
err := q.Preload("Endpoint").Find(&creds).Error
if err != nil {
return nil, errors.Wrap(err, "fetching github credentials")
return nil, fmt.Errorf("error fetching github credentials: %w", err)
}
var ret []params.ForgeCredentials
for _, c := range creds {
commonCreds, err := s.sqlToCommonForgeCredentials(c)
if err != nil {
return nil, errors.Wrap(err, "converting github credentials")
return nil, fmt.Errorf("error converting github credentials: %w", err)
}
ret = append(ret, commonCreds)
}
@ -393,16 +394,16 @@ func (s *sqlDatabase) UpdateGithubCredentials(ctx context.Context, id uint, para
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return errors.Wrap(err, "updating github credentials")
return fmt.Errorf("error updating github credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
if err := q.Where("id = ?", id).First(&creds).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "github credentials not found")
return fmt.Errorf("github credentials not found: %w", runnerErrors.ErrNotFound)
}
return errors.Wrap(err, "fetching github credentials")
return fmt.Errorf("error fetching github credentials: %w", err)
}
if param.Name != nil {
@ -421,7 +422,7 @@ func (s *sqlDatabase) UpdateGithubCredentials(ctx context.Context, id uint, para
}
if param.App != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update app credentials for PAT")
return fmt.Errorf("cannot update app credentials for PAT: %w", runnerErrors.ErrBadRequest)
}
case params.ForgeAuthTypeApp:
if param.App != nil {
@ -429,33 +430,33 @@ func (s *sqlDatabase) UpdateGithubCredentials(ctx context.Context, id uint, para
}
if param.PAT != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot update PAT credentials for app")
return fmt.Errorf("cannot update PAT credentials for app: %w", runnerErrors.ErrBadRequest)
}
default:
// This should never happen, unless there was a bug in the DB migration code,
// or the DB was manually modified.
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid auth type")
return fmt.Errorf("invalid auth type: %w", runnerErrors.ErrBadRequest)
}
if err != nil {
return errors.Wrap(err, "marshaling and sealing credentials")
return fmt.Errorf("error marshaling and sealing credentials: %w", err)
}
if len(data) > 0 {
creds.Payload = data
}
if err := tx.Save(&creds).Error; err != nil {
return errors.Wrap(err, "updating github credentials")
return fmt.Errorf("error updating github credentials: %w", err)
}
return nil
})
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "updating github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error updating github credentials: %w", err)
}
ghCreds, err = s.sqlToCommonForgeCredentials(creds)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error converting github credentials: %w", err)
}
return ghCreds, nil
}
@ -475,7 +476,7 @@ func (s *sqlDatabase) DeleteGithubCredentials(ctx context.Context, id uint) (err
if !auth.IsAdmin(ctx) {
userID, err := getUIDFromContext(ctx)
if err != nil {
return errors.Wrap(err, "deleting github credentials")
return fmt.Errorf("error deleting github credentials: %w", err)
}
q = q.Where("user_id = ?", userID)
}
@ -486,27 +487,27 @@ func (s *sqlDatabase) DeleteGithubCredentials(ctx context.Context, id uint) (err
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(err, "fetching github credentials")
return fmt.Errorf("error fetching github credentials: %w", err)
}
name = creds.Name
if len(creds.Repositories) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with repositories")
return fmt.Errorf("cannot delete credentials with repositories: %w", runnerErrors.ErrBadRequest)
}
if len(creds.Organizations) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with organizations")
return fmt.Errorf("cannot delete credentials with organizations: %w", runnerErrors.ErrBadRequest)
}
if len(creds.Enterprises) > 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "cannot delete credentials with enterprises")
return fmt.Errorf("cannot delete credentials with enterprises: %w", runnerErrors.ErrBadRequest)
}
if err := tx.Unscoped().Delete(&creds).Error; err != nil {
return errors.Wrap(err, "deleting github credentials")
return fmt.Errorf("error deleting github credentials: %w", err)
}
return nil
})
if err != nil {
return errors.Wrap(err, "deleting github credentials")
return fmt.Errorf("error deleting github credentials: %w", err)
}
return nil
}

View file

@ -265,7 +265,7 @@ func (s *GithubTestSuite) TestUpdateEndpointURLsFailsIfCredentialsAreAssociated(
_, err = s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
s.Require().EqualError(err, "error updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGithubEndpointParams{
UploadBaseURL: &newUploadBaseURL,
@ -274,7 +274,7 @@ func (s *GithubTestSuite) TestUpdateEndpointURLsFailsIfCredentialsAreAssociated(
_, err = s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
s.Require().EqualError(err, "error updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGithubEndpointParams{
APIBaseURL: &newAPIBaseURL,
@ -282,7 +282,7 @@ func (s *GithubTestSuite) TestUpdateEndpointURLsFailsIfCredentialsAreAssociated(
_, err = s.db.UpdateGithubEndpoint(ctx, testEndpointName, updateEpParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
s.Require().EqualError(err, "error updating github endpoint: cannot update endpoint URLs with existing credentials: invalid request")
updateEpParams = params.UpdateGithubEndpointParams{
Description: &newDescription,
@ -737,7 +737,7 @@ func (s *GithubTestSuite) TestUpdateGithubCredentialsFailIfWrongCredentialTypeIs
_, err = s.db.UpdateGithubCredentials(ctx, creds.ID, updateCredParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github credentials: cannot update app credentials for PAT: invalid request")
s.Require().EqualError(err, "error updating github credentials: cannot update app credentials for PAT: invalid request")
credParamsWithApp := params.CreateGithubCredentialsParams{
Name: "test-credsApp",
@ -764,7 +764,7 @@ func (s *GithubTestSuite) TestUpdateGithubCredentialsFailIfWrongCredentialTypeIs
_, err = s.db.UpdateGithubCredentials(ctx, credsApp.ID, updateCredParams)
s.Require().Error(err)
s.Require().ErrorIs(err, runnerErrors.ErrBadRequest)
s.Require().EqualError(err, "updating github credentials: cannot update PAT credentials for app: invalid request")
s.Require().EqualError(err, "error updating github credentials: cannot update PAT credentials for app: invalid request")
}
func (s *GithubTestSuite) TestUpdateCredentialsFailsForNonExistingCredentials() {

View file

@ -17,10 +17,11 @@ package sql
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/clause"
@ -33,7 +34,7 @@ import (
func (s *sqlDatabase) CreateInstance(_ context.Context, poolID string, param params.CreateInstanceParams) (instance params.Instance, err error) {
pool, err := s.getPoolByID(s.conn, poolID)
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching pool")
return params.Instance{}, fmt.Errorf("error fetching pool: %w", err)
}
defer func() {
@ -46,7 +47,7 @@ func (s *sqlDatabase) CreateInstance(_ context.Context, poolID string, param par
if len(param.AditionalLabels) > 0 {
labels, err = json.Marshal(param.AditionalLabels)
if err != nil {
return params.Instance{}, errors.Wrap(err, "marshalling labels")
return params.Instance{}, fmt.Errorf("error marshalling labels: %w", err)
}
}
@ -54,7 +55,7 @@ func (s *sqlDatabase) CreateInstance(_ context.Context, poolID string, param par
if len(param.JitConfiguration) > 0 {
secret, err = s.marshalAndSeal(param.JitConfiguration)
if err != nil {
return params.Instance{}, errors.Wrap(err, "marshalling jit config")
return params.Instance{}, fmt.Errorf("error marshalling jit config: %w", err)
}
}
@ -74,7 +75,7 @@ func (s *sqlDatabase) CreateInstance(_ context.Context, poolID string, param par
}
q := s.conn.Create(&newInstance)
if q.Error != nil {
return params.Instance{}, errors.Wrap(q.Error, "creating instance")
return params.Instance{}, fmt.Errorf("error creating instance: %w", q.Error)
}
return s.sqlToParamsInstance(newInstance)
@ -83,7 +84,7 @@ func (s *sqlDatabase) CreateInstance(_ context.Context, poolID string, param par
func (s *sqlDatabase) getPoolInstanceByName(poolID string, instanceName string) (Instance, error) {
pool, err := s.getPoolByID(s.conn, poolID)
if err != nil {
return Instance{}, errors.Wrap(err, "fetching pool")
return Instance{}, fmt.Errorf("error fetching pool: %w", err)
}
var instance Instance
@ -93,18 +94,25 @@ func (s *sqlDatabase) getPoolInstanceByName(poolID string, instanceName string)
First(&instance)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Instance{}, errors.Wrap(runnerErrors.ErrNotFound, "fetching pool instance by name")
return Instance{}, fmt.Errorf("error fetching pool instance by name: %w", runnerErrors.ErrNotFound)
}
return Instance{}, errors.Wrap(q.Error, "fetching pool instance by name")
return Instance{}, fmt.Errorf("error fetching pool instance by name: %w", q.Error)
}
instance.Pool = pool
return instance, nil
}
func (s *sqlDatabase) getInstanceByName(_ context.Context, instanceName string, preload ...string) (Instance, error) {
func (s *sqlDatabase) getInstance(_ context.Context, instanceNameOrID string, preload ...string) (Instance, error) {
var instance Instance
var whereArg any = instanceNameOrID
whereClause := "name = ?"
id, err := uuid.Parse(instanceNameOrID)
if err == nil {
whereArg = id
whereClause = "id = ?"
}
q := s.conn
if len(preload) > 0 {
@ -115,30 +123,21 @@ func (s *sqlDatabase) getInstanceByName(_ context.Context, instanceName string,
q = q.Model(&Instance{}).
Preload(clause.Associations).
Where("name = ?", instanceName).
Where(whereClause, whereArg).
First(&instance)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Instance{}, errors.Wrap(runnerErrors.ErrNotFound, "fetching instance by name")
return Instance{}, fmt.Errorf("error fetching instance by name: %w", runnerErrors.ErrNotFound)
}
return Instance{}, errors.Wrap(q.Error, "fetching instance by name")
return Instance{}, fmt.Errorf("error fetching instance by name: %w", q.Error)
}
return instance, nil
}
func (s *sqlDatabase) GetPoolInstanceByName(_ context.Context, poolID string, instanceName string) (params.Instance, error) {
instance, err := s.getPoolInstanceByName(poolID, instanceName)
func (s *sqlDatabase) GetInstance(ctx context.Context, instanceName string) (params.Instance, error) {
instance, err := s.getInstance(ctx, instanceName, "StatusMessages", "Pool", "ScaleSet")
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching instance")
}
return s.sqlToParamsInstance(instance)
}
func (s *sqlDatabase) GetInstanceByName(ctx context.Context, instanceName string) (params.Instance, error) {
instance, err := s.getInstanceByName(ctx, instanceName, "StatusMessages", "Pool", "ScaleSet")
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching instance")
return params.Instance{}, fmt.Errorf("error fetching instance: %w", err)
}
return s.sqlToParamsInstance(instance)
@ -150,7 +149,7 @@ func (s *sqlDatabase) DeleteInstance(_ context.Context, poolID string, instanceN
if errors.Is(err, runnerErrors.ErrNotFound) {
return nil
}
return errors.Wrap(err, "deleting instance")
return fmt.Errorf("error deleting instance: %w", err)
}
defer func() {
@ -182,18 +181,18 @@ func (s *sqlDatabase) DeleteInstance(_ context.Context, poolID string, instanceN
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(q.Error, "deleting instance")
return fmt.Errorf("error deleting instance: %w", q.Error)
}
return nil
}
func (s *sqlDatabase) DeleteInstanceByName(ctx context.Context, instanceName string) error {
instance, err := s.getInstanceByName(ctx, instanceName, "Pool", "ScaleSet")
instance, err := s.getInstance(ctx, instanceName, "Pool", "ScaleSet")
if err != nil {
if errors.Is(err, runnerErrors.ErrNotFound) {
return nil
}
return errors.Wrap(err, "deleting instance")
return fmt.Errorf("error deleting instance: %w", err)
}
defer func() {
@ -224,15 +223,15 @@ func (s *sqlDatabase) DeleteInstanceByName(ctx context.Context, instanceName str
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(q.Error, "deleting instance")
return fmt.Errorf("error deleting instance: %w", q.Error)
}
return nil
}
func (s *sqlDatabase) AddInstanceEvent(ctx context.Context, instanceName string, event params.EventType, eventLevel params.EventLevel, statusMessage string) error {
instance, err := s.getInstanceByName(ctx, instanceName)
instance, err := s.getInstance(ctx, instanceName)
if err != nil {
return errors.Wrap(err, "updating instance")
return fmt.Errorf("error updating instance: %w", err)
}
msg := InstanceStatusUpdate{
@ -242,15 +241,15 @@ func (s *sqlDatabase) AddInstanceEvent(ctx context.Context, instanceName string,
}
if err := s.conn.Model(&instance).Association("StatusMessages").Append(&msg); err != nil {
return errors.Wrap(err, "adding status message")
return fmt.Errorf("error adding status message: %w", err)
}
return nil
}
func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceName string, param params.UpdateInstanceParams) (params.Instance, error) {
instance, err := s.getInstanceByName(ctx, instanceName, "Pool", "ScaleSet")
instance, err := s.getInstance(ctx, instanceName, "Pool", "ScaleSet")
if err != nil {
return params.Instance{}, errors.Wrap(err, "updating instance")
return params.Instance{}, fmt.Errorf("error updating instance: %w", err)
}
if param.AgentID != 0 {
@ -287,7 +286,7 @@ func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceName string, p
if param.JitConfiguration != nil {
secret, err := s.marshalAndSeal(param.JitConfiguration)
if err != nil {
return params.Instance{}, errors.Wrap(err, "marshalling jit config")
return params.Instance{}, fmt.Errorf("error marshalling jit config: %w", err)
}
instance.JitConfiguration = secret
}
@ -296,7 +295,7 @@ func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceName string, p
q := s.conn.Save(&instance)
if q.Error != nil {
return params.Instance{}, errors.Wrap(q.Error, "updating instance")
return params.Instance{}, fmt.Errorf("error updating instance: %w", q.Error)
}
if len(param.Addresses) > 0 {
@ -308,12 +307,12 @@ func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceName string, p
})
}
if err := s.conn.Model(&instance).Association("Addresses").Replace(addrs); err != nil {
return params.Instance{}, errors.Wrap(err, "updating addresses")
return params.Instance{}, fmt.Errorf("error updating addresses: %w", err)
}
}
inst, err := s.sqlToParamsInstance(instance)
if err != nil {
return params.Instance{}, errors.Wrap(err, "converting instance")
return params.Instance{}, fmt.Errorf("error converting instance: %w", err)
}
s.sendNotify(common.InstanceEntityType, common.UpdateOperation, inst)
return inst, nil
@ -322,21 +321,24 @@ func (s *sqlDatabase) UpdateInstance(ctx context.Context, instanceName string, p
func (s *sqlDatabase) ListPoolInstances(_ context.Context, poolID string) ([]params.Instance, error) {
u, err := uuid.Parse(poolID)
if err != nil {
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return nil, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
var instances []Instance
query := s.conn.Model(&Instance{}).Preload("Job").Where("pool_id = ?", u)
query := s.conn.
Preload("Pool").
Preload("Job").
Where("pool_id = ?", u)
if err := query.Find(&instances); err.Error != nil {
return nil, errors.Wrap(err.Error, "fetching instances")
return nil, fmt.Errorf("error fetching instances: %w", err.Error)
}
ret := make([]params.Instance, len(instances))
for idx, inst := range instances {
ret[idx], err = s.sqlToParamsInstance(inst)
if err != nil {
return nil, errors.Wrap(err, "converting instance")
return nil, fmt.Errorf("error converting instance: %w", err)
}
}
return ret, nil
@ -345,16 +347,20 @@ func (s *sqlDatabase) ListPoolInstances(_ context.Context, poolID string) ([]par
func (s *sqlDatabase) ListAllInstances(_ context.Context) ([]params.Instance, error) {
var instances []Instance
q := s.conn.Model(&Instance{}).Preload("Job").Find(&instances)
q := s.conn.
Preload("Pool").
Preload("ScaleSet").
Preload("Job").
Find(&instances)
if q.Error != nil {
return nil, errors.Wrap(q.Error, "fetching instances")
return nil, fmt.Errorf("error fetching instances: %w", q.Error)
}
ret := make([]params.Instance, len(instances))
var err error
for idx, instance := range instances {
ret[idx], err = s.sqlToParamsInstance(instance)
if err != nil {
return nil, errors.Wrap(err, "converting instance")
return nil, fmt.Errorf("error converting instance: %w", err)
}
}
return ret, nil
@ -363,13 +369,13 @@ func (s *sqlDatabase) ListAllInstances(_ context.Context) ([]params.Instance, er
func (s *sqlDatabase) PoolInstanceCount(_ context.Context, poolID string) (int64, error) {
pool, err := s.getPoolByID(s.conn, poolID)
if err != nil {
return 0, errors.Wrap(err, "fetching pool")
return 0, fmt.Errorf("error fetching pool: %w", err)
}
var cnt int64
q := s.conn.Model(&Instance{}).Where("pool_id = ?", pool.ID).Count(&cnt)
if q.Error != nil {
return 0, errors.Wrap(q.Error, "fetching instance count")
return 0, fmt.Errorf("error fetching instance count: %w", q.Error)
}
return cnt, nil
}

View file

@ -196,7 +196,7 @@ func (s *InstancesTestSuite) TestCreateInstance() {
// assertions
s.Require().Nil(err)
storeInstance, err := s.Store.GetInstanceByName(s.adminCtx, s.Fixtures.CreateInstanceParams.Name)
storeInstance, err := s.Store.GetInstance(s.adminCtx, s.Fixtures.CreateInstanceParams.Name)
if err != nil {
s.FailNow(fmt.Sprintf("failed to get instance: %v", err))
}
@ -210,7 +210,7 @@ func (s *InstancesTestSuite) TestCreateInstance() {
func (s *InstancesTestSuite) TestCreateInstanceInvalidPoolID() {
_, err := s.Store.CreateInstance(s.adminCtx, "dummy-pool-id", params.CreateInstanceParams{})
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool: error parsing id: invalid request", err.Error())
}
func (s *InstancesTestSuite) TestCreateInstanceDBCreateErr() {
@ -233,32 +233,13 @@ func (s *InstancesTestSuite) TestCreateInstanceDBCreateErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("creating instance: mocked insert instance error", err.Error())
}
func (s *InstancesTestSuite) TestGetPoolInstanceByName() {
storeInstance := s.Fixtures.Instances[0] // this is already created in `SetupTest()`
instance, err := s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Nil(err)
s.Require().Equal(storeInstance.Name, instance.Name)
s.Require().Equal(storeInstance.PoolID, instance.PoolID)
s.Require().Equal(storeInstance.OSArch, instance.OSArch)
s.Require().Equal(storeInstance.OSType, instance.OSType)
s.Require().Equal(storeInstance.CallbackURL, instance.CallbackURL)
}
func (s *InstancesTestSuite) TestGetPoolInstanceByNameNotFound() {
_, err := s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, "not-existent-instance-name")
s.Require().Equal("fetching instance: fetching pool instance by name: not found", err.Error())
s.Require().Equal("error creating instance: mocked insert instance error", err.Error())
}
func (s *InstancesTestSuite) TestGetInstanceByName() {
storeInstance := s.Fixtures.Instances[1]
instance, err := s.Store.GetInstanceByName(s.adminCtx, storeInstance.Name)
instance, err := s.Store.GetInstance(s.adminCtx, storeInstance.Name)
s.Require().Nil(err)
s.Require().Equal(storeInstance.Name, instance.Name)
@ -269,9 +250,9 @@ func (s *InstancesTestSuite) TestGetInstanceByName() {
}
func (s *InstancesTestSuite) TestGetInstanceByNameFetchInstanceFailed() {
_, err := s.Store.GetInstanceByName(s.adminCtx, "not-existent-instance-name")
_, err := s.Store.GetInstance(s.adminCtx, "not-existent-instance-name")
s.Require().Equal("fetching instance: fetching instance by name: not found", err.Error())
s.Require().Equal("error fetching instance: error fetching instance by name: not found", err.Error())
}
func (s *InstancesTestSuite) TestDeleteInstance() {
@ -281,8 +262,8 @@ func (s *InstancesTestSuite) TestDeleteInstance() {
s.Require().Nil(err)
_, err = s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Equal("fetching instance: fetching pool instance by name: not found", err.Error())
_, err = s.Store.GetInstance(s.adminCtx, storeInstance.Name)
s.Require().Equal("error fetching instance: error fetching instance by name: not found", err.Error())
err = s.Store.DeleteInstance(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Nil(err)
@ -295,8 +276,8 @@ func (s *InstancesTestSuite) TestDeleteInstanceByName() {
s.Require().Nil(err)
_, err = s.Store.GetPoolInstanceByName(s.adminCtx, s.Fixtures.Pool.ID, storeInstance.Name)
s.Require().Equal("fetching instance: fetching pool instance by name: not found", err.Error())
_, err = s.Store.GetInstance(s.adminCtx, storeInstance.Name)
s.Require().Equal("error fetching instance: error fetching instance by name: not found", err.Error())
err = s.Store.DeleteInstanceByName(s.adminCtx, storeInstance.Name)
s.Require().Nil(err)
@ -305,7 +286,7 @@ func (s *InstancesTestSuite) TestDeleteInstanceByName() {
func (s *InstancesTestSuite) TestDeleteInstanceInvalidPoolID() {
err := s.Store.DeleteInstance(s.adminCtx, "dummy-pool-id", "dummy-instance-name")
s.Require().Equal("deleting instance: fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("error deleting instance: error fetching pool: error parsing id: invalid request", err.Error())
}
func (s *InstancesTestSuite) TestDeleteInstanceDBRecordNotFoundErr() {
@ -380,7 +361,7 @@ func (s *InstancesTestSuite) TestDeleteInstanceDBDeleteErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("deleting instance: mocked delete instance error", err.Error())
s.Require().Equal("error deleting instance: mocked delete instance error", err.Error())
}
func (s *InstancesTestSuite) TestAddInstanceEvent() {
@ -390,7 +371,7 @@ func (s *InstancesTestSuite) TestAddInstanceEvent() {
err := s.Store.AddInstanceEvent(s.adminCtx, storeInstance.Name, params.StatusEvent, params.EventInfo, statusMsg)
s.Require().Nil(err)
instance, err := s.Store.GetInstanceByName(s.adminCtx, storeInstance.Name)
instance, err := s.Store.GetInstance(s.adminCtx, storeInstance.Name)
if err != nil {
s.FailNow(fmt.Sprintf("failed to get db instance: %s", err))
}
@ -431,7 +412,7 @@ func (s *InstancesTestSuite) TestAddInstanceEventDBUpdateErr() {
err := s.StoreSQLMocked.AddInstanceEvent(s.adminCtx, instance.Name, params.StatusEvent, params.EventInfo, statusMsg)
s.Require().NotNil(err)
s.Require().Equal("adding status message: mocked add status message error", err.Error())
s.Require().Equal("error adding status message: mocked add status message error", err.Error())
s.assertSQLMockExpectations()
}
@ -476,7 +457,7 @@ func (s *InstancesTestSuite) TestUpdateInstanceDBUpdateInstanceErr() {
_, err := s.StoreSQLMocked.UpdateInstance(s.adminCtx, instance.Name, s.Fixtures.UpdateInstanceParams)
s.Require().NotNil(err)
s.Require().Equal("updating instance: mocked update instance error", err.Error())
s.Require().Equal("error updating instance: mocked update instance error", err.Error())
s.assertSQLMockExpectations()
}
@ -522,7 +503,7 @@ func (s *InstancesTestSuite) TestUpdateInstanceDBUpdateAddressErr() {
_, err := s.StoreSQLMocked.UpdateInstance(s.adminCtx, instance.Name, s.Fixtures.UpdateInstanceParams)
s.Require().NotNil(err)
s.Require().Equal("updating addresses: update addresses mock error", err.Error())
s.Require().Equal("error updating addresses: update addresses mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -536,7 +517,7 @@ func (s *InstancesTestSuite) TestListPoolInstances() {
func (s *InstancesTestSuite) TestListPoolInstancesInvalidPoolID() {
_, err := s.Store.ListPoolInstances(s.adminCtx, "dummy-pool-id")
s.Require().Equal("parsing id: invalid request", err.Error())
s.Require().Equal("error parsing id: invalid request", err.Error())
}
func (s *InstancesTestSuite) TestListAllInstances() {
@ -555,7 +536,7 @@ func (s *InstancesTestSuite) TestListAllInstancesDBFetchErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching instances: fetch instances mock error", err.Error())
s.Require().Equal("error fetching instances: fetch instances mock error", err.Error())
}
func (s *InstancesTestSuite) TestPoolInstanceCount() {
@ -568,7 +549,7 @@ func (s *InstancesTestSuite) TestPoolInstanceCount() {
func (s *InstancesTestSuite) TestPoolInstanceCountInvalidPoolID() {
_, err := s.Store.PoolInstanceCount(s.adminCtx, "dummy-pool-id")
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool: error parsing id: invalid request", err.Error())
}
func (s *InstancesTestSuite) TestPoolInstanceCountDBCountErr() {
@ -587,7 +568,7 @@ func (s *InstancesTestSuite) TestPoolInstanceCountDBCountErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching instance count: count mock error", err.Error())
s.Require().Equal("error fetching instance count: count mock error", err.Error())
}
func TestInstTestSuite(t *testing.T) {

View file

@ -17,10 +17,11 @@ package sql
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
@ -35,12 +36,14 @@ func sqlWorkflowJobToParamsJob(job WorkflowJob) (params.Job, error) {
labels := []string{}
if job.Labels != nil {
if err := json.Unmarshal(job.Labels, &labels); err != nil {
return params.Job{}, errors.Wrap(err, "unmarshaling labels")
return params.Job{}, fmt.Errorf("error unmarshaling labels: %w", err)
}
}
jobParam := params.Job{
ID: job.ID,
WorkflowJobID: job.WorkflowJobID,
ScaleSetJobID: job.ScaleSetJobID,
RunID: job.RunID,
Action: job.Action,
Status: job.Status,
@ -71,11 +74,12 @@ func sqlWorkflowJobToParamsJob(job WorkflowJob) (params.Job, error) {
func (s *sqlDatabase) paramsJobToWorkflowJob(ctx context.Context, job params.Job) (WorkflowJob, error) {
asJSON, err := json.Marshal(job.Labels)
if err != nil {
return WorkflowJob{}, errors.Wrap(err, "marshaling labels")
return WorkflowJob{}, fmt.Errorf("error marshaling labels: %w", err)
}
workflofJob := WorkflowJob{
ID: job.ID,
ScaleSetJobID: job.ScaleSetJobID,
WorkflowJobID: job.WorkflowJobID,
RunID: job.RunID,
Action: job.Action,
Status: job.Status,
@ -96,7 +100,7 @@ func (s *sqlDatabase) paramsJobToWorkflowJob(ctx context.Context, job params.Job
}
if job.RunnerName != "" {
instance, err := s.getInstanceByName(s.ctx, job.RunnerName)
instance, err := s.getInstance(s.ctx, job.RunnerName)
if err != nil {
// This usually is very normal as not all jobs run on our runners.
slog.DebugContext(ctx, "failed to get instance by name", "instance_name", job.RunnerName)
@ -109,19 +113,32 @@ func (s *sqlDatabase) paramsJobToWorkflowJob(ctx context.Context, job params.Job
}
func (s *sqlDatabase) DeleteJob(_ context.Context, jobID int64) (err error) {
defer func() {
if err == nil {
if notifyErr := s.sendNotify(common.JobEntityType, common.DeleteOperation, params.Job{ID: jobID}); notifyErr != nil {
slog.With(slog.Any("error", notifyErr)).Error("failed to send notify")
}
}
}()
q := s.conn.Delete(&WorkflowJob{}, jobID)
var workflowJob WorkflowJob
q := s.conn.Where("workflow_job_id = ?", jobID).Preload("Instance").First(&workflowJob)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(q.Error, "deleting job")
return fmt.Errorf("error fetching job: %w", q.Error)
}
removedJob, err := sqlWorkflowJobToParamsJob(workflowJob)
if err != nil {
return fmt.Errorf("error converting job: %w", err)
}
defer func() {
if err == nil {
if notifyErr := s.sendNotify(common.JobEntityType, common.DeleteOperation, removedJob); notifyErr != nil {
slog.With(slog.Any("error", notifyErr)).Error("failed to send notify")
}
}
}()
q = s.conn.Delete(&workflowJob)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil
}
return fmt.Errorf("error deleting job: %w", q.Error)
}
return nil
}
@ -129,16 +146,16 @@ func (s *sqlDatabase) DeleteJob(_ context.Context, jobID int64) (err error) {
func (s *sqlDatabase) LockJob(_ context.Context, jobID int64, entityID string) error {
entityUUID, err := uuid.Parse(entityID)
if err != nil {
return errors.Wrap(err, "parsing entity id")
return fmt.Errorf("error parsing entity id: %w", err)
}
var workflowJob WorkflowJob
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("id = ?", jobID).First(&workflowJob)
q := s.conn.Preload("Instance").Where("workflow_job_id = ?", jobID).First(&workflowJob)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return runnerErrors.ErrNotFound
}
return errors.Wrap(q.Error, "fetching job")
return fmt.Errorf("error fetching job: %w", q.Error)
}
if workflowJob.LockedBy.String() == entityID {
@ -153,12 +170,12 @@ func (s *sqlDatabase) LockJob(_ context.Context, jobID int64, entityID string) e
workflowJob.LockedBy = entityUUID
if err := s.conn.Save(&workflowJob).Error; err != nil {
return errors.Wrap(err, "saving job")
return fmt.Errorf("error saving job: %w", err)
}
asParams, err := sqlWorkflowJobToParamsJob(workflowJob)
if err != nil {
return errors.Wrap(err, "converting job")
return fmt.Errorf("error converting job: %w", err)
}
s.sendNotify(common.JobEntityType, common.UpdateOperation, asParams)
@ -167,13 +184,13 @@ func (s *sqlDatabase) LockJob(_ context.Context, jobID int64, entityID string) e
func (s *sqlDatabase) BreakLockJobIsQueued(_ context.Context, jobID int64) (err error) {
var workflowJob WorkflowJob
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("id = ? and status = ?", jobID, params.JobStatusQueued).First(&workflowJob)
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("workflow_job_id = ? and status = ?", jobID, params.JobStatusQueued).First(&workflowJob)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil
}
return errors.Wrap(q.Error, "fetching job")
return fmt.Errorf("error fetching job: %w", q.Error)
}
if workflowJob.LockedBy == uuid.Nil {
@ -183,11 +200,11 @@ func (s *sqlDatabase) BreakLockJobIsQueued(_ context.Context, jobID int64) (err
workflowJob.LockedBy = uuid.Nil
if err := s.conn.Save(&workflowJob).Error; err != nil {
return errors.Wrap(err, "saving job")
return fmt.Errorf("error saving job: %w", err)
}
asParams, err := sqlWorkflowJobToParamsJob(workflowJob)
if err != nil {
return errors.Wrap(err, "converting job")
return fmt.Errorf("error converting job: %w", err)
}
s.sendNotify(common.JobEntityType, common.UpdateOperation, asParams)
return nil
@ -195,13 +212,13 @@ func (s *sqlDatabase) BreakLockJobIsQueued(_ context.Context, jobID int64) (err
func (s *sqlDatabase) UnlockJob(_ context.Context, jobID int64, entityID string) error {
var workflowJob WorkflowJob
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", jobID).First(&workflowJob)
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Where("workflow_job_id = ?", jobID).First(&workflowJob)
if q.Error != nil {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return runnerErrors.ErrNotFound
}
return errors.Wrap(q.Error, "fetching job")
return fmt.Errorf("error fetching job: %w", q.Error)
}
if workflowJob.LockedBy == uuid.Nil {
@ -215,12 +232,12 @@ func (s *sqlDatabase) UnlockJob(_ context.Context, jobID int64, entityID string)
workflowJob.LockedBy = uuid.Nil
if err := s.conn.Save(&workflowJob).Error; err != nil {
return errors.Wrap(err, "saving job")
return fmt.Errorf("error saving job: %w", err)
}
asParams, err := sqlWorkflowJobToParamsJob(workflowJob)
if err != nil {
return errors.Wrap(err, "converting job")
return fmt.Errorf("error converting job: %w", err)
}
s.sendNotify(common.JobEntityType, common.UpdateOperation, asParams)
return nil
@ -229,11 +246,18 @@ func (s *sqlDatabase) UnlockJob(_ context.Context, jobID int64, entityID string)
func (s *sqlDatabase) CreateOrUpdateJob(ctx context.Context, job params.Job) (params.Job, error) {
var workflowJob WorkflowJob
var err error
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("id = ?", job.ID).First(&workflowJob)
searchField := "workflow_job_id = ?"
var searchVal any = job.WorkflowJobID
if job.ScaleSetJobID != "" {
searchField = "scale_set_job_id = ?"
searchVal = job.ScaleSetJobID
}
q := s.conn.Preload("Instance").Where(searchField, searchVal).First(&workflowJob)
if q.Error != nil {
if !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return params.Job{}, errors.Wrap(q.Error, "fetching job")
return params.Job{}, fmt.Errorf("error fetching job: %w", q.Error)
}
}
var operation common.OperationType
@ -249,13 +273,16 @@ func (s *sqlDatabase) CreateOrUpdateJob(ctx context.Context, job params.Job) (pa
workflowJob.GithubRunnerID = job.GithubRunnerID
workflowJob.RunnerGroupID = job.RunnerGroupID
workflowJob.RunnerGroupName = job.RunnerGroupName
if job.RunID != 0 && workflowJob.RunID == 0 {
workflowJob.RunID = job.RunID
}
if job.LockedBy != uuid.Nil {
workflowJob.LockedBy = job.LockedBy
}
if job.RunnerName != "" {
instance, err := s.getInstanceByName(ctx, job.RunnerName)
instance, err := s.getInstance(ctx, job.RunnerName)
if err == nil {
workflowJob.InstanceID = &instance.ID
} else {
@ -276,23 +303,23 @@ func (s *sqlDatabase) CreateOrUpdateJob(ctx context.Context, job params.Job) (pa
workflowJob.EnterpriseID = job.EnterpriseID
}
if err := s.conn.Save(&workflowJob).Error; err != nil {
return params.Job{}, errors.Wrap(err, "saving job")
return params.Job{}, fmt.Errorf("error saving job: %w", err)
}
} else {
operation = common.CreateOperation
workflowJob, err = s.paramsJobToWorkflowJob(ctx, job)
if err != nil {
return params.Job{}, errors.Wrap(err, "converting job")
return params.Job{}, fmt.Errorf("error converting job: %w", err)
}
if err := s.conn.Create(&workflowJob).Error; err != nil {
return params.Job{}, errors.Wrap(err, "creating job")
return params.Job{}, fmt.Errorf("error creating job: %w", err)
}
}
asParams, err := sqlWorkflowJobToParamsJob(workflowJob)
if err != nil {
return params.Job{}, errors.Wrap(err, "converting job")
return params.Job{}, fmt.Errorf("error converting job: %w", err)
}
s.sendNotify(common.JobEntityType, operation, asParams)
@ -312,7 +339,7 @@ func (s *sqlDatabase) ListJobsByStatus(_ context.Context, status params.JobStatu
for idx, job := range jobs {
jobParam, err := sqlWorkflowJobToParamsJob(job)
if err != nil {
return nil, errors.Wrap(err, "converting job")
return nil, fmt.Errorf("error converting job: %w", err)
}
ret[idx] = jobParam
}
@ -327,7 +354,11 @@ func (s *sqlDatabase) ListEntityJobsByStatus(_ context.Context, entityType param
}
var jobs []WorkflowJob
query := s.conn.Model(&WorkflowJob{}).Preload("Instance").Where("status = ?", status)
query := s.conn.
Model(&WorkflowJob{}).
Preload("Instance").
Where("status = ?", status).
Where("workflow_job_id > 0")
switch entityType {
case params.ForgeEntityTypeOrganization:
@ -349,7 +380,7 @@ func (s *sqlDatabase) ListEntityJobsByStatus(_ context.Context, entityType param
for idx, job := range jobs {
jobParam, err := sqlWorkflowJobToParamsJob(job)
if err != nil {
return nil, errors.Wrap(err, "converting job")
return nil, fmt.Errorf("error converting job: %w", err)
}
ret[idx] = jobParam
}
@ -371,7 +402,7 @@ func (s *sqlDatabase) ListAllJobs(_ context.Context) ([]params.Job, error) {
for idx, job := range jobs {
jobParam, err := sqlWorkflowJobToParamsJob(job)
if err != nil {
return nil, errors.Wrap(err, "converting job")
return nil, fmt.Errorf("error converting job: %w", err)
}
ret[idx] = jobParam
}
@ -381,7 +412,7 @@ func (s *sqlDatabase) ListAllJobs(_ context.Context) ([]params.Job, error) {
// GetJobByID gets a job by id.
func (s *sqlDatabase) GetJobByID(_ context.Context, jobID int64) (params.Job, error) {
var job WorkflowJob
query := s.conn.Model(&WorkflowJob{}).Preload("Instance").Where("id = ?", jobID)
query := s.conn.Model(&WorkflowJob{}).Preload("Instance").Where("workflow_job_id = ?", jobID)
if err := query.First(&job); err.Error != nil {
if errors.Is(err.Error, gorm.ErrRecordNotFound) {

View file

@ -15,10 +15,10 @@
package sql
import (
"fmt"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/datatypes"
"gorm.io/gorm"
@ -40,7 +40,7 @@ func (b *Base) BeforeCreate(_ *gorm.DB) error {
}
newID, err := uuid.NewRandom()
if err != nil {
return errors.Wrap(err, "generating id")
return fmt.Errorf("error generating id: %w", err)
}
b.ID = newID
return nil
@ -319,6 +319,12 @@ type User struct {
type WorkflowJob struct {
// ID is the ID of the job.
ID int64 `gorm:"index"`
// WorkflowJobID is the ID of the workflow job.
WorkflowJobID int64 `gorm:"index:workflow_job_id_idx"`
// ScaleSetJobID is the job ID for a scaleset job.
ScaleSetJobID string `gorm:"index:scaleset_job_id_idx"`
// RunID is the ID of the workflow run. A run may have multiple jobs.
RunID int64
// Action is the specific activity that triggered the event.

View file

@ -16,11 +16,11 @@ package sql
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -35,7 +35,7 @@ func (s *sqlDatabase) CreateOrganization(ctx context.Context, name string, crede
}
secret, err := util.Seal([]byte(webhookSecret), []byte(s.cfg.Passphrase))
if err != nil {
return params.Organization{}, errors.Wrap(err, "encoding secret")
return params.Organization{}, fmt.Errorf("error encoding secret: %w", err)
}
defer func() {
@ -56,23 +56,23 @@ func (s *sqlDatabase) CreateOrganization(ctx context.Context, name string, crede
case params.GiteaEndpointType:
newOrg.GiteaCredentialsID = &credentials.ID
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "unsupported credentials type")
return fmt.Errorf("unsupported credentials type: %w", runnerErrors.ErrBadRequest)
}
newOrg.EndpointName = &credentials.Endpoint.Name
q := tx.Create(&newOrg)
if q.Error != nil {
return errors.Wrap(q.Error, "creating org")
return fmt.Errorf("error creating org: %w", q.Error)
}
return nil
})
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org")
return params.Organization{}, fmt.Errorf("error creating org: %w", err)
}
ret, err := s.GetOrganizationByID(ctx, newOrg.ID.String())
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org")
return params.Organization{}, fmt.Errorf("error creating org: %w", err)
}
return ret, nil
@ -81,12 +81,12 @@ func (s *sqlDatabase) CreateOrganization(ctx context.Context, name string, crede
func (s *sqlDatabase) GetOrganization(ctx context.Context, name, endpointName string) (params.Organization, error) {
org, err := s.getOrg(ctx, name, endpointName)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
return params.Organization{}, fmt.Errorf("error fetching org: %w", err)
}
param, err := s.sqlToCommonOrganization(org, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
return params.Organization{}, fmt.Errorf("error fetching org: %w", err)
}
return param, nil
@ -110,7 +110,7 @@ func (s *sqlDatabase) ListOrganizations(_ context.Context, filter params.Organiz
}
q = q.Find(&orgs)
if q.Error != nil {
return []params.Organization{}, errors.Wrap(q.Error, "fetching org from database")
return []params.Organization{}, fmt.Errorf("error fetching org from database: %w", q.Error)
}
ret := make([]params.Organization, len(orgs))
@ -118,7 +118,7 @@ func (s *sqlDatabase) ListOrganizations(_ context.Context, filter params.Organiz
var err error
ret[idx], err = s.sqlToCommonOrganization(val, true)
if err != nil {
return nil, errors.Wrap(err, "fetching org")
return nil, fmt.Errorf("error fetching org: %w", err)
}
}
@ -128,7 +128,7 @@ func (s *sqlDatabase) ListOrganizations(_ context.Context, filter params.Organiz
func (s *sqlDatabase) DeleteOrganization(ctx context.Context, orgID string) (err error) {
org, err := s.getOrgByID(ctx, s.conn, orgID, "Endpoint", "Credentials", "Credentials.Endpoint", "GiteaCredentials", "GiteaCredentials.Endpoint")
if err != nil {
return errors.Wrap(err, "fetching org")
return fmt.Errorf("error fetching org: %w", err)
}
defer func(org Organization) {
@ -144,7 +144,7 @@ func (s *sqlDatabase) DeleteOrganization(ctx context.Context, orgID string) (err
q := s.conn.Unscoped().Delete(&org)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting org")
return fmt.Errorf("error deleting org: %w", q.Error)
}
return nil
@ -162,23 +162,23 @@ func (s *sqlDatabase) UpdateOrganization(ctx context.Context, orgID string, para
var err error
org, err = s.getOrgByID(ctx, tx, orgID)
if err != nil {
return errors.Wrap(err, "fetching org")
return fmt.Errorf("error fetching org: %w", err)
}
if org.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "org has no endpoint")
return fmt.Errorf("error org has no endpoint: %w", runnerErrors.ErrUnprocessable)
}
if param.CredentialsName != "" {
creds, err = s.getGithubCredentialsByName(ctx, tx, param.CredentialsName, false)
if err != nil {
return errors.Wrap(err, "fetching credentials")
return fmt.Errorf("error fetching credentials: %w", err)
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
return fmt.Errorf("error credentials have no endpoint: %w", runnerErrors.ErrUnprocessable)
}
if *creds.EndpointName != *org.EndpointName {
return errors.Wrap(runnerErrors.ErrBadRequest, "endpoint mismatch")
return fmt.Errorf("error endpoint mismatch: %w", runnerErrors.ErrBadRequest)
}
org.CredentialsID = &creds.ID
}
@ -197,22 +197,22 @@ func (s *sqlDatabase) UpdateOrganization(ctx context.Context, orgID string, para
q := tx.Save(&org)
if q.Error != nil {
return errors.Wrap(q.Error, "saving org")
return fmt.Errorf("error saving org: %w", q.Error)
}
return nil
})
if err != nil {
return params.Organization{}, errors.Wrap(err, "saving org")
return params.Organization{}, fmt.Errorf("error saving org: %w", err)
}
org, err = s.getOrgByID(ctx, s.conn, orgID, "Endpoint", "Credentials", "Credentials.Endpoint", "GiteaCredentials", "GiteaCredentials.Endpoint")
if err != nil {
return params.Organization{}, errors.Wrap(err, "updating enterprise")
return params.Organization{}, fmt.Errorf("error updating enterprise: %w", err)
}
paramOrg, err = s.sqlToCommonOrganization(org, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "saving org")
return params.Organization{}, fmt.Errorf("error saving org: %w", err)
}
return paramOrg, nil
}
@ -229,12 +229,12 @@ func (s *sqlDatabase) GetOrganizationByID(ctx context.Context, orgID string) (pa
}
org, err := s.getOrgByID(ctx, s.conn, orgID, preloadList...)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
return params.Organization{}, fmt.Errorf("error fetching org: %w", err)
}
param, err := s.sqlToCommonOrganization(org, true)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching org")
return params.Organization{}, fmt.Errorf("error fetching org: %w", err)
}
return param, nil
}
@ -242,7 +242,7 @@ func (s *sqlDatabase) GetOrganizationByID(ctx context.Context, orgID string) (pa
func (s *sqlDatabase) getOrgByID(_ context.Context, db *gorm.DB, id string, preload ...string) (Organization, error) {
u, err := uuid.Parse(id)
if err != nil {
return Organization{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return Organization{}, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
var org Organization
@ -258,7 +258,7 @@ func (s *sqlDatabase) getOrgByID(_ context.Context, db *gorm.DB, id string, prel
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Organization{}, runnerErrors.ErrNotFound
}
return Organization{}, errors.Wrap(q.Error, "fetching org from database")
return Organization{}, fmt.Errorf("error fetching org from database: %w", q.Error)
}
return org, nil
}
@ -277,7 +277,7 @@ func (s *sqlDatabase) getOrg(_ context.Context, name, endpointName string) (Orga
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Organization{}, runnerErrors.ErrNotFound
}
return Organization{}, errors.Wrap(q.Error, "fetching org from database")
return Organization{}, fmt.Errorf("error fetching org from database: %w", q.Error)
}
return org, nil
}

View file

@ -251,7 +251,7 @@ func (s *OrgTestSuite) TestCreateOrganizationInvalidForgeType() {
s.Fixtures.CreateOrgParams.WebhookSecret,
params.PoolBalancerTypeRoundRobin)
s.Require().NotNil(err)
s.Require().Equal("creating org: unsupported credentials type: invalid request", err.Error())
s.Require().Equal("error creating org: unsupported credentials type: invalid request", err.Error())
}
func (s *OrgTestSuite) TestCreateOrganizationInvalidDBPassphrase() {
@ -275,7 +275,7 @@ func (s *OrgTestSuite) TestCreateOrganizationInvalidDBPassphrase() {
params.PoolBalancerTypeRoundRobin)
s.Require().NotNil(err)
s.Require().Equal("encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error encoding secret: invalid passphrase length (expected length 32 characters)", err.Error())
}
func (s *OrgTestSuite) TestCreateOrganizationDBCreateErr() {
@ -293,7 +293,7 @@ func (s *OrgTestSuite) TestCreateOrganizationDBCreateErr() {
params.PoolBalancerTypeRoundRobin)
s.Require().NotNil(err)
s.Require().Equal("creating org: creating org: creating org mock error", err.Error())
s.Require().Equal("error creating org: error creating org: creating org mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -316,7 +316,7 @@ func (s *OrgTestSuite) TestGetOrganizationNotFound() {
_, err := s.Store.GetOrganization(s.adminCtx, "dummy-name", "github.com")
s.Require().NotNil(err)
s.Require().Equal("fetching org: not found", err.Error())
s.Require().Equal("error fetching org: not found", err.Error())
}
func (s *OrgTestSuite) TestGetOrganizationDBDecryptingErr() {
@ -328,7 +328,7 @@ func (s *OrgTestSuite) TestGetOrganizationDBDecryptingErr() {
_, err := s.StoreSQLMocked.GetOrganization(s.adminCtx, s.Fixtures.Orgs[0].Name, s.Fixtures.Orgs[0].Endpoint.Name)
s.Require().NotNil(err)
s.Require().Equal("fetching org: missing secret", err.Error())
s.Require().Equal("error fetching org: missing secret", err.Error())
s.assertSQLMockExpectations()
}
@ -404,7 +404,7 @@ func (s *OrgTestSuite) TestListOrganizationsDBFetchErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching org from database: fetching user from database mock error", err.Error())
s.Require().Equal("error fetching org from database: fetching user from database mock error", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganization() {
@ -413,14 +413,14 @@ func (s *OrgTestSuite) TestDeleteOrganization() {
s.Require().Nil(err)
_, err = s.Store.GetOrganizationByID(s.adminCtx, s.Fixtures.Orgs[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching org: not found", err.Error())
s.Require().Equal("error fetching org: not found", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganizationInvalidOrgID() {
err := s.Store.DeleteOrganization(s.adminCtx, "dummy-org-id")
s.Require().NotNil(err)
s.Require().Equal("fetching org: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching org: error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganizationDBDeleteErr() {
@ -439,7 +439,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationDBDeleteErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("deleting org: mocked delete org error", err.Error())
s.Require().Equal("error deleting org: mocked delete org error", err.Error())
}
func (s *OrgTestSuite) TestUpdateOrganization() {
@ -454,7 +454,7 @@ func (s *OrgTestSuite) TestUpdateOrganizationInvalidOrgID() {
_, err := s.Store.UpdateOrganization(s.adminCtx, "dummy-org-id", s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving org: fetching org: parsing id: invalid request", err.Error())
s.Require().Equal("error saving org: error fetching org: error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestUpdateOrganizationDBEncryptErr() {
@ -479,7 +479,7 @@ func (s *OrgTestSuite) TestUpdateOrganizationDBEncryptErr() {
_, err := s.StoreSQLMocked.UpdateOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving org: saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error saving org: saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
@ -507,7 +507,7 @@ func (s *OrgTestSuite) TestUpdateOrganizationDBSaveErr() {
_, err := s.StoreSQLMocked.UpdateOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving org: saving org: saving org mock error", err.Error())
s.Require().Equal("error saving org: error saving org: saving org mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -535,7 +535,7 @@ func (s *OrgTestSuite) TestUpdateOrganizationDBDecryptingErr() {
_, err := s.StoreSQLMocked.UpdateOrganization(s.adminCtx, s.Fixtures.Orgs[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving org: saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error saving org: saving org: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
@ -550,7 +550,7 @@ func (s *OrgTestSuite) TestGetOrganizationByIDInvalidOrgID() {
_, err := s.Store.GetOrganizationByID(s.adminCtx, "dummy-org-id")
s.Require().NotNil(err)
s.Require().Equal("fetching org: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching org: error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestGetOrganizationByIDDBDecryptingErr() {
@ -571,7 +571,7 @@ func (s *OrgTestSuite) TestGetOrganizationByIDDBDecryptingErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching org: missing secret", err.Error())
s.Require().Equal("error fetching org: missing secret", err.Error())
}
func (s *OrgTestSuite) TestCreateOrganizationPool() {
@ -610,7 +610,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolInvalidOrgID() {
_, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
s.Require().Equal("error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestCreateOrganizationPoolDBFetchTagErr() {
@ -628,7 +628,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBFetchTagErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating tag: fetching tag from database: mocked fetching tag error", err.Error())
s.Require().Equal("error creating tag: error fetching tag from database: mocked fetching tag error", err.Error())
s.assertSQLMockExpectations()
}
@ -656,7 +656,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBAddingPoolErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating pool: mocked adding pool error", err.Error())
s.Require().Equal("error creating pool: mocked adding pool error", err.Error())
s.assertSQLMockExpectations()
}
@ -687,7 +687,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBSaveTagErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("associating tags: mocked saving tag error", err.Error())
s.Require().Equal("error associating tags: mocked saving tag error", err.Error())
s.assertSQLMockExpectations()
}
@ -728,7 +728,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolDBFetchPoolErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: not found", err.Error())
s.Require().Equal("error fetching pool by ID: not found", err.Error())
s.assertSQLMockExpectations()
}
@ -758,7 +758,7 @@ func (s *OrgTestSuite) TestListOrgPoolsInvalidOrgID() {
_, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pools: error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestGetOrganizationPool() {
@ -783,7 +783,7 @@ func (s *OrgTestSuite) TestGetOrganizationPoolInvalidOrgID() {
_, err := s.Store.GetEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("fetching pool: error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganizationPool() {
@ -798,7 +798,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationPool() {
s.Require().Nil(err)
_, err = s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
s.Require().Equal("fetching pool: error finding pool: not found", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganizationPoolInvalidOrgID() {
@ -809,7 +809,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationPoolInvalidOrgID() {
err := s.Store.DeleteEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
s.Require().Equal("error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganizationPoolDBDeleteErr() {
@ -831,7 +831,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationPoolDBDeleteErr() {
err = s.StoreSQLMocked.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().NotNil(err)
s.Require().Equal("removing pool: mocked deleting pool error", err.Error())
s.Require().Equal("error removing pool: mocked deleting pool error", err.Error())
s.assertSQLMockExpectations()
}
@ -866,7 +866,7 @@ func (s *OrgTestSuite) TestListOrgInstancesInvalidOrgID() {
_, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching entity: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching entity: error parsing id: invalid request", err.Error())
}
func (s *OrgTestSuite) TestUpdateOrganizationPool() {
@ -916,7 +916,7 @@ func (s *OrgTestSuite) TestUpdateOrganizationPoolInvalidOrgID() {
_, err := s.Store.UpdateEntityPool(s.adminCtx, entity, "dummy-pool-id", s.Fixtures.UpdatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool: error parsing id: invalid request", err.Error())
}
func TestOrgTestSuite(t *testing.T) {

View file

@ -16,10 +16,10 @@ package sql
import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/datatypes"
"gorm.io/gorm"
@ -37,7 +37,7 @@ const (
func (s *sqlDatabase) ListAllPools(_ context.Context) ([]params.Pool, error) {
var pools []Pool
q := s.conn.Model(&Pool{}).
q := s.conn.
Preload("Tags").
Preload("Organization").
Preload("Organization.Endpoint").
@ -48,7 +48,7 @@ func (s *sqlDatabase) ListAllPools(_ context.Context) ([]params.Pool, error) {
Omit("extra_specs").
Find(&pools)
if q.Error != nil {
return nil, errors.Wrap(q.Error, "fetching all pools")
return nil, fmt.Errorf("error fetching all pools: %w", q.Error)
}
ret := make([]params.Pool, len(pools))
@ -56,7 +56,7 @@ func (s *sqlDatabase) ListAllPools(_ context.Context) ([]params.Pool, error) {
for idx, val := range pools {
ret[idx], err = s.sqlToCommonPool(val)
if err != nil {
return nil, errors.Wrap(err, "converting pool")
return nil, fmt.Errorf("error converting pool: %w", err)
}
}
return ret, nil
@ -75,7 +75,7 @@ func (s *sqlDatabase) GetPoolByID(_ context.Context, poolID string) (params.Pool
}
pool, err := s.getPoolByID(s.conn, poolID, preloadList...)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool by ID")
return params.Pool{}, fmt.Errorf("error fetching pool by ID: %w", err)
}
return s.sqlToCommonPool(pool)
}
@ -83,7 +83,7 @@ func (s *sqlDatabase) GetPoolByID(_ context.Context, poolID string) (params.Pool
func (s *sqlDatabase) DeletePoolByID(_ context.Context, poolID string) (err error) {
pool, err := s.getPoolByID(s.conn, poolID)
if err != nil {
return errors.Wrap(err, "fetching pool by ID")
return fmt.Errorf("error fetching pool by ID: %w", err)
}
defer func() {
@ -93,7 +93,7 @@ func (s *sqlDatabase) DeletePoolByID(_ context.Context, poolID string) (err erro
}()
if q := s.conn.Unscoped().Delete(&pool); q.Error != nil {
return errors.Wrap(q.Error, "removing pool")
return fmt.Errorf("error removing pool: %w", q.Error)
}
return nil
@ -101,12 +101,12 @@ func (s *sqlDatabase) DeletePoolByID(_ context.Context, poolID string) (err erro
func (s *sqlDatabase) getEntityPool(tx *gorm.DB, entityType params.ForgeEntityType, entityID, poolID string, preload ...string) (Pool, error) {
if entityID == "" {
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing entity id")
return Pool{}, fmt.Errorf("error missing entity id: %w", runnerErrors.ErrBadRequest)
}
u, err := uuid.Parse(poolID)
if err != nil {
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return Pool{}, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
var fieldName string
@ -140,9 +140,9 @@ func (s *sqlDatabase) getEntityPool(tx *gorm.DB, entityType params.ForgeEntityTy
First(&pool).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return Pool{}, errors.Wrap(runnerErrors.ErrNotFound, "finding pool")
return Pool{}, fmt.Errorf("error finding pool: %w", runnerErrors.ErrNotFound)
}
return Pool{}, errors.Wrap(err, "fetching pool")
return Pool{}, fmt.Errorf("error fetching pool: %w", err)
}
return pool, nil
@ -150,11 +150,11 @@ func (s *sqlDatabase) getEntityPool(tx *gorm.DB, entityType params.ForgeEntityTy
func (s *sqlDatabase) listEntityPools(tx *gorm.DB, entityType params.ForgeEntityType, entityID string, preload ...string) ([]Pool, error) {
if _, err := uuid.Parse(entityID); err != nil {
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return nil, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
if err := s.hasGithubEntity(tx, entityType, entityID); err != nil {
return nil, errors.Wrap(err, "checking entity existence")
return nil, fmt.Errorf("error checking entity existence: %w", err)
}
var preloadEntity string
@ -191,7 +191,7 @@ func (s *sqlDatabase) listEntityPools(tx *gorm.DB, entityType params.ForgeEntity
if errors.Is(err, gorm.ErrRecordNotFound) {
return []Pool{}, nil
}
return nil, errors.Wrap(err, "fetching pool")
return nil, fmt.Errorf("error fetching pool: %w", err)
}
return pools, nil
@ -203,7 +203,7 @@ func (s *sqlDatabase) findPoolByTags(id string, poolType params.ForgeEntityType,
}
u, err := uuid.Parse(id)
if err != nil {
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return nil, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
var fieldName string
@ -233,7 +233,7 @@ func (s *sqlDatabase) findPoolByTags(id string, poolType params.ForgeEntityType,
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return nil, runnerErrors.ErrNotFound
}
return nil, errors.Wrap(q.Error, "fetching pool")
return nil, fmt.Errorf("error fetching pool: %w", q.Error)
}
if len(pools) == 0 {
@ -244,7 +244,7 @@ func (s *sqlDatabase) findPoolByTags(id string, poolType params.ForgeEntityType,
for idx, val := range pools {
ret[idx], err = s.sqlToCommonPool(val)
if err != nil {
return nil, errors.Wrap(err, "converting pool")
return nil, fmt.Errorf("error converting pool: %w", err)
}
}
@ -261,13 +261,13 @@ func (s *sqlDatabase) FindPoolsMatchingAllTags(_ context.Context, entityType par
if errors.Is(err, runnerErrors.ErrNotFound) {
return []params.Pool{}, nil
}
return nil, errors.Wrap(err, "fetching pools")
return nil, fmt.Errorf("error fetching pools: %w", err)
}
return pools, nil
}
func (s *sqlDatabase) CreateEntityPool(_ context.Context, entity params.ForgeEntity, param params.CreatePoolParams) (pool params.Pool, err error) {
func (s *sqlDatabase) CreateEntityPool(ctx context.Context, entity params.ForgeEntity, param params.CreatePoolParams) (pool params.Pool, err error) {
if len(param.Tags) == 0 {
return params.Pool{}, runnerErrors.NewBadRequestError("no tags specified")
}
@ -298,7 +298,7 @@ func (s *sqlDatabase) CreateEntityPool(_ context.Context, entity params.ForgeEnt
entityID, err := uuid.Parse(entity.ID)
if err != nil {
return params.Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return params.Pool{}, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
switch entity.EntityType {
@ -311,26 +311,26 @@ func (s *sqlDatabase) CreateEntityPool(_ context.Context, entity params.ForgeEnt
}
err = s.conn.Transaction(func(tx *gorm.DB) error {
if err := s.hasGithubEntity(tx, entity.EntityType, entity.ID); err != nil {
return errors.Wrap(err, "checking entity existence")
return fmt.Errorf("error checking entity existence: %w", err)
}
tags := []Tag{}
for _, val := range param.Tags {
t, err := s.getOrCreateTag(tx, val)
if err != nil {
return errors.Wrap(err, "creating tag")
return fmt.Errorf("error creating tag: %w", err)
}
tags = append(tags, t)
}
q := tx.Create(&newPool)
if q.Error != nil {
return errors.Wrap(q.Error, "creating pool")
return fmt.Errorf("error creating pool: %w", q.Error)
}
for i := range tags {
if err := tx.Model(&newPool).Association("Tags").Append(&tags[i]); err != nil {
return errors.Wrap(err, "associating tags")
return fmt.Errorf("error associating tags: %w", err)
}
}
return nil
@ -339,16 +339,21 @@ func (s *sqlDatabase) CreateEntityPool(_ context.Context, entity params.ForgeEnt
return params.Pool{}, err
}
dbPool, err := s.getPoolByID(s.conn, newPool.ID.String(), "Tags", "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
}
return s.sqlToCommonPool(dbPool)
return s.GetPoolByID(ctx, newPool.ID.String())
}
func (s *sqlDatabase) GetEntityPool(_ context.Context, entity params.ForgeEntity, poolID string) (params.Pool, error) {
pool, err := s.getEntityPool(s.conn, entity.EntityType, entity.ID, poolID, "Tags", "Instances")
preloadList := []string{
"Tags",
"Instances",
"Enterprise",
"Enterprise.Endpoint",
"Organization",
"Organization.Endpoint",
"Repository",
"Repository.Endpoint",
}
pool, err := s.getEntityPool(s.conn, entity.EntityType, entity.ID, poolID, preloadList...)
if err != nil {
return params.Pool{}, fmt.Errorf("fetching pool: %w", err)
}
@ -358,7 +363,7 @@ func (s *sqlDatabase) GetEntityPool(_ context.Context, entity params.ForgeEntity
func (s *sqlDatabase) DeleteEntityPool(_ context.Context, entity params.ForgeEntity, poolID string) (err error) {
entityID, err := uuid.Parse(entity.ID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
defer func() {
@ -372,7 +377,7 @@ func (s *sqlDatabase) DeleteEntityPool(_ context.Context, entity params.ForgeEnt
poolUUID, err := uuid.Parse(poolID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing pool id")
return fmt.Errorf("error parsing pool id: %w", runnerErrors.ErrBadRequest)
}
var fieldName string
switch entity.EntityType {
@ -387,12 +392,12 @@ func (s *sqlDatabase) DeleteEntityPool(_ context.Context, entity params.ForgeEnt
}
condition := fmt.Sprintf("id = ? and %s = ?", fieldName)
if err := s.conn.Unscoped().Where(condition, poolUUID, entityID).Delete(&Pool{}).Error; err != nil {
return errors.Wrap(err, "removing pool")
return fmt.Errorf("error removing pool: %w", err)
}
return nil
}
func (s *sqlDatabase) UpdateEntityPool(_ context.Context, entity params.ForgeEntity, poolID string, param params.UpdatePoolParams) (updatedPool params.Pool, err error) {
func (s *sqlDatabase) UpdateEntityPool(ctx context.Context, entity params.ForgeEntity, poolID string, param params.UpdatePoolParams) (updatedPool params.Pool, err error) {
defer func() {
if err == nil {
s.sendNotify(common.PoolEntityType, common.UpdateOperation, updatedPool)
@ -401,32 +406,37 @@ func (s *sqlDatabase) UpdateEntityPool(_ context.Context, entity params.ForgeEnt
err = s.conn.Transaction(func(tx *gorm.DB) error {
pool, err := s.getEntityPool(tx, entity.EntityType, entity.ID, poolID, "Tags", "Instances")
if err != nil {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
updatedPool, err = s.updatePool(tx, pool, param)
if err != nil {
return errors.Wrap(err, "updating pool")
return fmt.Errorf("error updating pool: %w", err)
}
return nil
})
if err != nil {
return params.Pool{}, err
}
updatedPool, err = s.GetPoolByID(ctx, poolID)
if err != nil {
return params.Pool{}, err
}
return updatedPool, nil
}
func (s *sqlDatabase) ListEntityPools(_ context.Context, entity params.ForgeEntity) ([]params.Pool, error) {
pools, err := s.listEntityPools(s.conn, entity.EntityType, entity.ID, "Tags")
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
return nil, fmt.Errorf("error fetching pools: %w", err)
}
ret := make([]params.Pool, len(pools))
for idx, pool := range pools {
ret[idx], err = s.sqlToCommonPool(pool)
if err != nil {
return nil, errors.Wrap(err, "fetching pool")
return nil, fmt.Errorf("error fetching pool: %w", err)
}
}
@ -436,7 +446,7 @@ func (s *sqlDatabase) ListEntityPools(_ context.Context, entity params.ForgeEnti
func (s *sqlDatabase) ListEntityInstances(_ context.Context, entity params.ForgeEntity) ([]params.Instance, error) {
pools, err := s.listEntityPools(s.conn, entity.EntityType, entity.ID, "Instances", "Instances.Job")
if err != nil {
return nil, errors.Wrap(err, "fetching entity")
return nil, fmt.Errorf("error fetching entity: %w", err)
}
ret := []params.Instance{}
for _, pool := range pools {
@ -446,7 +456,7 @@ func (s *sqlDatabase) ListEntityInstances(_ context.Context, entity params.Forge
instance.Pool = pool
paramsInstance, err := s.sqlToParamsInstance(instance)
if err != nil {
return nil, errors.Wrap(err, "fetching instance")
return nil, fmt.Errorf("error fetching instance: %w", err)
}
ret = append(ret, paramsInstance)
}

View file

@ -157,7 +157,7 @@ func (s *PoolsTestSuite) TestListAllPoolsDBFetchErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("fetching all pools: mocked fetching all pools error", err.Error())
s.Require().Equal("error fetching all pools: mocked fetching all pools error", err.Error())
}
func (s *PoolsTestSuite) TestGetPoolByID() {
@ -171,7 +171,7 @@ func (s *PoolsTestSuite) TestGetPoolByIDInvalidPoolID() {
_, err := s.Store.GetPoolByID(s.adminCtx, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool by ID: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool by ID: error parsing id: invalid request", err.Error())
}
func (s *PoolsTestSuite) TestDeletePoolByID() {
@ -179,14 +179,14 @@ func (s *PoolsTestSuite) TestDeletePoolByID() {
s.Require().Nil(err)
_, err = s.Store.GetPoolByID(s.adminCtx, s.Fixtures.Pools[0].ID)
s.Require().Equal("fetching pool by ID: not found", err.Error())
s.Require().Equal("error fetching pool by ID: not found", err.Error())
}
func (s *PoolsTestSuite) TestDeletePoolByIDInvalidPoolID() {
err := s.Store.DeletePoolByID(s.adminCtx, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool by ID: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool by ID: error parsing id: invalid request", err.Error())
}
func (s *PoolsTestSuite) TestDeletePoolByIDDBRemoveErr() {
@ -204,7 +204,7 @@ func (s *PoolsTestSuite) TestDeletePoolByIDDBRemoveErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("removing pool: mocked removing pool error", err.Error())
s.Require().Equal("error removing pool: mocked removing pool error", err.Error())
}
func (s *PoolsTestSuite) TestEntityPoolOperations() {

View file

@ -16,11 +16,11 @@ package sql
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -57,23 +57,23 @@ func (s *sqlDatabase) CreateRepository(ctx context.Context, owner, name string,
case params.GiteaEndpointType:
newRepo.GiteaCredentialsID = &credentials.ID
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "unsupported credentials type")
return runnerErrors.NewBadRequestError("unsupported credentials type")
}
newRepo.EndpointName = &credentials.Endpoint.Name
q := tx.Create(&newRepo)
if q.Error != nil {
return errors.Wrap(q.Error, "creating repository")
return fmt.Errorf("error creating repository: %w", q.Error)
}
return nil
})
if err != nil {
return params.Repository{}, errors.Wrap(err, "creating repository")
return params.Repository{}, fmt.Errorf("error creating repository: %w", err)
}
ret, err := s.GetRepositoryByID(ctx, newRepo.ID.String())
if err != nil {
return params.Repository{}, errors.Wrap(err, "creating repository")
return params.Repository{}, fmt.Errorf("error creating repository: %w", err)
}
return ret, nil
@ -82,12 +82,12 @@ func (s *sqlDatabase) CreateRepository(ctx context.Context, owner, name string,
func (s *sqlDatabase) GetRepository(ctx context.Context, owner, name, endpointName string) (params.Repository, error) {
repo, err := s.getRepo(ctx, owner, name, endpointName)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
return params.Repository{}, fmt.Errorf("error fetching repo: %w", err)
}
param, err := s.sqlToCommonRepository(repo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
return params.Repository{}, fmt.Errorf("error fetching repo: %w", err)
}
return param, nil
@ -112,7 +112,7 @@ func (s *sqlDatabase) ListRepositories(_ context.Context, filter params.Reposito
}
q = q.Find(&repos)
if q.Error != nil {
return []params.Repository{}, errors.Wrap(q.Error, "fetching user from database")
return []params.Repository{}, fmt.Errorf("error fetching user from database: %w", q.Error)
}
ret := make([]params.Repository, len(repos))
@ -120,7 +120,7 @@ func (s *sqlDatabase) ListRepositories(_ context.Context, filter params.Reposito
var err error
ret[idx], err = s.sqlToCommonRepository(val, true)
if err != nil {
return nil, errors.Wrap(err, "fetching repositories")
return nil, fmt.Errorf("error fetching repositories: %w", err)
}
}
@ -130,7 +130,7 @@ func (s *sqlDatabase) ListRepositories(_ context.Context, filter params.Reposito
func (s *sqlDatabase) DeleteRepository(ctx context.Context, repoID string) (err error) {
repo, err := s.getRepoByID(ctx, s.conn, repoID, "Endpoint", "Credentials", "Credentials.Endpoint", "GiteaCredentials", "GiteaCredentials.Endpoint")
if err != nil {
return errors.Wrap(err, "fetching repo")
return fmt.Errorf("error fetching repo: %w", err)
}
defer func(repo Repository) {
@ -146,7 +146,7 @@ func (s *sqlDatabase) DeleteRepository(ctx context.Context, repoID string) (err
q := s.conn.Unscoped().Delete(&repo)
if q.Error != nil && !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return errors.Wrap(q.Error, "deleting repo")
return fmt.Errorf("error deleting repo: %w", q.Error)
}
return nil
@ -164,23 +164,23 @@ func (s *sqlDatabase) UpdateRepository(ctx context.Context, repoID string, param
var err error
repo, err = s.getRepoByID(ctx, tx, repoID)
if err != nil {
return errors.Wrap(err, "fetching repo")
return fmt.Errorf("error fetching repo: %w", err)
}
if repo.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "repository has no endpoint")
return runnerErrors.NewUnprocessableError("repository has no endpoint")
}
if param.CredentialsName != "" {
creds, err = s.getGithubCredentialsByName(ctx, tx, param.CredentialsName, false)
if err != nil {
return errors.Wrap(err, "fetching credentials")
return fmt.Errorf("error fetching credentials: %w", err)
}
if creds.EndpointName == nil {
return errors.Wrap(runnerErrors.ErrUnprocessable, "credentials have no endpoint")
return runnerErrors.NewUnprocessableError("credentials have no endpoint")
}
if *creds.EndpointName != *repo.EndpointName {
return errors.Wrap(runnerErrors.ErrBadRequest, "endpoint mismatch")
return runnerErrors.NewBadRequestError("endpoint mismatch")
}
repo.CredentialsID = &creds.ID
}
@ -199,23 +199,23 @@ func (s *sqlDatabase) UpdateRepository(ctx context.Context, repoID string, param
q := tx.Save(&repo)
if q.Error != nil {
return errors.Wrap(q.Error, "saving repo")
return fmt.Errorf("error saving repo: %w", q.Error)
}
return nil
})
if err != nil {
return params.Repository{}, errors.Wrap(err, "saving repo")
return params.Repository{}, fmt.Errorf("error saving repo: %w", err)
}
repo, err = s.getRepoByID(ctx, s.conn, repoID, "Endpoint", "Credentials", "Credentials.Endpoint", "GiteaCredentials", "GiteaCredentials.Endpoint")
if err != nil {
return params.Repository{}, errors.Wrap(err, "updating enterprise")
return params.Repository{}, fmt.Errorf("error updating enterprise: %w", err)
}
newParams, err = s.sqlToCommonRepository(repo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "saving repo")
return params.Repository{}, fmt.Errorf("error saving repo: %w", err)
}
return newParams, nil
}
@ -232,12 +232,12 @@ func (s *sqlDatabase) GetRepositoryByID(ctx context.Context, repoID string) (par
}
repo, err := s.getRepoByID(ctx, s.conn, repoID, preloadList...)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
return params.Repository{}, fmt.Errorf("error fetching repo: %w", err)
}
param, err := s.sqlToCommonRepository(repo, true)
if err != nil {
return params.Repository{}, errors.Wrap(err, "fetching repo")
return params.Repository{}, fmt.Errorf("error fetching repo: %w", err)
}
return param, nil
}
@ -259,7 +259,7 @@ func (s *sqlDatabase) getRepo(_ context.Context, owner, name, endpointName strin
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Repository{}, runnerErrors.ErrNotFound
}
return Repository{}, errors.Wrap(q.Error, "fetching repository from database")
return Repository{}, fmt.Errorf("error fetching repository from database: %w", q.Error)
}
return repo, nil
}
@ -267,7 +267,7 @@ func (s *sqlDatabase) getRepo(_ context.Context, owner, name, endpointName strin
func (s *sqlDatabase) getRepoByID(_ context.Context, tx *gorm.DB, id string, preload ...string) (Repository, error) {
u, err := uuid.Parse(id)
if err != nil {
return Repository{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return Repository{}, runnerErrors.NewBadRequestError("error parsing id: %s", err)
}
var repo Repository
@ -283,7 +283,7 @@ func (s *sqlDatabase) getRepoByID(_ context.Context, tx *gorm.DB, id string, pre
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Repository{}, runnerErrors.ErrNotFound
}
return Repository{}, errors.Wrap(q.Error, "fetching repository from database")
return Repository{}, fmt.Errorf("error fetching repository from database: %w", q.Error)
}
return repo, nil
}

View file

@ -284,7 +284,7 @@ func (s *RepoTestSuite) TestCreateRepositoryInvalidForgeType() {
)
s.Require().NotNil(err)
s.Require().Equal("creating repository: unsupported credentials type: invalid request", err.Error())
s.Require().Equal("error creating repository: unsupported credentials type", err.Error())
}
func (s *RepoTestSuite) TestCreateRepositoryInvalidDBPassphrase() {
@ -330,7 +330,7 @@ func (s *RepoTestSuite) TestCreateRepositoryInvalidDBCreateErr() {
)
s.Require().NotNil(err)
s.Require().Equal("creating repository: creating repository: creating repo mock error", err.Error())
s.Require().Equal("error creating repository: error creating repository: creating repo mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -355,7 +355,7 @@ func (s *RepoTestSuite) TestGetRepositoryNotFound() {
_, err := s.Store.GetRepository(s.adminCtx, "dummy-owner", "dummy-name", "github.com")
s.Require().NotNil(err)
s.Require().Equal("fetching repo: not found", err.Error())
s.Require().Equal("error fetching repo: not found", err.Error())
}
func (s *RepoTestSuite) TestGetRepositoryDBDecryptingErr() {
@ -371,7 +371,7 @@ func (s *RepoTestSuite) TestGetRepositoryDBDecryptingErr() {
_, err := s.StoreSQLMocked.GetRepository(s.adminCtx, s.Fixtures.Repos[0].Owner, s.Fixtures.Repos[0].Name, s.Fixtures.Repos[0].Endpoint.Name)
s.Require().NotNil(err)
s.Require().Equal("fetching repo: missing secret", err.Error())
s.Require().Equal("error fetching repo: missing secret", err.Error())
s.assertSQLMockExpectations()
}
@ -471,7 +471,7 @@ func (s *RepoTestSuite) TestListRepositoriesDBFetchErr() {
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx, params.RepositoryFilter{})
s.Require().NotNil(err)
s.Require().Equal("fetching user from database: fetching user from database mock error", err.Error())
s.Require().Equal("error fetching user from database: fetching user from database mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -485,7 +485,7 @@ func (s *RepoTestSuite) TestListRepositoriesDBDecryptingErr() {
_, err := s.StoreSQLMocked.ListRepositories(s.adminCtx, params.RepositoryFilter{})
s.Require().NotNil(err)
s.Require().Equal("fetching repositories: decrypting secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error fetching repositories: error decrypting secret: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
@ -495,14 +495,14 @@ func (s *RepoTestSuite) TestDeleteRepository() {
s.Require().Nil(err)
_, err = s.Store.GetRepositoryByID(s.adminCtx, s.Fixtures.Repos[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching repo: not found", err.Error())
s.Require().Equal("error fetching repo: not found", err.Error())
}
func (s *RepoTestSuite) TestDeleteRepositoryInvalidRepoID() {
err := s.Store.DeleteRepository(s.adminCtx, "dummy-repo-id")
s.Require().NotNil(err)
s.Require().Equal("fetching repo: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching repo: error parsing id: invalid UUID length: 13", err.Error())
}
func (s *RepoTestSuite) TestDeleteRepositoryDBRemoveErr() {
@ -520,7 +520,7 @@ func (s *RepoTestSuite) TestDeleteRepositoryDBRemoveErr() {
err := s.StoreSQLMocked.DeleteRepository(s.adminCtx, s.Fixtures.Repos[0].ID)
s.Require().NotNil(err)
s.Require().Equal("deleting repo: mocked deleting repo error", err.Error())
s.Require().Equal("error deleting repo: mocked deleting repo error", err.Error())
s.assertSQLMockExpectations()
}
@ -536,7 +536,7 @@ func (s *RepoTestSuite) TestUpdateRepositoryInvalidRepoID() {
_, err := s.Store.UpdateRepository(s.adminCtx, "dummy-repo-id", s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving repo: fetching repo: parsing id: invalid request", err.Error())
s.Require().Equal("error saving repo: error fetching repo: error parsing id: invalid UUID length: 13", err.Error())
}
func (s *RepoTestSuite) TestUpdateRepositoryDBEncryptErr() {
@ -561,7 +561,7 @@ func (s *RepoTestSuite) TestUpdateRepositoryDBEncryptErr() {
_, err := s.StoreSQLMocked.UpdateRepository(s.adminCtx, s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving repo: saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error saving repo: saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
@ -589,7 +589,7 @@ func (s *RepoTestSuite) TestUpdateRepositoryDBSaveErr() {
_, err := s.StoreSQLMocked.UpdateRepository(s.adminCtx, s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving repo: saving repo: saving repo mock error", err.Error())
s.Require().Equal("error saving repo: error saving repo: saving repo mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -616,7 +616,7 @@ func (s *RepoTestSuite) TestUpdateRepositoryDBDecryptingErr() {
_, err := s.StoreSQLMocked.UpdateRepository(s.adminCtx, s.Fixtures.Repos[0].ID, s.Fixtures.UpdateRepoParams)
s.Require().NotNil(err)
s.Require().Equal("saving repo: saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.Require().Equal("error saving repo: saving repo: failed to encrypt string: invalid passphrase length (expected length 32 characters)", err.Error())
s.assertSQLMockExpectations()
}
@ -631,7 +631,7 @@ func (s *RepoTestSuite) TestGetRepositoryByIDInvalidRepoID() {
_, err := s.Store.GetRepositoryByID(s.adminCtx, "dummy-repo-id")
s.Require().NotNil(err)
s.Require().Equal("fetching repo: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching repo: error parsing id: invalid UUID length: 13", err.Error())
}
func (s *RepoTestSuite) TestGetRepositoryByIDDBDecryptingErr() {
@ -651,7 +651,7 @@ func (s *RepoTestSuite) TestGetRepositoryByIDDBDecryptingErr() {
_, err := s.StoreSQLMocked.GetRepositoryByID(s.adminCtx, s.Fixtures.Repos[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching repo: missing secret", err.Error())
s.Require().Equal("error fetching repo: missing secret", err.Error())
s.assertSQLMockExpectations()
}
@ -690,7 +690,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolInvalidRepoID() {
_, err := s.Store.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
s.Require().Equal("error parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestCreateRepositoryPoolDBFetchTagErr() {
@ -709,7 +709,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBFetchTagErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating tag: fetching tag from database: mocked fetching tag error", err.Error())
s.Require().Equal("error creating tag: error fetching tag from database: mocked fetching tag error", err.Error())
s.assertSQLMockExpectations()
}
@ -738,7 +738,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBAddingPoolErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("creating pool: mocked adding pool error", err.Error())
s.Require().Equal("error creating pool: mocked adding pool error", err.Error())
s.assertSQLMockExpectations()
}
@ -769,7 +769,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBSaveTagErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("associating tags: mocked saving tag error", err.Error())
s.Require().Equal("error associating tags: mocked saving tag error", err.Error())
s.assertSQLMockExpectations()
}
@ -810,7 +810,7 @@ func (s *RepoTestSuite) TestCreateRepositoryPoolDBFetchPoolErr() {
_, err = s.StoreSQLMocked.CreateEntityPool(s.adminCtx, entity, s.Fixtures.CreatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: not found", err.Error())
s.Require().Equal("error fetching pool by ID: not found", err.Error())
s.assertSQLMockExpectations()
}
@ -841,7 +841,7 @@ func (s *RepoTestSuite) TestListRepoPoolsInvalidRepoID() {
_, err := s.Store.ListEntityPools(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching pools: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pools: error parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestGetRepositoryPool() {
@ -866,7 +866,7 @@ func (s *RepoTestSuite) TestGetRepositoryPoolInvalidRepoID() {
_, err := s.Store.GetEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("fetching pool: error parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestDeleteRepositoryPool() {
@ -881,7 +881,7 @@ func (s *RepoTestSuite) TestDeleteRepositoryPool() {
s.Require().Nil(err)
_, err = s.Store.GetEntityPool(s.adminCtx, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
s.Require().Equal("fetching pool: error finding pool: not found", err.Error())
}
func (s *RepoTestSuite) TestDeleteRepositoryPoolInvalidRepoID() {
@ -892,7 +892,7 @@ func (s *RepoTestSuite) TestDeleteRepositoryPoolInvalidRepoID() {
err := s.Store.DeleteEntityPool(s.adminCtx, entity, "dummy-pool-id")
s.Require().NotNil(err)
s.Require().Equal("parsing id: invalid request", err.Error())
s.Require().Equal("error parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestDeleteRepositoryPoolDBDeleteErr() {
@ -913,7 +913,7 @@ func (s *RepoTestSuite) TestDeleteRepositoryPoolDBDeleteErr() {
err = s.StoreSQLMocked.DeleteEntityPool(s.adminCtx, entity, pool.ID)
s.Require().NotNil(err)
s.Require().Equal("removing pool: mocked deleting pool error", err.Error())
s.Require().Equal("error removing pool: mocked deleting pool error", err.Error())
s.assertSQLMockExpectations()
}
@ -948,7 +948,7 @@ func (s *RepoTestSuite) TestListRepoInstancesInvalidRepoID() {
_, err := s.Store.ListEntityInstances(s.adminCtx, entity)
s.Require().NotNil(err)
s.Require().Equal("fetching entity: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching entity: error parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestUpdateRepositoryPool() {
@ -976,7 +976,7 @@ func (s *RepoTestSuite) TestUpdateRepositoryPoolInvalidRepoID() {
_, err := s.Store.UpdateEntityPool(s.adminCtx, entity, "dummy-repo-id", s.Fixtures.UpdatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool: error parsing id: invalid request", err.Error())
}
func (s *RepoTestSuite) TestAddRepoEntityEvent() {

View file

@ -16,8 +16,7 @@ package sql
import (
"context"
"github.com/pkg/errors"
"fmt"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
@ -26,7 +25,7 @@ import (
func (s *sqlDatabase) CreateScaleSetInstance(_ context.Context, scaleSetID uint, param params.CreateInstanceParams) (instance params.Instance, err error) {
scaleSet, err := s.getScaleSetByID(s.conn, scaleSetID)
if err != nil {
return params.Instance{}, errors.Wrap(err, "fetching scale set")
return params.Instance{}, fmt.Errorf("error fetching scale set: %w", err)
}
defer func() {
@ -39,7 +38,7 @@ func (s *sqlDatabase) CreateScaleSetInstance(_ context.Context, scaleSetID uint,
if len(param.JitConfiguration) > 0 {
secret, err = s.marshalAndSeal(param.JitConfiguration)
if err != nil {
return params.Instance{}, errors.Wrap(err, "marshalling jit config")
return params.Instance{}, fmt.Errorf("error marshalling jit config: %w", err)
}
}
@ -58,7 +57,7 @@ func (s *sqlDatabase) CreateScaleSetInstance(_ context.Context, scaleSetID uint,
}
q := s.conn.Create(&newInstance)
if q.Error != nil {
return params.Instance{}, errors.Wrap(q.Error, "creating instance")
return params.Instance{}, fmt.Errorf("error creating instance: %w", q.Error)
}
return s.sqlToParamsInstance(newInstance)
@ -66,10 +65,13 @@ func (s *sqlDatabase) CreateScaleSetInstance(_ context.Context, scaleSetID uint,
func (s *sqlDatabase) ListScaleSetInstances(_ context.Context, scalesetID uint) ([]params.Instance, error) {
var instances []Instance
query := s.conn.Model(&Instance{}).Preload("Job").Where("scale_set_fk_id = ?", scalesetID)
query := s.conn.
Preload("ScaleSet").
Preload("Job").
Where("scale_set_fk_id = ?", scalesetID)
if err := query.Find(&instances); err.Error != nil {
return nil, errors.Wrap(err.Error, "fetching instances")
return nil, fmt.Errorf("error fetching instances: %w", err.Error)
}
var err error
@ -77,7 +79,7 @@ func (s *sqlDatabase) ListScaleSetInstances(_ context.Context, scalesetID uint)
for idx, inst := range instances {
ret[idx], err = s.sqlToParamsInstance(inst)
if err != nil {
return nil, errors.Wrap(err, "converting instance")
return nil, fmt.Errorf("error converting instance: %w", err)
}
}
return ret, nil

View file

@ -16,10 +16,10 @@ package sql
import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/datatypes"
"gorm.io/gorm"
@ -33,13 +33,16 @@ func (s *sqlDatabase) ListAllScaleSets(_ context.Context) ([]params.ScaleSet, er
q := s.conn.Model(&ScaleSet{}).
Preload("Organization").
Preload("Organization.Endpoint").
Preload("Repository").
Preload("Repository.Endpoint").
Preload("Enterprise").
Preload("Enterprise.Endpoint").
Omit("extra_specs").
Omit("status_messages").
Find(&scaleSets)
if q.Error != nil {
return nil, errors.Wrap(q.Error, "fetching all scale sets")
return nil, fmt.Errorf("error fetching all scale sets: %w", q.Error)
}
ret := make([]params.ScaleSet, len(scaleSets))
@ -47,7 +50,7 @@ func (s *sqlDatabase) ListAllScaleSets(_ context.Context) ([]params.ScaleSet, er
for idx, val := range scaleSets {
ret[idx], err = s.sqlToCommonScaleSet(val)
if err != nil {
return nil, errors.Wrap(err, "converting scale sets")
return nil, fmt.Errorf("error converting scale sets: %w", err)
}
}
return ret, nil
@ -88,7 +91,7 @@ func (s *sqlDatabase) CreateEntityScaleSet(_ context.Context, entity params.Forg
entityID, err := uuid.Parse(entity.ID)
if err != nil {
return params.ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return params.ScaleSet{}, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
switch entity.EntityType {
@ -101,12 +104,12 @@ func (s *sqlDatabase) CreateEntityScaleSet(_ context.Context, entity params.Forg
}
err = s.conn.Transaction(func(tx *gorm.DB) error {
if err := s.hasGithubEntity(tx, entity.EntityType, entity.ID); err != nil {
return errors.Wrap(err, "checking entity existence")
return fmt.Errorf("error checking entity existence: %w", err)
}
q := tx.Create(&newScaleSet)
if q.Error != nil {
return errors.Wrap(q.Error, "creating scale set")
return fmt.Errorf("error creating scale set: %w", q.Error)
}
return nil
@ -117,7 +120,7 @@ func (s *sqlDatabase) CreateEntityScaleSet(_ context.Context, entity params.Forg
dbScaleSet, err := s.getScaleSetByID(s.conn, newScaleSet.ID, "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return params.ScaleSet{}, errors.Wrap(err, "fetching scale set")
return params.ScaleSet{}, fmt.Errorf("error fetching scale set: %w", err)
}
return s.sqlToCommonScaleSet(dbScaleSet)
@ -125,11 +128,11 @@ func (s *sqlDatabase) CreateEntityScaleSet(_ context.Context, entity params.Forg
func (s *sqlDatabase) listEntityScaleSets(tx *gorm.DB, entityType params.ForgeEntityType, entityID string, preload ...string) ([]ScaleSet, error) {
if _, err := uuid.Parse(entityID); err != nil {
return nil, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return nil, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
if err := s.hasGithubEntity(tx, entityType, entityID); err != nil {
return nil, errors.Wrap(err, "checking entity existence")
return nil, fmt.Errorf("error checking entity existence: %w", err)
}
var preloadEntity string
@ -167,7 +170,7 @@ func (s *sqlDatabase) listEntityScaleSets(tx *gorm.DB, entityType params.ForgeEn
if errors.Is(err, gorm.ErrRecordNotFound) {
return []ScaleSet{}, nil
}
return nil, errors.Wrap(err, "fetching scale sets")
return nil, fmt.Errorf("error fetching scale sets: %w", err)
}
return scaleSets, nil
@ -176,21 +179,21 @@ func (s *sqlDatabase) listEntityScaleSets(tx *gorm.DB, entityType params.ForgeEn
func (s *sqlDatabase) ListEntityScaleSets(_ context.Context, entity params.ForgeEntity) ([]params.ScaleSet, error) {
scaleSets, err := s.listEntityScaleSets(s.conn, entity.EntityType, entity.ID)
if err != nil {
return nil, errors.Wrap(err, "fetching scale sets")
return nil, fmt.Errorf("error fetching scale sets: %w", err)
}
ret := make([]params.ScaleSet, len(scaleSets))
for idx, set := range scaleSets {
ret[idx], err = s.sqlToCommonScaleSet(set)
if err != nil {
return nil, errors.Wrap(err, "conbverting scale set")
return nil, fmt.Errorf("error conbverting scale set: %w", err)
}
}
return ret, nil
}
func (s *sqlDatabase) UpdateEntityScaleSet(_ context.Context, entity params.ForgeEntity, scaleSetID uint, param params.UpdateScaleSetParams, callback func(old, newSet params.ScaleSet) error) (updatedScaleSet params.ScaleSet, err error) {
func (s *sqlDatabase) UpdateEntityScaleSet(ctx context.Context, entity params.ForgeEntity, scaleSetID uint, param params.UpdateScaleSetParams, callback func(old, newSet params.ScaleSet) error) (updatedScaleSet params.ScaleSet, err error) {
defer func() {
if err == nil {
s.sendNotify(common.ScaleSetEntityType, common.UpdateOperation, updatedScaleSet)
@ -199,22 +202,22 @@ func (s *sqlDatabase) UpdateEntityScaleSet(_ context.Context, entity params.Forg
err = s.conn.Transaction(func(tx *gorm.DB) error {
scaleSet, err := s.getEntityScaleSet(tx, entity.EntityType, entity.ID, scaleSetID, "Instances")
if err != nil {
return errors.Wrap(err, "fetching scale set")
return fmt.Errorf("error fetching scale set: %w", err)
}
old, err := s.sqlToCommonScaleSet(scaleSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
return fmt.Errorf("error converting scale set: %w", err)
}
updatedScaleSet, err = s.updateScaleSet(tx, scaleSet, param)
if err != nil {
return errors.Wrap(err, "updating scale set")
return fmt.Errorf("error updating scale set: %w", err)
}
if callback != nil {
if err := callback(old, updatedScaleSet); err != nil {
return errors.Wrap(err, "executing update callback")
return fmt.Errorf("error executing update callback: %w", err)
}
}
return nil
@ -222,16 +225,21 @@ func (s *sqlDatabase) UpdateEntityScaleSet(_ context.Context, entity params.Forg
if err != nil {
return params.ScaleSet{}, err
}
updatedScaleSet, err = s.GetScaleSetByID(ctx, scaleSetID)
if err != nil {
return params.ScaleSet{}, err
}
return updatedScaleSet, nil
}
func (s *sqlDatabase) getEntityScaleSet(tx *gorm.DB, entityType params.ForgeEntityType, entityID string, scaleSetID uint, preload ...string) (ScaleSet, error) {
if entityID == "" {
return ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing entity id")
return ScaleSet{}, fmt.Errorf("error missing entity id: %w", runnerErrors.ErrBadRequest)
}
if scaleSetID == 0 {
return ScaleSet{}, errors.Wrap(runnerErrors.ErrBadRequest, "missing scaleset id")
return ScaleSet{}, fmt.Errorf("error missing scaleset id: %w", runnerErrors.ErrBadRequest)
}
var fieldName string
@ -265,9 +273,9 @@ func (s *sqlDatabase) getEntityScaleSet(tx *gorm.DB, entityType params.ForgeEnti
First(&scaleSet).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ScaleSet{}, errors.Wrap(runnerErrors.ErrNotFound, "finding scale set")
return ScaleSet{}, fmt.Errorf("error finding scale set: %w", runnerErrors.ErrNotFound)
}
return ScaleSet{}, errors.Wrap(err, "fetching scale set")
return ScaleSet{}, fmt.Errorf("error fetching scale set: %w", err)
}
return scaleSet, nil
@ -286,6 +294,10 @@ func (s *sqlDatabase) updateScaleSet(tx *gorm.DB, scaleSet ScaleSet, param param
scaleSet.ExtendedState = *param.ExtendedState
}
if param.ScaleSetID != 0 {
scaleSet.ScaleSetID = param.ScaleSetID
}
if param.Name != "" {
scaleSet.Name = param.Name
}
@ -335,16 +347,26 @@ func (s *sqlDatabase) updateScaleSet(tx *gorm.DB, scaleSet ScaleSet, param param
}
if q := tx.Save(&scaleSet); q.Error != nil {
return params.ScaleSet{}, errors.Wrap(q.Error, "saving database entry")
return params.ScaleSet{}, fmt.Errorf("error saving database entry: %w", q.Error)
}
return s.sqlToCommonScaleSet(scaleSet)
}
func (s *sqlDatabase) GetScaleSetByID(_ context.Context, scaleSet uint) (params.ScaleSet, error) {
set, err := s.getScaleSetByID(s.conn, scaleSet, "Instances", "Enterprise", "Organization", "Repository")
set, err := s.getScaleSetByID(
s.conn,
scaleSet,
"Instances",
"Enterprise",
"Enterprise.Endpoint",
"Organization",
"Organization.Endpoint",
"Repository",
"Repository.Endpoint",
)
if err != nil {
return params.ScaleSet{}, errors.Wrap(err, "fetching scale set by ID")
return params.ScaleSet{}, fmt.Errorf("error fetching scale set by ID: %w", err)
}
return s.sqlToCommonScaleSet(set)
}
@ -359,7 +381,7 @@ func (s *sqlDatabase) DeleteScaleSetByID(_ context.Context, scaleSetID uint) (er
err = s.conn.Transaction(func(tx *gorm.DB) error {
dbSet, err := s.getScaleSetByID(tx, scaleSetID, "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return errors.Wrap(err, "fetching scale set")
return fmt.Errorf("error fetching scale set: %w", err)
}
if len(dbSet.Instances) > 0 {
@ -367,16 +389,16 @@ func (s *sqlDatabase) DeleteScaleSetByID(_ context.Context, scaleSetID uint) (er
}
scaleSet, err = s.sqlToCommonScaleSet(dbSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
return fmt.Errorf("error converting scale set: %w", err)
}
if q := tx.Unscoped().Delete(&dbSet); q.Error != nil {
return errors.Wrap(q.Error, "deleting scale set")
return fmt.Errorf("error deleting scale set: %w", q.Error)
}
return nil
})
if err != nil {
return errors.Wrap(err, "removing scale set")
return fmt.Errorf("error removing scale set: %w", err)
}
return nil
}
@ -389,21 +411,21 @@ func (s *sqlDatabase) SetScaleSetLastMessageID(_ context.Context, scaleSetID uin
}
}()
if err := s.conn.Transaction(func(tx *gorm.DB) error {
dbSet, err := s.getScaleSetByID(tx, scaleSetID)
dbSet, err := s.getScaleSetByID(tx, scaleSetID, "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return errors.Wrap(err, "fetching scale set")
return fmt.Errorf("error fetching scale set: %w", err)
}
dbSet.LastMessageID = lastMessageID
if err := tx.Save(&dbSet).Error; err != nil {
return errors.Wrap(err, "saving database entry")
return fmt.Errorf("error saving database entry: %w", err)
}
scaleSet, err = s.sqlToCommonScaleSet(dbSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
return fmt.Errorf("error converting scale set: %w", err)
}
return nil
}); err != nil {
return errors.Wrap(err, "setting last message ID")
return fmt.Errorf("error setting last message ID: %w", err)
}
return nil
}
@ -416,21 +438,21 @@ func (s *sqlDatabase) SetScaleSetDesiredRunnerCount(_ context.Context, scaleSetI
}
}()
if err := s.conn.Transaction(func(tx *gorm.DB) error {
dbSet, err := s.getScaleSetByID(tx, scaleSetID)
dbSet, err := s.getScaleSetByID(tx, scaleSetID, "Instances", "Enterprise", "Organization", "Repository")
if err != nil {
return errors.Wrap(err, "fetching scale set")
return fmt.Errorf("error fetching scale set: %w", err)
}
dbSet.DesiredRunnerCount = desiredRunnerCount
if err := tx.Save(&dbSet).Error; err != nil {
return errors.Wrap(err, "saving database entry")
return fmt.Errorf("error saving database entry: %w", err)
}
scaleSet, err = s.sqlToCommonScaleSet(dbSet)
if err != nil {
return errors.Wrap(err, "converting scale set")
return fmt.Errorf("error converting scale set: %w", err)
}
return nil
}); err != nil {
return errors.Wrap(err, "setting desired runner count")
return fmt.Errorf("error setting desired runner count: %w", err)
}
return nil
}

View file

@ -16,12 +16,12 @@ package sql
import (
"context"
"errors"
"fmt"
"log/slog"
"net/url"
"strings"
"github.com/pkg/errors"
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
@ -46,7 +46,7 @@ const (
func newDBConn(dbCfg config.Database) (conn *gorm.DB, err error) {
dbType, connURI, err := dbCfg.GormParams()
if err != nil {
return nil, errors.Wrap(err, "getting DB URI string")
return nil, fmt.Errorf("error getting DB URI string: %w", err)
}
gormConfig := &gorm.Config{}
@ -61,7 +61,7 @@ func newDBConn(dbCfg config.Database) (conn *gorm.DB, err error) {
conn, err = gorm.Open(sqlite.Open(connURI), gormConfig)
}
if err != nil {
return nil, errors.Wrap(err, "connecting to database")
return nil, fmt.Errorf("error connecting to database: %w", err)
}
if dbCfg.Debug {
@ -73,11 +73,11 @@ func newDBConn(dbCfg config.Database) (conn *gorm.DB, err error) {
func NewSQLDatabase(ctx context.Context, cfg config.Database) (common.Store, error) {
conn, err := newDBConn(cfg)
if err != nil {
return nil, errors.Wrap(err, "creating DB connection")
return nil, fmt.Errorf("error creating DB connection: %w", err)
}
producer, err := watcher.RegisterProducer(ctx, "sql")
if err != nil {
return nil, errors.Wrap(err, "registering producer")
return nil, fmt.Errorf("error registering producer: %w", err)
}
db := &sqlDatabase{
conn: conn,
@ -87,7 +87,7 @@ func NewSQLDatabase(ctx context.Context, cfg config.Database) (common.Store, err
}
if err := db.migrateDB(); err != nil {
return nil, errors.Wrap(err, "migrating database")
return nil, fmt.Errorf("error migrating database: %w", err)
}
return db, nil
}
@ -221,14 +221,14 @@ func (s *sqlDatabase) ensureGithubEndpoint() error {
var epCount int64
if err := s.conn.Model(&GithubEndpoint{}).Count(&epCount).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "counting github endpoints")
return fmt.Errorf("error counting github endpoints: %w", err)
}
}
if epCount == 0 {
if _, err := s.CreateGithubEndpoint(context.Background(), createEndpointParams); err != nil {
if !errors.Is(err, runnerErrors.ErrDuplicateEntity) {
return errors.Wrap(err, "creating default github endpoint")
return fmt.Errorf("error creating default github endpoint: %w", err)
}
}
}
@ -246,7 +246,7 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
// Admin user doesn't exist. This is a new deploy. Nothing to migrate.
return nil
}
return errors.Wrap(err, "getting admin user")
return fmt.Errorf("error getting admin user: %w", err)
}
// Impersonate the admin user. We're migrating from config credentials to
@ -259,7 +259,7 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
slog.Info("migrating credentials to DB")
slog.Info("creating github endpoints table")
if err := s.conn.AutoMigrate(&GithubEndpoint{}); err != nil {
return errors.Wrap(err, "migrating github endpoints")
return fmt.Errorf("error migrating github endpoints: %w", err)
}
defer func() {
@ -271,7 +271,7 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
slog.Info("creating github credentials table")
if err := s.conn.AutoMigrate(&GithubCredentials{}); err != nil {
return errors.Wrap(err, "migrating github credentials")
return fmt.Errorf("error migrating github credentials: %w", err)
}
defer func() {
@ -291,12 +291,12 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
slog.Info("importing credential", "name", cred.Name)
parsed, err := url.Parse(cred.BaseEndpoint())
if err != nil {
return errors.Wrap(err, "parsing base URL")
return fmt.Errorf("error parsing base URL: %w", err)
}
certBundle, err := cred.CACertBundle()
if err != nil {
return errors.Wrap(err, "getting CA cert bundle")
return fmt.Errorf("error getting CA cert bundle: %w", err)
}
hostname := parsed.Hostname()
createParams := params.CreateGithubEndpointParams{
@ -312,11 +312,11 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
endpoint, err = s.GetGithubEndpoint(adminCtx, hostname)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "getting github endpoint")
return fmt.Errorf("error getting github endpoint: %w", err)
}
endpoint, err = s.CreateGithubEndpoint(adminCtx, createParams)
if err != nil {
return errors.Wrap(err, "creating default github endpoint")
return fmt.Errorf("error creating default github endpoint: %w", err)
}
}
@ -330,7 +330,7 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
case params.ForgeAuthTypeApp:
keyBytes, err := cred.App.PrivateKeyBytes()
if err != nil {
return errors.Wrap(err, "getting private key bytes")
return fmt.Errorf("error getting private key bytes: %w", err)
}
credParams.App = params.GithubApp{
AppID: cred.App.AppID,
@ -339,7 +339,7 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
}
if err := credParams.App.Validate(); err != nil {
return errors.Wrap(err, "validating app credentials")
return fmt.Errorf("error validating app credentials: %w", err)
}
case params.ForgeAuthTypePAT:
token := cred.PAT.OAuth2Token
@ -356,19 +356,35 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
creds, err := s.CreateGithubCredentials(adminCtx, credParams)
if err != nil {
return errors.Wrap(err, "creating github credentials")
return fmt.Errorf("error creating github credentials: %w", err)
}
if err := s.conn.Exec("update repositories set credentials_id = ?,endpoint_name = ? where credentials_name = ?", creds.ID, creds.Endpoint.Name, creds.Name).Error; err != nil {
return errors.Wrap(err, "updating repositories")
return fmt.Errorf("error updating repositories: %w", err)
}
if err := s.conn.Exec("update organizations set credentials_id = ?,endpoint_name = ? where credentials_name = ?", creds.ID, creds.Endpoint.Name, creds.Name).Error; err != nil {
return errors.Wrap(err, "updating organizations")
return fmt.Errorf("error updating organizations: %w", err)
}
if err := s.conn.Exec("update enterprises set credentials_id = ?,endpoint_name = ? where credentials_name = ?", creds.ID, creds.Endpoint.Name, creds.Name).Error; err != nil {
return errors.Wrap(err, "updating enterprises")
return fmt.Errorf("error updating enterprises: %w", err)
}
}
return nil
}
func (s *sqlDatabase) migrateWorkflow() error {
if s.conn.Migrator().HasTable(&WorkflowJob{}) {
if s.conn.Migrator().HasColumn(&WorkflowJob{}, "runner_name") {
// Remove jobs that are not in "queued" status. We really only care about queued jobs. Once they transition
// to something else, we don't really consume them anyway.
if err := s.conn.Exec("delete from workflow_jobs where status is not 'queued'").Error; err != nil {
return fmt.Errorf("error updating workflow_jobs: %w", err)
}
if err := s.conn.Migrator().DropColumn(&WorkflowJob{}, "runner_name"); err != nil {
return fmt.Errorf("error updating workflow_jobs: %w", err)
}
}
}
return nil
@ -388,43 +404,34 @@ func (s *sqlDatabase) migrateDB() error {
}
if err := s.cascadeMigration(); err != nil {
return errors.Wrap(err, "running cascade migration")
return fmt.Errorf("error running cascade migration: %w", err)
}
if s.conn.Migrator().HasTable(&Pool{}) {
if err := s.conn.Exec("update pools set repo_id=NULL where repo_id='00000000-0000-0000-0000-000000000000'").Error; err != nil {
return errors.Wrap(err, "updating pools")
return fmt.Errorf("error updating pools %w", err)
}
if err := s.conn.Exec("update pools set org_id=NULL where org_id='00000000-0000-0000-0000-000000000000'").Error; err != nil {
return errors.Wrap(err, "updating pools")
return fmt.Errorf("error updating pools: %w", err)
}
if err := s.conn.Exec("update pools set enterprise_id=NULL where enterprise_id='00000000-0000-0000-0000-000000000000'").Error; err != nil {
return errors.Wrap(err, "updating pools")
return fmt.Errorf("error updating pools: %w", err)
}
}
if s.conn.Migrator().HasTable(&WorkflowJob{}) {
if s.conn.Migrator().HasColumn(&WorkflowJob{}, "runner_name") {
// Remove jobs that are not in "queued" status. We really only care about queued jobs. Once they transition
// to something else, we don't really consume them anyway.
if err := s.conn.Exec("delete from workflow_jobs where status is not 'queued'").Error; err != nil {
return errors.Wrap(err, "updating workflow_jobs")
}
if err := s.conn.Migrator().DropColumn(&WorkflowJob{}, "runner_name"); err != nil {
return errors.Wrap(err, "updating workflow_jobs")
}
}
if err := s.migrateWorkflow(); err != nil {
return fmt.Errorf("error migrating workflows: %w", err)
}
if s.conn.Migrator().HasTable(&GithubEndpoint{}) {
if !s.conn.Migrator().HasColumn(&GithubEndpoint{}, "endpoint_type") {
if err := s.conn.Migrator().AutoMigrate(&GithubEndpoint{}); err != nil {
return errors.Wrap(err, "migrating github endpoints")
return fmt.Errorf("error migrating github endpoints: %w", err)
}
if err := s.conn.Exec("update github_endpoints set endpoint_type = 'github' where endpoint_type is null").Error; err != nil {
return errors.Wrap(err, "updating github endpoints")
return fmt.Errorf("error updating github endpoints: %w", err)
}
}
}
@ -460,7 +467,7 @@ func (s *sqlDatabase) migrateDB() error {
&WorkflowJob{},
&ScaleSet{},
); err != nil {
return errors.Wrap(err, "running auto migrate")
return fmt.Errorf("error running auto migrate: %w", err)
}
s.conn.Exec("PRAGMA foreign_keys = ON")
@ -468,23 +475,23 @@ func (s *sqlDatabase) migrateDB() error {
var controller ControllerInfo
if err := s.conn.First(&controller).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "updating controller info")
return fmt.Errorf("error updating controller info: %w", err)
}
} else {
controller.MinimumJobAgeBackoff = 30
if err := s.conn.Save(&controller).Error; err != nil {
return errors.Wrap(err, "updating controller info")
return fmt.Errorf("error updating controller info: %w", err)
}
}
}
if err := s.ensureGithubEndpoint(); err != nil {
return errors.Wrap(err, "ensuring github endpoint")
return fmt.Errorf("error ensuring github endpoint: %w", err)
}
if needsCredentialMigration {
if err := s.migrateCredentialsToDB(); err != nil {
return errors.Wrap(err, "migrating credentials")
return fmt.Errorf("error migrating credentials: %w", err)
}
}
return nil

View file

@ -16,9 +16,9 @@ package sql
import (
"context"
"errors"
"fmt"
"github.com/pkg/errors"
"gorm.io/gorm"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -39,7 +39,7 @@ func (s *sqlDatabase) getUserByUsernameOrEmail(tx *gorm.DB, user string) (User,
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return User{}, runnerErrors.ErrNotFound
}
return User{}, errors.Wrap(q.Error, "fetching user")
return User{}, fmt.Errorf("error fetching user: %w", q.Error)
}
return dbUser, nil
}
@ -51,7 +51,7 @@ func (s *sqlDatabase) getUserByID(tx *gorm.DB, userID string) (User, error) {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return User{}, runnerErrors.ErrNotFound
}
return User{}, errors.Wrap(q.Error, "fetching user")
return User{}, fmt.Errorf("error fetching user: %w", q.Error)
}
return dbUser, nil
}
@ -82,12 +82,12 @@ func (s *sqlDatabase) CreateUser(_ context.Context, user params.NewUserParams) (
q := tx.Save(&newUser)
if q.Error != nil {
return errors.Wrap(q.Error, "creating user")
return fmt.Errorf("error creating user: %w", q.Error)
}
return nil
})
if err != nil {
return params.User{}, errors.Wrap(err, "creating user")
return params.User{}, fmt.Errorf("error creating user: %w", err)
}
return s.sqlToParamsUser(newUser), nil
}
@ -105,7 +105,7 @@ func (s *sqlDatabase) HasAdminUser(_ context.Context) bool {
func (s *sqlDatabase) GetUser(_ context.Context, user string) (params.User, error) {
dbUser, err := s.getUserByUsernameOrEmail(s.conn, user)
if err != nil {
return params.User{}, errors.Wrap(err, "fetching user")
return params.User{}, fmt.Errorf("error fetching user: %w", err)
}
return s.sqlToParamsUser(dbUser), nil
}
@ -113,7 +113,7 @@ func (s *sqlDatabase) GetUser(_ context.Context, user string) (params.User, erro
func (s *sqlDatabase) GetUserByID(_ context.Context, userID string) (params.User, error) {
dbUser, err := s.getUserByID(s.conn, userID)
if err != nil {
return params.User{}, errors.Wrap(err, "fetching user")
return params.User{}, fmt.Errorf("error fetching user: %w", err)
}
return s.sqlToParamsUser(dbUser), nil
}
@ -124,7 +124,7 @@ func (s *sqlDatabase) UpdateUser(_ context.Context, user string, param params.Up
err = s.conn.Transaction(func(tx *gorm.DB) error {
dbUser, err = s.getUserByUsernameOrEmail(tx, user)
if err != nil {
return errors.Wrap(err, "fetching user")
return fmt.Errorf("error fetching user: %w", err)
}
if param.FullName != "" {
@ -141,12 +141,12 @@ func (s *sqlDatabase) UpdateUser(_ context.Context, user string, param params.Up
}
if q := tx.Save(&dbUser); q.Error != nil {
return errors.Wrap(q.Error, "saving user")
return fmt.Errorf("error saving user: %w", q.Error)
}
return nil
})
if err != nil {
return params.User{}, errors.Wrap(err, "updating user")
return params.User{}, fmt.Errorf("error updating user: %w", err)
}
return s.sqlToParamsUser(dbUser), nil
}
@ -159,7 +159,7 @@ func (s *sqlDatabase) GetAdminUser(_ context.Context) (params.User, error) {
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return params.User{}, runnerErrors.ErrNotFound
}
return params.User{}, errors.Wrap(q.Error, "fetching admin user")
return params.User{}, fmt.Errorf("error fetching admin user: %w", q.Error)
}
return s.sqlToParamsUser(user), nil
}

View file

@ -161,7 +161,7 @@ func (s *UserTestSuite) TestCreateUserUsernameAlreadyExist() {
_, err := s.Store.CreateUser(context.Background(), s.Fixtures.NewUserParams)
s.Require().NotNil(err)
s.Require().Equal(("creating user: username already exists"), err.Error())
s.Require().Equal(("error creating user: username already exists"), err.Error())
}
func (s *UserTestSuite) TestCreateUserEmailAlreadyExist() {
@ -170,7 +170,7 @@ func (s *UserTestSuite) TestCreateUserEmailAlreadyExist() {
_, err := s.Store.CreateUser(context.Background(), s.Fixtures.NewUserParams)
s.Require().NotNil(err)
s.Require().Equal(("creating user: email already exists"), err.Error())
s.Require().Equal(("error creating user: email already exists"), err.Error())
}
func (s *UserTestSuite) TestCreateUserDBCreateErr() {
@ -191,7 +191,7 @@ func (s *UserTestSuite) TestCreateUserDBCreateErr() {
_, err := s.StoreSQLMocked.CreateUser(context.Background(), s.Fixtures.NewUserParams)
s.Require().NotNil(err)
s.Require().Equal("creating user: creating user: creating user mock error", err.Error())
s.Require().Equal("error creating user: error creating user: creating user mock error", err.Error())
s.assertSQLMockExpectations()
}
@ -230,7 +230,7 @@ func (s *UserTestSuite) TestGetUserNotFound() {
_, err := s.Store.GetUser(context.Background(), "dummy-user")
s.Require().NotNil(err)
s.Require().Equal("fetching user: not found", err.Error())
s.Require().Equal("error fetching user: not found", err.Error())
}
func (s *UserTestSuite) TestGetUserByID() {
@ -244,7 +244,7 @@ func (s *UserTestSuite) TestGetUserByIDNotFound() {
_, err := s.Store.GetUserByID(context.Background(), "dummy-user-id")
s.Require().NotNil(err)
s.Require().Equal("fetching user: not found", err.Error())
s.Require().Equal("error fetching user: not found", err.Error())
}
func (s *UserTestSuite) TestUpdateUser() {
@ -260,7 +260,7 @@ func (s *UserTestSuite) TestUpdateUserNotFound() {
_, err := s.Store.UpdateUser(context.Background(), "dummy-user", s.Fixtures.UpdateUserParams)
s.Require().NotNil(err)
s.Require().Equal("updating user: fetching user: not found", err.Error())
s.Require().Equal("error updating user: error fetching user: not found", err.Error())
}
func (s *UserTestSuite) TestUpdateUserDBSaveErr() {
@ -278,7 +278,7 @@ func (s *UserTestSuite) TestUpdateUserDBSaveErr() {
s.assertSQLMockExpectations()
s.Require().NotNil(err)
s.Require().Equal("updating user: saving user: saving user mock error", err.Error())
s.Require().Equal("error updating user: error saving user: saving user mock error", err.Error())
}
func TestUserTestSuite(t *testing.T) {

View file

@ -17,10 +17,10 @@ package sql
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/pkg/errors"
"gorm.io/datatypes"
"gorm.io/gorm"
@ -41,14 +41,14 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) (params.Instance, e
var labels []string
if len(instance.AditionalLabels) > 0 {
if err := json.Unmarshal(instance.AditionalLabels, &labels); err != nil {
return params.Instance{}, errors.Wrap(err, "unmarshalling labels")
return params.Instance{}, fmt.Errorf("error unmarshalling labels: %w", err)
}
}
var jitConfig map[string]string
if len(instance.JitConfiguration) > 0 {
if err := s.unsealAndUnmarshal(instance.JitConfiguration, &jitConfig); err != nil {
return params.Instance{}, errors.Wrap(err, "unmarshalling jit configuration")
return params.Instance{}, fmt.Errorf("error unmarshalling jit configuration: %w", err)
}
}
ret := params.Instance{
@ -95,7 +95,7 @@ func (s *sqlDatabase) sqlToParamsInstance(instance Instance) (params.Instance, e
if instance.Job != nil {
paramJob, err := sqlWorkflowJobToParamsJob(*instance.Job)
if err != nil {
return params.Instance{}, errors.Wrap(err, "converting job")
return params.Instance{}, fmt.Errorf("error converting job: %w", err)
}
ret.Job = &paramJob
}
@ -132,12 +132,12 @@ func (s *sqlDatabase) sqlToCommonOrganization(org Organization, detailed bool) (
}
secret, err := util.Unseal(org.WebhookSecret, []byte(s.cfg.Passphrase))
if err != nil {
return params.Organization{}, errors.Wrap(err, "decrypting secret")
return params.Organization{}, fmt.Errorf("error decrypting secret: %w", err)
}
endpoint, err := s.sqlToCommonGithubEndpoint(org.Endpoint)
if err != nil {
return params.Organization{}, errors.Wrap(err, "converting endpoint")
return params.Organization{}, fmt.Errorf("error converting endpoint: %w", err)
}
ret := params.Organization{
ID: org.ID.String(),
@ -163,7 +163,7 @@ func (s *sqlDatabase) sqlToCommonOrganization(org Organization, detailed bool) (
}
if err != nil {
return params.Organization{}, errors.Wrap(err, "converting credentials")
return params.Organization{}, fmt.Errorf("error converting credentials: %w", err)
}
if len(org.Events) > 0 {
@ -191,7 +191,7 @@ func (s *sqlDatabase) sqlToCommonOrganization(org Organization, detailed bool) (
for idx, pool := range org.Pools {
ret.Pools[idx], err = s.sqlToCommonPool(pool)
if err != nil {
return params.Organization{}, errors.Wrap(err, "converting pool")
return params.Organization{}, fmt.Errorf("error converting pool: %w", err)
}
}
@ -204,12 +204,12 @@ func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise, detailed bool
}
secret, err := util.Unseal(enterprise.WebhookSecret, []byte(s.cfg.Passphrase))
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "decrypting secret")
return params.Enterprise{}, fmt.Errorf("error decrypting secret: %w", err)
}
endpoint, err := s.sqlToCommonGithubEndpoint(enterprise.Endpoint)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "converting endpoint")
return params.Enterprise{}, fmt.Errorf("error converting endpoint: %w", err)
}
ret := params.Enterprise{
ID: enterprise.ID.String(),
@ -243,7 +243,7 @@ func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise, detailed bool
if detailed {
creds, err := s.sqlToCommonForgeCredentials(enterprise.Credentials)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "converting credentials")
return params.Enterprise{}, fmt.Errorf("error converting credentials: %w", err)
}
ret.Credentials = creds
}
@ -255,7 +255,7 @@ func (s *sqlDatabase) sqlToCommonEnterprise(enterprise Enterprise, detailed bool
for idx, pool := range enterprise.Pools {
ret.Pools[idx], err = s.sqlToCommonPool(pool)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "converting pool")
return params.Enterprise{}, fmt.Errorf("error converting pool: %w", err)
}
}
@ -309,7 +309,7 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) (params.Pool, error) {
endpoint, err := s.sqlToCommonGithubEndpoint(ep)
if err != nil {
return params.Pool{}, errors.Wrap(err, "converting endpoint")
return params.Pool{}, fmt.Errorf("error converting endpoint: %w", err)
}
ret.Endpoint = endpoint
@ -320,7 +320,7 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) (params.Pool, error) {
for idx, inst := range pool.Instances {
ret.Instances[idx], err = s.sqlToParamsInstance(inst)
if err != nil {
return params.Pool{}, errors.Wrap(err, "converting instance")
return params.Pool{}, fmt.Errorf("error converting instance: %w", err)
}
}
@ -330,6 +330,8 @@ func (s *sqlDatabase) sqlToCommonPool(pool Pool) (params.Pool, error) {
func (s *sqlDatabase) sqlToCommonScaleSet(scaleSet ScaleSet) (params.ScaleSet, error) {
ret := params.ScaleSet{
ID: scaleSet.ID,
CreatedAt: scaleSet.CreatedAt,
UpdatedAt: scaleSet.UpdatedAt,
ScaleSetID: scaleSet.ScaleSetID,
Name: scaleSet.Name,
DisableUpdate: scaleSet.DisableUpdate,
@ -355,28 +357,37 @@ func (s *sqlDatabase) sqlToCommonScaleSet(scaleSet ScaleSet) (params.ScaleSet, e
DesiredRunnerCount: scaleSet.DesiredRunnerCount,
}
var ep GithubEndpoint
if scaleSet.RepoID != nil {
ret.RepoID = scaleSet.RepoID.String()
if scaleSet.Repository.Owner != "" && scaleSet.Repository.Name != "" {
ret.RepoName = fmt.Sprintf("%s/%s", scaleSet.Repository.Owner, scaleSet.Repository.Name)
}
ep = scaleSet.Repository.Endpoint
}
if scaleSet.OrgID != nil && scaleSet.Organization.Name != "" {
if scaleSet.OrgID != nil {
ret.OrgID = scaleSet.OrgID.String()
ret.OrgName = scaleSet.Organization.Name
ep = scaleSet.Organization.Endpoint
}
if scaleSet.EnterpriseID != nil && scaleSet.Enterprise.Name != "" {
if scaleSet.EnterpriseID != nil {
ret.EnterpriseID = scaleSet.EnterpriseID.String()
ret.EnterpriseName = scaleSet.Enterprise.Name
ep = scaleSet.Enterprise.Endpoint
}
var err error
endpoint, err := s.sqlToCommonGithubEndpoint(ep)
if err != nil {
return params.ScaleSet{}, fmt.Errorf("error converting endpoint: %w", err)
}
ret.Endpoint = endpoint
for idx, inst := range scaleSet.Instances {
ret.Instances[idx], err = s.sqlToParamsInstance(inst)
if err != nil {
return params.ScaleSet{}, errors.Wrap(err, "converting instance")
return params.ScaleSet{}, fmt.Errorf("error converting instance: %w", err)
}
}
@ -396,11 +407,11 @@ func (s *sqlDatabase) sqlToCommonRepository(repo Repository, detailed bool) (par
}
secret, err := util.Unseal(repo.WebhookSecret, []byte(s.cfg.Passphrase))
if err != nil {
return params.Repository{}, errors.Wrap(err, "decrypting secret")
return params.Repository{}, fmt.Errorf("error decrypting secret: %w", err)
}
endpoint, err := s.sqlToCommonGithubEndpoint(repo.Endpoint)
if err != nil {
return params.Repository{}, errors.Wrap(err, "converting endpoint")
return params.Repository{}, fmt.Errorf("error converting endpoint: %w", err)
}
ret := params.Repository{
ID: repo.ID.String(),
@ -431,7 +442,7 @@ func (s *sqlDatabase) sqlToCommonRepository(repo Repository, detailed bool) (par
}
if err != nil {
return params.Repository{}, errors.Wrap(err, "converting credentials")
return params.Repository{}, fmt.Errorf("error converting credentials: %w", err)
}
if len(repo.Events) > 0 {
@ -459,7 +470,7 @@ func (s *sqlDatabase) sqlToCommonRepository(repo Repository, detailed bool) (par
for idx, pool := range repo.Pools {
ret.Pools[idx], err = s.sqlToCommonPool(pool)
if err != nil {
return params.Repository{}, errors.Wrap(err, "converting pool")
return params.Repository{}, fmt.Errorf("error converting pool: %w", err)
}
}
@ -488,14 +499,14 @@ func (s *sqlDatabase) getOrCreateTag(tx *gorm.DB, tagName string) (Tag, error) {
return tag, nil
}
if !errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Tag{}, errors.Wrap(q.Error, "fetching tag from database")
return Tag{}, fmt.Errorf("error fetching tag from database: %w", q.Error)
}
newTag := Tag{
Name: tagName,
}
if err := tx.Create(&newTag).Error; err != nil {
return Tag{}, errors.Wrap(err, "creating tag")
return Tag{}, fmt.Errorf("error creating tag: %w", err)
}
return newTag, nil
}
@ -550,7 +561,7 @@ func (s *sqlDatabase) updatePool(tx *gorm.DB, pool Pool, param params.UpdatePool
}
if q := tx.Save(&pool); q.Error != nil {
return params.Pool{}, errors.Wrap(q.Error, "saving database entry")
return params.Pool{}, fmt.Errorf("error saving database entry: %w", q.Error)
}
tags := []Tag{}
@ -558,13 +569,13 @@ func (s *sqlDatabase) updatePool(tx *gorm.DB, pool Pool, param params.UpdatePool
for _, val := range param.Tags {
t, err := s.getOrCreateTag(tx, val)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching tag")
return params.Pool{}, fmt.Errorf("error fetching tag: %w", err)
}
tags = append(tags, t)
}
if err := tx.Model(&pool).Association("Tags").Replace(&tags); err != nil {
return params.Pool{}, errors.Wrap(err, "replacing tags")
return params.Pool{}, fmt.Errorf("error replacing tags: %w", err)
}
}
@ -574,7 +585,7 @@ func (s *sqlDatabase) updatePool(tx *gorm.DB, pool Pool, param params.UpdatePool
func (s *sqlDatabase) getPoolByID(tx *gorm.DB, poolID string, preload ...string) (Pool, error) {
u, err := uuid.Parse(poolID)
if err != nil {
return Pool{}, errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return Pool{}, fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
var pool Pool
q := tx.Model(&Pool{})
@ -590,7 +601,7 @@ func (s *sqlDatabase) getPoolByID(tx *gorm.DB, poolID string, preload ...string)
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return Pool{}, runnerErrors.ErrNotFound
}
return Pool{}, errors.Wrap(q.Error, "fetching org from database")
return Pool{}, fmt.Errorf("error fetching org from database: %w", q.Error)
}
return pool, nil
}
@ -610,7 +621,7 @@ func (s *sqlDatabase) getScaleSetByID(tx *gorm.DB, scaleSetID uint, preload ...s
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
return ScaleSet{}, runnerErrors.ErrNotFound
}
return ScaleSet{}, errors.Wrap(q.Error, "fetching scale set from database")
return ScaleSet{}, fmt.Errorf("error fetching scale set from database: %w", q.Error)
}
return scaleSet, nil
}
@ -618,7 +629,7 @@ func (s *sqlDatabase) getScaleSetByID(tx *gorm.DB, scaleSetID uint, preload ...s
func (s *sqlDatabase) hasGithubEntity(tx *gorm.DB, entityType params.ForgeEntityType, entityID string) error {
u, err := uuid.Parse(entityID)
if err != nil {
return errors.Wrap(runnerErrors.ErrBadRequest, "parsing id")
return fmt.Errorf("error parsing id: %w", runnerErrors.ErrBadRequest)
}
var q *gorm.DB
switch entityType {
@ -629,15 +640,15 @@ func (s *sqlDatabase) hasGithubEntity(tx *gorm.DB, entityType params.ForgeEntity
case params.ForgeEntityTypeEnterprise:
q = tx.Model(&Enterprise{}).Where("id = ?", u)
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid entity type")
return fmt.Errorf("error invalid entity type: %w", runnerErrors.ErrBadRequest)
}
var entity interface{}
if err := q.First(entity).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(runnerErrors.ErrNotFound, "entity not found")
return fmt.Errorf("error entity not found: %w", runnerErrors.ErrNotFound)
}
return errors.Wrap(err, "fetching entity from database")
return fmt.Errorf("error fetching entity from database: %w", err)
}
return nil
}
@ -645,7 +656,7 @@ func (s *sqlDatabase) hasGithubEntity(tx *gorm.DB, entityType params.ForgeEntity
func (s *sqlDatabase) marshalAndSeal(data interface{}) ([]byte, error) {
enc, err := json.Marshal(data)
if err != nil {
return nil, errors.Wrap(err, "marshalling data")
return nil, fmt.Errorf("error marshalling data: %w", err)
}
return util.Seal(enc, []byte(s.cfg.Passphrase))
}
@ -653,10 +664,10 @@ func (s *sqlDatabase) marshalAndSeal(data interface{}) ([]byte, error) {
func (s *sqlDatabase) unsealAndUnmarshal(data []byte, target interface{}) error {
decrypted, err := util.Unseal(data, []byte(s.cfg.Passphrase))
if err != nil {
return errors.Wrap(err, "decrypting data")
return fmt.Errorf("error decrypting data: %w", err)
}
if err := json.Unmarshal(decrypted, target); err != nil {
return errors.Wrap(err, "unmarshalling data")
return fmt.Errorf("error unmarshalling data: %w", err)
}
return nil
}
@ -688,15 +699,15 @@ func (s *sqlDatabase) GetForgeEntity(_ context.Context, entityType params.ForgeE
case params.ForgeEntityTypeRepository:
ghEntity, err = s.GetRepositoryByID(s.ctx, entityID)
default:
return params.ForgeEntity{}, errors.Wrap(runnerErrors.ErrBadRequest, "invalid entity type")
return params.ForgeEntity{}, fmt.Errorf("error invalid entity type: %w", runnerErrors.ErrBadRequest)
}
if err != nil {
return params.ForgeEntity{}, errors.Wrap(err, "failed to get ")
return params.ForgeEntity{}, fmt.Errorf("error failed to get entity from db: %w", err)
}
entity, err := ghEntity.GetEntity()
if err != nil {
return params.ForgeEntity{}, errors.Wrap(err, "failed to get entity")
return params.ForgeEntity{}, fmt.Errorf("error failed to get entity: %w", err)
}
return entity, nil
}
@ -704,7 +715,7 @@ func (s *sqlDatabase) GetForgeEntity(_ context.Context, entityType params.ForgeE
func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
repo, err := s.getRepoByID(ctx, s.conn, repoID)
if err != nil {
return errors.Wrap(err, "updating instance")
return fmt.Errorf("error updating instance: %w", err)
}
msg := RepositoryEvent{
@ -714,7 +725,7 @@ func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, eve
}
if err := s.conn.Model(&repo).Association("Events").Append(&msg); err != nil {
return errors.Wrap(err, "adding status message")
return fmt.Errorf("error adding status message: %w", err)
}
if maxEvents > 0 {
@ -723,12 +734,12 @@ func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, eve
Limit(maxEvents).Order("id desc").
Where("repo_id = ?", repo.ID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
return fmt.Errorf("error fetching latest events: %w", q.Error)
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("repo_id = ? and id < ?", repo.ID, lastInList.ID).Unscoped().Delete(&RepositoryEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
return fmt.Errorf("error deleting old events: %w", err)
}
}
}
@ -738,7 +749,7 @@ func (s *sqlDatabase) addRepositoryEvent(ctx context.Context, repoID string, eve
func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
org, err := s.getOrgByID(ctx, s.conn, orgID)
if err != nil {
return errors.Wrap(err, "updating instance")
return fmt.Errorf("error updating instance: %w", err)
}
msg := OrganizationEvent{
@ -748,7 +759,7 @@ func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event param
}
if err := s.conn.Model(&org).Association("Events").Append(&msg); err != nil {
return errors.Wrap(err, "adding status message")
return fmt.Errorf("error adding status message: %w", err)
}
if maxEvents > 0 {
@ -757,12 +768,12 @@ func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event param
Limit(maxEvents).Order("id desc").
Where("org_id = ?", org.ID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
return fmt.Errorf("error fetching latest events: %w", q.Error)
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("org_id = ? and id < ?", org.ID, lastInList.ID).Unscoped().Delete(&OrganizationEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
return fmt.Errorf("error deleting old events: %w", err)
}
}
}
@ -772,7 +783,7 @@ func (s *sqlDatabase) addOrgEvent(ctx context.Context, orgID string, event param
func (s *sqlDatabase) addEnterpriseEvent(ctx context.Context, entID string, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
ent, err := s.getEnterpriseByID(ctx, s.conn, entID)
if err != nil {
return errors.Wrap(err, "updating instance")
return fmt.Errorf("error updating instance: %w", err)
}
msg := EnterpriseEvent{
@ -782,7 +793,7 @@ func (s *sqlDatabase) addEnterpriseEvent(ctx context.Context, entID string, even
}
if err := s.conn.Model(&ent).Association("Events").Append(&msg); err != nil {
return errors.Wrap(err, "adding status message")
return fmt.Errorf("error adding status message: %w", err)
}
if maxEvents > 0 {
@ -791,12 +802,12 @@ func (s *sqlDatabase) addEnterpriseEvent(ctx context.Context, entID string, even
Limit(maxEvents).Order("id desc").
Where("enterprise_id = ?", ent.ID).Find(&latestEvents)
if q.Error != nil {
return errors.Wrap(q.Error, "fetching latest events")
return fmt.Errorf("error fetching latest events: %w", q.Error)
}
if len(latestEvents) == maxEvents {
lastInList := latestEvents[len(latestEvents)-1]
if err := s.conn.Where("enterprise_id = ? and id < ?", ent.ID, lastInList.ID).Unscoped().Delete(&EnterpriseEvent{}).Error; err != nil {
return errors.Wrap(err, "deleting old events")
return fmt.Errorf("error deleting old events: %w", err)
}
}
}
@ -806,7 +817,7 @@ func (s *sqlDatabase) addEnterpriseEvent(ctx context.Context, entID string, even
func (s *sqlDatabase) AddEntityEvent(ctx context.Context, entity params.ForgeEntity, event params.EventType, eventLevel params.EventLevel, statusMessage string, maxEvents int) error {
if maxEvents == 0 {
return errors.Wrap(runnerErrors.ErrBadRequest, "max events cannot be 0")
return fmt.Errorf("max events cannot be 0: %w", runnerErrors.ErrBadRequest)
}
switch entity.EntityType {
@ -817,7 +828,7 @@ func (s *sqlDatabase) AddEntityEvent(ctx context.Context, entity params.ForgeEnt
case params.ForgeEntityTypeEnterprise:
return s.addEnterpriseEvent(ctx, entity.ID, event, eventLevel, statusMessage, maxEvents)
default:
return errors.Wrap(runnerErrors.ErrBadRequest, "invalid entity type")
return fmt.Errorf("invalid entity type: %w", runnerErrors.ErrBadRequest)
}
}
@ -827,12 +838,12 @@ func (s *sqlDatabase) sqlToCommonForgeCredentials(creds GithubCredentials) (para
}
data, err := util.Unseal(creds.Payload, []byte(s.cfg.Passphrase))
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "unsealing credentials")
return params.ForgeCredentials{}, fmt.Errorf("error unsealing credentials: %w", err)
}
ep, err := s.sqlToCommonGithubEndpoint(creds.Endpoint)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github endpoint")
return params.ForgeCredentials{}, fmt.Errorf("error converting github endpoint: %w", err)
}
commonCreds := params.ForgeCredentials{
@ -854,7 +865,7 @@ func (s *sqlDatabase) sqlToCommonForgeCredentials(creds GithubCredentials) (para
for _, repo := range creds.Repositories {
commonRepo, err := s.sqlToCommonRepository(repo, false)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github repository")
return params.ForgeCredentials{}, fmt.Errorf("error converting github repository: %w", err)
}
commonCreds.Repositories = append(commonCreds.Repositories, commonRepo)
}
@ -862,7 +873,7 @@ func (s *sqlDatabase) sqlToCommonForgeCredentials(creds GithubCredentials) (para
for _, org := range creds.Organizations {
commonOrg, err := s.sqlToCommonOrganization(org, false)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github organization")
return params.ForgeCredentials{}, fmt.Errorf("error converting github organization: %w", err)
}
commonCreds.Organizations = append(commonCreds.Organizations, commonOrg)
}
@ -870,7 +881,7 @@ func (s *sqlDatabase) sqlToCommonForgeCredentials(creds GithubCredentials) (para
for _, ent := range creds.Enterprises {
commonEnt, err := s.sqlToCommonEnterprise(ent, false)
if err != nil {
return params.ForgeCredentials{}, errors.Wrapf(err, "converting github enterprise: %s", ent.Name)
return params.ForgeCredentials{}, fmt.Errorf("error converting github enterprise %s: %w", ent.Name, err)
}
commonCreds.Enterprises = append(commonCreds.Enterprises, commonEnt)
}
@ -884,12 +895,12 @@ func (s *sqlDatabase) sqlGiteaToCommonForgeCredentials(creds GiteaCredentials) (
}
data, err := util.Unseal(creds.Payload, []byte(s.cfg.Passphrase))
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "unsealing credentials")
return params.ForgeCredentials{}, fmt.Errorf("error unsealing credentials: %w", err)
}
ep, err := s.sqlToCommonGithubEndpoint(creds.Endpoint)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github endpoint")
return params.ForgeCredentials{}, fmt.Errorf("error converting github endpoint: %w", err)
}
commonCreds := params.ForgeCredentials{
@ -910,7 +921,7 @@ func (s *sqlDatabase) sqlGiteaToCommonForgeCredentials(creds GiteaCredentials) (
for _, repo := range creds.Repositories {
commonRepo, err := s.sqlToCommonRepository(repo, false)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github repository")
return params.ForgeCredentials{}, fmt.Errorf("error converting github repository: %w", err)
}
commonCreds.Repositories = append(commonCreds.Repositories, commonRepo)
}
@ -918,7 +929,7 @@ func (s *sqlDatabase) sqlGiteaToCommonForgeCredentials(creds GiteaCredentials) (
for _, org := range creds.Organizations {
commonOrg, err := s.sqlToCommonOrganization(org, false)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "converting github organization")
return params.ForgeCredentials{}, fmt.Errorf("error converting github organization: %w", err)
}
commonCreds.Organizations = append(commonCreds.Organizations, commonOrg)
}
@ -943,12 +954,12 @@ func (s *sqlDatabase) sqlToCommonGithubEndpoint(ep GithubEndpoint) (params.Forge
func getUIDFromContext(ctx context.Context) (uuid.UUID, error) {
userID := auth.UserID(ctx)
if userID == "" {
return uuid.Nil, errors.Wrap(runnerErrors.ErrUnauthorized, "getting UID from context")
return uuid.Nil, fmt.Errorf("error getting UID from context: %w", runnerErrors.ErrUnauthorized)
}
asUUID, err := uuid.Parse(userID)
if err != nil {
return uuid.Nil, errors.Wrap(runnerErrors.ErrUnauthorized, "parsing UID from context")
return uuid.Nil, fmt.Errorf("error parsing UID from context: %w", runnerErrors.ErrUnauthorized)
}
return asUUID, nil
}

View file

@ -16,11 +16,10 @@ package watcher
import (
"context"
"fmt"
"log/slog"
"sync"
"github.com/pkg/errors"
"github.com/cloudbase/garm/database/common"
garmUtil "github.com/cloudbase/garm/util"
)
@ -83,7 +82,7 @@ func (w *watcher) RegisterProducer(ctx context.Context, id string) (common.Produ
defer w.mux.Unlock()
if _, ok := w.producers[id]; ok {
return nil, errors.Wrapf(common.ErrProducerAlreadyRegistered, "producer_id: %s", id)
return nil, fmt.Errorf("producer_id %s: %w", id, common.ErrProducerAlreadyRegistered)
}
p := &producer{
id: id,

View file

@ -50,12 +50,12 @@ func (s *WatcherStoreTestSuite) TestJobWatcher() {
consumeEvents(consumer)
jobParams := params.Job{
ID: 1,
RunID: 2,
Action: "test-action",
Conclusion: "started",
Status: "in_progress",
Name: "test-job",
WorkflowJobID: 2,
RunID: 2,
Action: "test-action",
Conclusion: "started",
Status: "in_progress",
Name: "test-job",
}
job, err := s.store.CreateOrUpdateJob(s.ctx, jobParams)
@ -76,8 +76,8 @@ func (s *WatcherStoreTestSuite) TestJobWatcher() {
s.T().Fatal("expected payload not received")
}
jobParams.Conclusion = "success"
updatedJob, err := s.store.CreateOrUpdateJob(s.ctx, jobParams)
job.Conclusion = "success"
updatedJob, err := s.store.CreateOrUpdateJob(s.ctx, job)
s.Require().NoError(err)
select {
@ -94,7 +94,7 @@ func (s *WatcherStoreTestSuite) TestJobWatcher() {
entityID, err := uuid.NewUUID()
s.Require().NoError(err)
err = s.store.LockJob(s.ctx, updatedJob.ID, entityID.String())
err = s.store.LockJob(s.ctx, updatedJob.WorkflowJobID, entityID.String())
s.Require().NoError(err)
select {
@ -110,7 +110,7 @@ func (s *WatcherStoreTestSuite) TestJobWatcher() {
s.T().Fatal("expected payload not received")
}
err = s.store.UnlockJob(s.ctx, updatedJob.ID, entityID.String())
err = s.store.UnlockJob(s.ctx, updatedJob.WorkflowJobID, entityID.String())
s.Require().NoError(err)
select {
@ -134,7 +134,7 @@ func (s *WatcherStoreTestSuite) TestJobWatcher() {
// We don't care about the update event here.
consumeEvents(consumer)
err = s.store.BreakLockJobIsQueued(s.ctx, updatedJob.ID)
err = s.store.BreakLockJobIsQueued(s.ctx, updatedJob.WorkflowJobID)
s.Require().NoError(err)
select {
@ -600,6 +600,11 @@ func (s *WatcherStoreTestSuite) TestScaleSetWatcher() {
// We updated last message ID and desired runner count above.
updatedScaleSet.DesiredRunnerCount = 5
updatedScaleSet.LastMessageID = 99
payloadFromEvent, ok := event.Payload.(params.ScaleSet)
s.Require().True(ok)
updatedScaleSet.UpdatedAt = payloadFromEvent.UpdatedAt
updatedScaleSet.CreatedAt = payloadFromEvent.CreatedAt
updatedScaleSet.Endpoint = params.ForgeEndpoint{}
s.Require().Equal(common.ChangePayload{
EntityType: common.ScaleSetEntityType,
Operation: common.DeleteOperation,

View file

@ -17,11 +17,11 @@ package watcher_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/stretchr/testify/suite"
commonParams "github.com/cloudbase/garm-provider-common/params"
@ -310,7 +310,7 @@ func maybeInitController(db common.Store) error {
}
if _, err := db.InitController(); err != nil {
return errors.Wrap(err, "initializing controller")
return fmt.Errorf("error initializing controller: %w", err)
}
return nil

View file

@ -6,12 +6,13 @@ First, clone the repository:
```bash
git clone https://github.com/cloudbase/garm
cd garm
```
Then build garm:
```bash
make
make build
```
You should now have both `garm` and `garm-cli` available in the `./bin` folder.
@ -22,4 +23,65 @@ If you have docker/podman installed, you can also build a static binary against
make build-static
```
This command will also build for both AMD64 and ARM64. Resulting binaries will be in the `./bin` folder.
This command will also build for both AMD64 and ARM64. Resulting binaries will be in the `./bin` folder.
## Hacking
If you're hacking on GARM and want to override the default version GARM injects, you can run the following command:
```bash
VERSION=v1.0.0 make build
```
> [!IMPORTANT]
> This only works for `make build`. The `make build-static` command does not support version overrides.
## The Web UI SPA
GARM now ships with a single page application. The application is written in svelte and tailwind CSS. To rebuild it or hack on it, you will need a number of dependencies installed and placed in your `$PATH`.
### Prerequisites
- **Node.js 24+** and **npm**
- **Go 1.21+** (for building the GARM backend)
- **openapi-generator-cli** in your PATH (for API client generation)
### Installing openapi-generator-cli
**Option 1: NPM Global Install**
```bash
npm install -g @openapitools/openapi-generator-cli
```
**Option 2: Manual Install**
Download from [OpenAPI Generator releases](https://github.com/OpenAPITools/openapi-generator/releases) and add to your PATH.
**Verify Installation:**
```bash
openapi-generator-cli version
```
### Hacking on the Web UI
If you need to change something in the `webapp/src` folder, make sure to rebuild the webapp before rebuilding GARM:
```bash
make build-webui
make build
```
> [!IMPORTANT]
> The Web UI that GARM ships with has `go generate` stanzas that require `@openapitools/openapi-generator-cli` and `tailwindcss` to be installed. You will also have to make sure that if you change API models, the Web UI still works, as adding new fields or changing the json tags of old fields will change accessors in the client code.
### Changing API models
If you need to change the models in the `params/` package, you will also need to regenerate the client both for garm-cli and for the web application we ship with GARM. To do this, you can run:
```bash
make generate
```
You will also need to make sure that the web app still works.

View file

@ -473,6 +473,8 @@ The config options are fairly straight forward.
certificate = ""
# The path on disk to the corresponding private key for the certificate.
key = ""
[apiserver.webui]
enable = true
```
The GARM API server has the option to enable TLS, but I suggest you use a reverse proxy and enable TLS termination in that reverse proxy. There is an `nginx` sample in this repository with TLS termination enabled.

View file

@ -88,7 +88,9 @@ The filter is defined as a JSON that you write over the websocket connections. T
"job",
"controller",
"github_credentials",
"github_endpoint"
"gitea_credentials",
"github_endpoint",
"scaleset"
],
"title": "entity type",
"description": "The type of entity to filter on",

View file

@ -346,7 +346,7 @@ garm-cli pool add \
You should now see 1 runner being spun up in LXD. You can check the status of the pool by doing:
```bash
garm-cli runner ls -a
garm-cli runner ls
```
To get more details about the runner, run:

View file

@ -61,6 +61,9 @@ time_to_live = "8760h"
bind = "0.0.0.0"
port = 80
use_tls = false
[apiserver.webui]
# Set this to false if you want to disable the Web UI.
enable = true
[database]
backend = "sqlite3"
@ -133,7 +136,7 @@ docker run -d \
-p 80:80 \
-v /etc/garm:/etc/garm:rw \
-v /var/snap/lxd/common/lxd/unix.socket:/var/snap/lxd/common/lxd/unix.socket:rw \
ghcr.io/cloudbase/garm:v0.1.4
ghcr.io/cloudbase/garm:v0.1.6
```
You will notice that we also mounted the LXD unix socket from the host inside the container where the config you pasted expects to find it. If you plan to use an external provider that does not need to connect to LXD over a unix socket, feel free to remove that mount.
@ -166,7 +169,7 @@ Adding the `garm` user to the LXD group will allow it to connect to the LXD unix
Next, download the latest release from the [releases page](https://github.com/cloudbase/garm/releases).
```bash
wget -q -O - https://github.com/cloudbase/garm/releases/download/v0.1.5/garm-linux-amd64.tgz | tar xzf - -C /usr/local/bin/
wget -q -O - https://github.com/cloudbase/garm/releases/download/v0.1.6/garm-linux-amd64.tgz | tar xzf - -C /usr/local/bin/
```
We'll be running under an unprivileged user. If we want to be able to listen on any port under `1024`, we'll have to set some capabilities on the binary:
@ -199,7 +202,7 @@ Copy the sample `systemd` service file:
```bash
wget -O /etc/systemd/system/garm.service \
https://raw.githubusercontent.com/cloudbase/garm/v0.1.5/contrib/garm.service
https://raw.githubusercontent.com/cloudbase/garm/v0.1.6/contrib/garm.service
```
Reload the `systemd` daemon and start the service:
@ -234,7 +237,7 @@ Before we can start using GARM, we need initialize it. This will create the `adm
To initialize GARM, we'll use the `garm-cli` tool. You can download the latest release from the [releases page](https://github.com/cloudbase/garm/releases):
```bash
wget -q -O - https://github.com/cloudbase/garm/releases/download/v0.1.5/garm-cli-linux-amd64.tgz | tar xzf - -C /usr/local/bin/
wget -q -O - https://github.com/cloudbase/garm/releases/download/v0.1.6/garm-cli-linux-amd64.tgz | tar xzf - -C /usr/local/bin/
```
Now we can initialize GARM:
@ -502,7 +505,7 @@ gabriel@rossak:~$ garm-cli pool add \
If we list the pool we should see it:
```bash
gabriel@rock:~$ garm-cli pool ls -a
gabriel@rock:~$ garm-cli pool ls
+--------------------------------------+---------------------------+--------------+-----------------+------------------+-------+---------+---------------+----------+
| ID | IMAGE | FLAVOR | TAGS | BELONGS TO | LEVEL | ENABLED | RUNNER PREFIX | PRIORITY |
+--------------------------------------+---------------------------+--------------+-----------------+------------------+-------+---------+---------------+----------+
@ -517,7 +520,7 @@ For the purposes of this guide, we'll increase it to 1 so we have a runner creat
First, list current runners:
```bash
gabriel@rossak:~$ garm-cli runner ls -a
gabriel@rossak:~$ garm-cli runner ls
+----+------+--------+---------------+---------+
| NR | NAME | STATUS | RUNNER STATUS | POOL ID |
+----+------+--------+---------------+---------+
@ -554,7 +557,7 @@ gabriel@rossak:~$ garm-cli pool update 344e4a72-2035-4a18-a3d5-87bd3874b56c --mi
Now if we list the runners:
```bash
gabriel@rossak:~$ garm-cli runner ls -a
gabriel@rossak:~$ garm-cli runner ls
+----+-------------------+----------------+---------------+--------------------------------------+
| NR | NAME | STATUS | RUNNER STATUS | POOL ID |
+----+-------------------+----------------+---------------+--------------------------------------+

View file

@ -66,7 +66,7 @@ garm-cli controller show
| Webhook Base URL | https://garm.example.com/webhooks |
| Controller Webhook URL | https://garm.example.com/webhooks/a4dd5f41-8e1e-42a7-af53-c0ba5ff6b0b3 |
| Minimum Job Age Backoff | 30 |
| Version | v0.1.5 |
| Version | v0.1.6 |
+-------------------------+----------------------------------------------------------------------------+
```
@ -567,10 +567,10 @@ ubuntu@garm:~$ garm-cli pool list --repo=be3a0673-56af-4395-9ebf-4521fea67567
If you want to list pools for an organization or enterprise, you can use the `--org` or `--enterprise` options respectively.
You can also list **all** pools from all configureg github entities by using the `--all` option.
In the absence or the `--repo`, `--org` or `--enterprise` options, the command will list all pools in GARM, regardless of the entity they belong to.
```bash
ubuntu@garm:~/garm$ garm-cli pool list --all
ubuntu@garm:~/garm$ garm-cli pool list
+--------------------------------------+---------------------------+--------------+-----------------------------------------+------------------+-------+---------+---------------+----------+
| ID | IMAGE | FLAVOR | TAGS | BELONGS TO | LEVEL | ENABLED | RUNNER PREFIX | PRIORITY |
+--------------------------------------+---------------------------+--------------+-----------------------------------------+------------------+-------+---------+---------------+----------+
@ -705,7 +705,7 @@ Awesome! This runner will be able to pick up jobs that match the labels we've se
You can list runners for a pool, for a repository, organization or enterprise, or for all of them. To list all runners, you can run:
```bash
ubuntu@garm:~$ garm-cli runner list --all
ubuntu@garm:~$ garm-cli runner list
+----+---------------------+---------+---------------+--------------------------------------+
| NR | NAME | STATUS | RUNNER STATUS | POOL ID |
+----+---------------------+---------+---------------+--------------------------------------+

45
go.mod
View file

@ -1,43 +1,38 @@
module github.com/cloudbase/garm
go 1.23.0
toolchain go1.23.6
go 1.24.6
require (
github.com/BurntSushi/toml v1.5.0
github.com/bradleyfalzon/ghinstallation/v2 v2.16.0
github.com/cloudbase/garm-provider-common v0.1.5
github.com/cloudbase/garm-provider-common v0.1.7
github.com/felixge/httpsnoop v1.0.4
github.com/go-openapi/errors v0.22.1
github.com/go-openapi/errors v0.22.2
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/strfmt v0.23.0
github.com/go-openapi/swag v0.23.1
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/go-github/v72 v72.0.0
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.4-0.20240702125206-a62d9d2a8413
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/juju/clock v1.1.1
github.com/juju/retry v1.0.1
github.com/jedib0t/go-pretty/v6 v6.6.8
github.com/manifoldco/promptui v0.9.0
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/client_golang v1.23.0
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.39.0
golang.org/x/mod v0.25.0
github.com/stretchr/testify v1.11.0
golang.org/x/crypto v0.41.0
golang.org/x/mod v0.27.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.15.0
golang.org/x/sync v0.16.0
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/datatypes v1.2.6
gorm.io/driver/mysql v1.6.0
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.30.0
gorm.io/gorm v1.30.1
)
require (
@ -50,7 +45,7 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.22.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
@ -62,24 +57,22 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/juju/errors v1.0.0 // indirect
github.com/juju/loggo v1.0.0 // indirect
github.com/juju/testing v1.0.2 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/mattn/go-sqlite3 v1.14.31 // indirect
github.com/minio/sio v0.4.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.64.0 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect
go.mongodb.org/mongo-driver v1.17.4 // indirect
@ -87,9 +80,9 @@ require (
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

88
go.sum
View file

@ -19,8 +19,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cloudbase/garm-provider-common v0.1.5 h1:aJL646l+VnZceQ2grbDYhWfxYpaQR2/QsUSD76kSZVs=
github.com/cloudbase/garm-provider-common v0.1.5/go.mod h1:2O51WbcfqRx5fDHyyJgIFq7KdTZZnefsM+aoOchyleU=
github.com/cloudbase/garm-provider-common v0.1.7 h1:V0upTejFRDiyFBO4hhkMWmPtmRTguyOt/4i1u9/rfbg=
github.com/cloudbase/garm-provider-common v0.1.7/go.mod h1:2O51WbcfqRx5fDHyyJgIFq7KdTZZnefsM+aoOchyleU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -34,10 +34,10 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU=
github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrYnZg=
github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
@ -56,8 +56,8 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@ -87,27 +87,14 @@ github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/clock v1.1.1 h1:NvgHG9DQmOpBevgt6gzkyimdWBooLXDy1cQn89qJzBI=
github.com/juju/clock v1.1.1/go.mod h1:HIBvJ8kiV/n7UHwKuCkdYL4l/MDECztHR2sAvWDxxf0=
github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM=
github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8=
github.com/juju/loggo v1.0.0 h1:Y6ZMQOGR9Aj3BGkiWx7HBbIx6zNwNkxhVNOHU2i1bl0=
github.com/juju/loggo v1.0.0/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg=
github.com/juju/retry v1.0.1 h1:EVwOPq273wO1o0BCU7Ay7XE/bNb+bTNYsCK6y+BboAk=
github.com/juju/retry v1.0.1/go.mod h1:SssN1eYeK3A2qjnFGTiVMbdzGJ2BfluaJblJXvuvgqA=
github.com/juju/testing v1.0.2 h1:OR90RqCd9CJONxXamZAjLknpZdtqDyxqW8IwCbgw3i4=
github.com/juju/testing v1.0.2/go.mod h1:h3Vd2rzB57KrdsBEy6R7bmSKPzP76BnNavt7i8PerwQ=
github.com/juju/utils/v3 v3.0.0 h1:Gg3n63mGPbBuoXCo+EPJuMi44hGZfloI8nlCIebHu2Q=
github.com/juju/utils/v3 v3.0.0/go.mod h1:8csUcj1VRkfjNIRzBFWzLFCMLwLqsRWvkmhfVAUwbC4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -116,19 +103,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.31 h1:ldt6ghyPJsokUIlksH63gWZkG6qVGeEAu4zLeS4aVZM=
github.com/mattn/go-sqlite3 v1.14.31/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/minio/sio v0.4.1 h1:EMe3YBC1nf+sRQia65Rutxi+Z554XPV0dt8BIBA+a/0=
@ -148,12 +132,12 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -164,15 +148,16 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLqFypcxvLmlvEciCHL7+Lv+4vwZqecI=
github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
@ -187,36 +172,35 @@ go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucg
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.6 h1:KafLdXvFUhzNeL2ncm03Gl3eTLONQfNKZ+wJ+9Y4Nck=
@ -229,5 +213,5 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc=
gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=

View file

@ -19,13 +19,13 @@ package testing
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"

View file

@ -420,7 +420,6 @@ func (r RunnerScaleSetMessage) GetJobsFromBody() ([]ScaleSetJobMessage, error) {
if r.Body == "" {
return nil, fmt.Errorf("no body specified")
}
if err := json.Unmarshal([]byte(r.Body), &body); err != nil {
return nil, fmt.Errorf("failed to unmarshal body: %w", err)
}
@ -428,21 +427,21 @@ func (r RunnerScaleSetMessage) GetJobsFromBody() ([]ScaleSetJobMessage, error) {
}
type RunnerReference struct {
ID int64 `json:"id"`
Name string `json:"name"`
OS string `json:"os"`
RunnerScaleSetID int `json:"runnerScaleSetId"`
CreatedOn interface{} `json:"createdOn"`
RunnerGroupID uint64 `json:"runnerGroupId"`
RunnerGroupName string `json:"runnerGroupName"`
Version string `json:"version"`
Enabled bool `json:"enabled"`
Ephemeral bool `json:"ephemeral"`
Status interface{} `json:"status"`
DisableUpdate bool `json:"disableUpdate"`
ProvisioningState string `json:"provisioningState"`
Busy bool `json:"busy"`
Labels []Label `json:"labels,omitempty"`
ID int64 `json:"id"`
Name string `json:"name"`
OS string `json:"os"`
RunnerScaleSetID int `json:"runnerScaleSetId"`
CreatedOn any `json:"createdOn"`
RunnerGroupID uint64 `json:"runnerGroupId"`
RunnerGroupName string `json:"runnerGroupName"`
Version string `json:"version"`
Enabled bool `json:"enabled"`
Ephemeral bool `json:"ephemeral"`
Status any `json:"status"`
DisableUpdate bool `json:"disableUpdate"`
ProvisioningState string `json:"provisioningState"`
Busy bool `json:"busy"`
Labels []Label `json:"labels,omitempty"`
}
func (r RunnerReference) GetStatus() RunnerStatus {
@ -519,6 +518,7 @@ type RunnerGroupList struct {
type ScaleSetJobMessage struct {
MessageType string `json:"messageType,omitempty"`
JobID string `json:"jobId,omitempty"`
RunnerRequestID int64 `json:"runnerRequestId,omitempty"`
RepositoryName string `json:"repositoryName,omitempty"`
OwnerName string `json:"ownerName,omitempty"`
@ -552,7 +552,7 @@ func (s ScaleSetJobMessage) MessageTypeToStatus() JobStatus {
func (s ScaleSetJobMessage) ToJob() Job {
return Job{
ID: s.RunnerRequestID,
ScaleSetJobID: s.JobID,
Action: s.EventName,
RunID: s.WorkflowRunID,
Status: string(s.MessageTypeToStatus()),

View file

@ -172,6 +172,7 @@ const (
MessageTypeJobAvailable = "JobAvailable"
)
// swagger:model StatusMessage
type StatusMessage struct {
CreatedAt time.Time `json:"created_at,omitempty"`
Message string `json:"message,omitempty"`
@ -179,6 +180,7 @@ type StatusMessage struct {
EventLevel EventLevel `json:"event_level,omitempty"`
}
// swagger:model EntityEvent
type EntityEvent struct {
ID uint `json:"id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
@ -188,6 +190,7 @@ type EntityEvent struct {
Message string `json:"message,omitempty"`
}
// swagger:model Instance
type Instance struct {
// ID is the database ID of this instance.
ID string `json:"id,omitempty"`
@ -282,6 +285,7 @@ func (i Instance) GetID() string {
}
// used by swagger client generated code
// swagger:model Instances
type Instances []Instance
type BootstrapInstance struct {
@ -352,6 +356,7 @@ type Tag struct {
Name string `json:"name,omitempty"`
}
// swagger:model Pool
type Pool struct {
RunnerPrefix
@ -376,7 +381,7 @@ type Pool struct {
EnterpriseID string `json:"enterprise_id,omitempty"`
EnterpriseName string `json:"enterprise_name,omitempty"`
Endpoint ForgeEndpoint `json:"forge_type,omitempty"`
Endpoint ForgeEndpoint `json:"endpoint,omitempty"`
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
@ -487,11 +492,16 @@ func (p *Pool) HasRequiredLabels(set []string) bool {
}
// used by swagger client generated code
// swagger:model Pools
type Pools []Pool
// swagger:model ScaleSet
type ScaleSet struct {
RunnerPrefix
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
ID uint `json:"id,omitempty"`
ScaleSetID int `json:"scale_set_id,omitempty"`
Name string `json:"name,omitempty"`
@ -511,6 +521,8 @@ type ScaleSet struct {
Instances []Instance `json:"instances,omitempty"`
DesiredRunnerCount int `json:"desired_runner_count,omitempty"`
Endpoint ForgeEndpoint `json:"endpoint,omitempty"`
RunnerBootstrapTimeout uint `json:"runner_bootstrap_timeout,omitempty"`
// ExtraSpecs is an opaque raw json that gets sent to the provider
// as part of the bootstrap params for instances. It can contain
@ -570,7 +582,7 @@ func (p ScaleSet) GetEntity() (ForgeEntity, error) {
EntityType: ForgeEntityTypeEnterprise,
}, nil
}
return ForgeEntity{}, fmt.Errorf("pool has no associated entity")
return ForgeEntity{}, fmt.Errorf("scale set has no associated entity")
}
func (p *ScaleSet) ScaleSetType() ForgeEntityType {
@ -593,8 +605,10 @@ func (p *ScaleSet) RunnerTimeout() uint {
}
// used by swagger client generated code
// swagger:model ScaleSets
type ScaleSets []ScaleSet
// swagger:model Repository
type Repository struct {
ID string `json:"id,omitempty"`
Owner string `json:"owner,omitempty"`
@ -666,8 +680,10 @@ func (r Repository) String() string {
}
// used by swagger client generated code
// swagger:model Repositories
type Repositories []Repository
// swagger:model Organization
type Organization struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
@ -724,8 +740,10 @@ func (o Organization) GetBalancerType() PoolBalancerType {
}
// used by swagger client generated code
// swagger:model Organizations
type Organizations []Organization
// swagger:model Enterprise
type Enterprise struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
@ -782,9 +800,11 @@ func (e Enterprise) GetBalancerType() PoolBalancerType {
}
// used by swagger client generated code
// swagger:model Enterprises
type Enterprises []Enterprise
// Users holds information about a particular user
// swagger:model User
type User struct {
ID string `json:"id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
@ -801,10 +821,12 @@ type User struct {
// JWTResponse holds the JWT token returned as a result of a
// successful auth
// swagger:model JWTResponse
type JWTResponse struct {
Token string `json:"token,omitempty"`
}
// swagger:model ControllerInfo
type ControllerInfo struct {
// ControllerID is the unique ID of this controller. This ID gets generated
// automatically on controller init.
@ -857,6 +879,7 @@ func (c *ControllerInfo) JobBackoff() time.Duration {
return time.Duration(int64(c.MinimumJobAgeBackoff))
}
// swagger:model GithubRateLimit
type GithubRateLimit struct {
Limit int `json:"limit,omitempty"`
Used int `json:"used,omitempty"`
@ -875,6 +898,7 @@ func (g GithubRateLimit) ResetAt() time.Time {
return time.Unix(g.Reset, 0)
}
// swagger:model ForgeCredentials
type ForgeCredentials struct {
ID uint `json:"id,omitempty"`
Name string `json:"name,omitempty"`
@ -1000,8 +1024,10 @@ func (g ForgeCredentials) RootCertificateBundle() (CertificateBundle, error) {
}
// used by swagger client generated code
// swagger:model Credentials
type Credentials []ForgeCredentials
// swagger:model Provider
type Provider struct {
Name string `json:"name,omitempty"`
ProviderType ProviderType `json:"type,omitempty"`
@ -1009,8 +1035,10 @@ type Provider struct {
}
// used by swagger client generated code
// swagger:model Providers
type Providers []Provider
// swagger:model PoolManagerStatus
type PoolManagerStatus struct {
IsRunning bool `json:"running,omitempty"`
FailureReason string `json:"failure_reason,omitempty"`
@ -1032,9 +1060,14 @@ func (p RunnerPrefix) GetRunnerPrefix() string {
return p.Prefix
}
// swagger:model Job
type Job struct {
// ID is the ID of the job.
ID int64 `json:"id,omitempty"`
WorkflowJobID int64 `json:"workflow_job_id,omitempty"`
// ScaleSetJobID is the job ID when generated for a scale set.
ScaleSetJobID string `json:"scaleset_job_id,omitempty"`
// RunID is the ID of the workflow run. A run may have multiple jobs.
RunID int64 `json:"run_id,omitempty"`
// Action is the specific activity that triggered the event.
@ -1082,14 +1115,17 @@ type Job struct {
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// swagger:model Jobs
// used by swagger client generated code
type Jobs []Job
// swagger:model InstallWebhookParams
type InstallWebhookParams struct {
WebhookEndpointType WebhookEndpointType `json:"webhook_endpoint_type,omitempty"`
InsecureSSL bool `json:"insecure_ssl,omitempty"`
}
// swagger:model HookInfo
type HookInfo struct {
ID int64 `json:"id,omitempty"`
URL string `json:"url,omitempty"`
@ -1102,6 +1138,7 @@ type CertificateBundle struct {
RootCertificates map[string][]byte `json:"root_certificates,omitempty"`
}
// swagger:model ForgeEntity
type UpdateSystemInfoParams struct {
OSName string `json:"os_name,omitempty"`
OSVersion string `json:"os_version,omitempty"`
@ -1133,18 +1170,13 @@ func (g ForgeEntity) GetForgeType() (EndpointType, error) {
}
func (g ForgeEntity) ForgeURL() string {
switch g.Credentials.ForgeType {
case GiteaEndpointType:
return g.Credentials.Endpoint.APIBaseURL
default:
switch g.EntityType {
case ForgeEntityTypeRepository:
return fmt.Sprintf("%s/%s/%s", g.Credentials.BaseURL, g.Owner, g.Name)
case ForgeEntityTypeOrganization:
return fmt.Sprintf("%s/%s", g.Credentials.BaseURL, g.Owner)
case ForgeEntityTypeEnterprise:
return fmt.Sprintf("%s/enterprises/%s", g.Credentials.BaseURL, g.Owner)
}
switch g.EntityType {
case ForgeEntityTypeRepository:
return fmt.Sprintf("%s/%s/%s", g.Credentials.BaseURL, g.Owner, g.Name)
case ForgeEntityTypeOrganization:
return fmt.Sprintf("%s/%s", g.Credentials.BaseURL, g.Owner)
case ForgeEntityTypeEnterprise:
return fmt.Sprintf("%s/enterprises/%s", g.Credentials.BaseURL, g.Owner)
}
return ""
}
@ -1190,8 +1222,10 @@ func (g ForgeEntity) GetIDAsUUID() (uuid.UUID, error) {
}
// used by swagger client generated code
// swagger:model ForgeEndpoints
type ForgeEndpoints []ForgeEndpoint
// swagger:model ForgeEndpoint
type ForgeEndpoint struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`

View file

@ -21,8 +21,6 @@ import (
"fmt"
"net/url"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
commonParams "github.com/cloudbase/garm-provider-common/params"
)
@ -39,6 +37,7 @@ type InstanceRequest struct {
OSVersion string `json:"os_version"`
}
// swagger:model CreateRepoParams
type CreateRepoParams struct {
Owner string `json:"owner,omitempty"`
Name string `json:"name,omitempty"`
@ -80,6 +79,7 @@ func (c *CreateRepoParams) Validate() error {
return nil
}
// swagger:model CreateOrgParams
type CreateOrgParams struct {
Name string `json:"name,omitempty"`
CredentialsName string `json:"credentials_name,omitempty"`
@ -115,6 +115,7 @@ func (c *CreateOrgParams) Validate() error {
return nil
}
// swagger:model CreateEnterpriseParams
type CreateEnterpriseParams struct {
Name string `json:"name,omitempty"`
CredentialsName string `json:"credentials_name,omitempty"`
@ -143,6 +144,7 @@ func (c *CreateEnterpriseParams) Validate() error {
// NewUserParams holds the needed information to create
// a new user
// swagger:model NewUserParams
type NewUserParams struct {
Email string `json:"email,omitempty"`
Username string `json:"username,omitempty"`
@ -152,6 +154,7 @@ type NewUserParams struct {
Enabled bool `json:"-"`
}
// swagger:model UpdatePoolParams
type UpdatePoolParams struct {
RunnerPrefix
@ -189,6 +192,7 @@ type CreateInstanceParams struct {
JitConfiguration map[string]string `json:"jit_configuration,omitempty"`
}
// swagger:model CreatePoolParams
type CreatePoolParams struct {
RunnerPrefix
@ -263,6 +267,7 @@ type UpdateUserParams struct {
Enabled *bool `json:"enabled,omitempty"`
}
// swagger:model PasswordLoginParams
// PasswordLoginParams holds information used during
// password authentication, that will be passed to a
// password login function
@ -279,6 +284,7 @@ func (p PasswordLoginParams) Validate() error {
return nil
}
// swagger:model UpdateEntityParams
type UpdateEntityParams struct {
CredentialsName string `json:"credentials_name,omitempty"`
WebhookSecret string `json:"webhook_secret,omitempty"`
@ -291,6 +297,7 @@ type InstanceUpdateMessage struct {
AgentID *int64 `json:"agent_id,omitempty"`
}
// swagger:model CreateGithubEndpointParams
type CreateGithubEndpointParams struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
@ -358,6 +365,7 @@ func (c CreateGithubEndpointParams) Validate() error {
return nil
}
// swagger:model UpdateGithubEndpointParams
type UpdateGithubEndpointParams struct {
Description *string `json:"description,omitempty"`
APIBaseURL *string `json:"api_base_url,omitempty"`
@ -416,10 +424,12 @@ func (u UpdateGithubEndpointParams) Validate() error {
return nil
}
// swagger:model GithubPAT
type GithubPAT struct {
OAuth2Token string `json:"oauth2_token,omitempty"`
}
// swagger:model GithubApp
type GithubApp struct {
AppID int64 `json:"app_id,omitempty"`
InstallationID int64 `json:"installation_id,omitempty"`
@ -452,6 +462,7 @@ func (g GithubApp) Validate() error {
return nil
}
// swagger:model CreateGithubCredentialsParams
type CreateGithubCredentialsParams struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
@ -484,13 +495,14 @@ func (c CreateGithubCredentialsParams) Validate() error {
if c.AuthType == ForgeAuthTypeApp {
if err := c.App.Validate(); err != nil {
return errors.Wrap(err, "invalid app")
return fmt.Errorf("invalid app: %w", err)
}
}
return nil
}
// swagger:model UpdateGithubCredentialsParams
type UpdateGithubCredentialsParams struct {
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
@ -511,13 +523,14 @@ func (u UpdateGithubCredentialsParams) Validate() error {
if u.App != nil {
if err := u.App.Validate(); err != nil {
return errors.Wrap(err, "invalid app")
return fmt.Errorf("invalid app: %w", err)
}
}
return nil
}
// swagger:model UpdateControllerParams
type UpdateControllerParams struct {
MetadataURL *string `json:"metadata_url,omitempty"`
CallbackURL *string `json:"callback_url,omitempty"`
@ -550,6 +563,7 @@ func (u UpdateControllerParams) Validate() error {
return nil
}
// swagger:model CreateScaleSetParams
type CreateScaleSetParams struct {
RunnerPrefix
@ -602,6 +616,7 @@ func (s *CreateScaleSetParams) Validate() error {
return nil
}
// swagger:model UpdateScaleSetParams
type UpdateScaleSetParams struct {
RunnerPrefix
@ -621,8 +636,10 @@ type UpdateScaleSetParams struct {
GitHubRunnerGroup *string `json:"runner_group,omitempty"`
State *ScaleSetState `json:"state"`
ExtendedState *string `json:"extended_state"`
ScaleSetID int `json:"-"`
}
// swagger:model CreateGiteaEndpointParams
type CreateGiteaEndpointParams struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
@ -674,6 +691,7 @@ func (c CreateGiteaEndpointParams) Validate() error {
return nil
}
// swagger:model UpdateGiteaEndpointParams
type UpdateGiteaEndpointParams struct {
Description *string `json:"description,omitempty"`
APIBaseURL *string `json:"api_base_url,omitempty"`
@ -719,6 +737,7 @@ func (u UpdateGiteaEndpointParams) Validate() error {
return nil
}
// swagger:model CreateGiteaCredentialsParams
type CreateGiteaCredentialsParams struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
@ -752,6 +771,7 @@ func (c CreateGiteaCredentialsParams) Validate() error {
return nil
}
// swagger:model UpdateGiteaCredentialsParams
type UpdateGiteaCredentialsParams struct {
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`

View file

@ -2,8 +2,8 @@ package runner
import (
"context"
"github.com/pkg/errors"
"errors"
"fmt"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
@ -12,11 +12,11 @@ import (
func (r *Runner) ResolveForgeCredentialByName(ctx context.Context, credentialsName string) (params.ForgeCredentials, error) {
githubCred, err := r.store.GetGithubCredentialsByName(ctx, credentialsName, false)
if err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
return params.ForgeCredentials{}, errors.Wrap(err, "fetching github credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching github credentials: %w", err)
}
giteaCred, err := r.store.GetGiteaCredentialsByName(ctx, credentialsName, false)
if err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
return params.ForgeCredentials{}, errors.Wrap(err, "fetching gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error fetching gitea credentials: %w", err)
}
if githubCred.ID != 0 && giteaCred.ID != 0 {
return params.ForgeCredentials{}, runnerErrors.NewBadRequestError("credentials %s are defined for both GitHub and Gitea, please specify the forge type", credentialsName)

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.53.3. DO NOT EDIT.
// Code generated by mockery. DO NOT EDIT.
package mocks
@ -18,6 +18,14 @@ type GithubClient struct {
mock.Mock
}
type GithubClient_Expecter struct {
mock *mock.Mock
}
func (_m *GithubClient) EXPECT() *GithubClient_Expecter {
return &GithubClient_Expecter{mock: &_m.Mock}
}
// CreateEntityHook provides a mock function with given fields: ctx, hook
func (_m *GithubClient) CreateEntityHook(ctx context.Context, hook *github.Hook) (*github.Hook, error) {
ret := _m.Called(ctx, hook)
@ -48,6 +56,35 @@ func (_m *GithubClient) CreateEntityHook(ctx context.Context, hook *github.Hook)
return r0, r1
}
// GithubClient_CreateEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateEntityHook'
type GithubClient_CreateEntityHook_Call struct {
*mock.Call
}
// CreateEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - hook *github.Hook
func (_e *GithubClient_Expecter) CreateEntityHook(ctx interface{}, hook interface{}) *GithubClient_CreateEntityHook_Call {
return &GithubClient_CreateEntityHook_Call{Call: _e.mock.On("CreateEntityHook", ctx, hook)}
}
func (_c *GithubClient_CreateEntityHook_Call) Run(run func(ctx context.Context, hook *github.Hook)) *GithubClient_CreateEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*github.Hook))
})
return _c
}
func (_c *GithubClient_CreateEntityHook_Call) Return(ret *github.Hook, err error) *GithubClient_CreateEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubClient_CreateEntityHook_Call) RunAndReturn(run func(context.Context, *github.Hook) (*github.Hook, error)) *GithubClient_CreateEntityHook_Call {
_c.Call.Return(run)
return _c
}
// CreateEntityRegistrationToken provides a mock function with given fields: ctx
func (_m *GithubClient) CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx)
@ -87,6 +124,34 @@ func (_m *GithubClient) CreateEntityRegistrationToken(ctx context.Context) (*git
return r0, r1, r2
}
// GithubClient_CreateEntityRegistrationToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateEntityRegistrationToken'
type GithubClient_CreateEntityRegistrationToken_Call struct {
*mock.Call
}
// CreateEntityRegistrationToken is a helper method to define mock.On call
// - ctx context.Context
func (_e *GithubClient_Expecter) CreateEntityRegistrationToken(ctx interface{}) *GithubClient_CreateEntityRegistrationToken_Call {
return &GithubClient_CreateEntityRegistrationToken_Call{Call: _e.mock.On("CreateEntityRegistrationToken", ctx)}
}
func (_c *GithubClient_CreateEntityRegistrationToken_Call) Run(run func(ctx context.Context)) *GithubClient_CreateEntityRegistrationToken_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *GithubClient_CreateEntityRegistrationToken_Call) Return(_a0 *github.RegistrationToken, _a1 *github.Response, _a2 error) *GithubClient_CreateEntityRegistrationToken_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *GithubClient_CreateEntityRegistrationToken_Call) RunAndReturn(run func(context.Context) (*github.RegistrationToken, *github.Response, error)) *GithubClient_CreateEntityRegistrationToken_Call {
_c.Call.Return(run)
return _c
}
// DeleteEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubClient) DeleteEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
@ -117,6 +182,35 @@ func (_m *GithubClient) DeleteEntityHook(ctx context.Context, id int64) (*github
return r0, r1
}
// GithubClient_DeleteEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteEntityHook'
type GithubClient_DeleteEntityHook_Call struct {
*mock.Call
}
// DeleteEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - id int64
func (_e *GithubClient_Expecter) DeleteEntityHook(ctx interface{}, id interface{}) *GithubClient_DeleteEntityHook_Call {
return &GithubClient_DeleteEntityHook_Call{Call: _e.mock.On("DeleteEntityHook", ctx, id)}
}
func (_c *GithubClient_DeleteEntityHook_Call) Run(run func(ctx context.Context, id int64)) *GithubClient_DeleteEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubClient_DeleteEntityHook_Call) Return(ret *github.Response, err error) *GithubClient_DeleteEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubClient_DeleteEntityHook_Call) RunAndReturn(run func(context.Context, int64) (*github.Response, error)) *GithubClient_DeleteEntityHook_Call {
_c.Call.Return(run)
return _c
}
// GetEntity provides a mock function with no fields
func (_m *GithubClient) GetEntity() params.ForgeEntity {
ret := _m.Called()
@ -135,6 +229,33 @@ func (_m *GithubClient) GetEntity() params.ForgeEntity {
return r0
}
// GithubClient_GetEntity_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntity'
type GithubClient_GetEntity_Call struct {
*mock.Call
}
// GetEntity is a helper method to define mock.On call
func (_e *GithubClient_Expecter) GetEntity() *GithubClient_GetEntity_Call {
return &GithubClient_GetEntity_Call{Call: _e.mock.On("GetEntity")}
}
func (_c *GithubClient_GetEntity_Call) Run(run func()) *GithubClient_GetEntity_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *GithubClient_GetEntity_Call) Return(_a0 params.ForgeEntity) *GithubClient_GetEntity_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *GithubClient_GetEntity_Call) RunAndReturn(run func() params.ForgeEntity) *GithubClient_GetEntity_Call {
_c.Call.Return(run)
return _c
}
// GetEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubClient) GetEntityHook(ctx context.Context, id int64) (*github.Hook, error) {
ret := _m.Called(ctx, id)
@ -165,6 +286,35 @@ func (_m *GithubClient) GetEntityHook(ctx context.Context, id int64) (*github.Ho
return r0, r1
}
// GithubClient_GetEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntityHook'
type GithubClient_GetEntityHook_Call struct {
*mock.Call
}
// GetEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - id int64
func (_e *GithubClient_Expecter) GetEntityHook(ctx interface{}, id interface{}) *GithubClient_GetEntityHook_Call {
return &GithubClient_GetEntityHook_Call{Call: _e.mock.On("GetEntityHook", ctx, id)}
}
func (_c *GithubClient_GetEntityHook_Call) Run(run func(ctx context.Context, id int64)) *GithubClient_GetEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubClient_GetEntityHook_Call) Return(ret *github.Hook, err error) *GithubClient_GetEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubClient_GetEntityHook_Call) RunAndReturn(run func(context.Context, int64) (*github.Hook, error)) *GithubClient_GetEntityHook_Call {
_c.Call.Return(run)
return _c
}
// GetEntityJITConfig provides a mock function with given fields: ctx, instance, pool, labels
func (_m *GithubClient) GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (map[string]string, *github.Runner, error) {
ret := _m.Called(ctx, instance, pool, labels)
@ -204,6 +354,94 @@ func (_m *GithubClient) GetEntityJITConfig(ctx context.Context, instance string,
return r0, r1, r2
}
// GithubClient_GetEntityJITConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntityJITConfig'
type GithubClient_GetEntityJITConfig_Call struct {
*mock.Call
}
// GetEntityJITConfig is a helper method to define mock.On call
// - ctx context.Context
// - instance string
// - pool params.Pool
// - labels []string
func (_e *GithubClient_Expecter) GetEntityJITConfig(ctx interface{}, instance interface{}, pool interface{}, labels interface{}) *GithubClient_GetEntityJITConfig_Call {
return &GithubClient_GetEntityJITConfig_Call{Call: _e.mock.On("GetEntityJITConfig", ctx, instance, pool, labels)}
}
func (_c *GithubClient_GetEntityJITConfig_Call) Run(run func(ctx context.Context, instance string, pool params.Pool, labels []string)) *GithubClient_GetEntityJITConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(params.Pool), args[3].([]string))
})
return _c
}
func (_c *GithubClient_GetEntityJITConfig_Call) Return(jitConfigMap map[string]string, runner *github.Runner, err error) *GithubClient_GetEntityJITConfig_Call {
_c.Call.Return(jitConfigMap, runner, err)
return _c
}
func (_c *GithubClient_GetEntityJITConfig_Call) RunAndReturn(run func(context.Context, string, params.Pool, []string) (map[string]string, *github.Runner, error)) *GithubClient_GetEntityJITConfig_Call {
_c.Call.Return(run)
return _c
}
// GetEntityRunnerGroupIDByName provides a mock function with given fields: ctx, runnerGroupName
func (_m *GithubClient) GetEntityRunnerGroupIDByName(ctx context.Context, runnerGroupName string) (int64, error) {
ret := _m.Called(ctx, runnerGroupName)
if len(ret) == 0 {
panic("no return value specified for GetEntityRunnerGroupIDByName")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (int64, error)); ok {
return rf(ctx, runnerGroupName)
}
if rf, ok := ret.Get(0).(func(context.Context, string) int64); ok {
r0 = rf(ctx, runnerGroupName)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, runnerGroupName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GithubClient_GetEntityRunnerGroupIDByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntityRunnerGroupIDByName'
type GithubClient_GetEntityRunnerGroupIDByName_Call struct {
*mock.Call
}
// GetEntityRunnerGroupIDByName is a helper method to define mock.On call
// - ctx context.Context
// - runnerGroupName string
func (_e *GithubClient_Expecter) GetEntityRunnerGroupIDByName(ctx interface{}, runnerGroupName interface{}) *GithubClient_GetEntityRunnerGroupIDByName_Call {
return &GithubClient_GetEntityRunnerGroupIDByName_Call{Call: _e.mock.On("GetEntityRunnerGroupIDByName", ctx, runnerGroupName)}
}
func (_c *GithubClient_GetEntityRunnerGroupIDByName_Call) Run(run func(ctx context.Context, runnerGroupName string)) *GithubClient_GetEntityRunnerGroupIDByName_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *GithubClient_GetEntityRunnerGroupIDByName_Call) Return(_a0 int64, _a1 error) *GithubClient_GetEntityRunnerGroupIDByName_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *GithubClient_GetEntityRunnerGroupIDByName_Call) RunAndReturn(run func(context.Context, string) (int64, error)) *GithubClient_GetEntityRunnerGroupIDByName_Call {
_c.Call.Return(run)
return _c
}
// GetWorkflowJobByID provides a mock function with given fields: ctx, owner, repo, jobID
func (_m *GithubClient) GetWorkflowJobByID(ctx context.Context, owner string, repo string, jobID int64) (*github.WorkflowJob, *github.Response, error) {
ret := _m.Called(ctx, owner, repo, jobID)
@ -243,6 +481,37 @@ func (_m *GithubClient) GetWorkflowJobByID(ctx context.Context, owner string, re
return r0, r1, r2
}
// GithubClient_GetWorkflowJobByID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWorkflowJobByID'
type GithubClient_GetWorkflowJobByID_Call struct {
*mock.Call
}
// GetWorkflowJobByID is a helper method to define mock.On call
// - ctx context.Context
// - owner string
// - repo string
// - jobID int64
func (_e *GithubClient_Expecter) GetWorkflowJobByID(ctx interface{}, owner interface{}, repo interface{}, jobID interface{}) *GithubClient_GetWorkflowJobByID_Call {
return &GithubClient_GetWorkflowJobByID_Call{Call: _e.mock.On("GetWorkflowJobByID", ctx, owner, repo, jobID)}
}
func (_c *GithubClient_GetWorkflowJobByID_Call) Run(run func(ctx context.Context, owner string, repo string, jobID int64)) *GithubClient_GetWorkflowJobByID_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(int64))
})
return _c
}
func (_c *GithubClient_GetWorkflowJobByID_Call) Return(_a0 *github.WorkflowJob, _a1 *github.Response, _a2 error) *GithubClient_GetWorkflowJobByID_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *GithubClient_GetWorkflowJobByID_Call) RunAndReturn(run func(context.Context, string, string, int64) (*github.WorkflowJob, *github.Response, error)) *GithubClient_GetWorkflowJobByID_Call {
_c.Call.Return(run)
return _c
}
// GithubBaseURL provides a mock function with no fields
func (_m *GithubClient) GithubBaseURL() *url.URL {
ret := _m.Called()
@ -263,6 +532,33 @@ func (_m *GithubClient) GithubBaseURL() *url.URL {
return r0
}
// GithubClient_GithubBaseURL_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GithubBaseURL'
type GithubClient_GithubBaseURL_Call struct {
*mock.Call
}
// GithubBaseURL is a helper method to define mock.On call
func (_e *GithubClient_Expecter) GithubBaseURL() *GithubClient_GithubBaseURL_Call {
return &GithubClient_GithubBaseURL_Call{Call: _e.mock.On("GithubBaseURL")}
}
func (_c *GithubClient_GithubBaseURL_Call) Run(run func()) *GithubClient_GithubBaseURL_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *GithubClient_GithubBaseURL_Call) Return(_a0 *url.URL) *GithubClient_GithubBaseURL_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *GithubClient_GithubBaseURL_Call) RunAndReturn(run func() *url.URL) *GithubClient_GithubBaseURL_Call {
_c.Call.Return(run)
return _c
}
// ListEntityHooks provides a mock function with given fields: ctx, opts
func (_m *GithubClient) ListEntityHooks(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, opts)
@ -302,6 +598,35 @@ func (_m *GithubClient) ListEntityHooks(ctx context.Context, opts *github.ListOp
return r0, r1, r2
}
// GithubClient_ListEntityHooks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEntityHooks'
type GithubClient_ListEntityHooks_Call struct {
*mock.Call
}
// ListEntityHooks is a helper method to define mock.On call
// - ctx context.Context
// - opts *github.ListOptions
func (_e *GithubClient_Expecter) ListEntityHooks(ctx interface{}, opts interface{}) *GithubClient_ListEntityHooks_Call {
return &GithubClient_ListEntityHooks_Call{Call: _e.mock.On("ListEntityHooks", ctx, opts)}
}
func (_c *GithubClient_ListEntityHooks_Call) Run(run func(ctx context.Context, opts *github.ListOptions)) *GithubClient_ListEntityHooks_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*github.ListOptions))
})
return _c
}
func (_c *GithubClient_ListEntityHooks_Call) Return(ret []*github.Hook, response *github.Response, err error) *GithubClient_ListEntityHooks_Call {
_c.Call.Return(ret, response, err)
return _c
}
func (_c *GithubClient_ListEntityHooks_Call) RunAndReturn(run func(context.Context, *github.ListOptions) ([]*github.Hook, *github.Response, error)) *GithubClient_ListEntityHooks_Call {
_c.Call.Return(run)
return _c
}
// ListEntityRunnerApplicationDownloads provides a mock function with given fields: ctx
func (_m *GithubClient) ListEntityRunnerApplicationDownloads(ctx context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx)
@ -341,6 +666,34 @@ func (_m *GithubClient) ListEntityRunnerApplicationDownloads(ctx context.Context
return r0, r1, r2
}
// GithubClient_ListEntityRunnerApplicationDownloads_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEntityRunnerApplicationDownloads'
type GithubClient_ListEntityRunnerApplicationDownloads_Call struct {
*mock.Call
}
// ListEntityRunnerApplicationDownloads is a helper method to define mock.On call
// - ctx context.Context
func (_e *GithubClient_Expecter) ListEntityRunnerApplicationDownloads(ctx interface{}) *GithubClient_ListEntityRunnerApplicationDownloads_Call {
return &GithubClient_ListEntityRunnerApplicationDownloads_Call{Call: _e.mock.On("ListEntityRunnerApplicationDownloads", ctx)}
}
func (_c *GithubClient_ListEntityRunnerApplicationDownloads_Call) Run(run func(ctx context.Context)) *GithubClient_ListEntityRunnerApplicationDownloads_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *GithubClient_ListEntityRunnerApplicationDownloads_Call) Return(_a0 []*github.RunnerApplicationDownload, _a1 *github.Response, _a2 error) *GithubClient_ListEntityRunnerApplicationDownloads_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *GithubClient_ListEntityRunnerApplicationDownloads_Call) RunAndReturn(run func(context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error)) *GithubClient_ListEntityRunnerApplicationDownloads_Call {
_c.Call.Return(run)
return _c
}
// ListEntityRunners provides a mock function with given fields: ctx, opts
func (_m *GithubClient) ListEntityRunners(ctx context.Context, opts *github.ListRunnersOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, opts)
@ -380,6 +733,35 @@ func (_m *GithubClient) ListEntityRunners(ctx context.Context, opts *github.List
return r0, r1, r2
}
// GithubClient_ListEntityRunners_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEntityRunners'
type GithubClient_ListEntityRunners_Call struct {
*mock.Call
}
// ListEntityRunners is a helper method to define mock.On call
// - ctx context.Context
// - opts *github.ListRunnersOptions
func (_e *GithubClient_Expecter) ListEntityRunners(ctx interface{}, opts interface{}) *GithubClient_ListEntityRunners_Call {
return &GithubClient_ListEntityRunners_Call{Call: _e.mock.On("ListEntityRunners", ctx, opts)}
}
func (_c *GithubClient_ListEntityRunners_Call) Run(run func(ctx context.Context, opts *github.ListRunnersOptions)) *GithubClient_ListEntityRunners_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*github.ListRunnersOptions))
})
return _c
}
func (_c *GithubClient_ListEntityRunners_Call) Return(_a0 *github.Runners, _a1 *github.Response, _a2 error) *GithubClient_ListEntityRunners_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *GithubClient_ListEntityRunners_Call) RunAndReturn(run func(context.Context, *github.ListRunnersOptions) (*github.Runners, *github.Response, error)) *GithubClient_ListEntityRunners_Call {
_c.Call.Return(run)
return _c
}
// PingEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubClient) PingEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
@ -410,6 +792,35 @@ func (_m *GithubClient) PingEntityHook(ctx context.Context, id int64) (*github.R
return r0, r1
}
// GithubClient_PingEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PingEntityHook'
type GithubClient_PingEntityHook_Call struct {
*mock.Call
}
// PingEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - id int64
func (_e *GithubClient_Expecter) PingEntityHook(ctx interface{}, id interface{}) *GithubClient_PingEntityHook_Call {
return &GithubClient_PingEntityHook_Call{Call: _e.mock.On("PingEntityHook", ctx, id)}
}
func (_c *GithubClient_PingEntityHook_Call) Run(run func(ctx context.Context, id int64)) *GithubClient_PingEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubClient_PingEntityHook_Call) Return(ret *github.Response, err error) *GithubClient_PingEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubClient_PingEntityHook_Call) RunAndReturn(run func(context.Context, int64) (*github.Response, error)) *GithubClient_PingEntityHook_Call {
_c.Call.Return(run)
return _c
}
// RateLimit provides a mock function with given fields: ctx
func (_m *GithubClient) RateLimit(ctx context.Context) (*github.RateLimits, error) {
ret := _m.Called(ctx)
@ -440,6 +851,34 @@ func (_m *GithubClient) RateLimit(ctx context.Context) (*github.RateLimits, erro
return r0, r1
}
// GithubClient_RateLimit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RateLimit'
type GithubClient_RateLimit_Call struct {
*mock.Call
}
// RateLimit is a helper method to define mock.On call
// - ctx context.Context
func (_e *GithubClient_Expecter) RateLimit(ctx interface{}) *GithubClient_RateLimit_Call {
return &GithubClient_RateLimit_Call{Call: _e.mock.On("RateLimit", ctx)}
}
func (_c *GithubClient_RateLimit_Call) Run(run func(ctx context.Context)) *GithubClient_RateLimit_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *GithubClient_RateLimit_Call) Return(_a0 *github.RateLimits, _a1 error) *GithubClient_RateLimit_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *GithubClient_RateLimit_Call) RunAndReturn(run func(context.Context) (*github.RateLimits, error)) *GithubClient_RateLimit_Call {
_c.Call.Return(run)
return _c
}
// RemoveEntityRunner provides a mock function with given fields: ctx, runnerID
func (_m *GithubClient) RemoveEntityRunner(ctx context.Context, runnerID int64) error {
ret := _m.Called(ctx, runnerID)
@ -458,6 +897,35 @@ func (_m *GithubClient) RemoveEntityRunner(ctx context.Context, runnerID int64)
return r0
}
// GithubClient_RemoveEntityRunner_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveEntityRunner'
type GithubClient_RemoveEntityRunner_Call struct {
*mock.Call
}
// RemoveEntityRunner is a helper method to define mock.On call
// - ctx context.Context
// - runnerID int64
func (_e *GithubClient_Expecter) RemoveEntityRunner(ctx interface{}, runnerID interface{}) *GithubClient_RemoveEntityRunner_Call {
return &GithubClient_RemoveEntityRunner_Call{Call: _e.mock.On("RemoveEntityRunner", ctx, runnerID)}
}
func (_c *GithubClient_RemoveEntityRunner_Call) Run(run func(ctx context.Context, runnerID int64)) *GithubClient_RemoveEntityRunner_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubClient_RemoveEntityRunner_Call) Return(_a0 error) *GithubClient_RemoveEntityRunner_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *GithubClient_RemoveEntityRunner_Call) RunAndReturn(run func(context.Context, int64) error) *GithubClient_RemoveEntityRunner_Call {
_c.Call.Return(run)
return _c
}
// NewGithubClient creates a new instance of GithubClient. 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 NewGithubClient(t interface {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.53.3. DO NOT EDIT.
// Code generated by mockery. DO NOT EDIT.
package mocks
@ -18,6 +18,14 @@ type GithubEntityOperations struct {
mock.Mock
}
type GithubEntityOperations_Expecter struct {
mock *mock.Mock
}
func (_m *GithubEntityOperations) EXPECT() *GithubEntityOperations_Expecter {
return &GithubEntityOperations_Expecter{mock: &_m.Mock}
}
// CreateEntityHook provides a mock function with given fields: ctx, hook
func (_m *GithubEntityOperations) CreateEntityHook(ctx context.Context, hook *github.Hook) (*github.Hook, error) {
ret := _m.Called(ctx, hook)
@ -48,6 +56,35 @@ func (_m *GithubEntityOperations) CreateEntityHook(ctx context.Context, hook *gi
return r0, r1
}
// GithubEntityOperations_CreateEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateEntityHook'
type GithubEntityOperations_CreateEntityHook_Call struct {
*mock.Call
}
// CreateEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - hook *github.Hook
func (_e *GithubEntityOperations_Expecter) CreateEntityHook(ctx interface{}, hook interface{}) *GithubEntityOperations_CreateEntityHook_Call {
return &GithubEntityOperations_CreateEntityHook_Call{Call: _e.mock.On("CreateEntityHook", ctx, hook)}
}
func (_c *GithubEntityOperations_CreateEntityHook_Call) Run(run func(ctx context.Context, hook *github.Hook)) *GithubEntityOperations_CreateEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*github.Hook))
})
return _c
}
func (_c *GithubEntityOperations_CreateEntityHook_Call) Return(ret *github.Hook, err error) *GithubEntityOperations_CreateEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubEntityOperations_CreateEntityHook_Call) RunAndReturn(run func(context.Context, *github.Hook) (*github.Hook, error)) *GithubEntityOperations_CreateEntityHook_Call {
_c.Call.Return(run)
return _c
}
// CreateEntityRegistrationToken provides a mock function with given fields: ctx
func (_m *GithubEntityOperations) CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error) {
ret := _m.Called(ctx)
@ -87,6 +124,34 @@ func (_m *GithubEntityOperations) CreateEntityRegistrationToken(ctx context.Cont
return r0, r1, r2
}
// GithubEntityOperations_CreateEntityRegistrationToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateEntityRegistrationToken'
type GithubEntityOperations_CreateEntityRegistrationToken_Call struct {
*mock.Call
}
// CreateEntityRegistrationToken is a helper method to define mock.On call
// - ctx context.Context
func (_e *GithubEntityOperations_Expecter) CreateEntityRegistrationToken(ctx interface{}) *GithubEntityOperations_CreateEntityRegistrationToken_Call {
return &GithubEntityOperations_CreateEntityRegistrationToken_Call{Call: _e.mock.On("CreateEntityRegistrationToken", ctx)}
}
func (_c *GithubEntityOperations_CreateEntityRegistrationToken_Call) Run(run func(ctx context.Context)) *GithubEntityOperations_CreateEntityRegistrationToken_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *GithubEntityOperations_CreateEntityRegistrationToken_Call) Return(_a0 *github.RegistrationToken, _a1 *github.Response, _a2 error) *GithubEntityOperations_CreateEntityRegistrationToken_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *GithubEntityOperations_CreateEntityRegistrationToken_Call) RunAndReturn(run func(context.Context) (*github.RegistrationToken, *github.Response, error)) *GithubEntityOperations_CreateEntityRegistrationToken_Call {
_c.Call.Return(run)
return _c
}
// DeleteEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubEntityOperations) DeleteEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
@ -117,6 +182,35 @@ func (_m *GithubEntityOperations) DeleteEntityHook(ctx context.Context, id int64
return r0, r1
}
// GithubEntityOperations_DeleteEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteEntityHook'
type GithubEntityOperations_DeleteEntityHook_Call struct {
*mock.Call
}
// DeleteEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - id int64
func (_e *GithubEntityOperations_Expecter) DeleteEntityHook(ctx interface{}, id interface{}) *GithubEntityOperations_DeleteEntityHook_Call {
return &GithubEntityOperations_DeleteEntityHook_Call{Call: _e.mock.On("DeleteEntityHook", ctx, id)}
}
func (_c *GithubEntityOperations_DeleteEntityHook_Call) Run(run func(ctx context.Context, id int64)) *GithubEntityOperations_DeleteEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubEntityOperations_DeleteEntityHook_Call) Return(ret *github.Response, err error) *GithubEntityOperations_DeleteEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubEntityOperations_DeleteEntityHook_Call) RunAndReturn(run func(context.Context, int64) (*github.Response, error)) *GithubEntityOperations_DeleteEntityHook_Call {
_c.Call.Return(run)
return _c
}
// GetEntity provides a mock function with no fields
func (_m *GithubEntityOperations) GetEntity() params.ForgeEntity {
ret := _m.Called()
@ -135,6 +229,33 @@ func (_m *GithubEntityOperations) GetEntity() params.ForgeEntity {
return r0
}
// GithubEntityOperations_GetEntity_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntity'
type GithubEntityOperations_GetEntity_Call struct {
*mock.Call
}
// GetEntity is a helper method to define mock.On call
func (_e *GithubEntityOperations_Expecter) GetEntity() *GithubEntityOperations_GetEntity_Call {
return &GithubEntityOperations_GetEntity_Call{Call: _e.mock.On("GetEntity")}
}
func (_c *GithubEntityOperations_GetEntity_Call) Run(run func()) *GithubEntityOperations_GetEntity_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *GithubEntityOperations_GetEntity_Call) Return(_a0 params.ForgeEntity) *GithubEntityOperations_GetEntity_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *GithubEntityOperations_GetEntity_Call) RunAndReturn(run func() params.ForgeEntity) *GithubEntityOperations_GetEntity_Call {
_c.Call.Return(run)
return _c
}
// GetEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubEntityOperations) GetEntityHook(ctx context.Context, id int64) (*github.Hook, error) {
ret := _m.Called(ctx, id)
@ -165,6 +286,35 @@ func (_m *GithubEntityOperations) GetEntityHook(ctx context.Context, id int64) (
return r0, r1
}
// GithubEntityOperations_GetEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntityHook'
type GithubEntityOperations_GetEntityHook_Call struct {
*mock.Call
}
// GetEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - id int64
func (_e *GithubEntityOperations_Expecter) GetEntityHook(ctx interface{}, id interface{}) *GithubEntityOperations_GetEntityHook_Call {
return &GithubEntityOperations_GetEntityHook_Call{Call: _e.mock.On("GetEntityHook", ctx, id)}
}
func (_c *GithubEntityOperations_GetEntityHook_Call) Run(run func(ctx context.Context, id int64)) *GithubEntityOperations_GetEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubEntityOperations_GetEntityHook_Call) Return(ret *github.Hook, err error) *GithubEntityOperations_GetEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubEntityOperations_GetEntityHook_Call) RunAndReturn(run func(context.Context, int64) (*github.Hook, error)) *GithubEntityOperations_GetEntityHook_Call {
_c.Call.Return(run)
return _c
}
// GetEntityJITConfig provides a mock function with given fields: ctx, instance, pool, labels
func (_m *GithubEntityOperations) GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (map[string]string, *github.Runner, error) {
ret := _m.Called(ctx, instance, pool, labels)
@ -204,6 +354,94 @@ func (_m *GithubEntityOperations) GetEntityJITConfig(ctx context.Context, instan
return r0, r1, r2
}
// GithubEntityOperations_GetEntityJITConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntityJITConfig'
type GithubEntityOperations_GetEntityJITConfig_Call struct {
*mock.Call
}
// GetEntityJITConfig is a helper method to define mock.On call
// - ctx context.Context
// - instance string
// - pool params.Pool
// - labels []string
func (_e *GithubEntityOperations_Expecter) GetEntityJITConfig(ctx interface{}, instance interface{}, pool interface{}, labels interface{}) *GithubEntityOperations_GetEntityJITConfig_Call {
return &GithubEntityOperations_GetEntityJITConfig_Call{Call: _e.mock.On("GetEntityJITConfig", ctx, instance, pool, labels)}
}
func (_c *GithubEntityOperations_GetEntityJITConfig_Call) Run(run func(ctx context.Context, instance string, pool params.Pool, labels []string)) *GithubEntityOperations_GetEntityJITConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(params.Pool), args[3].([]string))
})
return _c
}
func (_c *GithubEntityOperations_GetEntityJITConfig_Call) Return(jitConfigMap map[string]string, runner *github.Runner, err error) *GithubEntityOperations_GetEntityJITConfig_Call {
_c.Call.Return(jitConfigMap, runner, err)
return _c
}
func (_c *GithubEntityOperations_GetEntityJITConfig_Call) RunAndReturn(run func(context.Context, string, params.Pool, []string) (map[string]string, *github.Runner, error)) *GithubEntityOperations_GetEntityJITConfig_Call {
_c.Call.Return(run)
return _c
}
// GetEntityRunnerGroupIDByName provides a mock function with given fields: ctx, runnerGroupName
func (_m *GithubEntityOperations) GetEntityRunnerGroupIDByName(ctx context.Context, runnerGroupName string) (int64, error) {
ret := _m.Called(ctx, runnerGroupName)
if len(ret) == 0 {
panic("no return value specified for GetEntityRunnerGroupIDByName")
}
var r0 int64
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (int64, error)); ok {
return rf(ctx, runnerGroupName)
}
if rf, ok := ret.Get(0).(func(context.Context, string) int64); ok {
r0 = rf(ctx, runnerGroupName)
} else {
r0 = ret.Get(0).(int64)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, runnerGroupName)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GithubEntityOperations_GetEntityRunnerGroupIDByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEntityRunnerGroupIDByName'
type GithubEntityOperations_GetEntityRunnerGroupIDByName_Call struct {
*mock.Call
}
// GetEntityRunnerGroupIDByName is a helper method to define mock.On call
// - ctx context.Context
// - runnerGroupName string
func (_e *GithubEntityOperations_Expecter) GetEntityRunnerGroupIDByName(ctx interface{}, runnerGroupName interface{}) *GithubEntityOperations_GetEntityRunnerGroupIDByName_Call {
return &GithubEntityOperations_GetEntityRunnerGroupIDByName_Call{Call: _e.mock.On("GetEntityRunnerGroupIDByName", ctx, runnerGroupName)}
}
func (_c *GithubEntityOperations_GetEntityRunnerGroupIDByName_Call) Run(run func(ctx context.Context, runnerGroupName string)) *GithubEntityOperations_GetEntityRunnerGroupIDByName_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *GithubEntityOperations_GetEntityRunnerGroupIDByName_Call) Return(_a0 int64, _a1 error) *GithubEntityOperations_GetEntityRunnerGroupIDByName_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *GithubEntityOperations_GetEntityRunnerGroupIDByName_Call) RunAndReturn(run func(context.Context, string) (int64, error)) *GithubEntityOperations_GetEntityRunnerGroupIDByName_Call {
_c.Call.Return(run)
return _c
}
// GithubBaseURL provides a mock function with no fields
func (_m *GithubEntityOperations) GithubBaseURL() *url.URL {
ret := _m.Called()
@ -224,6 +462,33 @@ func (_m *GithubEntityOperations) GithubBaseURL() *url.URL {
return r0
}
// GithubEntityOperations_GithubBaseURL_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GithubBaseURL'
type GithubEntityOperations_GithubBaseURL_Call struct {
*mock.Call
}
// GithubBaseURL is a helper method to define mock.On call
func (_e *GithubEntityOperations_Expecter) GithubBaseURL() *GithubEntityOperations_GithubBaseURL_Call {
return &GithubEntityOperations_GithubBaseURL_Call{Call: _e.mock.On("GithubBaseURL")}
}
func (_c *GithubEntityOperations_GithubBaseURL_Call) Run(run func()) *GithubEntityOperations_GithubBaseURL_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *GithubEntityOperations_GithubBaseURL_Call) Return(_a0 *url.URL) *GithubEntityOperations_GithubBaseURL_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *GithubEntityOperations_GithubBaseURL_Call) RunAndReturn(run func() *url.URL) *GithubEntityOperations_GithubBaseURL_Call {
_c.Call.Return(run)
return _c
}
// ListEntityHooks provides a mock function with given fields: ctx, opts
func (_m *GithubEntityOperations) ListEntityHooks(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
ret := _m.Called(ctx, opts)
@ -263,6 +528,35 @@ func (_m *GithubEntityOperations) ListEntityHooks(ctx context.Context, opts *git
return r0, r1, r2
}
// GithubEntityOperations_ListEntityHooks_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEntityHooks'
type GithubEntityOperations_ListEntityHooks_Call struct {
*mock.Call
}
// ListEntityHooks is a helper method to define mock.On call
// - ctx context.Context
// - opts *github.ListOptions
func (_e *GithubEntityOperations_Expecter) ListEntityHooks(ctx interface{}, opts interface{}) *GithubEntityOperations_ListEntityHooks_Call {
return &GithubEntityOperations_ListEntityHooks_Call{Call: _e.mock.On("ListEntityHooks", ctx, opts)}
}
func (_c *GithubEntityOperations_ListEntityHooks_Call) Run(run func(ctx context.Context, opts *github.ListOptions)) *GithubEntityOperations_ListEntityHooks_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*github.ListOptions))
})
return _c
}
func (_c *GithubEntityOperations_ListEntityHooks_Call) Return(ret []*github.Hook, response *github.Response, err error) *GithubEntityOperations_ListEntityHooks_Call {
_c.Call.Return(ret, response, err)
return _c
}
func (_c *GithubEntityOperations_ListEntityHooks_Call) RunAndReturn(run func(context.Context, *github.ListOptions) ([]*github.Hook, *github.Response, error)) *GithubEntityOperations_ListEntityHooks_Call {
_c.Call.Return(run)
return _c
}
// ListEntityRunnerApplicationDownloads provides a mock function with given fields: ctx
func (_m *GithubEntityOperations) ListEntityRunnerApplicationDownloads(ctx context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error) {
ret := _m.Called(ctx)
@ -302,6 +596,34 @@ func (_m *GithubEntityOperations) ListEntityRunnerApplicationDownloads(ctx conte
return r0, r1, r2
}
// GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEntityRunnerApplicationDownloads'
type GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call struct {
*mock.Call
}
// ListEntityRunnerApplicationDownloads is a helper method to define mock.On call
// - ctx context.Context
func (_e *GithubEntityOperations_Expecter) ListEntityRunnerApplicationDownloads(ctx interface{}) *GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call {
return &GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call{Call: _e.mock.On("ListEntityRunnerApplicationDownloads", ctx)}
}
func (_c *GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call) Run(run func(ctx context.Context)) *GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call) Return(_a0 []*github.RunnerApplicationDownload, _a1 *github.Response, _a2 error) *GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call) RunAndReturn(run func(context.Context) ([]*github.RunnerApplicationDownload, *github.Response, error)) *GithubEntityOperations_ListEntityRunnerApplicationDownloads_Call {
_c.Call.Return(run)
return _c
}
// ListEntityRunners provides a mock function with given fields: ctx, opts
func (_m *GithubEntityOperations) ListEntityRunners(ctx context.Context, opts *github.ListRunnersOptions) (*github.Runners, *github.Response, error) {
ret := _m.Called(ctx, opts)
@ -341,6 +663,35 @@ func (_m *GithubEntityOperations) ListEntityRunners(ctx context.Context, opts *g
return r0, r1, r2
}
// GithubEntityOperations_ListEntityRunners_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEntityRunners'
type GithubEntityOperations_ListEntityRunners_Call struct {
*mock.Call
}
// ListEntityRunners is a helper method to define mock.On call
// - ctx context.Context
// - opts *github.ListRunnersOptions
func (_e *GithubEntityOperations_Expecter) ListEntityRunners(ctx interface{}, opts interface{}) *GithubEntityOperations_ListEntityRunners_Call {
return &GithubEntityOperations_ListEntityRunners_Call{Call: _e.mock.On("ListEntityRunners", ctx, opts)}
}
func (_c *GithubEntityOperations_ListEntityRunners_Call) Run(run func(ctx context.Context, opts *github.ListRunnersOptions)) *GithubEntityOperations_ListEntityRunners_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*github.ListRunnersOptions))
})
return _c
}
func (_c *GithubEntityOperations_ListEntityRunners_Call) Return(_a0 *github.Runners, _a1 *github.Response, _a2 error) *GithubEntityOperations_ListEntityRunners_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *GithubEntityOperations_ListEntityRunners_Call) RunAndReturn(run func(context.Context, *github.ListRunnersOptions) (*github.Runners, *github.Response, error)) *GithubEntityOperations_ListEntityRunners_Call {
_c.Call.Return(run)
return _c
}
// PingEntityHook provides a mock function with given fields: ctx, id
func (_m *GithubEntityOperations) PingEntityHook(ctx context.Context, id int64) (*github.Response, error) {
ret := _m.Called(ctx, id)
@ -371,6 +722,35 @@ func (_m *GithubEntityOperations) PingEntityHook(ctx context.Context, id int64)
return r0, r1
}
// GithubEntityOperations_PingEntityHook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PingEntityHook'
type GithubEntityOperations_PingEntityHook_Call struct {
*mock.Call
}
// PingEntityHook is a helper method to define mock.On call
// - ctx context.Context
// - id int64
func (_e *GithubEntityOperations_Expecter) PingEntityHook(ctx interface{}, id interface{}) *GithubEntityOperations_PingEntityHook_Call {
return &GithubEntityOperations_PingEntityHook_Call{Call: _e.mock.On("PingEntityHook", ctx, id)}
}
func (_c *GithubEntityOperations_PingEntityHook_Call) Run(run func(ctx context.Context, id int64)) *GithubEntityOperations_PingEntityHook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubEntityOperations_PingEntityHook_Call) Return(ret *github.Response, err error) *GithubEntityOperations_PingEntityHook_Call {
_c.Call.Return(ret, err)
return _c
}
func (_c *GithubEntityOperations_PingEntityHook_Call) RunAndReturn(run func(context.Context, int64) (*github.Response, error)) *GithubEntityOperations_PingEntityHook_Call {
_c.Call.Return(run)
return _c
}
// RateLimit provides a mock function with given fields: ctx
func (_m *GithubEntityOperations) RateLimit(ctx context.Context) (*github.RateLimits, error) {
ret := _m.Called(ctx)
@ -401,6 +781,34 @@ func (_m *GithubEntityOperations) RateLimit(ctx context.Context) (*github.RateLi
return r0, r1
}
// GithubEntityOperations_RateLimit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RateLimit'
type GithubEntityOperations_RateLimit_Call struct {
*mock.Call
}
// RateLimit is a helper method to define mock.On call
// - ctx context.Context
func (_e *GithubEntityOperations_Expecter) RateLimit(ctx interface{}) *GithubEntityOperations_RateLimit_Call {
return &GithubEntityOperations_RateLimit_Call{Call: _e.mock.On("RateLimit", ctx)}
}
func (_c *GithubEntityOperations_RateLimit_Call) Run(run func(ctx context.Context)) *GithubEntityOperations_RateLimit_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *GithubEntityOperations_RateLimit_Call) Return(_a0 *github.RateLimits, _a1 error) *GithubEntityOperations_RateLimit_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *GithubEntityOperations_RateLimit_Call) RunAndReturn(run func(context.Context) (*github.RateLimits, error)) *GithubEntityOperations_RateLimit_Call {
_c.Call.Return(run)
return _c
}
// RemoveEntityRunner provides a mock function with given fields: ctx, runnerID
func (_m *GithubEntityOperations) RemoveEntityRunner(ctx context.Context, runnerID int64) error {
ret := _m.Called(ctx, runnerID)
@ -419,6 +827,35 @@ func (_m *GithubEntityOperations) RemoveEntityRunner(ctx context.Context, runner
return r0
}
// GithubEntityOperations_RemoveEntityRunner_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveEntityRunner'
type GithubEntityOperations_RemoveEntityRunner_Call struct {
*mock.Call
}
// RemoveEntityRunner is a helper method to define mock.On call
// - ctx context.Context
// - runnerID int64
func (_e *GithubEntityOperations_Expecter) RemoveEntityRunner(ctx interface{}, runnerID interface{}) *GithubEntityOperations_RemoveEntityRunner_Call {
return &GithubEntityOperations_RemoveEntityRunner_Call{Call: _e.mock.On("RemoveEntityRunner", ctx, runnerID)}
}
func (_c *GithubEntityOperations_RemoveEntityRunner_Call) Run(run func(ctx context.Context, runnerID int64)) *GithubEntityOperations_RemoveEntityRunner_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(int64))
})
return _c
}
func (_c *GithubEntityOperations_RemoveEntityRunner_Call) Return(_a0 error) *GithubEntityOperations_RemoveEntityRunner_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *GithubEntityOperations_RemoveEntityRunner_Call) RunAndReturn(run func(context.Context, int64) error) *GithubEntityOperations_RemoveEntityRunner_Call {
_c.Call.Return(run)
return _c
}
// NewGithubEntityOperations creates a new instance of GithubEntityOperations. 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 NewGithubEntityOperations(t interface {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.53.3. DO NOT EDIT.
// Code generated by mockery. DO NOT EDIT.
package mocks
@ -14,6 +14,14 @@ type PoolManager struct {
mock.Mock
}
type PoolManager_Expecter struct {
mock *mock.Mock
}
func (_m *PoolManager) EXPECT() *PoolManager_Expecter {
return &PoolManager_Expecter{mock: &_m.Mock}
}
// GetWebhookInfo provides a mock function with given fields: ctx
func (_m *PoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, error) {
ret := _m.Called(ctx)
@ -42,6 +50,34 @@ func (_m *PoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, err
return r0, r1
}
// PoolManager_GetWebhookInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWebhookInfo'
type PoolManager_GetWebhookInfo_Call struct {
*mock.Call
}
// GetWebhookInfo is a helper method to define mock.On call
// - ctx context.Context
func (_e *PoolManager_Expecter) GetWebhookInfo(ctx interface{}) *PoolManager_GetWebhookInfo_Call {
return &PoolManager_GetWebhookInfo_Call{Call: _e.mock.On("GetWebhookInfo", ctx)}
}
func (_c *PoolManager_GetWebhookInfo_Call) Run(run func(ctx context.Context)) *PoolManager_GetWebhookInfo_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *PoolManager_GetWebhookInfo_Call) Return(_a0 params.HookInfo, _a1 error) *PoolManager_GetWebhookInfo_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManager_GetWebhookInfo_Call) RunAndReturn(run func(context.Context) (params.HookInfo, error)) *PoolManager_GetWebhookInfo_Call {
_c.Call.Return(run)
return _c
}
// GithubRunnerRegistrationToken provides a mock function with no fields
func (_m *PoolManager) GithubRunnerRegistrationToken() (string, error) {
ret := _m.Called()
@ -70,6 +106,33 @@ func (_m *PoolManager) GithubRunnerRegistrationToken() (string, error) {
return r0, r1
}
// PoolManager_GithubRunnerRegistrationToken_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GithubRunnerRegistrationToken'
type PoolManager_GithubRunnerRegistrationToken_Call struct {
*mock.Call
}
// GithubRunnerRegistrationToken is a helper method to define mock.On call
func (_e *PoolManager_Expecter) GithubRunnerRegistrationToken() *PoolManager_GithubRunnerRegistrationToken_Call {
return &PoolManager_GithubRunnerRegistrationToken_Call{Call: _e.mock.On("GithubRunnerRegistrationToken")}
}
func (_c *PoolManager_GithubRunnerRegistrationToken_Call) Run(run func()) *PoolManager_GithubRunnerRegistrationToken_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_GithubRunnerRegistrationToken_Call) Return(_a0 string, _a1 error) *PoolManager_GithubRunnerRegistrationToken_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManager_GithubRunnerRegistrationToken_Call) RunAndReturn(run func() (string, error)) *PoolManager_GithubRunnerRegistrationToken_Call {
_c.Call.Return(run)
return _c
}
// HandleWorkflowJob provides a mock function with given fields: job
func (_m *PoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
ret := _m.Called(job)
@ -88,6 +151,34 @@ func (_m *PoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
return r0
}
// PoolManager_HandleWorkflowJob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HandleWorkflowJob'
type PoolManager_HandleWorkflowJob_Call struct {
*mock.Call
}
// HandleWorkflowJob is a helper method to define mock.On call
// - job params.WorkflowJob
func (_e *PoolManager_Expecter) HandleWorkflowJob(job interface{}) *PoolManager_HandleWorkflowJob_Call {
return &PoolManager_HandleWorkflowJob_Call{Call: _e.mock.On("HandleWorkflowJob", job)}
}
func (_c *PoolManager_HandleWorkflowJob_Call) Run(run func(job params.WorkflowJob)) *PoolManager_HandleWorkflowJob_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(params.WorkflowJob))
})
return _c
}
func (_c *PoolManager_HandleWorkflowJob_Call) Return(_a0 error) *PoolManager_HandleWorkflowJob_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_HandleWorkflowJob_Call) RunAndReturn(run func(params.WorkflowJob) error) *PoolManager_HandleWorkflowJob_Call {
_c.Call.Return(run)
return _c
}
// ID provides a mock function with no fields
func (_m *PoolManager) ID() string {
ret := _m.Called()
@ -106,6 +197,33 @@ func (_m *PoolManager) ID() string {
return r0
}
// PoolManager_ID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ID'
type PoolManager_ID_Call struct {
*mock.Call
}
// ID is a helper method to define mock.On call
func (_e *PoolManager_Expecter) ID() *PoolManager_ID_Call {
return &PoolManager_ID_Call{Call: _e.mock.On("ID")}
}
func (_c *PoolManager_ID_Call) Run(run func()) *PoolManager_ID_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_ID_Call) Return(_a0 string) *PoolManager_ID_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_ID_Call) RunAndReturn(run func() string) *PoolManager_ID_Call {
_c.Call.Return(run)
return _c
}
// InstallWebhook provides a mock function with given fields: ctx, param
func (_m *PoolManager) InstallWebhook(ctx context.Context, param params.InstallWebhookParams) (params.HookInfo, error) {
ret := _m.Called(ctx, param)
@ -134,6 +252,35 @@ func (_m *PoolManager) InstallWebhook(ctx context.Context, param params.InstallW
return r0, r1
}
// PoolManager_InstallWebhook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InstallWebhook'
type PoolManager_InstallWebhook_Call struct {
*mock.Call
}
// InstallWebhook is a helper method to define mock.On call
// - ctx context.Context
// - param params.InstallWebhookParams
func (_e *PoolManager_Expecter) InstallWebhook(ctx interface{}, param interface{}) *PoolManager_InstallWebhook_Call {
return &PoolManager_InstallWebhook_Call{Call: _e.mock.On("InstallWebhook", ctx, param)}
}
func (_c *PoolManager_InstallWebhook_Call) Run(run func(ctx context.Context, param params.InstallWebhookParams)) *PoolManager_InstallWebhook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(params.InstallWebhookParams))
})
return _c
}
func (_c *PoolManager_InstallWebhook_Call) Return(_a0 params.HookInfo, _a1 error) *PoolManager_InstallWebhook_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManager_InstallWebhook_Call) RunAndReturn(run func(context.Context, params.InstallWebhookParams) (params.HookInfo, error)) *PoolManager_InstallWebhook_Call {
_c.Call.Return(run)
return _c
}
// RootCABundle provides a mock function with no fields
func (_m *PoolManager) RootCABundle() (params.CertificateBundle, error) {
ret := _m.Called()
@ -162,11 +309,67 @@ func (_m *PoolManager) RootCABundle() (params.CertificateBundle, error) {
return r0, r1
}
// PoolManager_RootCABundle_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RootCABundle'
type PoolManager_RootCABundle_Call struct {
*mock.Call
}
// RootCABundle is a helper method to define mock.On call
func (_e *PoolManager_Expecter) RootCABundle() *PoolManager_RootCABundle_Call {
return &PoolManager_RootCABundle_Call{Call: _e.mock.On("RootCABundle")}
}
func (_c *PoolManager_RootCABundle_Call) Run(run func()) *PoolManager_RootCABundle_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_RootCABundle_Call) Return(_a0 params.CertificateBundle, _a1 error) *PoolManager_RootCABundle_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManager_RootCABundle_Call) RunAndReturn(run func() (params.CertificateBundle, error)) *PoolManager_RootCABundle_Call {
_c.Call.Return(run)
return _c
}
// SetPoolRunningState provides a mock function with given fields: isRunning, failureReason
func (_m *PoolManager) SetPoolRunningState(isRunning bool, failureReason string) {
_m.Called(isRunning, failureReason)
}
// PoolManager_SetPoolRunningState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPoolRunningState'
type PoolManager_SetPoolRunningState_Call struct {
*mock.Call
}
// SetPoolRunningState is a helper method to define mock.On call
// - isRunning bool
// - failureReason string
func (_e *PoolManager_Expecter) SetPoolRunningState(isRunning interface{}, failureReason interface{}) *PoolManager_SetPoolRunningState_Call {
return &PoolManager_SetPoolRunningState_Call{Call: _e.mock.On("SetPoolRunningState", isRunning, failureReason)}
}
func (_c *PoolManager_SetPoolRunningState_Call) Run(run func(isRunning bool, failureReason string)) *PoolManager_SetPoolRunningState_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(bool), args[1].(string))
})
return _c
}
func (_c *PoolManager_SetPoolRunningState_Call) Return() *PoolManager_SetPoolRunningState_Call {
_c.Call.Return()
return _c
}
func (_c *PoolManager_SetPoolRunningState_Call) RunAndReturn(run func(bool, string)) *PoolManager_SetPoolRunningState_Call {
_c.Run(run)
return _c
}
// Start provides a mock function with no fields
func (_m *PoolManager) Start() error {
ret := _m.Called()
@ -185,6 +388,33 @@ func (_m *PoolManager) Start() error {
return r0
}
// PoolManager_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
type PoolManager_Start_Call struct {
*mock.Call
}
// Start is a helper method to define mock.On call
func (_e *PoolManager_Expecter) Start() *PoolManager_Start_Call {
return &PoolManager_Start_Call{Call: _e.mock.On("Start")}
}
func (_c *PoolManager_Start_Call) Run(run func()) *PoolManager_Start_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_Start_Call) Return(_a0 error) *PoolManager_Start_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_Start_Call) RunAndReturn(run func() error) *PoolManager_Start_Call {
_c.Call.Return(run)
return _c
}
// Status provides a mock function with no fields
func (_m *PoolManager) Status() params.PoolManagerStatus {
ret := _m.Called()
@ -203,6 +433,33 @@ func (_m *PoolManager) Status() params.PoolManagerStatus {
return r0
}
// PoolManager_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status'
type PoolManager_Status_Call struct {
*mock.Call
}
// Status is a helper method to define mock.On call
func (_e *PoolManager_Expecter) Status() *PoolManager_Status_Call {
return &PoolManager_Status_Call{Call: _e.mock.On("Status")}
}
func (_c *PoolManager_Status_Call) Run(run func()) *PoolManager_Status_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_Status_Call) Return(_a0 params.PoolManagerStatus) *PoolManager_Status_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_Status_Call) RunAndReturn(run func() params.PoolManagerStatus) *PoolManager_Status_Call {
_c.Call.Return(run)
return _c
}
// Stop provides a mock function with no fields
func (_m *PoolManager) Stop() error {
ret := _m.Called()
@ -221,6 +478,33 @@ func (_m *PoolManager) Stop() error {
return r0
}
// PoolManager_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
type PoolManager_Stop_Call struct {
*mock.Call
}
// Stop is a helper method to define mock.On call
func (_e *PoolManager_Expecter) Stop() *PoolManager_Stop_Call {
return &PoolManager_Stop_Call{Call: _e.mock.On("Stop")}
}
func (_c *PoolManager_Stop_Call) Run(run func()) *PoolManager_Stop_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_Stop_Call) Return(_a0 error) *PoolManager_Stop_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_Stop_Call) RunAndReturn(run func() error) *PoolManager_Stop_Call {
_c.Call.Return(run)
return _c
}
// UninstallWebhook provides a mock function with given fields: ctx
func (_m *PoolManager) UninstallWebhook(ctx context.Context) error {
ret := _m.Called(ctx)
@ -239,6 +523,34 @@ func (_m *PoolManager) UninstallWebhook(ctx context.Context) error {
return r0
}
// PoolManager_UninstallWebhook_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UninstallWebhook'
type PoolManager_UninstallWebhook_Call struct {
*mock.Call
}
// UninstallWebhook is a helper method to define mock.On call
// - ctx context.Context
func (_e *PoolManager_Expecter) UninstallWebhook(ctx interface{}) *PoolManager_UninstallWebhook_Call {
return &PoolManager_UninstallWebhook_Call{Call: _e.mock.On("UninstallWebhook", ctx)}
}
func (_c *PoolManager_UninstallWebhook_Call) Run(run func(ctx context.Context)) *PoolManager_UninstallWebhook_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *PoolManager_UninstallWebhook_Call) Return(_a0 error) *PoolManager_UninstallWebhook_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_UninstallWebhook_Call) RunAndReturn(run func(context.Context) error) *PoolManager_UninstallWebhook_Call {
_c.Call.Return(run)
return _c
}
// Wait provides a mock function with no fields
func (_m *PoolManager) Wait() error {
ret := _m.Called()
@ -257,6 +569,33 @@ func (_m *PoolManager) Wait() error {
return r0
}
// PoolManager_Wait_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Wait'
type PoolManager_Wait_Call struct {
*mock.Call
}
// Wait is a helper method to define mock.On call
func (_e *PoolManager_Expecter) Wait() *PoolManager_Wait_Call {
return &PoolManager_Wait_Call{Call: _e.mock.On("Wait")}
}
func (_c *PoolManager_Wait_Call) Run(run func()) *PoolManager_Wait_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_Wait_Call) Return(_a0 error) *PoolManager_Wait_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_Wait_Call) RunAndReturn(run func() error) *PoolManager_Wait_Call {
_c.Call.Return(run)
return _c
}
// WebhookSecret provides a mock function with no fields
func (_m *PoolManager) WebhookSecret() string {
ret := _m.Called()
@ -275,6 +614,33 @@ func (_m *PoolManager) WebhookSecret() string {
return r0
}
// PoolManager_WebhookSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WebhookSecret'
type PoolManager_WebhookSecret_Call struct {
*mock.Call
}
// WebhookSecret is a helper method to define mock.On call
func (_e *PoolManager_Expecter) WebhookSecret() *PoolManager_WebhookSecret_Call {
return &PoolManager_WebhookSecret_Call{Call: _e.mock.On("WebhookSecret")}
}
func (_c *PoolManager_WebhookSecret_Call) Run(run func()) *PoolManager_WebhookSecret_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManager_WebhookSecret_Call) Return(_a0 string) *PoolManager_WebhookSecret_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManager_WebhookSecret_Call) RunAndReturn(run func() string) *PoolManager_WebhookSecret_Call {
_c.Call.Return(run)
return _c
}
// NewPoolManager creates a new instance of PoolManager. 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 NewPoolManager(t interface {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.53.3. DO NOT EDIT.
// Code generated by mockery. DO NOT EDIT.
package mocks
@ -19,6 +19,14 @@ type Provider struct {
mock.Mock
}
type Provider_Expecter struct {
mock *mock.Mock
}
func (_m *Provider) EXPECT() *Provider_Expecter {
return &Provider_Expecter{mock: &_m.Mock}
}
// AsParams provides a mock function with no fields
func (_m *Provider) AsParams() params.Provider {
ret := _m.Called()
@ -37,6 +45,33 @@ func (_m *Provider) AsParams() params.Provider {
return r0
}
// Provider_AsParams_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AsParams'
type Provider_AsParams_Call struct {
*mock.Call
}
// AsParams is a helper method to define mock.On call
func (_e *Provider_Expecter) AsParams() *Provider_AsParams_Call {
return &Provider_AsParams_Call{Call: _e.mock.On("AsParams")}
}
func (_c *Provider_AsParams_Call) Run(run func()) *Provider_AsParams_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Provider_AsParams_Call) Return(_a0 params.Provider) *Provider_AsParams_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Provider_AsParams_Call) RunAndReturn(run func() params.Provider) *Provider_AsParams_Call {
_c.Call.Return(run)
return _c
}
// CreateInstance provides a mock function with given fields: ctx, bootstrapParams, createInstanceParams
func (_m *Provider) CreateInstance(ctx context.Context, bootstrapParams garm_provider_commonparams.BootstrapInstance, createInstanceParams common.CreateInstanceParams) (garm_provider_commonparams.ProviderInstance, error) {
ret := _m.Called(ctx, bootstrapParams, createInstanceParams)
@ -65,6 +100,36 @@ func (_m *Provider) CreateInstance(ctx context.Context, bootstrapParams garm_pro
return r0, r1
}
// Provider_CreateInstance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateInstance'
type Provider_CreateInstance_Call struct {
*mock.Call
}
// CreateInstance is a helper method to define mock.On call
// - ctx context.Context
// - bootstrapParams garm_provider_commonparams.BootstrapInstance
// - createInstanceParams common.CreateInstanceParams
func (_e *Provider_Expecter) CreateInstance(ctx interface{}, bootstrapParams interface{}, createInstanceParams interface{}) *Provider_CreateInstance_Call {
return &Provider_CreateInstance_Call{Call: _e.mock.On("CreateInstance", ctx, bootstrapParams, createInstanceParams)}
}
func (_c *Provider_CreateInstance_Call) Run(run func(ctx context.Context, bootstrapParams garm_provider_commonparams.BootstrapInstance, createInstanceParams common.CreateInstanceParams)) *Provider_CreateInstance_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(garm_provider_commonparams.BootstrapInstance), args[2].(common.CreateInstanceParams))
})
return _c
}
func (_c *Provider_CreateInstance_Call) Return(_a0 garm_provider_commonparams.ProviderInstance, _a1 error) *Provider_CreateInstance_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Provider_CreateInstance_Call) RunAndReturn(run func(context.Context, garm_provider_commonparams.BootstrapInstance, common.CreateInstanceParams) (garm_provider_commonparams.ProviderInstance, error)) *Provider_CreateInstance_Call {
_c.Call.Return(run)
return _c
}
// DeleteInstance provides a mock function with given fields: ctx, instance, deleteInstanceParams
func (_m *Provider) DeleteInstance(ctx context.Context, instance string, deleteInstanceParams common.DeleteInstanceParams) error {
ret := _m.Called(ctx, instance, deleteInstanceParams)
@ -83,6 +148,36 @@ func (_m *Provider) DeleteInstance(ctx context.Context, instance string, deleteI
return r0
}
// Provider_DeleteInstance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteInstance'
type Provider_DeleteInstance_Call struct {
*mock.Call
}
// DeleteInstance is a helper method to define mock.On call
// - ctx context.Context
// - instance string
// - deleteInstanceParams common.DeleteInstanceParams
func (_e *Provider_Expecter) DeleteInstance(ctx interface{}, instance interface{}, deleteInstanceParams interface{}) *Provider_DeleteInstance_Call {
return &Provider_DeleteInstance_Call{Call: _e.mock.On("DeleteInstance", ctx, instance, deleteInstanceParams)}
}
func (_c *Provider_DeleteInstance_Call) Run(run func(ctx context.Context, instance string, deleteInstanceParams common.DeleteInstanceParams)) *Provider_DeleteInstance_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(common.DeleteInstanceParams))
})
return _c
}
func (_c *Provider_DeleteInstance_Call) Return(_a0 error) *Provider_DeleteInstance_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Provider_DeleteInstance_Call) RunAndReturn(run func(context.Context, string, common.DeleteInstanceParams) error) *Provider_DeleteInstance_Call {
_c.Call.Return(run)
return _c
}
// DisableJITConfig provides a mock function with no fields
func (_m *Provider) DisableJITConfig() bool {
ret := _m.Called()
@ -101,6 +196,33 @@ func (_m *Provider) DisableJITConfig() bool {
return r0
}
// Provider_DisableJITConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisableJITConfig'
type Provider_DisableJITConfig_Call struct {
*mock.Call
}
// DisableJITConfig is a helper method to define mock.On call
func (_e *Provider_Expecter) DisableJITConfig() *Provider_DisableJITConfig_Call {
return &Provider_DisableJITConfig_Call{Call: _e.mock.On("DisableJITConfig")}
}
func (_c *Provider_DisableJITConfig_Call) Run(run func()) *Provider_DisableJITConfig_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Provider_DisableJITConfig_Call) Return(_a0 bool) *Provider_DisableJITConfig_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Provider_DisableJITConfig_Call) RunAndReturn(run func() bool) *Provider_DisableJITConfig_Call {
_c.Call.Return(run)
return _c
}
// GetInstance provides a mock function with given fields: ctx, instance, getInstanceParams
func (_m *Provider) GetInstance(ctx context.Context, instance string, getInstanceParams common.GetInstanceParams) (garm_provider_commonparams.ProviderInstance, error) {
ret := _m.Called(ctx, instance, getInstanceParams)
@ -129,6 +251,36 @@ func (_m *Provider) GetInstance(ctx context.Context, instance string, getInstanc
return r0, r1
}
// Provider_GetInstance_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInstance'
type Provider_GetInstance_Call struct {
*mock.Call
}
// GetInstance is a helper method to define mock.On call
// - ctx context.Context
// - instance string
// - getInstanceParams common.GetInstanceParams
func (_e *Provider_Expecter) GetInstance(ctx interface{}, instance interface{}, getInstanceParams interface{}) *Provider_GetInstance_Call {
return &Provider_GetInstance_Call{Call: _e.mock.On("GetInstance", ctx, instance, getInstanceParams)}
}
func (_c *Provider_GetInstance_Call) Run(run func(ctx context.Context, instance string, getInstanceParams common.GetInstanceParams)) *Provider_GetInstance_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(common.GetInstanceParams))
})
return _c
}
func (_c *Provider_GetInstance_Call) Return(_a0 garm_provider_commonparams.ProviderInstance, _a1 error) *Provider_GetInstance_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Provider_GetInstance_Call) RunAndReturn(run func(context.Context, string, common.GetInstanceParams) (garm_provider_commonparams.ProviderInstance, error)) *Provider_GetInstance_Call {
_c.Call.Return(run)
return _c
}
// ListInstances provides a mock function with given fields: ctx, poolID, listInstancesParams
func (_m *Provider) ListInstances(ctx context.Context, poolID string, listInstancesParams common.ListInstancesParams) ([]garm_provider_commonparams.ProviderInstance, error) {
ret := _m.Called(ctx, poolID, listInstancesParams)
@ -159,6 +311,36 @@ func (_m *Provider) ListInstances(ctx context.Context, poolID string, listInstan
return r0, r1
}
// Provider_ListInstances_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListInstances'
type Provider_ListInstances_Call struct {
*mock.Call
}
// ListInstances is a helper method to define mock.On call
// - ctx context.Context
// - poolID string
// - listInstancesParams common.ListInstancesParams
func (_e *Provider_Expecter) ListInstances(ctx interface{}, poolID interface{}, listInstancesParams interface{}) *Provider_ListInstances_Call {
return &Provider_ListInstances_Call{Call: _e.mock.On("ListInstances", ctx, poolID, listInstancesParams)}
}
func (_c *Provider_ListInstances_Call) Run(run func(ctx context.Context, poolID string, listInstancesParams common.ListInstancesParams)) *Provider_ListInstances_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(common.ListInstancesParams))
})
return _c
}
func (_c *Provider_ListInstances_Call) Return(_a0 []garm_provider_commonparams.ProviderInstance, _a1 error) *Provider_ListInstances_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Provider_ListInstances_Call) RunAndReturn(run func(context.Context, string, common.ListInstancesParams) ([]garm_provider_commonparams.ProviderInstance, error)) *Provider_ListInstances_Call {
_c.Call.Return(run)
return _c
}
// RemoveAllInstances provides a mock function with given fields: ctx, removeAllInstancesParams
func (_m *Provider) RemoveAllInstances(ctx context.Context, removeAllInstancesParams common.RemoveAllInstancesParams) error {
ret := _m.Called(ctx, removeAllInstancesParams)
@ -177,6 +359,35 @@ func (_m *Provider) RemoveAllInstances(ctx context.Context, removeAllInstancesPa
return r0
}
// Provider_RemoveAllInstances_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveAllInstances'
type Provider_RemoveAllInstances_Call struct {
*mock.Call
}
// RemoveAllInstances is a helper method to define mock.On call
// - ctx context.Context
// - removeAllInstancesParams common.RemoveAllInstancesParams
func (_e *Provider_Expecter) RemoveAllInstances(ctx interface{}, removeAllInstancesParams interface{}) *Provider_RemoveAllInstances_Call {
return &Provider_RemoveAllInstances_Call{Call: _e.mock.On("RemoveAllInstances", ctx, removeAllInstancesParams)}
}
func (_c *Provider_RemoveAllInstances_Call) Run(run func(ctx context.Context, removeAllInstancesParams common.RemoveAllInstancesParams)) *Provider_RemoveAllInstances_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(common.RemoveAllInstancesParams))
})
return _c
}
func (_c *Provider_RemoveAllInstances_Call) Return(_a0 error) *Provider_RemoveAllInstances_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Provider_RemoveAllInstances_Call) RunAndReturn(run func(context.Context, common.RemoveAllInstancesParams) error) *Provider_RemoveAllInstances_Call {
_c.Call.Return(run)
return _c
}
// Start provides a mock function with given fields: ctx, instance, startParams
func (_m *Provider) Start(ctx context.Context, instance string, startParams common.StartParams) error {
ret := _m.Called(ctx, instance, startParams)
@ -195,6 +406,36 @@ func (_m *Provider) Start(ctx context.Context, instance string, startParams comm
return r0
}
// Provider_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
type Provider_Start_Call struct {
*mock.Call
}
// Start is a helper method to define mock.On call
// - ctx context.Context
// - instance string
// - startParams common.StartParams
func (_e *Provider_Expecter) Start(ctx interface{}, instance interface{}, startParams interface{}) *Provider_Start_Call {
return &Provider_Start_Call{Call: _e.mock.On("Start", ctx, instance, startParams)}
}
func (_c *Provider_Start_Call) Run(run func(ctx context.Context, instance string, startParams common.StartParams)) *Provider_Start_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(common.StartParams))
})
return _c
}
func (_c *Provider_Start_Call) Return(_a0 error) *Provider_Start_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Provider_Start_Call) RunAndReturn(run func(context.Context, string, common.StartParams) error) *Provider_Start_Call {
_c.Call.Return(run)
return _c
}
// Stop provides a mock function with given fields: ctx, instance, stopParams
func (_m *Provider) Stop(ctx context.Context, instance string, stopParams common.StopParams) error {
ret := _m.Called(ctx, instance, stopParams)
@ -213,6 +454,36 @@ func (_m *Provider) Stop(ctx context.Context, instance string, stopParams common
return r0
}
// Provider_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
type Provider_Stop_Call struct {
*mock.Call
}
// Stop is a helper method to define mock.On call
// - ctx context.Context
// - instance string
// - stopParams common.StopParams
func (_e *Provider_Expecter) Stop(ctx interface{}, instance interface{}, stopParams interface{}) *Provider_Stop_Call {
return &Provider_Stop_Call{Call: _e.mock.On("Stop", ctx, instance, stopParams)}
}
func (_c *Provider_Stop_Call) Run(run func(ctx context.Context, instance string, stopParams common.StopParams)) *Provider_Stop_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(common.StopParams))
})
return _c
}
func (_c *Provider_Stop_Call) Return(_a0 error) *Provider_Stop_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Provider_Stop_Call) RunAndReturn(run func(context.Context, string, common.StopParams) error) *Provider_Stop_Call {
_c.Call.Return(run)
return _c
}
// NewProvider creates a new instance of Provider. 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 NewProvider(t interface {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.53.3. DO NOT EDIT.
// Code generated by mockery. DO NOT EDIT.
package mocks
@ -14,6 +14,14 @@ type RateLimitClient struct {
mock.Mock
}
type RateLimitClient_Expecter struct {
mock *mock.Mock
}
func (_m *RateLimitClient) EXPECT() *RateLimitClient_Expecter {
return &RateLimitClient_Expecter{mock: &_m.Mock}
}
// RateLimit provides a mock function with given fields: ctx
func (_m *RateLimitClient) RateLimit(ctx context.Context) (*github.RateLimits, error) {
ret := _m.Called(ctx)
@ -44,6 +52,34 @@ func (_m *RateLimitClient) RateLimit(ctx context.Context) (*github.RateLimits, e
return r0, r1
}
// RateLimitClient_RateLimit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RateLimit'
type RateLimitClient_RateLimit_Call struct {
*mock.Call
}
// RateLimit is a helper method to define mock.On call
// - ctx context.Context
func (_e *RateLimitClient_Expecter) RateLimit(ctx interface{}) *RateLimitClient_RateLimit_Call {
return &RateLimitClient_RateLimit_Call{Call: _e.mock.On("RateLimit", ctx)}
}
func (_c *RateLimitClient_RateLimit_Call) Run(run func(ctx context.Context)) *RateLimitClient_RateLimit_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *RateLimitClient_RateLimit_Call) Return(_a0 *github.RateLimits, _a1 error) *RateLimitClient_RateLimit_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *RateLimitClient_RateLimit_Call) RunAndReturn(run func(context.Context) (*github.RateLimits, error)) *RateLimitClient_RateLimit_Call {
_c.Call.Return(run)
return _c
}
// NewRateLimitClient creates a new instance of RateLimitClient. 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 NewRateLimitClient(t interface {

View file

@ -36,7 +36,7 @@ const (
BackoffTimer = 1 * time.Minute
)
//go:generate mockery --all
//go:generate go run github.com/vektra/mockery/v2@latest
type PoolManager interface {
// ID returns the ID of the entity (repo, org, enterprise)
ID() string

View file

@ -21,7 +21,7 @@ import (
"github.com/cloudbase/garm/params"
)
//go:generate mockery --all
//go:generate go run github.com/vektra/mockery/v2@latest
type Provider interface {
// CreateInstance creates a new compute instance in the provider.
CreateInstance(ctx context.Context, bootstrapParams commonParams.BootstrapInstance, createInstanceParams CreateInstanceParams) (commonParams.ProviderInstance, error)

View file

@ -35,6 +35,7 @@ type GithubEntityOperations interface {
RateLimit(ctx context.Context) (*github.RateLimits, error)
CreateEntityRegistrationToken(ctx context.Context) (*github.RegistrationToken, *github.Response, error)
GetEntityJITConfig(ctx context.Context, instance string, pool params.Pool, labels []string) (jitConfigMap map[string]string, runner *github.Runner, err error)
GetEntityRunnerGroupIDByName(ctx context.Context, runnerGroupName string) (int64, error)
// GetEntity returns the GitHub entity for which the github client was instanciated.
GetEntity() params.ForgeEntity
@ -49,7 +50,7 @@ type RateLimitClient interface {
// GithubClient that describes the minimum list of functions we need to interact with github.
// Allows for easier testing.
//
//go:generate mockery --all
//go:generate go run github.com/vektra/mockery/v2@latest
type GithubClient interface {
GithubEntityOperations

View file

@ -16,12 +16,11 @@ package runner
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/params"
@ -36,7 +35,7 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
err = param.Validate()
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "validating params")
return params.Enterprise{}, fmt.Errorf("error validating params: %w", err)
}
creds, err := r.store.GetGithubCredentialsByName(ctx, param.CredentialsName, true)
@ -47,7 +46,7 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
_, err = r.store.GetEnterprise(ctx, param.Name, creds.Endpoint.Name)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
return params.Enterprise{}, fmt.Errorf("error fetching enterprise: %w", err)
}
} else {
return params.Enterprise{}, runnerErrors.NewConflictError("enterprise %s already exists", param.Name)
@ -55,7 +54,7 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
enterprise, err = r.store.CreateEnterprise(ctx, param.Name, creds, param.WebhookSecret, param.PoolBalancerType)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise")
return params.Enterprise{}, fmt.Errorf("error creating enterprise: %w", err)
}
defer func() {
@ -73,7 +72,7 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
var poolMgr common.PoolManager
poolMgr, err = r.poolManagerCtrl.CreateEnterprisePoolManager(r.ctx, enterprise, r.providers, r.store)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "creating enterprise pool manager")
return params.Enterprise{}, fmt.Errorf("error creating enterprise pool manager: %w", err)
}
if err := poolMgr.Start(); err != nil {
if deleteErr := r.poolManagerCtrl.DeleteEnterprisePoolManager(enterprise); deleteErr != nil {
@ -81,7 +80,7 @@ func (r *Runner) CreateEnterprise(ctx context.Context, param params.CreateEnterp
ctx, "failed to cleanup pool manager for enterprise",
"enterprise_id", enterprise.ID)
}
return params.Enterprise{}, errors.Wrap(err, "starting enterprise pool manager")
return params.Enterprise{}, fmt.Errorf("error starting enterprise pool manager: %w", err)
}
return enterprise, nil
}
@ -93,7 +92,7 @@ func (r *Runner) ListEnterprises(ctx context.Context, filter params.EnterpriseFi
enterprises, err := r.store.ListEnterprises(ctx, filter)
if err != nil {
return nil, errors.Wrap(err, "listing enterprises")
return nil, fmt.Errorf("error listing enterprises: %w", err)
}
var allEnterprises []params.Enterprise
@ -119,7 +118,7 @@ func (r *Runner) GetEnterpriseByID(ctx context.Context, enterpriseID string) (pa
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "fetching enterprise")
return params.Enterprise{}, fmt.Errorf("error fetching enterprise: %w", err)
}
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
@ -137,17 +136,17 @@ func (r *Runner) DeleteEnterprise(ctx context.Context, enterpriseID string) erro
enterprise, err := r.store.GetEnterpriseByID(ctx, enterpriseID)
if err != nil {
return errors.Wrap(err, "fetching enterprise")
return fmt.Errorf("error fetching enterprise: %w", err)
}
entity, err := enterprise.GetEntity()
if err != nil {
return errors.Wrap(err, "getting entity")
return fmt.Errorf("error getting entity: %w", err)
}
pools, err := r.store.ListEntityPools(ctx, entity)
if err != nil {
return errors.Wrap(err, "fetching enterprise pools")
return fmt.Errorf("error fetching enterprise pools: %w", err)
}
if len(pools) > 0 {
@ -161,7 +160,7 @@ func (r *Runner) DeleteEnterprise(ctx context.Context, enterpriseID string) erro
scaleSets, err := r.store.ListEntityScaleSets(ctx, entity)
if err != nil {
return errors.Wrap(err, "fetching enterprise scale sets")
return fmt.Errorf("error fetching enterprise scale sets: %w", err)
}
if len(scaleSets) > 0 {
@ -169,11 +168,11 @@ func (r *Runner) DeleteEnterprise(ctx context.Context, enterpriseID string) erro
}
if err := r.poolManagerCtrl.DeleteEnterprisePoolManager(enterprise); err != nil {
return errors.Wrap(err, "deleting enterprise pool manager")
return fmt.Errorf("error deleting enterprise pool manager: %w", err)
}
if err := r.store.DeleteEnterprise(ctx, enterpriseID); err != nil {
return errors.Wrapf(err, "removing enterprise %s", enterpriseID)
return fmt.Errorf("error removing enterprise %s: %w", enterpriseID, err)
}
return nil
}
@ -194,7 +193,7 @@ func (r *Runner) UpdateEnterprise(ctx context.Context, enterpriseID string, para
enterprise, err := r.store.UpdateEnterprise(ctx, enterpriseID, param)
if err != nil {
return params.Enterprise{}, errors.Wrap(err, "updating enterprise")
return params.Enterprise{}, fmt.Errorf("error updating enterprise: %w", err)
}
poolMgr, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
@ -243,7 +242,7 @@ func (r *Runner) GetEnterprisePoolByID(ctx context.Context, enterpriseID, poolID
}
pool, err := r.store.GetEntityPool(ctx, entity, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
return params.Pool{}, fmt.Errorf("error fetching pool: %w", err)
}
return pool, nil
}
@ -260,7 +259,7 @@ func (r *Runner) DeleteEnterprisePool(ctx context.Context, enterpriseID, poolID
pool, err := r.store.GetEntityPool(ctx, entity, poolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
// nolint:golangci-lint,godox
@ -274,7 +273,7 @@ func (r *Runner) DeleteEnterprisePool(ctx context.Context, enterpriseID, poolID
}
if err := r.store.DeleteEntityPool(ctx, entity, poolID); err != nil {
return errors.Wrap(err, "deleting pool")
return fmt.Errorf("error deleting pool: %w", err)
}
return nil
}
@ -290,7 +289,7 @@ func (r *Runner) ListEnterprisePools(ctx context.Context, enterpriseID string) (
}
pools, err := r.store.ListEntityPools(ctx, entity)
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
return nil, fmt.Errorf("error fetching pools: %w", err)
}
return pools, nil
}
@ -306,7 +305,7 @@ func (r *Runner) UpdateEnterprisePool(ctx context.Context, enterpriseID, poolID
}
pool, err := r.store.GetEntityPool(ctx, entity, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
return params.Pool{}, fmt.Errorf("error fetching pool: %w", err)
}
maxRunners := pool.MaxRunners
@ -325,7 +324,7 @@ func (r *Runner) UpdateEnterprisePool(ctx context.Context, enterpriseID, poolID
newPool, err := r.store.UpdateEntityPool(ctx, entity, poolID, param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "updating pool")
return params.Pool{}, fmt.Errorf("error updating pool: %w", err)
}
return newPool, nil
}
@ -340,7 +339,7 @@ func (r *Runner) ListEnterpriseInstances(ctx context.Context, enterpriseID strin
}
instances, err := r.store.ListEntityInstances(ctx, entity)
if err != nil {
return []params.Instance{}, errors.Wrap(err, "fetching instances")
return []params.Instance{}, fmt.Errorf("error fetching instances: %w", err)
}
return instances, nil
}
@ -351,12 +350,12 @@ func (r *Runner) findEnterprisePoolManager(name, endpointName string) (common.Po
enterprise, err := r.store.GetEnterprise(r.ctx, name, endpointName)
if err != nil {
return nil, errors.Wrap(err, "fetching enterprise")
return nil, fmt.Errorf("error fetching enterprise: %w", err)
}
poolManager, err := r.poolManagerCtrl.GetEnterprisePoolManager(enterprise)
if err != nil {
return nil, errors.Wrap(err, "fetching pool manager for enterprise")
return nil, fmt.Errorf("error fetching pool manager for enterprise: %w", err)
}
return poolManager, nil
}

View file

@ -16,10 +16,10 @@ package runner
import (
"context"
"errors"
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
@ -210,7 +210,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterprisePoolMgrFailed() {
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Equal(fmt.Sprintf("creating enterprise pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
s.Require().Equal(fmt.Sprintf("error creating enterprise pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
}
func (s *EnterpriseTestSuite) TestCreateEnterpriseStartPoolMgrFailed() {
@ -222,7 +222,7 @@ func (s *EnterpriseTestSuite) TestCreateEnterpriseStartPoolMgrFailed() {
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Equal(fmt.Sprintf("starting enterprise pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
s.Require().Equal(fmt.Sprintf("error starting enterprise pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
}
func (s *EnterpriseTestSuite) TestListEnterprises() {
@ -324,7 +324,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprise() {
s.Require().Nil(err)
_, err = s.Fixtures.Store.GetEnterpriseByID(s.Fixtures.AdminContext, s.Fixtures.StoreEnterprises["test-enterprise-3"].ID)
s.Require().Equal("fetching enterprise: not found", err.Error())
s.Require().Equal("error fetching enterprise: not found", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterpriseErrUnauthorized() {
@ -354,7 +354,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolMgrFailed() {
err := s.Runner.DeleteEnterprise(s.Fixtures.AdminContext, s.Fixtures.StoreEnterprises["test-enterprise-1"].ID)
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Equal(fmt.Sprintf("deleting enterprise pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
s.Require().Equal(fmt.Sprintf("error deleting enterprise pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
}
func (s *EnterpriseTestSuite) TestUpdateEnterprise() {
@ -477,7 +477,7 @@ func (s *EnterpriseTestSuite) TestDeleteEnterprisePool() {
s.Require().Nil(err)
_, err = s.Fixtures.Store.GetEntityPool(s.Fixtures.AdminContext, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
s.Require().Equal("fetching pool: error finding pool: not found", err.Error())
}
func (s *EnterpriseTestSuite) TestDeleteEnterprisePoolErrUnauthorized() {

View file

@ -16,8 +16,7 @@ package runner
import (
"context"
"github.com/pkg/errors"
"fmt"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
@ -35,7 +34,7 @@ func (r *Runner) ListGiteaCredentials(ctx context.Context) ([]params.ForgeCreden
// there is a posibillity that not all creds will be in the cache.
creds, err := r.store.ListGiteaCredentials(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching gitea credentials")
return nil, fmt.Errorf("error fetching gitea credentials: %w", err)
}
return creds, nil
}
@ -46,12 +45,12 @@ func (r *Runner) CreateGiteaCredentials(ctx context.Context, param params.Create
}
if err := param.Validate(); err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to validate gitea credentials params")
return params.ForgeCredentials{}, fmt.Errorf("error failed to validate gitea credentials params: %w", err)
}
creds, err := r.store.CreateGiteaCredentials(ctx, param)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to create gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error failed to create gitea credentials: %w", err)
}
return creds, nil
@ -64,7 +63,7 @@ func (r *Runner) GetGiteaCredentials(ctx context.Context, id uint) (params.Forge
creds, err := r.store.GetGiteaCredentials(ctx, id, true)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to get gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error failed to get gitea credentials: %w", err)
}
return creds, nil
@ -76,7 +75,7 @@ func (r *Runner) DeleteGiteaCredentials(ctx context.Context, id uint) error {
}
if err := r.store.DeleteGiteaCredentials(ctx, id); err != nil {
return errors.Wrap(err, "failed to delete gitea credentials")
return fmt.Errorf("error failed to delete gitea credentials: %w", err)
}
return nil
@ -88,12 +87,12 @@ func (r *Runner) UpdateGiteaCredentials(ctx context.Context, id uint, param para
}
if err := param.Validate(); err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to validate gitea credentials params")
return params.ForgeCredentials{}, fmt.Errorf("error failed to validate gitea credentials params: %w", err)
}
newCreds, err := r.store.UpdateGiteaCredentials(ctx, id, param)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to update gitea credentials")
return params.ForgeCredentials{}, fmt.Errorf("error failed to update gitea credentials: %w", err)
}
return newCreds, nil

View file

@ -16,8 +16,7 @@ package runner
import (
"context"
"github.com/pkg/errors"
"fmt"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
@ -30,12 +29,12 @@ func (r *Runner) CreateGiteaEndpoint(ctx context.Context, param params.CreateGit
}
if err := param.Validate(); err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to validate gitea endpoint params")
return params.ForgeEndpoint{}, fmt.Errorf("failed to validate gitea endpoint params: %w", err)
}
ep, err := r.store.CreateGiteaEndpoint(ctx, param)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to create gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("failed to create gitea endpoint: %w", err)
}
return ep, nil
@ -47,7 +46,7 @@ func (r *Runner) GetGiteaEndpoint(ctx context.Context, name string) (params.Forg
}
endpoint, err := r.store.GetGiteaEndpoint(ctx, name)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to get gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("failed to get gitea endpoint: %w", err)
}
return endpoint, nil
@ -60,7 +59,7 @@ func (r *Runner) DeleteGiteaEndpoint(ctx context.Context, name string) error {
err := r.store.DeleteGiteaEndpoint(ctx, name)
if err != nil {
return errors.Wrap(err, "failed to delete gitea endpoint")
return fmt.Errorf("failed to delete gitea endpoint: %w", err)
}
return nil
@ -72,12 +71,12 @@ func (r *Runner) UpdateGiteaEndpoint(ctx context.Context, name string, param par
}
if err := param.Validate(); err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to validate gitea endpoint params")
return params.ForgeEndpoint{}, fmt.Errorf("failed to validate gitea endpoint params: %w", err)
}
newEp, err := r.store.UpdateGiteaEndpoint(ctx, name, param)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to update gitea endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("failed to update gitea endpoint: %w", err)
}
return newEp, nil
}
@ -89,7 +88,7 @@ func (r *Runner) ListGiteaEndpoints(ctx context.Context) ([]params.ForgeEndpoint
endpoints, err := r.store.ListGiteaEndpoints(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to list gitea endpoints")
return nil, fmt.Errorf("failed to list gitea endpoints: %w", err)
}
return endpoints, nil

View file

@ -16,8 +16,7 @@ package runner
import (
"context"
"github.com/pkg/errors"
"fmt"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
@ -36,7 +35,7 @@ func (r *Runner) ListCredentials(ctx context.Context) ([]params.ForgeCredentials
// there is a posibillity that not all creds will be in the cache.
creds, err := r.store.ListGithubCredentials(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching github credentials")
return nil, fmt.Errorf("error fetching github credentials: %w", err)
}
// If we do have cache, update the rate limit for each credential. The rate limits are queried
@ -57,12 +56,12 @@ func (r *Runner) CreateGithubCredentials(ctx context.Context, param params.Creat
}
if err := param.Validate(); err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to validate github credentials params")
return params.ForgeCredentials{}, fmt.Errorf("failed to validate github credentials params: %w", err)
}
creds, err := r.store.CreateGithubCredentials(ctx, param)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to create github credentials")
return params.ForgeCredentials{}, fmt.Errorf("failed to create github credentials: %w", err)
}
return creds, nil
@ -75,7 +74,7 @@ func (r *Runner) GetGithubCredentials(ctx context.Context, id uint) (params.Forg
creds, err := r.store.GetGithubCredentials(ctx, id, true)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to get github credentials")
return params.ForgeCredentials{}, fmt.Errorf("failed to get github credentials: %w", err)
}
cached, ok := cache.GetGithubCredentials((creds.ID))
@ -92,7 +91,7 @@ func (r *Runner) DeleteGithubCredentials(ctx context.Context, id uint) error {
}
if err := r.store.DeleteGithubCredentials(ctx, id); err != nil {
return errors.Wrap(err, "failed to delete github credentials")
return fmt.Errorf("failed to delete github credentials: %w", err)
}
return nil
@ -104,12 +103,12 @@ func (r *Runner) UpdateGithubCredentials(ctx context.Context, id uint, param par
}
if err := param.Validate(); err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to validate github credentials params")
return params.ForgeCredentials{}, fmt.Errorf("failed to validate github credentials params: %w", err)
}
newCreds, err := r.store.UpdateGithubCredentials(ctx, id, param)
if err != nil {
return params.ForgeCredentials{}, errors.Wrap(err, "failed to update github credentials")
return params.ForgeCredentials{}, fmt.Errorf("failed to update github credentials: %w", err)
}
return newCreds, nil

View file

@ -16,8 +16,7 @@ package runner
import (
"context"
"github.com/pkg/errors"
"fmt"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
@ -30,12 +29,12 @@ func (r *Runner) CreateGithubEndpoint(ctx context.Context, param params.CreateGi
}
if err := param.Validate(); err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to validate github endpoint params")
return params.ForgeEndpoint{}, fmt.Errorf("error failed to validate github endpoint params: %w", err)
}
ep, err := r.store.CreateGithubEndpoint(ctx, param)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to create github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("failed to create github endpoint: %w", err)
}
return ep, nil
@ -47,7 +46,7 @@ func (r *Runner) GetGithubEndpoint(ctx context.Context, name string) (params.For
}
endpoint, err := r.store.GetGithubEndpoint(ctx, name)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to get github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("failed to get github endpoint: %w", err)
}
return endpoint, nil
@ -60,7 +59,7 @@ func (r *Runner) DeleteGithubEndpoint(ctx context.Context, name string) error {
err := r.store.DeleteGithubEndpoint(ctx, name)
if err != nil {
return errors.Wrap(err, "failed to delete github endpoint")
return fmt.Errorf("failed to delete github endpoint: %w", err)
}
return nil
@ -72,12 +71,12 @@ func (r *Runner) UpdateGithubEndpoint(ctx context.Context, name string, param pa
}
if err := param.Validate(); err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to validate github endpoint params")
return params.ForgeEndpoint{}, fmt.Errorf("failed to validate github endpoint params: %w", err)
}
newEp, err := r.store.UpdateGithubEndpoint(ctx, name, param)
if err != nil {
return params.ForgeEndpoint{}, errors.Wrap(err, "failed to update github endpoint")
return params.ForgeEndpoint{}, fmt.Errorf("failed to update github endpoint: %w", err)
}
return newEp, nil
}
@ -89,7 +88,7 @@ func (r *Runner) ListGithubEndpoints(ctx context.Context) ([]params.ForgeEndpoin
endpoints, err := r.store.ListGithubEndpoints(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to list github endpoints")
return nil, fmt.Errorf("failed to list github endpoints: %w", err)
}
return endpoints, nil

View file

@ -43,7 +43,7 @@ type EnterprisePoolManager interface {
GetEnterprisePoolManagers() (map[string]common.PoolManager, error)
}
//go:generate mockery --name=PoolManagerController
//go:generate go run github.com/vektra/mockery/v2@latest
type PoolManagerController interface {
RepoPoolManager

View file

@ -18,12 +18,11 @@ import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"html/template"
"log/slog"
"github.com/pkg/errors"
"github.com/cloudbase/garm-provider-common/defaults"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
@ -92,7 +91,7 @@ func (r *Runner) getForgeEntityFromInstance(ctx context.Context, instance params
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get entity getter",
"instance", instance.Name)
return params.ForgeEntity{}, errors.Wrap(err, "fetching entity getter")
return params.ForgeEntity{}, fmt.Errorf("error fetching entity getter: %w", err)
}
poolEntity, err := entityGetter.GetEntity()
@ -100,7 +99,7 @@ func (r *Runner) getForgeEntityFromInstance(ctx context.Context, instance params
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get entity",
"instance", instance.Name)
return params.ForgeEntity{}, errors.Wrap(err, "fetching entity")
return params.ForgeEntity{}, fmt.Errorf("error fetching entity: %w", err)
}
entity, err := r.store.GetForgeEntity(r.ctx, poolEntity.EntityType, poolEntity.ID)
@ -108,7 +107,7 @@ func (r *Runner) getForgeEntityFromInstance(ctx context.Context, instance params
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get entity",
"instance", instance.Name)
return params.ForgeEntity{}, errors.Wrap(err, "fetching entity")
return params.ForgeEntity{}, fmt.Errorf("error fetching entity: %w", err)
}
return entity, nil
}
@ -136,13 +135,13 @@ func (r *Runner) GetRunnerServiceName(ctx context.Context) (string, error) {
entity, err := r.getForgeEntityFromInstance(ctx, instance)
if err != nil {
slog.ErrorContext(r.ctx, "failed to get entity", "error", err)
return "", errors.Wrap(err, "fetching entity")
return "", fmt.Errorf("error fetching entity: %w", err)
}
serviceName, err := r.getServiceNameForEntity(entity)
if err != nil {
slog.ErrorContext(r.ctx, "failed to get service name", "error", err)
return "", errors.Wrap(err, "fetching service name")
return "", fmt.Errorf("error fetching service name: %w", err)
}
return serviceName, nil
}
@ -157,13 +156,13 @@ func (r *Runner) GenerateSystemdUnitFile(ctx context.Context, runAsUser string)
entity, err := r.getForgeEntityFromInstance(ctx, instance)
if err != nil {
slog.ErrorContext(r.ctx, "failed to get entity", "error", err)
return nil, errors.Wrap(err, "fetching entity")
return nil, fmt.Errorf("error fetching entity: %w", err)
}
serviceName, err := r.getServiceNameForEntity(entity)
if err != nil {
slog.ErrorContext(r.ctx, "failed to get service name", "error", err)
return nil, errors.Wrap(err, "fetching service name")
return nil, fmt.Errorf("error fetching service name: %w", err)
}
var unitTemplate *template.Template
@ -178,7 +177,7 @@ func (r *Runner) GenerateSystemdUnitFile(ctx context.Context, runAsUser string)
}
if err != nil {
slog.ErrorContext(r.ctx, "failed to parse template", "error", err)
return nil, errors.Wrap(err, "parsing template")
return nil, fmt.Errorf("error parsing template: %w", err)
}
if runAsUser == "" {
@ -196,14 +195,14 @@ func (r *Runner) GenerateSystemdUnitFile(ctx context.Context, runAsUser string)
var unitFile bytes.Buffer
if err := unitTemplate.Execute(&unitFile, data); err != nil {
slog.ErrorContext(r.ctx, "failed to execute template", "error", err)
return nil, errors.Wrap(err, "executing template")
return nil, fmt.Errorf("error executing template: %w", err)
}
return unitFile.Bytes(), nil
}
func (r *Runner) GetJITConfigFile(ctx context.Context, file string) ([]byte, error) {
if !auth.InstanceHasJITConfig(ctx) {
return nil, fmt.Errorf("instance not configured for JIT: %w", runnerErrors.ErrNotFound)
return nil, runnerErrors.NewNotFoundError("instance not configured for JIT")
}
instance, err := validateInstanceState(ctx)
@ -215,12 +214,12 @@ func (r *Runner) GetJITConfigFile(ctx context.Context, file string) ([]byte, err
jitConfig := instance.JitConfiguration
contents, ok := jitConfig[file]
if !ok {
return nil, errors.Wrap(runnerErrors.ErrNotFound, "retrieving file")
return nil, runnerErrors.NewNotFoundError("could not find file %q", file)
}
decoded, err := base64.StdEncoding.DecodeString(contents)
if err != nil {
return nil, errors.Wrap(err, "decoding file contents")
return nil, fmt.Errorf("error decoding file contents: %w", err)
}
return decoded, nil
@ -249,12 +248,12 @@ func (r *Runner) GetInstanceGithubRegistrationToken(ctx context.Context) (string
poolMgr, err := r.getPoolManagerFromInstance(ctx, instance)
if err != nil {
return "", errors.Wrap(err, "fetching pool manager for instance")
return "", fmt.Errorf("error fetching pool manager for instance: %w", err)
}
token, err := poolMgr.GithubRunnerRegistrationToken()
if err != nil {
return "", errors.Wrap(err, "fetching runner token")
return "", fmt.Errorf("error fetching runner token: %w", err)
}
tokenFetched := true
@ -263,11 +262,11 @@ func (r *Runner) GetInstanceGithubRegistrationToken(ctx context.Context) (string
}
if _, err := r.store.UpdateInstance(r.ctx, instance.Name, updateParams); err != nil {
return "", errors.Wrap(err, "setting token_fetched for instance")
return "", fmt.Errorf("error setting token_fetched for instance: %w", err)
}
if err := r.store.AddInstanceEvent(ctx, instance.Name, params.FetchTokenEvent, params.EventInfo, "runner registration token was retrieved"); err != nil {
return "", errors.Wrap(err, "recording event")
return "", fmt.Errorf("error recording event: %w", err)
}
return token, nil
@ -283,7 +282,7 @@ func (r *Runner) GetRootCertificateBundle(ctx context.Context) (params.Certifica
poolMgr, err := r.getPoolManagerFromInstance(ctx, instance)
if err != nil {
return params.CertificateBundle{}, errors.Wrap(err, "fetching pool manager for instance")
return params.CertificateBundle{}, fmt.Errorf("error fetching pool manager for instance: %w", err)
}
bundle, err := poolMgr.RootCABundle()

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.53.3. DO NOT EDIT.
// Code generated by mockery. DO NOT EDIT.
package mocks
@ -19,6 +19,14 @@ type PoolManagerController struct {
mock.Mock
}
type PoolManagerController_Expecter struct {
mock *mock.Mock
}
func (_m *PoolManagerController) EXPECT() *PoolManagerController_Expecter {
return &PoolManagerController_Expecter{mock: &_m.Mock}
}
// CreateEnterprisePoolManager provides a mock function with given fields: ctx, enterprise, providers, store
func (_m *PoolManagerController) CreateEnterprisePoolManager(ctx context.Context, enterprise params.Enterprise, providers map[string]common.Provider, store databasecommon.Store) (common.PoolManager, error) {
ret := _m.Called(ctx, enterprise, providers, store)
@ -49,6 +57,37 @@ func (_m *PoolManagerController) CreateEnterprisePoolManager(ctx context.Context
return r0, r1
}
// PoolManagerController_CreateEnterprisePoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateEnterprisePoolManager'
type PoolManagerController_CreateEnterprisePoolManager_Call struct {
*mock.Call
}
// CreateEnterprisePoolManager is a helper method to define mock.On call
// - ctx context.Context
// - enterprise params.Enterprise
// - providers map[string]common.Provider
// - store databasecommon.Store
func (_e *PoolManagerController_Expecter) CreateEnterprisePoolManager(ctx interface{}, enterprise interface{}, providers interface{}, store interface{}) *PoolManagerController_CreateEnterprisePoolManager_Call {
return &PoolManagerController_CreateEnterprisePoolManager_Call{Call: _e.mock.On("CreateEnterprisePoolManager", ctx, enterprise, providers, store)}
}
func (_c *PoolManagerController_CreateEnterprisePoolManager_Call) Run(run func(ctx context.Context, enterprise params.Enterprise, providers map[string]common.Provider, store databasecommon.Store)) *PoolManagerController_CreateEnterprisePoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(params.Enterprise), args[2].(map[string]common.Provider), args[3].(databasecommon.Store))
})
return _c
}
func (_c *PoolManagerController_CreateEnterprisePoolManager_Call) Return(_a0 common.PoolManager, _a1 error) *PoolManagerController_CreateEnterprisePoolManager_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_CreateEnterprisePoolManager_Call) RunAndReturn(run func(context.Context, params.Enterprise, map[string]common.Provider, databasecommon.Store) (common.PoolManager, error)) *PoolManagerController_CreateEnterprisePoolManager_Call {
_c.Call.Return(run)
return _c
}
// CreateOrgPoolManager provides a mock function with given fields: ctx, org, providers, store
func (_m *PoolManagerController) CreateOrgPoolManager(ctx context.Context, org params.Organization, providers map[string]common.Provider, store databasecommon.Store) (common.PoolManager, error) {
ret := _m.Called(ctx, org, providers, store)
@ -79,6 +118,37 @@ func (_m *PoolManagerController) CreateOrgPoolManager(ctx context.Context, org p
return r0, r1
}
// PoolManagerController_CreateOrgPoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrgPoolManager'
type PoolManagerController_CreateOrgPoolManager_Call struct {
*mock.Call
}
// CreateOrgPoolManager is a helper method to define mock.On call
// - ctx context.Context
// - org params.Organization
// - providers map[string]common.Provider
// - store databasecommon.Store
func (_e *PoolManagerController_Expecter) CreateOrgPoolManager(ctx interface{}, org interface{}, providers interface{}, store interface{}) *PoolManagerController_CreateOrgPoolManager_Call {
return &PoolManagerController_CreateOrgPoolManager_Call{Call: _e.mock.On("CreateOrgPoolManager", ctx, org, providers, store)}
}
func (_c *PoolManagerController_CreateOrgPoolManager_Call) Run(run func(ctx context.Context, org params.Organization, providers map[string]common.Provider, store databasecommon.Store)) *PoolManagerController_CreateOrgPoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(params.Organization), args[2].(map[string]common.Provider), args[3].(databasecommon.Store))
})
return _c
}
func (_c *PoolManagerController_CreateOrgPoolManager_Call) Return(_a0 common.PoolManager, _a1 error) *PoolManagerController_CreateOrgPoolManager_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_CreateOrgPoolManager_Call) RunAndReturn(run func(context.Context, params.Organization, map[string]common.Provider, databasecommon.Store) (common.PoolManager, error)) *PoolManagerController_CreateOrgPoolManager_Call {
_c.Call.Return(run)
return _c
}
// CreateRepoPoolManager provides a mock function with given fields: ctx, repo, providers, store
func (_m *PoolManagerController) CreateRepoPoolManager(ctx context.Context, repo params.Repository, providers map[string]common.Provider, store databasecommon.Store) (common.PoolManager, error) {
ret := _m.Called(ctx, repo, providers, store)
@ -109,6 +179,37 @@ func (_m *PoolManagerController) CreateRepoPoolManager(ctx context.Context, repo
return r0, r1
}
// PoolManagerController_CreateRepoPoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRepoPoolManager'
type PoolManagerController_CreateRepoPoolManager_Call struct {
*mock.Call
}
// CreateRepoPoolManager is a helper method to define mock.On call
// - ctx context.Context
// - repo params.Repository
// - providers map[string]common.Provider
// - store databasecommon.Store
func (_e *PoolManagerController_Expecter) CreateRepoPoolManager(ctx interface{}, repo interface{}, providers interface{}, store interface{}) *PoolManagerController_CreateRepoPoolManager_Call {
return &PoolManagerController_CreateRepoPoolManager_Call{Call: _e.mock.On("CreateRepoPoolManager", ctx, repo, providers, store)}
}
func (_c *PoolManagerController_CreateRepoPoolManager_Call) Run(run func(ctx context.Context, repo params.Repository, providers map[string]common.Provider, store databasecommon.Store)) *PoolManagerController_CreateRepoPoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(params.Repository), args[2].(map[string]common.Provider), args[3].(databasecommon.Store))
})
return _c
}
func (_c *PoolManagerController_CreateRepoPoolManager_Call) Return(_a0 common.PoolManager, _a1 error) *PoolManagerController_CreateRepoPoolManager_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_CreateRepoPoolManager_Call) RunAndReturn(run func(context.Context, params.Repository, map[string]common.Provider, databasecommon.Store) (common.PoolManager, error)) *PoolManagerController_CreateRepoPoolManager_Call {
_c.Call.Return(run)
return _c
}
// DeleteEnterprisePoolManager provides a mock function with given fields: enterprise
func (_m *PoolManagerController) DeleteEnterprisePoolManager(enterprise params.Enterprise) error {
ret := _m.Called(enterprise)
@ -127,6 +228,34 @@ func (_m *PoolManagerController) DeleteEnterprisePoolManager(enterprise params.E
return r0
}
// PoolManagerController_DeleteEnterprisePoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteEnterprisePoolManager'
type PoolManagerController_DeleteEnterprisePoolManager_Call struct {
*mock.Call
}
// DeleteEnterprisePoolManager is a helper method to define mock.On call
// - enterprise params.Enterprise
func (_e *PoolManagerController_Expecter) DeleteEnterprisePoolManager(enterprise interface{}) *PoolManagerController_DeleteEnterprisePoolManager_Call {
return &PoolManagerController_DeleteEnterprisePoolManager_Call{Call: _e.mock.On("DeleteEnterprisePoolManager", enterprise)}
}
func (_c *PoolManagerController_DeleteEnterprisePoolManager_Call) Run(run func(enterprise params.Enterprise)) *PoolManagerController_DeleteEnterprisePoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(params.Enterprise))
})
return _c
}
func (_c *PoolManagerController_DeleteEnterprisePoolManager_Call) Return(_a0 error) *PoolManagerController_DeleteEnterprisePoolManager_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManagerController_DeleteEnterprisePoolManager_Call) RunAndReturn(run func(params.Enterprise) error) *PoolManagerController_DeleteEnterprisePoolManager_Call {
_c.Call.Return(run)
return _c
}
// DeleteOrgPoolManager provides a mock function with given fields: org
func (_m *PoolManagerController) DeleteOrgPoolManager(org params.Organization) error {
ret := _m.Called(org)
@ -145,6 +274,34 @@ func (_m *PoolManagerController) DeleteOrgPoolManager(org params.Organization) e
return r0
}
// PoolManagerController_DeleteOrgPoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteOrgPoolManager'
type PoolManagerController_DeleteOrgPoolManager_Call struct {
*mock.Call
}
// DeleteOrgPoolManager is a helper method to define mock.On call
// - org params.Organization
func (_e *PoolManagerController_Expecter) DeleteOrgPoolManager(org interface{}) *PoolManagerController_DeleteOrgPoolManager_Call {
return &PoolManagerController_DeleteOrgPoolManager_Call{Call: _e.mock.On("DeleteOrgPoolManager", org)}
}
func (_c *PoolManagerController_DeleteOrgPoolManager_Call) Run(run func(org params.Organization)) *PoolManagerController_DeleteOrgPoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(params.Organization))
})
return _c
}
func (_c *PoolManagerController_DeleteOrgPoolManager_Call) Return(_a0 error) *PoolManagerController_DeleteOrgPoolManager_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManagerController_DeleteOrgPoolManager_Call) RunAndReturn(run func(params.Organization) error) *PoolManagerController_DeleteOrgPoolManager_Call {
_c.Call.Return(run)
return _c
}
// DeleteRepoPoolManager provides a mock function with given fields: repo
func (_m *PoolManagerController) DeleteRepoPoolManager(repo params.Repository) error {
ret := _m.Called(repo)
@ -163,6 +320,34 @@ func (_m *PoolManagerController) DeleteRepoPoolManager(repo params.Repository) e
return r0
}
// PoolManagerController_DeleteRepoPoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteRepoPoolManager'
type PoolManagerController_DeleteRepoPoolManager_Call struct {
*mock.Call
}
// DeleteRepoPoolManager is a helper method to define mock.On call
// - repo params.Repository
func (_e *PoolManagerController_Expecter) DeleteRepoPoolManager(repo interface{}) *PoolManagerController_DeleteRepoPoolManager_Call {
return &PoolManagerController_DeleteRepoPoolManager_Call{Call: _e.mock.On("DeleteRepoPoolManager", repo)}
}
func (_c *PoolManagerController_DeleteRepoPoolManager_Call) Run(run func(repo params.Repository)) *PoolManagerController_DeleteRepoPoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(params.Repository))
})
return _c
}
func (_c *PoolManagerController_DeleteRepoPoolManager_Call) Return(_a0 error) *PoolManagerController_DeleteRepoPoolManager_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *PoolManagerController_DeleteRepoPoolManager_Call) RunAndReturn(run func(params.Repository) error) *PoolManagerController_DeleteRepoPoolManager_Call {
_c.Call.Return(run)
return _c
}
// GetEnterprisePoolManager provides a mock function with given fields: enterprise
func (_m *PoolManagerController) GetEnterprisePoolManager(enterprise params.Enterprise) (common.PoolManager, error) {
ret := _m.Called(enterprise)
@ -193,6 +378,34 @@ func (_m *PoolManagerController) GetEnterprisePoolManager(enterprise params.Ente
return r0, r1
}
// PoolManagerController_GetEnterprisePoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEnterprisePoolManager'
type PoolManagerController_GetEnterprisePoolManager_Call struct {
*mock.Call
}
// GetEnterprisePoolManager is a helper method to define mock.On call
// - enterprise params.Enterprise
func (_e *PoolManagerController_Expecter) GetEnterprisePoolManager(enterprise interface{}) *PoolManagerController_GetEnterprisePoolManager_Call {
return &PoolManagerController_GetEnterprisePoolManager_Call{Call: _e.mock.On("GetEnterprisePoolManager", enterprise)}
}
func (_c *PoolManagerController_GetEnterprisePoolManager_Call) Run(run func(enterprise params.Enterprise)) *PoolManagerController_GetEnterprisePoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(params.Enterprise))
})
return _c
}
func (_c *PoolManagerController_GetEnterprisePoolManager_Call) Return(_a0 common.PoolManager, _a1 error) *PoolManagerController_GetEnterprisePoolManager_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_GetEnterprisePoolManager_Call) RunAndReturn(run func(params.Enterprise) (common.PoolManager, error)) *PoolManagerController_GetEnterprisePoolManager_Call {
_c.Call.Return(run)
return _c
}
// GetEnterprisePoolManagers provides a mock function with no fields
func (_m *PoolManagerController) GetEnterprisePoolManagers() (map[string]common.PoolManager, error) {
ret := _m.Called()
@ -223,6 +436,33 @@ func (_m *PoolManagerController) GetEnterprisePoolManagers() (map[string]common.
return r0, r1
}
// PoolManagerController_GetEnterprisePoolManagers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetEnterprisePoolManagers'
type PoolManagerController_GetEnterprisePoolManagers_Call struct {
*mock.Call
}
// GetEnterprisePoolManagers is a helper method to define mock.On call
func (_e *PoolManagerController_Expecter) GetEnterprisePoolManagers() *PoolManagerController_GetEnterprisePoolManagers_Call {
return &PoolManagerController_GetEnterprisePoolManagers_Call{Call: _e.mock.On("GetEnterprisePoolManagers")}
}
func (_c *PoolManagerController_GetEnterprisePoolManagers_Call) Run(run func()) *PoolManagerController_GetEnterprisePoolManagers_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManagerController_GetEnterprisePoolManagers_Call) Return(_a0 map[string]common.PoolManager, _a1 error) *PoolManagerController_GetEnterprisePoolManagers_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_GetEnterprisePoolManagers_Call) RunAndReturn(run func() (map[string]common.PoolManager, error)) *PoolManagerController_GetEnterprisePoolManagers_Call {
_c.Call.Return(run)
return _c
}
// GetOrgPoolManager provides a mock function with given fields: org
func (_m *PoolManagerController) GetOrgPoolManager(org params.Organization) (common.PoolManager, error) {
ret := _m.Called(org)
@ -253,6 +493,34 @@ func (_m *PoolManagerController) GetOrgPoolManager(org params.Organization) (com
return r0, r1
}
// PoolManagerController_GetOrgPoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrgPoolManager'
type PoolManagerController_GetOrgPoolManager_Call struct {
*mock.Call
}
// GetOrgPoolManager is a helper method to define mock.On call
// - org params.Organization
func (_e *PoolManagerController_Expecter) GetOrgPoolManager(org interface{}) *PoolManagerController_GetOrgPoolManager_Call {
return &PoolManagerController_GetOrgPoolManager_Call{Call: _e.mock.On("GetOrgPoolManager", org)}
}
func (_c *PoolManagerController_GetOrgPoolManager_Call) Run(run func(org params.Organization)) *PoolManagerController_GetOrgPoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(params.Organization))
})
return _c
}
func (_c *PoolManagerController_GetOrgPoolManager_Call) Return(_a0 common.PoolManager, _a1 error) *PoolManagerController_GetOrgPoolManager_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_GetOrgPoolManager_Call) RunAndReturn(run func(params.Organization) (common.PoolManager, error)) *PoolManagerController_GetOrgPoolManager_Call {
_c.Call.Return(run)
return _c
}
// GetOrgPoolManagers provides a mock function with no fields
func (_m *PoolManagerController) GetOrgPoolManagers() (map[string]common.PoolManager, error) {
ret := _m.Called()
@ -283,6 +551,33 @@ func (_m *PoolManagerController) GetOrgPoolManagers() (map[string]common.PoolMan
return r0, r1
}
// PoolManagerController_GetOrgPoolManagers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOrgPoolManagers'
type PoolManagerController_GetOrgPoolManagers_Call struct {
*mock.Call
}
// GetOrgPoolManagers is a helper method to define mock.On call
func (_e *PoolManagerController_Expecter) GetOrgPoolManagers() *PoolManagerController_GetOrgPoolManagers_Call {
return &PoolManagerController_GetOrgPoolManagers_Call{Call: _e.mock.On("GetOrgPoolManagers")}
}
func (_c *PoolManagerController_GetOrgPoolManagers_Call) Run(run func()) *PoolManagerController_GetOrgPoolManagers_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManagerController_GetOrgPoolManagers_Call) Return(_a0 map[string]common.PoolManager, _a1 error) *PoolManagerController_GetOrgPoolManagers_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_GetOrgPoolManagers_Call) RunAndReturn(run func() (map[string]common.PoolManager, error)) *PoolManagerController_GetOrgPoolManagers_Call {
_c.Call.Return(run)
return _c
}
// GetRepoPoolManager provides a mock function with given fields: repo
func (_m *PoolManagerController) GetRepoPoolManager(repo params.Repository) (common.PoolManager, error) {
ret := _m.Called(repo)
@ -313,6 +608,34 @@ func (_m *PoolManagerController) GetRepoPoolManager(repo params.Repository) (com
return r0, r1
}
// PoolManagerController_GetRepoPoolManager_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepoPoolManager'
type PoolManagerController_GetRepoPoolManager_Call struct {
*mock.Call
}
// GetRepoPoolManager is a helper method to define mock.On call
// - repo params.Repository
func (_e *PoolManagerController_Expecter) GetRepoPoolManager(repo interface{}) *PoolManagerController_GetRepoPoolManager_Call {
return &PoolManagerController_GetRepoPoolManager_Call{Call: _e.mock.On("GetRepoPoolManager", repo)}
}
func (_c *PoolManagerController_GetRepoPoolManager_Call) Run(run func(repo params.Repository)) *PoolManagerController_GetRepoPoolManager_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(params.Repository))
})
return _c
}
func (_c *PoolManagerController_GetRepoPoolManager_Call) Return(_a0 common.PoolManager, _a1 error) *PoolManagerController_GetRepoPoolManager_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_GetRepoPoolManager_Call) RunAndReturn(run func(params.Repository) (common.PoolManager, error)) *PoolManagerController_GetRepoPoolManager_Call {
_c.Call.Return(run)
return _c
}
// GetRepoPoolManagers provides a mock function with no fields
func (_m *PoolManagerController) GetRepoPoolManagers() (map[string]common.PoolManager, error) {
ret := _m.Called()
@ -343,6 +666,33 @@ func (_m *PoolManagerController) GetRepoPoolManagers() (map[string]common.PoolMa
return r0, r1
}
// PoolManagerController_GetRepoPoolManagers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRepoPoolManagers'
type PoolManagerController_GetRepoPoolManagers_Call struct {
*mock.Call
}
// GetRepoPoolManagers is a helper method to define mock.On call
func (_e *PoolManagerController_Expecter) GetRepoPoolManagers() *PoolManagerController_GetRepoPoolManagers_Call {
return &PoolManagerController_GetRepoPoolManagers_Call{Call: _e.mock.On("GetRepoPoolManagers")}
}
func (_c *PoolManagerController_GetRepoPoolManagers_Call) Run(run func()) *PoolManagerController_GetRepoPoolManagers_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *PoolManagerController_GetRepoPoolManagers_Call) Return(_a0 map[string]common.PoolManager, _a1 error) *PoolManagerController_GetRepoPoolManagers_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *PoolManagerController_GetRepoPoolManagers_Call) RunAndReturn(run func() (map[string]common.PoolManager, error)) *PoolManagerController_GetRepoPoolManagers_Call {
_c.Call.Return(run)
return _c
}
// NewPoolManagerController creates a new instance of PoolManagerController. 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 NewPoolManagerController(t interface {

View file

@ -16,12 +16,11 @@ package runner
import (
"context"
"errors"
"fmt"
"log/slog"
"strings"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/params"
@ -35,7 +34,7 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
}
if err := param.Validate(); err != nil {
return params.Organization{}, errors.Wrap(err, "validating params")
return params.Organization{}, fmt.Errorf("error validating params: %w", err)
}
var creds params.ForgeCredentials
@ -57,7 +56,7 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
_, err = r.store.GetOrganization(ctx, param.Name, creds.Endpoint.Name)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return params.Organization{}, errors.Wrap(err, "fetching org")
return params.Organization{}, fmt.Errorf("error fetching org: %w", err)
}
} else {
return params.Organization{}, runnerErrors.NewConflictError("organization %s already exists", param.Name)
@ -65,7 +64,7 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
org, err = r.store.CreateOrganization(ctx, param.Name, creds, param.WebhookSecret, param.PoolBalancerType)
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating organization")
return params.Organization{}, fmt.Errorf("error creating organization: %w", err)
}
defer func() {
@ -82,7 +81,7 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
// updating the store.
poolMgr, err := r.poolManagerCtrl.CreateOrgPoolManager(r.ctx, org, r.providers, r.store)
if err != nil {
return params.Organization{}, errors.Wrap(err, "creating org pool manager")
return params.Organization{}, fmt.Errorf("error creating org pool manager: %w", err)
}
if err := poolMgr.Start(); err != nil {
if deleteErr := r.poolManagerCtrl.DeleteOrgPoolManager(org); deleteErr != nil {
@ -90,7 +89,7 @@ func (r *Runner) CreateOrganization(ctx context.Context, param params.CreateOrgP
ctx, "failed to cleanup pool manager for org",
"org_id", org.ID)
}
return params.Organization{}, errors.Wrap(err, "starting org pool manager")
return params.Organization{}, fmt.Errorf("error starting org pool manager: %w", err)
}
return org, nil
}
@ -102,7 +101,7 @@ func (r *Runner) ListOrganizations(ctx context.Context, filter params.Organizati
orgs, err := r.store.ListOrganizations(ctx, filter)
if err != nil {
return nil, errors.Wrap(err, "listing organizations")
return nil, fmt.Errorf("error listing organizations: %w", err)
}
var allOrgs []params.Organization
@ -129,7 +128,7 @@ func (r *Runner) GetOrganizationByID(ctx context.Context, orgID string) (params.
org, err := r.store.GetOrganizationByID(ctx, orgID)
if err != nil {
return params.Organization{}, errors.Wrap(err, "fetching organization")
return params.Organization{}, fmt.Errorf("error fetching organization: %w", err)
}
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
@ -148,17 +147,17 @@ func (r *Runner) DeleteOrganization(ctx context.Context, orgID string, keepWebho
org, err := r.store.GetOrganizationByID(ctx, orgID)
if err != nil {
return errors.Wrap(err, "fetching org")
return fmt.Errorf("error fetching org: %w", err)
}
entity, err := org.GetEntity()
if err != nil {
return errors.Wrap(err, "getting entity")
return fmt.Errorf("error getting entity: %w", err)
}
pools, err := r.store.ListEntityPools(ctx, entity)
if err != nil {
return errors.Wrap(err, "fetching org pools")
return fmt.Errorf("error fetching org pools: %w", err)
}
if len(pools) > 0 {
@ -172,7 +171,7 @@ func (r *Runner) DeleteOrganization(ctx context.Context, orgID string, keepWebho
scaleSets, err := r.store.ListEntityScaleSets(ctx, entity)
if err != nil {
return errors.Wrap(err, "fetching organization scale sets")
return fmt.Errorf("error fetching organization scale sets: %w", err)
}
if len(scaleSets) > 0 {
@ -182,7 +181,7 @@ func (r *Runner) DeleteOrganization(ctx context.Context, orgID string, keepWebho
if !keepWebhook && r.config.Default.EnableWebhookManagement {
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {
return errors.Wrap(err, "fetching pool manager")
return fmt.Errorf("error fetching pool manager: %w", err)
}
if err := poolMgr.UninstallWebhook(ctx); err != nil {
@ -195,11 +194,11 @@ func (r *Runner) DeleteOrganization(ctx context.Context, orgID string, keepWebho
}
if err := r.poolManagerCtrl.DeleteOrgPoolManager(org); err != nil {
return errors.Wrap(err, "deleting org pool manager")
return fmt.Errorf("error deleting org pool manager: %w", err)
}
if err := r.store.DeleteOrganization(ctx, orgID); err != nil {
return errors.Wrapf(err, "removing organization %s", orgID)
return fmt.Errorf("error removing organization %s: %w", orgID, err)
}
return nil
}
@ -220,7 +219,7 @@ func (r *Runner) UpdateOrganization(ctx context.Context, orgID string, param par
org, err := r.store.UpdateOrganization(ctx, orgID, param)
if err != nil {
return params.Organization{}, errors.Wrap(err, "updating org")
return params.Organization{}, fmt.Errorf("error updating org: %w", err)
}
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
@ -239,7 +238,7 @@ func (r *Runner) CreateOrgPool(ctx context.Context, orgID string, param params.C
createPoolParams, err := r.appendTagsToCreatePoolParams(param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool params")
return params.Pool{}, fmt.Errorf("error fetching pool params: %w", err)
}
if param.RunnerBootstrapTimeout == 0 {
@ -253,7 +252,7 @@ func (r *Runner) CreateOrgPool(ctx context.Context, orgID string, param params.C
pool, err := r.store.CreateEntityPool(ctx, entity, createPoolParams)
if err != nil {
return params.Pool{}, errors.Wrap(err, "creating pool")
return params.Pool{}, fmt.Errorf("error creating pool: %w", err)
}
return pool, nil
@ -271,7 +270,7 @@ func (r *Runner) GetOrgPoolByID(ctx context.Context, orgID, poolID string) (para
pool, err := r.store.GetEntityPool(ctx, entity, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
return params.Pool{}, fmt.Errorf("error fetching pool: %w", err)
}
return pool, nil
@ -290,7 +289,7 @@ func (r *Runner) DeleteOrgPool(ctx context.Context, orgID, poolID string) error
pool, err := r.store.GetEntityPool(ctx, entity, poolID)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
return nil
}
@ -306,7 +305,7 @@ func (r *Runner) DeleteOrgPool(ctx context.Context, orgID, poolID string) error
}
if err := r.store.DeleteEntityPool(ctx, entity, poolID); err != nil {
return errors.Wrap(err, "deleting pool")
return fmt.Errorf("error deleting pool: %w", err)
}
return nil
}
@ -321,7 +320,7 @@ func (r *Runner) ListOrgPools(ctx context.Context, orgID string) ([]params.Pool,
}
pools, err := r.store.ListEntityPools(ctx, entity)
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
return nil, fmt.Errorf("error fetching pools: %w", err)
}
return pools, nil
}
@ -338,7 +337,7 @@ func (r *Runner) UpdateOrgPool(ctx context.Context, orgID, poolID string, param
pool, err := r.store.GetEntityPool(ctx, entity, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
return params.Pool{}, fmt.Errorf("error fetching pool: %w", err)
}
maxRunners := pool.MaxRunners
@ -357,7 +356,7 @@ func (r *Runner) UpdateOrgPool(ctx context.Context, orgID, poolID string, param
newPool, err := r.store.UpdateEntityPool(ctx, entity, poolID, param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "updating pool")
return params.Pool{}, fmt.Errorf("error updating pool: %w", err)
}
return newPool, nil
}
@ -374,7 +373,7 @@ func (r *Runner) ListOrgInstances(ctx context.Context, orgID string) ([]params.I
instances, err := r.store.ListEntityInstances(ctx, entity)
if err != nil {
return []params.Instance{}, errors.Wrap(err, "fetching instances")
return []params.Instance{}, fmt.Errorf("error fetching instances: %w", err)
}
return instances, nil
}
@ -385,12 +384,12 @@ func (r *Runner) findOrgPoolManager(name, endpointName string) (common.PoolManag
org, err := r.store.GetOrganization(r.ctx, name, endpointName)
if err != nil {
return nil, errors.Wrap(err, "fetching org")
return nil, fmt.Errorf("error fetching org: %w", err)
}
poolManager, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {
return nil, errors.Wrap(err, "fetching pool manager for org")
return nil, fmt.Errorf("error fetching pool manager for org: %w", err)
}
return poolManager, nil
}
@ -402,17 +401,17 @@ func (r *Runner) InstallOrgWebhook(ctx context.Context, orgID string, param para
org, err := r.store.GetOrganizationByID(ctx, orgID)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "fetching org")
return params.HookInfo{}, fmt.Errorf("error fetching org: %w", err)
}
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "fetching pool manager for org")
return params.HookInfo{}, fmt.Errorf("error fetching pool manager for org: %w", err)
}
info, err := poolMgr.InstallWebhook(ctx, param)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "installing webhook")
return params.HookInfo{}, fmt.Errorf("error installing webhook: %w", err)
}
return info, nil
}
@ -424,16 +423,16 @@ func (r *Runner) UninstallOrgWebhook(ctx context.Context, orgID string) error {
org, err := r.store.GetOrganizationByID(ctx, orgID)
if err != nil {
return errors.Wrap(err, "fetching org")
return fmt.Errorf("error fetching org: %w", err)
}
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {
return errors.Wrap(err, "fetching pool manager for org")
return fmt.Errorf("error fetching pool manager for org: %w", err)
}
if err := poolMgr.UninstallWebhook(ctx); err != nil {
return errors.Wrap(err, "uninstalling webhook")
return fmt.Errorf("error uninstalling webhook: %w", err)
}
return nil
}
@ -445,17 +444,17 @@ func (r *Runner) GetOrgWebhookInfo(ctx context.Context, orgID string) (params.Ho
org, err := r.store.GetOrganizationByID(ctx, orgID)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "fetching org")
return params.HookInfo{}, fmt.Errorf("error fetching org: %w", err)
}
poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "fetching pool manager for org")
return params.HookInfo{}, fmt.Errorf("error fetching pool manager for org: %w", err)
}
info, err := poolMgr.GetWebhookInfo(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "fetching webhook info")
return params.HookInfo{}, fmt.Errorf("error fetching webhook info: %w", err)
}
return info, nil
}

View file

@ -16,10 +16,10 @@ package runner
import (
"context"
"errors"
"fmt"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
@ -224,7 +224,7 @@ func (s *OrgTestSuite) TestCreateOrganizationPoolMgrFailed() {
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Equal(fmt.Sprintf("creating org pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
s.Require().Equal(fmt.Sprintf("error creating org pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
}
func (s *OrgTestSuite) TestCreateOrganizationStartPoolMgrFailed() {
@ -236,7 +236,7 @@ func (s *OrgTestSuite) TestCreateOrganizationStartPoolMgrFailed() {
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Equal(fmt.Sprintf("starting org pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
s.Require().Equal(fmt.Sprintf("error starting org pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
}
func (s *OrgTestSuite) TestListOrganizations() {
@ -338,7 +338,7 @@ func (s *OrgTestSuite) TestDeleteOrganization() {
s.Require().Nil(err)
_, err = s.Fixtures.Store.GetOrganizationByID(s.Fixtures.AdminContext, s.Fixtures.StoreOrgs["test-org-3"].ID)
s.Require().Equal("fetching org: not found", err.Error())
s.Require().Equal("error fetching org: not found", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrganizationErrUnauthorized() {
@ -368,7 +368,7 @@ func (s *OrgTestSuite) TestDeleteOrganizationPoolMgrFailed() {
err := s.Runner.DeleteOrganization(s.Fixtures.AdminContext, s.Fixtures.StoreOrgs["test-org-1"].ID, true)
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
s.Require().Equal(fmt.Sprintf("deleting org pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
s.Require().Equal(fmt.Sprintf("error deleting org pool manager: %s", s.Fixtures.ErrMock.Error()), err.Error())
}
func (s *OrgTestSuite) TestUpdateOrganization() {
@ -502,7 +502,7 @@ func (s *OrgTestSuite) TestDeleteOrgPool() {
s.Require().Nil(err)
_, err = s.Fixtures.Store.GetEntityPool(s.Fixtures.AdminContext, entity, pool.ID)
s.Require().Equal("fetching pool: finding pool: not found", err.Error())
s.Require().Equal("fetching pool: error finding pool: not found", err.Error())
}
func (s *OrgTestSuite) TestDeleteOrgPoolErrUnauthorized() {

75
runner/pool/cache.go Normal file
View file

@ -0,0 +1,75 @@
package pool
import (
"sort"
"strings"
"sync"
"sync/atomic"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
)
type poolCacheStore interface {
Next() (params.Pool, error)
Reset()
Len() int
}
type poolRoundRobin struct {
pools []params.Pool
next uint32
}
func (p *poolRoundRobin) Next() (params.Pool, error) {
if len(p.pools) == 0 {
return params.Pool{}, runnerErrors.ErrNoPoolsAvailable
}
n := atomic.AddUint32(&p.next, 1)
return p.pools[(int(n)-1)%len(p.pools)], nil
}
func (p *poolRoundRobin) Len() int {
return len(p.pools)
}
func (p *poolRoundRobin) Reset() {
atomic.StoreUint32(&p.next, 0)
}
type poolsForTags struct {
pools sync.Map
poolCacheType params.PoolBalancerType
}
func (p *poolsForTags) Get(tags []string) (poolCacheStore, bool) {
sort.Strings(tags)
key := strings.Join(tags, "^")
v, ok := p.pools.Load(key)
if !ok {
return nil, false
}
poolCache := v.(*poolRoundRobin)
if p.poolCacheType == params.PoolBalancerTypePack {
// When we service a list of jobs, we want to try each pool in turn
// for each job. Pools are sorted by priority so we always start from the
// highest priority pool and move on to the next if the first one is full.
poolCache.Reset()
}
return poolCache, true
}
func (p *poolsForTags) Add(tags []string, pools []params.Pool) poolCacheStore {
sort.Slice(pools, func(i, j int) bool {
return pools[i].Priority > pools[j].Priority
})
sort.Strings(tags)
key := strings.Join(tags, "^")
poolRR := &poolRoundRobin{pools: pools}
v, _ := p.pools.LoadOrStore(key, poolRR)
return v.(*poolRoundRobin)
}

View file

@ -14,79 +14,15 @@
package pool
import (
"context"
"net/http"
"net/url"
"strings"
"github.com/google/go-github/v72/github"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
)
func validateHookRequest(controllerID, baseURL string, allHooks []*github.Hook, req *github.Hook) error {
parsed, err := url.Parse(baseURL)
if err != nil {
return errors.Wrap(err, "parsing webhook url")
}
partialMatches := []string{}
for _, hook := range allHooks {
hookURL := strings.ToLower(hook.Config.GetURL())
if hookURL == "" {
continue
}
if hook.Config.GetURL() == req.Config.GetURL() {
return runnerErrors.NewConflictError("hook already installed")
} else if strings.Contains(hookURL, controllerID) || strings.Contains(hookURL, parsed.Hostname()) {
partialMatches = append(partialMatches, hook.Config.GetURL())
}
}
if len(partialMatches) > 0 {
return runnerErrors.NewConflictError("a webhook containing the controller ID or hostname of this contreoller is already installed on this repository")
}
return nil
type RunnerLabels struct {
ID int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
}
func hookToParamsHookInfo(hook *github.Hook) params.HookInfo {
hookURL := hook.Config.GetURL()
insecureSSLConfig := hook.Config.GetInsecureSSL()
insecureSSL := insecureSSLConfig == "1"
return params.HookInfo{
ID: *hook.ID,
URL: hookURL,
Events: hook.Events,
Active: *hook.Active,
InsecureSSL: insecureSSL,
}
}
func (r *basePoolManager) listHooks(ctx context.Context) ([]*github.Hook, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allHooks []*github.Hook
for {
hooks, ghResp, err := r.ghcli.ListEntityHooks(ctx, &opts)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusNotFound {
return nil, runnerErrors.NewBadRequestError("repository not found or your PAT does not have access to manage webhooks")
}
return nil, errors.Wrap(err, "fetching hooks")
}
allHooks = append(allHooks, hooks...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allHooks, nil
type forgeRunner struct {
ID int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"`
Labels []RunnerLabels `json:"labels,omitempty"`
}

View file

@ -17,6 +17,7 @@ package pool
import (
"context"
"crypto/rand"
"errors"
"fmt"
"log/slog"
"math"
@ -29,7 +30,6 @@ import (
"github.com/google/go-github/v72/github"
"github.com/google/uuid"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
@ -44,6 +44,7 @@ import (
"github.com/cloudbase/garm/runner/common"
garmUtil "github.com/cloudbase/garm/util"
ghClient "github.com/cloudbase/garm/util/github"
"github.com/cloudbase/garm/util/github/scalesets"
)
var (
@ -68,19 +69,24 @@ const (
)
func NewEntityPoolManager(ctx context.Context, entity params.ForgeEntity, instanceTokenGetter auth.InstanceTokenGetter, providers map[string]common.Provider, store dbCommon.Store) (common.PoolManager, error) {
ctx = garmUtil.WithSlogContext(ctx, slog.Any("pool_mgr", entity.String()), slog.Any("pool_type", entity.EntityType))
ctx = garmUtil.WithSlogContext(
ctx,
slog.Any("pool_mgr", entity.String()),
slog.Any("endpoint", entity.Credentials.Endpoint.Name),
slog.Any("pool_type", entity.EntityType),
)
ghc, err := ghClient.Client(ctx, entity)
if err != nil {
return nil, errors.Wrap(err, "getting github client")
return nil, fmt.Errorf("error getting github client: %w", err)
}
if entity.WebhookSecret == "" {
return nil, errors.New("webhook secret is empty")
return nil, fmt.Errorf("webhook secret is empty")
}
controllerInfo, err := store.ControllerInfo()
if err != nil {
return nil, errors.Wrap(err, "getting controller info")
return nil, fmt.Errorf("error getting controller info: %w", err)
}
consumerID := fmt.Sprintf("pool-manager-%s-%s", entity.String(), entity.Credentials.Endpoint.Name)
@ -90,20 +96,28 @@ func NewEntityPoolManager(ctx context.Context, entity params.ForgeEntity, instan
composeWatcherFilters(entity),
)
if err != nil {
return nil, errors.Wrap(err, "registering consumer")
return nil, fmt.Errorf("error registering consumer: %w", err)
}
wg := &sync.WaitGroup{}
backoff, err := locking.NewInstanceDeleteBackoff(ctx)
if err != nil {
return nil, errors.Wrap(err, "creating backoff")
return nil, fmt.Errorf("error creating backoff: %w", err)
}
var scaleSetCli *scalesets.ScaleSetClient
if entity.Credentials.ForgeType == params.GithubEndpointType {
scaleSetCli, err = scalesets.NewClient(ghc)
if err != nil {
return nil, fmt.Errorf("failed to get scalesets client: %w", err)
}
}
repo := &basePoolManager{
ctx: ctx,
consumerID: consumerID,
entity: entity,
ghcli: ghc,
scaleSetClient: scaleSetCli,
controllerInfo: controllerInfo,
instanceTokenGetter: instanceTokenGetter,
@ -122,6 +136,7 @@ type basePoolManager struct {
consumerID string
entity params.ForgeEntity
ghcli common.GithubClient
scaleSetClient *scalesets.ScaleSetClient
controllerInfo params.ControllerInfo
instanceTokenGetter auth.InstanceTokenGetter
consumer dbCommon.Consumer
@ -153,7 +168,7 @@ func (r *basePoolManager) getProviderBaseParams(pool params.Pool) common.Provide
func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
if err := r.ValidateOwner(job); err != nil {
slog.ErrorContext(r.ctx, "failed to validate owner", "error", err)
return errors.Wrap(err, "validating owner")
return fmt.Errorf("error validating owner: %w", err)
}
// we see events where the lables seem to be missing. We should ignore these
@ -166,24 +181,24 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
jobParams, err := r.paramsWorkflowJobToParamsJob(job)
if err != nil {
slog.ErrorContext(r.ctx, "failed to convert job to params", "error", err)
return errors.Wrap(err, "converting job to params")
return fmt.Errorf("error converting job to params: %w", err)
}
var triggeredBy int64
defer func() {
if jobParams.ID == 0 {
if jobParams.WorkflowJobID == 0 {
return
}
// we're updating the job in the database, regardless of whether it was successful or not.
// or if it was meant for this pool or not. Github will send the same job data to all hierarchies
// that have been configured to work with garm. Updating the job at all levels should yield the same
// outcome in the db.
_, err := r.store.GetJobByID(r.ctx, jobParams.ID)
_, err := r.store.GetJobByID(r.ctx, jobParams.WorkflowJobID)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to get job",
"job_id", jobParams.ID)
"job_id", jobParams.WorkflowJobID)
return
}
// This job is new to us. Check if we have a pool that can handle it.
@ -198,10 +213,10 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
if _, jobErr := r.store.CreateOrUpdateJob(r.ctx, jobParams); jobErr != nil {
slog.With(slog.Any("error", jobErr)).ErrorContext(
r.ctx, "failed to update job", "job_id", jobParams.ID)
r.ctx, "failed to update job", "job_id", jobParams.WorkflowJobID)
}
if triggeredBy != 0 && jobParams.ID != triggeredBy {
if triggeredBy != 0 && jobParams.WorkflowJobID != triggeredBy {
// The triggeredBy value is only set by the "in_progress" webhook. The runner that
// transitioned to in_progress was created as a result of a different queued job. If that job is
// still queued and we don't remove the lock, it will linger until the lock timeout is reached.
@ -244,7 +259,7 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to update runner status",
"runner_name", util.SanitizeLogEntry(jobParams.RunnerName))
return errors.Wrap(err, "updating runner")
return fmt.Errorf("error updating runner: %w", err)
}
slog.DebugContext(
r.ctx, "marking instance as pending_delete",
@ -256,7 +271,7 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to update runner status",
"runner_name", util.SanitizeLogEntry(jobParams.RunnerName))
return errors.Wrap(err, "updating runner")
return fmt.Errorf("error updating runner: %w", err)
}
case "in_progress":
fromCache, ok := cache.GetInstanceCache(jobParams.RunnerName)
@ -279,7 +294,7 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to update runner status",
"runner_name", util.SanitizeLogEntry(jobParams.RunnerName))
return errors.Wrap(err, "updating runner")
return fmt.Errorf("error updating runner: %w", err)
}
// Set triggeredBy here so we break the lock on any potential queued job.
triggeredBy = jobIDFromLabels(instance.AditionalLabels)
@ -388,10 +403,10 @@ func (r *basePoolManager) updateTools() error {
// happens, github will remove the ephemeral worker and send a webhook our way.
// If we were offline and did not process the webhook, the instance will linger.
// We need to remove it from the provider and database.
func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runner) error {
func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []forgeRunner) error {
dbInstances, err := r.store.ListEntityInstances(r.ctx, r.entity)
if err != nil {
return errors.Wrap(err, "fetching instances from db")
return fmt.Errorf("error fetching instances from db: %w", err)
}
runnerNames := map[string]bool{}
@ -399,10 +414,10 @@ func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runne
if !isManagedRunner(labelsFromRunner(run), r.controllerInfo.ControllerID.String()) {
slog.DebugContext(
r.ctx, "runner is not managed by a pool we manage",
"runner_name", run.GetName())
"runner_name", run.Name)
continue
}
runnerNames[*run.Name] = true
runnerNames[run.Name] = true
}
for _, instance := range dbInstances {
@ -430,7 +445,7 @@ func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runne
}
pool, err := r.store.GetEntityPool(r.ctx, r.entity, instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching instance pool info")
return fmt.Errorf("error fetching instance pool info: %w", err)
}
switch instance.RunnerStatus {
@ -458,7 +473,7 @@ func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runne
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to update runner",
"runner_name", instance.Name)
return errors.Wrap(err, "updating runner")
return fmt.Errorf("error updating runner: %w", err)
}
}
}
@ -468,21 +483,21 @@ func (r *basePoolManager) cleanupOrphanedProviderRunners(runners []*github.Runne
// reapTimedOutRunners will mark as pending_delete any runner that has a status
// of "running" in the provider, but that has not registered with Github, and has
// received no new updates in the configured timeout interval.
func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
func (r *basePoolManager) reapTimedOutRunners(runners []forgeRunner) error {
dbInstances, err := r.store.ListEntityInstances(r.ctx, r.entity)
if err != nil {
return errors.Wrap(err, "fetching instances from db")
return fmt.Errorf("error fetching instances from db: %w", err)
}
runnersByName := map[string]*github.Runner{}
runnersByName := map[string]forgeRunner{}
for _, run := range runners {
if !isManagedRunner(labelsFromRunner(run), r.controllerInfo.ControllerID.String()) {
slog.DebugContext(
r.ctx, "runner is not managed by a pool we manage",
"runner_name", run.GetName())
"runner_name", run.Name)
continue
}
runnersByName[*run.Name] = run
runnersByName[run.Name] = run
}
for _, instance := range dbInstances {
@ -505,7 +520,7 @@ func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
pool, err := r.store.GetEntityPool(r.ctx, r.entity, instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching instance pool info")
return fmt.Errorf("error fetching instance pool info: %w", err)
}
if time.Since(instance.UpdatedAt).Minutes() < float64(pool.RunnerTimeout()) {
continue
@ -516,7 +531,7 @@ func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
// * The runner managed to join github, but the setup process failed later and the runner
// never started on the instance.
// * A JIT config was created, but the runner never joined github.
if runner, ok := runnersByName[instance.Name]; !ok || runner.GetStatus() == "offline" {
if runner, ok := runnersByName[instance.Name]; !ok || runner.Status == "offline" {
slog.InfoContext(
r.ctx, "reaping timed-out/failed runner",
"runner_name", instance.Name)
@ -524,7 +539,7 @@ func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to update runner status",
"runner_name", instance.Name)
return errors.Wrap(err, "updating runner")
return fmt.Errorf("error updating runner: %w", err)
}
}
}
@ -535,39 +550,39 @@ func (r *basePoolManager) reapTimedOutRunners(runners []*github.Runner) error {
// as offline and for which we no longer have a local instance.
// This may happen if someone manually deletes the instance in the provider. We need to
// first remove the instance from github, and then from our database.
func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner) error {
func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []forgeRunner) error {
poolInstanceCache := map[string][]commonParams.ProviderInstance{}
g, ctx := errgroup.WithContext(r.ctx)
for _, runner := range runners {
if !isManagedRunner(labelsFromRunner(runner), r.controllerInfo.ControllerID.String()) {
slog.DebugContext(
r.ctx, "runner is not managed by a pool we manage",
"runner_name", runner.GetName())
"runner_name", runner.Name)
continue
}
status := runner.GetStatus()
status := runner.Status
if status != "offline" {
// Runner is online. Ignore it.
continue
}
dbInstance, err := r.store.GetInstanceByName(r.ctx, *runner.Name)
dbInstance, err := r.store.GetInstance(r.ctx, runner.Name)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "fetching instance from DB")
return fmt.Errorf("error fetching instance from DB: %w", err)
}
// We no longer have a DB entry for this instance, and the runner appears offline in github.
// Previous forceful removal may have failed?
slog.InfoContext(
r.ctx, "Runner has no database entry in garm, removing from github",
"runner_name", runner.GetName())
if err := r.ghcli.RemoveEntityRunner(r.ctx, runner.GetID()); err != nil {
"runner_name", runner.Name)
if err := r.ghcli.RemoveEntityRunner(r.ctx, runner.ID); err != nil {
// Removed in the meantime?
if errors.Is(err, runnerErrors.ErrNotFound) {
continue
}
return errors.Wrap(err, "removing runner")
return fmt.Errorf("error removing runner: %w", err)
}
continue
}
@ -601,7 +616,7 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
pool, err := r.store.GetEntityPool(r.ctx, r.entity, dbInstance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
// check if the provider still has the instance.
@ -623,7 +638,7 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
}
poolInstances, err = provider.ListInstances(r.ctx, pool.ID, listInstancesParams)
if err != nil {
return errors.Wrapf(err, "fetching instances for pool %s", dbInstance.PoolID)
return fmt.Errorf("error fetching instances for pool %s: %w", dbInstance.PoolID, err)
}
poolInstanceCache[dbInstance.PoolID] = poolInstances
}
@ -650,14 +665,14 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
slog.InfoContext(
r.ctx, "Runner instance is no longer on the provider, removing from github",
"runner_name", dbInstance.Name)
if err := r.ghcli.RemoveEntityRunner(r.ctx, runner.GetID()); err != nil {
if err := r.ghcli.RemoveEntityRunner(r.ctx, runner.ID); err != nil {
// Removed in the meantime?
if errors.Is(err, runnerErrors.ErrNotFound) {
slog.DebugContext(
r.ctx, "runner disappeared from github",
"runner_name", dbInstance.Name)
} else {
return errors.Wrap(err, "removing runner from github")
return fmt.Errorf("error removing runner from github: %w", err)
}
}
// Remove the database entry for the runner.
@ -665,7 +680,7 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
r.ctx, "Removing from database",
"runner_name", dbInstance.Name)
if err := r.store.DeleteInstance(ctx, dbInstance.PoolID, dbInstance.Name); err != nil {
return errors.Wrap(err, "removing runner from database")
return fmt.Errorf("error removing runner from database: %w", err)
}
deleteMux = true
return nil
@ -691,13 +706,13 @@ func (r *basePoolManager) cleanupOrphanedGithubRunners(runners []*github.Runner)
},
}
if err := provider.Start(r.ctx, dbInstance.ProviderID, startParams); err != nil {
return errors.Wrapf(err, "starting instance %s", dbInstance.ProviderID)
return fmt.Errorf("error starting instance %s: %w", dbInstance.ProviderID, err)
}
return nil
})
}
if err := r.waitForErrorGroupOrContextCancelled(g); err != nil {
return errors.Wrap(err, "removing orphaned github runners")
return fmt.Errorf("error removing orphaned github runners: %w", err)
}
return nil
}
@ -727,7 +742,7 @@ func (r *basePoolManager) setInstanceRunnerStatus(runnerName string, status para
}
instance, err := r.store.UpdateInstance(r.ctx, runnerName, updateParams)
if err != nil {
return params.Instance{}, errors.Wrap(err, "updating runner state")
return params.Instance{}, fmt.Errorf("error updating runner state: %w", err)
}
return instance, nil
}
@ -740,7 +755,7 @@ func (r *basePoolManager) setInstanceStatus(runnerName string, status commonPara
instance, err := r.store.UpdateInstance(r.ctx, runnerName, updateParams)
if err != nil {
return params.Instance{}, errors.Wrap(err, "updating runner state")
return params.Instance{}, fmt.Errorf("error updating runner state: %w", err)
}
return instance, nil
}
@ -748,7 +763,7 @@ func (r *basePoolManager) setInstanceStatus(runnerName string, status commonPara
func (r *basePoolManager) AddRunner(ctx context.Context, poolID string, aditionalLabels []string) (err error) {
pool, err := r.store.GetEntityPool(r.ctx, r.entity, poolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
provider, ok := r.providers[pool.ProviderName]
@ -762,12 +777,10 @@ func (r *basePoolManager) AddRunner(ctx context.Context, poolID string, aditiona
jitConfig := make(map[string]string)
var runner *github.Runner
if !provider.DisableJITConfig() {
// Attempt to create JIT config
if !provider.DisableJITConfig() && r.entity.Credentials.ForgeType != params.GiteaEndpointType {
jitConfig, runner, err = r.ghcli.GetEntityJITConfig(ctx, name, pool, labels)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get JIT config, falling back to registration token")
return fmt.Errorf("failed to generate JIT config: %w", err)
}
}
@ -791,7 +804,7 @@ func (r *basePoolManager) AddRunner(ctx context.Context, poolID string, aditiona
instance, err := r.store.CreateInstance(r.ctx, poolID, createParams)
if err != nil {
return errors.Wrap(err, "creating instance")
return fmt.Errorf("error creating instance: %w", err)
}
defer func() {
@ -859,7 +872,7 @@ func (r *basePoolManager) getLabelsForInstance(pool params.Pool) []string {
func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error {
pool, err := r.store.GetEntityPool(r.ctx, r.entity, instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
provider, ok := r.providers[pool.ProviderName]
@ -871,7 +884,7 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
jwtToken, err := r.instanceTokenGetter.NewInstanceJWTToken(instance, r.entity, pool.PoolType(), jwtValidity)
if err != nil {
return errors.Wrap(err, "fetching instance jwt token")
return fmt.Errorf("error fetching instance jwt token: %w", err)
}
hasJITConfig := len(instance.JitConfiguration) > 0
@ -928,7 +941,7 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
providerInstance, err := provider.CreateInstance(r.ctx, bootstrapArgs, createInstanceParams)
if err != nil {
instanceIDToDelete = instance.Name
return errors.Wrap(err, "creating instance")
return fmt.Errorf("error creating instance: %w", err)
}
if providerInstance.Status == commonParams.InstanceError {
@ -940,7 +953,7 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
updateInstanceArgs := r.updateArgsFromProviderInstance(providerInstance)
if _, err := r.store.UpdateInstance(r.ctx, instance.Name, updateInstanceArgs); err != nil {
return errors.Wrap(err, "updating instance")
return fmt.Errorf("error updating instance: %w", err)
}
return nil
}
@ -961,11 +974,11 @@ func (r *basePoolManager) addInstanceToProvider(instance params.Instance) error
func (r *basePoolManager) paramsWorkflowJobToParamsJob(job params.WorkflowJob) (params.Job, error) {
asUUID, err := uuid.Parse(r.ID())
if err != nil {
return params.Job{}, errors.Wrap(err, "parsing pool ID as UUID")
return params.Job{}, fmt.Errorf("error parsing pool ID as UUID: %w", err)
}
jobParams := params.Job{
ID: job.WorkflowJob.ID,
WorkflowJobID: job.WorkflowJob.ID,
Action: job.Action,
RunID: job.WorkflowJob.RunID,
Status: job.WorkflowJob.Status,
@ -990,7 +1003,7 @@ func (r *basePoolManager) paramsWorkflowJobToParamsJob(job params.WorkflowJob) (
case params.ForgeEntityTypeOrganization:
jobParams.OrgID = &asUUID
default:
return jobParams, errors.Errorf("unknown pool type: %s", r.entity.EntityType)
return jobParams, fmt.Errorf("unknown pool type: %s", r.entity.EntityType)
}
return jobParams, nil
@ -1096,15 +1109,15 @@ func (r *basePoolManager) scaleDownOnePool(ctx context.Context, pool params.Pool
// instead of returning a bunch of results and filtering manually.
queued, err := r.store.ListEntityJobsByStatus(r.ctx, r.entity.EntityType, r.entity.ID, params.JobStatusQueued)
if err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "listing queued jobs")
return fmt.Errorf("error listing queued jobs: %w", err)
}
for _, job := range queued {
if time.Since(job.CreatedAt).Minutes() > 10 && pool.HasRequiredLabels(job.Labels) {
if err := r.store.DeleteJob(ctx, job.ID); err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
if err := r.store.DeleteJob(ctx, job.WorkflowJobID); err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to delete job",
"job_id", job.ID)
"job_id", job.WorkflowJobID)
}
}
}
@ -1336,7 +1349,7 @@ func (r *basePoolManager) ensureMinIdleRunners() error {
func (r *basePoolManager) deleteInstanceFromProvider(ctx context.Context, instance params.Instance) error {
pool, err := r.store.GetEntityPool(r.ctx, r.entity, instance.PoolID)
if err != nil {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
provider, ok := r.providers[instance.ProviderName]
@ -1362,13 +1375,16 @@ func (r *basePoolManager) deleteInstanceFromProvider(ctx context.Context, instan
},
}
if err := provider.DeleteInstance(ctx, identifier, deleteInstanceParams); err != nil {
return errors.Wrap(err, "removing instance")
return fmt.Errorf("error removing instance: %w", err)
}
return nil
}
func (r *basePoolManager) sleepWithCancel(sleepTime time.Duration) (canceled bool) {
if sleepTime == 0 {
return false
}
ticker := time.NewTicker(sleepTime)
defer ticker.Stop()
@ -1575,7 +1591,7 @@ func (r *basePoolManager) Wait() error {
select {
case <-done:
case <-timer.C:
return errors.Wrap(runnerErrors.ErrTimeout, "waiting for pool to stop")
return runnerErrors.NewTimeoutError("waiting for pool to stop")
}
return nil
}
@ -1599,13 +1615,13 @@ func (r *basePoolManager) runnerCleanup() (err error) {
return nil
}
func (r *basePoolManager) cleanupOrphanedRunners(runners []*github.Runner) error {
func (r *basePoolManager) cleanupOrphanedRunners(runners []forgeRunner) error {
if err := r.cleanupOrphanedProviderRunners(runners); err != nil {
return errors.Wrap(err, "cleaning orphaned instances")
return fmt.Errorf("error cleaning orphaned instances: %w", err)
}
if err := r.cleanupOrphanedGithubRunners(runners); err != nil {
return errors.Wrap(err, "cleaning orphaned github runners")
return fmt.Errorf("error cleaning orphaned github runners: %w", err)
}
return nil
@ -1685,10 +1701,10 @@ func (r *basePoolManager) DeleteRunner(runner params.Instance, forceRemove, bypa
if bypassGHUnauthorizedError {
slog.Info("bypass github unauthorized error is set, marking runner for deletion")
} else {
return errors.Wrap(err, "removing runner")
return fmt.Errorf("error removing runner: %w", err)
}
} else {
return errors.Wrap(err, "removing runner")
return fmt.Errorf("error removing runner: %w", err)
}
}
}
@ -1706,7 +1722,7 @@ func (r *basePoolManager) DeleteRunner(runner params.Instance, forceRemove, bypa
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to update runner",
"runner_name", runner.Name)
return errors.Wrap(err, "updating runner")
return fmt.Errorf("error updating runner: %w", err)
}
return nil
}
@ -1737,7 +1753,7 @@ func (r *basePoolManager) DeleteRunner(runner params.Instance, forceRemove, bypa
func (r *basePoolManager) consumeQueuedJobs() error {
queued, err := r.store.ListEntityJobsByStatus(r.ctx, r.entity.EntityType, r.entity.ID, params.JobStatusQueued)
if err != nil {
return errors.Wrap(err, "listing queued jobs")
return fmt.Errorf("error listing queued jobs: %w", err)
}
poolsCache := poolsForTags{
@ -1752,7 +1768,7 @@ func (r *basePoolManager) consumeQueuedJobs() error {
// Job was handled by us or another entity.
slog.DebugContext(
r.ctx, "job is locked",
"job_id", job.ID,
"job_id", job.WorkflowJobID,
"locking_entity", job.LockedBy.String())
continue
}
@ -1761,7 +1777,7 @@ func (r *basePoolManager) consumeQueuedJobs() error {
// give the idle runners a chance to pick up the job.
slog.DebugContext(
r.ctx, "job backoff not reached", "backoff_interval", r.controllerInfo.MinimumJobAgeBackoff,
"job_id", job.ID)
"job_id", job.WorkflowJobID)
continue
}
@ -1769,12 +1785,12 @@ func (r *basePoolManager) consumeQueuedJobs() error {
// Job is still queued in our db, 10 minutes after a matching runner
// was spawned. Unlock it and try again. A different job may have picked up
// the runner.
if err := r.store.UnlockJob(r.ctx, job.ID, r.ID()); err != nil {
if err := r.store.UnlockJob(r.ctx, job.WorkflowJobID, r.ID()); err != nil {
// nolint:golangci-lint,godox
// TODO: Implament a cache? Should we return here?
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to unlock job",
"job_id", job.ID)
"job_id", job.WorkflowJobID)
continue
}
}
@ -1787,7 +1803,7 @@ func (r *basePoolManager) consumeQueuedJobs() error {
// runner.
slog.DebugContext(
r.ctx, "job is locked by us",
"job_id", job.ID)
"job_id", job.WorkflowJobID)
continue
}
@ -1808,29 +1824,29 @@ func (r *basePoolManager) consumeQueuedJobs() error {
}
runnerCreated := false
if err := r.store.LockJob(r.ctx, job.ID, r.ID()); err != nil {
if err := r.store.LockJob(r.ctx, job.WorkflowJobID, r.ID()); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "could not lock job",
"job_id", job.ID)
"job_id", job.WorkflowJobID)
continue
}
jobLabels := []string{
fmt.Sprintf("%s=%d", jobLabelPrefix, job.ID),
fmt.Sprintf("%s=%d", jobLabelPrefix, job.WorkflowJobID),
}
for i := 0; i < poolRR.Len(); i++ {
pool, err := poolRR.Next()
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "could not find a pool to create a runner for job",
"job_id", job.ID)
"job_id", job.WorkflowJobID)
break
}
slog.InfoContext(
r.ctx, "attempting to create a runner in pool",
"pool_id", pool.ID,
"job_id", job.ID)
"job_id", job.WorkflowJobID)
if err := r.addRunnerToPool(pool, jobLabels); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "could not add runner to pool",
@ -1839,7 +1855,7 @@ func (r *basePoolManager) consumeQueuedJobs() error {
}
slog.DebugContext(r.ctx, "a new runner was added as a response to queued job",
"pool_id", pool.ID,
"job_id", job.ID)
"job_id", job.WorkflowJobID)
runnerCreated = true
break
}
@ -1847,12 +1863,12 @@ func (r *basePoolManager) consumeQueuedJobs() error {
if !runnerCreated {
slog.WarnContext(
r.ctx, "could not create a runner for job; unlocking",
"job_id", job.ID)
if err := r.store.UnlockJob(r.ctx, job.ID, r.ID()); err != nil {
"job_id", job.WorkflowJobID)
if err := r.store.UnlockJob(r.ctx, job.WorkflowJobID, r.ID()); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
r.ctx, "failed to unlock job",
"job_id", job.ID)
return errors.Wrap(err, "unlocking job")
"job_id", job.WorkflowJobID)
return fmt.Errorf("error unlocking job: %w", err)
}
}
}
@ -1866,12 +1882,12 @@ func (r *basePoolManager) consumeQueuedJobs() error {
func (r *basePoolManager) UninstallWebhook(ctx context.Context) error {
if r.controllerInfo.ControllerWebhookURL == "" {
return errors.Wrap(runnerErrors.ErrBadRequest, "controller webhook url is empty")
return runnerErrors.NewBadRequestError("controller webhook url is empty")
}
allHooks, err := r.listHooks(ctx)
if err != nil {
return errors.Wrap(err, "listing hooks")
return fmt.Errorf("error listing hooks: %w", err)
}
var controllerHookID int64
@ -1909,16 +1925,16 @@ func (r *basePoolManager) UninstallWebhook(ctx context.Context) error {
func (r *basePoolManager) InstallHook(ctx context.Context, req *github.Hook) (params.HookInfo, error) {
allHooks, err := r.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
return params.HookInfo{}, fmt.Errorf("error listing hooks: %w", err)
}
if err := validateHookRequest(r.controllerInfo.ControllerID.String(), r.controllerInfo.WebhookURL, allHooks, req); err != nil {
return params.HookInfo{}, errors.Wrap(err, "validating hook request")
return params.HookInfo{}, fmt.Errorf("error validating hook request: %w", err)
}
hook, err := r.ghcli.CreateEntityHook(ctx, req)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "creating entity hook")
return params.HookInfo{}, fmt.Errorf("error creating entity hook: %w", err)
}
if _, err := r.ghcli.PingEntityHook(ctx, hook.GetID()); err != nil {
@ -1933,7 +1949,7 @@ func (r *basePoolManager) InstallHook(ctx context.Context, req *github.Hook) (pa
func (r *basePoolManager) InstallWebhook(ctx context.Context, param params.InstallWebhookParams) (params.HookInfo, error) {
if r.controllerInfo.ControllerWebhookURL == "" {
return params.HookInfo{}, errors.Wrap(runnerErrors.ErrBadRequest, "controller webhook url is empty")
return params.HookInfo{}, runnerErrors.NewBadRequestError("controller webhook url is empty")
}
insecureSSL := "0"
@ -1981,9 +1997,9 @@ func (r *basePoolManager) GithubRunnerRegistrationToken() (string, error) {
tk, ghResp, err := r.ghcli.CreateEntityRegistrationToken(r.ctx)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return "", errors.Wrap(runnerErrors.ErrUnauthorized, "fetching token")
return "", runnerErrors.NewUnauthorizedError("error fetching token")
}
return "", errors.Wrap(err, "creating runner token")
return "", fmt.Errorf("error creating runner token: %w", err)
}
return *tk.Token, nil
}
@ -1992,9 +2008,9 @@ func (r *basePoolManager) FetchTools() ([]commonParams.RunnerApplicationDownload
tools, ghResp, err := r.ghcli.ListEntityRunnerApplicationDownloads(r.ctx)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching tools")
return nil, runnerErrors.NewUnauthorizedError("error fetching tools")
}
return nil, errors.Wrap(err, "fetching runner tools")
return nil, fmt.Errorf("error fetching runner tools: %w", err)
}
ret := []commonParams.RunnerApplicationDownload{}
@ -2007,36 +2023,10 @@ func (r *basePoolManager) FetchTools() ([]commonParams.RunnerApplicationDownload
return ret, nil
}
func (r *basePoolManager) GetGithubRunners() ([]*github.Runner, error) {
opts := github.ListRunnersOptions{
ListOptions: github.ListOptions{
PerPage: 100,
},
}
var allRunners []*github.Runner
for {
runners, ghResp, err := r.ghcli.ListEntityRunners(r.ctx, &opts)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, errors.Wrap(runnerErrors.ErrUnauthorized, "fetching runners")
}
return nil, errors.Wrap(err, "fetching runners")
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allRunners, nil
}
func (r *basePoolManager) GetWebhookInfo(ctx context.Context) (params.HookInfo, error) {
allHooks, err := r.listHooks(ctx)
if err != nil {
return params.HookInfo{}, errors.Wrap(err, "listing hooks")
return params.HookInfo{}, fmt.Errorf("error listing hooks: %w", err)
}
trimmedBase := strings.TrimRight(r.controllerInfo.WebhookURL, "/")
trimmedController := strings.TrimRight(r.controllerInfo.ControllerWebhookURL, "/")

View file

@ -82,3 +82,7 @@ func (s *stubGithubClient) GithubBaseURL() *url.URL {
func (s *stubGithubClient) RateLimit(_ context.Context) (*github.RateLimits, error) {
return nil, s.err
}
func (s *stubGithubClient) GetEntityRunnerGroupIDByName(_ context.Context, _ string) (int64, error) {
return 0, s.err
}

View file

@ -15,10 +15,12 @@
package pool
import (
"sort"
"context"
"fmt"
"log/slog"
"net/http"
"net/url"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/google/go-github/v72/github"
@ -31,70 +33,6 @@ import (
"github.com/cloudbase/garm/params"
)
type poolCacheStore interface {
Next() (params.Pool, error)
Reset()
Len() int
}
type poolRoundRobin struct {
pools []params.Pool
next uint32
}
func (p *poolRoundRobin) Next() (params.Pool, error) {
if len(p.pools) == 0 {
return params.Pool{}, runnerErrors.ErrNoPoolsAvailable
}
n := atomic.AddUint32(&p.next, 1)
return p.pools[(int(n)-1)%len(p.pools)], nil
}
func (p *poolRoundRobin) Len() int {
return len(p.pools)
}
func (p *poolRoundRobin) Reset() {
atomic.StoreUint32(&p.next, 0)
}
type poolsForTags struct {
pools sync.Map
poolCacheType params.PoolBalancerType
}
func (p *poolsForTags) Get(tags []string) (poolCacheStore, bool) {
sort.Strings(tags)
key := strings.Join(tags, "^")
v, ok := p.pools.Load(key)
if !ok {
return nil, false
}
poolCache := v.(*poolRoundRobin)
if p.poolCacheType == params.PoolBalancerTypePack {
// When we service a list of jobs, we want to try each pool in turn
// for each job. Pools are sorted by priority so we always start from the
// highest priority pool and move on to the next if the first one is full.
poolCache.Reset()
}
return poolCache, true
}
func (p *poolsForTags) Add(tags []string, pools []params.Pool) poolCacheStore {
sort.Slice(pools, func(i, j int) bool {
return pools[i].Priority > pools[j].Priority
})
sort.Strings(tags)
key := strings.Join(tags, "^")
poolRR := &poolRoundRobin{pools: pools}
v, _ := p.pools.LoadOrStore(key, poolRR)
return v.(*poolRoundRobin)
}
func instanceInList(instanceName string, instances []commonParams.ProviderInstance) (commonParams.ProviderInstance, bool) {
for _, val := range instances {
if val.Name == instanceName {
@ -114,17 +52,14 @@ func controllerIDFromLabels(labels []string) string {
return ""
}
func labelsFromRunner(runner *github.Runner) []string {
if runner == nil || runner.Labels == nil {
func labelsFromRunner(runner forgeRunner) []string {
if runner.Labels == nil {
return []string{}
}
var labels []string
for _, val := range runner.Labels {
if val == nil {
continue
}
labels = append(labels, val.GetName())
labels = append(labels, val.Name)
}
return labels
}
@ -167,3 +102,172 @@ func (r *basePoolManager) waitForToolsOrCancel() (hasTools, stopped bool) {
return false, true
}
}
func validateHookRequest(controllerID, baseURL string, allHooks []*github.Hook, req *github.Hook) error {
parsed, err := url.Parse(baseURL)
if err != nil {
return fmt.Errorf("error parsing webhook url: %w", err)
}
partialMatches := []string{}
for _, hook := range allHooks {
hookURL := strings.ToLower(hook.Config.GetURL())
if hookURL == "" {
continue
}
if hook.Config.GetURL() == req.Config.GetURL() {
return runnerErrors.NewConflictError("hook already installed")
} else if strings.Contains(hookURL, controllerID) || strings.Contains(hookURL, parsed.Hostname()) {
partialMatches = append(partialMatches, hook.Config.GetURL())
}
}
if len(partialMatches) > 0 {
return runnerErrors.NewConflictError("a webhook containing the controller ID or hostname of this contreoller is already installed on this repository")
}
return nil
}
func hookToParamsHookInfo(hook *github.Hook) params.HookInfo {
hookURL := hook.Config.GetURL()
insecureSSLConfig := hook.Config.GetInsecureSSL()
insecureSSL := insecureSSLConfig == "1"
return params.HookInfo{
ID: *hook.ID,
URL: hookURL,
Events: hook.Events,
Active: *hook.Active,
InsecureSSL: insecureSSL,
}
}
func (r *basePoolManager) listHooks(ctx context.Context) ([]*github.Hook, error) {
opts := github.ListOptions{
PerPage: 100,
}
var allHooks []*github.Hook
for {
hooks, ghResp, err := r.ghcli.ListEntityHooks(ctx, &opts)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusNotFound {
return nil, runnerErrors.NewBadRequestError("repository not found or your PAT does not have access to manage webhooks")
}
return nil, fmt.Errorf("error fetching hooks: %w", err)
}
allHooks = append(allHooks, hooks...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
return allHooks, nil
}
func (r *basePoolManager) listRunnersWithPagination() ([]forgeRunner, error) {
opts := github.ListRunnersOptions{
ListOptions: github.ListOptions{
PerPage: 100,
},
}
var allRunners []*github.Runner
// Paginating like this can lead to a situation where if we have many pages of runners,
// while we paginate, a particular runner can move from page n to page n-1 while we move
// from page n-1 to page n. In situations such as that, we end up with a list of runners
// that does not contain the runner that swapped pages while we were paginating.
// Sadly, the GitHub API does not allow listing more than 100 runners per page.
for {
runners, ghResp, err := r.ghcli.ListEntityRunners(r.ctx, &opts)
if err != nil {
if ghResp != nil && ghResp.StatusCode == http.StatusUnauthorized {
return nil, runnerErrors.NewUnauthorizedError("error fetching runners")
}
return nil, fmt.Errorf("error fetching runners: %w", err)
}
allRunners = append(allRunners, runners.Runners...)
if ghResp.NextPage == 0 {
break
}
opts.Page = ghResp.NextPage
}
ret := make([]forgeRunner, len(allRunners))
for idx, val := range allRunners {
ret[idx] = forgeRunner{
ID: val.GetID(),
Name: val.GetName(),
Status: val.GetStatus(),
Labels: make([]RunnerLabels, len(val.Labels)),
}
for labelIdx, label := range val.Labels {
ret[idx].Labels[labelIdx] = RunnerLabels{
Name: label.GetName(),
Type: label.GetType(),
ID: label.GetID(),
}
}
}
return ret, nil
}
func (r *basePoolManager) listRunnersWithScaleSetAPI() ([]forgeRunner, error) {
if r.scaleSetClient == nil {
return nil, fmt.Errorf("scaleset client not initialized")
}
runners, err := r.scaleSetClient.ListAllRunners(r.ctx)
if err != nil {
return nil, fmt.Errorf("failed to list runners through scaleset API: %w", err)
}
ret := []forgeRunner{}
for _, runner := range runners.RunnerReferences {
if runner.RunnerScaleSetID != 0 {
// skip scale set runners.
continue
}
run := forgeRunner{
Name: runner.Name,
ID: runner.ID,
Status: string(runner.GetStatus()),
Labels: make([]RunnerLabels, len(runner.Labels)),
}
for labelIDX, label := range runner.Labels {
run.Labels[labelIDX] = RunnerLabels{
Name: label.Name,
Type: label.Type,
}
}
ret = append(ret, run)
}
return ret, nil
}
func (r *basePoolManager) GetGithubRunners() ([]forgeRunner, error) {
// Gitea has no scale sets API
if r.scaleSetClient == nil {
return r.listRunnersWithPagination()
}
// try the scale sets API for github
runners, err := r.listRunnersWithScaleSetAPI()
if err != nil {
slog.WarnContext(r.ctx, "failed to list runners via scaleset API; falling back to pagination", "error", err)
return r.listRunnersWithPagination()
}
entityInstances := cache.GetEntityInstances(r.entity.ID)
if len(entityInstances) > 0 && len(runners) == 0 {
// I have trust issues in the undocumented API. We seem to have runners for this
// entity, but the scaleset API returned nothing and no error. Fall back to pagination.
slog.DebugContext(r.ctx, "the scaleset api returned nothing, but we seem to have runners in the db; falling back to paginated API runner list")
return r.listRunnersWithPagination()
}
slog.DebugContext(r.ctx, "Scaleset API runner list succeeded", "runners", runners)
return runners, nil
}

View file

@ -17,8 +17,6 @@ package pool
import (
"log/slog"
"github.com/pkg/errors"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/database/common"
"github.com/cloudbase/garm/params"
@ -46,7 +44,7 @@ func (r *basePoolManager) getClientOrStub() runnerCommon.GithubClient {
if err != nil {
slog.WarnContext(r.ctx, "failed to create github client", "error", err)
ghc = &stubGithubClient{
err: errors.Wrapf(runnerErrors.ErrUnauthorized, "failed to create github client; please update credentials: %v", err),
err: runnerErrors.NewUnauthorizedError("failed to create github client; please update credentials"),
}
}
return ghc

View file

@ -16,8 +16,8 @@ package runner
import (
"context"
"github.com/pkg/errors"
"errors"
"fmt"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
@ -31,7 +31,7 @@ func (r *Runner) ListAllPools(ctx context.Context) ([]params.Pool, error) {
pools, err := r.store.ListAllPools(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching pools")
return nil, fmt.Errorf("error fetching pools: %w", err)
}
return pools, nil
}
@ -43,7 +43,7 @@ func (r *Runner) GetPoolByID(ctx context.Context, poolID string) (params.Pool, e
pool, err := r.store.GetPoolByID(ctx, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
return params.Pool{}, fmt.Errorf("error fetching pool: %w", err)
}
return pool, nil
}
@ -56,7 +56,7 @@ func (r *Runner) DeletePoolByID(ctx context.Context, poolID string) error {
pool, err := r.store.GetPoolByID(ctx, poolID)
if err != nil {
if !errors.Is(err, runnerErrors.ErrNotFound) {
return errors.Wrap(err, "fetching pool")
return fmt.Errorf("error fetching pool: %w", err)
}
return nil
}
@ -66,7 +66,7 @@ func (r *Runner) DeletePoolByID(ctx context.Context, poolID string) error {
}
if err := r.store.DeletePoolByID(ctx, poolID); err != nil {
return errors.Wrap(err, "deleting pool")
return fmt.Errorf("error deleting pool: %w", err)
}
return nil
}
@ -78,7 +78,7 @@ func (r *Runner) UpdatePoolByID(ctx context.Context, poolID string, param params
pool, err := r.store.GetPoolByID(ctx, poolID)
if err != nil {
return params.Pool{}, errors.Wrap(err, "fetching pool")
return params.Pool{}, fmt.Errorf("error fetching pool: %w", err)
}
maxRunners := pool.MaxRunners
@ -101,12 +101,12 @@ func (r *Runner) UpdatePoolByID(ctx context.Context, poolID string, param params
entity, err := pool.GetEntity()
if err != nil {
return params.Pool{}, errors.Wrap(err, "getting entity")
return params.Pool{}, fmt.Errorf("error getting entity: %w", err)
}
newPool, err := r.store.UpdateEntityPool(ctx, entity, poolID, param)
if err != nil {
return params.Pool{}, errors.Wrap(err, "updating pool")
return params.Pool{}, fmt.Errorf("error updating pool: %w", err)
}
return newPool, nil
}
@ -118,7 +118,7 @@ func (r *Runner) ListAllJobs(ctx context.Context) ([]params.Job, error) {
jobs, err := r.store.ListAllJobs(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching jobs")
return nil, fmt.Errorf("error fetching jobs: %w", err)
}
return jobs, nil
}

View file

@ -169,7 +169,7 @@ func (s *PoolTestSuite) TestGetPoolByIDNotFound() {
s.Require().Nil(err)
_, err = s.Runner.GetPoolByID(s.Fixtures.AdminContext, s.Fixtures.Pools[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: fetching pool by ID: not found", err.Error())
s.Require().Equal("error fetching pool: error fetching pool by ID: not found", err.Error())
}
func (s *PoolTestSuite) TestDeletePoolByID() {
@ -178,7 +178,7 @@ func (s *PoolTestSuite) TestDeletePoolByID() {
s.Require().Nil(err)
_, err = s.Fixtures.Store.GetPoolByID(s.Fixtures.AdminContext, s.Fixtures.Pools[0].ID)
s.Require().NotNil(err)
s.Require().Equal("fetching pool by ID: not found", err.Error())
s.Require().Equal("error fetching pool by ID: not found", err.Error())
}
func (s *PoolTestSuite) TestDeletePoolByIDErrUnauthorized() {
@ -220,7 +220,7 @@ func (s *PoolTestSuite) TestTestUpdatePoolByIDInvalidPoolID() {
_, err := s.Runner.UpdatePoolByID(s.Fixtures.AdminContext, "dummy-pool-id", s.Fixtures.UpdatePoolParams)
s.Require().NotNil(err)
s.Require().Equal("fetching pool: fetching pool by ID: parsing id: invalid request", err.Error())
s.Require().Equal("error fetching pool: error fetching pool by ID: error parsing id: invalid request", err.Error())
}
func (s *PoolTestSuite) TestTestUpdatePoolByIDRunnerBootstrapTimeoutFailed() {

View file

@ -16,10 +16,9 @@ package providers
import (
"context"
"fmt"
"log/slog"
"github.com/pkg/errors"
"github.com/cloudbase/garm/config"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/runner/common"
@ -39,11 +38,11 @@ func LoadProvidersFromConfig(ctx context.Context, cfg config.Config, controllerI
conf := providerCfg
provider, err := external.NewProvider(ctx, &conf, controllerID)
if err != nil {
return nil, errors.Wrap(err, "creating provider")
return nil, fmt.Errorf("error creating provider: %w", err)
}
providers[providerCfg.Name] = provider
default:
return nil, errors.Errorf("unknown provider type %s", providerCfg.ProviderType)
return nil, fmt.Errorf("unknown provider type %s", providerCfg.ProviderType)
}
}
return providers, nil

Some files were not shown because too many files have changed in this diff Show more