From a6168e8b8c7079149b5287e55915592446e812d0 Mon Sep 17 00:00:00 2001 From: Aleksey Poruchikov Date: Sat, 28 Mar 2026 19:25:36 +0300 Subject: [PATCH] Spizdil =) --- .gitea/workflows/test.yml | 43 ++ .gitignore | 1 + LICENSE | 19 + README.md | 41 ++ action.yml | 30 + go.mod | 13 + go.sum | 37 + main.go | 188 +++++ vendor/code.gitea.io/sdk/gitea/LICENSE | 20 + vendor/code.gitea.io/sdk/gitea/admin_cron.go | 47 ++ vendor/code.gitea.io/sdk/gitea/admin_org.go | 39 + vendor/code.gitea.io/sdk/gitea/admin_repo.go | 25 + vendor/code.gitea.io/sdk/gitea/admin_user.go | 130 ++++ vendor/code.gitea.io/sdk/gitea/attachment.go | 111 +++ vendor/code.gitea.io/sdk/gitea/client.go | 347 +++++++++ vendor/code.gitea.io/sdk/gitea/doc.go | 5 + vendor/code.gitea.io/sdk/gitea/fork.go | 49 ++ vendor/code.gitea.io/sdk/gitea/git_blob.go | 28 + vendor/code.gitea.io/sdk/gitea/git_hook.go | 71 ++ vendor/code.gitea.io/sdk/gitea/helper.go | 20 + vendor/code.gitea.io/sdk/gitea/hook.go | 194 +++++ vendor/code.gitea.io/sdk/gitea/issue.go | 288 ++++++++ .../code.gitea.io/sdk/gitea/issue_comment.go | 154 ++++ vendor/code.gitea.io/sdk/gitea/issue_label.go | 211 ++++++ .../sdk/gitea/issue_milestone.go | 237 ++++++ .../code.gitea.io/sdk/gitea/issue_reaction.go | 104 +++ .../sdk/gitea/issue_stopwatch.go | 57 ++ .../sdk/gitea/issue_subscription.go | 87 +++ .../sdk/gitea/issue_tracked_time.go | 142 ++++ .../code.gitea.io/sdk/gitea/list_options.go | 42 ++ .../code.gitea.io/sdk/gitea/notifications.go | 241 ++++++ vendor/code.gitea.io/sdk/gitea/oauth2.go | 91 +++ vendor/code.gitea.io/sdk/gitea/org.go | 155 ++++ vendor/code.gitea.io/sdk/gitea/org_member.go | 119 +++ vendor/code.gitea.io/sdk/gitea/org_team.go | 242 ++++++ vendor/code.gitea.io/sdk/gitea/pull.go | 328 +++++++++ vendor/code.gitea.io/sdk/gitea/pull_review.go | 325 ++++++++ vendor/code.gitea.io/sdk/gitea/release.go | 202 +++++ vendor/code.gitea.io/sdk/gitea/repo.go | 534 ++++++++++++++ vendor/code.gitea.io/sdk/gitea/repo_branch.go | 143 ++++ .../sdk/gitea/repo_branch_protection.go | 168 +++++ .../sdk/gitea/repo_collaborator.go | 136 ++++ vendor/code.gitea.io/sdk/gitea/repo_commit.go | 101 +++ vendor/code.gitea.io/sdk/gitea/repo_file.go | 247 +++++++ vendor/code.gitea.io/sdk/gitea/repo_key.go | 91 +++ .../code.gitea.io/sdk/gitea/repo_migrate.go | 132 ++++ vendor/code.gitea.io/sdk/gitea/repo_refs.go | 78 ++ vendor/code.gitea.io/sdk/gitea/repo_stars.go | 96 +++ vendor/code.gitea.io/sdk/gitea/repo_tag.go | 130 ++++ vendor/code.gitea.io/sdk/gitea/repo_team.go | 65 ++ .../code.gitea.io/sdk/gitea/repo_template.go | 65 ++ vendor/code.gitea.io/sdk/gitea/repo_topics.go | 68 ++ .../code.gitea.io/sdk/gitea/repo_transfer.go | 36 + vendor/code.gitea.io/sdk/gitea/repo_tree.go | 44 ++ vendor/code.gitea.io/sdk/gitea/repo_watch.go | 87 +++ vendor/code.gitea.io/sdk/gitea/settings.go | 78 ++ vendor/code.gitea.io/sdk/gitea/status.go | 108 +++ vendor/code.gitea.io/sdk/gitea/user.go | 90 +++ vendor/code.gitea.io/sdk/gitea/user_app.go | 90 +++ vendor/code.gitea.io/sdk/gitea/user_email.go | 64 ++ vendor/code.gitea.io/sdk/gitea/user_follow.go | 93 +++ vendor/code.gitea.io/sdk/gitea/user_gpgkey.go | 89 +++ vendor/code.gitea.io/sdk/gitea/user_key.go | 83 +++ vendor/code.gitea.io/sdk/gitea/user_search.go | 48 ++ .../code.gitea.io/sdk/gitea/user_settings.go | 62 ++ vendor/code.gitea.io/sdk/gitea/version.go | 101 +++ .../github.com/hashicorp/go-version/LICENSE | 354 +++++++++ .../github.com/hashicorp/go-version/README.md | 66 ++ .../hashicorp/go-version/constraint.go | 204 ++++++ .../hashicorp/go-version/version.go | 384 ++++++++++ .../go-version/version_collection.go | 17 + .../github.com/sethvargo/go-envconfig/AUTHORS | 8 + .../github.com/sethvargo/go-envconfig/LICENSE | 202 +++++ .../sethvargo/go-envconfig/Makefile | 32 + .../sethvargo/go-envconfig/README.md | 405 ++++++++++ .../sethvargo/go-envconfig/decoding.go | 57 ++ .../sethvargo/go-envconfig/envconfig.go | 691 ++++++++++++++++++ .../sethvargo/go-githubactions/.gitignore | 1 + .../sethvargo/go-githubactions/AUTHORS | 7 + .../sethvargo/go-githubactions/LICENSE | 202 +++++ .../sethvargo/go-githubactions/Makefile | 29 + .../sethvargo/go-githubactions/README.md | 150 ++++ .../sethvargo/go-githubactions/actions.go | 576 +++++++++++++++ .../go-githubactions/actions_root.go | 156 ++++ .../sethvargo/go-githubactions/command.go | 112 +++ .../sethvargo/go-githubactions/eof_crlf.go | 20 + .../sethvargo/go-githubactions/eof_lf.go | 20 + .../sethvargo/go-githubactions/options.go | 59 ++ vendor/modules.txt | 12 + 89 files changed, 11374 insertions(+) create mode 100644 .gitea/workflows/test.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 action.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 vendor/code.gitea.io/sdk/gitea/LICENSE create mode 100644 vendor/code.gitea.io/sdk/gitea/admin_cron.go create mode 100644 vendor/code.gitea.io/sdk/gitea/admin_org.go create mode 100644 vendor/code.gitea.io/sdk/gitea/admin_repo.go create mode 100644 vendor/code.gitea.io/sdk/gitea/admin_user.go create mode 100644 vendor/code.gitea.io/sdk/gitea/attachment.go create mode 100644 vendor/code.gitea.io/sdk/gitea/client.go create mode 100644 vendor/code.gitea.io/sdk/gitea/doc.go create mode 100644 vendor/code.gitea.io/sdk/gitea/fork.go create mode 100644 vendor/code.gitea.io/sdk/gitea/git_blob.go create mode 100644 vendor/code.gitea.io/sdk/gitea/git_hook.go create mode 100644 vendor/code.gitea.io/sdk/gitea/helper.go create mode 100644 vendor/code.gitea.io/sdk/gitea/hook.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_comment.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_label.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_milestone.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_reaction.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_subscription.go create mode 100644 vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go create mode 100644 vendor/code.gitea.io/sdk/gitea/list_options.go create mode 100644 vendor/code.gitea.io/sdk/gitea/notifications.go create mode 100644 vendor/code.gitea.io/sdk/gitea/oauth2.go create mode 100644 vendor/code.gitea.io/sdk/gitea/org.go create mode 100644 vendor/code.gitea.io/sdk/gitea/org_member.go create mode 100644 vendor/code.gitea.io/sdk/gitea/org_team.go create mode 100644 vendor/code.gitea.io/sdk/gitea/pull.go create mode 100644 vendor/code.gitea.io/sdk/gitea/pull_review.go create mode 100644 vendor/code.gitea.io/sdk/gitea/release.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_branch.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_collaborator.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_commit.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_file.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_key.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_migrate.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_refs.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_stars.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_tag.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_team.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_template.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_topics.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_transfer.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_tree.go create mode 100644 vendor/code.gitea.io/sdk/gitea/repo_watch.go create mode 100644 vendor/code.gitea.io/sdk/gitea/settings.go create mode 100644 vendor/code.gitea.io/sdk/gitea/status.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user_app.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user_email.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user_follow.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user_gpgkey.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user_key.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user_search.go create mode 100644 vendor/code.gitea.io/sdk/gitea/user_settings.go create mode 100644 vendor/code.gitea.io/sdk/gitea/version.go create mode 100644 vendor/github.com/hashicorp/go-version/LICENSE create mode 100644 vendor/github.com/hashicorp/go-version/README.md create mode 100644 vendor/github.com/hashicorp/go-version/constraint.go create mode 100644 vendor/github.com/hashicorp/go-version/version.go create mode 100644 vendor/github.com/hashicorp/go-version/version_collection.go create mode 100644 vendor/github.com/sethvargo/go-envconfig/AUTHORS create mode 100644 vendor/github.com/sethvargo/go-envconfig/LICENSE create mode 100644 vendor/github.com/sethvargo/go-envconfig/Makefile create mode 100644 vendor/github.com/sethvargo/go-envconfig/README.md create mode 100644 vendor/github.com/sethvargo/go-envconfig/decoding.go create mode 100644 vendor/github.com/sethvargo/go-envconfig/envconfig.go create mode 100644 vendor/github.com/sethvargo/go-githubactions/.gitignore create mode 100644 vendor/github.com/sethvargo/go-githubactions/AUTHORS create mode 100644 vendor/github.com/sethvargo/go-githubactions/LICENSE create mode 100644 vendor/github.com/sethvargo/go-githubactions/Makefile create mode 100644 vendor/github.com/sethvargo/go-githubactions/README.md create mode 100644 vendor/github.com/sethvargo/go-githubactions/actions.go create mode 100644 vendor/github.com/sethvargo/go-githubactions/actions_root.go create mode 100644 vendor/github.com/sethvargo/go-githubactions/command.go create mode 100644 vendor/github.com/sethvargo/go-githubactions/eof_crlf.go create mode 100644 vendor/github.com/sethvargo/go-githubactions/eof_lf.go create mode 100644 vendor/github.com/sethvargo/go-githubactions/options.go create mode 100644 vendor/modules.txt diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..2af1967 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,43 @@ +name: checks +on: + - push + - pull_request + +env: + GOPROXY: https://goproxy.io,direct + GOPATH: /go_path + GOCACHE: /go_cache + +jobs: + lint: + name: check and test + runs-on: ubuntu-latest + steps: + - name: cache go path + id: cache-go-path + uses: https://github.com/actions/cache@v3 + with: + path: /go_path + key: go_path-${{ github.repository }}-${{ github.ref_name }} + restore-keys: | + go_path-${{ github.repository }}- + go_path- + - name: cache go cache + id: cache-go-cache + uses: https://github.com/actions/cache@v3 + with: + path: /go_cache + key: go_cache-${{ github.repository }}-${{ github.ref_name }} + restore-keys: | + go_cache-${{ github.repository }}- + go_cache- + - uses: actions/setup-go@v3 + with: + go-version: 1.20 + - uses: actions/checkout@v3 + - name: lint + run: go install golang.org/x/lint/golint@latest && golint ./... + - name: vet + run: go vet + - name: test + run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1462aa6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +release-action \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63a2d6d --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 The Actions Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..60b48c6 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# Release Action + +**Please use https://gitea.com/actions/gitea-release-action instead** + +This action will help Gitea users to publish release and attachments. + +## Example + +```yaml +name: release + +on: + push: + tags: + - '*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: setup go + uses: https://github.com/actions/setup-go@v4 + with: + go-version: '>=1.20.1' + - name: go build + run: go build -o bin/myapp + - name: Use Go Action + id: use-go-action + uses: https://gitea.com/actions/release-action@main + with: + files: |- + bin/** + api_key: '${{secrets.RELEASE_TOKEN}}' +``` + +You can find real examples here: + +- https://gitea.com/xorm/reverse/src/branch/main/.gitea/workflows/release-tag.yml diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..336e73f --- /dev/null +++ b/action.yml @@ -0,0 +1,30 @@ +name: 'Gitea Release Action' +description: 'A Gitea Action to manage Gitea release' +inputs: + files: + description: 'The files needs to be uploaded' + required: true + title: + description: 'The release title' + required: false + body: + description: 'The release body' + required: false + pre_release: + description: "Whether it's a pre release" + required: false + draft: + description: "Whether it's a draft" + required: false + api_key: + description: 'The access token to interact with Gitea' + required: false + insecure: + description: 'Whether allow insecure Gitea instance' + required: false +outputs: + status: + description: 'The upload status' +runs: + using: 'go' + main: 'main.go' diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3c7835f --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module gitea.com/actions/release-action + +go 1.20 + +require ( + code.gitea.io/sdk/gitea v0.15.1 + github.com/sethvargo/go-githubactions v1.1.0 +) + +require ( + github.com/hashicorp/go-version v1.2.1 // indirect + github.com/sethvargo/go-envconfig v0.8.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2e1a267 --- /dev/null +++ b/go.sum @@ -0,0 +1,37 @@ +code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= +code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= +code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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/sethvargo/go-envconfig v0.8.0 h1:AcmdAewSFAc7pQ1Ghz+vhZkilUtxX559QlDuLLiSkdI= +github.com/sethvargo/go-envconfig v0.8.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= +github.com/sethvargo/go-githubactions v1.1.0 h1:mg03w+b+/s5SMS298/2G6tHv8P0w0VhUFaqL1THIqzY= +github.com/sethvargo/go-githubactions v1.1.0/go.mod h1:qIboSF7yq2Qnaw2WXDsqCReM0Lo1gU4QXUWmhBC3pxE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4ec0417 --- /dev/null +++ b/main.go @@ -0,0 +1,188 @@ +package main + +import ( + "crypto/tls" + "fmt" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + + "code.gitea.io/sdk/gitea" + gha "github.com/sethvargo/go-githubactions" +) + +func main() { + ctx, err := gha.Context() + if err != nil { + gha.Fatalf("failed to get context: %v", err) + } + + if !strings.HasPrefix(ctx.Ref, "refs/tags/") { + gha.Fatalf("ref %s is not a tag", ctx.Ref) + } + + files := gha.GetInput("files") + title := gha.GetInput("title") + body := gha.GetInput("body") + apiKey := gha.GetInput("api_key") + preRelease, _ := strconv.ParseBool(gha.GetInput("pre_release")) + draft, _ := strconv.ParseBool(gha.GetInput("draft")) + if title == "" { + title = ctx.RefName + } + if apiKey == "" { + apiKey = os.Getenv("GITHUB_TOKEN") + } + insecure, _ := strconv.ParseBool(gha.GetInput("insecure")) + + client := http.DefaultClient + if insecure { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client = &http.Client{Transport: tr} + } + + c, err := gitea.NewClient(ctx.ServerURL, gitea.SetToken(apiKey), gitea.SetHTTPClient(client)) + if err != nil { + gha.Fatalf("failed to create gitea client: %v", err) + } + + owner := ctx.RepositoryOwner + repo := strings.Split(ctx.Repository, "/")[1] + + rel, err := createOrGetRelease(ctx, c, owner, repo, gitea.CreateReleaseOption{ + TagName: ctx.RefName, + IsDraft: draft, + IsPrerelease: preRelease, + Title: title, + Target: ctx.SHA, + Note: body, + }) + if err != nil { + gha.Fatalf("failed to create release: %v", err) + } + + matchedFiles, err := getFiles(ctx.Workspace, files) + if err != nil { + gha.Fatalf("failed to get files: %v", err) + } + + if err := uploadFiles(ctx, c, owner, repo, rel.ID, matchedFiles); err != nil { + gha.Fatalf("Failed to upload files: %v", err) + } + + gha.SetOutput("status", "success") +} + +func getDirFiles(dir string) ([]string, error) { + d, err := os.Open(dir) + if err != nil { + return nil, err + } + defer d.Close() + info, err := d.Stat() + if err != nil { + return nil, err + } + if !info.IsDir() { + return []string{dir}, nil + } + list, err := d.Readdirnames(0) + if err != nil { + return nil, err + } + res := make([]string, 0, len(list)) + for _, f := range list { + subs, err := getDirFiles(filepath.Join(dir, f)) + if err != nil { + return nil, err + } + res = append(res, subs...) + } + return res, nil +} + +func getFiles(parentDir, files string) ([]string, error) { + var fileList []string + lines := strings.Split(files, "\n") + for _, line := range lines { + line = strings.Trim(line, "'") + line = strings.TrimSpace(strings.Trim(line, `"`)) + if line == "" { + continue + } + if filepath.IsAbs(line) { + return nil, fmt.Errorf("file path %s is absolute", line) + } + line = filepath.Join(parentDir, line) + matches, err := filepath.Glob(line) + if err != nil { + return nil, err + } + for _, match := range matches { + files, err := getDirFiles(match) + if err != nil { + return nil, err + } + fileList = append(fileList, files...) + } + } + return fileList, nil +} + +func createOrGetRelease(ctx *gha.GitHubContext, c *gitea.Client, owner, repo string, opts gitea.CreateReleaseOption) (*gitea.Release, error) { + // Get the release by tag + release, resp, err := c.GetReleaseByTag(owner, repo, opts.TagName) + if err == nil { + return release, nil + } + errMessage := fmt.Errorf("failed to get release for tag: %s with error: %w", opts.TagName, err) + if resp.StatusCode != 404 { + return nil, errMessage + } + fmt.Printf("%s trying to create it\n", errMessage) + // Create the release + release, _, err = c.CreateRelease(owner, repo, opts) + if err != nil { + return nil, fmt.Errorf("failed to create release: %w and %s", err, errMessage) + } + return release, nil +} + +func uploadFiles(ctx *gha.GitHubContext, c *gitea.Client, owner, repo string, releaseID int64, files []string) error { + attachments, _, err := c.ListReleaseAttachments(owner, repo, releaseID, gitea.ListReleaseAttachmentsOptions{}) + if err != nil { + return fmt.Errorf("failed to fetch existing release attachments: %w", err) + } + + for _, file := range files { + f, err := os.Open(file) + if err != nil { + return fmt.Errorf("failed to open release attachment %s: %w", file, err) + } + + for _, attachment := range attachments { + if attachment.Name == filepath.Base(file) { + if _, err := c.DeleteReleaseAttachment(owner, repo, releaseID, attachment.ID); err != nil { + f.Close() + return fmt.Errorf("failed to delete release attachment %s: %w", file, err) + } + + fmt.Printf("Successfully deleted old release attachment %s\n", attachment.Name) + } + } + + if _, _, err = c.CreateReleaseAttachment(owner, repo, releaseID, f, filepath.Base(file)); err != nil { + f.Close() + return fmt.Errorf("failed to upload release attachment %s: %w", file, err) + } + f.Close() + + fmt.Printf("Successfully uploaded release attachment %s\n", file) + } + + return nil +} diff --git a/vendor/code.gitea.io/sdk/gitea/LICENSE b/vendor/code.gitea.io/sdk/gitea/LICENSE new file mode 100644 index 0000000..10aeba4 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 The Gitea Authors +Copyright (c) 2014 The Gogs Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/code.gitea.io/sdk/gitea/admin_cron.go b/vendor/code.gitea.io/sdk/gitea/admin_cron.go new file mode 100644 index 0000000..84316da --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/admin_cron.go @@ -0,0 +1,47 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "time" +) + +// CronTask represents a Cron task +type CronTask struct { + Name string `json:"name"` + Schedule string `json:"schedule"` + Next time.Time `json:"next"` + Prev time.Time `json:"prev"` + ExecTimes int64 `json:"exec_times"` +} + +// ListCronTaskOptions list options for ListCronTasks +type ListCronTaskOptions struct { + ListOptions +} + +// ListCronTasks list available cron tasks +func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + ct := make([]*CronTask, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/cron?%s", opt.getURLQuery().Encode()), jsonHeader, nil, &ct) + return ct, resp, err +} + +// RunCronTasks run a cron task +func (c *Client) RunCronTasks(task string) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&task); err != nil { + return nil, err + } + _, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/admin_org.go b/vendor/code.gitea.io/sdk/gitea/admin_org.go new file mode 100644 index 0000000..26bf81f --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/admin_org.go @@ -0,0 +1,39 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AdminListOrgsOptions options for listing admin's organizations +type AdminListOrgsOptions struct { + ListOptions +} + +// AdminListOrgs lists all orgs +func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Response, error) { + opt.setDefaults() + orgs := make([]*Organization, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) + return orgs, resp, err +} + +// AdminCreateOrg create an organization +func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + org := new(Organization) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/orgs", user), jsonHeader, bytes.NewReader(body), org) + return org, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/admin_repo.go b/vendor/code.gitea.io/sdk/gitea/admin_repo.go new file mode 100644 index 0000000..8666690 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/admin_repo.go @@ -0,0 +1,25 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AdminCreateRepo create a repo +func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/repos", user), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/admin_user.go b/vendor/code.gitea.io/sdk/gitea/admin_user.go new file mode 100644 index 0000000..172f064 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/admin_user.go @@ -0,0 +1,130 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AdminListUsersOptions options for listing admin users +type AdminListUsersOptions struct { + ListOptions +} + +// AdminListUsers lists all users +func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response, error) { + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/users?%s", opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// CreateUserOption create user options +type CreateUserOption struct { + SourceID int64 `json:"source_id"` + LoginName string `json:"login_name"` + Username string `json:"username"` + FullName string `json:"full_name"` + Email string `json:"email"` + Password string `json:"password"` + MustChangePassword *bool `json:"must_change_password"` + SendNotify bool `json:"send_notify"` + Visibility *VisibleType `json:"visibility"` +} + +// Validate the CreateUserOption struct +func (opt CreateUserOption) Validate() error { + if len(opt.Email) == 0 { + return fmt.Errorf("email is empty") + } + if len(opt.Username) == 0 { + return fmt.Errorf("username is empty") + } + return nil +} + +// AdminCreateUser create a user +func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error) { + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + user := new(User) + resp, err := c.getParsedResponse("POST", "/admin/users", jsonHeader, bytes.NewReader(body), user) + return user, resp, err +} + +// EditUserOption edit user options +type EditUserOption struct { + SourceID int64 `json:"source_id"` + LoginName string `json:"login_name"` + Email *string `json:"email"` + FullName *string `json:"full_name"` + Password string `json:"password"` + Description *string `json:"description"` + MustChangePassword *bool `json:"must_change_password"` + Website *string `json:"website"` + Location *string `json:"location"` + Active *bool `json:"active"` + Admin *bool `json:"admin"` + AllowGitHook *bool `json:"allow_git_hook"` + AllowImportLocal *bool `json:"allow_import_local"` + MaxRepoCreation *int `json:"max_repo_creation"` + ProhibitLogin *bool `json:"prohibit_login"` + AllowCreateOrganization *bool `json:"allow_create_organization"` + Restricted *bool `json:"restricted"` + Visibility *VisibleType `json:"visibility"` +} + +// AdminEditUser modify user informations +func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PATCH", fmt.Sprintf("/admin/users/%s", user), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// AdminDeleteUser delete one user according name +func (c *Client) AdminDeleteUser(user string) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil) + return resp, err +} + +// AdminCreateUserPublicKey adds a public key for the user +func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(PublicKey) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/keys", user), jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// AdminDeleteUserPublicKey deletes a user's public key +func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/attachment.go b/vendor/code.gitea.io/sdk/gitea/attachment.go new file mode 100644 index 0000000..24c57ba --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/attachment.go @@ -0,0 +1,111 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea // import "code.gitea.io/sdk/gitea" +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "time" +) + +// Attachment a generic attachment +type Attachment struct { + ID int64 `json:"id"` + Name string `json:"name"` + Size int64 `json:"size"` + DownloadCount int64 `json:"download_count"` + Created time.Time `json:"created_at"` + UUID string `json:"uuid"` + DownloadURL string `json:"browser_download_url"` +} + +// ListReleaseAttachmentsOptions options for listing release's attachments +type ListReleaseAttachmentsOptions struct { + ListOptions +} + +// ListReleaseAttachments list release's attachments +func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + attachments := make([]*Attachment, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/%d/assets?%s", user, repo, release, opt.getURLQuery().Encode()), + nil, nil, &attachments) + return attachments, resp, err +} + +// GetReleaseAttachment returns the requested attachment +func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + a := new(Attachment) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), + nil, nil, &a) + return a, resp, err +} + +// CreateReleaseAttachment creates an attachment for the given release +func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + // Write file to body + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", filename) + if err != nil { + return nil, nil, err + } + + if _, err = io.Copy(part, file); err != nil { + return nil, nil, err + } + if err = writer.Close(); err != nil { + return nil, nil, err + } + + // Send request + attachment := new(Attachment) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/releases/%d/assets", user, repo, release), + http.Header{"Content-Type": {writer.FormDataContentType()}}, body, &attachment) + return attachment, resp, err +} + +// EditAttachmentOptions options for editing attachments +type EditAttachmentOptions struct { + Name string `json:"name"` +} + +// EditReleaseAttachment updates the given attachment with the given options +func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&form) + if err != nil { + return nil, nil, err + } + attach := new(Attachment) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, attachment), jsonHeader, bytes.NewReader(body), attach) + return attach, resp, err +} + +// DeleteReleaseAttachment deletes the given attachment including the uploaded file +func (c *Client) DeleteReleaseAttachment(user, repo string, release int64, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/client.go b/vendor/code.gitea.io/sdk/gitea/client.go new file mode 100644 index 0000000..3fe3e8b --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/client.go @@ -0,0 +1,347 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + "sync" + + "github.com/hashicorp/go-version" +) + +var jsonHeader = http.Header{"content-type": []string{"application/json"}} + +// Version return the library version +func Version() string { + return "0.15.1" +} + +// Client represents a thread-safe Gitea API client. +type Client struct { + url string + accessToken string + username string + password string + otp string + sudo string + debug bool + client *http.Client + ctx context.Context + mutex sync.RWMutex + + serverVersion *version.Version + getVersionOnce sync.Once + ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock +} + +// Response represents the gitea response +type Response struct { + *http.Response +} + +// ClientOption are functions used to init a new client +type ClientOption func(*Client) error + +// NewClient initializes and returns a API client. +// Usage of all gitea.Client methods is concurrency-safe. +func NewClient(url string, options ...ClientOption) (*Client, error) { + client := &Client{ + url: strings.TrimSuffix(url, "/"), + client: &http.Client{}, + ctx: context.Background(), + } + for _, opt := range options { + if err := opt(client); err != nil { + return nil, err + } + } + if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil { + return nil, err + } + return client, nil +} + +// NewClientWithHTTP creates an API client with a custom http client +// Deprecated use SetHTTPClient option +func NewClientWithHTTP(url string, httpClient *http.Client) *Client { + client, _ := NewClient(url, SetHTTPClient(httpClient)) + return client +} + +// SetHTTPClient is an option for NewClient to set custom http client +func SetHTTPClient(httpClient *http.Client) ClientOption { + return func(client *Client) error { + client.SetHTTPClient(httpClient) + return nil + } +} + +// SetHTTPClient replaces default http.Client with user given one. +func (c *Client) SetHTTPClient(client *http.Client) { + c.mutex.Lock() + c.client = client + c.mutex.Unlock() +} + +// SetToken is an option for NewClient to set token +func SetToken(token string) ClientOption { + return func(client *Client) error { + client.mutex.Lock() + client.accessToken = token + client.mutex.Unlock() + return nil + } +} + +// SetBasicAuth is an option for NewClient to set username and password +func SetBasicAuth(username, password string) ClientOption { + return func(client *Client) error { + client.SetBasicAuth(username, password) + return nil + } +} + +// SetBasicAuth sets username and password +func (c *Client) SetBasicAuth(username, password string) { + c.mutex.Lock() + c.username, c.password = username, password + c.mutex.Unlock() +} + +// SetOTP is an option for NewClient to set OTP for 2FA +func SetOTP(otp string) ClientOption { + return func(client *Client) error { + client.SetOTP(otp) + return nil + } +} + +// SetOTP sets OTP for 2FA +func (c *Client) SetOTP(otp string) { + c.mutex.Lock() + c.otp = otp + c.mutex.Unlock() +} + +// SetContext is an option for NewClient to set the default context +func SetContext(ctx context.Context) ClientOption { + return func(client *Client) error { + client.SetContext(ctx) + return nil + } +} + +// SetContext set default context witch is used for http requests +func (c *Client) SetContext(ctx context.Context) { + c.mutex.Lock() + c.ctx = ctx + c.mutex.Unlock() +} + +// SetSudo is an option for NewClient to set sudo header +func SetSudo(sudo string) ClientOption { + return func(client *Client) error { + client.SetSudo(sudo) + return nil + } +} + +// SetSudo sets username to impersonate. +func (c *Client) SetSudo(sudo string) { + c.mutex.Lock() + c.sudo = sudo + c.mutex.Unlock() +} + +// SetDebugMode is an option for NewClient to enable debug mode +func SetDebugMode() ClientOption { + return func(client *Client) error { + client.mutex.Lock() + client.debug = true + client.mutex.Unlock() + return nil + } +} + +func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) { + c.mutex.RLock() + debug := c.debug + if debug { + fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body) + } + req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body) + + client := c.client // client ref can change from this point on so safe it + c.mutex.RUnlock() + + if err != nil { + return nil, nil, err + } + + resp, err := client.Do(req) + if err != nil { + return nil, nil, err + } + + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if debug { + fmt.Printf("Response: %v\n\n", resp) + } + return data, &Response{resp}, nil +} + +func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) { + c.mutex.RLock() + debug := c.debug + if debug { + fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, body) + } + req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body) + if err != nil { + c.mutex.RUnlock() + return nil, err + } + if len(c.accessToken) != 0 { + req.Header.Set("Authorization", "token "+c.accessToken) + } + if len(c.otp) != 0 { + req.Header.Set("X-GITEA-OTP", c.otp) + } + if len(c.username) != 0 { + req.SetBasicAuth(c.username, c.password) + } + if len(c.sudo) != 0 { + req.Header.Set("Sudo", c.sudo) + } + + client := c.client // client ref can change from this point on so safe it + c.mutex.RUnlock() + + for k, v := range header { + req.Header[k] = v + } + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if debug { + fmt.Printf("Response: %v\n\n", resp) + } + return &Response{resp}, nil +} + +// Converts a response for a HTTP status code indicating an error condition +// (non-2XX) to a well-known error value and response body. For non-problematic +// (2XX) status codes nil will be returned. Note that on a non-2XX response, the +// response body stream will have been read and, hence, is closed on return. +func statusCodeToErr(resp *Response) (body []byte, err error) { + // no error + if resp.StatusCode/100 == 2 { + return nil, nil + } + + // + // error: body will be read for details + // + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err) + } + + switch resp.StatusCode { + case 403: + return data, errors.New("403 Forbidden") + case 404: + return data, errors.New("404 Not Found") + case 409: + return data, errors.New("409 Conflict") + case 422: + return data, fmt.Errorf("422 Unprocessable Entity: %s", string(data)) + } + + path := resp.Request.URL.Path + method := resp.Request.Method + header := resp.Request.Header + errMap := make(map[string]interface{}) + if err = json.Unmarshal(data, &errMap); err != nil { + // when the JSON can't be parsed, data was probably empty or a + // plain string, so we try to return a helpful error anyway + return data, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data)) + } + return data, errors.New(errMap["message"].(string)) +} + +func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + // check for errors + data, err := statusCodeToErr(resp) + if err != nil { + return data, resp, err + } + + // success (2XX), read body + data, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, resp, err + } + + return data, resp, nil +} + +func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) { + data, resp, err := c.getResponse(method, path, header, body) + if err != nil { + return resp, err + } + return resp, json.Unmarshal(data, obj) +} + +func (c *Client) getStatusCode(method, path string, header http.Header, body io.Reader) (int, *Response, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return -1, resp, err + } + defer resp.Body.Close() + + return resp.StatusCode, resp, nil +} + +// pathEscapeSegments escapes segments of a path while not escaping forward slash +func pathEscapeSegments(path string) string { + slice := strings.Split(path, "/") + for index := range slice { + slice[index] = url.PathEscape(slice[index]) + } + escapedPath := strings.Join(slice, "/") + return escapedPath +} + +// escapeValidatePathSegments is a help function to validate and encode url path segments +func escapeValidatePathSegments(seg ...*string) error { + for i := range seg { + if seg[i] == nil || len(*seg[i]) == 0 { + return fmt.Errorf("path segment [%d] is empty", i) + } + *seg[i] = url.PathEscape(*seg[i]) + } + return nil +} diff --git a/vendor/code.gitea.io/sdk/gitea/doc.go b/vendor/code.gitea.io/sdk/gitea/doc.go new file mode 100644 index 0000000..6bd327d --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/doc.go @@ -0,0 +1,5 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea // import "code.gitea.io/sdk/gitea" diff --git a/vendor/code.gitea.io/sdk/gitea/fork.go b/vendor/code.gitea.io/sdk/gitea/fork.go new file mode 100644 index 0000000..c8e5323 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/fork.go @@ -0,0 +1,49 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListForksOptions options for listing repository's forks +type ListForksOptions struct { + ListOptions +} + +// ListForks list a repository's forks +func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + forks := make([]*Repository, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/forks?%s", user, repo, opt.getURLQuery().Encode()), + nil, nil, &forks) + return forks, resp, err +} + +// CreateForkOption options for creating a fork +type CreateForkOption struct { + // organization name, if forking into an organization + Organization *string `json:"organization"` +} + +// CreateFork create a fork of a repository +func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(form) + if err != nil { + return nil, nil, err + } + fork := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/forks", user, repo), jsonHeader, bytes.NewReader(body), &fork) + return fork, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/git_blob.go b/vendor/code.gitea.io/sdk/gitea/git_blob.go new file mode 100644 index 0000000..7668672 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/git_blob.go @@ -0,0 +1,28 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" +) + +// GitBlobResponse represents a git blob +type GitBlobResponse struct { + Content string `json:"content"` + Encoding string `json:"encoding"` + URL string `json:"url"` + SHA string `json:"sha"` + Size int64 `json:"size"` +} + +// GetBlob get the blob of a repository file +func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil { + return nil, nil, err + } + blob := new(GitBlobResponse) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob) + return blob, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/git_hook.go b/vendor/code.gitea.io/sdk/gitea/git_hook.go new file mode 100644 index 0000000..d8fbf71 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/git_hook.go @@ -0,0 +1,71 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// GitHook represents a Git repository hook +type GitHook struct { + Name string `json:"name"` + IsActive bool `json:"is_active"` + Content string `json:"content,omitempty"` +} + +// ListRepoGitHooksOptions options for listing repository's githooks +type ListRepoGitHooksOptions struct { + ListOptions +} + +// ListRepoGitHooks list all the Git hooks of one repository +func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + hooks := make([]*GitHook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks) + return hooks, resp, err +} + +// GetRepoGitHook get a Git hook of a repository +func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &id); err != nil { + return nil, nil, err + } + h := new(GitHook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h) + return h, resp, err +} + +// EditGitHookOption options when modifying one Git hook +type EditGitHookOption struct { + Content string `json:"content"` +} + +// EditRepoGitHook modify one Git hook of a repository +func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &id); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// DeleteRepoGitHook delete one Git hook from a repository +func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &id); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/helper.go b/vendor/code.gitea.io/sdk/gitea/helper.go new file mode 100644 index 0000000..ff8038b --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/helper.go @@ -0,0 +1,20 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +// OptionalBool convert a bool to a bool reference +func OptionalBool(v bool) *bool { + return &v +} + +// OptionalString convert a string to a string reference +func OptionalString(v string) *string { + return &v +} + +// OptionalInt64 convert a int64 to a int64 reference +func OptionalInt64(v int64) *int64 { + return &v +} diff --git a/vendor/code.gitea.io/sdk/gitea/hook.go b/vendor/code.gitea.io/sdk/gitea/hook.go new file mode 100644 index 0000000..67a7518 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/hook.go @@ -0,0 +1,194 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Hook a hook is a web hook when one repository changed +type Hook struct { + ID int64 `json:"id"` + Type string `json:"type"` + URL string `json:"-"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + Active bool `json:"active"` + Updated time.Time `json:"updated_at"` + Created time.Time `json:"created_at"` +} + +// HookType represent all webhook types gitea currently offer +type HookType string + +const ( + // HookTypeDingtalk webhook that dingtalk understand + HookTypeDingtalk HookType = "dingtalk" + // HookTypeDiscord webhook that discord understand + HookTypeDiscord HookType = "discord" + // HookTypeGitea webhook that gitea understand + HookTypeGitea HookType = "gitea" + // HookTypeGogs webhook that gogs understand + HookTypeGogs HookType = "gogs" + // HookTypeMsteams webhook that msteams understand + HookTypeMsteams HookType = "msteams" + // HookTypeSlack webhook that slack understand + HookTypeSlack HookType = "slack" + // HookTypeTelegram webhook that telegram understand + HookTypeTelegram HookType = "telegram" + // HookTypeFeishu webhook that feishu understand + HookTypeFeishu HookType = "feishu" +) + +// ListHooksOptions options for listing hooks +type ListHooksOptions struct { + ListOptions +} + +// ListOrgHooks list all the hooks of one organization +func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + hooks := make([]*Hook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks) + return hooks, resp, err +} + +// ListRepoHooks list all the hooks of one repository +func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + hooks := make([]*Hook, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks) + return hooks, resp, err +} + +// GetOrgHook get a hook of an organization +func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h) + return h, resp, err +} + +// GetRepoHook get a hook of a repository +func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h) + return h, resp, err +} + +// CreateHookOption options when create a hook +type CreateHookOption struct { + Type HookType `json:"type"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter"` + Active bool `json:"active"` +} + +// Validate the CreateHookOption struct +func (opt CreateHookOption) Validate() error { + if len(opt.Type) == 0 { + return fmt.Errorf("hook type needed") + } + return nil +} + +// CreateOrgHook create one hook for an organization, with options +func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/hooks", org), jsonHeader, bytes.NewReader(body), h) + return h, resp, err +} + +// CreateRepoHook create one hook for a repository, with options +func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + h := new(Hook) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), jsonHeader, bytes.NewReader(body), h) + return h, resp, err +} + +// EditHookOption options when modify one hook +type EditHookOption struct { + Config map[string]string `json:"config"` + Events []string `json:"events"` + BranchFilter string `json:"branch_filter"` + Active *bool `json:"active"` +} + +// EditOrgHook modify one hook of an organization, with hook id and options +func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// EditRepoHook modify one hook of a repository, with hook id and options +func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// DeleteOrgHook delete one hook from an organization, with hook id +func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil) + return resp, err +} + +// DeleteRepoHook delete one hook from a repository, with hook id +func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue.go b/vendor/code.gitea.io/sdk/gitea/issue.go new file mode 100644 index 0000000..c33856a --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue.go @@ -0,0 +1,288 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// PullRequestMeta PR info if an issue is a PR +type PullRequestMeta struct { + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` +} + +// RepositoryMeta basic repository information +type RepositoryMeta struct { + ID int64 `json:"id"` + Name string `json:"name"` + Owner string `json:"owner"` + FullName string `json:"full_name"` +} + +// Issue represents an issue in a repository +type Issue struct { + ID int64 `json:"id"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int64 `json:"original_author_id"` + Title string `json:"title"` + Body string `json:"body"` + Ref string `json:"ref"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignees []*User `json:"assignees"` + // Whether the issue is open or closed + State StateType `json:"state"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + Deadline *time.Time `json:"due_date"` + PullRequest *PullRequestMeta `json:"pull_request"` + Repository *RepositoryMeta `json:"repository"` +} + +// ListIssueOption list issue options +type ListIssueOption struct { + ListOptions + State StateType + Type IssueType + Labels []string + Milestones []string + KeyWord string + Since time.Time + Before time.Time + // filter by created by username + CreatedBy string + // filter by assigned to username + AssignedBy string + // filter by username mentioned + MentionedBy string +} + +// StateType issue state type +type StateType string + +const ( + // StateOpen pr/issue is opend + StateOpen StateType = "open" + // StateClosed pr/issue is closed + StateClosed StateType = "closed" + // StateAll is all + StateAll StateType = "all" +) + +// IssueType is issue a pull or only an issue +type IssueType string + +const ( + // IssueTypeAll pr and issue + IssueTypeAll IssueType = "" + // IssueTypeIssue only issues + IssueTypeIssue IssueType = "issues" + // IssueTypePull only pulls + IssueTypePull IssueType = "pulls" +) + +// QueryEncode turns options into querystring argument +func (opt *ListIssueOption) QueryEncode() string { + query := opt.getURLQuery() + + if len(opt.State) > 0 { + query.Add("state", string(opt.State)) + } + + if len(opt.Labels) > 0 { + query.Add("labels", strings.Join(opt.Labels, ",")) + } + + if len(opt.KeyWord) > 0 { + query.Add("q", opt.KeyWord) + } + + query.Add("type", string(opt.Type)) + + if len(opt.Milestones) > 0 { + query.Add("milestones", strings.Join(opt.Milestones, ",")) + } + + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + + if len(opt.CreatedBy) > 0 { + query.Add("created_by", opt.CreatedBy) + } + if len(opt.AssignedBy) > 0 { + query.Add("assigned_by", opt.AssignedBy) + } + if len(opt.MentionedBy) > 0 { + query.Add("mentioned_by", opt.MentionedBy) + } + + return query.Encode() +} + +// ListIssues returns all issues assigned the authenticated user +func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) { + opt.setDefaults() + issues := make([]*Issue, 0, opt.PageSize) + + link, _ := url.Parse("/repos/issues/search") + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) + if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil { + for i := 0; i < len(issues); i++ { + if issues[i].Repository != nil { + issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] + } + } + } + for i := range issues { + c.issueBackwardsCompatibility(issues[i]) + } + return issues, resp, err +} + +// ListRepoIssues returns all issues for a given repository +func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + issues := make([]*Issue, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo)) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues) + if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil { + for i := 0; i < len(issues); i++ { + if issues[i].Repository != nil { + issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] + } + } + } + for i := range issues { + c.issueBackwardsCompatibility(issues[i]) + } + return issues, resp, err +} + +// GetIssue returns a single issue for a given repository +func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue) + if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil && issue.Repository != nil { + issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0] + } + c.issueBackwardsCompatibility(issue) + return issue, resp, err +} + +// CreateIssueOption options to create one issue +type CreateIssueOption struct { + Title string `json:"title"` + Body string `json:"body"` + Ref string `json:"ref"` + Assignees []string `json:"assignees"` + Deadline *time.Time `json:"due_date"` + // milestone id + Milestone int64 `json:"milestone"` + // list of label ids + Labels []int64 `json:"labels"` + Closed bool `json:"closed"` +} + +// Validate the CreateIssueOption struct +func (opt CreateIssueOption) Validate() error { + if len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// CreateIssue create a new issue for a given repository +func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo), + jsonHeader, bytes.NewReader(body), issue) + c.issueBackwardsCompatibility(issue) + return issue, resp, err +} + +// EditIssueOption options for editing an issue +type EditIssueOption struct { + Title string `json:"title"` + Body *string `json:"body"` + Ref *string `json:"ref"` + Assignees []string `json:"assignees"` + Milestone *int64 `json:"milestone"` + State *StateType `json:"state"` + Deadline *time.Time `json:"due_date"` + RemoveDeadline *bool `json:"unset_due_date"` +} + +// Validate the EditIssueOption struct +func (opt EditIssueOption) Validate() error { + if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// EditIssue modify an existing issue for a given repository +func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + issue := new(Issue) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), + jsonHeader, bytes.NewReader(body), issue) + c.issueBackwardsCompatibility(issue) + return issue, resp, err +} + +func (c *Client) issueBackwardsCompatibility(issue *Issue) { + if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil { + c.mutex.RLock() + issue.HTMLURL = fmt.Sprintf("%s/%s/issues/%d", c.url, issue.Repository.FullName, issue.Index) + c.mutex.RUnlock() + } +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_comment.go b/vendor/code.gitea.io/sdk/gitea/issue_comment.go new file mode 100644 index 0000000..8131a6e --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_comment.go @@ -0,0 +1,154 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// Comment represents a comment on a commit or issue +type Comment struct { + ID int64 `json:"id"` + HTMLURL string `json:"html_url"` + PRURL string `json:"pull_request_url"` + IssueURL string `json:"issue_url"` + Poster *User `json:"user"` + OriginalAuthor string `json:"original_author"` + OriginalAuthorID int64 `json:"original_author_id"` + Body string `json:"body"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// ListIssueCommentOptions list comment options +type ListIssueCommentOptions struct { + ListOptions + Since time.Time + Before time.Time +} + +// QueryEncode turns options into querystring argument +func (opt *ListIssueCommentOptions) QueryEncode() string { + query := opt.getURLQuery() + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + return query.Encode() +} + +// ListIssueComments list comments on an issue. +func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index)) + link.RawQuery = opt.QueryEncode() + comments := make([]*Comment, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) + return comments, resp, err +} + +// ListRepoIssueComments list comments for a given repo. +func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo)) + link.RawQuery = opt.QueryEncode() + comments := make([]*Comment, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) + return comments, resp, err +} + +// GetIssueComment get a comment for a given repo by id. +func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + comment := new(Comment) + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return comment, nil, err + } + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment) + return comment, resp, err +} + +// CreateIssueCommentOption options for creating a comment on an issue +type CreateIssueCommentOption struct { + Body string `json:"body"` +} + +// Validate the CreateIssueCommentOption struct +func (opt CreateIssueCommentOption) Validate() error { + if len(opt.Body) == 0 { + return fmt.Errorf("body is empty") + } + return nil +} + +// CreateIssueComment create comment on an issue. +func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + comment := new(Comment) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment) + return comment, resp, err +} + +// EditIssueCommentOption options for editing a comment +type EditIssueCommentOption struct { + Body string `json:"body"` +} + +// Validate the EditIssueCommentOption struct +func (opt EditIssueCommentOption) Validate() error { + if len(opt.Body) == 0 { + return fmt.Errorf("body is empty") + } + return nil +} + +// EditIssueComment edits an issue comment. +func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + comment := new(Comment) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), jsonHeader, bytes.NewReader(body), comment) + return comment, resp, err +} + +// DeleteIssueComment deletes an issue comment. +func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_label.go b/vendor/code.gitea.io/sdk/gitea/issue_label.go new file mode 100644 index 0000000..f343ee5 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_label.go @@ -0,0 +1,211 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "regexp" + "strings" +) + +// Label a label to an issue or a pr +type Label struct { + ID int64 `json:"id"` + Name string `json:"name"` + // example: 00aabb + Color string `json:"color"` + Description string `json:"description"` + URL string `json:"url"` +} + +// ListLabelsOptions options for listing repository's labels +type ListLabelsOptions struct { + ListOptions +} + +// ListRepoLabels list labels of one repository +func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + labels := make([]*Label, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels) + return labels, resp, err +} + +// GetRepoLabel get one label of repository by repo it +func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label) + return label, resp, err +} + +// CreateLabelOption options for creating a label +type CreateLabelOption struct { + Name string `json:"name"` + // example: #00aabb + Color string `json:"color"` + Description string `json:"description"` +} + +// Validate the CreateLabelOption struct +func (opt CreateLabelOption) Validate() error { + aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", opt.Color) + if err != nil { + return err + } + if !aw { + return fmt.Errorf("invalid color format") + } + if len(strings.TrimSpace(opt.Name)) == 0 { + return fmt.Errorf("empty name not allowed") + } + return nil +} + +// CreateLabel create one label of repository +func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + if len(opt.Color) == 6 { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + opt.Color = "#" + opt.Color + } + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/labels", owner, repo), + jsonHeader, bytes.NewReader(body), label) + return label, resp, err +} + +// EditLabelOption options for editing a label +type EditLabelOption struct { + Name *string `json:"name"` + Color *string `json:"color"` + Description *string `json:"description"` +} + +// Validate the EditLabelOption struct +func (opt EditLabelOption) Validate() error { + if opt.Color != nil { + aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", *opt.Color) + if err != nil { + return err + } + if !aw { + return fmt.Errorf("invalid color format") + } + } + if opt.Name != nil { + if len(strings.TrimSpace(*opt.Name)) == 0 { + return fmt.Errorf("empty name not allowed") + } + } + return nil +} + +// EditLabel modify one label with options +func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + label := new(Label) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), label) + return label, resp, err +} + +// DeleteLabel delete one label of repository by id +func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil) + return resp, err +} + +// GetIssueLabels get labels of one issue via issue id +func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + labels := make([]*Label, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels) + return labels, resp, err +} + +// IssueLabelsOption a collection of labels +type IssueLabelsOption struct { + // list of label IDs + Labels []int64 `json:"labels"` +} + +// AddIssueLabels add one or more labels to one issue +func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + var labels []*Label + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) + return labels, resp, err +} + +// ReplaceIssueLabels replace old labels of issue with new labels +func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + var labels []*Label + resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) + return labels, resp, err +} + +// DeleteIssueLabel delete one label of one issue by issue id and label id +// TODO: maybe we need delete by label name and issue id +func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil) + return resp, err +} + +// ClearIssueLabels delete all the labels of one issue. +func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_milestone.go b/vendor/code.gitea.io/sdk/gitea/issue_milestone.go new file mode 100644 index 0000000..dfb5720 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_milestone.go @@ -0,0 +1,237 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// Milestone milestone is a collection of issues on one repository +type Milestone struct { + ID int64 `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + State StateType `json:"state"` + OpenIssues int `json:"open_issues"` + ClosedIssues int `json:"closed_issues"` + Created time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` + Deadline *time.Time `json:"due_on"` +} + +// ListMilestoneOption list milestone options +type ListMilestoneOption struct { + ListOptions + // open, closed, all + State StateType + Name string +} + +// QueryEncode turns options into querystring argument +func (opt *ListMilestoneOption) QueryEncode() string { + query := opt.getURLQuery() + if opt.State != "" { + query.Add("state", string(opt.State)) + } + if len(opt.Name) != 0 { + query.Add("name", opt.Name) + } + return query.Encode() +} + +// ListRepoMilestones list all the milestones of one repository +func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + milestones := make([]*Milestone, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/milestones", owner, repo)) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &milestones) + return milestones, resp, err +} + +// GetMilestone get one milestone by repo name and milestone id +func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone) + return milestone, resp, err +} + +// GetMilestoneByName get one milestone by repo and milestone name +func (c *Client) GetMilestoneByName(owner, repo string, name string) (*Milestone, *Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + // backwards compatibility mode + m, resp, err := c.resolveMilestoneByName(owner, repo, name) + return m, resp, err + } + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone) + return milestone, resp, err +} + +// CreateMilestoneOption options for creating a milestone +type CreateMilestoneOption struct { + Title string `json:"title"` + Description string `json:"description"` + State StateType `json:"state"` + Deadline *time.Time `json:"due_on"` +} + +// Validate the CreateMilestoneOption struct +func (opt CreateMilestoneOption) Validate() error { + if len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// CreateMilestone create one milestone with options +func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), jsonHeader, bytes.NewReader(body), milestone) + + // make creating closed milestones need gitea >= v1.13.0 + // this make it backwards compatible + if err == nil && opt.State == StateClosed && milestone.State != StateClosed { + closed := StateClosed + return c.EditMilestone(owner, repo, milestone.ID, EditMilestoneOption{ + State: &closed, + }) + } + + return milestone, resp, err +} + +// EditMilestoneOption options for editing a milestone +type EditMilestoneOption struct { + Title string `json:"title"` + Description *string `json:"description"` + State *StateType `json:"state"` + Deadline *time.Time `json:"due_on"` +} + +// Validate the EditMilestoneOption struct +func (opt EditMilestoneOption) Validate() error { + if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// EditMilestone modify milestone with options +func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), milestone) + return milestone, resp, err +} + +// EditMilestoneByName modify milestone with options +func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMilestoneOption) (*Milestone, *Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + // backwards compatibility mode + m, _, err := c.resolveMilestoneByName(owner, repo, name) + if err != nil { + return nil, nil, err + } + return c.EditMilestone(owner, repo, m.ID, opt) + } + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + milestone := new(Milestone) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), milestone) + return milestone, resp, err +} + +// DeleteMilestone delete one milestone by id +func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil) + return resp, err +} + +// DeleteMilestoneByName delete one milestone by name +func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + // backwards compatibility mode + m, _, err := c.resolveMilestoneByName(owner, repo, name) + if err != nil { + return nil, err + } + return c.DeleteMilestone(owner, repo, m.ID) + } + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil) + return resp, err +} + +// resolveMilestoneByName is a fallback method to find milestone id by name +func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) { + for i := 1; ; i++ { + miles, resp, err := c.ListRepoMilestones(owner, repo, ListMilestoneOption{ + ListOptions: ListOptions{ + Page: i, + }, + State: "all", + }) + if err != nil { + return nil, nil, err + } + if len(miles) == 0 { + return nil, nil, fmt.Errorf("milestone '%s' do not exist", name) + } + for _, m := range miles { + if strings.ToLower(strings.TrimSpace(m.Title)) == strings.ToLower(strings.TrimSpace(name)) { + return m, resp, nil + } + } + } +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_reaction.go b/vendor/code.gitea.io/sdk/gitea/issue_reaction.go new file mode 100644 index 0000000..b45c066 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_reaction.go @@ -0,0 +1,104 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Reaction contain one reaction +type Reaction struct { + User *User `json:"user"` + Reaction string `json:"content"` + Created time.Time `json:"created_at"` +} + +// GetIssueReactions get a list reactions of an issue +func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + reactions := make([]*Reaction, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), nil, nil, &reactions) + return reactions, resp, err +} + +// GetIssueCommentReactions get a list of reactions from a comment of an issue +func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + reactions := make([]*Reaction, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), nil, nil, &reactions) + return reactions, resp, err +} + +// editReactionOption contain the reaction type +type editReactionOption struct { + Reaction string `json:"content"` +} + +// PostIssueReaction add a reaction to an issue +func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + reactionResponse := new(Reaction) + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), + jsonHeader, bytes.NewReader(body), reactionResponse) + return reactionResponse, resp, err +} + +// DeleteIssueReaction remove a reaction from an issue +func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// PostIssueCommentReaction add a reaction to a comment of an issue +func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + reactionResponse := new(Reaction) + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), + jsonHeader, bytes.NewReader(body), reactionResponse) + return reactionResponse, resp, err +} + +// DeleteIssueCommentReaction remove a reaction from a comment of an issue +func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + body, err := json.Marshal(&editReactionOption{Reaction: reaction}) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), + jsonHeader, bytes.NewReader(body)) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go b/vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go new file mode 100644 index 0000000..ebb0b8a --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_stopwatch.go @@ -0,0 +1,57 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "time" +) + +// StopWatch represents a running stopwatch of an issue / pr +type StopWatch struct { + Created time.Time `json:"created"` + Seconds int64 `json:"seconds"` + Duration string `json:"duration"` + IssueIndex int64 `json:"issue_index"` + IssueTitle string `json:"issue_title"` + RepoOwnerName string `json:"repo_owner_name"` + RepoName string `json:"repo_name"` +} + +// GetMyStopwatches list all stopwatches +func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) { + stopwatches := make([]*StopWatch, 0, 1) + resp, err := c.getParsedResponse("GET", "/user/stopwatches", nil, nil, &stopwatches) + return stopwatches, resp, err +} + +// DeleteIssueStopwatch delete / cancel a specific stopwatch +func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil) + return resp, err +} + +// StartIssueStopWatch starts a stopwatch for an existing issue for a given +// repository +func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil) + return resp, err +} + +// StopIssueStopWatch stops an existing stopwatch for an issue in a given +// repository +func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_subscription.go b/vendor/code.gitea.io/sdk/gitea/issue_subscription.go new file mode 100644 index 0000000..86853c7 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_subscription.go @@ -0,0 +1,87 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" +) + +// GetIssueSubscribers get list of users who subscribed on an issue +func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + subscribers := make([]*User, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions", owner, repo, index), nil, nil, &subscribers) + return subscribers, resp, err +} + +// AddIssueSubscription Subscribe user to issue +func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusCreated { + return resp, nil + } + if status == http.StatusOK { + return resp, fmt.Errorf("already subscribed") + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} + +// DeleteIssueSubscription unsubscribe user from issue +func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusCreated { + return resp, nil + } + if status == http.StatusOK { + return resp, fmt.Errorf("already unsubscribed") + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} + +// CheckIssueSubscription check if current user is subscribed to an issue +func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + wi := new(WatchInfo) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/check", owner, repo, index), nil, nil, wi) + return wi, resp, err +} + +// IssueSubscribe subscribe current user to an issue +func (c *Client) IssueSubscribe(owner, repo string, index int64) (*Response, error) { + u, _, err := c.GetMyUserInfo() + if err != nil { + return nil, err + } + return c.AddIssueSubscription(owner, repo, index, u.UserName) +} + +// IssueUnSubscribe unsubscribe current user from an issue +func (c *Client) IssueUnSubscribe(owner, repo string, index int64) (*Response, error) { + u, _, err := c.GetMyUserInfo() + if err != nil { + return nil, err + } + return c.DeleteIssueSubscription(owner, repo, index, u.UserName) +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go new file mode 100644 index 0000000..c558516 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_tracked_time.go @@ -0,0 +1,142 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// TrackedTime worked time for an issue / pr +type TrackedTime struct { + ID int64 `json:"id"` + Created time.Time `json:"created"` + // Time in seconds + Time int64 `json:"time"` + // deprecated (only for backwards compatibility) + UserID int64 `json:"user_id"` + UserName string `json:"user_name"` + // deprecated (only for backwards compatibility) + IssueID int64 `json:"issue_id"` + Issue *Issue `json:"issue"` +} + +// ListTrackedTimesOptions options for listing repository's tracked times +type ListTrackedTimesOptions struct { + ListOptions + Since time.Time + Before time.Time + // User filter is only used by ListRepoTrackedTimes !!! + User string +} + +// QueryEncode turns options into querystring argument +func (opt *ListTrackedTimesOptions) QueryEncode() string { + query := opt.getURLQuery() + + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + + if len(opt.User) != 0 { + query.Add("user", opt.User) + } + + return query.Encode() +} + +// ListRepoTrackedTimes list tracked times of a repository +func (c *Client) ListRepoTrackedTimes(owner, repo string, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/times", owner, repo)) + opt.setDefaults() + link.RawQuery = opt.QueryEncode() + times := make([]*TrackedTime, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×) + return times, resp, err +} + +// GetMyTrackedTimes list tracked times of the current user +func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) { + times := make([]*TrackedTime, 0, 10) + resp, err := c.getParsedResponse("GET", "/user/times", jsonHeader, nil, ×) + return times, resp, err +} + +// AddTimeOption options for adding time to an issue +type AddTimeOption struct { + // time in seconds + Time int64 `json:"time"` + // optional + Created time.Time `json:"created"` + // optional + User string `json:"user_name"` +} + +// Validate the AddTimeOption struct +func (opt AddTimeOption) Validate() error { + if opt.Time == 0 { + return fmt.Errorf("no time to add") + } + return nil +} + +// AddTime adds time to issue with the given index +func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + t := new(TrackedTime) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), + jsonHeader, bytes.NewReader(body), t) + return t, resp, err +} + +// ListIssueTrackedTimes list tracked times of a single issue for a given repository +func (c *Client) ListIssueTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index)) + opt.setDefaults() + link.RawQuery = opt.QueryEncode() + times := make([]*TrackedTime, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×) + return times, resp, err +} + +// ResetIssueTime reset tracked time of a single issue for a given repository +func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), jsonHeader, nil) + return resp, err +} + +// DeleteTime delete a specific tracked time by id of a single issue for a given repository +func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), jsonHeader, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/list_options.go b/vendor/code.gitea.io/sdk/gitea/list_options.go new file mode 100644 index 0000000..6f3ffb2 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/list_options.go @@ -0,0 +1,42 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" +) + +const defaultPageSize = 10 +const maxPageSize = 50 + +// ListOptions options for using Gitea's API pagination +type ListOptions struct { + Page int + PageSize int +} + +func (o ListOptions) getURLQuery() url.Values { + query := make(url.Values) + query.Add("page", fmt.Sprintf("%d", o.Page)) + query.Add("limit", fmt.Sprintf("%d", o.PageSize)) + + return query +} + +// setDefaults set default pagination options if none or wrong are set +// if you set -1 as page it will set all to 0 +func (o *ListOptions) setDefaults() { + if o.Page < 0 { + o.Page, o.PageSize = 0, 0 + return + } else if o.Page == 0 { + o.Page = 1 + } + + if o.PageSize < 0 || o.PageSize > maxPageSize { + o.PageSize = defaultPageSize + } +} diff --git a/vendor/code.gitea.io/sdk/gitea/notifications.go b/vendor/code.gitea.io/sdk/gitea/notifications.go new file mode 100644 index 0000000..8b1ffa7 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/notifications.go @@ -0,0 +1,241 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" + + "github.com/hashicorp/go-version" +) + +var ( + version1_12_3, _ = version.NewVersion("1.12.3") +) + +// NotificationThread expose Notification on API +type NotificationThread struct { + ID int64 `json:"id"` + Repository *Repository `json:"repository"` + Subject *NotificationSubject `json:"subject"` + Unread bool `json:"unread"` + Pinned bool `json:"pinned"` + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` +} + +// NotificationSubject contains the notification subject (Issue/Pull/Commit) +type NotificationSubject struct { + Title string `json:"title"` + URL string `json:"url"` + LatestCommentURL string `json:"latest_comment_url"` + Type NotifySubjectType `json:"type"` + State NotifySubjectState `json:"state"` +} + +// NotifyStatus notification status type +type NotifyStatus string + +const ( + // NotifyStatusUnread was not read + NotifyStatusUnread NotifyStatus = "unread" + // NotifyStatusRead was already read by user + NotifyStatusRead NotifyStatus = "read" + // NotifyStatusPinned notification is pinned by user + NotifyStatusPinned NotifyStatus = "pinned" +) + +// NotifySubjectType represent type of notification subject +type NotifySubjectType string + +const ( + // NotifySubjectIssue an issue is subject of an notification + NotifySubjectIssue NotifySubjectType = "Issue" + // NotifySubjectPull an pull is subject of an notification + NotifySubjectPull NotifySubjectType = "Pull" + // NotifySubjectCommit an commit is subject of an notification + NotifySubjectCommit NotifySubjectType = "Commit" + // NotifySubjectRepository an repository is subject of an notification + NotifySubjectRepository NotifySubjectType = "Repository" +) + +// NotifySubjectState reflect state of notification subject +type NotifySubjectState string + +const ( + // NotifySubjectOpen if subject is a pull/issue and is open at the moment + NotifySubjectOpen NotifySubjectState = "open" + // NotifySubjectClosed if subject is a pull/issue and is closed at the moment + NotifySubjectClosed NotifySubjectState = "closed" + // NotifySubjectMerged if subject is a pull and got merged + NotifySubjectMerged NotifySubjectState = "merged" +) + +// ListNotificationOptions represents the filter options +type ListNotificationOptions struct { + ListOptions + Since time.Time + Before time.Time + Status []NotifyStatus + SubjectTypes []NotifySubjectType +} + +// MarkNotificationOptions represents the filter & modify options +type MarkNotificationOptions struct { + LastReadAt time.Time + Status []NotifyStatus + ToStatus NotifyStatus +} + +// QueryEncode encode options to url query +func (opt *ListNotificationOptions) QueryEncode() string { + query := opt.getURLQuery() + if !opt.Since.IsZero() { + query.Add("since", opt.Since.Format(time.RFC3339)) + } + if !opt.Before.IsZero() { + query.Add("before", opt.Before.Format(time.RFC3339)) + } + for _, s := range opt.Status { + query.Add("status-types", string(s)) + } + for _, s := range opt.SubjectTypes { + query.Add("subject-type", string(s)) + } + return query.Encode() +} + +// Validate the CreateUserOption struct +func (opt ListNotificationOptions) Validate(c *Client) error { + if len(opt.Status) != 0 { + return c.checkServerVersionGreaterThanOrEqual(version1_12_3) + } + return nil +} + +// QueryEncode encode options to url query +func (opt *MarkNotificationOptions) QueryEncode() string { + query := make(url.Values) + if !opt.LastReadAt.IsZero() { + query.Add("last_read_at", opt.LastReadAt.Format(time.RFC3339)) + } + for _, s := range opt.Status { + query.Add("status-types", string(s)) + } + if len(opt.ToStatus) != 0 { + query.Add("to-status", string(opt.ToStatus)) + } + return query.Encode() +} + +// Validate the CreateUserOption struct +func (opt MarkNotificationOptions) Validate(c *Client) error { + if len(opt.Status) != 0 || len(opt.ToStatus) != 0 { + return c.checkServerVersionGreaterThanOrEqual(version1_12_3) + } + return nil +} + +// CheckNotifications list users's notification threads +func (c *Client) CheckNotifications() (int64, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return 0, nil, err + } + new := struct { + New int64 `json:"new"` + }{} + + resp, err := c.getParsedResponse("GET", "/notifications/new", jsonHeader, nil, &new) + return new.New, resp, err +} + +// GetNotification get notification thread by ID +func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + thread := new(NotificationThread) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/notifications/threads/%d", id), nil, nil, thread) + return thread, resp, err +} + +// ReadNotification mark notification thread as read by ID +// It optionally takes a second argument if status has to be set other than 'read' +func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + link := fmt.Sprintf("/notifications/threads/%d", id) + if len(status) != 0 { + link += fmt.Sprintf("?to-status=%s", status[0]) + } + _, resp, err := c.getResponse("PATCH", link, nil, nil) + return resp, err +} + +// ListNotifications list users's notification threads +func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + link, _ := url.Parse("/notifications") + link.RawQuery = opt.QueryEncode() + threads := make([]*NotificationThread, 0, 10) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) + return threads, resp, err +} + +// ReadNotifications mark notification threads as read +func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + if err := opt.Validate(c); err != nil { + return nil, err + } + link, _ := url.Parse("/notifications") + link.RawQuery = opt.QueryEncode() + _, resp, err := c.getResponse("PUT", link.String(), nil, nil) + return resp, err +} + +// ListRepoNotifications list users's notification threads on a specific repo +func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo)) + link.RawQuery = opt.QueryEncode() + threads := make([]*NotificationThread, 0, 10) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) + return threads, resp, err +} + +// ReadRepoNotifications mark notification threads as read on a specific repo +func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + if err := opt.Validate(c); err != nil { + return nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo)) + link.RawQuery = opt.QueryEncode() + _, resp, err := c.getResponse("PUT", link.String(), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/oauth2.go b/vendor/code.gitea.io/sdk/gitea/oauth2.go new file mode 100644 index 0000000..bbdfdaf --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/oauth2.go @@ -0,0 +1,91 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Oauth2 represents an Oauth2 Application +type Oauth2 struct { + ID int64 `json:"id"` + Name string `json:"name"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectURIs []string `json:"redirect_uris"` + Created time.Time `json:"created"` +} + +// ListOauth2Option for listing Oauth2 Applications +type ListOauth2Option struct { + ListOptions +} + +// CreateOauth2Option required options for creating an Application +type CreateOauth2Option struct { + Name string `json:"name"` + RedirectURIs []string `json:"redirect_uris"` +} + +// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object. +func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + oauth := new(Oauth2) + resp, err := c.getParsedResponse("POST", "/user/applications/oauth2", jsonHeader, bytes.NewReader(body), oauth) + return oauth, resp, err +} + +// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object. +func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + oauth := new(Oauth2) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), jsonHeader, bytes.NewReader(body), oauth) + return oauth, resp, err +} + +// GetOauth2 a specific Oauth2 Application by ID. +func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + oauth2s := &Oauth2{} + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil, &oauth2s) + return oauth2s, resp, err +} + +// ListOauth2 all of your Oauth2 Applications. +func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + oauth2s := make([]*Oauth2, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2?%s", opt.getURLQuery().Encode()), nil, nil, &oauth2s) + return oauth2s, resp, err +} + +// DeleteOauth2 delete an Oauth2 application by ID +func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/org.go b/vendor/code.gitea.io/sdk/gitea/org.go new file mode 100644 index 0000000..82e1bf5 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/org.go @@ -0,0 +1,155 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Organization represents an organization +type Organization struct { + ID int64 `json:"id"` + UserName string `json:"username"` + FullName string `json:"full_name"` + AvatarURL string `json:"avatar_url"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility string `json:"visibility"` +} + +// VisibleType defines the visibility +type VisibleType string + +const ( + // VisibleTypePublic Visible for everyone + VisibleTypePublic VisibleType = "public" + + // VisibleTypeLimited Visible for every connected user + VisibleTypeLimited VisibleType = "limited" + + // VisibleTypePrivate Visible only for organization's members + VisibleTypePrivate VisibleType = "private" +) + +// ListOrgsOptions options for listing organizations +type ListOrgsOptions struct { + ListOptions +} + +// ListMyOrgs list all of current user's organizations +func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) { + opt.setDefaults() + orgs := make([]*Organization, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) + return orgs, resp, err +} + +// ListUserOrgs list all of some user's organizations +func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + orgs := make([]*Organization, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs) + return orgs, resp, err +} + +// GetOrg get one organization by name +func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) { + if err := escapeValidatePathSegments(&orgname); err != nil { + return nil, nil, err + } + org := new(Organization) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org) + return org, resp, err +} + +// CreateOrgOption options for creating an organization +type CreateOrgOption struct { + Name string `json:"username"` + FullName string `json:"full_name"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility VisibleType `json:"visibility"` + RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"` +} + +// checkVisibilityOpt check if mode exist +func checkVisibilityOpt(v VisibleType) bool { + return v == VisibleTypePublic || v == VisibleTypeLimited || v == VisibleTypePrivate +} + +// Validate the CreateOrgOption struct +func (opt CreateOrgOption) Validate() error { + if len(opt.Name) == 0 { + return fmt.Errorf("empty org name") + } + if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { + return fmt.Errorf("infalid bisibility option") + } + return nil +} + +// CreateOrg creates an organization +func (c *Client) CreateOrg(opt CreateOrgOption) (*Organization, *Response, error) { + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + org := new(Organization) + resp, err := c.getParsedResponse("POST", "/orgs", jsonHeader, bytes.NewReader(body), org) + return org, resp, err +} + +// EditOrgOption options for editing an organization +type EditOrgOption struct { + FullName string `json:"full_name"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` + Visibility VisibleType `json:"visibility"` +} + +// Validate the EditOrgOption struct +func (opt EditOrgOption) Validate() error { + if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { + return fmt.Errorf("infalid bisibility option") + } + return nil +} + +// EditOrg modify one organization via options +func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) { + if err := escapeValidatePathSegments(&orgname); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// DeleteOrg deletes an organization +func (c *Client) DeleteOrg(orgname string) (*Response, error) { + if err := escapeValidatePathSegments(&orgname); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/org_member.go b/vendor/code.gitea.io/sdk/gitea/org_member.go new file mode 100644 index 0000000..1eed90f --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/org_member.go @@ -0,0 +1,119 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "net/url" +) + +// DeleteOrgMembership remove a member from an organization +func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil) + return resp, err +} + +// ListOrgMembershipOption list OrgMembership options +type ListOrgMembershipOption struct { + ListOptions +} + +// ListOrgMembership list an organization's members +func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", org)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) + return users, resp, err +} + +// ListPublicOrgMembership list an organization's members +func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", org)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) + return users, resp, err +} + +// CheckOrgMembership Check if a user is a member of an organization +func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil) + if err != nil { + return false, resp, err + } + switch status { + case http.StatusNoContent: + return true, resp, nil + case http.StatusNotFound: + return false, resp, nil + default: + return false, resp, fmt.Errorf("unexpected Status: %d", status) + } +} + +// CheckPublicOrgMembership Check if a user is a member of an organization +func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil) + if err != nil { + return false, resp, err + } + switch status { + case http.StatusNoContent: + return true, resp, nil + case http.StatusNotFound: + return false, resp, nil + default: + return false, resp, fmt.Errorf("unexpected Status: %d", status) + } +} + +// SetPublicOrgMembership publicize/conceal a user's membership +func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) { + if err := escapeValidatePathSegments(&org, &user); err != nil { + return nil, err + } + var ( + status int + err error + resp *Response + ) + if visible { + status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil) + } else { + status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil) + } + if err != nil { + return resp, err + } + switch status { + case http.StatusNoContent: + return resp, nil + case http.StatusNotFound: + return resp, fmt.Errorf("forbidden") + default: + return resp, fmt.Errorf("unexpected Status: %d", status) + } +} diff --git a/vendor/code.gitea.io/sdk/gitea/org_team.go b/vendor/code.gitea.io/sdk/gitea/org_team.go new file mode 100644 index 0000000..4b1b27b --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/org_team.go @@ -0,0 +1,242 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Team represents a team in an organization +type Team struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Organization *Organization `json:"organization"` + Permission AccessMode `json:"permission"` + CanCreateOrgRepo bool `json:"can_create_org_repo"` + IncludesAllRepositories bool `json:"includes_all_repositories"` + Units []RepoUnitType `json:"units"` +} + +// RepoUnitType represent all unit types of a repo gitea currently offer +type RepoUnitType string + +const ( + // RepoUnitCode represent file view of a repository + RepoUnitCode RepoUnitType = "repo.code" + // RepoUnitIssues represent issues of a repository + RepoUnitIssues RepoUnitType = "repo.issues" + // RepoUnitPulls represent pulls of a repository + RepoUnitPulls RepoUnitType = "repo.pulls" + // RepoUnitExtIssues represent external issues of a repository + RepoUnitExtIssues RepoUnitType = "repo.ext_issues" + // RepoUnitWiki represent wiki of a repository + RepoUnitWiki RepoUnitType = "repo.wiki" + // RepoUnitExtWiki represent external wiki of a repository + RepoUnitExtWiki RepoUnitType = "repo.ext_wiki" + // RepoUnitReleases represent releases of a repository + RepoUnitReleases RepoUnitType = "repo.releases" + // RepoUnitProjects represent projects of a repository + RepoUnitProjects RepoUnitType = "repo.projects" +) + +// ListTeamsOptions options for listing teams +type ListTeamsOptions struct { + ListOptions +} + +// ListOrgTeams lists all teams of an organization +func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + teams := make([]*Team, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams) + return teams, resp, err +} + +// ListMyTeams lists all the teams of the current user +func (c *Client) ListMyTeams(opt *ListTeamsOptions) ([]*Team, *Response, error) { + opt.setDefaults() + teams := make([]*Team, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/teams?%s", opt.getURLQuery().Encode()), nil, nil, &teams) + return teams, resp, err +} + +// GetTeam gets a team by ID +func (c *Client) GetTeam(id int64) (*Team, *Response, error) { + t := new(Team) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d", id), nil, nil, t) + return t, resp, err +} + +// CreateTeamOption options for creating a team +type CreateTeamOption struct { + Name string `json:"name"` + Description string `json:"description"` + Permission AccessMode `json:"permission"` + CanCreateOrgRepo bool `json:"can_create_org_repo"` + IncludesAllRepositories bool `json:"includes_all_repositories"` + Units []RepoUnitType `json:"units"` +} + +// Validate the CreateTeamOption struct +func (opt CreateTeamOption) Validate() error { + if opt.Permission == AccessModeOwner { + opt.Permission = AccessModeAdmin + } else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { + return fmt.Errorf("permission mode invalid") + } + if len(opt.Name) == 0 { + return fmt.Errorf("name required") + } + if len(opt.Name) > 30 { + return fmt.Errorf("name to long") + } + if len(opt.Description) > 255 { + return fmt.Errorf("description to long") + } + return nil +} + +// CreateTeam creates a team for an organization +func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + t := new(Team) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/teams", org), jsonHeader, bytes.NewReader(body), t) + return t, resp, err +} + +// EditTeamOption options for editing a team +type EditTeamOption struct { + Name string `json:"name"` + Description *string `json:"description"` + Permission AccessMode `json:"permission"` + CanCreateOrgRepo *bool `json:"can_create_org_repo"` + IncludesAllRepositories *bool `json:"includes_all_repositories"` + Units []RepoUnitType `json:"units"` +} + +// Validate the EditTeamOption struct +func (opt EditTeamOption) Validate() error { + if opt.Permission == AccessModeOwner { + opt.Permission = AccessModeAdmin + } else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { + return fmt.Errorf("permission mode invalid") + } + if len(opt.Name) == 0 { + return fmt.Errorf("name required") + } + if len(opt.Name) > 30 { + return fmt.Errorf("name to long") + } + if opt.Description != nil && len(*opt.Description) > 255 { + return fmt.Errorf("description to long") + } + return nil +} + +// EditTeam edits a team of an organization +func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) { + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PATCH", fmt.Sprintf("/teams/%d", id), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// DeleteTeam deletes a team of an organization +func (c *Client) DeleteTeam(id int64) (*Response, error) { + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d", id), nil, nil) + return resp, err +} + +// ListTeamMembersOptions options for listing team's members +type ListTeamMembersOptions struct { + ListOptions +} + +// ListTeamMembers lists all members of a team +func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User, *Response, error) { + opt.setDefaults() + members := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members?%s", id, opt.getURLQuery().Encode()), nil, nil, &members) + return members, resp, err +} + +// GetTeamMember gets a member of a team +func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + m := new(User) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m) + return m, resp, err +} + +// AddTeamMember adds a member to a team +func (c *Client) AddTeamMember(id int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil) + return resp, err +} + +// RemoveTeamMember removes a member from a team +func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil) + return resp, err +} + +// ListTeamRepositoriesOptions options for listing team's repositories +type ListTeamRepositoriesOptions struct { + ListOptions +} + +// ListTeamRepositories lists all repositories of a team +func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions) ([]*Repository, *Response, error) { + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/repos?%s", id, opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// AddTeamRepository adds a repository to a team +func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil) + return resp, err +} + +// RemoveTeamRepository removes a repository from a team +func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&org, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/pull.go b/vendor/code.gitea.io/sdk/gitea/pull.go new file mode 100644 index 0000000..e0198c2 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/pull.go @@ -0,0 +1,328 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" + + "github.com/hashicorp/go-version" +) + +// PRBranchInfo information about a branch +type PRBranchInfo struct { + Name string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + RepoID int64 `json:"repo_id"` + Repository *Repository `json:"repo"` +} + +// PullRequest represents a pull request +type PullRequest struct { + ID int64 `json:"id"` + URL string `json:"url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignee *User `json:"assignee"` + Assignees []*User `json:"assignees"` + State StateType `json:"state"` + IsLocked bool `json:"is_locked"` + Comments int `json:"comments"` + + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + + Mergeable bool `json:"mergeable"` + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` + MergedCommitID *string `json:"merge_commit_sha"` + MergedBy *User `json:"merged_by"` + + Base *PRBranchInfo `json:"base"` + Head *PRBranchInfo `json:"head"` + MergeBase string `json:"merge_base"` + + Deadline *time.Time `json:"due_date"` + Created *time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` + Closed *time.Time `json:"closed_at"` +} + +// ListPullRequestsOptions options for listing pull requests +type ListPullRequestsOptions struct { + ListOptions + State StateType `json:"state"` + // oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority + Sort string + Milestone int64 +} + +// MergeStyle is used specify how a pull is merged +type MergeStyle string + +const ( + // MergeStyleMerge merge pull as usual + MergeStyleMerge MergeStyle = "merge" + // MergeStyleRebase rebase pull + MergeStyleRebase MergeStyle = "rebase" + // MergeStyleRebaseMerge rebase and merge pull + MergeStyleRebaseMerge MergeStyle = "rebase-merge" + // MergeStyleSquash squash and merge pull + MergeStyleSquash MergeStyle = "squash" +) + +// QueryEncode turns options into querystring argument +func (opt *ListPullRequestsOptions) QueryEncode() string { + query := opt.getURLQuery() + if len(opt.State) > 0 { + query.Add("state", string(opt.State)) + } + if len(opt.Sort) > 0 { + query.Add("sort", opt.Sort) + } + if opt.Milestone > 0 { + query.Add("milestone", fmt.Sprintf("%d", opt.Milestone)) + } + return query.Encode() +} + +// ListRepoPullRequests list PRs of one repository +func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + prs := make([]*PullRequest, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo)) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs) + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + for i := range prs { + if err := fixPullHeadSha(c, prs[i]); err != nil { + return prs, resp, err + } + } + } + return prs, resp, err +} + +// GetPullRequest get information of one PR +func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + pr := new(PullRequest) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr) + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + if err := fixPullHeadSha(c, pr); err != nil { + return pr, resp, err + } + } + return pr, resp, err +} + +// CreatePullRequestOption options when creating a pull request +type CreatePullRequestOption struct { + Head string `json:"head"` + Base string `json:"base"` + Title string `json:"title"` + Body string `json:"body"` + Assignee string `json:"assignee"` + Assignees []string `json:"assignees"` + Milestone int64 `json:"milestone"` + Labels []int64 `json:"labels"` + Deadline *time.Time `json:"due_date"` +} + +// CreatePullRequest create pull request with options +func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + pr := new(PullRequest) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls", owner, repo), + jsonHeader, bytes.NewReader(body), pr) + return pr, resp, err +} + +// EditPullRequestOption options when modify pull request +type EditPullRequestOption struct { + Title string `json:"title"` + Body string `json:"body"` + Base string `json:"base"` + Assignee string `json:"assignee"` + Assignees []string `json:"assignees"` + Milestone int64 `json:"milestone"` + Labels []int64 `json:"labels"` + State *StateType `json:"state"` + Deadline *time.Time `json:"due_date"` +} + +// Validate the EditPullRequestOption struct +func (opt EditPullRequestOption) Validate(c *Client) error { + if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + if len(opt.Base) != 0 { + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return fmt.Errorf("can not change base gitea to old") + } + } + return nil +} + +// EditPullRequest modify pull request with PR id and options +func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + pr := new(PullRequest) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), + jsonHeader, bytes.NewReader(body), pr) + return pr, resp, err +} + +// MergePullRequestOption options when merging a pull request +type MergePullRequestOption struct { + Style MergeStyle `json:"Do"` + Title string `json:"MergeTitleField"` + Message string `json:"MergeMessageField"` +} + +var version1_11_5, _ = version.NewVersion("1.11.5") + +// Validate the MergePullRequestOption struct +func (opt MergePullRequestOption) Validate(c *Client) error { + if opt.Style == MergeStyleSquash { + if err := c.checkServerVersionGreaterThanOrEqual(version1_11_5); err != nil { + return err + } + } + return nil +} + +// MergePullRequest merge a PR to repository by PR id +func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return false, nil, err + } + if err := opt.Validate(c); err != nil { + return false, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("POST", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), jsonHeader, bytes.NewReader(body)) + if err != nil { + return false, resp, err + } + return status == 200, resp, nil +} + +// IsPullRequestMerged test if one PR is merged to one repository +func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil) + + if err != nil { + return false, resp, err + } + + return status == 204, resp, nil +} + +// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR +func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &kind); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + r, _, err2 := c.GetRepo(owner, repo) + if err2 != nil { + return nil, nil, err + } + if r.Private { + return nil, nil, err + } + return c.getWebResponse("GET", fmt.Sprintf("/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil) + } + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil) +} + +// GetPullRequestPatch gets the .patch file as bytes for a PR +func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) { + return c.getPullRequestDiffOrPatch(owner, repo, "patch", index) +} + +// GetPullRequestDiff gets the .diff file as bytes for a PR +func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) { + return c.getPullRequestDiffOrPatch(owner, repo, "diff", index) +} + +// ListPullRequestCommitsOptions options for listing pull requests +type ListPullRequestCommitsOptions struct { + ListOptions +} + +// ListPullRequestCommits list commits for a pull request +func (c *Client) ListPullRequestCommits(owner, repo string, index int64, opt ListPullRequestCommitsOptions) ([]*Commit, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/commits", owner, repo, index)) + opt.setDefaults() + commits := make([]*Commit, 0, opt.PageSize) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) + return commits, resp, err +} + +// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675 +// When no head sha is available, this is because the branch got deleted in the base repo. +// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref, +// which stays available to resolve the commit sha. This is fixed for gitea >= 1.14.0 +func fixPullHeadSha(client *Client, pr *PullRequest) error { + if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil && + pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" { + owner := pr.Base.Repository.Owner.UserName + repo := pr.Base.Repository.Name + refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref) + if err != nil { + return err + } else if len(refs) == 0 { + return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref) + } + pr.Head.Sha = refs[0].Object.SHA + } + return nil +} diff --git a/vendor/code.gitea.io/sdk/gitea/pull_review.go b/vendor/code.gitea.io/sdk/gitea/pull_review.go new file mode 100644 index 0000000..a484dfd --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/pull_review.go @@ -0,0 +1,325 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" +) + +// ReviewStateType review state type +type ReviewStateType string + +const ( + // ReviewStateApproved pr is approved + ReviewStateApproved ReviewStateType = "APPROVED" + // ReviewStatePending pr state is pending + ReviewStatePending ReviewStateType = "PENDING" + // ReviewStateComment is a comment review + ReviewStateComment ReviewStateType = "COMMENT" + // ReviewStateRequestChanges changes for pr are requested + ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES" + // ReviewStateRequestReview review is requested from user + ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW" + // ReviewStateUnknown state of pr is unknown + ReviewStateUnknown ReviewStateType = "" +) + +// PullReview represents a pull request review +type PullReview struct { + ID int64 `json:"id"` + Reviewer *User `json:"user"` + ReviewerTeam *Team `json:"team"` + State ReviewStateType `json:"state"` + Body string `json:"body"` + CommitID string `json:"commit_id"` + // Stale indicates if the pull has changed since the review + Stale bool `json:"stale"` + // Official indicates if the review counts towards the required approval limit, if PR base is a protected branch + Official bool `json:"official"` + Dismissed bool `json:"dismissed"` + CodeCommentsCount int `json:"comments_count"` + Submitted time.Time `json:"submitted_at"` + + HTMLURL string `json:"html_url"` + HTMLPullURL string `json:"pull_request_url"` +} + +// PullReviewComment represents a comment on a pull request review +type PullReviewComment struct { + ID int64 `json:"id"` + Body string `json:"body"` + Reviewer *User `json:"user"` + ReviewID int64 `json:"pull_request_review_id"` + Resolver *User `json:"resolver"` + + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + + Path string `json:"path"` + CommitID string `json:"commit_id"` + OrigCommitID string `json:"original_commit_id"` + DiffHunk string `json:"diff_hunk"` + LineNum uint64 `json:"position"` + OldLineNum uint64 `json:"original_position"` + + HTMLURL string `json:"html_url"` + HTMLPullURL string `json:"pull_request_url"` +} + +// CreatePullReviewOptions are options to create a pull review +type CreatePullReviewOptions struct { + State ReviewStateType `json:"event"` + Body string `json:"body"` + CommitID string `json:"commit_id"` + Comments []CreatePullReviewComment `json:"comments"` +} + +// CreatePullReviewComment represent a review comment for creation api +type CreatePullReviewComment struct { + // the tree path + Path string `json:"path"` + Body string `json:"body"` + // if comment to old file line or 0 + OldLineNum int64 `json:"old_position"` + // if comment to new file line or 0 + NewLineNum int64 `json:"new_position"` +} + +// SubmitPullReviewOptions are options to submit a pending pull review +type SubmitPullReviewOptions struct { + State ReviewStateType `json:"event"` + Body string `json:"body"` +} + +// DismissPullReviewOptions are options to dismiss a pull review +type DismissPullReviewOptions struct { + Message string `json:"message"` +} + +// PullReviewRequestOptions are options to add or remove pull review requests +type PullReviewRequestOptions struct { + Reviewers []string `json:"reviewers"` + TeamReviewers []string `json:"team_reviewers"` +} + +// ListPullReviewsOptions options for listing PullReviews +type ListPullReviewsOptions struct { + ListOptions +} + +// Validate the CreatePullReviewOptions struct +func (opt CreatePullReviewOptions) Validate() error { + if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 { + return fmt.Errorf("body is empty") + } + for i := range opt.Comments { + if err := opt.Comments[i].Validate(); err != nil { + return err + } + } + return nil +} + +// Validate the SubmitPullReviewOptions struct +func (opt SubmitPullReviewOptions) Validate() error { + if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 { + return fmt.Errorf("body is empty") + } + return nil +} + +// Validate the CreatePullReviewComment struct +func (opt CreatePullReviewComment) Validate() error { + if len(strings.TrimSpace(opt.Body)) == 0 { + return fmt.Errorf("body is empty") + } + if opt.NewLineNum != 0 && opt.OldLineNum != 0 { + return fmt.Errorf("old and new line num are set, cant identify the code comment position") + } + return nil +} + +// ListPullReviews lists all reviews of a pull request +func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + opt.setDefaults() + rs := make([]*PullReview, 0, opt.PageSize) + + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index)) + link.RawQuery = opt.ListOptions.getURLQuery().Encode() + + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rs) + return rs, resp, err +} + +// GetPullReview gets a specific review of a pull request +func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + + r := new(PullReview) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil, &r) + return r, resp, err +} + +// ListPullReviewComments lists all comments of a pull request review +func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + rcl := make([]*PullReviewComment, 0, 4) + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments", owner, repo, index, id)) + + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rcl) + return rcl, resp, err +} + +// DeletePullReview delete a specific review from a pull request +func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil) + return resp, err +} + +// CreatePullReview create a review to an pull request +func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + r := new(PullReview) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// SubmitPullReview submit a pending review to an pull request +func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + r := new(PullReview) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// CreateReviewRequests create review requests to an pull request +func (c *Client) CreateReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + + _, resp, err := c.getResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index), + jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// DeleteReviewRequests delete review requests to an pull request +func (c *Client) DeleteReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + + _, resp, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index), + jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// DismissPullReview dismiss a review for a pull request +func (c *Client) DismissPullReview(owner, repo string, index, id int64, opt DismissPullReviewOptions) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + + _, resp, err := c.getResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, id), + jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// UnDismissPullReview cancel to dismiss a review for a pull request +func (c *Client) UnDismissPullReview(owner, repo string, index, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + + _, resp, err := c.getResponse("POST", + fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, id), + jsonHeader, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/release.go b/vendor/code.gitea.io/sdk/gitea/release.go new file mode 100644 index 0000000..c8e7681 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/release.go @@ -0,0 +1,202 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" +) + +// Release represents a repository release +type Release struct { + ID int64 `json:"id"` + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + TarURL string `json:"tarball_url"` + ZipURL string `json:"zipball_url"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + PublishedAt time.Time `json:"published_at"` + Publisher *User `json:"author"` + Attachments []*Attachment `json:"assets"` +} + +// ListReleasesOptions options for listing repository's releases +type ListReleasesOptions struct { + ListOptions + IsDraft *bool + IsPreRelease *bool +} + +// QueryEncode turns options into querystring argument +func (opt *ListReleasesOptions) QueryEncode() string { + query := opt.getURLQuery() + + if opt.IsDraft != nil { + query.Add("draft", fmt.Sprintf("%t", *opt.IsDraft)) + } + if opt.IsPreRelease != nil { + query.Add("draft", fmt.Sprintf("%t", *opt.IsPreRelease)) + } + + return query.Encode() +} + +// ListReleases list releases of a repository +func (c *Client) ListReleases(owner, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + releases := make([]*Release, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.QueryEncode()), + nil, nil, &releases) + return releases, resp, err +} + +// GetRelease get a release of a repository by id +func (c *Client) GetRelease(owner, repo string, id int64) (*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id), + jsonHeader, nil, &r) + return r, resp, err +} + +// GetReleaseByTag get a release of a repository by tag +func (c *Client) GetReleaseByTag(owner, repo string, tag string) (*Release, *Response, error) { + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + return c.fallbackGetReleaseByTag(owner, repo, tag) + } + if err := escapeValidatePathSegments(&owner, &repo, &tag); err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/tags/%s", owner, repo, tag), + nil, nil, &r) + return r, resp, err +} + +// CreateReleaseOption options when creating a release +type CreateReleaseOption struct { + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` +} + +// Validate the CreateReleaseOption struct +func (opt CreateReleaseOption) Validate() error { + if len(strings.TrimSpace(opt.Title)) == 0 { + return fmt.Errorf("title is empty") + } + return nil +} + +// CreateRelease create a release +func (c *Client) CreateRelease(owner, repo string, opt CreateReleaseOption) (*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(opt) + if err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/releases", owner, repo), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// EditReleaseOption options when editing a release +type EditReleaseOption struct { + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft *bool `json:"draft"` + IsPrerelease *bool `json:"prerelease"` +} + +// EditRelease edit a release +func (c *Client) EditRelease(owner, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(form) + if err != nil { + return nil, nil, err + } + r := new(Release) + resp, err := c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id), + jsonHeader, bytes.NewReader(body), r) + return r, resp, err +} + +// DeleteRelease delete a release from a repository, keeping its tag +func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), + nil, nil) + return resp, err +} + +// DeleteReleaseByTag deletes a release frm a repository by tag +func (c *Client) DeleteReleaseByTag(user, repo string, tag string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag), + nil, nil) + return resp, err +} + +// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 ) +func (c *Client) fallbackGetReleaseByTag(owner, repo string, tag string) (*Release, *Response, error) { + for i := 1; ; i++ { + rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}}) + if err != nil { + return nil, resp, err + } + if len(rl) == 0 { + return nil, + &Response{&http.Response{StatusCode: 404}}, + fmt.Errorf("release with tag '%s' not found", tag) + } + for _, r := range rl { + if r.TagName == tag { + return r, resp, nil + } + } + } +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo.go b/vendor/code.gitea.io/sdk/gitea/repo.go new file mode 100644 index 0000000..62d13a3 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo.go @@ -0,0 +1,534 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/url" + "strings" + "time" +) + +// Permission represents a set of permissions +type Permission struct { + Admin bool `json:"admin"` + Push bool `json:"push"` + Pull bool `json:"pull"` +} + +// InternalTracker represents settings for internal tracker +type InternalTracker struct { + // Enable time tracking (Built-in issue tracker) + EnableTimeTracker bool `json:"enable_time_tracker"` + // Let only contributors track time (Built-in issue tracker) + AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"` + // Enable dependencies for issues and pull requests (Built-in issue tracker) + EnableIssueDependencies bool `json:"enable_issue_dependencies"` +} + +// ExternalTracker represents settings for external tracker +type ExternalTracker struct { + // URL of external issue tracker. + ExternalTrackerURL string `json:"external_tracker_url"` + // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index. + ExternalTrackerFormat string `json:"external_tracker_format"` + // External Issue Tracker Number Format, either `numeric` or `alphanumeric` + ExternalTrackerStyle string `json:"external_tracker_style"` +} + +// ExternalWiki represents setting for external wiki +type ExternalWiki struct { + // URL of external wiki. + ExternalWikiURL string `json:"external_wiki_url"` +} + +// Repository represents a repository +type Repository struct { + ID int64 `json:"id"` + Owner *User `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Description string `json:"description"` + Empty bool `json:"empty"` + Private bool `json:"private"` + Fork bool `json:"fork"` + Template bool `json:"template"` + Parent *Repository `json:"parent"` + Mirror bool `json:"mirror"` + Size int `json:"size"` + HTMLURL string `json:"html_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + OriginalURL string `json:"original_url"` + Website string `json:"website"` + Stars int `json:"stars_count"` + Forks int `json:"forks_count"` + Watchers int `json:"watchers_count"` + OpenIssues int `json:"open_issues_count"` + OpenPulls int `json:"open_pr_counter"` + Releases int `json:"release_counter"` + DefaultBranch string `json:"default_branch"` + Archived bool `json:"archived"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Permissions *Permission `json:"permissions,omitempty"` + HasIssues bool `json:"has_issues"` + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` + HasWiki bool `json:"has_wiki"` + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + HasPullRequests bool `json:"has_pull_requests"` + HasProjects bool `json:"has_projects"` + IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"` + AllowMerge bool `json:"allow_merge_commits"` + AllowRebase bool `json:"allow_rebase"` + AllowRebaseMerge bool `json:"allow_rebase_explicit"` + AllowSquash bool `json:"allow_squash_merge"` + AvatarURL string `json:"avatar_url"` + Internal bool `json:"internal"` + MirrorInterval string `json:"mirror_interval"` + DefaultMergeStyle MergeStyle `json:"default_merge_style"` +} + +// RepoType represent repo type +type RepoType string + +const ( + // RepoTypeNone dont specify a type + RepoTypeNone RepoType = "" + // RepoTypeSource is the default repo type + RepoTypeSource RepoType = "source" + // RepoTypeFork is a repo witch was forked from an other one + RepoTypeFork RepoType = "fork" + // RepoTypeMirror represents an mirror repo + RepoTypeMirror RepoType = "mirror" +) + +// TrustModel represent how git signatures are handled in a repository +type TrustModel string + +const ( + // TrustModelDefault use TM set by global config + TrustModelDefault TrustModel = "default" + // TrustModelCollaborator gpg signature has to be owned by a repo collaborator + TrustModelCollaborator TrustModel = "collaborator" + // TrustModelCommitter gpg signature has to match committer + TrustModelCommitter TrustModel = "committer" + // TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator + TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter" +) + +// ListReposOptions options for listing repositories +type ListReposOptions struct { + ListOptions +} + +// ListMyRepos lists all repositories for the authenticated user that has access to. +func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, error) { + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/repos?%s", opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// ListUserRepos list all repositories of one user by user's name +func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// ListOrgReposOptions options for a organization's repositories +type ListOrgReposOptions struct { + ListOptions +} + +// ListOrgRepos list all repositories of one organization by organization's name +func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + opt.setDefaults() + repos := make([]*Repository, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos) + return repos, resp, err +} + +// SearchRepoOptions options for searching repositories +type SearchRepoOptions struct { + ListOptions + + // The keyword to query + Keyword string + // Limit search to repositories with keyword as topic + KeywordIsTopic bool + // Include search of keyword within repository description + KeywordInDescription bool + + /* + User Filter + */ + + // Repo Owner + OwnerID int64 + // Stared By UserID + StarredByUserID int64 + + /* + Repo Attributes + */ + + // pubic, private or all repositories (defaults to all) + IsPrivate *bool + // archived, non-archived or all repositories (defaults to all) + IsArchived *bool + // Exclude template repos from search + ExcludeTemplate bool + // Filter by "fork", "source", "mirror" + Type RepoType + + /* + Sort Filters + */ + + // sort repos by attribute. Supported values are "alpha", "created", "updated", "size", and "id". Default is "alpha" + Sort string + // sort order, either "asc" (ascending) or "desc" (descending). Default is "asc", ignored if "sort" is not specified. + Order string + // Repo owner to prioritize in the results + PrioritizedByOwnerID int64 + + /* + Cover EdgeCases + */ + // if set all other options are ignored and this string is used as query + RawQuery string +} + +// QueryEncode turns options into querystring argument +func (opt *SearchRepoOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.Keyword != "" { + query.Add("q", opt.Keyword) + } + if opt.KeywordIsTopic { + query.Add("topic", "true") + } + if opt.KeywordInDescription { + query.Add("includeDesc", "true") + } + + // User Filter + if opt.OwnerID > 0 { + query.Add("uid", fmt.Sprintf("%d", opt.OwnerID)) + query.Add("exclusive", "true") + } + if opt.StarredByUserID > 0 { + query.Add("starredBy", fmt.Sprintf("%d", opt.StarredByUserID)) + } + + // Repo Attributes + if opt.IsPrivate != nil { + query.Add("is_private", fmt.Sprintf("%v", opt.IsPrivate)) + } + if opt.IsArchived != nil { + query.Add("archived", fmt.Sprintf("%v", opt.IsArchived)) + } + if opt.ExcludeTemplate { + query.Add("template", "false") + } + if len(opt.Type) != 0 { + query.Add("mode", string(opt.Type)) + } + + // Sort Filters + if opt.Sort != "" { + query.Add("sort", opt.Sort) + } + if opt.PrioritizedByOwnerID > 0 { + query.Add("priority_owner_id", fmt.Sprintf("%d", opt.PrioritizedByOwnerID)) + } + if opt.Order != "" { + query.Add("order", opt.Order) + } + + return query.Encode() +} + +type searchRepoResponse struct { + Repos []*Repository `json:"data"` +} + +// SearchRepos searches for repositories matching the given filters +func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, error) { + opt.setDefaults() + repos := new(searchRepoResponse) + + link, _ := url.Parse("/repos/search") + + if len(opt.RawQuery) != 0 { + link.RawQuery = opt.RawQuery + } else { + link.RawQuery = opt.QueryEncode() + // IsPrivate only works on gitea >= 1.12.0 + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil { + if *opt.IsPrivate { + // private repos only not supported on gitea <= 1.11.x + return nil, nil, err + } + link.Query().Add("private", "false") + } + } + + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &repos) + return repos.Repos, resp, err +} + +// CreateRepoOption options when creating repository +type CreateRepoOption struct { + // Name of the repository to create + Name string `json:"name"` + // Description of the repository to create + Description string `json:"description"` + // Whether the repository is private + Private bool `json:"private"` + // Issue Label set to use + IssueLabels string `json:"issue_labels"` + // Whether the repository should be auto-intialized? + AutoInit bool `json:"auto_init"` + // Whether the repository is template + Template bool `json:"template"` + // Gitignores to use + Gitignores string `json:"gitignores"` + // License to use + License string `json:"license"` + // Readme of the repository to create + Readme string `json:"readme"` + // DefaultBranch of the repository (used when initializes and in template) + DefaultBranch string `json:"default_branch"` + // TrustModel of the repository + TrustModel TrustModel `json:"trust_model"` +} + +// Validate the CreateRepoOption struct +func (opt CreateRepoOption) Validate(c *Client) error { + if len(strings.TrimSpace(opt.Name)) == 0 { + return fmt.Errorf("name is empty") + } + if len(opt.Name) > 100 { + return fmt.Errorf("name has more than 100 chars") + } + if len(opt.Description) > 255 { + return fmt.Errorf("name has more than 255 chars") + } + if len(opt.DefaultBranch) > 100 { + return fmt.Errorf("name has more than 100 chars") + } + if len(opt.TrustModel) != 0 { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return err + } + } + return nil +} + +// CreateRepo creates a repository for authenticated user. +func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) { + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", "/user/repos", jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// CreateOrgRepo creates an organization repository for authenticated user. +func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&org); err != nil { + return nil, nil, err + } + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/org/%s/repos", org), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// GetRepo returns information of a repository of given owner. +func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo) + return repo, resp, err +} + +// GetRepoByID returns information of a repository by a giver repository ID. +func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) { + repo := new(Repository) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo) + return repo, resp, err +} + +// EditRepoOption options when editing a repository's properties +type EditRepoOption struct { + // name of the repository + Name *string `json:"name,omitempty"` + // a short description of the repository. + Description *string `json:"description,omitempty"` + // a URL with more information about the repository. + Website *string `json:"website,omitempty"` + // either `true` to make the repository private or `false` to make it public. + // Note: you will get a 422 error if the organization restricts changing repository visibility to organization + // owners and a non-owner tries to change the value of private. + Private *bool `json:"private,omitempty"` + // either `true` to make this repository a template or `false` to make it a normal repository + Template *bool `json:"template,omitempty"` + // either `true` to enable issues for this repository or `false` to disable them. + HasIssues *bool `json:"has_issues,omitempty"` + // set this structure to configure internal issue tracker (requires has_issues) + InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` + // set this structure to use external issue tracker (requires has_issues) + ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"` + // either `true` to enable the wiki for this repository or `false` to disable it. + HasWiki *bool `json:"has_wiki,omitempty"` + // set this structure to use external wiki instead of internal (requires has_wiki) + ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"` + // sets the default branch for this repository. + DefaultBranch *string `json:"default_branch,omitempty"` + // either `true` to allow pull requests, or `false` to prevent pull request. + HasPullRequests *bool `json:"has_pull_requests,omitempty"` + // either `true` to enable project unit, or `false` to disable them. + HasProjects *bool `json:"has_projects,omitempty"` + // either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`. + IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"` + // either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`. + AllowMerge *bool `json:"allow_merge_commits,omitempty"` + // either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. `has_pull_requests` must be `true`. + AllowRebase *bool `json:"allow_rebase,omitempty"` + // either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits. `has_pull_requests` must be `true`. + AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` + // either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`. + AllowSquash *bool `json:"allow_squash_merge,omitempty"` + // set to `true` to archive this repository. + Archived *bool `json:"archived,omitempty"` + // set to a string like `8h30m0s` to set the mirror interval time + MirrorInterval *string `json:"mirror_interval,omitempty"` + // either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`. + AllowManualMerge *bool `json:"allow_manual_merge,omitempty"` + // either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur. + AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"` + // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`. + DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"` + // set to `true` to archive this repository. +} + +// EditRepo edit the properties of a repository +func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s", owner, reponame), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} + +// DeleteRepo deletes a repository of user or organization. +func (c *Client) DeleteRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil) + return resp, err +} + +// MirrorSync adds a mirrored repository to the mirror sync queue. +func (c *Client) MirrorSync(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil) + return resp, err +} + +// GetRepoLanguages return language stats of a repo +func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + langMap := make(map[string]int64) + + data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil) + if err != nil { + return nil, resp, err + } + if err = json.Unmarshal(data, &langMap); err != nil { + return nil, resp, err + } + return langMap, resp, nil +} + +// ArchiveType represent supported archive formats by gitea +type ArchiveType string + +const ( + // ZipArchive represent zip format + ZipArchive ArchiveType = ".zip" + // TarGZArchive represent tar.gz format + TarGZArchive ArchiveType = ".tar.gz" +) + +// GetArchive get an archive of a repository by git reference +// e.g.: ref -> master, 70b7c74b33, v1.2.1, ... +func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + ref = pathEscapeSegments(ref) + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil) +} + +// GetArchiveReader gets a `git archive` for a particular tree-ish git reference +// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag +// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is +// the responsibility of the client to close the reader. +func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + ref = pathEscapeSegments(ref) + resp, err := c.doRequest("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil) + if err != nil { + return nil, resp, err + } + + if _, err := statusCodeToErr(resp); err != nil { + return nil, resp, err + } + + return resp.Body, resp, nil +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_branch.go b/vendor/code.gitea.io/sdk/gitea/repo_branch.go new file mode 100644 index 0000000..0b7e873 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_branch.go @@ -0,0 +1,143 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// PayloadUser represents the author or committer of a commit +type PayloadUser struct { + // Full name of the commit author + Name string `json:"name"` + Email string `json:"email"` + UserName string `json:"username"` +} + +// PayloadCommit represents a commit +type PayloadCommit struct { + // sha1 hash of the commit + ID string `json:"id"` + Message string `json:"message"` + URL string `json:"url"` + Author *PayloadUser `json:"author"` + Committer *PayloadUser `json:"committer"` + Verification *PayloadCommitVerification `json:"verification"` + Timestamp time.Time `json:"timestamp"` + Added []string `json:"added"` + Removed []string `json:"removed"` + Modified []string `json:"modified"` +} + +// PayloadCommitVerification represents the GPG verification of a commit +type PayloadCommitVerification struct { + Verified bool `json:"verified"` + Reason string `json:"reason"` + Signature string `json:"signature"` + Payload string `json:"payload"` +} + +// Branch represents a repository branch +type Branch struct { + Name string `json:"name"` + Commit *PayloadCommit `json:"commit"` + Protected bool `json:"protected"` + RequiredApprovals int64 `json:"required_approvals"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + UserCanPush bool `json:"user_can_push"` + UserCanMerge bool `json:"user_can_merge"` + EffectiveBranchProtectionName string `json:"effective_branch_protection_name"` +} + +// ListRepoBranchesOptions options for listing a repository's branches +type ListRepoBranchesOptions struct { + ListOptions +} + +// ListRepoBranches list all the branches of one repository +func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + branches := make([]*Branch, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches) + return branches, resp, err +} + +// GetRepoBranch get one branch's information of one repository +func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil { + return nil, nil, err + } + b := new(Branch) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b) + if err != nil { + return nil, resp, err + } + return b, resp, nil +} + +// DeleteRepoBranch delete a branch in a repository +func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil { + return false, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil) + if err != nil { + return false, resp, err + } + return status == 204, resp, nil +} + +// CreateBranchOption options when creating a branch in a repository +type CreateBranchOption struct { + // Name of the branch to create + BranchName string `json:"new_branch_name"` + // Name of the old branch to create from (optional) + OldBranchName string `json:"old_branch_name"` +} + +// Validate the CreateBranchOption struct +func (opt CreateBranchOption) Validate() error { + if len(opt.BranchName) == 0 { + return fmt.Errorf("BranchName is empty") + } + if len(opt.BranchName) > 100 { + return fmt.Errorf("BranchName to long") + } + if len(opt.OldBranchName) > 100 { + return fmt.Errorf("OldBranchName to long") + } + return nil +} + +// CreateBranch creates a branch for a user's repository +func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + branch := new(Branch) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branches", owner, repo), jsonHeader, bytes.NewReader(body), branch) + return branch, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go b/vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go new file mode 100644 index 0000000..22bd7b9 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_branch_protection.go @@ -0,0 +1,168 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// BranchProtection represents a branch protection for a repository +type BranchProtection struct { + BranchName string `json:"branch_name"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int64 `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateBranchProtectionOption options for creating a branch protection +type CreateBranchProtectionOption struct { + BranchName string `json:"branch_name"` + EnablePush bool `json:"enable_push"` + EnablePushWhitelist bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals int64 `json:"required_approvals"` + EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` + DismissStaleApprovals bool `json:"dismiss_stale_approvals"` + RequireSignedCommits bool `json:"require_signed_commits"` + ProtectedFilePatterns string `json:"protected_file_patterns"` +} + +// EditBranchProtectionOption options for editing a branch protection +type EditBranchProtectionOption struct { + EnablePush *bool `json:"enable_push"` + EnablePushWhitelist *bool `json:"enable_push_whitelist"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"` + EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + EnableStatusCheck *bool `json:"enable_status_check"` + StatusCheckContexts []string `json:"status_check_contexts"` + RequiredApprovals *int64 `json:"required_approvals"` + EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` + ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"` + BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"` + BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` + DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` + RequireSignedCommits *bool `json:"require_signed_commits"` + ProtectedFilePatterns *string `json:"protected_file_patterns"` +} + +// ListBranchProtectionsOptions list branch protection options +type ListBranchProtectionsOptions struct { + ListOptions +} + +// ListBranchProtections list branch protections for a repo +func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bps := make([]*BranchProtection, 0, opt.PageSize) + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo)) + link.RawQuery = opt.getURLQuery().Encode() + resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &bps) + return bps, resp, err +} + +// GetBranchProtection gets a branch protection +func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bp := new(BranchProtection) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil, bp) + return bp, resp, err +} + +// CreateBranchProtection creates a branch protection for a repo +func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bp := new(BranchProtection) + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo), jsonHeader, bytes.NewReader(body), bp) + return bp, resp, err +} + +// EditBranchProtection edits a branch protection for a repo +func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + bp := new(BranchProtection) + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), bp) + return bp, resp, err +} + +// DeleteBranchProtection deletes a branch protection for a repo +func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go b/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go new file mode 100644 index 0000000..c766250 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go @@ -0,0 +1,136 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListCollaboratorsOptions options for listing a repository's collaborators +type ListCollaboratorsOptions struct { + ListOptions +} + +// ListCollaborators list a repository's collaborators +func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + collaborators := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/collaborators?%s", user, repo, opt.getURLQuery().Encode()), + nil, nil, &collaborators) + return collaborators, resp, err +} + +// IsCollaborator check if a user is a collaborator of a repository +func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil) + if err != nil { + return false, resp, err + } + if status == 204 { + return true, resp, nil + } + return false, resp, nil +} + +// AddCollaboratorOption options when adding a user as a collaborator of a repository +type AddCollaboratorOption struct { + Permission *AccessMode `json:"permission"` +} + +// AccessMode represent the grade of access you have to something +type AccessMode string + +const ( + // AccessModeNone no access + AccessModeNone AccessMode = "none" + // AccessModeRead read access + AccessModeRead AccessMode = "read" + // AccessModeWrite write access + AccessModeWrite AccessMode = "write" + // AccessModeAdmin admin access + AccessModeAdmin AccessMode = "admin" + // AccessModeOwner owner + AccessModeOwner AccessMode = "owner" +) + +// Validate the AddCollaboratorOption struct +func (opt AddCollaboratorOption) Validate() error { + if opt.Permission != nil { + if *opt.Permission == AccessModeOwner { + *opt.Permission = AccessModeAdmin + return nil + } + if *opt.Permission == AccessModeNone { + opt.Permission = nil + return nil + } + if *opt.Permission != AccessModeRead && *opt.Permission != AccessModeWrite && *opt.Permission != AccessModeAdmin { + return fmt.Errorf("permission mode invalid") + } + } + return nil +} + +// AddCollaborator add some user as a collaborator of a repository +func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { + return nil, err + } + if err := opt.Validate(); err != nil { + return nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// DeleteCollaborator remove a collaborator from a repository +func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil) + return resp, err +} + +// GetReviewers return all users that can be requested to review in this repo +func (c *Client) GetReviewers(user, repo string) ([]*User, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + reviewers := make([]*User, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/reviewers", user, repo), nil, nil, &reviewers) + return reviewers, resp, err +} + +// GetAssignees return all users that have write access and can be assigned to issues +func (c *Client) GetAssignees(user, repo string) ([]*User, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + assignees := make([]*User, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/assignees", user, repo), nil, nil, &assignees) + return assignees, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_commit.go b/vendor/code.gitea.io/sdk/gitea/repo_commit.go new file mode 100644 index 0000000..9bb65b3 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_commit.go @@ -0,0 +1,101 @@ +// Copyright 2018 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "time" +) + +// Identity for a person's identity like an author or committer +type Identity struct { + Name string `json:"name"` + Email string `json:"email"` +} + +// CommitMeta contains meta information of a commit in terms of API. +type CommitMeta struct { + URL string `json:"url"` + SHA string `json:"sha"` + Created time.Time `json:"created"` +} + +// CommitUser contains information of a user in the context of a commit. +type CommitUser struct { + Identity + Date string `json:"date"` +} + +// RepoCommit contains information of a commit in the context of a repository. +type RepoCommit struct { + URL string `json:"url"` + Author *CommitUser `json:"author"` + Committer *CommitUser `json:"committer"` + Message string `json:"message"` + Tree *CommitMeta `json:"tree"` +} + +// Commit contains information generated from a Git commit. +type Commit struct { + *CommitMeta + HTMLURL string `json:"html_url"` + RepoCommit *RepoCommit `json:"commit"` + Author *User `json:"author"` + Committer *User `json:"committer"` + Parents []*CommitMeta `json:"parents"` + Files []*CommitAffectedFiles `json:"files"` +} + +// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE +type CommitDateOptions struct { + Author time.Time `json:"author"` + Committer time.Time `json:"committer"` +} + +// CommitAffectedFiles store information about files affected by the commit +type CommitAffectedFiles struct { + Filename string `json:"filename"` +} + +// GetSingleCommit returns a single commit +func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &commitID); err != nil { + return nil, nil, err + } + commit := new(Commit) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit) + return commit, resp, err +} + +// ListCommitOptions list commit options +type ListCommitOptions struct { + ListOptions + //SHA or branch to start listing commits from (usually 'master') + SHA string +} + +// QueryEncode turns options into querystring argument +func (opt *ListCommitOptions) QueryEncode() string { + query := opt.ListOptions.getURLQuery() + if opt.SHA != "" { + query.Add("sha", opt.SHA) + } + return query.Encode() +} + +// ListRepoCommits return list of commits from a repo +func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo)) + opt.setDefaults() + commits := make([]*Commit, 0, opt.PageSize) + link.RawQuery = opt.QueryEncode() + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) + return commits, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_file.go b/vendor/code.gitea.io/sdk/gitea/repo_file.go new file mode 100644 index 0000000..6f99ea0 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_file.go @@ -0,0 +1,247 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" +) + +// FileOptions options for all file APIs +type FileOptions struct { + // message (optional) for the commit of this file. if not supplied, a default message will be used + Message string `json:"message"` + // branch (optional) to base this file from. if not given, the default branch is used + BranchName string `json:"branch"` + // new_branch (optional) will make a new branch from `branch` before creating the file + NewBranchName string `json:"new_branch"` + // `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) + Author Identity `json:"author"` + Committer Identity `json:"committer"` + Dates CommitDateOptions `json:"dates"` + // Add a Signed-off-by trailer by the committer at the end of the commit log message. + Signoff bool `json:"signoff"` +} + +// CreateFileOptions options for creating files +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type CreateFileOptions struct { + FileOptions + // content must be base64 encoded + // required: true + Content string `json:"content"` +} + +// DeleteFileOptions options for deleting files (used for other File structs below) +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type DeleteFileOptions struct { + FileOptions + // sha is the SHA for the file that already exists + // required: true + SHA string `json:"sha"` +} + +// UpdateFileOptions options for updating files +// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) +type UpdateFileOptions struct { + FileOptions + // sha is the SHA for the file that already exists + // required: true + SHA string `json:"sha"` + // content must be base64 encoded + // required: true + Content string `json:"content"` + // from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL + FromPath string `json:"from_path"` +} + +// FileLinksResponse contains the links for a repo's file +type FileLinksResponse struct { + Self *string `json:"self"` + GitURL *string `json:"git"` + HTMLURL *string `json:"html"` +} + +// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content +type ContentsResponse struct { + Name string `json:"name"` + Path string `json:"path"` + SHA string `json:"sha"` + // `type` will be `file`, `dir`, `symlink`, or `submodule` + Type string `json:"type"` + Size int64 `json:"size"` + // `encoding` is populated when `type` is `file`, otherwise null + Encoding *string `json:"encoding"` + // `content` is populated when `type` is `file`, otherwise null + Content *string `json:"content"` + // `target` is populated when `type` is `symlink`, otherwise null + Target *string `json:"target"` + URL *string `json:"url"` + HTMLURL *string `json:"html_url"` + GitURL *string `json:"git_url"` + DownloadURL *string `json:"download_url"` + // `submodule_git_url` is populated when `type` is `submodule`, otherwise null + SubmoduleGitURL *string `json:"submodule_git_url"` + Links *FileLinksResponse `json:"_links"` +} + +// FileCommitResponse contains information generated from a Git commit for a repo's file. +type FileCommitResponse struct { + CommitMeta + HTMLURL string `json:"html_url"` + Author *CommitUser `json:"author"` + Committer *CommitUser `json:"committer"` + Parents []*CommitMeta `json:"parents"` + Message string `json:"message"` + Tree *CommitMeta `json:"tree"` +} + +// FileResponse contains information about a repo's file +type FileResponse struct { + Content *ContentsResponse `json:"content"` + Commit *FileCommitResponse `json:"commit"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// FileDeleteResponse contains information about a repo's file that was deleted +type FileDeleteResponse struct { + Content interface{} `json:"content"` // to be set to nil + Commit *FileCommitResponse `json:"commit"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// GetFile downloads a file of repository, ref can be branch/tag/commit. +// e.g.: ref -> master, filepath -> README.md (no leading slash) +func (c *Client) GetFile(owner, repo, ref, filepath string) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + filepath = pathEscapeSegments(filepath) + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + ref = pathEscapeSegments(ref) + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil) + } + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil) +} + +// GetContents get the metadata and contents of a file in a repository +// ref is optional +func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) { + data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath) + if err != nil { + return nil, resp, err + } + cr := new(ContentsResponse) + if json.Unmarshal(data, &cr) != nil { + return nil, resp, fmt.Errorf("expect file, got directory") + } + return cr, resp, err +} + +// ListContents gets a list of entries in a dir +// ref is optional +func (c *Client) ListContents(owner, repo, ref, filepath string) ([]*ContentsResponse, *Response, error) { + data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath) + if err != nil { + return nil, resp, err + } + crl := make([]*ContentsResponse, 0) + if json.Unmarshal(data, &crl) != nil { + return nil, resp, fmt.Errorf("expect directory, got file") + } + return crl, resp, err +} + +func (c *Client) getDirOrFileContents(owner, repo, ref, filepath string) ([]byte, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + filepath = pathEscapeSegments(strings.TrimPrefix(filepath, "/")) + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), jsonHeader, nil) +} + +// CreateFile create a file in a repository +func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) { + var err error + if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + filepath = pathEscapeSegments(filepath) + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + fr := new(FileResponse) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) + return fr, resp, err +} + +// UpdateFile update a file in a repository +func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions) (*FileResponse, *Response, error) { + var err error + if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { + return nil, nil, err + } + + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + filepath = pathEscapeSegments(filepath) + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + fr := new(FileResponse) + resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) + return fr, resp, err +} + +// DeleteFile delete a file from repository +func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions) (*Response, error) { + var err error + if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + filepath = pathEscapeSegments(filepath) + + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body)) + if err != nil { + return resp, err + } + if status != 200 && status != 204 { + return resp, fmt.Errorf("unexpected Status: %d", status) + } + return resp, nil +} + +func (c *Client) setDefaultBranchForOldVersions(owner, repo, branch string) (string, error) { + if len(branch) == 0 { + // Gitea >= 1.12.0 Use DefaultBranch on "", mimic this for older versions + if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil { + r, _, err := c.GetRepo(owner, repo) + if err != nil { + return "", err + } + return r.DefaultBranch, nil + } + } + return branch, nil +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_key.go b/vendor/code.gitea.io/sdk/gitea/repo_key.go new file mode 100644 index 0000000..ee2ff40 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_key.go @@ -0,0 +1,91 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// DeployKey a deploy key +type DeployKey struct { + ID int64 `json:"id"` + KeyID int64 `json:"key_id"` + Key string `json:"key"` + URL string `json:"url"` + Title string `json:"title"` + Fingerprint string `json:"fingerprint"` + Created time.Time `json:"created_at"` + ReadOnly bool `json:"read_only"` + Repository *Repository `json:"repository,omitempty"` +} + +// ListDeployKeysOptions options for listing a repository's deploy keys +type ListDeployKeysOptions struct { + ListOptions + KeyID int64 + Fingerprint string +} + +// QueryEncode turns options into querystring argument +func (opt *ListDeployKeysOptions) QueryEncode() string { + query := opt.getURLQuery() + if opt.KeyID > 0 { + query.Add("key_id", fmt.Sprintf("%d", opt.KeyID)) + } + if len(opt.Fingerprint) > 0 { + query.Add("fingerprint", opt.Fingerprint) + } + return query.Encode() +} + +// ListDeployKeys list all the deploy keys of one repository +func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo)) + opt.setDefaults() + link.RawQuery = opt.QueryEncode() + keys := make([]*DeployKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &keys) + return keys, resp, err +} + +// GetDeployKey get one deploy key with key id +func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + key := new(DeployKey) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key) + return key, resp, err +} + +// CreateDeployKey options when create one deploy key +func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(DeployKey) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/keys", user, repo), jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// DeleteDeployKey delete deploy key with key id +func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_migrate.go b/vendor/code.gitea.io/sdk/gitea/repo_migrate.go new file mode 100644 index 0000000..cd0fe44 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_migrate.go @@ -0,0 +1,132 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// GitServiceType represents a git service +type GitServiceType string + +const ( + // GitServicePlain represents a plain git service + GitServicePlain GitServiceType = "git" + //GitServiceGithub represents github.com + GitServiceGithub GitServiceType = "github" + // GitServiceGitlab represents a gitlab service + GitServiceGitlab GitServiceType = "gitlab" + // GitServiceGitea represents a gitea service + GitServiceGitea GitServiceType = "gitea" + // GitServiceGogs represents a gogs service + GitServiceGogs GitServiceType = "gogs" +) + +// MigrateRepoOption options for migrating a repository from an external service +type MigrateRepoOption struct { + RepoName string `json:"repo_name"` + RepoOwner string `json:"repo_owner"` + // deprecated use RepoOwner + RepoOwnerID int64 `json:"uid"` + CloneAddr string `json:"clone_addr"` + Service GitServiceType `json:"service"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + AuthToken string `json:"auth_token"` + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description"` + Wiki bool `json:"wiki"` + Milestones bool `json:"milestones"` + Labels bool `json:"labels"` + Issues bool `json:"issues"` + PullRequests bool `json:"pull_requests"` + Releases bool `json:"releases"` + MirrorInterval string `json:"mirror_interval"` + LFS bool `json:"lfs"` + LFSEndpoint string `json:"lfs_endpoint"` +} + +// Validate the MigrateRepoOption struct +func (opt *MigrateRepoOption) Validate(c *Client) error { + // check user options + if len(opt.CloneAddr) == 0 { + return fmt.Errorf("CloneAddr required") + } + if len(opt.RepoName) == 0 { + return fmt.Errorf("RepoName required") + } else if len(opt.RepoName) > 100 { + return fmt.Errorf("RepoName to long") + } + if len(opt.Description) > 255 { + return fmt.Errorf("Description to long") + } + switch opt.Service { + case GitServiceGithub: + if len(opt.AuthToken) == 0 { + return fmt.Errorf("github requires token authentication") + } + case GitServiceGitlab, GitServiceGitea: + if len(opt.AuthToken) == 0 { + return fmt.Errorf("%s requires token authentication", opt.Service) + } + // Gitlab is supported since 1.12.0 but api cant handle it until 1.13.0 + // https://github.com/go-gitea/gitea/pull/12672 + if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { + return fmt.Errorf("migrate from service %s need gitea >= 1.13.0", opt.Service) + } + case GitServiceGogs: + if len(opt.AuthToken) == 0 { + return fmt.Errorf("gogs requires token authentication") + } + if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { + return fmt.Errorf("migrate from service gogs need gitea >= 1.14.0") + } + } + return nil +} + +// MigrateRepo migrates a repository from other Git hosting sources for the authenticated user. +// +// To migrate a repository for a organization, the authenticated user must be a +// owner of the specified organization. +func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, *Response, error) { + if err := opt.Validate(c); err != nil { + return nil, nil, err + } + + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + if len(opt.AuthToken) != 0 { + // gitea <= 1.12 dont understand AuthToken + opt.AuthUsername = opt.AuthToken + opt.AuthPassword, opt.AuthToken = "", "" + } + if len(opt.RepoOwner) != 0 { + // gitea <= 1.12 dont understand RepoOwner + u, _, err := c.GetUserInfo(opt.RepoOwner) + if err != nil { + return nil, nil, err + } + opt.RepoOwnerID = u.ID + } else if opt.RepoOwnerID == 0 { + // gitea <= 1.12 require RepoOwnerID + u, _, err := c.GetMyUserInfo() + if err != nil { + return nil, nil, err + } + opt.RepoOwnerID = u.ID + } + } + + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", "/repos/migrate", jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_refs.go b/vendor/code.gitea.io/sdk/gitea/repo_refs.go new file mode 100644 index 0000000..c954a80 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_refs.go @@ -0,0 +1,78 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +// Reference represents a Git reference. +type Reference struct { + Ref string `json:"ref"` + URL string `json:"url"` + Object *GitObject `json:"object"` +} + +// GitObject represents a Git object. +type GitObject struct { + Type string `json:"type"` + SHA string `json:"sha"` + URL string `json:"url"` +} + +// GetRepoRef get one ref's information of one repository +func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + ref = strings.TrimPrefix(ref, "refs/") + ref = pathEscapeSegments(ref) + r := new(Reference) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil, &r) + if _, ok := err.(*json.UnmarshalTypeError); ok { + // Multiple refs + return nil, resp, errors.New("no exact match found for this ref") + } else if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// GetRepoRefs get list of ref's information of one repository +func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + ref = strings.TrimPrefix(ref, "refs/") + ref = pathEscapeSegments(ref) + + data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil) + if err != nil { + return nil, resp, err + } + + // Attempt to unmarshal single returned ref. + r := new(Reference) + refErr := json.Unmarshal(data, r) + if refErr == nil { + return []*Reference{r}, resp, nil + } + + // Attempt to unmarshal multiple refs. + var rs []*Reference + refsErr := json.Unmarshal(data, &rs) + if refsErr == nil { + if len(rs) == 0 { + return nil, resp, errors.New("unexpected response: an array of refs with length 0") + } + return rs, resp, nil + } + + return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", refErr, refsErr) +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_stars.go b/vendor/code.gitea.io/sdk/gitea/repo_stars.go new file mode 100644 index 0000000..01243c2 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_stars.go @@ -0,0 +1,96 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" +) + +// ListStargazersOptions options for listing a repository's stargazers +type ListStargazersOptions struct { + ListOptions +} + +// ListRepoStargazers list a repository's stargazers +func (c *Client) ListRepoStargazers(user, repo string, opt ListStargazersOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + stargazers := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/stargazers?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &stargazers) + return stargazers, resp, err +} + +// GetStarredRepos returns the repos that the given user has starred +func (c *Client) GetStarredRepos(user string) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/starred", user), jsonHeader, nil, &repos) + return repos, resp, err +} + +// GetMyStarredRepos returns the repos that the authenticated user has starred +func (c *Client) GetMyStarredRepos() ([]*Repository, *Response, error) { + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", "/user/starred", jsonHeader, nil, &repos) + return repos, resp, err +} + +// IsRepoStarring returns whether the authenticated user has starred the repo or not +func (c *Client) IsRepoStarring(user, repo string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return false, nil, err + } + _, resp, err := c.getResponse("GET", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil) + if resp != nil { + switch resp.StatusCode { + case http.StatusNotFound: + return false, resp, nil + case http.StatusNoContent: + return true, resp, nil + default: + return false, resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode) + } + } + return false, nil, err +} + +// StarRepo star specified repo as the authenticated user +func (c *Client) StarRepo(user, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil) + if resp != nil { + switch resp.StatusCode { + case http.StatusNoContent: + return resp, nil + default: + return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode) + } + } + return nil, err +} + +// UnStarRepo remove star to specified repo as the authenticated user +func (c *Client) UnStarRepo(user, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil) + if resp != nil { + switch resp.StatusCode { + case http.StatusNoContent: + return resp, nil + default: + return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode) + } + } + return nil, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_tag.go b/vendor/code.gitea.io/sdk/gitea/repo_tag.go new file mode 100644 index 0000000..7317d3f --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_tag.go @@ -0,0 +1,130 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Tag represents a repository tag +type Tag struct { + Name string `json:"name"` + Message string `json:"message"` + ID string `json:"id"` + Commit *CommitMeta `json:"commit"` + ZipballURL string `json:"zipball_url"` + TarballURL string `json:"tarball_url"` +} + +// AnnotatedTag represents an annotated tag +type AnnotatedTag struct { + Tag string `json:"tag"` + SHA string `json:"sha"` + URL string `json:"url"` + Message string `json:"message"` + Tagger *CommitUser `json:"tagger"` + Object *AnnotatedTagObject `json:"object"` + Verification *PayloadCommitVerification `json:"verification"` +} + +// AnnotatedTagObject contains meta information of the tag object +type AnnotatedTagObject struct { + Type string `json:"type"` + URL string `json:"url"` + SHA string `json:"sha"` +} + +// ListRepoTagsOptions options for listing a repository's tags +type ListRepoTagsOptions struct { + ListOptions +} + +// ListRepoTags list all the branches of one repository +func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + tags := make([]*Tag, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags) + return tags, resp, err +} + +// GetTag get the tag of a repository +func (c *Client) GetTag(user, repo, tag string) (*Tag, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil { + return nil, nil, err + } + t := new(Tag) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), nil, nil, &t) + return t, resp, err +} + +// GetAnnotatedTag get the tag object of an annotated tag (not lightweight tags) of a repository +func (c *Client) GetAnnotatedTag(user, repo, sha string) (*AnnotatedTag, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil { + return nil, nil, err + } + t := new(AnnotatedTag) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/tags/%s", user, repo, sha), nil, nil, &t) + return t, resp, err +} + +// CreateTagOption options when creating a tag +type CreateTagOption struct { + TagName string `json:"tag_name"` + Message string `json:"message"` + Target string `json:"target"` +} + +// Validate validates CreateTagOption +func (opt CreateTagOption) Validate() error { + if len(opt.TagName) == 0 { + return fmt.Errorf("TagName is required") + } + return nil +} + +// CreateTag create a new git tag in a repository +func (c *Client) CreateTag(user, repo string, opt CreateTagOption) (*Tag, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(opt) + if err != nil { + return nil, nil, err + } + t := new(Tag) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/tags", user, repo), jsonHeader, bytes.NewReader(body), &t) + return t, resp, err +} + +// DeleteTag deletes a tag from a repository, if no release refers to it +func (c *Client) DeleteTag(user, repo, tag string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil { + return nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), + nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_team.go b/vendor/code.gitea.io/sdk/gitea/repo_team.go new file mode 100644 index 0000000..b983d87 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_team.go @@ -0,0 +1,65 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" +) + +// GetRepoTeams return teams from a repository +func (c *Client) GetRepoTeams(user, repo string) ([]*Team, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + teams := make([]*Team, 0, 5) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams", user, repo), nil, nil, &teams) + return teams, resp, err +} + +// AddRepoTeam add a team to a repository +func (c *Client) AddRepoTeam(user, repo, team string) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &team); err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil) + return resp, err +} + +// RemoveRepoTeam delete a team from a repository +func (c *Client) RemoveRepoTeam(user, repo, team string) (*Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &team); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil) + return resp, err +} + +// CheckRepoTeam check if team is assigned to repo by name and return it. +// If not assigned, it will return nil. +func (c *Client) CheckRepoTeam(user, repo, team string) (*Team, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + if err := escapeValidatePathSegments(&user, &repo, &team); err != nil { + return nil, nil, err + } + t := new(Team) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil, &t) + if resp != nil && resp.StatusCode == http.StatusNotFound { + // if not found it's not an error, it indicates it's not assigned + return nil, resp, nil + } + return t, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_template.go b/vendor/code.gitea.io/sdk/gitea/repo_template.go new file mode 100644 index 0000000..8b689be --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_template.go @@ -0,0 +1,65 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// CreateRepoFromTemplateOption options when creating repository using a template +type CreateRepoFromTemplateOption struct { + // Owner is the organization or person who will own the new repository + Owner string `json:"owner"` + // Name of the repository to create + Name string `json:"name"` + // Description of the repository to create + Description string `json:"description"` + // Private is whether the repository is private + Private bool `json:"private"` + // GitContent include git content of default branch in template repo + GitContent bool `json:"git_content"` + // Topics include topics of template repo + Topics bool `json:"topics"` + // GitHooks include git hooks of template repo + GitHooks bool `json:"git_hooks"` + // Webhooks include webhooks of template repo + Webhooks bool `json:"webhooks"` + // Avatar include avatar of the template repo + Avatar bool `json:"avatar"` + // Labels include labels of template repo + Labels bool `json:"labels"` +} + +// Validate validates CreateRepoFromTemplateOption +func (opt CreateRepoFromTemplateOption) Validate() error { + if len(opt.Owner) == 0 { + return fmt.Errorf("field Owner is required") + } + if len(opt.Name) == 0 { + return fmt.Errorf("field Name is required") + } + return nil +} + +// CreateRepoFromTemplate create a repository using a template +func (c *Client) CreateRepoFromTemplate(templateOwner, templateRepo string, opt CreateRepoFromTemplateOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&templateOwner, &templateRepo); err != nil { + return nil, nil, err + } + + if err := opt.Validate(); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/generate", templateOwner, templateRepo), jsonHeader, bytes.NewReader(body), &repo) + return repo, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_topics.go b/vendor/code.gitea.io/sdk/gitea/repo_topics.go new file mode 100644 index 0000000..92f2228 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_topics.go @@ -0,0 +1,68 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListRepoTopicsOptions options for listing repo's topics +type ListRepoTopicsOptions struct { + ListOptions +} + +// topicsList represents a list of repo's topics +type topicsList struct { + Topics []string `json:"topics"` +} + +// ListRepoTopics list all repository's topics +func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, nil, err + } + opt.setDefaults() + + list := new(topicsList) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/topics?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, list) + if err != nil { + return nil, resp, err + } + return list.Topics, resp, nil +} + +// SetRepoTopics replaces the list of repo's topics +func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo); err != nil { + return nil, err + } + l := topicsList{Topics: list} + body, err := json.Marshal(&l) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics", user, repo), jsonHeader, bytes.NewReader(body)) + return resp, err +} + +// AddRepoTopic adds a topic to a repo's topics list +func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil) + return resp, err +} + +// DeleteRepoTopic deletes a topic from repo's topics list +func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_transfer.go b/vendor/code.gitea.io/sdk/gitea/repo_transfer.go new file mode 100644 index 0000000..be06010 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_transfer.go @@ -0,0 +1,36 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// TransferRepoOption options when transfer a repository's ownership +type TransferRepoOption struct { + // required: true + NewOwner string `json:"new_owner"` + // ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories. + TeamIDs *[]int64 `json:"team_ids"` +} + +// TransferRepo transfers the ownership of a repository +func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) { + if err := escapeValidatePathSegments(&owner, &reponame); err != nil { + return nil, nil, err + } + if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + repo := new(Repository) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo) + return repo, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_tree.go b/vendor/code.gitea.io/sdk/gitea/repo_tree.go new file mode 100644 index 0000000..452394a --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_tree.go @@ -0,0 +1,44 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" +) + +// GitEntry represents a git tree +type GitEntry struct { + Path string `json:"path"` + Mode string `json:"mode"` + Type string `json:"type"` + Size int64 `json:"size"` + SHA string `json:"sha"` + URL string `json:"url"` +} + +// GitTreeResponse returns a git tree +type GitTreeResponse struct { + SHA string `json:"sha"` + URL string `json:"url"` + Entries []GitEntry `json:"tree"` + Truncated bool `json:"truncated"` + Page int `json:"page"` + TotalCount int `json:"total_count"` +} + +// GetTrees downloads a file of repository, ref can be branch/tag/commit. +// e.g.: ref -> master, tree -> macaron.go(no leading slash) +func (c *Client) GetTrees(user, repo, ref string, recursive bool) (*GitTreeResponse, *Response, error) { + if err := escapeValidatePathSegments(&user, &repo, &ref); err != nil { + return nil, nil, err + } + trees := new(GitTreeResponse) + var path = fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref) + if recursive { + path += "?recursive=1" + } + resp, err := c.getParsedResponse("GET", path, nil, nil, trees) + return trees, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_watch.go b/vendor/code.gitea.io/sdk/gitea/repo_watch.go new file mode 100644 index 0000000..f499aff --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_watch.go @@ -0,0 +1,87 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "time" +) + +// WatchInfo represents an API watch status of one repository +type WatchInfo struct { + Subscribed bool `json:"subscribed"` + Ignored bool `json:"ignored"` + Reason interface{} `json:"reason"` + CreatedAt time.Time `json:"created_at"` + URL string `json:"url"` + RepositoryURL string `json:"repository_url"` +} + +// GetWatchedRepos list all the watched repos of user +func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), nil, nil, &repos) + return repos, resp, err +} + +// GetMyWatchedRepos list repositories watched by the authenticated user +func (c *Client) GetMyWatchedRepos() ([]*Repository, *Response, error) { + repos := make([]*Repository, 0, 10) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/subscriptions"), nil, nil, &repos) + return repos, resp, err +} + +// CheckRepoWatch check if the current user is watching a repo +func (c *Client) CheckRepoWatch(owner, repo string) (bool, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return false, nil, err + } + status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil) + if err != nil { + return false, resp, err + } + switch status { + case http.StatusNotFound: + return false, resp, nil + case http.StatusOK: + return true, resp, nil + default: + return false, resp, fmt.Errorf("unexpected Status: %d", status) + } +} + +// WatchRepo start to watch a repository +func (c *Client) WatchRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusOK { + return resp, nil + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} + +// UnWatchRepo stop to watch a repository +func (c *Client) UnWatchRepo(owner, repo string) (*Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, err + } + status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil) + if err != nil { + return resp, err + } + if status == http.StatusNoContent { + return resp, nil + } + return resp, fmt.Errorf("unexpected Status: %d", status) +} diff --git a/vendor/code.gitea.io/sdk/gitea/settings.go b/vendor/code.gitea.io/sdk/gitea/settings.go new file mode 100644 index 0000000..fb94248 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/settings.go @@ -0,0 +1,78 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +// GlobalUISettings represent the global ui settings of a gitea instance witch is exposed by API +type GlobalUISettings struct { + DefaultTheme string `json:"default_theme"` + AllowedReactions []string `json:"allowed_reactions"` + CustomEmojis []string `json:"custom_emojis"` +} + +// GlobalRepoSettings represent the global repository settings of a gitea instance witch is exposed by API +type GlobalRepoSettings struct { + MirrorsDisabled bool `json:"mirrors_disabled"` + HTTPGitDisabled bool `json:"http_git_disabled"` + MigrationsDisabled bool `json:"migrations_disabled"` + StarsDisabled bool `json:"stars_disabled"` + TimeTrackingDisabled bool `json:"time_tracking_disabled"` + LFSDisabled bool `json:"lfs_disabled"` +} + +// GlobalAPISettings contains global api settings exposed by it +type GlobalAPISettings struct { + MaxResponseItems int `json:"max_response_items"` + DefaultPagingNum int `json:"default_paging_num"` + DefaultGitTreesPerPage int `json:"default_git_trees_per_page"` + DefaultMaxBlobSize int64 `json:"default_max_blob_size"` +} + +// GlobalAttachmentSettings contains global Attachment settings exposed by API +type GlobalAttachmentSettings struct { + Enabled bool `json:"enabled"` + AllowedTypes string `json:"allowed_types"` + MaxSize int64 `json:"max_size"` + MaxFiles int `json:"max_files"` +} + +// GetGlobalUISettings get global ui settings witch are exposed by API +func (c *Client) GetGlobalUISettings() (*GlobalUISettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalUISettings) + resp, err := c.getParsedResponse("GET", "/settings/ui", jsonHeader, nil, &conf) + return conf, resp, err +} + +// GetGlobalRepoSettings get global repository settings witch are exposed by API +func (c *Client) GetGlobalRepoSettings() (*GlobalRepoSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalRepoSettings) + resp, err := c.getParsedResponse("GET", "/settings/repository", jsonHeader, nil, &conf) + return conf, resp, err +} + +// GetGlobalAPISettings get global api settings witch are exposed by it +func (c *Client) GetGlobalAPISettings() (*GlobalAPISettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalAPISettings) + resp, err := c.getParsedResponse("GET", "/settings/api", jsonHeader, nil, &conf) + return conf, resp, err +} + +// GetGlobalAttachmentSettings get global repository settings witch are exposed by API +func (c *Client) GetGlobalAttachmentSettings() (*GlobalAttachmentSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, nil, err + } + conf := new(GlobalAttachmentSettings) + resp, err := c.getParsedResponse("GET", "/settings/attachment", jsonHeader, nil, &conf) + return conf, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/status.go b/vendor/code.gitea.io/sdk/gitea/status.go new file mode 100644 index 0000000..fe5d971 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/status.go @@ -0,0 +1,108 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "time" +) + +// StatusState holds the state of a Status +// It can be "pending", "success", "error", "failure", and "warning" +type StatusState string + +const ( + // StatusPending is for when the Status is Pending + StatusPending StatusState = "pending" + // StatusSuccess is for when the Status is Success + StatusSuccess StatusState = "success" + // StatusError is for when the Status is Error + StatusError StatusState = "error" + // StatusFailure is for when the Status is Failure + StatusFailure StatusState = "failure" + // StatusWarning is for when the Status is Warning + StatusWarning StatusState = "warning" +) + +// Status holds a single Status of a single Commit +type Status struct { + ID int64 `json:"id"` + State StatusState `json:"status"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + URL string `json:"url"` + Context string `json:"context"` + Creator *User `json:"creator"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CreateStatusOption holds the information needed to create a new Status for a Commit +type CreateStatusOption struct { + State StatusState `json:"state"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + Context string `json:"context"` +} + +// CreateStatus creates a new Status for a given Commit +func (c *Client) CreateStatus(owner, repo, sha string, opts CreateStatusOption) (*Status, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opts) + if err != nil { + return nil, nil, err + } + status := new(Status) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, url.QueryEscape(sha)), jsonHeader, bytes.NewReader(body), status) + return status, resp, err +} + +// ListStatusesOption options for listing a repository's commit's statuses +type ListStatusesOption struct { + ListOptions +} + +// ListStatuses returns all statuses for a given Commit by ref +func (c *Client) ListStatuses(owner, repo, ref string, opt ListStatusesOption) ([]*Status, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil { + return nil, nil, err + } + opt.setDefaults() + statuses := make([]*Status, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?%s", owner, repo, ref, opt.getURLQuery().Encode()), jsonHeader, nil, &statuses) + return statuses, resp, err +} + +// CombinedStatus holds the combined state of several statuses for a single commit +type CombinedStatus struct { + State StatusState `json:"state"` + SHA string `json:"sha"` + TotalCount int `json:"total_count"` + Statuses []*Status `json:"statuses"` + Repository *Repository `json:"repository"` + CommitURL string `json:"commit_url"` + URL string `json:"url"` +} + +// GetCombinedStatus returns the CombinedStatus for a given Commit +func (c *Client) GetCombinedStatus(owner, repo, ref string) (*CombinedStatus, *Response, error) { + if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil { + return nil, nil, err + } + status := new(CombinedStatus) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, ref), jsonHeader, nil, status) + + // gitea api return empty body if nothing here jet + if resp != nil && resp.StatusCode == 200 && err != nil { + return status, resp, nil + } + + return status, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user.go b/vendor/code.gitea.io/sdk/gitea/user.go new file mode 100644 index 0000000..c37627b --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user.go @@ -0,0 +1,90 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" + "strconv" + "time" +) + +// User represents a user +type User struct { + // the user's id + ID int64 `json:"id"` + // the user's username + UserName string `json:"login"` + // the user's full name + FullName string `json:"full_name"` + Email string `json:"email"` + // URL to the user's avatar + AvatarURL string `json:"avatar_url"` + // User locale + Language string `json:"language"` + // Is the user an administrator + IsAdmin bool `json:"is_admin"` + // Date and Time of last login + LastLogin time.Time `json:"last_login"` + // Date and Time of user creation + Created time.Time `json:"created"` + // Is user restricted + Restricted bool `json:"restricted"` + // Is user active + IsActive bool `json:"active"` + // Is user login prohibited + ProhibitLogin bool `json:"prohibit_login"` + // the user's location + Location string `json:"location"` + // the user's website + Website string `json:"website"` + // the user's description + Description string `json:"description"` + // User visibility level option + Visibility VisibleType `json:"visibility"` + + // user counts + FollowerCount int `json:"followers_count"` + FollowingCount int `json:"following_count"` + StarredRepoCount int `json:"starred_repos_count"` +} + +// GetUserInfo get user info by user's name +func (c *Client) GetUserInfo(user string) (*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + u := new(User) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u) + return u, resp, err +} + +// GetMyUserInfo get user info of current user +func (c *Client) GetMyUserInfo() (*User, *Response, error) { + u := new(User) + resp, err := c.getParsedResponse("GET", "/user", nil, nil, u) + return u, resp, err +} + +// GetUserByID returns user by a given user ID +func (c *Client) GetUserByID(id int64) (*User, *Response, error) { + if id < 0 { + return nil, nil, fmt.Errorf("invalid user id %d", id) + } + + query := make(url.Values) + query.Add("uid", strconv.FormatInt(id, 10)) + users, resp, err := c.searchUsers(query.Encode()) + + if err != nil { + return nil, resp, err + } + + if len(users) == 1 { + return users[0], resp, err + } + + return nil, resp, fmt.Errorf("user not found with id %d", id) +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_app.go b/vendor/code.gitea.io/sdk/gitea/user_app.go new file mode 100644 index 0000000..88b01ed --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_app.go @@ -0,0 +1,90 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "reflect" +) + +// AccessToken represents an API access token. +type AccessToken struct { + ID int64 `json:"id"` + Name string `json:"name"` + Token string `json:"sha1"` + TokenLastEight string `json:"token_last_eight"` +} + +// ListAccessTokensOptions options for listing a users's access tokens +type ListAccessTokensOptions struct { + ListOptions +} + +// ListAccessTokens lists all the access tokens of user +func (c *Client) ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, *Response, error) { + c.mutex.RLock() + username := c.username + c.mutex.RUnlock() + if len(username) == 0 { + return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") + } + opts.setDefaults() + tokens := make([]*AccessToken, 0, opts.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens?%s", url.PathEscape(username), opts.getURLQuery().Encode()), jsonHeader, nil, &tokens) + return tokens, resp, err +} + +// CreateAccessTokenOption options when create access token +type CreateAccessTokenOption struct { + Name string `json:"name"` +} + +// CreateAccessToken create one access token with options +func (c *Client) CreateAccessToken(opt CreateAccessTokenOption) (*AccessToken, *Response, error) { + c.mutex.RLock() + username := c.username + c.mutex.RUnlock() + if len(username) == 0 { + return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + t := new(AccessToken) + resp, err := c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", url.PathEscape(username)), jsonHeader, bytes.NewReader(body), t) + return t, resp, err +} + +// DeleteAccessToken delete token, identified by ID and if not available by name +func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) { + c.mutex.RLock() + username := c.username + c.mutex.RUnlock() + if len(username) == 0 { + return nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") + } + + var token = "" + + switch reflect.ValueOf(value).Kind() { + case reflect.Int64: + token = fmt.Sprintf("%d", value.(int64)) + case reflect.String: + if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { + return nil, err + } + token = value.(string) + default: + return nil, fmt.Errorf("only string and int64 supported") + } + + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/users/%s/tokens/%s", url.PathEscape(username), url.PathEscape(token)), jsonHeader, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_email.go b/vendor/code.gitea.io/sdk/gitea/user_email.go new file mode 100644 index 0000000..4962b08 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_email.go @@ -0,0 +1,64 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Email an email address belonging to a user +type Email struct { + Email string `json:"email"` + Verified bool `json:"verified"` + Primary bool `json:"primary"` +} + +// ListEmailsOptions options for listing current's user emails +type ListEmailsOptions struct { + ListOptions +} + +// ListEmails all the email addresses of user +func (c *Client) ListEmails(opt ListEmailsOptions) ([]*Email, *Response, error) { + opt.setDefaults() + emails := make([]*Email, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/emails?%s", opt.getURLQuery().Encode()), nil, nil, &emails) + return emails, resp, err +} + +// CreateEmailOption options when creating email addresses +type CreateEmailOption struct { + // email addresses to add + Emails []string `json:"emails"` +} + +// AddEmail add one email to current user with options +func (c *Client) AddEmail(opt CreateEmailOption) ([]*Email, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + emails := make([]*Email, 0, 3) + resp, err := c.getParsedResponse("POST", "/user/emails", jsonHeader, bytes.NewReader(body), &emails) + return emails, resp, err +} + +// DeleteEmailOption options when deleting email addresses +type DeleteEmailOption struct { + // email addresses to delete + Emails []string `json:"emails"` +} + +// DeleteEmail delete one email of current users' +func (c *Client) DeleteEmail(opt DeleteEmailOption) (*Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", "/user/emails", jsonHeader, bytes.NewReader(body)) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_follow.go b/vendor/code.gitea.io/sdk/gitea/user_follow.go new file mode 100644 index 0000000..7bd340c --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_follow.go @@ -0,0 +1,93 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import "fmt" + +// ListFollowersOptions options for listing followers +type ListFollowersOptions struct { + ListOptions +} + +// ListMyFollowers list all the followers of current user +func (c *Client) ListMyFollowers(opt ListFollowersOptions) ([]*User, *Response, error) { + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/followers?%s", opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// ListFollowers list all the followers of one user +func (c *Client) ListFollowers(user string, opt ListFollowersOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/followers?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// ListFollowingOptions options for listing a user's users being followed +type ListFollowingOptions struct { + ListOptions +} + +// ListMyFollowing list all the users current user followed +func (c *Client) ListMyFollowing(opt ListFollowingOptions) ([]*User, *Response, error) { + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/following?%s", opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// ListFollowing list all the users the user followed +func (c *Client) ListFollowing(user string, opt ListFollowingOptions) ([]*User, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + users := make([]*User, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/following?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) + return users, resp, err +} + +// IsFollowing if current user followed the target +func (c *Client) IsFollowing(target string) (bool, *Response) { + if err := escapeValidatePathSegments(&target); err != nil { + // ToDo return err + return false, nil + } + _, resp, err := c.getResponse("GET", fmt.Sprintf("/user/following/%s", target), nil, nil) + return err == nil, resp +} + +// IsUserFollowing if the user followed the target +func (c *Client) IsUserFollowing(user, target string) (bool, *Response) { + if err := escapeValidatePathSegments(&user, &target); err != nil { + // ToDo return err + return false, nil + } + _, resp, err := c.getResponse("GET", fmt.Sprintf("/users/%s/following/%s", user, target), nil, nil) + return err == nil, resp +} + +// Follow set current user follow the target +func (c *Client) Follow(target string) (*Response, error) { + if err := escapeValidatePathSegments(&target); err != nil { + return nil, err + } + _, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/following/%s", target), nil, nil) + return resp, err +} + +// Unfollow set current user unfollow the target +func (c *Client) Unfollow(target string) (*Response, error) { + if err := escapeValidatePathSegments(&target); err != nil { + return nil, err + } + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/following/%s", target), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go new file mode 100644 index 0000000..6c1b9d1 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go @@ -0,0 +1,89 @@ +// Copyright 2017 Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// GPGKey a user GPG key to sign commit and tag in repository +type GPGKey struct { + ID int64 `json:"id"` + PrimaryKeyID string `json:"primary_key_id"` + KeyID string `json:"key_id"` + PublicKey string `json:"public_key"` + Emails []*GPGKeyEmail `json:"emails"` + SubsKey []*GPGKey `json:"subkeys"` + CanSign bool `json:"can_sign"` + CanEncryptComms bool `json:"can_encrypt_comms"` + CanEncryptStorage bool `json:"can_encrypt_storage"` + CanCertify bool `json:"can_certify"` + Created time.Time `json:"created_at,omitempty"` + Expires time.Time `json:"expires_at,omitempty"` +} + +// GPGKeyEmail an email attached to a GPGKey +type GPGKeyEmail struct { + Email string `json:"email"` + Verified bool `json:"verified"` +} + +// ListGPGKeysOptions options for listing a user's GPGKeys +type ListGPGKeysOptions struct { + ListOptions +} + +// ListGPGKeys list all the GPG keys of the user +func (c *Client) ListGPGKeys(user string, opt ListGPGKeysOptions) ([]*GPGKey, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + keys := make([]*GPGKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// ListMyGPGKeys list all the GPG keys of current user +func (c *Client) ListMyGPGKeys(opt *ListGPGKeysOptions) ([]*GPGKey, *Response, error) { + opt.setDefaults() + keys := make([]*GPGKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// GetGPGKey get current user's GPG key by key id +func (c *Client) GetGPGKey(keyID int64) (*GPGKey, *Response, error) { + key := new(GPGKey) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil, &key) + return key, resp, err +} + +// CreateGPGKeyOption options create user GPG key +type CreateGPGKeyOption struct { + // An armored GPG key to add + // + ArmoredKey string `json:"armored_public_key"` +} + +// CreateGPGKey create GPG key with options +func (c *Client) CreateGPGKey(opt CreateGPGKeyOption) (*GPGKey, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(GPGKey) + resp, err := c.getParsedResponse("POST", "/user/gpg_keys", jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// DeleteGPGKey delete GPG key with key id +func (c *Client) DeleteGPGKey(keyID int64) (*Response, error) { + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_key.go b/vendor/code.gitea.io/sdk/gitea/user_key.go new file mode 100644 index 0000000..02795ba --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_key.go @@ -0,0 +1,83 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// PublicKey publickey is a user key to push code to repository +type PublicKey struct { + ID int64 `json:"id"` + Key string `json:"key"` + URL string `json:"url,omitempty"` + Title string `json:"title,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + Created time.Time `json:"created_at,omitempty"` + Owner *User `json:"user,omitempty"` + ReadOnly bool `json:"read_only,omitempty"` + KeyType string `json:"key_type,omitempty"` +} + +// ListPublicKeysOptions options for listing a user's PublicKeys +type ListPublicKeysOptions struct { + ListOptions +} + +// ListPublicKeys list all the public keys of the user +func (c *Client) ListPublicKeys(user string, opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { + if err := escapeValidatePathSegments(&user); err != nil { + return nil, nil, err + } + opt.setDefaults() + keys := make([]*PublicKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// ListMyPublicKeys list all the public keys of current user +func (c *Client) ListMyPublicKeys(opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { + opt.setDefaults() + keys := make([]*PublicKey, 0, opt.PageSize) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) + return keys, resp, err +} + +// GetPublicKey get current user's public key by key id +func (c *Client) GetPublicKey(keyID int64) (*PublicKey, *Response, error) { + key := new(PublicKey) + resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys/%d", keyID), nil, nil, &key) + return key, resp, err +} + +// CreateKeyOption options when creating a key +type CreateKeyOption struct { + // Title of the key to add + Title string `json:"title"` + // An armored SSH key to add + Key string `json:"key"` + // Describe if the key has only read access or read/write + ReadOnly bool `json:"read_only"` +} + +// CreatePublicKey create public key with options +func (c *Client) CreatePublicKey(opt CreateKeyOption) (*PublicKey, *Response, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + key := new(PublicKey) + resp, err := c.getParsedResponse("POST", "/user/keys", jsonHeader, bytes.NewReader(body), key) + return key, resp, err +} + +// DeletePublicKey delete public key with key id +func (c *Client) DeletePublicKey(keyID int64) (*Response, error) { + _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/keys/%d", keyID), nil, nil) + return resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_search.go b/vendor/code.gitea.io/sdk/gitea/user_search.go new file mode 100644 index 0000000..5ea0c45 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_search.go @@ -0,0 +1,48 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/url" +) + +type searchUsersResponse struct { + Users []*User `json:"data"` +} + +// SearchUsersOption options for SearchUsers +type SearchUsersOption struct { + ListOptions + KeyWord string +} + +// QueryEncode turns options into querystring argument +func (opt *SearchUsersOption) QueryEncode() string { + query := make(url.Values) + if opt.Page > 0 { + query.Add("page", fmt.Sprintf("%d", opt.Page)) + } + if opt.PageSize > 0 { + query.Add("limit", fmt.Sprintf("%d", opt.PageSize)) + } + if len(opt.KeyWord) > 0 { + query.Add("q", opt.KeyWord) + } + return query.Encode() +} + +func (c *Client) searchUsers(rawQuery string) ([]*User, *Response, error) { + link, _ := url.Parse("/users/search") + link.RawQuery = rawQuery + userResp := new(searchUsersResponse) + resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &userResp) + return userResp.Users, resp, err +} + +// SearchUsers finds users by query +func (c *Client) SearchUsers(opt SearchUsersOption) ([]*User, *Response, error) { + return c.searchUsers(opt.QueryEncode()) +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_settings.go b/vendor/code.gitea.io/sdk/gitea/user_settings.go new file mode 100644 index 0000000..494cab3 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_settings.go @@ -0,0 +1,62 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" +) + +// UserSettings represents user settings +type UserSettings struct { + FullName string `json:"full_name"` + Website string `json:"website"` + Description string `json:"description"` + Location string `json:"location"` + Language string `json:"language"` + Theme string `json:"theme"` + DiffViewStyle string `json:"diff_view_style"` + // Privacy + HideEmail bool `json:"hide_email"` + HideActivity bool `json:"hide_activity"` +} + +// UserSettingsOptions represents options to change user settings +type UserSettingsOptions struct { + FullName *string `json:"full_name,omitempty"` + Website *string `json:"website,omitempty"` + Description *string `json:"description,omitempty"` + Location *string `json:"location,omitempty"` + Language *string `json:"language,omitempty"` + Theme *string `json:"theme,omitempty"` + DiffViewStyle *string `json:"diff_view_style,omitempty"` + // Privacy + HideEmail *bool `json:"hide_email,omitempty"` + HideActivity *bool `json:"hide_activity,omitempty"` +} + +// GetUserSettings returns user settings +func (c *Client) GetUserSettings() (*UserSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + userConfig := new(UserSettings) + resp, err := c.getParsedResponse("GET", "/user/settings", nil, nil, userConfig) + return userConfig, resp, err +} + +// UpdateUserSettings returns user settings +func (c *Client) UpdateUserSettings(opt UserSettingsOptions) (*UserSettings, *Response, error) { + if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil { + return nil, nil, err + } + body, err := json.Marshal(&opt) + if err != nil { + return nil, nil, err + } + userConfig := new(UserSettings) + resp, err := c.getParsedResponse("PATCH", "/user/settings", jsonHeader, bytes.NewReader(body), userConfig) + return userConfig, resp, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/version.go b/vendor/code.gitea.io/sdk/gitea/version.go new file mode 100644 index 0000000..1f41c77 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/version.go @@ -0,0 +1,101 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + + "github.com/hashicorp/go-version" +) + +// ServerVersion returns the version of the server +func (c *Client) ServerVersion() (string, *Response, error) { + var v = struct { + Version string `json:"version"` + }{} + resp, err := c.getParsedResponse("GET", "/version", nil, nil, &v) + return v.Version, resp, err +} + +// CheckServerVersionConstraint validates that the login's server satisfies a +// given version constraint such as ">= 1.11.0+dev" +func (c *Client) CheckServerVersionConstraint(constraint string) error { + if err := c.loadServerVersion(); err != nil { + return err + } + + check, err := version.NewConstraint(constraint) + if err != nil { + return err + } + if !check.Check(c.serverVersion) { + c.mutex.RLock() + url := c.url + c.mutex.RUnlock() + return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", url, constraint) + } + return nil +} + +// SetGiteaVersion configures the Client to assume the given version of the +// Gitea server, instead of querying the server for it when initializing. +// Use "" to skip all canonical ways in the SDK to check for versions +func SetGiteaVersion(v string) ClientOption { + if v == "" { + return func(c *Client) error { + c.ignoreVersion = true + return nil + } + } + return func(c *Client) (err error) { + c.getVersionOnce.Do(func() { + c.serverVersion, err = version.NewVersion(v) + return + }) + return + } +} + +// predefined versions only have to be parsed by library once +var ( + version1_11_0, _ = version.NewVersion("1.11.0") + version1_12_0, _ = version.NewVersion("1.12.0") + version1_13_0, _ = version.NewVersion("1.13.0") + version1_14_0, _ = version.NewVersion("1.14.0") + version1_15_0, _ = version.NewVersion("1.15.0") +) + +// checkServerVersionGreaterThanOrEqual is the canonical way in the SDK to check for versions for API compatibility reasons +func (c *Client) checkServerVersionGreaterThanOrEqual(v *version.Version) error { + if c.ignoreVersion { + return nil + } + if err := c.loadServerVersion(); err != nil { + return err + } + + if !c.serverVersion.GreaterThanOrEqual(v) { + c.mutex.RLock() + url := c.url + c.mutex.RUnlock() + return fmt.Errorf("gitea server at %s is older than %s", url, v.Original()) + } + return nil +} + +// loadServerVersion init the serverVersion variable +func (c *Client) loadServerVersion() (err error) { + c.getVersionOnce.Do(func() { + raw, _, err2 := c.ServerVersion() + if err2 != nil { + err = err2 + return + } + if c.serverVersion, err = version.NewVersion(raw); err != nil { + return + } + }) + return +} diff --git a/vendor/github.com/hashicorp/go-version/LICENSE b/vendor/github.com/hashicorp/go-version/LICENSE new file mode 100644 index 0000000..c33dcc7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-version/LICENSE @@ -0,0 +1,354 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + diff --git a/vendor/github.com/hashicorp/go-version/README.md b/vendor/github.com/hashicorp/go-version/README.md new file mode 100644 index 0000000..851a337 --- /dev/null +++ b/vendor/github.com/hashicorp/go-version/README.md @@ -0,0 +1,66 @@ +# Versioning Library for Go +[![Build Status](https://circleci.com/gh/hashicorp/go-version/tree/master.svg?style=svg)](https://circleci.com/gh/hashicorp/go-version/tree/master) +[![GoDoc](https://godoc.org/github.com/hashicorp/go-version?status.svg)](https://godoc.org/github.com/hashicorp/go-version) + +go-version is a library for parsing versions and version constraints, +and verifying versions against a set of constraints. go-version +can sort a collection of versions properly, handles prerelease/beta +versions, can increment versions, etc. + +Versions used with go-version must follow [SemVer](http://semver.org/). + +## Installation and Usage + +Package documentation can be found on +[GoDoc](http://godoc.org/github.com/hashicorp/go-version). + +Installation can be done with a normal `go get`: + +``` +$ go get github.com/hashicorp/go-version +``` + +#### Version Parsing and Comparison + +```go +v1, err := version.NewVersion("1.2") +v2, err := version.NewVersion("1.5+metadata") + +// Comparison example. There is also GreaterThan, Equal, and just +// a simple Compare that returns an int allowing easy >=, <=, etc. +if v1.LessThan(v2) { + fmt.Printf("%s is less than %s", v1, v2) +} +``` + +#### Version Constraints + +```go +v1, err := version.NewVersion("1.2") + +// Constraints example. +constraints, err := version.NewConstraint(">= 1.0, < 1.4") +if constraints.Check(v1) { + fmt.Printf("%s satisfies constraints %s", v1, constraints) +} +``` + +#### Version Sorting + +```go +versionsRaw := []string{"1.1", "0.7.1", "1.4-beta", "1.4", "2"} +versions := make([]*version.Version, len(versionsRaw)) +for i, raw := range versionsRaw { + v, _ := version.NewVersion(raw) + versions[i] = v +} + +// After this, the versions are properly sorted +sort.Sort(version.Collection(versions)) +``` + +## Issues and Contributing + +If you find an issue with this library, please report an issue. If you'd +like, we welcome any contributions. Fork this library and submit a pull +request. diff --git a/vendor/github.com/hashicorp/go-version/constraint.go b/vendor/github.com/hashicorp/go-version/constraint.go new file mode 100644 index 0000000..d055759 --- /dev/null +++ b/vendor/github.com/hashicorp/go-version/constraint.go @@ -0,0 +1,204 @@ +package version + +import ( + "fmt" + "reflect" + "regexp" + "strings" +) + +// Constraint represents a single constraint for a version, such as +// ">= 1.0". +type Constraint struct { + f constraintFunc + check *Version + original string +} + +// Constraints is a slice of constraints. We make a custom type so that +// we can add methods to it. +type Constraints []*Constraint + +type constraintFunc func(v, c *Version) bool + +var constraintOperators map[string]constraintFunc + +var constraintRegexp *regexp.Regexp + +func init() { + constraintOperators = map[string]constraintFunc{ + "": constraintEqual, + "=": constraintEqual, + "!=": constraintNotEqual, + ">": constraintGreaterThan, + "<": constraintLessThan, + ">=": constraintGreaterThanEqual, + "<=": constraintLessThanEqual, + "~>": constraintPessimistic, + } + + ops := make([]string, 0, len(constraintOperators)) + for k := range constraintOperators { + ops = append(ops, regexp.QuoteMeta(k)) + } + + constraintRegexp = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + strings.Join(ops, "|"), + VersionRegexpRaw)) +} + +// NewConstraint will parse one or more constraints from the given +// constraint string. The string must be a comma-separated list of +// constraints. +func NewConstraint(v string) (Constraints, error) { + vs := strings.Split(v, ",") + result := make([]*Constraint, len(vs)) + for i, single := range vs { + c, err := parseSingle(single) + if err != nil { + return nil, err + } + + result[i] = c + } + + return Constraints(result), nil +} + +// Check tests if a version satisfies all the constraints. +func (cs Constraints) Check(v *Version) bool { + for _, c := range cs { + if !c.Check(v) { + return false + } + } + + return true +} + +// Returns the string format of the constraints +func (cs Constraints) String() string { + csStr := make([]string, len(cs)) + for i, c := range cs { + csStr[i] = c.String() + } + + return strings.Join(csStr, ",") +} + +// Check tests if a constraint is validated by the given version. +func (c *Constraint) Check(v *Version) bool { + return c.f(v, c.check) +} + +func (c *Constraint) String() string { + return c.original +} + +func parseSingle(v string) (*Constraint, error) { + matches := constraintRegexp.FindStringSubmatch(v) + if matches == nil { + return nil, fmt.Errorf("Malformed constraint: %s", v) + } + + check, err := NewVersion(matches[2]) + if err != nil { + return nil, err + } + + return &Constraint{ + f: constraintOperators[matches[1]], + check: check, + original: v, + }, nil +} + +func prereleaseCheck(v, c *Version) bool { + switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; { + case cPre && vPre: + // A constraint with a pre-release can only match a pre-release version + // with the same base segments. + return reflect.DeepEqual(c.Segments64(), v.Segments64()) + + case !cPre && vPre: + // A constraint without a pre-release can only match a version without a + // pre-release. + return false + + case cPre && !vPre: + // OK, except with the pessimistic operator + case !cPre && !vPre: + // OK + } + return true +} + +//------------------------------------------------------------------- +// Constraint functions +//------------------------------------------------------------------- + +func constraintEqual(v, c *Version) bool { + return v.Equal(c) +} + +func constraintNotEqual(v, c *Version) bool { + return !v.Equal(c) +} + +func constraintGreaterThan(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) == 1 +} + +func constraintLessThan(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) == -1 +} + +func constraintGreaterThanEqual(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) >= 0 +} + +func constraintLessThanEqual(v, c *Version) bool { + return prereleaseCheck(v, c) && v.Compare(c) <= 0 +} + +func constraintPessimistic(v, c *Version) bool { + // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases + if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") { + return false + } + + // If the version being checked is naturally less than the constraint, then there + // is no way for the version to be valid against the constraint + if v.LessThan(c) { + return false + } + // We'll use this more than once, so grab the length now so it's a little cleaner + // to write the later checks + cs := len(c.segments) + + // If the version being checked has less specificity than the constraint, then there + // is no way for the version to be valid against the constraint + if cs > len(v.segments) { + return false + } + + // Check the segments in the constraint against those in the version. If the version + // being checked, at any point, does not have the same values in each index of the + // constraints segments, then it cannot be valid against the constraint. + for i := 0; i < c.si-1; i++ { + if v.segments[i] != c.segments[i] { + return false + } + } + + // Check the last part of the segment in the constraint. If the version segment at + // this index is less than the constraints segment at this index, then it cannot + // be valid against the constraint + if c.segments[cs-1] > v.segments[cs-1] { + return false + } + + // If nothing has rejected the version by now, it's valid + return true +} diff --git a/vendor/github.com/hashicorp/go-version/version.go b/vendor/github.com/hashicorp/go-version/version.go new file mode 100644 index 0000000..09703e8 --- /dev/null +++ b/vendor/github.com/hashicorp/go-version/version.go @@ -0,0 +1,384 @@ +package version + +import ( + "bytes" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" +) + +// The compiled regular expression used to test the validity of a version. +var ( + versionRegexp *regexp.Regexp + semverRegexp *regexp.Regexp +) + +// The raw regular expression string used for testing the validity +// of a version. +const ( + VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + + `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + + `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + + `?` + + // SemverRegexpRaw requires a separator between version and prerelease + SemverRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + + `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + + `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + + `?` +) + +// Version represents a single version. +type Version struct { + metadata string + pre string + segments []int64 + si int + original string +} + +func init() { + versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") + semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$") +} + +// NewVersion parses the given version and returns a new +// Version. +func NewVersion(v string) (*Version, error) { + return newVersion(v, versionRegexp) +} + +// NewSemver parses the given version and returns a new +// Version that adheres strictly to SemVer specs +// https://semver.org/ +func NewSemver(v string) (*Version, error) { + return newVersion(v, semverRegexp) +} + +func newVersion(v string, pattern *regexp.Regexp) (*Version, error) { + matches := pattern.FindStringSubmatch(v) + if matches == nil { + return nil, fmt.Errorf("Malformed version: %s", v) + } + segmentsStr := strings.Split(matches[1], ".") + segments := make([]int64, len(segmentsStr)) + si := 0 + for i, str := range segmentsStr { + val, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return nil, fmt.Errorf( + "Error parsing version: %s", err) + } + + segments[i] = int64(val) + si++ + } + + // Even though we could support more than three segments, if we + // got less than three, pad it with 0s. This is to cover the basic + // default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum + for i := len(segments); i < 3; i++ { + segments = append(segments, 0) + } + + pre := matches[7] + if pre == "" { + pre = matches[4] + } + + return &Version{ + metadata: matches[10], + pre: pre, + segments: segments, + si: si, + original: v, + }, nil +} + +// Must is a helper that wraps a call to a function returning (*Version, error) +// and panics if error is non-nil. +func Must(v *Version, err error) *Version { + if err != nil { + panic(err) + } + + return v +} + +// Compare compares this version to another version. This +// returns -1, 0, or 1 if this version is smaller, equal, +// or larger than the other version, respectively. +// +// If you want boolean results, use the LessThan, Equal, +// GreaterThan, GreaterThanOrEqual or LessThanOrEqual methods. +func (v *Version) Compare(other *Version) int { + // A quick, efficient equality check + if v.String() == other.String() { + return 0 + } + + segmentsSelf := v.Segments64() + segmentsOther := other.Segments64() + + // If the segments are the same, we must compare on prerelease info + if reflect.DeepEqual(segmentsSelf, segmentsOther) { + preSelf := v.Prerelease() + preOther := other.Prerelease() + if preSelf == "" && preOther == "" { + return 0 + } + if preSelf == "" { + return 1 + } + if preOther == "" { + return -1 + } + + return comparePrereleases(preSelf, preOther) + } + + // Get the highest specificity (hS), or if they're equal, just use segmentSelf length + lenSelf := len(segmentsSelf) + lenOther := len(segmentsOther) + hS := lenSelf + if lenSelf < lenOther { + hS = lenOther + } + // Compare the segments + // Because a constraint could have more/less specificity than the version it's + // checking, we need to account for a lopsided or jagged comparison + for i := 0; i < hS; i++ { + if i > lenSelf-1 { + // This means Self had the lower specificity + // Check to see if the remaining segments in Other are all zeros + if !allZero(segmentsOther[i:]) { + // if not, it means that Other has to be greater than Self + return -1 + } + break + } else if i > lenOther-1 { + // this means Other had the lower specificity + // Check to see if the remaining segments in Self are all zeros - + if !allZero(segmentsSelf[i:]) { + //if not, it means that Self has to be greater than Other + return 1 + } + break + } + lhs := segmentsSelf[i] + rhs := segmentsOther[i] + if lhs == rhs { + continue + } else if lhs < rhs { + return -1 + } + // Otherwis, rhs was > lhs, they're not equal + return 1 + } + + // if we got this far, they're equal + return 0 +} + +func allZero(segs []int64) bool { + for _, s := range segs { + if s != 0 { + return false + } + } + return true +} + +func comparePart(preSelf string, preOther string) int { + if preSelf == preOther { + return 0 + } + + var selfInt int64 + selfNumeric := true + selfInt, err := strconv.ParseInt(preSelf, 10, 64) + if err != nil { + selfNumeric = false + } + + var otherInt int64 + otherNumeric := true + otherInt, err = strconv.ParseInt(preOther, 10, 64) + if err != nil { + otherNumeric = false + } + + // if a part is empty, we use the other to decide + if preSelf == "" { + if otherNumeric { + return -1 + } + return 1 + } + + if preOther == "" { + if selfNumeric { + return 1 + } + return -1 + } + + if selfNumeric && !otherNumeric { + return -1 + } else if !selfNumeric && otherNumeric { + return 1 + } else if !selfNumeric && !otherNumeric && preSelf > preOther { + return 1 + } else if selfInt > otherInt { + return 1 + } + + return -1 +} + +func comparePrereleases(v string, other string) int { + // the same pre release! + if v == other { + return 0 + } + + // split both pre releases for analyse their parts + selfPreReleaseMeta := strings.Split(v, ".") + otherPreReleaseMeta := strings.Split(other, ".") + + selfPreReleaseLen := len(selfPreReleaseMeta) + otherPreReleaseLen := len(otherPreReleaseMeta) + + biggestLen := otherPreReleaseLen + if selfPreReleaseLen > otherPreReleaseLen { + biggestLen = selfPreReleaseLen + } + + // loop for parts to find the first difference + for i := 0; i < biggestLen; i = i + 1 { + partSelfPre := "" + if i < selfPreReleaseLen { + partSelfPre = selfPreReleaseMeta[i] + } + + partOtherPre := "" + if i < otherPreReleaseLen { + partOtherPre = otherPreReleaseMeta[i] + } + + compare := comparePart(partSelfPre, partOtherPre) + // if parts are equals, continue the loop + if compare != 0 { + return compare + } + } + + return 0 +} + +// Equal tests if two versions are equal. +func (v *Version) Equal(o *Version) bool { + if v == nil || o == nil { + return v == o + } + + return v.Compare(o) == 0 +} + +// GreaterThan tests if this version is greater than another version. +func (v *Version) GreaterThan(o *Version) bool { + return v.Compare(o) > 0 +} + +// GreaterThanOrEqual tests if this version is greater than or equal to another version. +func (v *Version) GreaterThanOrEqual(o *Version) bool { + return v.Compare(o) >= 0 +} + +// LessThan tests if this version is less than another version. +func (v *Version) LessThan(o *Version) bool { + return v.Compare(o) < 0 +} + +// LessThanOrEqual tests if this version is less than or equal to another version. +func (v *Version) LessThanOrEqual(o *Version) bool { + return v.Compare(o) <= 0 +} + +// Metadata returns any metadata that was part of the version +// string. +// +// Metadata is anything that comes after the "+" in the version. +// For example, with "1.2.3+beta", the metadata is "beta". +func (v *Version) Metadata() string { + return v.metadata +} + +// Prerelease returns any prerelease data that is part of the version, +// or blank if there is no prerelease data. +// +// Prerelease information is anything that comes after the "-" in the +// version (but before any metadata). For example, with "1.2.3-beta", +// the prerelease information is "beta". +func (v *Version) Prerelease() string { + return v.pre +} + +// Segments returns the numeric segments of the version as a slice of ints. +// +// This excludes any metadata or pre-release information. For example, +// for a version "1.2.3-beta", segments will return a slice of +// 1, 2, 3. +func (v *Version) Segments() []int { + segmentSlice := make([]int, len(v.segments)) + for i, v := range v.segments { + segmentSlice[i] = int(v) + } + return segmentSlice +} + +// Segments64 returns the numeric segments of the version as a slice of int64s. +// +// This excludes any metadata or pre-release information. For example, +// for a version "1.2.3-beta", segments will return a slice of +// 1, 2, 3. +func (v *Version) Segments64() []int64 { + result := make([]int64, len(v.segments)) + copy(result, v.segments) + return result +} + +// String returns the full version string included pre-release +// and metadata information. +// +// This value is rebuilt according to the parsed segments and other +// information. Therefore, ambiguities in the version string such as +// prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and +// missing parts (1.0 => 1.0.0) will be made into a canonicalized form +// as shown in the parenthesized examples. +func (v *Version) String() string { + var buf bytes.Buffer + fmtParts := make([]string, len(v.segments)) + for i, s := range v.segments { + // We can ignore err here since we've pre-parsed the values in segments + str := strconv.FormatInt(s, 10) + fmtParts[i] = str + } + fmt.Fprintf(&buf, strings.Join(fmtParts, ".")) + if v.pre != "" { + fmt.Fprintf(&buf, "-%s", v.pre) + } + if v.metadata != "" { + fmt.Fprintf(&buf, "+%s", v.metadata) + } + + return buf.String() +} + +// Original returns the original parsed version as-is, including any +// potential whitespace, `v` prefix, etc. +func (v *Version) Original() string { + return v.original +} diff --git a/vendor/github.com/hashicorp/go-version/version_collection.go b/vendor/github.com/hashicorp/go-version/version_collection.go new file mode 100644 index 0000000..cc888d4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-version/version_collection.go @@ -0,0 +1,17 @@ +package version + +// Collection is a type that implements the sort.Interface interface +// so that versions can be sorted. +type Collection []*Version + +func (v Collection) Len() int { + return len(v) +} + +func (v Collection) Less(i, j int) bool { + return v[i].LessThan(v[j]) +} + +func (v Collection) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} diff --git a/vendor/github.com/sethvargo/go-envconfig/AUTHORS b/vendor/github.com/sethvargo/go-envconfig/AUTHORS new file mode 100644 index 0000000..2107f4a --- /dev/null +++ b/vendor/github.com/sethvargo/go-envconfig/AUTHORS @@ -0,0 +1,8 @@ +# This is the list of envconfig authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. + +Google LLC +Seth Vargo diff --git a/vendor/github.com/sethvargo/go-envconfig/LICENSE b/vendor/github.com/sethvargo/go-envconfig/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/sethvargo/go-envconfig/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/sethvargo/go-envconfig/Makefile b/vendor/github.com/sethvargo/go-envconfig/Makefile new file mode 100644 index 0000000..b0b140b --- /dev/null +++ b/vendor/github.com/sethvargo/go-envconfig/Makefile @@ -0,0 +1,32 @@ +# Copyright The envconfig Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +test: + @go test \ + -count=1 \ + -short \ + -timeout=5m \ + -vet="${VETTERS}" \ + ./... +.PHONY: test + +test-acc: + @go test \ + -count=1 \ + -race \ + -shuffle=on \ + -timeout=10m \ + -vet="${VETTERS}" \ + ./... +.PHONY: test-acc diff --git a/vendor/github.com/sethvargo/go-envconfig/README.md b/vendor/github.com/sethvargo/go-envconfig/README.md new file mode 100644 index 0000000..fe817a9 --- /dev/null +++ b/vendor/github.com/sethvargo/go-envconfig/README.md @@ -0,0 +1,405 @@ +# Envconfig + +[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/mod/github.com/sethvargo/go-envconfig) +[![GitHub Actions](https://img.shields.io/github/workflow/status/sethvargo/go-envconfig/unit/main?style=flat-square)](https://github.com/sethvargo/go-envconfig/actions?query=branch%3Amain+-event%3Aschedule) + +Envconfig populates struct field values based on environment variables or +arbitrary lookup functions. It supports pre-setting mutations, which is useful +for things like converting values to uppercase, trimming whitespace, or looking +up secrets. + +**Note:** Versions prior to v0.2 used a different import path. This README and +examples are for v0.2+. + +## Usage + +Define a struct with fields using the `env` tag: + +```go +type MyConfig struct { + Port int `env:"PORT"` + Username string `env:"USERNAME"` +} +``` + +Set some environment variables: + +```sh +export PORT=5555 +export USERNAME=yoyo +``` + +Process it using envconfig: + +```go +package main + +import ( + "context" + "log" + + "github.com/sethvargo/go-envconfig" +) + +func main() { + ctx := context.Background() + + var c MyConfig + if err := envconfig.Process(ctx, &c); err != nil { + log.Fatal(err) + } + + // c.Port = 5555 + // c.Username = "yoyo" +} +``` + +You can also use nested structs, just remember that any fields you want to +process must be public: + +```go +type MyConfig struct { + Database *DatabaseConfig +} + +type DatabaseConfig struct { + Port int `env:"PORT"` + Username string `env:"USERNAME"` +} +``` + +## Configuration + +Use the `env` struct tag to define configuration. + +### Overwrite + +If overwrite is set, the value will be overwritten if there is an +environment variable match regardless if the value is non-zero. + +```go +type MyStruct struct { + Port int `env:"PORT,overwrite"` +} +``` + +### Required + +If a field is required, processing will error if the environment variable is +unset. + +```go +type MyStruct struct { + Port int `env:"PORT,required"` +} +``` + +It is invalid to have a field as both `required` and `default`. + +### Default + +If an environment variable is not set, the field will be set to the default +value. Note that the environment variable must not be set (e.g. `unset PORT`). +If the environment variable is the empty string, that counts as a "value" and +the default will not be used. + +```go +type MyStruct struct { + Port int `env:"PORT,default=5555"` +} +``` + +You can also set the default value to another field or value from the +environment, for example: + +```go +type MyStruct struct { + DefaultPort int `env:"DEFAULT_PORT,default=5555"` + Port int `env:"OVERRIDE_PORT,default=$DEFAULT_PORT"` +} +``` + +The value for `Port` will default to the value of `DEFAULT_PORT`. + +It is invalid to have a field as both `required` and `default`. + +### Prefix + +For shared, embedded structs, you can define a prefix to use when processing +struct values for that embed. + +```go +type SharedConfig struct { + Port int `env:"PORT,default=5555"` +} + +type Server1 struct { + // This processes Port from $FOO_PORT. + *SharedConfig `env:",prefix=FOO_"` +} + +type Server2 struct { + // This processes Port from $BAR_PORT. + *SharedConfig `env:",prefix=BAR_"` +} +``` + +It is invalid to specify a prefix on non-struct fields. + +### Delimiter + +When parsing maps and slices, a comma (`,`) is the default element delimiter. +Define a custom delimiter with `delimiter`: + +```go +type MyStruct struct { + MyVar map[string]string `env:"MYVAR,delimiter=;"` +``` + +```bash +export MYVAR="a:1;b:2" +# map[string]string{"a":"1", "b":"2"} +``` + +This is especially helpful when your values include the default delimiter +character. + +```bash +export MYVAR="a:1,2,3;b:4,5" +# map[string]string{"a":"1,2,3", "b":"4,5"} +``` + +### Separator + +When parsing maps, a colon (`:`) is the default key-value separator. Define a +separator with `separator`: + +```go +type MyStruct struct { + MyVar map[string]string `env:"MYVAR,separator=="` +} +``` + +```bash +export MYVAR="a=b,c=d" +# map[string]string{"a":"b", "c":"d"} +``` + +This is especially helpful when your keys or values include the default +separator character. + +```bash +export MYVAR="client=abcd::1/128,backend=abcd::2/128" +# map[string]string{"client":"abcd::1/128", "backend":"abcd::2/128"} +``` + +## Complex Types + +### Durations + +In the environment, `time.Duration` values are specified as a parsable Go +duration: + +```go +type MyStruct struct { + MyVar time.Duration `env:"MYVAR"` +} +``` + +```bash +export MYVAR="10m" # 10 * time.Minute +``` + +### TextUnmarshaler / BinaryUnmarshaler + +Types that implement `TextUnmarshaler` or `BinaryUnmarshaler` are processed as such. + +### json.Unmarshaler + +Types that implement `json.Unmarshaler` are processed as such. + +### gob.Decoder + +Types that implement `gob.Decoder` are processed as such. + + +### Slices + +Slices are specified as comma-separated values: + +```go +type MyStruct struct { + MyVar []string `env:"MYVAR"` +} +``` + +```bash +export MYVAR="a,b,c,d" # []string{"a", "b", "c", "d"} +``` + +Note that byte slices are special cased and interpreted as strings from the +environment. + +### Maps + +Maps are specified as comma-separated key:value pairs: + +```go +type MyStruct struct { + MyVar map[string]string `env:"MYVAR"` +} +``` + +```bash +export MYVAR="a:b,c:d" # map[string]string{"a":"b", "c":"d"} +``` + +### Structs + +Envconfig walks the entire struct, including nested structs, so deeply-nested +fields are also supported. + +If a nested struct is a pointer type, it will automatically be instantianted to +the non-nil value. To change this behavior, see +(Initialization)[#Initialization]. + + +### Custom + +You can also define your own decoder for structs (see below). + + +## Prefixing + +You can define a custom prefix using the `PrefixLookuper`. This will lookup +values in the environment by prefixing the keys with the provided value: + +```go +type MyStruct struct { + MyVar string `env:"MYVAR"` +} +``` + +```go +// Process variables, but look for the "APP_" prefix. +l := envconfig.PrefixLookuper("APP_", envconfig.OsLookuper()) +if err := envconfig.ProcessWith(ctx, &c, l); err != nil { + panic(err) +} +``` + +```bash +export APP_MYVAR="foo" +``` + +## Initialization + +By default, all pointer fields are initialized (allocated) so they are not +`nil`. To disable this behavior, use the tag the field as `noinit`: + +```go +type MyStruct struct { + // Without `noinit`, DeleteUser would be initialized to the default boolean + // value. With `noinit`, if the environment variable is not given, the value + // is kept as uninitialized (nil). + DeleteUser *bool `env:"DELETE_USER, noinit"` +} +``` + +This also applies to nested fields in a struct: + +```go +type ParentConfig struct { + // Without `noinit` tag, `Child` would be set to `&ChildConfig{}` whether + // or not `FIELD` is set in the env var. + // With `noinit`, `Child` would stay nil if `FIELD` is not set in the env var. + Child *ChildConfig `env:",noinit"` +} + +type ChildConfig struct { + Field string `env:"FIELD"` +} +``` + +The `noinit` tag is only applicable for pointer fields. Putting the tag on a +non-struct-pointer will return an error. + + +## Extension + +All built-in types are supported except Func and Chan. If you need to define a +custom decoder, implement the `Decoder` interface: + +```go +type MyStruct struct { + field string +} + +func (v *MyStruct) EnvDecode(val string) error { + v.field = fmt.Sprintf("PREFIX-%s", val) + return nil +} + +var _ envconfig.Decoder = (*MyStruct)(nil) // interface check +``` + +If you need to modify environment variable values before processing, you can +specify a custom `Mutator`: + +```go +type Config struct { + Password `env:"PASSWORD"` +} + +func resolveSecretFunc(ctx context.Context, key, value string) (string, error) { + if strings.HasPrefix(value, "secret://") { + return secretmanager.Resolve(ctx, value) // example + } + return value, nil +} + +var config Config +envconfig.ProcessWith(ctx, &config, envconfig.OsLookuper(), resolveSecretFunc) +``` + +## Testing + +Relying on the environment in tests can be troublesome because environment +variables are global, which makes it difficult to parallelize the tests. +Envconfig supports extracting data from anything that returns a value: + +```go +lookuper := envconfig.MapLookuper(map[string]string{ + "FOO": "bar", + "ZIP": "zap", +}) + +var config Config +envconfig.ProcessWith(ctx, &config, lookuper) +``` + +Now you can parallelize all your tests by providing a map for the lookup +function. In fact, that's how the tests in this repo work, so check there for an +example. + +You can also combine multiple lookupers with `MultiLookuper`. See the GoDoc for +more information and examples. + + +## Inspiration + +This library is conceptually similar to [kelseyhightower/envconfig](https://github.com/kelseyhightower/envconfig), with the following +major behavioral differences: + +- Adds support for specifying a custom lookup function (such as a map), which + is useful for testing. + +- Only populates fields if they contain zero or nil values if `overwrite` is unset. + This means you can pre-initialize a struct and any pre-populated fields will not + be overwritten during processing. + +- Support for interpolation. The default value for a field can be the value of + another field. + +- Support for arbitrary mutators that change/resolve data before type + conversion. diff --git a/vendor/github.com/sethvargo/go-envconfig/decoding.go b/vendor/github.com/sethvargo/go-envconfig/decoding.go new file mode 100644 index 0000000..3d1cd28 --- /dev/null +++ b/vendor/github.com/sethvargo/go-envconfig/decoding.go @@ -0,0 +1,57 @@ +// Copyright The envconfig Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package envconfig + +import ( + "encoding/base64" + "encoding/hex" + "strings" +) + +// Base64Bytes is a slice of bytes where the information is base64-encoded in +// the environment variable. +type Base64Bytes []byte + +// EnvDecode implements env.Decoder. +func (b *Base64Bytes) EnvDecode(val string) error { + val = strings.ReplaceAll(val, "+", "-") + val = strings.ReplaceAll(val, "/", "_") + val = strings.TrimRight(val, "=") + + var err error + *b, err = base64.RawURLEncoding.DecodeString(val) + return err +} + +// Bytes returns the underlying bytes. +func (b Base64Bytes) Bytes() []byte { + return []byte(b) +} + +// HexBytes is a slice of bytes where the information is hex-encoded in the +// environment variable. +type HexBytes []byte + +// EnvDecode implements env.Decoder. +func (b *HexBytes) EnvDecode(val string) error { + var err error + *b, err = hex.DecodeString(val) + return err +} + +// Bytes returns the underlying bytes. +func (b HexBytes) Bytes() []byte { + return []byte(b) +} diff --git a/vendor/github.com/sethvargo/go-envconfig/envconfig.go b/vendor/github.com/sethvargo/go-envconfig/envconfig.go new file mode 100644 index 0000000..145d285 --- /dev/null +++ b/vendor/github.com/sethvargo/go-envconfig/envconfig.go @@ -0,0 +1,691 @@ +// Copyright The envconfig Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package envconfig populates struct fields based on environment variable +// values (or anything that responds to "Lookup"). Structs declare their +// environment dependencies using the `env` tag with the key being the name of +// the environment variable, case sensitive. +// +// type MyStruct struct { +// A string `env:"A"` // resolves A to $A +// B string `env:"B,required"` // resolves B to $B, errors if $B is unset +// C string `env:"C,default=foo"` // resolves C to $C, defaults to "foo" +// +// D string `env:"D,required,default=foo"` // error, cannot be required and default +// E string `env:""` // error, must specify key +// } +// +// All built-in types are supported except Func and Chan. If you need to define +// a custom decoder, implement Decoder: +// +// type MyStruct struct { +// field string +// } +// +// func (v *MyStruct) EnvDecode(val string) error { +// v.field = fmt.Sprintf("PREFIX-%s", val) +// return nil +// } +// +// In the environment, slices are specified as comma-separated values: +// +// export MYVAR="a,b,c,d" // []string{"a", "b", "c", "d"} +// +// In the environment, maps are specified as comma-separated key:value pairs: +// +// export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"} +// +// If you need to modify environment variable values before processing, you can +// specify a custom mutator: +// +// type Config struct { +// Password `env:"PASSWORD_SECRET"` +// } +// +// func resolveSecretFunc(ctx context.Context, key, value string) (string, error) { +// if strings.HasPrefix(value, "secret://") { +// return secretmanager.Resolve(ctx, value) // example +// } +// return value, nil +// } +// +// var config Config +// ProcessWith(&config, OsLookuper(), resolveSecretFunc) +// +package envconfig + +import ( + "context" + "encoding" + "encoding/gob" + "encoding/json" + "errors" + "fmt" + "os" + "reflect" + "regexp" + "strconv" + "strings" + "time" +) + +const ( + envTag = "env" + + optDefault = "default=" + optDelimiter = "delimiter=" + optNoInit = "noinit" + optOverwrite = "overwrite" + optPrefix = "prefix=" + optRequired = "required" + optSeparator = "separator=" + + defaultDelimiter = "," + defaultSeparator = ":" +) + +var envvarNameRe = regexp.MustCompile(`\A[a-zA-Z_][a-zA-Z0-9_]*\z`) + +// Error is a custom error type for errors returned by envconfig. +type Error string + +// Error implements error. +func (e Error) Error() string { + return string(e) +} + +const ( + ErrInvalidEnvvarName = Error("invalid environment variable name") + ErrInvalidMapItem = Error("invalid map item") + ErrLookuperNil = Error("lookuper cannot be nil") + ErrMissingKey = Error("missing key") + ErrMissingRequired = Error("missing required value") + ErrNoInitNotPtr = Error("field must be a pointer to have noinit") + ErrNotPtr = Error("input must be a pointer") + ErrNotStruct = Error("input must be a struct") + ErrPrefixNotStruct = Error("prefix is only valid on struct types") + ErrPrivateField = Error("cannot parse private fields") + ErrRequiredAndDefault = Error("field cannot be required and have a default value") + ErrUnknownOption = Error("unknown option") +) + +// Lookuper is an interface that provides a lookup for a string-based key. +type Lookuper interface { + // Lookup searches for the given key and returns the corresponding string + // value. If a value is found, it returns the value and true. If a value is + // not found, it returns the empty string and false. + Lookup(key string) (string, bool) +} + +// osLookuper looks up environment configuration from the local environment. +type osLookuper struct{} + +// Verify implements interface. +var _ Lookuper = (*osLookuper)(nil) + +func (o *osLookuper) Lookup(key string) (string, bool) { + return os.LookupEnv(key) +} + +// OsLookuper returns a lookuper that uses the environment (os.LookupEnv) to +// resolve values. +func OsLookuper() Lookuper { + return new(osLookuper) +} + +type mapLookuper map[string]string + +var _ Lookuper = (*mapLookuper)(nil) + +func (m mapLookuper) Lookup(key string) (string, bool) { + v, ok := m[key] + return v, ok +} + +// MapLookuper looks up environment configuration from a provided map. This is +// useful for testing, especially in parallel, since it does not require you to +// mutate the parent environment (which is stateful). +func MapLookuper(m map[string]string) Lookuper { + return mapLookuper(m) +} + +type multiLookuper struct { + ls []Lookuper +} + +var _ Lookuper = (*multiLookuper)(nil) + +func (m *multiLookuper) Lookup(key string) (string, bool) { + for _, l := range m.ls { + if v, ok := l.Lookup(key); ok { + return v, true + } + } + return "", false +} + +// PrefixLookuper looks up environment configuration using the specified prefix. +// This is useful if you want all your variables to start with a particular +// prefix like "MY_APP_". +func PrefixLookuper(prefix string, l Lookuper) Lookuper { + if typ, ok := l.(*prefixLookuper); ok { + return &prefixLookuper{prefix: typ.prefix + prefix, l: typ.l} + } + return &prefixLookuper{prefix: prefix, l: l} +} + +type prefixLookuper struct { + l Lookuper + prefix string +} + +func (p *prefixLookuper) Lookup(key string) (string, bool) { + return p.l.Lookup(p.prefix + key) +} + +// MultiLookuper wraps a collection of lookupers. It does not combine them, and +// lookups appear in the order in which they are provided to the initializer. +func MultiLookuper(lookupers ...Lookuper) Lookuper { + return &multiLookuper{ls: lookupers} +} + +// Decoder is an interface that custom types/fields can implement to control how +// decoding takes place. For example: +// +// type MyType string +// +// func (mt MyType) EnvDecode(val string) error { +// return "CUSTOM-"+val +// } +// +type Decoder interface { + EnvDecode(val string) error +} + +// MutatorFunc is a function that mutates a given value before it is passed +// along for processing. This is useful if you want to mutate the environment +// variable value before it's converted to the proper type. +type MutatorFunc func(ctx context.Context, k, v string) (string, error) + +// options are internal options for decoding. +type options struct { + Default string + Delimiter string + Prefix string + Separator string + NoInit bool + Overwrite bool + Required bool +} + +// Process processes the struct using the environment. See ProcessWith for a +// more customizable version. +func Process(ctx context.Context, i interface{}) error { + return ProcessWith(ctx, i, OsLookuper()) +} + +// ProcessWith processes the given interface with the given lookuper. See the +// package-level documentation for specific examples and behaviors. +func ProcessWith(ctx context.Context, i interface{}, l Lookuper, fns ...MutatorFunc) error { + return processWith(ctx, i, l, false, fns...) +} + +// processWith is a helper that captures whether the parent wanted +// initialization. +func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bool, fns ...MutatorFunc) error { + if l == nil { + return ErrLookuperNil + } + + v := reflect.ValueOf(i) + if v.Kind() != reflect.Ptr { + return ErrNotPtr + } + + e := v.Elem() + if e.Kind() != reflect.Struct { + return ErrNotStruct + } + + t := e.Type() + + for i := 0; i < t.NumField(); i++ { + ef := e.Field(i) + tf := t.Field(i) + tag := tf.Tag.Get(envTag) + + if !ef.CanSet() { + if tag != "" { + // There's an "env" tag on a private field, we can't alter it, and it's + // likely a mistake. Return an error so the user can handle. + return fmt.Errorf("%s: %w", tf.Name, ErrPrivateField) + } + + // Otherwise continue to the next field. + continue + } + + // Parse the key and options. + key, opts, err := keyAndOpts(tag) + if err != nil { + return fmt.Errorf("%s: %w", tf.Name, err) + } + + // NoInit is only permitted on pointers. + if opts.NoInit && ef.Kind() != reflect.Ptr { + return fmt.Errorf("%s: %w", tf.Name, ErrNoInitNotPtr) + } + + isNilStructPtr := false + setNilStruct := func(v reflect.Value) { + origin := e.Field(i) + if isNilStructPtr { + empty := reflect.New(origin.Type().Elem()).Interface() + // If a struct (after traversal) equals to the empty value, it means + // nothing was changed in any sub-fields. With the noinit opt, we skip + // setting the empty value to the original struct pointer (aka. keep it + // nil). + if !reflect.DeepEqual(v.Interface(), empty) || (!opts.NoInit && !parentNoInit) { + origin.Set(v) + } + } + } + + // Initialize pointer structs. + for ef.Kind() == reflect.Ptr { + if ef.IsNil() { + if ef.Type().Elem().Kind() != reflect.Struct { + // This is a nil pointer to something that isn't a struct, like + // *string. Move along. + break + } + + isNilStructPtr = true + // Use an empty struct of the type so we can traverse. + ef = reflect.New(ef.Type().Elem()).Elem() + + } else { + ef = ef.Elem() + } + } + + // Special case handle structs. This has to come after the value resolution in + // case the struct has a custom decoder. + if ef.Kind() == reflect.Struct { + for ef.CanAddr() { + ef = ef.Addr() + } + + // Lookup the value, ignoring an error if the key isn't defined. This is + // required for nested structs that don't declare their own `env` keys, + // but have internal fields with an `env` defined. + val, found, usedDefault, err := lookup(key, opts, l) + if err != nil && !errors.Is(err, ErrMissingKey) { + return fmt.Errorf("%s: %w", tf.Name, err) + } + + if found || usedDefault { + if ok, err := processAsDecoder(val, ef); ok { + if err != nil { + return err + } + + setNilStruct(ef) + continue + } + } + + plu := l + if opts.Prefix != "" { + plu = PrefixLookuper(opts.Prefix, l) + } + + if err := processWith(ctx, ef.Interface(), plu, opts.NoInit, fns...); err != nil { + return fmt.Errorf("%s: %w", tf.Name, err) + } + + setNilStruct(ef) + continue + } + + // It's invalid to have a prefix on a non-struct field. + if opts.Prefix != "" { + return ErrPrefixNotStruct + } + + // Stop processing if there's no env tag (this comes after nested parsing), + // in case there's an env tag in an embedded struct. + if tag == "" { + continue + } + + // The field already has a non-zero value and overwrite is false, do not + // overwrite. + if !ef.IsZero() && !opts.Overwrite { + continue + } + + val, found, usedDefault, err := lookup(key, opts, l) + if err != nil { + return fmt.Errorf("%s: %w", tf.Name, err) + } + + // If the field already has a non-zero value and there was no value directly + // specified, do not overwrite the existing field. We only want to overwrite + // when the envvar was provided directly. + if !ef.IsZero() && !found { + continue + } + + // Apply any mutators. Mutators are applied after the lookup, but before any + // type conversions. They always resolve to a string (or error), so we don't + // call mutators when the environment variable was not set. + if found || usedDefault { + for _, fn := range fns { + if fn != nil { + val, err = fn(ctx, key, val) + if err != nil { + return fmt.Errorf("%s: %w", tf.Name, err) + } + } + } + } + + // If Delimiter is not defined set it to "," + if opts.Delimiter == "" { + opts.Delimiter = defaultDelimiter + } + + // If Separator is not defined set it to ":" + if opts.Separator == "" { + opts.Separator = defaultSeparator + } + + // Set value. + if err := processField(val, ef, opts.Delimiter, opts.Separator, opts.NoInit); err != nil { + return fmt.Errorf("%s(%q): %w", tf.Name, val, err) + } + } + + return nil +} + +// keyAndOpts parses the given tag value (e.g. env:"foo,required") and +// returns the key name and options as a list. +func keyAndOpts(tag string) (string, *options, error) { + parts := strings.Split(tag, ",") + key, tagOpts := strings.TrimSpace(parts[0]), parts[1:] + + if key != "" && !envvarNameRe.MatchString(key) { + return "", nil, fmt.Errorf("%q: %w ", key, ErrInvalidEnvvarName) + } + + var opts options + +LOOP: + for i, o := range tagOpts { + o = strings.TrimSpace(o) + switch { + case o == optOverwrite: + opts.Overwrite = true + case o == optRequired: + opts.Required = true + case o == optNoInit: + opts.NoInit = true + case strings.HasPrefix(o, optPrefix): + opts.Prefix = strings.TrimPrefix(o, optPrefix) + case strings.HasPrefix(o, optDelimiter): + opts.Delimiter = strings.TrimPrefix(o, optDelimiter) + case strings.HasPrefix(o, optSeparator): + opts.Separator = strings.TrimPrefix(o, optSeparator) + case strings.HasPrefix(o, optDefault): + // If a default value was given, assume everything after is the provided + // value, including comma-seprated items. + o = strings.TrimLeft(strings.Join(tagOpts[i:], ","), " ") + opts.Default = strings.TrimPrefix(o, optDefault) + break LOOP + default: + return "", nil, fmt.Errorf("%q: %w", o, ErrUnknownOption) + } + } + + return key, &opts, nil +} + +// lookup looks up the given key using the provided Lookuper and options. The +// first boolean parameter indicates whether the value was found in the +// lookuper. The second boolean parameter indicates whether the default value +// was used. +func lookup(key string, opts *options, l Lookuper) (string, bool, bool, error) { + if key == "" { + // The struct has something like `env:",required"`, which is likely a + // mistake. We could try to infer the envvar from the field name, but that + // feels too magical. + return "", false, false, ErrMissingKey + } + + if opts.Required && opts.Default != "" { + // Having a default value on a required value doesn't make sense. + return "", false, false, ErrRequiredAndDefault + } + + // Lookup value. + val, found := l.Lookup(key) + if !found { + if opts.Required { + if pl, ok := l.(*prefixLookuper); ok { + key = pl.prefix + key + } + + return "", false, false, fmt.Errorf("%w: %s", ErrMissingRequired, key) + } + + if opts.Default != "" { + // Expand the default value. This allows for a default value that maps to + // a different variable. + val = os.Expand(opts.Default, func(i string) string { + s, ok := l.Lookup(i) + if ok { + return s + } + return "" + }) + + return val, false, true, nil + } + } + + return val, found, false, nil +} + +// processAsDecoder processes the given value as a decoder or custom +// unmarshaller. +func processAsDecoder(v string, ef reflect.Value) (bool, error) { + // Keep a running error. It's possible that a property might implement + // multiple decoders, and we don't know *which* decoder will succeed. If we + // get through all of them, we'll return the most recent error. + var imp bool + var err error + + // Resolve any pointers. + for ef.CanAddr() { + ef = ef.Addr() + } + + if ef.CanInterface() { + iface := ef.Interface() + + // If a developer chooses to implement the Decoder interface on a type, + // never attempt to use other decoders in case of failure. EnvDecode's + // decoding logic is "the right one", and the error returned (if any) + // is the most specific we can get. + if dec, ok := iface.(Decoder); ok { + imp = true + err = dec.EnvDecode(v) + return imp, err + } + + if tu, ok := iface.(encoding.TextUnmarshaler); ok { + imp = true + if err = tu.UnmarshalText([]byte(v)); err == nil { + return imp, nil + } + } + + if tu, ok := iface.(json.Unmarshaler); ok { + imp = true + if err = tu.UnmarshalJSON([]byte(v)); err == nil { + return imp, nil + } + } + + if tu, ok := iface.(encoding.BinaryUnmarshaler); ok { + imp = true + if err = tu.UnmarshalBinary([]byte(v)); err == nil { + return imp, nil + } + } + + if tu, ok := iface.(gob.GobDecoder); ok { + imp = true + if err = tu.GobDecode([]byte(v)); err == nil { + return imp, nil + } + } + } + + return imp, err +} + +func processField(v string, ef reflect.Value, delimiter, separator string, noInit bool) error { + // If the input value is empty and initialization is skipped, do nothing. + if v == "" && noInit { + return nil + } + + // Handle pointers and uninitialized pointers. + for ef.Type().Kind() == reflect.Ptr { + if ef.IsNil() { + ef.Set(reflect.New(ef.Type().Elem())) + } + ef = ef.Elem() + } + + tf := ef.Type() + tk := tf.Kind() + + // Handle existing decoders. + if ok, err := processAsDecoder(v, ef); ok { + return err + } + + // We don't check if the value is empty earlier, because the user might want + // to define a custom decoder and treat the empty variable as a special case. + // However, if we got this far, none of the remaining parsers will succeed, so + // bail out now. + if v == "" { + return nil + } + + switch tk { + case reflect.Bool: + b, err := strconv.ParseBool(v) + if err != nil { + return err + } + ef.SetBool(b) + case reflect.Float32, reflect.Float64: + f, err := strconv.ParseFloat(v, tf.Bits()) + if err != nil { + return err + } + ef.SetFloat(f) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: + i, err := strconv.ParseInt(v, 0, tf.Bits()) + if err != nil { + return err + } + ef.SetInt(i) + case reflect.Int64: + // Special case time.Duration values. + if tf.PkgPath() == "time" && tf.Name() == "Duration" { + d, err := time.ParseDuration(v) + if err != nil { + return err + } + ef.SetInt(int64(d)) + } else { + i, err := strconv.ParseInt(v, 0, tf.Bits()) + if err != nil { + return err + } + ef.SetInt(i) + } + case reflect.String: + ef.SetString(v) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + i, err := strconv.ParseUint(v, 0, tf.Bits()) + if err != nil { + return err + } + ef.SetUint(i) + + case reflect.Interface: + return fmt.Errorf("cannot decode into interfaces") + + // Maps + case reflect.Map: + vals := strings.Split(v, delimiter) + mp := reflect.MakeMapWithSize(tf, len(vals)) + for _, val := range vals { + pair := strings.SplitN(val, separator, 2) + if len(pair) < 2 { + return fmt.Errorf("%s: %w", val, ErrInvalidMapItem) + } + mKey, mVal := strings.TrimSpace(pair[0]), strings.TrimSpace(pair[1]) + + k := reflect.New(tf.Key()).Elem() + if err := processField(mKey, k, delimiter, separator, noInit); err != nil { + return fmt.Errorf("%s: %w", mKey, err) + } + + v := reflect.New(tf.Elem()).Elem() + if err := processField(mVal, v, delimiter, separator, noInit); err != nil { + return fmt.Errorf("%s: %w", mVal, err) + } + + mp.SetMapIndex(k, v) + } + ef.Set(mp) + + // Slices + case reflect.Slice: + // Special case: []byte + if tf.Elem().Kind() == reflect.Uint8 { + ef.Set(reflect.ValueOf([]byte(v))) + } else { + vals := strings.Split(v, delimiter) + s := reflect.MakeSlice(tf, len(vals), len(vals)) + for i, val := range vals { + val = strings.TrimSpace(val) + if err := processField(val, s.Index(i), delimiter, separator, noInit); err != nil { + return fmt.Errorf("%s: %w", val, err) + } + } + ef.Set(s) + } + } + + return nil +} diff --git a/vendor/github.com/sethvargo/go-githubactions/.gitignore b/vendor/github.com/sethvargo/go-githubactions/.gitignore new file mode 100644 index 0000000..2d83068 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/.gitignore @@ -0,0 +1 @@ +coverage.out diff --git a/vendor/github.com/sethvargo/go-githubactions/AUTHORS b/vendor/github.com/sethvargo/go-githubactions/AUTHORS new file mode 100644 index 0000000..4d29cb9 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/AUTHORS @@ -0,0 +1,7 @@ +# This is the list of authors for copyright purposes. +# +# This does not necessarily list everyone who has contributed code, since in +# some cases, their employer may be the copyright holder. To see the full list +# of contributors, see the revision history in source control. + +Seth Vargo diff --git a/vendor/github.com/sethvargo/go-githubactions/LICENSE b/vendor/github.com/sethvargo/go-githubactions/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/sethvargo/go-githubactions/Makefile b/vendor/github.com/sethvargo/go-githubactions/Makefile new file mode 100644 index 0000000..5524779 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/Makefile @@ -0,0 +1,29 @@ +# Copyright 2020 The Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +test: + @go test \ + -count=1 \ + -short \ + -timeout=5m \ + ./... +.PHONY: test + +test-acc: + @go test \ + -count=1 \ + -race \ + -timeout=10m \ + ./... +.PHONY: test-acc diff --git a/vendor/github.com/sethvargo/go-githubactions/README.md b/vendor/github.com/sethvargo/go-githubactions/README.md new file mode 100644 index 0000000..8a1d5c3 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/README.md @@ -0,0 +1,150 @@ +# GitHub Actions SDK (Go) + +[![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/sethvargo/go-githubactions) +[![GitHub Actions](https://img.shields.io/github/workflow/status/sethvargo/go-githubactions/Test?style=flat-square)](https://github.com/sethvargo/go-githubactions/actions?query=workflow%3ATest) + +This library provides an SDK for authoring [GitHub Actions][gh-actions] in Go. It has no external dependencies and provides a Go-like interface for interacting with GitHub Actions' build system. + + +## Installation + +Download the library: + +```text +$ go get -u github.com/sethvargo/go-githubactions/... +``` + + +## Usage + +The easiest way to use the library is by importing it and invoking the functions +at the root: + +```go +import ( + "github.com/sethvargo/go-githubactions" +) + +func main() { + val := githubactions.GetInput("val") + if val == "" { + githubactions.Fatalf("missing 'val'") + } +} +``` + +You can also create an instance with custom fields that will be included in log messages: + +```go +import ( + "github.com/sethvargo/go-githubactions" +) + +func main() { + actions := githubactions.WithFieldsMap(map[string]string{ + "file": "myfile.js", + "line": "100", + }) + + val := actions.GetInput("val") + if val == "" { + actions.Fatalf("missing 'val'") + } +} +``` + +For more examples and API documentation, please see the [Go docs][godoc]. + + +## Publishing + +There are multiple ways to publish GitHub Actions written in Go: + +- [Composite actions](https://github.com/FerretDB/github-actions/blob/2ae30fd2cdb635d8aefdaf9f770257e156c9f77b/extract-docker-tag/action.yml) +- [Pre-compiled binaries with a shim](https://full-stack.blend.com/how-we-write-github-actions-in-go.html) +- Docker containers (see below) + +By default, GitHub Actions expects actions to be written in Node.js. For other languages like Go, you need to provide a `Dockerfile` and entrypoint instructions in an `action.yml` file: + +```dockerfile +# your-repo/Dockerfile +FROM golang:1.18 +RUN go build -o /bin/app . +ENTRYPOINT ["/bin/app"] +``` + +```yaml +# your-repo/action.yml +name: My action +author: My name +description: My description + +runs: + using: docker + image: Dockerfile +``` + +And then users can import your action by the repository name: + +```yaml +# their-repo/.github/workflows/thing.yml +steps: +- name: My action + uses: username/repo@latest +``` + +However, this will clone the entire repo and compile the Go code each time the action runs. Worse, it uses the Go base container which is a few hundred MBs and includes a ton of unnecessary things. + +Fortunately, GitHub Actions can also source from a Docker container directly from Docker Hub: + +```yaml +steps: +- name: My action + uses: docker://username/repo:latest +``` + +Now we can precompile and publish our Go Action as a Docker container, but we need to make it much, much smaller first. This can be achieved using multi-stage Docker builds: + +```dockerfile +FROM golang:1.18 AS builder + +ENV GO111MODULE=on \ + CGO_ENABLED=0 \ + GOOS=linux \ + GOARCH=amd64 + +RUN apt-get -qq update && \ + apt-get -yqq install upx + +WORKDIR /src +COPY . . + +RUN go build \ + -ldflags "-s -w -extldflags '-static'" \ + -o /bin/app \ + . \ + && strip /bin/app \ + && upx -q -9 /bin/app + +RUN echo "nobody:x:65534:65534:Nobody:/:" > /etc_passwd + + + +FROM scratch + +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /etc_passwd /etc/passwd +COPY --from=builder --chown=65534:0 /bin/app /app + +USER nobody +ENTRYPOINT ["/app"] +``` + +The first step, uses a fat container to build, strip, and compress the compiled Go binary. Then, in the second step, the compiled and compressed binary is copied into a scratch (bare) container along with some SSL certificates and a `nobody` user in which to execute the container. + +This will usually produce an image that is less than 10MB in size, making for +much faster builds. + + +[gh-actions]: https://github.com/features/actions +[godoc]: https://godoc.org/github.com/sethvargo/go-githubactions diff --git a/vendor/github.com/sethvargo/go-githubactions/actions.go b/vendor/github.com/sethvargo/go-githubactions/actions.go new file mode 100644 index 0000000..1f7d47c --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/actions.go @@ -0,0 +1,576 @@ +// Copyright 2020 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package githubactions provides an SDK for authoring GitHub Actions in Go. It +// has no external dependencies and provides a Go-like interface for interacting +// with GitHub Actions' build system. +package githubactions + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "html/template" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/sethvargo/go-envconfig" +) + +var ( + // osExit allows `os.Exit()` to be stubbed during testing. + osExit = os.Exit +) + +const ( + addMaskCmd = "add-mask" + + envCmd = "env" + outputCmd = "output" + pathCmd = "path" + stateCmd = "state" + + // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings + multiLineFileDelim = "_GitHubActionsFileCommandDelimeter_" + multilineFileCmd = "%s<<" + multiLineFileDelim + EOF + "%s" + EOF + multiLineFileDelim // ${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter} + + addMatcherCmd = "add-matcher" + removeMatcherCmd = "remove-matcher" + + groupCmd = "group" + endGroupCmd = "endgroup" + + stepSummaryCmd = "step-summary" + + debugCmd = "debug" + noticeCmd = "notice" + warningCmd = "warning" + errorCmd = "error" + + errFileCmdFmt = "unable to write command to the environment file: %s" +) + +// New creates a new wrapper with helpers for outputting information in GitHub +// actions format. +func New(opts ...Option) *Action { + a := &Action{ + w: os.Stdout, + getenv: os.Getenv, + httpClient: &http.Client{ + Timeout: 10 * time.Second, + }, + } + + for _, opt := range opts { + if opt == nil { + continue + } + a = opt(a) + } + + return a +} + +// Action is an internal wrapper around GitHub Actions' output and magic +// strings. +type Action struct { + w io.Writer + fields CommandProperties + getenv GetenvFunc + httpClient *http.Client +} + +// IssueCommand issues a new GitHub actions Command. It panics if it cannot +// write to the output stream. +func (c *Action) IssueCommand(cmd *Command) { + if _, err := fmt.Fprint(c.w, cmd.String()+EOF); err != nil { + panic(fmt.Errorf("failed to issue command: %w", err)) + } +} + +// IssueFileCommand issues a new GitHub actions Command using environment files. +// It panics if writing to the file fails. +// +// https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#environment-files +// +// The TypeScript equivalent function: +// +// https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/file-command.ts#L10-L23 +// +// IssueFileCommand currently ignores the 'CommandProperties' field provided +// with the 'Command' argument as it's scope is unclear in the current +// TypeScript implementation. +func (c *Action) IssueFileCommand(cmd *Command) { + if err := c.issueFileCommand(cmd); err != nil { + panic(err) + } +} + +// issueFileCommand is an internal-only helper that issues the command and +// returns an error to make testing easier. +func (c *Action) issueFileCommand(cmd *Command) (retErr error) { + e := strings.ReplaceAll(cmd.Name, "-", "_") + e = strings.ToUpper(e) + e = "GITHUB_" + e + + filepath := c.getenv(e) + msg := []byte(cmd.Message + EOF) + f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + retErr = fmt.Errorf(errFileCmdFmt, err) + return + } + + defer func() { + if err := f.Close(); err != nil && retErr == nil { + retErr = err + } + }() + + if _, err := f.Write(msg); err != nil { + retErr = fmt.Errorf(errFileCmdFmt, err) + return + } + return +} + +// AddMask adds a new field mask for the given string "p". After called, future +// attempts to log "p" will be replaced with "***" in log output. It panics if +// it cannot write to the output stream. +func (c *Action) AddMask(p string) { + // ::add-mask::

+ c.IssueCommand(&Command{ + Name: addMaskCmd, + Message: p, + }) +} + +// AddMatcher adds a new matcher with the given file path. It panics if it +// cannot write to the output stream. +func (c *Action) AddMatcher(p string) { + // ::add-matcher::

+ c.IssueCommand(&Command{ + Name: addMatcherCmd, + Message: p, + }) +} + +// RemoveMatcher removes a matcher with the given owner name. It panics if it +// cannot write to the output stream. +func (c *Action) RemoveMatcher(o string) { + // ::remove-matcher owner=:: + c.IssueCommand(&Command{ + Name: removeMatcherCmd, + Properties: CommandProperties{ + "owner": o, + }, + }) +} + +// AddPath adds the string "p" to the path for the invocation. It panics if it +// cannot write to the output file. +// +// https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#adding-a-system-path +// https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/ +func (c *Action) AddPath(p string) { + c.IssueFileCommand(&Command{ + Name: pathCmd, + Message: p, + }) +} + +// SaveState saves state to be used in the "finally" post job entry point. It +// panics if it cannot write to the output stream. +// +// On 2022-10-11, GitHub deprecated "::save-state name=::" in favor of +// [environment files]. +// +// [environment files]: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ +func (c *Action) SaveState(k, v string) { + c.IssueFileCommand(&Command{ + Name: stateCmd, + Message: fmt.Sprintf(multilineFileCmd, k, v), + }) +} + +// GetInput gets the input by the given name. It returns the empty string if the +// input is not defined. +func (c *Action) GetInput(i string) string { + e := strings.ReplaceAll(i, " ", "_") + e = strings.ToUpper(e) + e = "INPUT_" + e + return strings.TrimSpace(c.getenv(e)) +} + +// Group starts a new collapsable region up to the next ungroup invocation. It +// panics if it cannot write to the output stream. +func (c *Action) Group(t string) { + // ::group:: + c.IssueCommand(&Command{ + Name: groupCmd, + Message: t, + }) +} + +// EndGroup ends the current group. It panics if it cannot write to the output +// stream. +func (c *Action) EndGroup() { + // ::endgroup:: + c.IssueCommand(&Command{ + Name: endGroupCmd, + }) +} + +// AddStepSummary writes the given markdown to the job summary. If a job summary +// already exists, this value is appended. +// +// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary +// https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/ +func (c *Action) AddStepSummary(markdown string) { + c.IssueFileCommand(&Command{ + Name: stepSummaryCmd, + Message: markdown, + }) +} + +// AddStepSummaryTemplate adds a summary template by parsing the given Go +// template using html/template with the given input data. See AddStepSummary +// for caveats. +// +// This primarily exists as a convenience function that renders a template. +func (c *Action) AddStepSummaryTemplate(tmpl string, data any) error { + t, err := template.New("").Parse(tmpl) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + var b bytes.Buffer + if err := t.Execute(&b, data); err != nil { + return fmt.Errorf("failed to execute template: %w", err) + } + + c.AddStepSummary(b.String()) + return nil +} + +// SetEnv sets an environment variable. It panics if it cannot write to the +// output file. +// +// https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable +// https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/ +func (c *Action) SetEnv(k, v string) { + c.IssueFileCommand(&Command{ + Name: envCmd, + Message: fmt.Sprintf(multilineFileCmd, k, v), + }) +} + +// SetOutput sets an output parameter. It panics if it cannot write to the +// output stream. +// +// On 2022-10-11, GitHub deprecated "::set-output name=::" in favor of +// [environment files]. +// +// [environment files]: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ +func (c *Action) SetOutput(k, v string) { + c.IssueFileCommand(&Command{ + Name: outputCmd, + Message: fmt.Sprintf(multilineFileCmd, k, v), + }) +} + +// Debugf prints a debug-level message. It follows the standard fmt.Printf +// arguments, appending an OS-specific line break to the end of the message. It +// panics if it cannot write to the output stream. +func (c *Action) Debugf(msg string, args ...any) { + // ::debug :: + c.IssueCommand(&Command{ + Name: debugCmd, + Message: fmt.Sprintf(msg, args...), + Properties: c.fields, + }) +} + +// Noticef prints a notice-level message. It follows the standard fmt.Printf +// arguments, appending an OS-specific line break to the end of the message. It +// panics if it cannot write to the output stream. +func (c *Action) Noticef(msg string, args ...any) { + // ::notice :: + c.IssueCommand(&Command{ + Name: noticeCmd, + Message: fmt.Sprintf(msg, args...), + Properties: c.fields, + }) +} + +// Warningf prints a warning-level message. It follows the standard fmt.Printf +// arguments, appending an OS-specific line break to the end of the message. It +// panics if it cannot write to the output stream. +func (c *Action) Warningf(msg string, args ...any) { + // ::warning :: + c.IssueCommand(&Command{ + Name: warningCmd, + Message: fmt.Sprintf(msg, args...), + Properties: c.fields, + }) +} + +// Errorf prints a error-level message. It follows the standard fmt.Printf +// arguments, appending an OS-specific line break to the end of the message. It +// panics if it cannot write to the output stream. +func (c *Action) Errorf(msg string, args ...any) { + // ::error :: + c.IssueCommand(&Command{ + Name: errorCmd, + Message: fmt.Sprintf(msg, args...), + Properties: c.fields, + }) +} + +// Fatalf prints a error-level message and exits. This is equivalent to Errorf +// followed by os.Exit(1). +func (c *Action) Fatalf(msg string, args ...any) { + c.Errorf(msg, args...) + osExit(1) +} + +// Infof prints message to stdout without any level annotations. It follows the +// standard fmt.Printf arguments, appending an OS-specific line break to the end +// of the message. It panics if it cannot write to the output stream. +func (c *Action) Infof(msg string, args ...any) { + if _, err := fmt.Fprintf(c.w, msg+EOF, args...); err != nil { + panic(fmt.Errorf("failed to write info command: %w", err)) + } +} + +// WithFieldsSlice includes the provided fields in log output. "f" must be a +// slice of k=v pairs. The given slice will be sorted. It panics if any of the +// string in the given slice does not construct a valid 'key=value' pair. +func (c *Action) WithFieldsSlice(f []string) *Action { + m := make(CommandProperties) + for _, s := range f { + pair := strings.SplitN(s, "=", 2) + if len(pair) < 2 { + panic(fmt.Sprintf("%q is not a proper k=v pair!", s)) + } + + m[pair[0]] = pair[1] + } + + return c.WithFieldsMap(m) +} + +// WithFieldsMap includes the provided fields in log output. The fields in "m" +// are automatically converted to k=v pairs and sorted. +func (c *Action) WithFieldsMap(m map[string]string) *Action { + return &Action{ + w: c.w, + fields: m, + getenv: c.getenv, + httpClient: c.httpClient, + } +} + +// idTokenResponse is the response from minting an ID token. +type idTokenResponse struct { + Value string `json:"value,omitempty"` +} + +// GetIDToken returns the GitHub OIDC token from the GitHub Actions runtime. +func (c *Action) GetIDToken(ctx context.Context, audience string) (string, error) { + requestURL := c.getenv("ACTIONS_ID_TOKEN_REQUEST_URL") + if requestURL == "" { + return "", fmt.Errorf("missing ACTIONS_ID_TOKEN_REQUEST_URL in environment") + } + + requestToken := c.getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN") + if requestToken == "" { + return "", fmt.Errorf("missing ACTIONS_ID_TOKEN_REQUEST_TOKEN in environment") + } + + u, err := url.Parse(requestURL) + if err != nil { + return "", fmt.Errorf("failed to parse request URL: %w", err) + } + if audience != "" { + q := u.Query() + q.Set("audience", audience) + u.RawQuery = q.Encode() + } + + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) + if err != nil { + return "", fmt.Errorf("failed to create HTTP request: %w", err) + } + req.Header.Set("Authorization", "Bearer "+requestToken) + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", fmt.Errorf("failed to make HTTP request: %w", err) + } + defer resp.Body.Close() + + // This has moved to the io package in Go 1.16, but we still support up to Go + // 1.13 for now. + body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1000)) + if err != nil { + return "", fmt.Errorf("failed to read response body: %w", err) + } + body = bytes.TrimSpace(body) + + if resp.StatusCode != 200 { + return "", fmt.Errorf("non-successful response from minting OIDC token: %s", body) + } + + var tokenResp idTokenResponse + if err := json.Unmarshal(body, &tokenResp); err != nil { + return "", fmt.Errorf("failed to process response as JSON: %w", err) + } + return tokenResp.Value, nil +} + +// Getenv retrieves the value of the environment variable named by the key. +// It uses an internal function that can be set with `WithGetenv`. +func (c *Action) Getenv(key string) string { + return c.getenv(key) +} + +// GetenvFunc is an abstraction to make tests feasible for commands that +// interact with environment variables. +type GetenvFunc func(key string) string + +// GitHubContext of current workflow. +// +// See: https://docs.github.com/en/actions/learn-github-actions/environment-variables +type GitHubContext struct { + Action string `env:"GITHUB_ACTION"` + ActionPath string `env:"GITHUB_ACTION_PATH"` + ActionRepository string `env:"GITHUB_ACTION_REPOSITORY"` + Actions bool `env:"GITHUB_ACTIONS"` + Actor string `env:"GITHUB_ACTOR"` + APIURL string `env:"GITHUB_API_URL,default=https://api.github.com"` + BaseRef string `env:"GITHUB_BASE_REF"` + Env string `env:"GITHUB_ENV"` + EventName string `env:"GITHUB_EVENT_NAME"` + EventPath string `env:"GITHUB_EVENT_PATH"` + GraphqlURL string `env:"GITHUB_GRAPHQL_URL,default=https://api.github.com/graphql"` + HeadRef string `env:"GITHUB_HEAD_REF"` + Job string `env:"GITHUB_JOB"` + Path string `env:"GITHUB_PATH"` + Ref string `env:"GITHUB_REF"` + RefName string `env:"GITHUB_REF_NAME"` + RefProtected bool `env:"GITHUB_REF_PROTECTED"` + RefType string `env:"GITHUB_REF_TYPE"` + + // Repository is the owner and repository name. For example, octocat/Hello-World + // It is not recommended to use this field to acquire the repository name + // but to use the Repo method instead. + Repository string `env:"GITHUB_REPOSITORY"` + + // RepositoryOwner is the repository owner. For example, octocat + // It is not recommended to use this field to acquire the repository owner + // but to use the Repo method instead. + RepositoryOwner string `env:"GITHUB_REPOSITORY_OWNER"` + + RetentionDays int64 `env:"GITHUB_RETENTION_DAYS"` + RunAttempt int64 `env:"GITHUB_RUN_ATTEMPT"` + RunID int64 `env:"GITHUB_RUN_ID"` + RunNumber int64 `env:"GITHUB_RUN_NUMBER"` + ServerURL string `env:"GITHUB_SERVER_URL,default=https://github.com"` + SHA string `env:"GITHUB_SHA"` + StepSummary string `env:"GITHUB_STEP_SUMMARY"` + Workflow string `env:"GITHUB_WORKFLOW"` + Workspace string `env:"GITHUB_WORKSPACE"` + + // Event is populated by parsing the file at EventPath, if it exists. + Event map[string]any +} + +// Repo returns the username of the repository owner and repository name. +func (c *GitHubContext) Repo() (string, string) { + if c == nil { + return "", "" + } + + // Based on https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts + if c.Repository != "" { + parts := strings.SplitN(c.Repository, "/", 2) + if len(parts) == 1 { + return parts[0], "" + } + return parts[0], parts[1] + } + + // If c.Repository is empty attempt to get the repo from the Event data. + var repoName string + // NOTE: differs from context.ts. Fall back to GITHUB_REPOSITORY_OWNER + ownerName := c.RepositoryOwner + if c.Event != nil { + if repo, ok := c.Event["repository"].(map[string]any); ok { + if name, ok := repo["name"].(string); ok { + repoName = name + } + if owner, ok := repo["owner"].(map[string]any); ok { + if name, ok := owner["name"].(string); ok { + ownerName = name + } + } + } + } + return ownerName, repoName +} + +// Context returns the context of current action with the payload object +// that triggered the workflow +func (c *Action) Context() (*GitHubContext, error) { + ctx := context.Background() + lookuper := &wrappedLookuper{f: c.getenv} + + var githubContext GitHubContext + if err := envconfig.ProcessWith(ctx, &githubContext, lookuper); err != nil { + return nil, fmt.Errorf("could not process github context variables: %w", err) + } + + if githubContext.EventPath != "" { + eventData, err := os.ReadFile(githubContext.EventPath) + if err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("could not read event file: %w", err) + } + if eventData != nil { + if err := json.Unmarshal(eventData, &githubContext.Event); err != nil { + return nil, fmt.Errorf("failed to unmarshal event payload: %w", err) + } + } + } + + return &githubContext, nil +} + +// wrappedLookuper creates a lookuper that wraps a given getenv func. +type wrappedLookuper struct { + f GetenvFunc +} + +// Lookup implements a custom lookuper. +func (w *wrappedLookuper) Lookup(key string) (string, bool) { + if v := w.f(key); v != "" { + return v, true + } + return "", false +} diff --git a/vendor/github.com/sethvargo/go-githubactions/actions_root.go b/vendor/github.com/sethvargo/go-githubactions/actions_root.go new file mode 100644 index 0000000..f8c25a9 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/actions_root.go @@ -0,0 +1,156 @@ +// Copyright 2020 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package githubactions + +import ( + "context" +) + +var ( + defaultAction = New() +) + +// IssueCommand issues an arbitrary GitHub actions Command. +func IssueCommand(cmd *Command) { + defaultAction.IssueCommand(cmd) +} + +// IssueFileCommand issues a new GitHub actions Command using environment files. +func IssueFileCommand(cmd *Command) { + defaultAction.IssueFileCommand(cmd) +} + +// AddMask adds a new field mask for the given string "p". After called, future +// attempts to log "p" will be replaced with "***" in log output. +func AddMask(p string) { + defaultAction.AddMask(p) +} + +// AddMatcher adds a new matcher with the given file path. +func AddMatcher(p string) { + defaultAction.AddMatcher(p) +} + +// RemoveMatcher removes a matcher with the given owner name. +func RemoveMatcher(o string) { + defaultAction.RemoveMatcher(o) +} + +// AddPath adds the string "p" to the path for the invocation. +func AddPath(p string) { + defaultAction.AddPath(p) +} + +// SaveState saves state to be used in the "finally" post job entry point. +func SaveState(k, v string) { + defaultAction.SaveState(k, v) +} + +// GetInput gets the input by the given name. +func GetInput(i string) string { + return defaultAction.GetInput(i) +} + +// Group starts a new collapsable region up to the next ungroup invocation. +func Group(t string) { + defaultAction.Group(t) +} + +// EndGroup ends the current group. +func EndGroup() { + defaultAction.EndGroup() +} + +// AddStepSummary writes the given markdown to the job summary. If a job summary +// already exists, this value is appended. +func AddStepSummary(markdown string) { + defaultAction.AddStepSummary(markdown) +} + +// AddStepSummaryTemplate adds a summary template by parsing the given Go +// template using html/template with the given input data. See AddStepSummary +// for caveats. +// +// This primarily exists as a convenience function that renders a template. +func AddStepSummaryTemplate(tmpl string, data any) error { + return defaultAction.AddStepSummaryTemplate(tmpl, data) +} + +// SetEnv sets an environment variable. +func SetEnv(k, v string) { + defaultAction.SetEnv(k, v) +} + +// SetOutput sets an output parameter. +func SetOutput(k, v string) { + defaultAction.SetOutput(k, v) +} + +// Debugf prints a debug-level message. The arguments follow the standard Printf +// arguments. +func Debugf(msg string, args ...any) { + defaultAction.Debugf(msg, args...) +} + +// Noticef prints a notice-level message. The arguments follow the standard +// Printf arguments. +func Noticef(msg string, args ...any) { + defaultAction.Noticef(msg, args...) +} + +// Errorf prints a error-level message. The arguments follow the standard Printf +// arguments. +func Errorf(msg string, args ...any) { + defaultAction.Errorf(msg, args...) +} + +// Fatalf prints a error-level message and exits. This is equivalent to Errorf +// followed by os.Exit(1). +func Fatalf(msg string, args ...any) { + defaultAction.Fatalf(msg, args...) +} + +// Infof prints a info-level message. The arguments follow the standard Printf +// arguments. +func Infof(msg string, args ...any) { + defaultAction.Infof(msg, args...) +} + +// Warningf prints a warning-level message. The arguments follow the standard +// Printf arguments. +func Warningf(msg string, args ...any) { + defaultAction.Warningf(msg, args...) +} + +// WithFieldsSlice includes the provided fields in log output. "f" must be a +// slice of k=v pairs. The given slice will be sorted. +func WithFieldsSlice(f []string) *Action { + return defaultAction.WithFieldsSlice(f) +} + +// WithFieldsMap includes the provided fields in log output. The fields in "m" +// are automatically converted to k=v pairs and sorted. +func WithFieldsMap(m map[string]string) *Action { + return defaultAction.WithFieldsMap(m) +} + +// GetIDToken returns the GitHub OIDC token from the GitHub Actions runtime. +func GetIDToken(ctx context.Context, audience string) (string, error) { + return defaultAction.GetIDToken(ctx, audience) +} + +func Context() (*GitHubContext, error) { + return defaultAction.Context() +} diff --git a/vendor/github.com/sethvargo/go-githubactions/command.go b/vendor/github.com/sethvargo/go-githubactions/command.go new file mode 100644 index 0000000..24ef400 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/command.go @@ -0,0 +1,112 @@ +// Copyright 2020 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package githubactions + +import ( + "fmt" + "sort" + "strings" +) + +const ( + cmdSeparator = "::" + cmdPropertiesPrefix = " " +) + +// CommandProperties is a named "map[string]string" type to hold key-value pairs +// passed to an actions command. +type CommandProperties map[string]string + +// String encodes the CommandProperties to a string as comma separated +// 'key=value' pairs. The pairs are joined in a chronological order. +func (props *CommandProperties) String() string { + l := make([]string, 0, len(*props)) + for k, v := range *props { + l = append(l, fmt.Sprintf("%s=%s", k, escapeProperty(v))) + } + + sort.Strings(l) + return strings.Join(l, ",") +} + +// Command can be issued by a GitHub action by writing to `stdout` with +// following format. +// +// ::name key=value,key=value::message +// +// Examples: +// ::warning::This is the message +// ::set-env name=MY_VAR::some value +type Command struct { + Name string + Message string + Properties CommandProperties +} + +// String encodes the Command to a string in the following format: +// +// ::name key=value,key=value::message +func (cmd *Command) String() string { + // https://github.com/actions/toolkit/blob/9ad01e4fd30025e8858650d38e95cfe9193a3222/packages/core/src/command.ts#L43-L45 + if cmd.Name == "" { + cmd.Name = "missing.command" + } + + var builder strings.Builder + builder.WriteString(cmdSeparator) + builder.WriteString(cmd.Name) + if len(cmd.Properties) > 0 { + builder.WriteString(cmdPropertiesPrefix) + builder.WriteString(cmd.Properties.String()) + } + + builder.WriteString(cmdSeparator) + builder.WriteString(escapeData(cmd.Message)) + return builder.String() +} + +// escapeData escapes string values for presentation in the output of a command. +// This is a not-so-well-documented requirement of commands that define a +// message: +// +// https://github.com/actions/toolkit/blob/9ad01e4fd30025e8858650d38e95cfe9193a3222/packages/core/src/command.ts#L74 +// +// The equivalent toolkit function can be found here: +// +// https://github.com/actions/toolkit/blob/9ad01e4fd30025e8858650d38e95cfe9193a3222/packages/core/src/command.ts#L92 +// +func escapeData(v string) string { + v = strings.ReplaceAll(v, "%", "%25") + v = strings.ReplaceAll(v, "\r", "%0D") + v = strings.ReplaceAll(v, "\n", "%0A") + return v +} + +// escapeData escapes command property values for presentation in the output of +// a command. +// +// https://github.com/actions/toolkit/blob/9ad01e4fd30025e8858650d38e95cfe9193a3222/packages/core/src/command.ts#L68 +// +// The equivalent toolkit function can be found here: +// +// https://github.com/actions/toolkit/blob/1cc56db0ff126f4d65aeb83798852e02a2c180c3/packages/core/src/command.ts#L99-L106 +func escapeProperty(v string) string { + v = strings.ReplaceAll(v, "%", "%25") + v = strings.ReplaceAll(v, "\r", "%0D") + v = strings.ReplaceAll(v, "\n", "%0A") + v = strings.ReplaceAll(v, ":", "%3A") + v = strings.ReplaceAll(v, ",", "%2C") + return v +} diff --git a/vendor/github.com/sethvargo/go-githubactions/eof_crlf.go b/vendor/github.com/sethvargo/go-githubactions/eof_crlf.go new file mode 100644 index 0000000..12d0d31 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/eof_crlf.go @@ -0,0 +1,20 @@ +// Copyright 2021 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows +// +build windows + +package githubactions + +const EOF = "\r\n" diff --git a/vendor/github.com/sethvargo/go-githubactions/eof_lf.go b/vendor/github.com/sethvargo/go-githubactions/eof_lf.go new file mode 100644 index 0000000..b2c3456 --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/eof_lf.go @@ -0,0 +1,20 @@ +// Copyright 2021 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !windows +// +build !windows + +package githubactions + +const EOF = "\n" diff --git a/vendor/github.com/sethvargo/go-githubactions/options.go b/vendor/github.com/sethvargo/go-githubactions/options.go new file mode 100644 index 0000000..55727aa --- /dev/null +++ b/vendor/github.com/sethvargo/go-githubactions/options.go @@ -0,0 +1,59 @@ +// Copyright 2021 The Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package githubactions + +import ( + "io" + "net/http" +) + +// Option is a modifier for an Action. +type Option func(*Action) *Action + +// WithWriter sets the writer function on an Action. By default, this will +// be `os.Stdout` from the standard library. +func WithWriter(w io.Writer) Option { + return func(a *Action) *Action { + a.w = w + return a + } +} + +// WithFields sets the extra command field on an Action. +func WithFields(fields CommandProperties) Option { + return func(a *Action) *Action { + a.fields = fields + return a + } +} + +// WithGetenv sets the `Getenv` function on an Action. By default, this will +// be `os.Getenv` from the standard library. +func WithGetenv(getenv GetenvFunc) Option { + return func(a *Action) *Action { + a.getenv = getenv + return a + } +} + +// WithHTTPClient sets a custom HTTP client on the action. This is only used +// when the action makes output HTTP requests (such as generating an OIDC +// token). +func WithHTTPClient(c *http.Client) Option { + return func(a *Action) *Action { + a.httpClient = c + return a + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..b9672c2 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,12 @@ +# code.gitea.io/sdk/gitea v0.15.1 +## explicit; go 1.13 +code.gitea.io/sdk/gitea +# github.com/hashicorp/go-version v1.2.1 +## explicit +github.com/hashicorp/go-version +# github.com/sethvargo/go-envconfig v0.8.0 +## explicit; go 1.17 +github.com/sethvargo/go-envconfig +# github.com/sethvargo/go-githubactions v1.1.0 +## explicit; go 1.18 +github.com/sethvargo/go-githubactions