mirror of
https://gh-proxy.org/https://github.com/klinsmaya/CLIProxyAPI.git
synced 2026-11-08 17:36:47 +08:00
Merge pull request #4039 from router-for-me/plugin
feat(pluginstore): add API URL to ReleaseAsset and update asset download logic
This commit is contained in:
@@ -82,11 +82,7 @@ func AuthConfigured(auth []AuthConfig, requestURL string, kind string) bool {
|
||||
case AuthTypeNone:
|
||||
return false
|
||||
case AuthTypeBearer, AuthTypeGitHubToken:
|
||||
envName := item.TokenEnv
|
||||
if envName == "" && item.Type == AuthTypeGitHubToken {
|
||||
envName = "GITSTORE_GIT_TOKEN"
|
||||
}
|
||||
return strings.TrimSpace(os.Getenv(envName)) != ""
|
||||
return strings.TrimSpace(os.Getenv(item.TokenEnv)) != ""
|
||||
case AuthTypeBasic:
|
||||
return strings.TrimSpace(os.Getenv(item.UsernameEnv)) != "" && strings.TrimSpace(os.Getenv(item.PasswordEnv)) != ""
|
||||
case AuthTypeHeader:
|
||||
@@ -162,12 +158,9 @@ func applyPluginStoreAuth(headers http.Header, auth []AuthConfig, requestURL str
|
||||
}
|
||||
headers.Set(item.HeaderName, value)
|
||||
case AuthTypeGitHubToken:
|
||||
token := strings.TrimSpace(os.Getenv(strings.TrimSpace(item.TokenEnv)))
|
||||
if token == "" {
|
||||
token = strings.TrimSpace(os.Getenv("GITSTORE_GIT_TOKEN"))
|
||||
}
|
||||
if token == "" {
|
||||
return fmt.Errorf("plugin store auth missing token-env")
|
||||
token, errToken := envValueRequired(item.TokenEnv, "token-env")
|
||||
if errToken != nil {
|
||||
return errToken
|
||||
}
|
||||
headers.Set("Authorization", "Bearer "+token)
|
||||
default:
|
||||
|
||||
@@ -44,6 +44,24 @@ func TestPluginStoreAuthMatchesURLHostAndPathBoundaries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginStoreGitHubTokenUsesExplicitTokenEnv(t *testing.T) {
|
||||
t.Setenv("PLUGIN_STORE_TOKEN", "secret-token")
|
||||
headers := http.Header{}
|
||||
auth := []AuthConfig{{
|
||||
Match: "https://api.github.com/repos/author-name/sample-provider/releases/",
|
||||
ApplyTo: []string{RequestKindArtifact},
|
||||
Type: AuthTypeGitHubToken,
|
||||
TokenEnv: "PLUGIN_STORE_TOKEN",
|
||||
}}
|
||||
|
||||
if errAuth := applyPluginStoreAuth(headers, auth, "https://api.github.com/repos/author-name/sample-provider/releases/assets/1", RequestKindArtifact); errAuth != nil {
|
||||
t.Fatalf("applyPluginStoreAuth() error = %v", errAuth)
|
||||
}
|
||||
if gotAuth := headers.Get("Authorization"); gotAuth != "Bearer secret-token" {
|
||||
t.Fatalf("Authorization = %q, want Bearer secret-token", gotAuth)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginAuthConfiguredCoversInstallRequestKinds(t *testing.T) {
|
||||
t.Setenv("PLUGIN_STORE_TOKEN", "secret-token")
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/httpfetch"
|
||||
@@ -33,6 +32,7 @@ type Release struct {
|
||||
}
|
||||
|
||||
type ReleaseAsset struct {
|
||||
APIURL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
}
|
||||
@@ -114,10 +114,25 @@ func ReleaseVersion(release Release) (string, error) {
|
||||
}
|
||||
|
||||
func (c Client) DownloadAsset(ctx context.Context, asset ReleaseAsset) ([]byte, error) {
|
||||
if strings.TrimSpace(asset.BrowserDownloadURL) == "" {
|
||||
return nil, fmt.Errorf("asset %q missing browser_download_url", asset.Name)
|
||||
downloadURL := strings.TrimSpace(asset.BrowserDownloadURL)
|
||||
apiURL := strings.TrimSpace(asset.APIURL)
|
||||
if downloadURL == "" || c.releaseAssetAPIAuthenticated(apiURL) {
|
||||
if apiURL != "" {
|
||||
downloadURL = apiURL
|
||||
}
|
||||
}
|
||||
return c.get(ctx, asset.BrowserDownloadURL, "application/octet-stream", RequestKindArtifact, 0)
|
||||
if downloadURL == "" {
|
||||
return nil, fmt.Errorf("asset %q missing download url", asset.Name)
|
||||
}
|
||||
return c.get(ctx, downloadURL, "application/octet-stream", RequestKindArtifact, 0)
|
||||
}
|
||||
|
||||
func (c Client) releaseAssetAPIAuthenticated(apiURL string) bool {
|
||||
apiURL = strings.TrimSpace(apiURL)
|
||||
if apiURL == "" {
|
||||
return false
|
||||
}
|
||||
return AuthConfigured(c.Auth, apiURL, RequestKindArtifact)
|
||||
}
|
||||
|
||||
func (c Client) get(ctx context.Context, requestURL string, accept string, kind string, maxSize int64) ([]byte, error) {
|
||||
@@ -133,11 +148,6 @@ func (c Client) get(ctx context.Context, requestURL string, accept string, kind
|
||||
if errAuth := applyPluginStoreAuth(headers, c.Auth, currentURL, kind); errAuth != nil {
|
||||
return nil, errAuth
|
||||
}
|
||||
if headers.Get("Authorization") == "" {
|
||||
if token := gitHubAPIToken(currentURL); token != "" {
|
||||
headers.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
}
|
||||
resp, errDo := pluginStoreGetNoRedirect(ctx, c.httpClient(), currentURL, headers)
|
||||
if errDo != nil {
|
||||
return nil, errDo
|
||||
@@ -160,20 +170,6 @@ func (c Client) get(ctx context.Context, requestURL string, accept string, kind
|
||||
}
|
||||
}
|
||||
|
||||
// gitHubAPIToken returns the optional GitHub token for GitHub API requests to
|
||||
// raise the unauthenticated rate limit, mirroring the management asset updater.
|
||||
func gitHubAPIToken(requestURL string) string {
|
||||
parsed, errParse := url.Parse(requestURL)
|
||||
if errParse != nil || !strings.EqualFold(parsed.Host, "api.github.com") {
|
||||
return ""
|
||||
}
|
||||
gitURL := strings.ToLower(strings.TrimSpace(os.Getenv("GITSTORE_GIT_URL")))
|
||||
if !strings.Contains(gitURL, "github.com") {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(os.Getenv("GITSTORE_GIT_TOKEN"))
|
||||
}
|
||||
|
||||
func (c Client) httpClient() HTTPDoer {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
|
||||
@@ -319,8 +319,16 @@ func TestInstallUsesLatestReleaseVersion(t *testing.T) {
|
||||
"https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/latest": []byte(`{
|
||||
"tag_name": "v0.2.0",
|
||||
"assets": [
|
||||
{"name": "` + archiveName + `", "browser_download_url": "https://downloads.example/` + archiveName + `"},
|
||||
{"name": "checksums.txt", "browser_download_url": "https://downloads.example/checksums.txt"}
|
||||
{
|
||||
"name": "` + archiveName + `",
|
||||
"url": "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1",
|
||||
"browser_download_url": "https://downloads.example/` + archiveName + `"
|
||||
},
|
||||
{
|
||||
"name": "checksums.txt",
|
||||
"url": "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/2",
|
||||
"browser_download_url": "https://downloads.example/checksums.txt"
|
||||
}
|
||||
]
|
||||
}`),
|
||||
"https://downloads.example/" + archiveName: archiveData,
|
||||
@@ -347,6 +355,82 @@ func TestInstallUsesLatestReleaseVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadAssetFallsBackToReleaseAssetAPIURLWhenBrowserDownloadURLEmpty(t *testing.T) {
|
||||
apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1"
|
||||
client := Client{HTTPClient: mapHTTPDoer{
|
||||
apiURL: []byte("artifact-data"),
|
||||
}}
|
||||
|
||||
data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{
|
||||
Name: "sample-provider_0.2.0_darwin_arm64.zip",
|
||||
APIURL: apiURL,
|
||||
})
|
||||
if errDownload != nil {
|
||||
t.Fatalf("DownloadAsset() error = %v", errDownload)
|
||||
}
|
||||
if string(data) != "artifact-data" {
|
||||
t.Fatalf("DownloadAsset() = %q, want artifact-data", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadAssetUsesAPIURLWhenAuthMatchesArtifact(t *testing.T) {
|
||||
t.Setenv("PLUGIN_STORE_TOKEN", "secret-token")
|
||||
apiURL := "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1"
|
||||
client := Client{
|
||||
HTTPClient: authCheckingHTTPDoer{
|
||||
url: apiURL,
|
||||
wantAuth: "Bearer secret-token",
|
||||
responseBytes: []byte("artifact-data"),
|
||||
},
|
||||
Auth: []AuthConfig{{
|
||||
Match: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/",
|
||||
ApplyTo: []string{RequestKindArtifact},
|
||||
Type: AuthTypeBearer,
|
||||
TokenEnv: "PLUGIN_STORE_TOKEN",
|
||||
}},
|
||||
}
|
||||
|
||||
data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{
|
||||
Name: "sample-provider_0.2.0_darwin_arm64.zip",
|
||||
APIURL: apiURL,
|
||||
BrowserDownloadURL: "https://downloads.example/sample-provider.zip",
|
||||
})
|
||||
if errDownload != nil {
|
||||
t.Fatalf("DownloadAsset() error = %v", errDownload)
|
||||
}
|
||||
if string(data) != "artifact-data" {
|
||||
t.Fatalf("DownloadAsset() = %q, want artifact-data", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadAssetUsesBrowserDownloadURLWithUnrelatedAuth(t *testing.T) {
|
||||
t.Setenv("PLUGIN_STORE_TOKEN", "secret-token")
|
||||
browserURL := "https://downloads.example/sample-provider.zip"
|
||||
client := Client{
|
||||
HTTPClient: mapHTTPDoer{
|
||||
browserURL: []byte("artifact-data"),
|
||||
},
|
||||
Auth: []AuthConfig{{
|
||||
Match: "https://registry.example/",
|
||||
ApplyTo: []string{RequestKindRegistry},
|
||||
Type: AuthTypeBearer,
|
||||
TokenEnv: "PLUGIN_STORE_TOKEN",
|
||||
}},
|
||||
}
|
||||
|
||||
data, errDownload := client.DownloadAsset(context.Background(), ReleaseAsset{
|
||||
Name: "sample-provider_0.2.0_darwin_arm64.zip",
|
||||
APIURL: "https://api.github.com/repos/author-name/cliproxy-sample-provider-plugin/releases/assets/1",
|
||||
BrowserDownloadURL: browserURL,
|
||||
})
|
||||
if errDownload != nil {
|
||||
t.Fatalf("DownloadAsset() error = %v", errDownload)
|
||||
}
|
||||
if string(data) != "artifact-data" {
|
||||
t.Fatalf("DownloadAsset() = %q, want artifact-data", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallVersionUsesPinnedReleaseTag(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user