From 00b46b4effc6cab41cbcd69f1a773141cf362b63 Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Tue, 7 May 2024 12:09:08 +0200 Subject: [PATCH] docs: hide system flags and refactor generator (#42) --- Makefile | 7 +- cmd/wp-gitea-release/docs.go | 58 ----- cmd/wp-gitea-release/flags.go | 96 --------- cmd/wp-gitea-release/main.go | 15 +- .../templates/docs-data.yaml.tmpl | 18 -- docs/data/data.yaml | 16 ++ go.mod | 12 +- go.sum | 21 +- internal/doc/main.go | 42 ++++ plugin/impl.go | 10 +- plugin/plugin.go | 120 ++++++++++- plugin/release.go | 24 ++- plugin/{utils.go => util.go} | 54 ++--- plugin/util_test.go | 204 ++++++++++++++++++ 14 files changed, 449 insertions(+), 248 deletions(-) delete mode 100644 cmd/wp-gitea-release/docs.go delete mode 100644 cmd/wp-gitea-release/flags.go delete mode 100644 cmd/wp-gitea-release/templates/docs-data.yaml.tmpl create mode 100644 internal/doc/main.go rename plugin/{utils.go => util.go} (53%) create mode 100644 plugin/util_test.go diff --git a/Makefile b/Makefile index 7154ba4..8ccd76e 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,6 @@ GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(G XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GOTESTSUM_PACKAGE ?= gotest.tools/gotestsum@latest -GENERATE ?= XGO_VERSION := go-1.22.x XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64 @@ -65,11 +64,7 @@ lint: golangci-lint .PHONY: generate generate: - $(GO) generate $(GENERATE) - -.PHONY: generate-docs -generate-docs: - $(GO) generate ./cmd/$(EXECUTABLE)/flags.go + $(GO) generate $(PACKAGES) .PHONY: test test: diff --git a/cmd/wp-gitea-release/docs.go b/cmd/wp-gitea-release/docs.go deleted file mode 100644 index 37b319d..0000000 --- a/cmd/wp-gitea-release/docs.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build generate -// +build generate - -package main - -import ( - "bytes" - "embed" - "fmt" - "os" - "text/template" - - "github.com/thegeeklab/wp-gitea-release/plugin" - "github.com/thegeeklab/wp-plugin-go/docs" - wp "github.com/thegeeklab/wp-plugin-go/plugin" - wp_template "github.com/thegeeklab/wp-plugin-go/template" - "github.com/urfave/cli/v2" -) - -//go:embed templates/docs-data.yaml.tmpl -var yamlTemplate embed.FS - -func main() { - settings := &plugin.Settings{} - app := &cli.App{ - Flags: settingsFlags(settings, wp.FlagsPluginCategory), - } - - out, err := toYAML(app) - if err != nil { - panic(err) - } - - fi, err := os.Create("../../docs/data/data-raw.yaml") - if err != nil { - panic(err) - } - defer fi.Close() - if _, err := fi.WriteString(out); err != nil { - panic(err) - } -} - -func toYAML(app *cli.App) (string, error) { - var w bytes.Buffer - - yamlTmpl, err := template.New("docs").Funcs(wp_template.LoadFuncMap()).ParseFS(yamlTemplate, "templates/docs-data.yaml.tmpl") - if err != nil { - fmt.Println(yamlTmpl) - return "", err - } - - if err := yamlTmpl.ExecuteTemplate(&w, "docs-data.yaml.tmpl", docs.GetTemplateData(app)); err != nil { - return "", err - } - - return w.String(), nil -} diff --git a/cmd/wp-gitea-release/flags.go b/cmd/wp-gitea-release/flags.go deleted file mode 100644 index c95bd6c..0000000 --- a/cmd/wp-gitea-release/flags.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "github.com/thegeeklab/wp-gitea-release/plugin" - "github.com/urfave/cli/v2" -) - -// settingsFlags has the cli.Flags for the plugin.Settings. -// -//go:generate go run docs.go flags.go -func settingsFlags(settings *plugin.Settings, category string) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: "api-key", - Usage: "api key to access Gitea API", - EnvVars: []string{"PLUGIN_API_KEY", "GITEA_RELEASE_API_KEY", "GITEA_TOKEN"}, - Destination: &settings.APIKey, - Category: category, - Required: true, - }, - &cli.StringSliceFlag{ - Name: "files", - Usage: "list of files to upload", - EnvVars: []string{"PLUGIN_FILES", "GITEA_RELEASE_FILES"}, - Category: category, - }, - &cli.StringFlag{ - Name: "file-exists", - Value: "overwrite", - Usage: "what to do if file already exist", - EnvVars: []string{"PLUGIN_FILE_EXIST", "GITEA_RELEASE_FILE_EXIST"}, - Destination: &settings.FileExists, - Category: category, - }, - &cli.StringSliceFlag{ - Name: "checksum", - Usage: "generate specific checksums", - EnvVars: []string{"PLUGIN_CHECKSUM", "GITEA_RELEASE_CHECKSUM"}, - Destination: &settings.Checksum, - Category: category, - }, - &cli.BoolFlag{ - Name: "draft", - Usage: "create a draft release", - EnvVars: []string{"PLUGIN_DRAFT", "GITEA_RELEASE_DRAFT"}, - Destination: &settings.Draft, - Category: category, - }, - &cli.BoolFlag{ - Name: "prerelease", - Usage: "set the release as prerelease", - EnvVars: []string{"PLUGIN_PRERELEASE", "GITEA_RELEASE_PRERELEASE"}, - Destination: &settings.PreRelease, - Category: category, - }, - &cli.StringFlag{ - Name: "base-url", - Usage: "URL of the Gitea instance", - EnvVars: []string{"PLUGIN_BASE_URL", "GITEA_RELEASE_BASE_URL"}, - Category: category, - Required: true, - }, - &cli.StringFlag{ - Name: "note", - Usage: "file or string with notes for the release", - EnvVars: []string{"PLUGIN_NOTE", "GITEA_RELEASE_NOTE"}, - Destination: &settings.Note, - Category: category, - }, - &cli.StringFlag{ - Name: "title", - Usage: "file or string for the title shown in the Gitea release", - EnvVars: []string{"PLUGIN_TITLE", "GITEA_RELEASE_TITLE", "CI_COMMIT_TAG"}, - Destination: &settings.Title, - DefaultText: "$CI_COMMIT_TAG", - Category: category, - }, - &cli.StringFlag{ - Name: "event", - Value: "push", - Usage: "build event", - EnvVars: []string{"CI_PIPELINE_EVENT"}, - Destination: &settings.Event, - DefaultText: "$CI_PIPELINE_EVENT", - Category: category, - }, - &cli.StringFlag{ - Name: "commit-ref", - Value: "refs/heads/main", - Usage: "git commit ref", - EnvVars: []string{"CI_COMMIT_REF"}, - Destination: &settings.CommitRef, - Category: category, - }, - } -} diff --git a/cmd/wp-gitea-release/main.go b/cmd/wp-gitea-release/main.go index fd3bb59..0dd287f 100644 --- a/cmd/wp-gitea-release/main.go +++ b/cmd/wp-gitea-release/main.go @@ -1,11 +1,7 @@ package main import ( - "fmt" - "github.com/thegeeklab/wp-gitea-release/plugin" - - wp "github.com/thegeeklab/wp-plugin-go/plugin" ) //nolint:gochecknoglobals @@ -15,14 +11,5 @@ var ( ) func main() { - settings := &plugin.Settings{} - options := wp.Options{ - Name: "wp-gitea-release", - Description: "Publish files and artifacts to Gitea releases", - Version: BuildVersion, - VersionMetadata: fmt.Sprintf("date=%s", BuildDate), - Flags: settingsFlags(settings, wp.FlagsPluginCategory), - } - - plugin.New(options, settings).Run() + plugin.New(nil, BuildVersion, BuildDate).Run() } diff --git a/cmd/wp-gitea-release/templates/docs-data.yaml.tmpl b/cmd/wp-gitea-release/templates/docs-data.yaml.tmpl deleted file mode 100644 index e453a95..0000000 --- a/cmd/wp-gitea-release/templates/docs-data.yaml.tmpl +++ /dev/null @@ -1,18 +0,0 @@ ---- -{{- if .GlobalArgs }} -properties: -{{- range $v := .GlobalArgs }} - - name: {{ $v.Name }} - {{- with $v.Description }} - description: | - {{ . | ToSentence }} - {{- end }} - {{- with $v.Type }} - type: {{ . }} - {{- end }} - {{- with $v.Default }} - defaultValue: {{ . }} - {{- end }} - required: {{ default false $v.Required }} -{{ end -}} -{{ end -}} diff --git a/docs/data/data.yaml b/docs/data/data.yaml index c8cf3ab..fc6e621 100644 --- a/docs/data/data.yaml +++ b/docs/data/data.yaml @@ -38,6 +38,22 @@ properties: type: list required: false + - name: insecure_skip_verify + description: | + Skip SSL verification. + + Activating this option is insecure and should be avoided in most cases. + type: bool + defaultValue: false + required: false + + - name: log_level + description: | + Plugin log level. + type: string + defaultValue: "info" + required: false + - name: note description: | File or string with notes for the release. diff --git a/go.mod b/go.mod index 11d3057..1a4d2e7 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,10 @@ go 1.22 require ( code.gitea.io/sdk/gitea v0.18.0 github.com/rs/zerolog v1.32.0 - github.com/thegeeklab/wp-plugin-go v1.7.1 + github.com/stretchr/testify v1.9.0 + github.com/thegeeklab/wp-plugin-go/v2 v2.3.1 github.com/urfave/cli/v2 v2.27.2 - golang.org/x/crypto v0.22.0 + golang.org/x/crypto v0.23.0 ) require ( @@ -15,6 +16,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/go-fed/httpsig v1.1.0 // indirect github.com/google/uuid v1.1.1 // indirect @@ -26,10 +28,12 @@ require ( github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0cb0070..b5b7d64 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/thegeeklab/wp-plugin-go v1.7.1 h1:zfR/rfNPuyVhXJu1fsLfp4+Mz2pTf6WwW/mIqw9750I= -github.com/thegeeklab/wp-plugin-go v1.7.1/go.mod h1:Ixi5plt9tpFGTu6yc/Inm5DcDpp3xPTeohfr86gf2EU= +github.com/thegeeklab/wp-plugin-go/v2 v2.3.1 h1:ARwYgTPZ5iPsmOenmqcCf8TjiEe8wBOHKO7H/Xshe48= +github.com/thegeeklab/wp-plugin-go/v2 v2.3.1/go.mod h1:0t8M8txtEFiaB6RqLX8vLrxkqAo5FT5Hx7dztN592D4= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= @@ -66,16 +66,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -88,13 +88,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -103,6 +103,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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= diff --git a/internal/doc/main.go b/internal/doc/main.go new file mode 100644 index 0000000..b3968dd --- /dev/null +++ b/internal/doc/main.go @@ -0,0 +1,42 @@ +//go:build generate +// +build generate + +package main + +import ( + "context" + "flag" + "net/http" + "os" + "time" + + "github.com/thegeeklab/wp-gitea-release/plugin" + "github.com/thegeeklab/wp-plugin-go/v2/docs" + "github.com/thegeeklab/wp-plugin-go/v2/template" +) + +func main() { + tmpl := "https://raw.githubusercontent.com/thegeeklab/woodpecker-plugins/main/templates/docs-data.yaml.tmpl" + client := http.Client{ + Timeout: 30 * time.Second, + } + + p := plugin.New(nil) + + out, err := template.Render(context.Background(), client, tmpl, docs.GetTemplateData(p.App)) + if err != nil { + panic(err) + } + + outputFile := flag.String("output", "", "Output file path") + flag.Parse() + + if *outputFile == "" { + panic("no output file specified") + } + + err = os.WriteFile(*outputFile, []byte(out), 0o644) + if err != nil { + panic(err) + } +} diff --git a/plugin/impl.go b/plugin/impl.go index f4a1697..384f997 100644 --- a/plugin/impl.go +++ b/plugin/impl.go @@ -10,7 +10,7 @@ import ( "strings" "code.gitea.io/sdk/gitea" - "github.com/thegeeklab/wp-plugin-go/file" + "github.com/thegeeklab/wp-plugin-go/v2/file" ) var ( @@ -81,7 +81,7 @@ func (p *Plugin) Execute() error { return err } - rc := releaseClient{ + r := &Release{ Client: client, Owner: p.Metadata.Repository.Owner, Repo: p.Metadata.Repository.Name, @@ -93,12 +93,12 @@ func (p *Plugin) Execute() error { Note: p.Settings.Note, } - release, err := rc.buildRelease() + release, err := r.buildRelease() if err != nil { return fmt.Errorf("failed to create the release: %w", err) } - if err := rc.uploadFiles(release.ID, p.Settings.files); err != nil { + if err := r.uploadFiles(release.ID, p.Settings.files); err != nil { return fmt.Errorf("failed to upload the files: %w", err) } @@ -136,7 +136,7 @@ func (p *Plugin) FlagsFromContext() error { if len(p.Settings.Checksum.Value()) > 0 { var err error - files, err = writeChecksums(files, p.Settings.Checksum.Value()) + files, err = WriteChecksums(files, p.Settings.Checksum.Value(), "") if err != nil { return fmt.Errorf("failed to write checksums: %w", err) } diff --git a/plugin/plugin.go b/plugin/plugin.go index 7e22840..ac75651 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -1,12 +1,15 @@ package plugin import ( + "fmt" "net/url" - wp "github.com/thegeeklab/wp-plugin-go/plugin" + wp "github.com/thegeeklab/wp-plugin-go/v2/plugin" "github.com/urfave/cli/v2" ) +//go:generate go run ../internal/doc/main.go -output=../docs/data/data-raw.yaml + // Plugin implements provide the plugin. type Plugin struct { *wp.Plugin @@ -29,13 +32,120 @@ type Settings struct { files []string } -func New(options wp.Options, settings *Settings) *Plugin { - p := &Plugin{} +func New(e wp.ExecuteFunc, build ...string) *Plugin { + p := &Plugin{ + Settings: &Settings{}, + } - options.Execute = p.run + options := wp.Options{ + Name: "wp-gitea-release", + Description: "Publish files and artifacts to Gitea releases", + Flags: Flags(p.Settings, wp.FlagsPluginCategory), + Execute: p.run, + HideWoodpeckerFlags: true, + } + + if len(build) > 0 { + options.Version = build[0] + } + + if len(build) > 1 { + options.VersionMetadata = fmt.Sprintf("date=%s", build[1]) + } + + if e != nil { + options.Execute = e + } p.Plugin = wp.New(options) - p.Settings = settings return p } + +// Flags returns a slice of CLI flags for the plugin. +func Flags(settings *Settings, category string) []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "api-key", + Usage: "api key to access Gitea API", + EnvVars: []string{"PLUGIN_API_KEY", "GITEA_RELEASE_API_KEY", "GITEA_TOKEN"}, + Destination: &settings.APIKey, + Category: category, + Required: true, + }, + &cli.StringSliceFlag{ + Name: "files", + Usage: "list of files to upload", + EnvVars: []string{"PLUGIN_FILES", "GITEA_RELEASE_FILES"}, + Category: category, + }, + &cli.StringFlag{ + Name: "file-exists", + Value: "overwrite", + Usage: "what to do if file already exist", + EnvVars: []string{"PLUGIN_FILE_EXIST", "GITEA_RELEASE_FILE_EXIST"}, + Destination: &settings.FileExists, + Category: category, + }, + &cli.StringSliceFlag{ + Name: "checksum", + Usage: "generate specific checksums", + EnvVars: []string{"PLUGIN_CHECKSUM", "GITEA_RELEASE_CHECKSUM"}, + Destination: &settings.Checksum, + Category: category, + }, + &cli.BoolFlag{ + Name: "draft", + Usage: "create a draft release", + EnvVars: []string{"PLUGIN_DRAFT", "GITEA_RELEASE_DRAFT"}, + Destination: &settings.Draft, + Category: category, + }, + &cli.BoolFlag{ + Name: "prerelease", + Usage: "set the release as prerelease", + EnvVars: []string{"PLUGIN_PRERELEASE", "GITEA_RELEASE_PRERELEASE"}, + Destination: &settings.PreRelease, + Category: category, + }, + &cli.StringFlag{ + Name: "base-url", + Usage: "URL of the Gitea instance", + EnvVars: []string{"PLUGIN_BASE_URL", "GITEA_RELEASE_BASE_URL"}, + Category: category, + Required: true, + }, + &cli.StringFlag{ + Name: "note", + Usage: "file or string with notes for the release", + EnvVars: []string{"PLUGIN_NOTE", "GITEA_RELEASE_NOTE"}, + Destination: &settings.Note, + Category: category, + }, + &cli.StringFlag{ + Name: "title", + Usage: "file or string for the title shown in the Gitea release", + EnvVars: []string{"PLUGIN_TITLE", "GITEA_RELEASE_TITLE", "CI_COMMIT_TAG"}, + Destination: &settings.Title, + DefaultText: "$CI_COMMIT_TAG", + Category: category, + }, + &cli.StringFlag{ + Name: "event", + Value: "push", + Usage: "build event", + EnvVars: []string{"CI_PIPELINE_EVENT"}, + Destination: &settings.Event, + DefaultText: "$CI_PIPELINE_EVENT", + Category: category, + }, + &cli.StringFlag{ + Name: "commit-ref", + Value: "refs/heads/main", + Usage: "git commit ref", + EnvVars: []string{"CI_COMMIT_REF"}, + Destination: &settings.CommitRef, + Category: category, + }, + } +} diff --git a/plugin/release.go b/plugin/release.go index ba40be9..149c50e 100644 --- a/plugin/release.go +++ b/plugin/release.go @@ -15,8 +15,8 @@ var ( ErrFileExists = errors.New("asset file already exist") ) -// Release holds ties the Woodpecker env data and gitea client together. -type releaseClient struct { +// Release represents a release for a Gitea repository. +type Release struct { *gitea.Client Owner string Repo string @@ -28,7 +28,8 @@ type releaseClient struct { Note string } -func (rc *releaseClient) buildRelease() (*gitea.Release, error) { +// buildRelease attempts to retrieve an existing release by the specified tag name. +func (rc *Release) buildRelease() (*gitea.Release, error) { // first attempt to get a release by that tag release, err := rc.getRelease() @@ -47,7 +48,8 @@ func (rc *releaseClient) buildRelease() (*gitea.Release, error) { return release, nil } -func (rc *releaseClient) getRelease() (*gitea.Release, error) { +// getRelease retrieves the release with the specified tag name from the repository. +func (rc *Release) getRelease() (*gitea.Release, error) { releases, _, err := rc.Client.ListReleases(rc.Owner, rc.Repo, gitea.ListReleasesOptions{}) if err != nil { return nil, err @@ -64,7 +66,8 @@ func (rc *releaseClient) getRelease() (*gitea.Release, error) { return nil, fmt.Errorf("%w: %s", ErrReleaseNotFound, rc.Tag) } -func (rc *releaseClient) newRelease() (*gitea.Release, error) { +// newRelease creates a new release on the repository with the specified options. +func (rc *Release) newRelease() (*gitea.Release, error) { r := gitea.CreateReleaseOption{ TagName: rc.Tag, IsDraft: rc.Draft, @@ -83,7 +86,16 @@ func (rc *releaseClient) newRelease() (*gitea.Release, error) { return release, nil } -func (rc *releaseClient) uploadFiles(releaseID int64, files []string) error { +// uploadFiles uploads the specified files as attachments to the release with the given ID. +// It first checks for any existing attachments with the same names, +// and handles them according to the FileExists option: +// +// - "overwrite": overwrites the existing attachment +// - "fail": returns an error if the file already exists +// - "skip": skips uploading the file and logs a warning +// +// If there are no conflicts, it uploads the new files as attachments to the release. +func (rc *Release) uploadFiles(releaseID int64, files []string) error { attachments, _, err := rc.Client.ListReleaseAttachments( rc.Owner, rc.Repo, diff --git a/plugin/utils.go b/plugin/util.go similarity index 53% rename from plugin/utils.go rename to plugin/util.go index ff58cb1..aaa33f7 100644 --- a/plugin/utils.go +++ b/plugin/util.go @@ -11,7 +11,7 @@ import ( "hash/crc32" "io" "os" - "strconv" + "path/filepath" "golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2s" @@ -19,7 +19,9 @@ import ( var ErrHashMethodNotSupported = errors.New("hash method not supported") -func checksum(r io.Reader, method string) (string, error) { +// Checksum calculates the checksum of the given io.Reader using the specified hash method. +// Supported hash methods are: "md5", "sha1", "sha256", "sha512", "adler32", "crc32", "blake2b", "blake2s". +func Checksum(r io.Reader, method string) (string, error) { b, err := io.ReadAll(r) if err != nil { return "", err @@ -37,9 +39,9 @@ func checksum(r io.Reader, method string) (string, error) { case "sha512": return fmt.Sprintf("%x", sha512.Sum512(b)), nil case "adler32": - return strconv.FormatUint(uint64(adler32.Checksum(b)), 10), nil + return fmt.Sprintf("%08x", adler32.Checksum(b)), nil case "crc32": - return strconv.FormatUint(uint64(crc32.ChecksumIEEE(b)), 10), nil + return fmt.Sprintf("%08x", crc32.ChecksumIEEE(b)), nil case "blake2b": return fmt.Sprintf("%x", blake2b.Sum256(b)), nil case "blake2s": @@ -49,44 +51,44 @@ func checksum(r io.Reader, method string) (string, error) { return "", fmt.Errorf("%w: %q", ErrHashMethodNotSupported, method) } -func writeChecksums(files, methods []string) ([]string, error) { - checksums := make(map[string][]string) +// WriteChecksums calculates the checksums for the given files using the specified hash methods, +// and writes the checksums to files named after the hash methods (e.g. "md5sum.txt", "sha256sum.txt"). +func WriteChecksums(files, methods []string, outDir string) ([]string, error) { + if len(files) == 0 { + return files, nil + } + + checksumFiles := make([]string, 0) for _, method := range methods { + checksumFile := filepath.Join(outDir, method+"sum.txt") + + f, err := os.Create(checksumFile) + if err != nil { + return nil, err + } + defer f.Close() + for _, file := range files { handle, err := os.Open(file) if err != nil { return nil, fmt.Errorf("failed to read %q artifact: %w", file, err) } + defer handle.Close() - hash, err := checksum(handle, method) + hash, err := Checksum(handle, method) if err != nil { return nil, fmt.Errorf("could not checksum %q file: %w", file, err) } - checksums[method] = append(checksums[method], hash, file) - } - } - - for method, results := range checksums { - filename := method + "sum.txt" - - f, err := os.Create(filename) - if err != nil { - return nil, err - } - - for i := 0; i < len(results); i += 2 { - hash := results[i] - file := results[i+1] - - if _, err := f.WriteString(fmt.Sprintf("%s %s\n", hash, file)); err != nil { + _, err = f.WriteString(fmt.Sprintf("%s %s\n", hash, file)) + if err != nil { return nil, err } } - files = append(files, filename) + checksumFiles = append(checksumFiles, checksumFile) } - return files, nil + return append(files, checksumFiles...), nil } diff --git a/plugin/util_test.go b/plugin/util_test.go new file mode 100644 index 0000000..6973002 --- /dev/null +++ b/plugin/util_test.go @@ -0,0 +1,204 @@ +package plugin + +import ( + "bytes" + "io" + "os" + "path/filepath" + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +//nolint:lll +func TestChecksum(t *testing.T) { + tests := []struct { + name string + method string + input []byte + want string + wantErr bool + }{ + { + name: "md5", + method: "md5", + input: []byte("hello"), + want: "5d41402abc4b2a76b9719d911017c592", + wantErr: false, + }, + { + name: "sha1", + method: "sha1", + input: []byte("hello"), + want: "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d", + wantErr: false, + }, + { + name: "sha256", + method: "sha256", + input: []byte("hello"), + want: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", + wantErr: false, + }, + { + name: "sha512", + method: "sha512", + input: []byte("hello"), + want: "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043", + wantErr: false, + }, + { + name: "adler32", + method: "adler32", + input: []byte("hello"), + want: "062c0215", + wantErr: false, + }, + { + name: "crc32", + method: "crc32", + input: []byte("hello"), + want: "3610a686", + wantErr: false, + }, + { + name: "blake2b", + method: "blake2b", + input: []byte("hello"), + want: "324dcf027dd4a30a932c441f365a25e86b173defa4b8e58948253471b81b72cf", + wantErr: false, + }, + { + name: "blake2s", + method: "blake2s", + input: []byte("hello"), + want: "19213bacc58dee6dbde3ceb9a47cbb330b3d86f8cca8997eb00be456f140ca25", + wantErr: false, + }, + { + name: "unsupported_method", + method: "unsupported", + input: []byte("hello"), + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := bytes.NewReader(tt.input) + + sum, err := Checksum(r, tt.method) + if tt.wantErr { + assert.Error(t, err) + + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, sum) + }) + } +} + +func BenchmarkChecksum(b *testing.B) { + input := []byte("hello") + methods := []string{"md5", "sha1", "sha256", "sha512", "adler32", "crc32", "blake2b", "blake2s"} + + for _, method := range methods { + b.Run(method, func(b *testing.B) { + r := bytes.NewReader(input) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, _ = Checksum(r, method) + _, _ = r.Seek(0, io.SeekStart) + } + }) + } +} + +func TestWriteChecksums(t *testing.T) { + tempDir := t.TempDir() + files := []string{ + filepath.Join(tempDir, "file1.txt"), + filepath.Join(tempDir, "file2.txt"), + } + + for _, file := range files { + err := os.WriteFile(file, []byte("hello"), 0o600) + if err != nil { + t.Fatalf("failed to create test file: %v", err) + } + } + + methods := []string{"md5", "sha256"} + + tests := []struct { + name string + tempDir string + files []string + methods []string + want []string + wantErr bool + }{ + { + name: "valid input", + tempDir: tempDir, + files: files, + methods: methods, + want: []string{ + files[0], + files[1], + filepath.Join(tempDir, "md5sum.txt"), + filepath.Join(tempDir, "sha256sum.txt"), + }, + wantErr: false, + }, + { + name: "empty files", + tempDir: tempDir, + files: []string{}, + methods: methods, + want: []string{}, + wantErr: false, + }, + { + name: "empty methods", + tempDir: tempDir, + files: files, + methods: []string{}, + want: []string{ + files[0], + files[1], + }, + wantErr: false, + }, + { + name: "non existent file", + tempDir: tempDir, + files: append(files, filepath.Join(tempDir, "non_existent.txt")), + methods: methods, + want: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := WriteChecksums(tt.files, tt.methods, tt.tempDir) + if tt.wantErr { + assert.Error(t, err) + + return + } + + assert.NoError(t, err) + sort.Strings(result) + sort.Strings(tt.want) + assert.Equal(t, tt.want, result) + }) + } +}