mirror of
https://github.com/restic/restic.git
synced 2026-08-17 02:53:17 +00:00
Allow for a personal token to be specified for self-updates (#5568)
* Allow for a personal token to be specified for self-updates This change will allow for setting the $GITHUB_ACCESS_TOKEN environment variable with a Github personal access token, allowing e.g. for higher rate limits * Refactor github request and add test --------- Co-authored-by: Paulo Saraiva <pauloman@cern.ch>
This commit is contained in:
co-authored by
Paulo Saraiva
parent
3826167474
commit
c1a89d5150
@@ -0,0 +1,8 @@
|
||||
Enhancement: Allow Github personal access token to be specified for `self-update`
|
||||
|
||||
`restic self-update` previously only used unauthenticated GitHub API requests when checking for the latest release. This caused some users sharing IP addresses to hit the GitHub rate limit, resulting in a 403 Forbidden error and preventing updates.
|
||||
|
||||
Restic still uses unauthenticated requests by default, but it now optionally supports authenticated GitHub API requests during `self-update`. Users can set the `$GITHUB_ACCESS_TOKEN` environment variable to use a [personal access token](https://github.com/settings/tokens) for this effect, avoiding update failures due to rate limiting.
|
||||
|
||||
https://github.com/restic/restic/issues/3738
|
||||
https://github.com/restic/restic/pull/5568
|
||||
@@ -25,7 +25,12 @@ you can download and run without having to do additional installation work.
|
||||
|
||||
Please see the :ref:`official_binaries` section below for various downloads.
|
||||
Official binaries can be updated in place by using the ``restic self-update``
|
||||
command.
|
||||
command.
|
||||
|
||||
The environment variable ``$GITHUB_ACCESS_TOKEN`` can be set to use a personal
|
||||
access token when updating. This increases the rate limit through authenticated GitHub API
|
||||
requests, and prevents update failures when many
|
||||
unauthenticated requests have already been made from the same IP.
|
||||
|
||||
Alpine Linux
|
||||
============
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -45,6 +46,23 @@ type githubError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func newGitHubRequest(ctx context.Context, url, acceptHeader string) (*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the Accept header to pin the API version
|
||||
req.Header.Set("Accept", acceptHeader)
|
||||
|
||||
// Add Authorization header if token is available
|
||||
if token := os.Getenv("GITHUB_ACCESS_TOKEN"); token != "" {
|
||||
req.Header.Set("Authorization", "token "+token)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// GitHubLatestRelease uses the GitHub API to get information about the latest
|
||||
// release of a repository.
|
||||
func GitHubLatestRelease(ctx context.Context, owner, repo string) (Release, error) {
|
||||
@@ -52,14 +70,11 @@ func GitHubLatestRelease(ctx context.Context, owner, repo string) (Release, erro
|
||||
defer cancel()
|
||||
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
req, err := newGitHubRequest(ctx, url, "application/vnd.github.v3+json")
|
||||
if err != nil {
|
||||
return Release{}, err
|
||||
}
|
||||
|
||||
// pin API version 3
|
||||
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return Release{}, err
|
||||
@@ -111,14 +126,11 @@ func GitHubLatestRelease(ctx context.Context, owner, repo string) (Release, erro
|
||||
}
|
||||
|
||||
func getGithubData(ctx context.Context, url string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
req, err := newGitHubRequest(ctx, url, "application/octet-stream")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// request binary data
|
||||
req.Header.Set("Accept", "application/octet-stream")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package selfupdate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func TestNewGitHubRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
url := "https://api.github.com/repos/restic/restic/releases/latest"
|
||||
acceptHeader := "application/vnd.github.v3+json"
|
||||
|
||||
t.Run("With GITHUB_ACCESS_TOKEN", func(t *testing.T) {
|
||||
expectedToken := "testtoken123"
|
||||
t.Setenv("GITHUB_ACCESS_TOKEN", expectedToken)
|
||||
|
||||
req, err := newGitHubRequest(ctx, url, acceptHeader)
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.Assert(t, req.Method == http.MethodGet, "expected method %s, got %s", http.MethodGet, req.Method)
|
||||
rtest.Assert(t, req.URL.String() == url, "expected URL %s, got %s", url, req.URL.String())
|
||||
rtest.Assert(t, req.Header.Get("Accept") == acceptHeader, "expected Accept header %s, got %s", acceptHeader, req.Header.Get("Accept"))
|
||||
rtest.Assert(t, req.Header.Get("Authorization") == "token "+expectedToken, "expected Authorization header 'token %s', got %s", expectedToken, req.Header.Get("Authorization"))
|
||||
})
|
||||
|
||||
t.Run("Without GITHUB_ACCESS_TOKEN", func(t *testing.T) {
|
||||
t.Setenv("GITHUB_ACCESS_TOKEN", "")
|
||||
|
||||
req, err := newGitHubRequest(ctx, url, acceptHeader)
|
||||
rtest.OK(t, err)
|
||||
|
||||
rtest.Assert(t, req.Header.Get("Authorization") == "", "expected no Authorization header, got %s", req.Header.Get("Authorization"))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user