优化项目客户端下载与通道隔离
This commit is contained in:
parent
e3cea87302
commit
141ae16233
@ -228,6 +228,7 @@ func (s *Server) download(writer http.ResponseWriter, request *http.Request, nam
|
||||
version,
|
||||
query.Get("platform"),
|
||||
query.Get("architecture"),
|
||||
query.Get("channel"),
|
||||
query.Get("current_version"),
|
||||
)
|
||||
if err != nil || len(item.Targets) == 0 {
|
||||
|
||||
@ -120,6 +120,14 @@ func TestTargetDownloadChannelAndDeltaSelection(t *testing.T) {
|
||||
uploadTargetPackageOK(t, server.URL, map[string]string{
|
||||
"name": "channel-beta", "version": "1.0.0", "platform": "linux", "architecture": "amd64", "channel": "beta",
|
||||
}, "beta.pkg", []byte("beta"))
|
||||
stableDownload, err := http.Get(server.URL + "/api/v1/packages/channel-beta/1.0.0/download?platform=linux&architecture=amd64&channel=stable")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stableDownload.Body.Close()
|
||||
if stableDownload.StatusCode != http.StatusNotFound {
|
||||
t.Fatalf("稳定通道不应下载 beta 包:%d", stableDownload.StatusCode)
|
||||
}
|
||||
list := getTargetPackage(t, server.URL+"/api/v1/packages?platform=linux&architecture=amd64&channel=stable")
|
||||
defer list.Body.Close()
|
||||
var payload struct {
|
||||
|
||||
@ -230,7 +230,7 @@ func (r *Registry) Get(name, version string) (Package, bool) {
|
||||
}
|
||||
|
||||
func (r *Registry) OpenPackage(name, version string) (*os.File, Package, error) {
|
||||
return r.OpenPackageFor(name, version, "", "", "")
|
||||
return r.OpenPackageFor(name, version, "", "", "", "")
|
||||
}
|
||||
|
||||
func (r *Registry) Subscribe() (<-chan Update, func()) {
|
||||
|
||||
@ -256,8 +256,8 @@ func (r *Registry) GetFor(name, version, platform, architecture, channel, curren
|
||||
return SelectTarget(item, platform, architecture, currentVersion)
|
||||
}
|
||||
|
||||
func (r *Registry) OpenPackageFor(name, version, platform, architecture, currentVersion string) (*os.File, Package, error) {
|
||||
item, ok := r.GetFor(name, version, platform, architecture, "", currentVersion)
|
||||
func (r *Registry) OpenPackageFor(name, version, platform, architecture, channel, currentVersion string) (*os.File, Package, error) {
|
||||
item, ok := r.GetFor(name, version, platform, architecture, channel, currentVersion)
|
||||
if !ok || len(item.Targets) == 0 {
|
||||
return nil, Package{}, os.ErrNotExist
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ func TestLegacyReleaseCanBeLoaded(t *testing.T) {
|
||||
if !ok || item.Platform != "any" || item.Architecture != "universal" {
|
||||
t.Fatalf("\u65e7\u6570\u636e\u517c\u5bb9\u5931\u8d25: %+v", item)
|
||||
}
|
||||
file, _, err := store.OpenPackageFor("legacy-demo", "1.0.0", "linux", "amd64", "")
|
||||
file, _, err := store.OpenPackageFor("legacy-demo", "1.0.0", "linux", "amd64", "", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ func (c *Client) ListVersionsFor(ctx context.Context, name string, options Query
|
||||
Name string `json:"name"`
|
||||
Versions []Package `json:"versions"`
|
||||
}
|
||||
path := "/api/v1/packages/" + url.PathEscape(name) + "/versions"
|
||||
path := c.resourcePath("/api/v1/packages/") + url.PathEscape(name) + "/versions"
|
||||
if err := c.getJSON(ctx, withQuery(path, options.values()), &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -97,32 +97,41 @@ func (c *Client) DownloadFor(ctx context.Context, name, version, destination str
|
||||
if err := os.MkdirAll(filepath.Dir(destination), 0o755); err != nil {
|
||||
return Package{}, fmt.Errorf("\u521b\u5efa\u4fdd\u5b58\u76ee\u5f55\u5931\u8d25: %w", err)
|
||||
}
|
||||
file, err := os.Create(destination)
|
||||
temporary, err := os.CreateTemp(filepath.Dir(destination), ".scout-download-*")
|
||||
if err != nil {
|
||||
return Package{}, fmt.Errorf("\u521b\u5efa\u4fdd\u5b58\u6587\u4ef6\u5931\u8d25: %w", err)
|
||||
return Package{}, fmt.Errorf("\u521b\u5efa\u4e0b\u8f7d\u4e34\u65f6\u6587\u4ef6\u5931\u8d25: %w", err)
|
||||
}
|
||||
temporaryName := temporary.Name()
|
||||
defer os.Remove(temporaryName)
|
||||
hash := sha256.New()
|
||||
_, copyErr := io.Copy(io.MultiWriter(file, hash), response.Body)
|
||||
closeErr := file.Close()
|
||||
size, copyErr := io.Copy(io.MultiWriter(temporary, hash), response.Body)
|
||||
closeErr := temporary.Close()
|
||||
if copyErr != nil {
|
||||
return Package{}, fmt.Errorf("\u4fdd\u5b58\u5305\u6587\u4ef6\u5931\u8d25: %w", copyErr)
|
||||
}
|
||||
if closeErr != nil {
|
||||
return Package{}, fmt.Errorf("\u5173\u95ed\u4fdd\u5b58\u6587\u4ef6\u5931\u8d25: %w", closeErr)
|
||||
return Package{}, fmt.Errorf("\u5173\u95ed\u4e0b\u8f7d\u4e34\u65f6\u6587\u4ef6\u5931\u8d25: %w", closeErr)
|
||||
}
|
||||
if response.ContentLength >= 0 && size != response.ContentLength {
|
||||
return Package{}, fmt.Errorf("\u5305\u6587\u4ef6\u5927\u5c0f\u6821\u9a8c\u5931\u8d25: \u671f\u671b %d\uff0c\u5b9e\u9645 %d", response.ContentLength, size)
|
||||
}
|
||||
digest := hex.EncodeToString(hash.Sum(nil))
|
||||
expected := response.Header.Get("X-Scout-SHA256")
|
||||
if expected != "" && !strings.EqualFold(expected, digest) {
|
||||
return Package{}, fmt.Errorf("\u5305\u6587\u4ef6\u6821\u9a8c\u5931\u8d25: \u671f\u671b %s\uff0c\u5b9e\u9645 %s", expected, digest)
|
||||
}
|
||||
if err := copyDownloadedFile(temporaryName, destination); err != nil {
|
||||
return Package{}, err
|
||||
}
|
||||
target := Target{
|
||||
Platform: response.Header.Get("X-Scout-Platform"),
|
||||
Architecture: response.Header.Get("X-Scout-Architecture"),
|
||||
PackageType: response.Header.Get("X-Scout-Package-Type"),
|
||||
Size: response.ContentLength,
|
||||
Size: size,
|
||||
SHA256: digest,
|
||||
}
|
||||
return Package{
|
||||
Project: c.project,
|
||||
Name: name,
|
||||
Version: version,
|
||||
Platform: target.Platform,
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -30,6 +32,56 @@ func TestProjectClientUsesProjectScopedPaths(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectClientUsesProjectScopedVersionPaths(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path != "/api/v1/projects/ssl/packages/demo/versions" {
|
||||
t.Fatalf("项目版本请求路径错误:%s", request.URL.Path)
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
_, _ = writer.Write([]byte(`{"name":"demo","versions":[{"name":"demo","version":"1.0.0","project":"ssl"}]}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
api, err := NewProject(server.URL, "ssl")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
versions, err := api.ListVersionsFor(context.Background(), "demo", QueryOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(versions) != 1 || versions[0].Project != "ssl" {
|
||||
t.Fatalf("项目版本响应错误:%+v", versions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectDownloadKeepsDestinationWhenChecksumFails(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.Header().Set("Content-Length", "7")
|
||||
writer.Header().Set("X-Scout-SHA256", "invalid")
|
||||
_, _ = writer.Write([]byte("payload"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
api, err := NewProject(server.URL, "ssl")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
destination := filepath.Join(t.TempDir(), "package.pkg")
|
||||
if err := os.WriteFile(destination, []byte("original"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := api.DownloadFor(context.Background(), "demo", "1.0.0", destination, QueryOptions{}); err == nil {
|
||||
t.Fatal("错误校验值应该导致下载失败")
|
||||
}
|
||||
data, err := os.ReadFile(destination)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(data) != "original" {
|
||||
t.Fatalf("项目下载校验失败不应覆盖原文件:%q", data)
|
||||
}
|
||||
}
|
||||
func TestNewProjectRejectsUnsafeProjectID(t *testing.T) {
|
||||
if _, err := NewProject("http://127.0.0.1", "../ssl"); err == nil {
|
||||
t.Fatal("不安全项目标识应该失败")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user