0
0
mirror of https://github.com/thegeeklab/wp-gitea-release.git synced 2024-06-02 18:29:43 +02:00

Compare commits

...

35 Commits
v0.1.2 ... main

Author SHA1 Message Date
renovate[bot]
8671b7ef22 chore(deps): update dependency golangci/golangci-lint to v1.59.0 2024-05-27 04:42:08 +00:00
renovate[bot]
38b9523c8f
chore(deps): update docker.io/library/alpine docker tag to v3.20 (#50)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 09:41:18 +02:00
renovate[bot]
328ba9c68c
fix(deps): update module github.com/rs/zerolog to v1.33.0 (#51)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 09:36:52 +02:00
3f5b43cfdf
chore: migrate to wp-plugin-go v3 (#49) 2024-05-17 21:50:11 +02:00
renovate[bot]
6485a9b36d
chore(docker): update docker.io/library/golang:1.22 docker digest to f43c6f0 (#47)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-16 13:40:28 +02:00
renovate[bot]
1181fcc6c6 chore(deps): update dependency golangci/golangci-lint to v1.58.1 2024-05-13 04:46:35 +00:00
070bf083f3
ci: fix golangci-lint deprecations 2024-05-12 11:08:32 +02:00
750b60a81f
test: use static errors 2024-05-12 11:01:08 +02:00
d88993d496
cleanup gitea options struct 2024-05-12 00:03:07 +02:00
14e918c6cb
test: use wrapper interface and generated mocks (#45) 2024-05-11 10:25:08 +02:00
a196e5f4ac
refactor: rework gitea client and add tests (#44) 2024-05-08 12:54:51 +02:00
renovate[bot]
8b057ea06b chore(docker): update docker.io/library/golang:1.22 docker digest to b1e05e2 2024-05-08 05:22:46 +00:00
00b46b4eff
docs: hide system flags and refactor generator (#42) 2024-05-07 12:09:08 +02:00
renovate[bot]
917195656c chore(deps): update dependency golangci/golangci-lint to v1.58.0 2024-05-06 04:29:16 +00:00
renovate[bot]
bd08bdd7fc
fix(deps): update module github.com/urfave/cli/v2 to v2.27.2 (#37)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-29 10:27:16 +02:00
renovate[bot]
68b907846e chore(docker): update docker.io/library/golang:1.22 docker digest to d5302d4 2024-04-25 03:33:33 +00:00
renovate[bot]
0dff9dd40e
fix(deps): update module github.com/thegeeklab/wp-plugin-go to v1.7.1 (#35)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-22 12:38:25 +02:00
renovate[bot]
0f8ed75513
fix(deps): update module code.gitea.io/sdk/gitea to v0.18.0 (#34)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-17 14:22:21 +02:00
renovate[bot]
37bd796447 chore(docker): update docker.io/library/golang:1.22 docker digest to 450e382 2024-04-11 05:01:07 +00:00
renovate[bot]
db82f87db3
fix(deps): update module golang.org/x/crypto to v0.22.0 (#32)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-05 08:56:30 +02:00
renovate[bot]
5f80426280 chore(docker): update docker.io/library/golang:1.22 docker digest to c4fb952 2024-04-04 03:21:42 +00:00
renovate[bot]
e0f12a17bb chore(deps): update dependency golangci/golangci-lint to v1.57.2 2024-04-01 03:33:07 +00:00
renovate[bot]
f24bfd501a
chore(deps): update quay.io/thegeeklab/wp-docker-buildx docker tag to v4 (#29)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-27 08:55:28 +01:00
renovate[bot]
2d5a62c9d4
fix(deps): update module github.com/thegeeklab/wp-plugin-go to v1.7.0 (#26)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-26 21:30:48 +01:00
renovate[bot]
0cdd879414 chore(deps): update dependency golangci/golangci-lint to v1.57.1 2024-03-25 03:39:36 +00:00
renovate[bot]
15c7f3d57f chore(docker): update docker.io/library/golang:1.22 docker digest to 0b55ab8 2024-03-13 06:25:19 +00:00
9eeb0d8ac1
cleanup docs and examples 2024-03-12 20:43:39 +01:00
renovate[bot]
6ddd3608aa
fix(deps): update module github.com/thegeeklab/wp-plugin-go to v1.6.1 (#25)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-07 21:26:20 +01:00
renovate[bot]
d16a25c1bf
fix(deps): update module golang.org/x/crypto to v0.21.0 (#23)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-06 14:42:55 +01:00
renovate[bot]
0d2579dad8 chore(docker): update docker.io/library/golang:1.22 docker digest to 34ce21a 2024-03-06 03:10:33 +00:00
renovate[bot]
6f67efabfb
fix(deps): update module golang.org/x/crypto to v0.20.0 (#22)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-02 14:49:16 +01:00
renovate[bot]
2988bd14d0 chore(deps): update dependency golangci/golangci-lint to v1.56.2 2024-02-19 03:11:11 +00:00
renovate[bot]
94ef9c793d chore(docker): update docker.io/library/golang:1.22 docker digest to 7b297d9 2024-02-16 04:17:06 +00:00
557e3e18f1
[skip ci] revert renovate automerge config 2024-02-15 12:19:33 +01:00
renovate[bot]
127b468730
chore(deps): update dependency golangci/golangci-lint to v1.56.1 (#19)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
2024-02-12 09:09:14 +01:00
26 changed files with 1376 additions and 465 deletions

View File

@ -23,7 +23,6 @@ linters:
- errchkjson
- errname
- errorlint
- execinquery
- exhaustive
- exportloopref
- forcetypeassert
@ -37,12 +36,12 @@ linters:
- gocyclo
- godot
- godox
- goerr113
- err113
- gofmt
- gofumpt
- goheader
- goimports
- gomnd
- mnd
- gomoddirectives
- gomodguard
- goprintffuncname

6
.mockery.yaml Normal file
View File

@ -0,0 +1,6 @@
---
all: True
dir: "{{.PackageName}}/mocks"
outpkg: "mocks"
packages:
github.com/thegeeklab/wp-gitea-release/gitea:

View File

@ -4,13 +4,10 @@ when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- event: [push]
branch:
- renovate/auto/*
steps:
- name: dryrun
image: quay.io/thegeeklab/wp-docker-buildx:3
image: quay.io/thegeeklab/wp-docker-buildx:4
settings:
containerfile: Containerfile.multiarch
dry_run: true
@ -23,7 +20,7 @@ steps:
- event: [pull_request]
- name: publish-dockerhub
image: quay.io/thegeeklab/wp-docker-buildx:3
image: quay.io/thegeeklab/wp-docker-buildx:4
group: container
settings:
auto_tag: true
@ -44,7 +41,7 @@ steps:
- ${CI_REPO_DEFAULT_BRANCH}
- name: publish-quay
image: quay.io/thegeeklab/wp-docker-buildx:3
image: quay.io/thegeeklab/wp-docker-buildx:4
group: container
settings:
auto_tag: true

View File

@ -4,9 +4,6 @@ when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- event: [push]
branch:
- renovate/auto/*
steps:
- name: build

View File

@ -4,9 +4,6 @@ when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- event: [push]
branch:
- renovate/auto/*
steps:
- name: markdownlint

View File

@ -4,9 +4,6 @@ when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- event: [push]
branch:
- renovate/auto/*
steps:
- name: lint

View File

@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.22@sha256:ef61a20960397f4d44b0e729298bf02327ca94f1519239ddc6d91689615b1367 as build
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.22@sha256:f43c6f049f04cbbaeb28f0aad3eea15274a7d0a7899a617d0037aec48d7ab010 as build
ARG TARGETOS
ARG TARGETARCH
@ -8,7 +8,7 @@ WORKDIR /src
RUN make build
FROM docker.io/library/alpine:3.19@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b
FROM docker.io/library/alpine:3.20@sha256:77726ef6b57ddf65bb551896826ec38bc3e53f75cdde31354fbffb4f25238ebd
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"

View File

@ -1,7 +1,7 @@
# renovate: datasource=github-releases depName=mvdan/gofumpt
GOFUMPT_PACKAGE_VERSION := v0.6.0
# renovate: datasource=github-releases depName=golangci/golangci-lint
GOLANGCI_LINT_PACKAGE_VERSION := v1.55.2
GOLANGCI_LINT_PACKAGE_VERSION := v1.59.0
EXECUTABLE := wp-gitea-release
@ -18,8 +18,8 @@ GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@$(GOFUMPT_PACKAGE_VERSION)
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_PACKAGE_VERSION)
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GOTESTSUM_PACKAGE ?= gotest.tools/gotestsum@latest
MOCKERY_PACKAGE ?= github.com/vektra/mockery/v2@latest
GENERATE ?=
XGO_VERSION := go-1.22.x
XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64
@ -65,11 +65,8 @@ lint: golangci-lint
.PHONY: generate
generate:
$(GO) generate $(GENERATE)
.PHONY: generate-docs
generate-docs:
$(GO) generate ./cmd/$(EXECUTABLE)/flags.go
$(GO) generate $(PACKAGES)
$(GO) run $(MOCKERY_PACKAGE)
.PHONY: test
test:

View File

@ -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
}

View File

@ -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,
},
}
}

View File

@ -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()
}

View File

@ -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 -}}

View File

@ -25,14 +25,11 @@ Only tag events are supported by this plugin. Running the plugin on other events
{{< /hint >}}
```YAML
kind: pipeline
name: default
steps:
- name: publish
image: quay.io/thegeeklab/wp-gitea-release
settings:
api_key: 3LbMg9Kncpdkhjp3bh3dMnKNXLjVMTsXk4sM
api_key: randomstring
base_url: https://gitea.rknet.org
files: build/*
```
@ -64,7 +61,7 @@ docker build --file Containerfile.multiarch --tag thegeeklab/wp-gitea-release .
```Shell
docker run --rm \
-e PLUGIN_BASE_URL=https://try.gitea.io \
-e PLUGIN_API_KEY=your-api-key \
-e PLUGIN_API_KEY=randomstring \
-e PLUGIN_FILES=build/* \
-e CI_REPO_OWNER=gitea \
-e CI_REPO_NAME=test \

View File

@ -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.

16
gitea/api.go Normal file
View File

@ -0,0 +1,16 @@
package gitea
import (
"io"
"code.gitea.io/sdk/gitea"
)
//nolint:lll
type APIClient interface {
ListReleases(owner, repo string, opt gitea.ListReleasesOptions) ([]*gitea.Release, *gitea.Response, error)
CreateRelease(owner, repo string, opt gitea.CreateReleaseOption) (*gitea.Release, *gitea.Response, error)
ListReleaseAttachments(user, repo string, release int64, opt gitea.ListReleaseAttachmentsOptions) ([]*gitea.Attachment, *gitea.Response, error)
CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*gitea.Attachment, *gitea.Response, error)
DeleteReleaseAttachment(user, repo string, release, id int64) (*gitea.Response, error)
}

175
gitea/gitea.go Normal file
View File

@ -0,0 +1,175 @@
package gitea
import (
"errors"
"fmt"
"net/http"
"os"
"path"
"code.gitea.io/sdk/gitea"
"github.com/rs/zerolog/log"
)
var (
ErrReleaseNotFound = errors.New("release not found")
ErrFileExists = errors.New("asset file already exist")
)
const (
FileExistsOverwrite FileExists = "overwrite"
FileExistsFail FileExists = "fail"
FileExistsSkip FileExists = "skip"
)
type Client struct {
client APIClient
Release *Release
}
type Release struct {
client APIClient
Opt ReleaseOptions
}
type ReleaseOptions struct {
Owner string
Repo string
Tag string
Draft bool
Prerelease bool
FileExists string
Title string
Note string
}
type FileExists string
// NewClient creates a new Client instance with the provided Gitea client.
func NewClient(url, key string, client *http.Client) (*Client, error) {
c, err := gitea.NewClient(url, gitea.SetToken(key), gitea.SetHTTPClient(client))
if err != nil {
return nil, err
}
return &Client{
client: c,
Release: &Release{
client: c,
Opt: ReleaseOptions{},
},
}, nil
}
// Find retrieves the release with the specified tag name from the repository.
// If the release is not found, it returns an ErrReleaseNotFound error.
func (r *Release) Find() (*gitea.Release, error) {
releases, _, err := r.client.ListReleases(r.Opt.Owner, r.Opt.Repo, gitea.ListReleasesOptions{})
if err != nil {
return nil, err
}
for _, release := range releases {
if release.TagName == r.Opt.Tag {
log.Info().Msgf("found release: %s", r.Opt.Tag)
return release, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrReleaseNotFound, r.Opt.Tag)
}
// Create creates a new release on the Gitea repository with the specified options.
// It returns the created release or an error if the creation failed.
func (r *Release) Create() (*gitea.Release, error) {
opts := gitea.CreateReleaseOption{
TagName: r.Opt.Tag,
IsDraft: r.Opt.Draft,
IsPrerelease: r.Opt.Prerelease,
Title: r.Opt.Title,
Note: r.Opt.Note,
}
release, _, err := r.client.CreateRelease(r.Opt.Owner, r.Opt.Repo, opts)
if err != nil {
return nil, fmt.Errorf("failed to create release: %w", err)
}
log.Info().Msgf("created release: %s", r.Opt.Tag)
return release, nil
}
// AddAttachments 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 (r *Release) AddAttachments(releaseID int64, files []string) error {
attachments, _, err := r.client.ListReleaseAttachments(
r.Opt.Owner,
r.Opt.Repo,
releaseID,
gitea.ListReleaseAttachmentsOptions{},
)
if err != nil {
return fmt.Errorf("failed to fetch attachments: %w", err)
}
existingAttachments := make(map[string]bool)
attachmentsMap := make(map[string]*gitea.Attachment)
for _, attachment := range attachments {
attachmentsMap[attachment.Name] = attachment
existingAttachments[attachment.Name] = true
}
for _, file := range files {
fileName := path.Base(file)
if existingAttachments[fileName] {
switch FileExists(r.Opt.FileExists) {
case FileExistsOverwrite:
_, err := r.client.DeleteReleaseAttachment(r.Opt.Owner, r.Opt.Repo, releaseID, attachmentsMap[fileName].ID)
if err != nil {
return fmt.Errorf("failed to delete artifact: %s: %w", fileName, err)
}
log.Info().Msgf("deleted artifact: %s", fileName)
case FileExistsFail:
return fmt.Errorf("%w: %s", ErrFileExists, fileName)
case FileExistsSkip:
log.Warn().Msgf("skip existing artifact: %s", fileName)
continue
}
}
if err := r.uploadFile(releaseID, file); err != nil {
return err
}
}
return nil
}
func (r *Release) uploadFile(releaseID int64, file string) error {
handle, err := os.Open(file)
if err != nil {
return fmt.Errorf("failed to read artifact: %s: %w", file, err)
}
defer handle.Close()
_, _, err = r.client.CreateReleaseAttachment(r.Opt.Owner, r.Opt.Repo, releaseID, handle, path.Base(file))
if err != nil {
return fmt.Errorf("failed to upload artifact: %s: %w", file, err)
}
log.Info().Msgf("uploaded artifact: %s", path.Base(file))
return nil
}

321
gitea/gitea_test.go Normal file
View File

@ -0,0 +1,321 @@
package gitea
import (
"bytes"
"errors"
"os"
"path/filepath"
"testing"
"code.gitea.io/sdk/gitea"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/thegeeklab/wp-gitea-release/gitea/mocks"
)
var ErrNoSuchFileOrDirectory = errors.New("no such file or directory")
func TestReleaseFind(t *testing.T) {
tests := []struct {
name string
opt ReleaseOptions
want *gitea.Release
wantErr error
}{
{
name: "find release by tag",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v1.0.0",
},
want: &gitea.Release{
TagName: "v1.0.0",
},
},
{
name: "release not found",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v1.1.0",
},
want: nil,
wantErr: ErrReleaseNotFound,
},
}
for _, tt := range tests {
mockClient := mocks.NewMockAPIClient(t)
r := &Release{
Opt: tt.opt,
client: mockClient,
}
mockClient.
On("ListReleases", mock.Anything, mock.Anything, mock.Anything).
Return([]*gitea.Release{
{
ID: 1,
TagName: "v1.0.0",
Title: "Release v1.0.0",
Note: "This is the release notes for v1.0.0",
IsDraft: false,
IsPrerelease: false,
},
}, nil, nil)
t.Run(tt.name, func(t *testing.T) {
release, err := r.Find()
if tt.wantErr != nil {
assert.Error(t, err)
assert.Nil(t, release)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want.TagName, release.TagName)
})
}
}
func TestReleaseCreate(t *testing.T) {
tests := []struct {
name string
opt ReleaseOptions
want *gitea.Release
wantErr error
}{
{
name: "create release",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v1.1.0",
Title: "Release v1.1.0",
Note: "This is the release notes for v1.1.0",
Draft: false,
Prerelease: false,
},
want: &gitea.Release{
TagName: "v1.1.0",
Title: "Release v1.1.0",
Note: "This is the release notes for v1.1.0",
IsDraft: false,
IsPrerelease: false,
},
},
{
name: "create draft release",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v1.2.0",
Title: "Release v1.2.0",
Note: "This is the release notes for v1.2.0",
Draft: true,
Prerelease: false,
},
want: &gitea.Release{
TagName: "v1.2.0",
Title: "Release v1.2.0",
Note: "This is the release notes for v1.2.0",
IsDraft: true,
IsPrerelease: false,
},
},
{
name: "create prerelease",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v1.3.0-rc1",
Title: "Release v1.3.0-rc1",
Note: "This is the release notes for v1.3.0-rc1",
Draft: false,
Prerelease: true,
},
want: &gitea.Release{
TagName: "v1.3.0-rc1",
Title: "Release v1.3.0-rc1",
Note: "This is the release notes for v1.3.0-rc1",
IsDraft: false,
IsPrerelease: true,
},
},
}
for _, tt := range tests {
mockClient := mocks.NewMockAPIClient(t)
r := &Release{
Opt: tt.opt,
client: mockClient,
}
mockClient.
On("CreateRelease", mock.Anything, mock.Anything, mock.Anything).
Return(&gitea.Release{
ID: 1,
TagName: tt.opt.Tag,
Title: tt.opt.Title,
Note: tt.opt.Note,
IsDraft: tt.opt.Draft,
IsPrerelease: tt.opt.Prerelease,
}, nil, nil)
t.Run(tt.name, func(t *testing.T) {
release, err := r.Create()
if tt.wantErr != nil {
assert.Error(t, err)
assert.Nil(t, release)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want.TagName, release.TagName)
assert.Equal(t, tt.want.Title, release.Title)
assert.Equal(t, tt.want.Note, release.Note)
assert.Equal(t, tt.want.IsDraft, release.IsDraft)
assert.Equal(t, tt.want.IsPrerelease, release.IsPrerelease)
})
}
}
func TestReleaseAddAttachments(t *testing.T) {
logBuffer := &bytes.Buffer{}
logger := zerolog.New(logBuffer)
log.Logger = logger
tests := []struct {
name string
opt ReleaseOptions
files []string
fileExists string
wantErr error
wantLogs []string
}{
{
name: "add new attachments",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v2.0.0",
Title: "Release v2.0.0",
FileExists: "overwrite",
},
files: []string{createTempFile(t, "file1.txt"), createTempFile(t, "file2.txt")},
wantLogs: []string{"uploaded artifact: file1.txt", "uploaded artifact: file2.txt"},
},
{
name: "fail on existing attachments",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v2.0.0",
Title: "Release v2.0.0",
FileExists: "fail",
},
files: []string{createTempFile(t, "file1.txt"), createTempFile(t, "file2.txt")},
wantErr: ErrFileExists,
},
{
name: "overwrite on existing attachments",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v2.0.0",
Title: "Release v2.0.0",
FileExists: "overwrite",
},
files: []string{createTempFile(t, "file1.txt"), createTempFile(t, "file2.txt")},
wantLogs: []string{"deleted artifact: file1.txt", "uploaded artifact: file1.txt"},
},
{
name: "skip on existing attachments",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v2.0.0",
Title: "Release v2.0.0",
FileExists: "skip",
},
files: []string{createTempFile(t, "file1.txt"), createTempFile(t, "file2.txt")},
wantLogs: []string{"skip existing artifact: file1"},
},
{
name: "fail on invalid file",
opt: ReleaseOptions{
Owner: "test-owner",
Repo: "test-repo",
Tag: "v2.0.0",
Title: "Release v2.0.0",
FileExists: "overwrite",
},
files: []string{"testdata/file1.txt", "testdata/invalid.txt"},
wantErr: ErrNoSuchFileOrDirectory,
},
}
for _, tt := range tests {
logBuffer.Reset()
mockClient := mocks.NewMockAPIClient(t)
r := &Release{
Opt: tt.opt,
client: mockClient,
}
mockClient.
On("ListReleaseAttachments", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return([]*gitea.Attachment{
{
Name: "file1.txt",
},
}, nil, nil)
if FileExists(tt.opt.FileExists) == FileExistsOverwrite {
mockClient.
On("DeleteReleaseAttachment", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(nil, nil)
}
if tt.wantErr == nil {
mockClient.
On("CreateReleaseAttachment", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(nil, nil, nil)
}
t.Run(tt.name, func(t *testing.T) {
err := r.AddAttachments(1, tt.files)
// Assert log output.
for _, l := range tt.wantLogs {
assert.Contains(t, logBuffer.String(), l)
}
if tt.wantErr != nil {
assert.Error(t, err)
assert.ErrorContains(t, err, tt.wantErr.Error())
return
}
assert.NoError(t, err)
})
}
}
func createTempFile(t *testing.T, name string) string {
t.Helper()
name = filepath.Join(t.TempDir(), name)
_ = os.WriteFile(name, []byte("hello"), 0o600)
return name
}

View File

@ -0,0 +1,378 @@
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
import (
io "io"
gitea "code.gitea.io/sdk/gitea"
mock "github.com/stretchr/testify/mock"
)
// MockAPIClient is an autogenerated mock type for the APIClient type
type MockAPIClient struct {
mock.Mock
}
type MockAPIClient_Expecter struct {
mock *mock.Mock
}
func (_m *MockAPIClient) EXPECT() *MockAPIClient_Expecter {
return &MockAPIClient_Expecter{mock: &_m.Mock}
}
// CreateRelease provides a mock function with given fields: owner, repo, opt
func (_m *MockAPIClient) CreateRelease(owner string, repo string, opt gitea.CreateReleaseOption) (*gitea.Release, *gitea.Response, error) {
ret := _m.Called(owner, repo, opt)
if len(ret) == 0 {
panic("no return value specified for CreateRelease")
}
var r0 *gitea.Release
var r1 *gitea.Response
var r2 error
if rf, ok := ret.Get(0).(func(string, string, gitea.CreateReleaseOption) (*gitea.Release, *gitea.Response, error)); ok {
return rf(owner, repo, opt)
}
if rf, ok := ret.Get(0).(func(string, string, gitea.CreateReleaseOption) *gitea.Release); ok {
r0 = rf(owner, repo, opt)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gitea.Release)
}
}
if rf, ok := ret.Get(1).(func(string, string, gitea.CreateReleaseOption) *gitea.Response); ok {
r1 = rf(owner, repo, opt)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*gitea.Response)
}
}
if rf, ok := ret.Get(2).(func(string, string, gitea.CreateReleaseOption) error); ok {
r2 = rf(owner, repo, opt)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// MockAPIClient_CreateRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRelease'
type MockAPIClient_CreateRelease_Call struct {
*mock.Call
}
// CreateRelease is a helper method to define mock.On call
// - owner string
// - repo string
// - opt gitea.CreateReleaseOption
func (_e *MockAPIClient_Expecter) CreateRelease(owner interface{}, repo interface{}, opt interface{}) *MockAPIClient_CreateRelease_Call {
return &MockAPIClient_CreateRelease_Call{Call: _e.mock.On("CreateRelease", owner, repo, opt)}
}
func (_c *MockAPIClient_CreateRelease_Call) Run(run func(owner string, repo string, opt gitea.CreateReleaseOption)) *MockAPIClient_CreateRelease_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].(gitea.CreateReleaseOption))
})
return _c
}
func (_c *MockAPIClient_CreateRelease_Call) Return(_a0 *gitea.Release, _a1 *gitea.Response, _a2 error) *MockAPIClient_CreateRelease_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *MockAPIClient_CreateRelease_Call) RunAndReturn(run func(string, string, gitea.CreateReleaseOption) (*gitea.Release, *gitea.Response, error)) *MockAPIClient_CreateRelease_Call {
_c.Call.Return(run)
return _c
}
// CreateReleaseAttachment provides a mock function with given fields: user, repo, release, file, filename
func (_m *MockAPIClient) CreateReleaseAttachment(user string, repo string, release int64, file io.Reader, filename string) (*gitea.Attachment, *gitea.Response, error) {
ret := _m.Called(user, repo, release, file, filename)
if len(ret) == 0 {
panic("no return value specified for CreateReleaseAttachment")
}
var r0 *gitea.Attachment
var r1 *gitea.Response
var r2 error
if rf, ok := ret.Get(0).(func(string, string, int64, io.Reader, string) (*gitea.Attachment, *gitea.Response, error)); ok {
return rf(user, repo, release, file, filename)
}
if rf, ok := ret.Get(0).(func(string, string, int64, io.Reader, string) *gitea.Attachment); ok {
r0 = rf(user, repo, release, file, filename)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gitea.Attachment)
}
}
if rf, ok := ret.Get(1).(func(string, string, int64, io.Reader, string) *gitea.Response); ok {
r1 = rf(user, repo, release, file, filename)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*gitea.Response)
}
}
if rf, ok := ret.Get(2).(func(string, string, int64, io.Reader, string) error); ok {
r2 = rf(user, repo, release, file, filename)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// MockAPIClient_CreateReleaseAttachment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateReleaseAttachment'
type MockAPIClient_CreateReleaseAttachment_Call struct {
*mock.Call
}
// CreateReleaseAttachment is a helper method to define mock.On call
// - user string
// - repo string
// - release int64
// - file io.Reader
// - filename string
func (_e *MockAPIClient_Expecter) CreateReleaseAttachment(user interface{}, repo interface{}, release interface{}, file interface{}, filename interface{}) *MockAPIClient_CreateReleaseAttachment_Call {
return &MockAPIClient_CreateReleaseAttachment_Call{Call: _e.mock.On("CreateReleaseAttachment", user, repo, release, file, filename)}
}
func (_c *MockAPIClient_CreateReleaseAttachment_Call) Run(run func(user string, repo string, release int64, file io.Reader, filename string)) *MockAPIClient_CreateReleaseAttachment_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].(int64), args[3].(io.Reader), args[4].(string))
})
return _c
}
func (_c *MockAPIClient_CreateReleaseAttachment_Call) Return(_a0 *gitea.Attachment, _a1 *gitea.Response, _a2 error) *MockAPIClient_CreateReleaseAttachment_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *MockAPIClient_CreateReleaseAttachment_Call) RunAndReturn(run func(string, string, int64, io.Reader, string) (*gitea.Attachment, *gitea.Response, error)) *MockAPIClient_CreateReleaseAttachment_Call {
_c.Call.Return(run)
return _c
}
// DeleteReleaseAttachment provides a mock function with given fields: user, repo, release, id
func (_m *MockAPIClient) DeleteReleaseAttachment(user string, repo string, release int64, id int64) (*gitea.Response, error) {
ret := _m.Called(user, repo, release, id)
if len(ret) == 0 {
panic("no return value specified for DeleteReleaseAttachment")
}
var r0 *gitea.Response
var r1 error
if rf, ok := ret.Get(0).(func(string, string, int64, int64) (*gitea.Response, error)); ok {
return rf(user, repo, release, id)
}
if rf, ok := ret.Get(0).(func(string, string, int64, int64) *gitea.Response); ok {
r0 = rf(user, repo, release, id)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gitea.Response)
}
}
if rf, ok := ret.Get(1).(func(string, string, int64, int64) error); ok {
r1 = rf(user, repo, release, id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockAPIClient_DeleteReleaseAttachment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteReleaseAttachment'
type MockAPIClient_DeleteReleaseAttachment_Call struct {
*mock.Call
}
// DeleteReleaseAttachment is a helper method to define mock.On call
// - user string
// - repo string
// - release int64
// - id int64
func (_e *MockAPIClient_Expecter) DeleteReleaseAttachment(user interface{}, repo interface{}, release interface{}, id interface{}) *MockAPIClient_DeleteReleaseAttachment_Call {
return &MockAPIClient_DeleteReleaseAttachment_Call{Call: _e.mock.On("DeleteReleaseAttachment", user, repo, release, id)}
}
func (_c *MockAPIClient_DeleteReleaseAttachment_Call) Run(run func(user string, repo string, release int64, id int64)) *MockAPIClient_DeleteReleaseAttachment_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].(int64), args[3].(int64))
})
return _c
}
func (_c *MockAPIClient_DeleteReleaseAttachment_Call) Return(_a0 *gitea.Response, _a1 error) *MockAPIClient_DeleteReleaseAttachment_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockAPIClient_DeleteReleaseAttachment_Call) RunAndReturn(run func(string, string, int64, int64) (*gitea.Response, error)) *MockAPIClient_DeleteReleaseAttachment_Call {
_c.Call.Return(run)
return _c
}
// ListReleaseAttachments provides a mock function with given fields: user, repo, release, opt
func (_m *MockAPIClient) ListReleaseAttachments(user string, repo string, release int64, opt gitea.ListReleaseAttachmentsOptions) ([]*gitea.Attachment, *gitea.Response, error) {
ret := _m.Called(user, repo, release, opt)
if len(ret) == 0 {
panic("no return value specified for ListReleaseAttachments")
}
var r0 []*gitea.Attachment
var r1 *gitea.Response
var r2 error
if rf, ok := ret.Get(0).(func(string, string, int64, gitea.ListReleaseAttachmentsOptions) ([]*gitea.Attachment, *gitea.Response, error)); ok {
return rf(user, repo, release, opt)
}
if rf, ok := ret.Get(0).(func(string, string, int64, gitea.ListReleaseAttachmentsOptions) []*gitea.Attachment); ok {
r0 = rf(user, repo, release, opt)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*gitea.Attachment)
}
}
if rf, ok := ret.Get(1).(func(string, string, int64, gitea.ListReleaseAttachmentsOptions) *gitea.Response); ok {
r1 = rf(user, repo, release, opt)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*gitea.Response)
}
}
if rf, ok := ret.Get(2).(func(string, string, int64, gitea.ListReleaseAttachmentsOptions) error); ok {
r2 = rf(user, repo, release, opt)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// MockAPIClient_ListReleaseAttachments_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReleaseAttachments'
type MockAPIClient_ListReleaseAttachments_Call struct {
*mock.Call
}
// ListReleaseAttachments is a helper method to define mock.On call
// - user string
// - repo string
// - release int64
// - opt gitea.ListReleaseAttachmentsOptions
func (_e *MockAPIClient_Expecter) ListReleaseAttachments(user interface{}, repo interface{}, release interface{}, opt interface{}) *MockAPIClient_ListReleaseAttachments_Call {
return &MockAPIClient_ListReleaseAttachments_Call{Call: _e.mock.On("ListReleaseAttachments", user, repo, release, opt)}
}
func (_c *MockAPIClient_ListReleaseAttachments_Call) Run(run func(user string, repo string, release int64, opt gitea.ListReleaseAttachmentsOptions)) *MockAPIClient_ListReleaseAttachments_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].(int64), args[3].(gitea.ListReleaseAttachmentsOptions))
})
return _c
}
func (_c *MockAPIClient_ListReleaseAttachments_Call) Return(_a0 []*gitea.Attachment, _a1 *gitea.Response, _a2 error) *MockAPIClient_ListReleaseAttachments_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *MockAPIClient_ListReleaseAttachments_Call) RunAndReturn(run func(string, string, int64, gitea.ListReleaseAttachmentsOptions) ([]*gitea.Attachment, *gitea.Response, error)) *MockAPIClient_ListReleaseAttachments_Call {
_c.Call.Return(run)
return _c
}
// ListReleases provides a mock function with given fields: owner, repo, opt
func (_m *MockAPIClient) ListReleases(owner string, repo string, opt gitea.ListReleasesOptions) ([]*gitea.Release, *gitea.Response, error) {
ret := _m.Called(owner, repo, opt)
if len(ret) == 0 {
panic("no return value specified for ListReleases")
}
var r0 []*gitea.Release
var r1 *gitea.Response
var r2 error
if rf, ok := ret.Get(0).(func(string, string, gitea.ListReleasesOptions) ([]*gitea.Release, *gitea.Response, error)); ok {
return rf(owner, repo, opt)
}
if rf, ok := ret.Get(0).(func(string, string, gitea.ListReleasesOptions) []*gitea.Release); ok {
r0 = rf(owner, repo, opt)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*gitea.Release)
}
}
if rf, ok := ret.Get(1).(func(string, string, gitea.ListReleasesOptions) *gitea.Response); ok {
r1 = rf(owner, repo, opt)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*gitea.Response)
}
}
if rf, ok := ret.Get(2).(func(string, string, gitea.ListReleasesOptions) error); ok {
r2 = rf(owner, repo, opt)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// MockAPIClient_ListReleases_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReleases'
type MockAPIClient_ListReleases_Call struct {
*mock.Call
}
// ListReleases is a helper method to define mock.On call
// - owner string
// - repo string
// - opt gitea.ListReleasesOptions
func (_e *MockAPIClient_Expecter) ListReleases(owner interface{}, repo interface{}, opt interface{}) *MockAPIClient_ListReleases_Call {
return &MockAPIClient_ListReleases_Call{Call: _e.mock.On("ListReleases", owner, repo, opt)}
}
func (_c *MockAPIClient_ListReleases_Call) Run(run func(owner string, repo string, opt gitea.ListReleasesOptions)) *MockAPIClient_ListReleases_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(string), args[2].(gitea.ListReleasesOptions))
})
return _c
}
func (_c *MockAPIClient_ListReleases_Call) Return(_a0 []*gitea.Release, _a1 *gitea.Response, _a2 error) *MockAPIClient_ListReleases_Call {
_c.Call.Return(_a0, _a1, _a2)
return _c
}
func (_c *MockAPIClient_ListReleases_Call) RunAndReturn(run func(string, string, gitea.ListReleasesOptions) ([]*gitea.Release, *gitea.Response, error)) *MockAPIClient_ListReleases_Call {
_c.Call.Return(run)
return _c
}
// NewMockAPIClient creates a new instance of MockAPIClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockAPIClient(t interface {
mock.TestingT
Cleanup(func())
}) *MockAPIClient {
mock := &MockAPIClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

23
go.mod
View File

@ -3,18 +3,20 @@ module github.com/thegeeklab/wp-gitea-release
go 1.22
require (
code.gitea.io/sdk/gitea v0.17.1
github.com/rs/zerolog v1.32.0
github.com/thegeeklab/wp-plugin-go v1.6.0
github.com/urfave/cli/v2 v2.27.1
golang.org/x/crypto v0.19.0
code.gitea.io/sdk/gitea v0.18.0
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0
github.com/thegeeklab/wp-plugin-go/v3 v3.0.2
github.com/urfave/cli/v2 v2.27.2
golang.org/x/crypto v0.23.0
)
require (
github.com/Masterminds/goutils v1.1.1 // indirect
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.2 // 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,13 @@ 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-20201216005158-039620a65673 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // 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
)

65
go.sum
View File

@ -1,5 +1,5 @@
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
code.gitea.io/sdk/gitea v0.18.0 h1:+zZrwVmujIrgobt6wVBWCqITz6bn1aBjnCUHmpZrerI=
code.gitea.io/sdk/gitea v0.18.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
@ -8,8 +8,8 @@ github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYr
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -18,8 +18,6 @@ github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9e
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
@ -43,8 +41,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
@ -52,39 +50,36 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/thegeeklab/wp-plugin-go v1.6.0 h1:DQ+oLs67FusgeZe3881x9pxH+UrddWygE0eGHLM7jgg=
github.com/thegeeklab/wp-plugin-go v1.6.0/go.mod h1:r+Oi0CpnnlG59u4Qp2n6xHGTxd6Krk+OPBZ9F7LQT8A=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
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/v3 v3.0.2 h1:Mv5i8S1WY+BUNjTjX6lOnB3p8S9mvM+XwfY4R98gx0g=
github.com/thegeeklab/wp-plugin-go/v3 v3.0.2/go.mod h1:ij1iJcAVgzerBTqXnmq0bu1VA+hhVVwzXKqiqfoGjjg=
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=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
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/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -93,37 +88,27 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
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=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
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=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

42
internal/doc/main.go Normal file
View File

@ -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"
plugin_docs "github.com/thegeeklab/wp-plugin-go/v3/docs"
plugin_template "github.com/thegeeklab/wp-plugin-go/v3/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 := plugin_template.Render(context.Background(), client, tmpl, plugin_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)
}
}

View File

@ -4,13 +4,12 @@ import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"path/filepath"
"strings"
"code.gitea.io/sdk/gitea"
"github.com/thegeeklab/wp-plugin-go/file"
"github.com/thegeeklab/wp-gitea-release/gitea"
plugin_file "github.com/thegeeklab/wp-plugin-go/v3/file"
)
var (
@ -54,13 +53,13 @@ func (p *Plugin) Validate() error {
}
if p.Settings.Note != "" {
if p.Settings.Note, _, err = file.ReadStringOrFile(p.Settings.Note); err != nil {
if p.Settings.Note, _, err = plugin_file.ReadStringOrFile(p.Settings.Note); err != nil {
return fmt.Errorf("error while reading %s: %w", p.Settings.Note, err)
}
}
if p.Settings.Title != "" {
if p.Settings.Title, _, err = file.ReadStringOrFile(p.Settings.Title); err != nil {
if p.Settings.Title, _, err = plugin_file.ReadStringOrFile(p.Settings.Title); err != nil {
return fmt.Errorf("error while reading %s: %w", p.Settings.Title, err)
}
}
@ -70,19 +69,12 @@ func (p *Plugin) Validate() error {
// Execute provides the implementation of the plugin.
func (p *Plugin) Execute() error {
httpClient := &http.Client{}
client, err := gitea.NewClient(
p.Settings.baseURL.String(),
gitea.SetToken(p.Settings.APIKey),
gitea.SetHTTPClient(httpClient),
)
client, err := gitea.NewClient(p.Settings.baseURL.String(), p.Settings.APIKey, p.Network.Client)
if err != nil {
return err
return fmt.Errorf("failed to create Gitea client: %w", err)
}
rc := releaseClient{
Client: client,
client.Release.Opt = gitea.ReleaseOptions{
Owner: p.Metadata.Repository.Owner,
Repo: p.Metadata.Repository.Name,
Tag: strings.TrimPrefix(p.Settings.CommitRef, "refs/tags/"),
@ -93,12 +85,20 @@ func (p *Plugin) Execute() error {
Note: p.Settings.Note,
}
release, err := rc.buildRelease()
if err != nil {
return fmt.Errorf("failed to create the release: %w", err)
release, err := client.Release.Find()
if err != nil && !errors.Is(err, gitea.ErrReleaseNotFound) {
return fmt.Errorf("failed to retrieve release: %w", err)
}
if err := rc.uploadFiles(release.ID, p.Settings.files); err != nil {
// If no release was found by that tag, create a new one.
if release == nil {
release, err = client.Release.Create()
if err != nil {
return fmt.Errorf("failed to create release: %w", err)
}
}
if err := client.Release.AddAttachments(release.ID, p.Settings.files); err != nil {
return fmt.Errorf("failed to upload the files: %w", err)
}
@ -136,8 +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)
}

View File

@ -1,15 +1,18 @@
package plugin
import (
"fmt"
"net/url"
wp "github.com/thegeeklab/wp-plugin-go/plugin"
plugin_base "github.com/thegeeklab/wp-plugin-go/v3/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
*plugin_base.Plugin
Settings *Settings
}
@ -29,13 +32,120 @@ type Settings struct {
files []string
}
func New(options wp.Options, settings *Settings) *Plugin {
p := &Plugin{}
func New(e plugin_base.ExecuteFunc, build ...string) *Plugin {
p := &Plugin{
Settings: &Settings{},
}
options.Execute = p.run
options := plugin_base.Options{
Name: "wp-gitea-release",
Description: "Publish files and artifacts to Gitea releases",
Flags: Flags(p.Settings, plugin_base.FlagsPluginCategory),
Execute: p.run,
HideWoodpeckerFlags: true,
}
p.Plugin = wp.New(options)
p.Settings = settings
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 = plugin_base.New(options)
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,
},
}
}

View File

@ -1,144 +0,0 @@
package plugin
import (
"errors"
"fmt"
"os"
"path"
"code.gitea.io/sdk/gitea"
"github.com/rs/zerolog/log"
)
var (
ErrReleaseNotFound = errors.New("release not found")
ErrFileExists = errors.New("asset file already exist")
)
// Release holds ties the Woodpecker env data and gitea client together.
type releaseClient struct {
*gitea.Client
Owner string
Repo string
Tag string
Draft bool
Prerelease bool
FileExists string
Title string
Note string
}
func (rc *releaseClient) buildRelease() (*gitea.Release, error) {
// first attempt to get a release by that tag
release, err := rc.getRelease()
if err != nil && release == nil {
fmt.Println(err)
} else if release != nil {
return release, nil
}
// if no release was found by that tag, create a new one
release, err = rc.newRelease()
if err != nil {
return nil, fmt.Errorf("failed to retrieve or create a release: %w", err)
}
return release, nil
}
func (rc *releaseClient) getRelease() (*gitea.Release, error) {
releases, _, err := rc.Client.ListReleases(rc.Owner, rc.Repo, gitea.ListReleasesOptions{})
if err != nil {
return nil, err
}
for _, release := range releases {
if release.TagName == rc.Tag {
log.Info().Msgf("successfully retrieved %s release", rc.Tag)
return release, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrReleaseNotFound, rc.Tag)
}
func (rc *releaseClient) newRelease() (*gitea.Release, error) {
r := gitea.CreateReleaseOption{
TagName: rc.Tag,
IsDraft: rc.Draft,
IsPrerelease: rc.Prerelease,
Title: rc.Title,
Note: rc.Note,
}
release, _, err := rc.Client.CreateRelease(rc.Owner, rc.Repo, r)
if err != nil {
return nil, fmt.Errorf("failed to create release: %w", err)
}
log.Info().Msgf("successfully created %s release", rc.Tag)
return release, nil
}
func (rc *releaseClient) uploadFiles(releaseID int64, files []string) error {
attachments, _, err := rc.Client.ListReleaseAttachments(
rc.Owner,
rc.Repo,
releaseID,
gitea.ListReleaseAttachmentsOptions{},
)
if err != nil {
return fmt.Errorf("failed to fetch existing assets: %w", err)
}
var uploadFiles []string
files:
for _, file := range files {
for _, attachment := range attachments {
if attachment.Name == path.Base(file) {
switch rc.FileExists {
case "overwrite":
// do nothing
case "fail":
return fmt.Errorf("%w: %s", ErrFileExists, path.Base(file))
case "skip":
log.Warn().Msgf("skipping pre-existing %s artifact", attachment.Name)
continue files
}
}
}
uploadFiles = append(uploadFiles, file)
}
for _, file := range uploadFiles {
handle, err := os.Open(file)
if err != nil {
return fmt.Errorf("failed to read %s artifact: %w", file, err)
}
for _, attachment := range attachments {
if attachment.Name == path.Base(file) {
if _, err := rc.Client.DeleteReleaseAttachment(rc.Owner, rc.Repo, releaseID, attachment.ID); err != nil {
return fmt.Errorf("failed to delete %s artifact: %w", file, err)
}
log.Info().Msgf("successfully deleted old %s artifact", attachment.Name)
}
}
if _, _, err = rc.Client.CreateReleaseAttachment(rc.Owner, rc.Repo, releaseID, handle, path.Base(file)); err != nil {
return fmt.Errorf("failed to upload %s artifact: %w", file, err)
}
log.Info().Msgf("successfully uploaded %s artifact", file)
}
return nil
}

View File

@ -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
}

204
plugin/util_test.go Normal file
View File

@ -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)
})
}
}