From 014265dc570d3fb0a07c4bdfa61497263bf9d404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Sun, 26 Nov 2023 16:29:01 +0100 Subject: [PATCH] feat: woodpecker 2.0 support (#19) --- .github/workflows/ci.yml | 10 +- .golangci.yml | 2 + docs/data-sources/repository_secret.md | 1 - docs/data-sources/secret.md | 1 - docs/index.md | 5 + docs/resources/repository_secret.md | 1 - docs/resources/secret.md | 1 - go.mod | 6 +- go.sum | 8 +- internal/data_source_repository.go | 2 +- internal/data_source_repository_cron.go | 2 +- internal/data_source_repository_registry.go | 2 +- internal/data_source_repository_secret.go | 7 +- .../data_source_repository_secret_test.go | 1 - internal/data_source_secret.go | 7 +- internal/data_source_secret_test.go | 1 - internal/data_source_user.go | 2 +- internal/internal_test.go | 4 +- internal/models.go | 63 +- internal/provider.go | 56 +- internal/resource_repository.go | 4 +- internal/resource_repository_cron.go | 2 +- internal/resource_repository_cron_test.go | 2 +- internal/resource_repository_registry.go | 2 +- internal/resource_repository_registry_test.go | 2 +- internal/resource_repository_secret.go | 111 ++- internal/resource_repository_secret_test.go | 8 +- internal/resource_repository_test.go | 4 +- internal/resource_secret.go | 103 ++- internal/resource_secret_test.go | 7 +- internal/resource_user.go | 2 +- internal/resource_user_test.go | 2 +- internal/utils_test.go | 2 +- internal/woodpecker/client.go | 667 ++++++++++++++++++ internal/woodpecker/const.go | 57 ++ internal/woodpecker/interface.go | 231 ++++++ internal/woodpecker/types.go | 257 +++++++ internal/woodpecker/woodpecker.go | 5 + 38 files changed, 1536 insertions(+), 114 deletions(-) create mode 100644 internal/woodpecker/client.go create mode 100644 internal/woodpecker/const.go create mode 100644 internal/woodpecker/interface.go create mode 100644 internal/woodpecker/types.go create mode 100644 internal/woodpecker/woodpecker.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2510f57..d22afbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,13 +31,13 @@ jobs: - uses: golangci/golangci-lint-action@v3 with: version: v1.55 - - name: Gitea 1.20 & Woodpecker 1.0 + - name: Gitea 1.21 & Woodpecker 2.0 run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... env: - GITEA_IMAGE: gitea/gitea:1.20 - WOODPECKER_IMAGE: woodpeckerci/woodpecker-server:v1.0 - - name: Forgejo 1.20 & Woodpecker 1.0 + GITEA_IMAGE: gitea/gitea:1.21 + WOODPECKER_IMAGE: woodpeckerci/woodpecker-server:v2.0 + - name: Forgejo 1.20 & Woodpecker 2.0 run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... env: GITEA_IMAGE: codeberg.org/forgejo/forgejo:1.20 - WOODPECKER_IMAGE: woodpeckerci/woodpecker-server:v1.0 + WOODPECKER_IMAGE: woodpeckerci/woodpecker-server:v2.0 diff --git a/.golangci.yml b/.golangci.yml index dd0ffb1..e25e737 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,8 @@ run: tests: true timeout: 5m + skip-dirs: + - internal/woodpecker linters: disable-all: true diff --git a/docs/data-sources/repository_secret.md b/docs/data-sources/repository_secret.md index a74b813..aeed754 100644 --- a/docs/data-sources/repository_secret.md +++ b/docs/data-sources/repository_secret.md @@ -36,4 +36,3 @@ data "woodpecker_repository_secret" "test_secret" { - `events` (Set of String) events for which the secret is available (push, tag, pull_request, deployment, cron, manual) - `id` (Number) the secret's id - `images` (Set of String) list of Docker images for which this secret is available -- `plugins_only` (Boolean) whether secret is only available for [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins) diff --git a/docs/data-sources/secret.md b/docs/data-sources/secret.md index a65cda5..e8734b6 100644 --- a/docs/data-sources/secret.md +++ b/docs/data-sources/secret.md @@ -30,4 +30,3 @@ data "woodpecker_secret" "test_secret" { - `events` (Set of String) events for which the secret is available (push, tag, pull_request, deployment, cron, manual) - `id` (Number) the secret's id - `images` (Set of String) list of Docker images for which this secret is available -- `plugins_only` (Boolean) whether secret is only available for [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins) diff --git a/docs/index.md b/docs/index.md index 8e58e47..47ecd1b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,12 +4,17 @@ page_title: "woodpecker Provider" subcategory: "" description: |- A Terraform provider used to interact with Woodpecker CI https://woodpecker-ci.org/ resources. + v0.2.x and later versions of the provider work with Woodpecker 2.x+v0.1.x version of the provider works with Woodpecker 2.0.0>1.x>=1.0.0 --- # woodpecker Provider A Terraform provider used to interact with [Woodpecker CI](https://woodpecker-ci.org/) resources. + +- v0.2.x and later versions of the provider work with Woodpecker 2.x+ +- v0.1.x version of the provider works with Woodpecker 2.0.0>1.x>=1.0.0 + ## Example Usage ```terraform diff --git a/docs/resources/repository_secret.md b/docs/resources/repository_secret.md index 441365a..031e79e 100644 --- a/docs/resources/repository_secret.md +++ b/docs/resources/repository_secret.md @@ -40,7 +40,6 @@ resource "woodpecker_repository_secret" "test" { ### Optional - `images` (Set of String) list of Docker images for which this secret is available, leave blank to allow all images -- `plugins_only` (Boolean) whether secret is only available for [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins) ### Read-Only diff --git a/docs/resources/secret.md b/docs/resources/secret.md index 3e7a3fa..290d7b3 100644 --- a/docs/resources/secret.md +++ b/docs/resources/secret.md @@ -33,7 +33,6 @@ resource "woodpecker_secret" "test" { ### Optional - `images` (Set of String) list of Docker images for which this secret is available, leave blank to allow all images -- `plugins_only` (Boolean) whether secret is only available for [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins) ### Read-Only diff --git a/go.mod b/go.mod index c5c1983..3844cd9 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.21 require ( code.gitea.io/sdk/gitea v0.16.0 + github.com/Masterminds/semver/v3 v3.2.1 github.com/google/uuid v1.4.0 github.com/hashicorp/terraform-plugin-framework v1.3.5 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-testing v1.5.1 github.com/ory/dockertest/v3 v3.10.0 - go.woodpecker-ci.org/woodpecker v1.0.5 golang.org/x/oauth2 v0.14.0 ) @@ -31,6 +31,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -55,6 +56,7 @@ require ( github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/imdario/mergo v0.3.13 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -69,6 +71,7 @@ require ( github.com/opencontainers/runc v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect + github.com/stretchr/testify v1.8.3 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect @@ -88,4 +91,5 @@ require ( google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gotest.tools/v3 v3.4.0 // indirect ) diff --git a/go.sum b/go.sum index a3b5a1d..0f40a96 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ -code.gitea.io/sdk/gitea v0.15.1-0.20230815151548-091528835fc2 h1:IFmjjaW1PUn4yUB9a3F59KjVcVMB8TDdocL5Cqy6Xrk= -code.gitea.io/sdk/gitea v0.15.1-0.20230815151548-091528835fc2/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= code.gitea.io/sdk/gitea v0.16.0 h1:gAfssETO1Hv9QbE+/nhWu7EjoFQYKt6kPoyDytQgw00= code.gitea.io/sdk/gitea v0.16.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -233,8 +233,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zclconf/go-cty v1.13.3 h1:m+b9q3YDbg6Bec5rr+KGy1MzEVzY/jC2X+YX4yqKtHI= github.com/zclconf/go-cty v1.13.3/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= -go.woodpecker-ci.org/woodpecker v1.0.5 h1:pPkSrfzK1y2PRYokJVJdyyvot7IG056Hdin/4Y38C8M= -go.woodpecker-ci.org/woodpecker v1.0.5/go.mod h1:R9G3k/rj+INGcHgrCd/BlhOaKQGdru5agHMIRZsIDVk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -276,6 +274,7 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -304,6 +303,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/data_source_repository.go b/internal/data_source_repository.go index 37afccf..88791e3 100644 --- a/internal/data_source_repository.go +++ b/internal/data_source_repository.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositoryDataSource struct { diff --git a/internal/data_source_repository_cron.go b/internal/data_source_repository_cron.go index 24d06b4..4fea2a2 100644 --- a/internal/data_source_repository_cron.go +++ b/internal/data_source_repository_cron.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositoryCronDataSource struct { diff --git a/internal/data_source_repository_registry.go b/internal/data_source_repository_registry.go index 9c88819..b9b1a73 100644 --- a/internal/data_source_repository_registry.go +++ b/internal/data_source_repository_registry.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositoryRegistryDataSource struct { diff --git a/internal/data_source_repository_secret.go b/internal/data_source_repository_secret.go index c2b07eb..ebdefe6 100644 --- a/internal/data_source_repository_secret.go +++ b/internal/data_source_repository_secret.go @@ -4,10 +4,10 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositorySecretDataSource struct { @@ -54,11 +54,6 @@ func (d *repositorySecretDataSource) Schema( Computed: true, Description: "events for which the secret is available (push, tag, pull_request, deployment, cron, manual)", }, - "plugins_only": schema.BoolAttribute{ - Computed: true, - MarkdownDescription: "whether secret is only available for " + - "[plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins)", - }, "images": schema.SetAttribute{ ElementType: types.StringType, Computed: true, diff --git a/internal/data_source_repository_secret_test.go b/internal/data_source_repository_secret_test.go index 885fc0b..0366173 100644 --- a/internal/data_source_repository_secret_test.go +++ b/internal/data_source_repository_secret_test.go @@ -41,7 +41,6 @@ data "woodpecker_repository_secret" "test_secret" { resource.TestCheckResourceAttrSet("data.woodpecker_repository_secret.test_secret", "repository_id"), resource.TestCheckResourceAttr("data.woodpecker_repository_secret.test_secret", "name", name), resource.TestCheckTypeSetElemAttr("data.woodpecker_repository_secret.test_secret", "events.*", "push"), - resource.TestCheckResourceAttr("data.woodpecker_repository_secret.test_secret", "plugins_only", "false"), ), }, }, diff --git a/internal/data_source_secret.go b/internal/data_source_secret.go index 8233b44..c841766 100644 --- a/internal/data_source_secret.go +++ b/internal/data_source_secret.go @@ -4,10 +4,10 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type secretDataSource struct { @@ -46,11 +46,6 @@ func (d *secretDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, Computed: true, Description: "events for which the secret is available (push, tag, pull_request, deployment, cron, manual)", }, - "plugins_only": schema.BoolAttribute{ - Computed: true, - MarkdownDescription: "whether secret is only available for " + - "[plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins)", - }, "images": schema.SetAttribute{ ElementType: types.StringType, Computed: true, diff --git a/internal/data_source_secret_test.go b/internal/data_source_secret_test.go index 75292ba..ed52670 100644 --- a/internal/data_source_secret_test.go +++ b/internal/data_source_secret_test.go @@ -33,7 +33,6 @@ data "woodpecker_secret" "test_secret" { resource.TestCheckResourceAttrSet("data.woodpecker_secret.test_secret", "id"), resource.TestCheckResourceAttr("data.woodpecker_secret.test_secret", "name", name), resource.TestCheckTypeSetElemAttr("data.woodpecker_secret.test_secret", "events.*", "push"), - resource.TestCheckResourceAttr("data.woodpecker_secret.test_secret", "plugins_only", "false"), ), }, }, diff --git a/internal/data_source_user.go b/internal/data_source_user.go index 55b68d7..c9c6294 100644 --- a/internal/data_source_user.go +++ b/internal/data_source_user.go @@ -4,9 +4,9 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type userDataSource struct { diff --git a/internal/internal_test.go b/internal/internal_test.go index b347d78..14ff92d 100644 --- a/internal/internal_test.go +++ b/internal/internal_test.go @@ -19,10 +19,10 @@ import ( "time" "code.gitea.io/sdk/gitea" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" "golang.org/x/oauth2" ) @@ -345,7 +345,7 @@ func (r woodpeckerResource) Close() error { return r.docker.Close() } -const defaultWoodpeckerImage = "woodpeckerci/woodpecker-server:v1.0.2" +const defaultWoodpeckerImage = "woodpeckerci/woodpecker-server:v2.0.0" //nolint:nonamedreturns func getWoodpeckerRepoTag() (repo string, tag string) { diff --git a/internal/models.go b/internal/models.go index 0f58d04..f92105e 100644 --- a/internal/models.go +++ b/internal/models.go @@ -3,9 +3,9 @@ package internal import ( "context" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type userModel struct { @@ -38,7 +38,7 @@ func (m *userModel) toWoodpeckerModel(_ context.Context) (*woodpecker.User, diag }, nil } -type secretResourceModel struct { +type secretResourceModelV0 struct { ID types.Int64 `tfsdk:"id"` Name types.String `tfsdk:"name"` Value types.String `tfsdk:"value"` @@ -47,7 +47,15 @@ type secretResourceModel struct { Events types.Set `tfsdk:"events"` } -func (m *secretResourceModel) setValues(ctx context.Context, secret *woodpecker.Secret) diag.Diagnostics { +type secretResourceModelV1 struct { + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Value types.String `tfsdk:"value"` + Images types.Set `tfsdk:"images"` + Events types.Set `tfsdk:"events"` +} + +func (m *secretResourceModelV1) setValues(ctx context.Context, secret *woodpecker.Secret) diag.Diagnostics { var diagsRes diag.Diagnostics var diags diag.Diagnostics @@ -55,21 +63,19 @@ func (m *secretResourceModel) setValues(ctx context.Context, secret *woodpecker. m.Name = types.StringValue(secret.Name) m.Images, diags = types.SetValueFrom(ctx, types.StringType, secret.Images) diagsRes.Append(diags...) - m.PluginsOnly = types.BoolValue(secret.PluginsOnly) m.Events, diags = types.SetValueFrom(ctx, types.StringType, secret.Events) diagsRes.Append(diags...) return diagsRes } -func (m *secretResourceModel) toWoodpeckerModel(ctx context.Context) (*woodpecker.Secret, diag.Diagnostics) { +func (m *secretResourceModelV1) toWoodpeckerModel(ctx context.Context) (*woodpecker.Secret, diag.Diagnostics) { var diags diag.Diagnostics secret := &woodpecker.Secret{ - ID: m.ID.ValueInt64(), - Name: m.Name.ValueString(), - Value: m.Value.ValueString(), - PluginsOnly: m.PluginsOnly.ValueBool(), + ID: m.ID.ValueInt64(), + Name: m.Name.ValueString(), + Value: m.Value.ValueString(), } diags.Append(m.Images.ElementsAs(ctx, &secret.Images, false)...) diags.Append(m.Events.ElementsAs(ctx, &secret.Events, false)...) @@ -78,11 +84,10 @@ func (m *secretResourceModel) toWoodpeckerModel(ctx context.Context) (*woodpecke } type secretDataSourceModel struct { - ID types.Int64 `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Images types.Set `tfsdk:"images"` - PluginsOnly types.Bool `tfsdk:"plugins_only"` - Events types.Set `tfsdk:"events"` + ID types.Int64 `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Images types.Set `tfsdk:"images"` + Events types.Set `tfsdk:"events"` } func (m *secretDataSourceModel) setValues(ctx context.Context, secret *woodpecker.Secret) diag.Diagnostics { @@ -93,7 +98,6 @@ func (m *secretDataSourceModel) setValues(ctx context.Context, secret *woodpecke m.Name = types.StringValue(secret.Name) m.Images, diags = types.SetValueFrom(ctx, types.StringType, secret.Images) diagsRes.Append(diags...) - m.PluginsOnly = types.BoolValue(secret.PluginsOnly) m.Events, diags = types.SetValueFrom(ctx, types.StringType, secret.Events) diagsRes.Append(diags...) @@ -128,7 +132,7 @@ func (m *repositoryModel) setValues(_ context.Context, repo *woodpecker.Repo) di m.Name = types.StringValue(repo.Name) m.FullName = types.StringValue(repo.FullName) m.AvatarURL = types.StringValue(repo.Avatar) - m.URL = types.StringValue(repo.Link) + m.URL = types.StringValue(repo.ForgeURL) m.CloneURL = types.StringValue(repo.Clone) m.DefaultBranch = types.StringValue(repo.DefaultBranch) m.SCMKind = types.StringValue(repo.SCMKind) @@ -154,7 +158,7 @@ func (m *repositoryModel) toWoodpeckerPatch(_ context.Context) (*woodpecker.Repo }, nil } -type repositorySecretResourceModel struct { +type repositorySecretResourceModelV0 struct { ID types.Int64 `tfsdk:"id"` RepositoryID types.Int64 `tfsdk:"repository_id"` Name types.String `tfsdk:"name"` @@ -164,7 +168,16 @@ type repositorySecretResourceModel struct { Events types.Set `tfsdk:"events"` } -func (m *repositorySecretResourceModel) setValues(ctx context.Context, secret *woodpecker.Secret) diag.Diagnostics { +type repositorySecretResourceModelV1 struct { + ID types.Int64 `tfsdk:"id"` + RepositoryID types.Int64 `tfsdk:"repository_id"` + Name types.String `tfsdk:"name"` + Value types.String `tfsdk:"value"` + Images types.Set `tfsdk:"images"` + Events types.Set `tfsdk:"events"` +} + +func (m *repositorySecretResourceModelV1) setValues(ctx context.Context, secret *woodpecker.Secret) diag.Diagnostics { var diagsRes diag.Diagnostics var diags diag.Diagnostics @@ -172,21 +185,21 @@ func (m *repositorySecretResourceModel) setValues(ctx context.Context, secret *w m.Name = types.StringValue(secret.Name) m.Images, diags = types.SetValueFrom(ctx, types.StringType, secret.Images) diagsRes.Append(diags...) - m.PluginsOnly = types.BoolValue(secret.PluginsOnly) m.Events, diags = types.SetValueFrom(ctx, types.StringType, secret.Events) diagsRes.Append(diags...) return diagsRes } -func (m *repositorySecretResourceModel) toWoodpeckerModel(ctx context.Context) (*woodpecker.Secret, diag.Diagnostics) { +func (m *repositorySecretResourceModelV1) toWoodpeckerModel( + ctx context.Context, +) (*woodpecker.Secret, diag.Diagnostics) { var diags diag.Diagnostics secret := &woodpecker.Secret{ - ID: m.ID.ValueInt64(), - Name: m.Name.ValueString(), - Value: m.Value.ValueString(), - PluginsOnly: m.PluginsOnly.ValueBool(), + ID: m.ID.ValueInt64(), + Name: m.Name.ValueString(), + Value: m.Value.ValueString(), } diags.Append(m.Images.ElementsAs(ctx, &secret.Images, false)...) diags.Append(m.Events.ElementsAs(ctx, &secret.Events, false)...) @@ -199,7 +212,6 @@ type repositorySecretDataSourceModel struct { RepositoryID types.Int64 `tfsdk:"repository_id"` Name types.String `tfsdk:"name"` Images types.Set `tfsdk:"images"` - PluginsOnly types.Bool `tfsdk:"plugins_only"` Events types.Set `tfsdk:"events"` } @@ -211,7 +223,6 @@ func (m *repositorySecretDataSourceModel) setValues(ctx context.Context, secret m.Name = types.StringValue(secret.Name) m.Images, diags = types.SetValueFrom(ctx, types.StringType, secret.Images) diagsRes.Append(diags...) - m.PluginsOnly = types.BoolValue(secret.PluginsOnly) m.Events, diags = types.SetValueFrom(ctx, types.StringType, secret.Events) diagsRes.Append(diags...) diff --git a/internal/provider.go b/internal/provider.go index b502461..47988dd 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -2,14 +2,17 @@ package internal import ( "context" + "fmt" "os" + "strings" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" + "github.com/Masterminds/semver/v3" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" "golang.org/x/oauth2" ) @@ -37,7 +40,9 @@ func (p *woodpeckerProvider) Metadata(_ context.Context, _ provider.MetadataRequ func (p *woodpeckerProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "A Terraform provider used to interact with" + - " [Woodpecker CI](https://woodpecker-ci.org/) resources.", + " [Woodpecker CI](https://woodpecker-ci.org/) resources." + + "\n\n\n- v0.2.x and later versions of the provider work with Woodpecker 2.x+" + + "\n- v0.1.x version of the provider works with Woodpecker 2.0.0>1.x>=1.0.0", Attributes: map[string]schema.Attribute{ "server": schema.StringAttribute{ Optional: true, @@ -88,6 +93,9 @@ func (p *woodpeckerProvider) Configure( } client := newClient(ctx, cfg, resp) + if resp.Diagnostics.HasError() { + return + } resp.DataSourceData = client resp.ResourceData = client @@ -156,5 +164,49 @@ func newClient( return nil } + c, err := semver.NewConstraint(">= 2.0.0") + if err != nil { + resp.Diagnostics.AddError( + "Couldn't parse woodpecker version constraint", + fmt.Sprintf( + "%s. Please report this issue to the provider developers.", + err, + ), + ) + return nil + } + + ver, err := client.Version() + if err != nil { + resp.Diagnostics.AddError("Couldn't get woodpecker version", err.Error()) + return nil + } + + // split is required because in some cases the version looks like this: 2.0.0-f05c1631d2 + parsedVer, err := semver.NewVersion(strings.Split(ver.Version, "-")[0]) + if err != nil { + resp.Diagnostics.AddError( + "Couldn't parse woodpecker version", + fmt.Sprintf( + "%s. Please report this issue to the provider developers.", + err, + ), + ) + return nil + } + + if !c.Check(parsedVer) { + resp.Diagnostics.AddError( + "Woodpecker version doesn't satisfy the constraint", + fmt.Sprintf( + "Current woodpecker version: %s, expected: %s."+ + " Consider using an older version of the provider or update woodpecker.", + ver.Version, + c.String(), + ), + ) + return nil + } + return client } diff --git a/internal/resource_repository.go b/internal/resource_repository.go index 7a14928..ecc965e 100644 --- a/internal/resource_repository.go +++ b/internal/resource_repository.go @@ -6,6 +6,7 @@ import ( "slices" "strconv" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" @@ -16,7 +17,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositoryResource struct { @@ -242,7 +242,7 @@ func (r *repositoryResource) Create(ctx context.Context, req resource.CreateRequ repoFullName := data.FullName.ValueString() - repos, err := r.client.RepoListOpts(true, true) + repos, err := r.client.RepoListOpts(true) if err != nil { resp.Diagnostics.AddError("Couldn't list repositories", err.Error()) return diff --git a/internal/resource_repository_cron.go b/internal/resource_repository_cron.go index a65357b..7ef7088 100644 --- a/internal/resource_repository_cron.go +++ b/internal/resource_repository_cron.go @@ -6,13 +6,13 @@ import ( "strconv" "strings" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositoryCronResource struct { diff --git a/internal/resource_repository_cron_test.go b/internal/resource_repository_cron_test.go index b4d165f..2812f21 100644 --- a/internal/resource_repository_cron_test.go +++ b/internal/resource_repository_cron_test.go @@ -7,11 +7,11 @@ import ( "strconv" "testing" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) func TestRepositoryCronResource(t *testing.T) { diff --git a/internal/resource_repository_registry.go b/internal/resource_repository_registry.go index 908f34d..74e5c02 100644 --- a/internal/resource_repository_registry.go +++ b/internal/resource_repository_registry.go @@ -6,13 +6,13 @@ import ( "strconv" "strings" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositoryRegistryResource struct { diff --git a/internal/resource_repository_registry_test.go b/internal/resource_repository_registry_test.go index 776fc6d..c92157e 100644 --- a/internal/resource_repository_registry_test.go +++ b/internal/resource_repository_registry_test.go @@ -7,11 +7,11 @@ import ( "strconv" "testing" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) func TestRepositoryRegistryResource(t *testing.T) { diff --git a/internal/resource_repository_secret.go b/internal/resource_repository_secret.go index d53fc36..14cf8bb 100644 --- a/internal/resource_repository_secret.go +++ b/internal/resource_repository_secret.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" @@ -18,7 +19,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type repositorySecretResource struct { @@ -28,6 +28,7 @@ type repositorySecretResource struct { var _ resource.Resource = (*repositorySecretResource)(nil) var _ resource.ResourceWithConfigure = (*repositorySecretResource)(nil) var _ resource.ResourceWithImportState = (*repositorySecretResource)(nil) +var _ resource.ResourceWithUpgradeState = (*repositorySecretResource)(nil) func newRepositorySecretResource() resource.Resource { return &repositorySecretResource{} @@ -91,15 +92,6 @@ func (r *repositorySecretResource) Schema(_ context.Context, _ resource.SchemaRe setplanmodifier.UseStateForUnknown(), }, }, - "plugins_only": schema.BoolAttribute{ - Optional: true, - Computed: true, - MarkdownDescription: "whether secret is only available for" + - " [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins)", - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.UseStateForUnknown(), - }, - }, "images": schema.SetAttribute{ ElementType: types.StringType, Optional: true, @@ -110,6 +102,7 @@ func (r *repositorySecretResource) Schema(_ context.Context, _ resource.SchemaRe }, }, }, + Version: 1, } } @@ -143,7 +136,7 @@ func (r *repositorySecretResource) Create( req resource.CreateRequest, resp *resource.CreateResponse, ) { - var data repositorySecretResourceModel + var data repositorySecretResourceModelV1 resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -171,7 +164,7 @@ func (r *repositorySecretResource) Create( } func (r *repositorySecretResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data repositorySecretResourceModel + var data repositorySecretResourceModelV1 resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -196,7 +189,7 @@ func (r *repositorySecretResource) Update( req resource.UpdateRequest, resp *resource.UpdateResponse, ) { - var data repositorySecretResourceModel + var data repositorySecretResourceModelV1 resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -228,7 +221,7 @@ func (r *repositorySecretResource) Delete( req resource.DeleteRequest, resp *resource.DeleteResponse, ) { - var data repositorySecretResourceModel + var data repositorySecretResourceModelV1 resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -267,3 +260,93 @@ func (r *repositorySecretResource) ImportState( resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("repository_id"), repoID)...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[1])...) } + +func (r *repositorySecretResource) UpgradeState(_ context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + // remove plugins_only attribute + PriorSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + Description: "the secret's id", + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "repository_id": schema.Int64Attribute{ + Required: true, + Description: "the ID of the repository", + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "the name of the secret", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "value": schema.StringAttribute{ + Required: true, + Description: "the value of the secret", + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "events": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Description: "events for which the secret is available (push, tag, pull_request, deployment, cron, manual)", + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + stringvalidator.OneOfCaseInsensitive("push", "tag", "pull_request", "deployment", "cron", "manual"), + ), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + }, + "plugins_only": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "whether secret is only available for" + + " [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins)", + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "images": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, + Description: "list of Docker images for which this secret is available, leave blank to allow all images", + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var priorStateData repositorySecretResourceModelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, repositorySecretResourceModelV1{ + ID: priorStateData.ID, + RepositoryID: priorStateData.RepositoryID, + Name: priorStateData.Name, + Value: priorStateData.Value, + Images: priorStateData.Images, + Events: priorStateData.Events, + })...) + }, + }, + } +} diff --git a/internal/resource_repository_secret_test.go b/internal/resource_repository_secret_test.go index 9fcc3b4..be73cd7 100644 --- a/internal/resource_repository_secret_test.go +++ b/internal/resource_repository_secret_test.go @@ -8,11 +8,11 @@ import ( "strconv" "testing" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) func TestRepositorySecretResource(t *testing.T) { @@ -54,7 +54,6 @@ resource "woodpecker_repository_secret" "test_secret" { resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "name", name), resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "value", "test123"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "push"), - resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "plugins_only", "false"), ), }, { // update secret @@ -64,7 +63,6 @@ resource "woodpecker_repository_secret" "test_secret" { name = "%s" value = "test123123" events = ["push", "deployment"] - plugins_only = true images = ["testimage"] } `, repo.ID, name), @@ -79,7 +77,6 @@ resource "woodpecker_repository_secret" "test_secret" { resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "value", "test123123"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "push"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "deployment"), - resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "plugins_only", "true"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "images.*", "testimage"), ), }, @@ -104,7 +101,6 @@ resource "woodpecker_repository_secret" "test_secret" { resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "push"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "deployment"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "cron"), - resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "plugins_only", "true"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "images.*", "testimage"), ), }, @@ -139,7 +135,6 @@ resource "woodpecker_repository_secret" "test_secret" { resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "name", newName), resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "value", "test123New"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "push"), - resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "plugins_only", "false"), ), }, { // replace secret (new repo id) @@ -166,7 +161,6 @@ resource "woodpecker_repository_secret" "test_secret" { resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "name", newName), resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "value", "test123New"), resource.TestCheckTypeSetElemAttr("woodpecker_repository_secret.test_secret", "events.*", "push"), - resource.TestCheckResourceAttr("woodpecker_repository_secret.test_secret", "plugins_only", "false"), ), }, }, diff --git a/internal/resource_repository_test.go b/internal/resource_repository_test.go index 16de906..71af885 100644 --- a/internal/resource_repository_test.go +++ b/internal/resource_repository_test.go @@ -8,11 +8,11 @@ import ( "strconv" "testing" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) func TestRepositoryResource(t *testing.T) { @@ -230,7 +230,7 @@ resource "woodpecker_repository" "test_repo" { func checkRepositoryResourceDestroy(names ...string) func(state *terraform.State) error { return func(state *terraform.State) error { - repos, err := woodpeckerClient.RepoListOpts(true, true) + repos, err := woodpeckerClient.RepoListOpts(true) if err != nil { return fmt.Errorf("couldn't list repos: %w", err) } diff --git a/internal/resource_secret.go b/internal/resource_secret.go index 6620e0a..351b8e9 100644 --- a/internal/resource_secret.go +++ b/internal/resource_secret.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" @@ -16,7 +17,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type secretResource struct { @@ -26,6 +26,7 @@ type secretResource struct { var _ resource.Resource = (*secretResource)(nil) var _ resource.ResourceWithConfigure = (*secretResource)(nil) var _ resource.ResourceWithImportState = (*secretResource)(nil) +var _ resource.ResourceWithUpgradeState = (*secretResource)(nil) func newSecretResource() resource.Resource { return &secretResource{} @@ -77,15 +78,6 @@ func (r *secretResource) Schema(_ context.Context, _ resource.SchemaRequest, res setplanmodifier.UseStateForUnknown(), }, }, - "plugins_only": schema.BoolAttribute{ - Optional: true, - Computed: true, - MarkdownDescription: "whether secret is only available for" + - " [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins)", - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.UseStateForUnknown(), - }, - }, "images": schema.SetAttribute{ ElementType: types.StringType, Optional: true, @@ -96,6 +88,7 @@ func (r *secretResource) Schema(_ context.Context, _ resource.SchemaRequest, res }, }, }, + Version: 1, } } @@ -121,7 +114,7 @@ func (r *secretResource) Configure(_ context.Context, req resource.ConfigureRequ } func (r *secretResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data secretResourceModel + var data secretResourceModelV1 resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -149,7 +142,7 @@ func (r *secretResource) Create(ctx context.Context, req resource.CreateRequest, } func (r *secretResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data secretResourceModel + var data secretResourceModelV1 resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -170,7 +163,7 @@ func (r *secretResource) Read(ctx context.Context, req resource.ReadRequest, res } func (r *secretResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data secretResourceModel + var data secretResourceModelV1 resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -198,7 +191,7 @@ func (r *secretResource) Update(ctx context.Context, req resource.UpdateRequest, } func (r *secretResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data secretResourceModel + var data secretResourceModelV1 resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -220,3 +213,85 @@ func (r *secretResource) ImportState( ) { resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), req.ID)...) } + +func (r *secretResource) UpgradeState(_ context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + // remove plugins_only attribute + PriorSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + Description: "the secret's id", + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "the name of the secret", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "value": schema.StringAttribute{ + Required: true, + Description: "the value of the secret", + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "events": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Description: "events for which the secret is available (push, tag, pull_request, deployment, cron, manual)", + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + stringvalidator.OneOfCaseInsensitive("push", "tag", "pull_request", "deployment", "cron", "manual"), + ), + }, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + }, + "plugins_only": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "whether secret is only available for" + + " [plugins](https://woodpecker-ci.org/docs/usage/plugins/plugins)", + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "images": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, + Description: "list of Docker images for which this secret is available, leave blank to allow all images", + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var priorStateData secretResourceModelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, secretResourceModelV1{ + ID: priorStateData.ID, + Name: priorStateData.Name, + Value: priorStateData.Value, + Images: priorStateData.Images, + Events: priorStateData.Events, + })...) + }, + }, + } +} diff --git a/internal/resource_secret_test.go b/internal/resource_secret_test.go index 0df9969..4f754be 100644 --- a/internal/resource_secret_test.go +++ b/internal/resource_secret_test.go @@ -7,11 +7,11 @@ import ( "slices" "testing" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) func TestSecretResource(t *testing.T) { @@ -41,7 +41,6 @@ resource "woodpecker_secret" "test_secret" { resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "name", name), resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "value", "test123"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "events.*", "push"), - resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "plugins_only", "false"), ), }, { // update secret @@ -50,7 +49,6 @@ resource "woodpecker_secret" "test_secret" { name = "%s" value = "test123123" events = ["push", "deployment"] - plugins_only = true images = ["testimage"] } `, name), @@ -60,7 +58,6 @@ resource "woodpecker_secret" "test_secret" { resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "value", "test123123"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "events.*", "push"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "events.*", "deployment"), - resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "plugins_only", "true"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "images.*", "testimage"), ), }, @@ -79,7 +76,6 @@ resource "woodpecker_secret" "test_secret" { resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "events.*", "push"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "events.*", "deployment"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "events.*", "cron"), - resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "plugins_only", "true"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "images.*", "testimage"), ), }, @@ -108,7 +104,6 @@ resource "woodpecker_secret" "test_secret" { resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "name", newName), resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "value", "test123New"), resource.TestCheckTypeSetElemAttr("woodpecker_secret.test_secret", "events.*", "push"), - resource.TestCheckResourceAttr("woodpecker_secret.test_secret", "plugins_only", "false"), ), }, }, diff --git a/internal/resource_user.go b/internal/resource_user.go index 94d83a6..963d536 100644 --- a/internal/resource_user.go +++ b/internal/resource_user.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -11,7 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) type userResource struct { diff --git a/internal/resource_user_test.go b/internal/resource_user_test.go index c01118b..9b8b1ce 100644 --- a/internal/resource_user_test.go +++ b/internal/resource_user_test.go @@ -6,11 +6,11 @@ import ( "slices" "testing" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) func TestUserResource(t *testing.T) { diff --git a/internal/utils_test.go b/internal/utils_test.go index f0845e0..c418ed0 100644 --- a/internal/utils_test.go +++ b/internal/utils_test.go @@ -5,8 +5,8 @@ import ( "testing" "code.gitea.io/sdk/gitea" + "github.com/Kichiyaki/terraform-provider-woodpecker/internal/woodpecker" "github.com/google/uuid" - "go.woodpecker-ci.org/woodpecker/woodpecker-go/woodpecker" ) func createRepo(tb testing.TB) *gitea.Repository { diff --git a/internal/woodpecker/client.go b/internal/woodpecker/client.go new file mode 100644 index 0000000..e8b0a5f --- /dev/null +++ b/internal/woodpecker/client.go @@ -0,0 +1,667 @@ +// Copyright 2022 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package woodpecker + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" +) + +const ( + pathSelf = "%s/api/user" + pathRepos = "%s/api/user/repos" + pathRepoPost = "%s/api/repos?forge_remote_id=%d" + pathRepo = "%s/api/repos/%d" + pathRepoLookup = "%s/api/repos/lookup/%s" + pathRepoMove = "%s/api/repos/%d/move?to=%s" + pathChown = "%s/api/repos/%d/chown" + pathRepair = "%s/api/repos/%d/repair" + pathPipelines = "%s/api/repos/%d/pipelines" + pathPipeline = "%s/api/repos/%d/pipelines/%v" + pathLogs = "%s/api/repos/%d/logs/%d/%d" + pathApprove = "%s/api/repos/%d/pipelines/%d/approve" + pathDecline = "%s/api/repos/%d/pipelines/%d/decline" + pathStop = "%s/api/repos/%d/pipelines/%d/cancel" + pathLogPurge = "%s/api/repos/%d/logs/%d" + pathRepoSecrets = "%s/api/repos/%d/secrets" + pathRepoSecret = "%s/api/repos/%d/secrets/%s" + pathRepoRegistries = "%s/api/repos/%d/registry" + pathRepoRegistry = "%s/api/repos/%d/registry/%s" + pathRepoCrons = "%s/api/repos/%d/cron" + pathRepoCron = "%s/api/repos/%d/cron/%d" + pathOrg = "%s/api/orgs/%d" + pathOrgLookup = "%s/api/orgs/lookup/%s" + pathOrgSecrets = "%s/api/orgs/%d/secrets" + pathOrgSecret = "%s/api/orgs/%d/secrets/%s" + pathGlobalSecrets = "%s/api/secrets" + pathGlobalSecret = "%s/api/secrets/%s" + pathUsers = "%s/api/users" + pathUser = "%s/api/users/%s" + pathPipelineQueue = "%s/api/pipelines" + pathQueue = "%s/api/queue" + pathLogLevel = "%s/api/log-level" + pathAgents = "%s/api/agents" + pathAgent = "%s/api/agents/%d" + pathAgentTasks = "%s/api/agents/%d/tasks" + pathVersion = "%s/version" + // TODO: implement endpoints + // pathFeed = "%s/api/user/feed" +) + +type client struct { + client *http.Client + addr string +} + +// New returns a client at the specified url. +func New(uri string) Client { + return &client{http.DefaultClient, strings.TrimSuffix(uri, "/")} +} + +// NewClient returns a client at the specified url. +func NewClient(uri string, cli *http.Client) Client { + return &client{cli, strings.TrimSuffix(uri, "/")} +} + +// SetClient sets the http.Client. +func (c *client) SetClient(client *http.Client) { + c.client = client +} + +// SetAddress sets the server address. +func (c *client) SetAddress(addr string) { + c.addr = addr +} + +// Self returns the currently authenticated user. +func (c *client) Self() (*User, error) { + out := new(User) + uri := fmt.Sprintf(pathSelf, c.addr) + err := c.get(uri, out) + return out, err +} + +// User returns a user by login. +func (c *client) User(login string) (*User, error) { + out := new(User) + uri := fmt.Sprintf(pathUser, c.addr, login) + err := c.get(uri, out) + return out, err +} + +// UserList returns a list of all registered users. +func (c *client) UserList() ([]*User, error) { + var out []*User + uri := fmt.Sprintf(pathUsers, c.addr) + err := c.get(uri, &out) + return out, err +} + +// UserPost creates a new user account. +func (c *client) UserPost(in *User) (*User, error) { + out := new(User) + uri := fmt.Sprintf(pathUsers, c.addr) + err := c.post(uri, in, out) + return out, err +} + +// UserPatch updates a user account. +func (c *client) UserPatch(in *User) (*User, error) { + out := new(User) + uri := fmt.Sprintf(pathUser, c.addr, in.Login) + err := c.patch(uri, in, out) + return out, err +} + +// UserDel deletes a user account. +func (c *client) UserDel(login string) error { + uri := fmt.Sprintf(pathUser, c.addr, login) + err := c.delete(uri) + return err +} + +// Repo returns a repository by id. +func (c *client) Repo(repoID int64) (*Repo, error) { + out := new(Repo) + uri := fmt.Sprintf(pathRepo, c.addr, repoID) + err := c.get(uri, out) + return out, err +} + +// RepoLookup returns a repository by name. +func (c *client) RepoLookup(fullName string) (*Repo, error) { + out := new(Repo) + uri := fmt.Sprintf(pathRepoLookup, c.addr, fullName) + err := c.get(uri, out) + return out, err +} + +// RepoList returns a list of all repositories to which +// the user has explicit access in the host system. +func (c *client) RepoList() ([]*Repo, error) { + var out []*Repo + uri := fmt.Sprintf(pathRepos, c.addr) + err := c.get(uri, &out) + return out, err +} + +// RepoListOpts returns a list of all repositories to which +// the user has explicit access in the host system. +func (c *client) RepoListOpts(all bool) ([]*Repo, error) { + var out []*Repo + uri := fmt.Sprintf(pathRepos+"?all=%v", c.addr, all) + err := c.get(uri, &out) + return out, err +} + +// RepoPost activates a repository. +func (c *client) RepoPost(forgeRemoteID int64) (*Repo, error) { + out := new(Repo) + uri := fmt.Sprintf(pathRepoPost, c.addr, forgeRemoteID) + err := c.post(uri, nil, out) + return out, err +} + +// RepoChown updates a repository owner. +func (c *client) RepoChown(repoID int64) (*Repo, error) { + out := new(Repo) + uri := fmt.Sprintf(pathChown, c.addr, repoID) + err := c.post(uri, nil, out) + return out, err +} + +// RepoRepair repairs the repository hooks. +func (c *client) RepoRepair(repoID int64) error { + uri := fmt.Sprintf(pathRepair, c.addr, repoID) + return c.post(uri, nil, nil) +} + +// RepoPatch updates a repository. +func (c *client) RepoPatch(repoID int64, in *RepoPatch) (*Repo, error) { + out := new(Repo) + uri := fmt.Sprintf(pathRepo, c.addr, repoID) + err := c.patch(uri, in, out) + return out, err +} + +// RepoDel deletes a repository. +func (c *client) RepoDel(repoID int64) error { + uri := fmt.Sprintf(pathRepo, c.addr, repoID) + err := c.delete(uri) + return err +} + +// RepoMove moves a repository +func (c *client) RepoMove(repoID int64, newFullName string) error { + uri := fmt.Sprintf(pathRepoMove, c.addr, repoID, newFullName) + return c.post(uri, nil, nil) +} + +// Pipeline returns a repository pipeline by pipeline-id. +func (c *client) Pipeline(repoID, pipeline int64) (*Pipeline, error) { + out := new(Pipeline) + uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline) + err := c.get(uri, out) + return out, err +} + +// Pipeline returns the latest repository pipeline by branch. +func (c *client) PipelineLast(repoID int64, branch string) (*Pipeline, error) { + out := new(Pipeline) + uri := fmt.Sprintf(pathPipeline, c.addr, repoID, "latest") + if len(branch) != 0 { + uri += "?branch=" + branch + } + err := c.get(uri, out) + return out, err +} + +// PipelineList returns a list of recent pipelines for the +// the specified repository. +func (c *client) PipelineList(repoID int64) ([]*Pipeline, error) { + var out []*Pipeline + uri := fmt.Sprintf(pathPipelines, c.addr, repoID) + err := c.get(uri, &out) + return out, err +} + +func (c *client) PipelineCreate(repoID int64, options *PipelineOptions) (*Pipeline, error) { + var out *Pipeline + uri := fmt.Sprintf(pathPipelines, c.addr, repoID) + err := c.post(uri, options, &out) + return out, err +} + +// PipelineQueue returns a list of enqueued pipelines. +func (c *client) PipelineQueue() ([]*Feed, error) { + var out []*Feed + uri := fmt.Sprintf(pathPipelineQueue, c.addr) + err := c.get(uri, &out) + return out, err +} + +// PipelineStart re-starts a stopped pipeline. +func (c *client) PipelineStart(repoID, pipeline int64, params map[string]string) (*Pipeline, error) { + out := new(Pipeline) + val := mapValues(params) + uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline) + err := c.post(uri+"?"+val.Encode(), nil, out) + return out, err +} + +// PipelineStop cancels the running step. +func (c *client) PipelineStop(repoID, pipeline int64) error { + uri := fmt.Sprintf(pathStop, c.addr, repoID, pipeline) + err := c.post(uri, nil, nil) + return err +} + +// PipelineApprove approves a blocked pipeline. +func (c *client) PipelineApprove(repoID, pipeline int64) (*Pipeline, error) { + out := new(Pipeline) + uri := fmt.Sprintf(pathApprove, c.addr, repoID, pipeline) + err := c.post(uri, nil, out) + return out, err +} + +// PipelineDecline declines a blocked pipeline. +func (c *client) PipelineDecline(repoID, pipeline int64) (*Pipeline, error) { + out := new(Pipeline) + uri := fmt.Sprintf(pathDecline, c.addr, repoID, pipeline) + err := c.post(uri, nil, out) + return out, err +} + +// PipelineKill force kills the running pipeline. +func (c *client) PipelineKill(repoID, pipeline int64) error { + uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline) + err := c.delete(uri) + return err +} + +// PipelineLogs returns the pipeline logs for the specified step. +func (c *client) StepLogEntries(repoID, num, step int64) ([]*LogEntry, error) { + uri := fmt.Sprintf(pathLogs, c.addr, repoID, num, step) + var out []*LogEntry + err := c.get(uri, &out) + return out, err +} + +// Deploy triggers a deployment for an existing pipeline using the +// specified target environment. +func (c *client) Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) { + out := new(Pipeline) + val := mapValues(params) + val.Set("event", EventDeploy) + val.Set("deploy_to", env) + uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline) + err := c.post(uri+"?"+val.Encode(), nil, out) + return out, err +} + +// LogsPurge purges the pipeline logs for the specified pipeline. +func (c *client) LogsPurge(repoID, pipeline int64) error { + uri := fmt.Sprintf(pathLogPurge, c.addr, repoID, pipeline) + err := c.delete(uri) + return err +} + +// Registry returns a registry by hostname. +func (c *client) Registry(repoID int64, hostname string) (*Registry, error) { + out := new(Registry) + uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname) + err := c.get(uri, out) + return out, err +} + +// RegistryList returns a list of all repository registries. +func (c *client) RegistryList(repoID int64) ([]*Registry, error) { + var out []*Registry + uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID) + err := c.get(uri, &out) + return out, err +} + +// RegistryCreate creates a registry. +func (c *client) RegistryCreate(repoID int64, in *Registry) (*Registry, error) { + out := new(Registry) + uri := fmt.Sprintf(pathRepoRegistries, c.addr, repoID) + err := c.post(uri, in, out) + return out, err +} + +// RegistryUpdate updates a registry. +func (c *client) RegistryUpdate(repoID int64, in *Registry) (*Registry, error) { + out := new(Registry) + uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, in.Address) + err := c.patch(uri, in, out) + return out, err +} + +// RegistryDelete deletes a registry. +func (c *client) RegistryDelete(repoID int64, hostname string) error { + uri := fmt.Sprintf(pathRepoRegistry, c.addr, repoID, hostname) + return c.delete(uri) +} + +// Secret returns a secret by name. +func (c *client) Secret(repoID int64, secret string) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret) + err := c.get(uri, out) + return out, err +} + +// SecretList returns a list of all repository secrets. +func (c *client) SecretList(repoID int64) ([]*Secret, error) { + var out []*Secret + uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID) + err := c.get(uri, &out) + return out, err +} + +// SecretCreate creates a secret. +func (c *client) SecretCreate(repoID int64, in *Secret) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathRepoSecrets, c.addr, repoID) + err := c.post(uri, in, out) + return out, err +} + +// SecretUpdate updates a secret. +func (c *client) SecretUpdate(repoID int64, in *Secret) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, in.Name) + err := c.patch(uri, in, out) + return out, err +} + +// SecretDelete deletes a secret. +func (c *client) SecretDelete(repoID int64, secret string) error { + uri := fmt.Sprintf(pathRepoSecret, c.addr, repoID, secret) + return c.delete(uri) +} + +// Org returns an organization by id. +func (c *client) Org(orgID int64) (*Org, error) { + out := new(Org) + uri := fmt.Sprintf(pathOrg, c.addr, orgID) + err := c.get(uri, out) + return out, err +} + +// OrgLookup returns a organsization by its name. +func (c *client) OrgLookup(name string) (*Org, error) { + out := new(Org) + uri := fmt.Sprintf(pathOrgLookup, c.addr, name) + err := c.get(uri, out) + return out, err +} + +// OrgSecret returns an organization secret by name. +func (c *client) OrgSecret(orgID int64, secret string) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret) + err := c.get(uri, out) + return out, err +} + +// OrgSecretList returns a list of all organization secrets. +func (c *client) OrgSecretList(orgID int64) ([]*Secret, error) { + var out []*Secret + uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID) + err := c.get(uri, &out) + return out, err +} + +// OrgSecretCreate creates an organization secret. +func (c *client) OrgSecretCreate(orgID int64, in *Secret) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathOrgSecrets, c.addr, orgID) + err := c.post(uri, in, out) + return out, err +} + +// OrgSecretUpdate updates an organization secret. +func (c *client) OrgSecretUpdate(orgID int64, in *Secret) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, in.Name) + err := c.patch(uri, in, out) + return out, err +} + +// OrgSecretDelete deletes an organization secret. +func (c *client) OrgSecretDelete(orgID int64, secret string) error { + uri := fmt.Sprintf(pathOrgSecret, c.addr, orgID, secret) + return c.delete(uri) +} + +// GlobalOrgSecret returns an global secret by name. +func (c *client) GlobalSecret(secret string) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret) + err := c.get(uri, out) + return out, err +} + +// GlobalSecretList returns a list of all global secrets. +func (c *client) GlobalSecretList() ([]*Secret, error) { + var out []*Secret + uri := fmt.Sprintf(pathGlobalSecrets, c.addr) + err := c.get(uri, &out) + return out, err +} + +// GlobalSecretCreate creates a global secret. +func (c *client) GlobalSecretCreate(in *Secret) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathGlobalSecrets, c.addr) + err := c.post(uri, in, out) + return out, err +} + +// GlobalSecretUpdate updates a global secret. +func (c *client) GlobalSecretUpdate(in *Secret) (*Secret, error) { + out := new(Secret) + uri := fmt.Sprintf(pathGlobalSecret, c.addr, in.Name) + err := c.patch(uri, in, out) + return out, err +} + +// GlobalSecretDelete deletes a global secret. +func (c *client) GlobalSecretDelete(secret string) error { + uri := fmt.Sprintf(pathGlobalSecret, c.addr, secret) + return c.delete(uri) +} + +// QueueInfo returns queue info +func (c *client) QueueInfo() (*Info, error) { + out := new(Info) + uri := fmt.Sprintf(pathQueue+"/info", c.addr) + err := c.get(uri, out) + return out, err +} + +// LogLevel returns the current logging level +func (c *client) LogLevel() (*LogLevel, error) { + out := new(LogLevel) + uri := fmt.Sprintf(pathLogLevel, c.addr) + err := c.get(uri, out) + return out, err +} + +// SetLogLevel sets the logging level of the server +func (c *client) SetLogLevel(in *LogLevel) (*LogLevel, error) { + out := new(LogLevel) + uri := fmt.Sprintf(pathLogLevel, c.addr) + err := c.post(uri, in, out) + return out, err +} + +func (c *client) CronList(repoID int64) ([]*Cron, error) { + out := make([]*Cron, 0, 5) + uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID) + return out, c.get(uri, &out) +} + +func (c *client) CronCreate(repoID int64, in *Cron) (*Cron, error) { + out := new(Cron) + uri := fmt.Sprintf(pathRepoCrons, c.addr, repoID) + return out, c.post(uri, in, out) +} + +func (c *client) CronUpdate(repoID int64, in *Cron) (*Cron, error) { + out := new(Cron) + uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, in.ID) + err := c.patch(uri, in, out) + return out, err +} + +func (c *client) CronDelete(repoID, cronID int64) error { + uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID) + return c.delete(uri) +} + +func (c *client) CronGet(repoID, cronID int64) (*Cron, error) { + out := new(Cron) + uri := fmt.Sprintf(pathRepoCron, c.addr, repoID, cronID) + return out, c.get(uri, out) +} + +func (c *client) AgentList() ([]*Agent, error) { + out := make([]*Agent, 0, 5) + uri := fmt.Sprintf(pathAgents, c.addr) + return out, c.get(uri, &out) +} + +func (c *client) Agent(agentID int64) (*Agent, error) { + out := new(Agent) + uri := fmt.Sprintf(pathAgent, c.addr, agentID) + return out, c.get(uri, out) +} + +func (c *client) AgentCreate(in *Agent) (*Agent, error) { + out := new(Agent) + uri := fmt.Sprintf(pathAgents, c.addr) + return out, c.post(uri, in, out) +} + +func (c *client) AgentUpdate(in *Agent) (*Agent, error) { + out := new(Agent) + uri := fmt.Sprintf(pathAgent, c.addr, in.ID) + return out, c.patch(uri, in, out) +} + +func (c *client) AgentDelete(agentID int64) error { + uri := fmt.Sprintf(pathAgent, c.addr, agentID) + return c.delete(uri) +} + +func (c *client) AgentTasksList(agentID int64) ([]*Task, error) { + out := make([]*Task, 0, 5) + uri := fmt.Sprintf(pathAgentTasks, c.addr, agentID) + return out, c.get(uri, &out) +} + +func (c *client) Version() (*Version, error) { + out := new(Version) + uri := fmt.Sprintf(pathVersion, c.addr) + return out, c.get(uri, &out) +} + +// +// http request helper functions +// + +// helper function for making an http GET request. +func (c *client) get(rawurl string, out any) error { + return c.do(rawurl, http.MethodGet, nil, out) +} + +// helper function for making an http POST request. +func (c *client) post(rawurl string, in, out any) error { + return c.do(rawurl, http.MethodPost, in, out) +} + +// helper function for making an http PATCH request. +func (c *client) patch(rawurl string, in, out any) error { + return c.do(rawurl, http.MethodPatch, in, out) +} + +// helper function for making an http DELETE request. +func (c *client) delete(rawurl string) error { + return c.do(rawurl, http.MethodDelete, nil, nil) +} + +// helper function to make an http request +func (c *client) do(rawurl, method string, in, out any) error { + body, err := c.open(rawurl, method, in) + if err != nil { + return err + } + defer body.Close() + if out != nil { + return json.NewDecoder(body).Decode(out) + } + return nil +} + +// helper function to open an http request +func (c *client) open(rawurl, method string, in any) (io.ReadCloser, error) { + uri, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + req, err := http.NewRequest(method, uri.String(), nil) + if err != nil { + return nil, err + } + if in != nil { + decoded, derr := json.Marshal(in) + if derr != nil { + return nil, derr + } + buf := bytes.NewBuffer(decoded) + req.Body = io.NopCloser(buf) + req.ContentLength = int64(len(decoded)) + req.Header.Set("Content-Length", strconv.Itoa(len(decoded))) + req.Header.Set("Content-Type", "application/json") + } + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode > http.StatusPartialContent { + defer resp.Body.Close() + out, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("client error %d: %s", resp.StatusCode, string(out)) + } + return resp.Body, nil +} + +// mapValues converts a map to url.Values +func mapValues(params map[string]string) url.Values { + values := url.Values{} + for key, val := range params { + values.Add(key, val) + } + return values +} diff --git a/internal/woodpecker/const.go b/internal/woodpecker/const.go new file mode 100644 index 0000000..8ba2852 --- /dev/null +++ b/internal/woodpecker/const.go @@ -0,0 +1,57 @@ +// Copyright 2022 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package woodpecker + +// Event values. +const ( + EventPush = "push" + EventPull = "pull_request" + EventTag = "tag" + EventDeploy = "deployment" +) + +// Status values. +const ( + StatusBlocked = "blocked" + StatusSkipped = "skipped" + StatusPending = "pending" + StatusRunning = "running" + StatusSuccess = "success" + StatusFailure = "failure" + StatusKilled = "killed" + StatusError = "error" +) + +// LogEntryType identifies the type of line in the logs. +type LogEntryType int + +const ( + LogEntryStdout LogEntryType = iota + LogEntryStderr + LogEntryExitCode + LogEntryMetadata + LogEntryProgress +) + +// StepType identifies the type of step +type StepType string + +const ( + StepTypeClone StepType = "clone" + StepTypeService StepType = "service" + StepTypePlugin StepType = "plugin" + StepTypeCommands StepType = "commands" + StepTypeCache StepType = "cache" +) diff --git a/internal/woodpecker/interface.go b/internal/woodpecker/interface.go new file mode 100644 index 0000000..e17cdf3 --- /dev/null +++ b/internal/woodpecker/interface.go @@ -0,0 +1,231 @@ +// Copyright 2022 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package woodpecker + +import ( + "net/http" +) + +// Client is used to communicate with a Woodpecker server. +type Client interface { + // SetClient sets the http.Client. + SetClient(*http.Client) + + // SetAddress sets the server address. + SetAddress(string) + + // Self returns the currently authenticated user. + Self() (*User, error) + + // User returns a user by login. + User(string) (*User, error) + + // UserList returns a list of all registered users. + UserList() ([]*User, error) + + // UserPost creates a new user account. + UserPost(*User) (*User, error) + + // UserPatch updates a user account. + UserPatch(*User) (*User, error) + + // UserDel deletes a user account. + UserDel(string) error + + // Repo returns a repository by name. + Repo(repoID int64) (*Repo, error) + + // RepoLookup returns a repository id by the owner and name. + RepoLookup(repoFullName string) (*Repo, error) + + // RepoList returns a list of all repositories to which the user has explicit + // access in the host system. + RepoList() ([]*Repo, error) + + // RepoListOpts returns a list of all repositories to which the user has + // explicit access in the host system. + RepoListOpts(bool) ([]*Repo, error) + + // RepoPost activates a repository. + RepoPost(forgeRemoteID int64) (*Repo, error) + + // RepoPatch updates a repository. + RepoPatch(repoID int64, repo *RepoPatch) (*Repo, error) + + // RepoMove moves the repository + RepoMove(repoID int64, dst string) error + + // RepoChown updates a repository owner. + RepoChown(repoID int64) (*Repo, error) + + // RepoRepair repairs the repository hooks. + RepoRepair(repoID int64) error + + // RepoDel deletes a repository. + RepoDel(repoID int64) error + + // Pipeline returns a repository pipeline by number. + Pipeline(repoID, pipeline int64) (*Pipeline, error) + + // PipelineLast returns the latest repository pipeline by branch. An empty branch + // will result in the default branch. + PipelineLast(repoID int64, branch string) (*Pipeline, error) + + // PipelineList returns a list of recent pipelines for the + // the specified repository. + PipelineList(repoID int64) ([]*Pipeline, error) + + // PipelineQueue returns a list of enqueued pipelines. + PipelineQueue() ([]*Feed, error) + + // PipelineCreate returns creates a pipeline on specified branch. + PipelineCreate(repoID int64, opts *PipelineOptions) (*Pipeline, error) + + // PipelineStart re-starts a stopped pipeline. + PipelineStart(repoID, num int64, params map[string]string) (*Pipeline, error) + + // PipelineStop stops the given pipeline. + PipelineStop(repoID, pipeline int64) error + + // PipelineApprove approves a blocked pipeline. + PipelineApprove(repoID, pipeline int64) (*Pipeline, error) + + // PipelineDecline declines a blocked pipeline. + PipelineDecline(repoID, pipeline int64) (*Pipeline, error) + + // PipelineKill force kills the running pipeline. + PipelineKill(repoID, pipeline int64) error + + // StepLogEntries returns the LogEntries for the given pipeline step + StepLogEntries(repoID, pipeline, stepID int64) ([]*LogEntry, error) + + // Deploy triggers a deployment for an existing pipeline using the specified + // target environment. + Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) + + // LogsPurge purges the pipeline logs for the specified pipeline. + LogsPurge(repoID, pipeline int64) error + + // Registry returns a registry by hostname. + Registry(repoID int64, hostname string) (*Registry, error) + + // RegistryList returns a list of all repository registries. + RegistryList(repoID int64) ([]*Registry, error) + + // RegistryCreate creates a registry. + RegistryCreate(repoID int64, registry *Registry) (*Registry, error) + + // RegistryUpdate updates a registry. + RegistryUpdate(repoID int64, registry *Registry) (*Registry, error) + + // RegistryDelete deletes a registry. + RegistryDelete(repoID int64, hostname string) error + + // Secret returns a secret by name. + Secret(repoID int64, secret string) (*Secret, error) + + // SecretList returns a list of all repository secrets. + SecretList(repoID int64) ([]*Secret, error) + + // SecretCreate creates a secret. + SecretCreate(repoID int64, secret *Secret) (*Secret, error) + + // SecretUpdate updates a secret. + SecretUpdate(repoID int64, secret *Secret) (*Secret, error) + + // SecretDelete deletes a secret. + SecretDelete(repoID int64, secret string) error + + // Org returns an organization by name. + Org(orgID int64) (*Org, error) + + // OrgLookup returns an organization id by name. + OrgLookup(orgName string) (*Org, error) + + // OrgSecret returns an organization secret by name. + OrgSecret(orgID int64, secret string) (*Secret, error) + + // OrgSecretList returns a list of all organization secrets. + OrgSecretList(orgID int64) ([]*Secret, error) + + // OrgSecretCreate creates an organization secret. + OrgSecretCreate(orgID int64, secret *Secret) (*Secret, error) + + // OrgSecretUpdate updates an organization secret. + OrgSecretUpdate(orgID int64, secret *Secret) (*Secret, error) + + // OrgSecretDelete deletes an organization secret. + OrgSecretDelete(orgID int64, secret string) error + + // GlobalSecret returns an global secret by name. + GlobalSecret(secret string) (*Secret, error) + + // GlobalSecretList returns a list of all global secrets. + GlobalSecretList() ([]*Secret, error) + + // GlobalSecretCreate creates a global secret. + GlobalSecretCreate(secret *Secret) (*Secret, error) + + // GlobalSecretUpdate updates a global secret. + GlobalSecretUpdate(secret *Secret) (*Secret, error) + + // GlobalSecretDelete deletes a global secret. + GlobalSecretDelete(secret string) error + + // QueueInfo returns the queue state. + QueueInfo() (*Info, error) + + // LogLevel returns the current logging level + LogLevel() (*LogLevel, error) + + // SetLogLevel sets the server's logging level + SetLogLevel(logLevel *LogLevel) (*LogLevel, error) + + // CronList list all cron jobs of a repo + CronList(repoID int64) ([]*Cron, error) + + // CronGet get a specific cron job of a repo by id + CronGet(repoID, cronID int64) (*Cron, error) + + // CronDelete delete a specific cron job of a repo by id + CronDelete(repoID, cronID int64) error + + // CronCreate create a new cron job in a repo + CronCreate(repoID int64, cron *Cron) (*Cron, error) + + // CronUpdate update an existing cron job of a repo + CronUpdate(repoID int64, cron *Cron) (*Cron, error) + + // AgentList returns a list of all registered agents + AgentList() ([]*Agent, error) + + // Agent returns an agent by id + Agent(int64) (*Agent, error) + + // AgentCreate creates a new agent + AgentCreate(*Agent) (*Agent, error) + + // AgentUpdate updates an existing agent + AgentUpdate(*Agent) (*Agent, error) + + // AgentDelete deletes an agent + AgentDelete(int64) error + + // AgentTasksList returns a list of all tasks executed by an agent + AgentTasksList(int64) ([]*Task, error) + + // Version returns the instance version. + Version() (*Version, error) +} diff --git a/internal/woodpecker/types.go b/internal/woodpecker/types.go new file mode 100644 index 0000000..d243d36 --- /dev/null +++ b/internal/woodpecker/types.go @@ -0,0 +1,257 @@ +// Copyright 2022 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package woodpecker + +type ( + // User represents a user account. + User struct { + ID int64 `json:"id"` + Login string `json:"login"` + Email string `json:"email"` + Avatar string `json:"avatar_url"` + Active bool `json:"active"` + Admin bool `json:"admin"` + } + + // Repo represents a repository. + Repo struct { + ID int64 `json:"id,omitempty"` + ForgeRemoteID string `json:"forge_remote_id"` + Owner string `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Avatar string `json:"avatar_url,omitempty"` + ForgeURL string `json:"forge_url,omitempty"` + Clone string `json:"clone_url,omitempty"` + DefaultBranch string `json:"default_branch,omitempty"` + SCMKind string `json:"scm,omitempty"` + Timeout int64 `json:"timeout,omitempty"` + Visibility string `json:"visibility"` + IsSCMPrivate bool `json:"private"` + IsTrusted bool `json:"trusted"` + IsGated bool `json:"gated"` + IsActive bool `json:"active"` + AllowPullRequests bool `json:"allow_pr"` + Config string `json:"config_file"` + CancelPreviousPipelineEvents []string `json:"cancel_previous_pipeline_events"` + NetrcOnlyTrusted bool `json:"netrc_only_trusted"` + } + + // RepoPatch defines a repository patch request. + RepoPatch struct { + Config *string `json:"config_file,omitempty"` + IsTrusted *bool `json:"trusted,omitempty"` + IsGated *bool `json:"gated,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Visibility *string `json:"visibility"` + AllowPull *bool `json:"allow_pr,omitempty"` + PipelineCounter *int `json:"pipeline_counter,omitempty"` + } + + PipelineError struct { + Type string `json:"type"` + Message string `json:"message"` + IsWarning bool `json:"is_warning"` + Data any `json:"data"` + } + + // Pipeline defines a pipeline object. + Pipeline struct { + ID int64 `json:"id"` + Number int64 `json:"number"` + Parent int64 `json:"parent"` + Event string `json:"event"` + Status string `json:"status"` + Errors PipelineError `json:"errors"` + Enqueued int64 `json:"enqueued_at"` + Created int64 `json:"created_at"` + Updated int64 `json:"updated_at"` + Started int64 `json:"started_at"` + Finished int64 `json:"finished_at"` + Deploy string `json:"deploy_to"` + Commit string `json:"commit"` + Branch string `json:"branch"` + Ref string `json:"ref"` + Refspec string `json:"refspec"` + CloneURL string `json:"clone_url"` + Title string `json:"title"` + Message string `json:"message"` + Timestamp int64 `json:"timestamp"` + Sender string `json:"sender"` + Author string `json:"author"` + Avatar string `json:"author_avatar"` + Email string `json:"author_email"` + ForgeURL string `json:"forge_url"` + Reviewer string `json:"reviewed_by"` + Reviewed int64 `json:"reviewed_at"` + Workflows []*Workflow `json:"workflows,omitempty"` + } + + // Workflow represents a workflow in the pipeline. + Workflow struct { + ID int64 `json:"id"` + PID int `json:"pid"` + Name string `json:"name"` + State string `json:"state"` + Error string `json:"error,omitempty"` + Started int64 `json:"start_time,omitempty"` + Stopped int64 `json:"end_time,omitempty"` + AgentID int64 `json:"agent_id,omitempty"` + Platform string `json:"platform,omitempty"` + Environ map[string]string `json:"environ,omitempty"` + Children []*Step `json:"children,omitempty"` + } + + // Step represents a process in the pipeline. + Step struct { + ID int64 `json:"id"` + PID int `json:"pid"` + PPID int `json:"ppid"` + Name string `json:"name"` + State string `json:"state"` + Error string `json:"error,omitempty"` + ExitCode int `json:"exit_code"` + Started int64 `json:"start_time,omitempty"` + Stopped int64 `json:"end_time,omitempty"` + Type StepType `json:"type,omitempty"` + } + + // Registry represents a docker registry with credentials. + Registry struct { + ID int64 `json:"id"` + Address string `json:"address"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + Email string `json:"email"` + Token string `json:"token"` + } + + // Secret represents a secret variable, such as a password or token. + Secret struct { + ID int64 `json:"id"` + Name string `json:"name"` + Value string `json:"value,omitempty"` + Images []string `json:"images"` + Events []string `json:"events"` + } + + // Feed represents an item in the user's feed or timeline. + Feed struct { + RepoID int64 `json:"repo_id"` + ID int64 `json:"id,omitempty"` + Number int64 `json:"number,omitempty"` + Event string `json:"event,omitempty"` + Status string `json:"status,omitempty"` + Created int64 `json:"created_at,omitempty"` + Started int64 `json:"started_at,omitempty"` + Finished int64 `json:"finished_at,omitempty"` + Commit string `json:"commit,omitempty"` + Branch string `json:"branch,omitempty"` + Ref string `json:"ref,omitempty"` + Refspec string `json:"refspec,omitempty"` + Remote string `json:"remote,omitempty"` + Title string `json:"title,omitempty"` + Message string `json:"message,omitempty"` + Author string `json:"author,omitempty"` + Avatar string `json:"author_avatar,omitempty"` + Email string `json:"author_email,omitempty"` + } + + // Version provides system version details. + Version struct { + Source string `json:"source,omitempty"` + Version string `json:"version,omitempty"` + Commit string `json:"commit,omitempty"` + } + + // Info provides queue stats. + Info struct { + Stats struct { + Workers int `json:"worker_count"` + Pending int `json:"pending_count"` + WaitingOnDeps int `json:"waiting_on_deps_count"` + Running int `json:"running_count"` + Complete int `json:"completed_count"` + } `json:"stats"` + Paused bool `json:"paused,omitempty"` + } + + // LogLevel is for checking/setting logging level + LogLevel struct { + Level string `json:"log-level"` + } + + // LogEntry is a single log entry + LogEntry struct { + ID int64 `json:"id"` + StepID int64 `json:"step_id"` + Time int64 `json:"time"` + Line int `json:"line"` + Data []byte `json:"data"` + Type LogEntryType `json:"type"` + } + + // Cron is the JSON data of a cron job + Cron struct { + ID int64 `json:"id"` + Name string `json:"name"` + RepoID int64 `json:"repo_id"` + CreatorID int64 `json:"creator_id"` + NextExec int64 `json:"next_exec"` + Schedule string `json:"schedule"` + Created int64 `json:"created_at"` + Branch string `json:"branch"` + } + + // PipelineOptions is the JSON data for creating a new pipeline + PipelineOptions struct { + Branch string `json:"branch"` + Variables map[string]string `json:"variables"` + } + + // Agent is the JSON data for an agent + Agent struct { + ID int64 `json:"id"` + Created int64 `json:"created"` + Updated int64 `json:"updated"` + Name string `json:"name"` + OwnerID int64 `json:"owner_id"` + Token string `json:"token"` + LastContact int64 `json:"last_contact"` + Platform string `json:"platform"` + Backend string `json:"backend"` + Capacity int32 `json:"capacity"` + Version string `json:"version"` + NoSchedule bool `json:"no_schedule"` + } + + // Task is the JSON data for a task + Task struct { + ID string `json:"id"` + Data []byte `json:"data"` + Labels map[string]string `json:"labels"` + Dependencies []string `json:"dependencies"` + RunOn []string `json:"run_on"` + DepStatus map[string]string `json:"dep_status"` + AgentID int64 `json:"agent_id"` + } + + // Org is the JSON data for an organization + Org struct { + ID int64 `json:"id"` + Name string `json:"name"` + IsUser bool `json:"is_user"` + } +) diff --git a/internal/woodpecker/woodpecker.go b/internal/woodpecker/woodpecker.go new file mode 100644 index 0000000..801831f --- /dev/null +++ b/internal/woodpecker/woodpecker.go @@ -0,0 +1,5 @@ +package woodpecker + +// This package has been copied from https://github.com/woodpecker-ci/woodpecker/tree/v2.0.0/woodpecker-go/woodpecker. +// At the moment of writing this comment, this package can't be imported because the module name hasn't been updated - https://github.com/woodpecker-ci/woodpecker/blob/v2.0.0/go.mod#L1. +// https://go.dev/blog/v2-go-modules#publishing-v2-and-beyond