Repository navigation
feat: serve plugins with dependencies at the commit pinned by the Go module proxy #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
adabd04
46da4c8
ac8eadf
39bba2d
801d327
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| package handlers | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "strings" | ||
|
|
||
| "github.com/rs/zerolog/log" | ||
| "golang.org/x/mod/module" | ||
| ) | ||
|
|
||
| // errNotPinned the Go module proxy does not know which commit the version was published from. | ||
| var errNotPinned = errors.New("no pinned commit") | ||
|
|
||
| // archiveRef returns the ref to download the GitHub zipball of moduleName@version from. | ||
| // | ||
| // It is the tag while the tag still points at the commit pinned by the Go module proxy at publication, | ||
| // and the pinned commit once the tag moved or was deleted. | ||
| // Requesting the tag keeps the archive bytes: the zipball of an annotated tag is named after the tag object, not the commit. | ||
| // The pinned commit of a moved annotated tag cannot give those bytes back, nor match a hash recorded after the move: | ||
| // those hashes are realigned with `piceus realign --only`, reviewed one by one. | ||
| // Without an answer from the Go module proxy, it falls back to the tag, as before pinning. | ||
| func (h Handlers) archiveRef(ctx context.Context, owner, repoName, moduleName, version string) (string, error) { | ||
| if h.pinProxyURL == "" { | ||
| return version, nil | ||
| } | ||
|
|
||
| pinned, err := h.pinnedCommit(ctx, moduleName, version) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: the pinned commit never changes, but we look it up on every download. Could we store it? It will also reduce number of proxy calls.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, cached in memory per module@version. Only successful lookups are cached, as the proxy may learn a version later. |
||
| if errors.Is(err, errNotPinned) { | ||
| return version, nil | ||
| } | ||
|
|
||
| if err != nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The description says a proxy error fails the download, but here we serve the tag. Please update the description.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, it changed after the first review. Description updated. |
||
| log.Warn().Err(err).Str("module_name", moduleName).Str("module_version", version). | ||
| Msg("Failed to get the pinned commit, serving the tag") | ||
|
|
||
| return version, nil | ||
| } | ||
|
|
||
| // A conditional request answered 304 is not counted in the GitHub rate limit. | ||
| sha, resp, err := h.gh.Repositories.GetCommitSHA1(ctx, owner, repoName, version, pinned) | ||
|
|
||
| unchanged := (resp != nil && resp.StatusCode == http.StatusNotModified) || (err == nil && sha == pinned) | ||
|
mmatur marked this conversation as resolved.
Resolved
|
||
| moved := err == nil | ||
| deleted := resp != nil && (resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusUnprocessableEntity) | ||
|
|
||
| switch { | ||
| case unchanged: | ||
| return version, nil | ||
|
mmatur marked this conversation as resolved.
Resolved
|
||
| case moved: | ||
| log.Info().Str("module_name", moduleName).Str("module_version", version). | ||
| Str("tag_commit", sha).Str("pinned_commit", pinned). | ||
| Msg("Tag moved after publication, serving the pinned commit") | ||
|
|
||
| return pinned, nil | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a version was first downloaded after its tag moved, its recorded hash is the moved one, and this now breaks it. How many versions are in that case?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 10 of the 2,159 public versions. They will be realigned with |
||
| case deleted: | ||
| log.Info().Str("module_name", moduleName).Str("module_version", version). | ||
| Str("pinned_commit", pinned).Int("status", resp.StatusCode). | ||
| Msg("Tag not found, serving the pinned commit") | ||
|
|
||
| return pinned, nil | ||
|
mmatur marked this conversation as resolved.
Resolved
|
||
| default: | ||
| return "", fmt.Errorf("resolve tag %s: %w", version, err) | ||
| } | ||
| } | ||
|
|
||
| // pinnedCommit returns the commit the Go module proxy pinned when moduleName@version was published. | ||
| func (h Handlers) pinnedCommit(ctx context.Context, moduleName, version string) (string, error) { | ||
| key := moduleName + "@" + version | ||
|
|
||
| if cached, ok := h.pins.Load(key); ok { | ||
| if pinned, ok := cached.(string); ok { | ||
| return pinned, nil | ||
| } | ||
| } | ||
|
|
||
| pinned, err := h.fetchPinnedCommit(ctx, moduleName, version) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| h.pins.Store(key, pinned) | ||
|
|
||
| return pinned, nil | ||
| } | ||
|
|
||
| func (h Handlers) fetchPinnedCommit(ctx context.Context, moduleName, version string) (string, error) { | ||
| escModule, err := module.EscapePath(moduleName) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| escVersion, err := module.EscapeVersion(version) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| endpoint := fmt.Sprintf("%s/%s/@v/%s.info", strings.TrimSuffix(h.pinProxyURL, "/"), escModule, escVersion) | ||
|
|
||
| //nolint:gosec // G704: the host is the configured Go module proxy. | ||
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| resp, err := h.httpClient.Do(req) //nolint:gosec // G704: same request. | ||
| if err != nil { | ||
| return "", fmt.Errorf("get %s: %w", endpoint, err) | ||
| } | ||
|
|
||
| defer func() { _ = resp.Body.Close() }() | ||
|
|
||
| switch resp.StatusCode { | ||
| case http.StatusOK: | ||
| case http.StatusNotFound, http.StatusGone: | ||
| return "", errNotPinned | ||
| default: | ||
| return "", fmt.Errorf("get %s: status %d", endpoint, resp.StatusCode) | ||
| } | ||
|
|
||
| body, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| var info struct { | ||
| Origin *struct { | ||
| Hash string `json:"Hash"` //nolint:tagliatelle // Go module proxy protocol. | ||
| } `json:"Origin"` //nolint:tagliatelle // Go module proxy protocol. | ||
| } | ||
|
|
||
| if err = json.Unmarshal(body, &info); err != nil { | ||
| return "", fmt.Errorf("decode %s: %w", endpoint, err) | ||
| } | ||
|
|
||
| if info.Origin == nil || info.Origin.Hash == "" { | ||
| return "", errNotPinned | ||
| } | ||
|
|
||
| return info.Origin.Hash, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add
--pin-proxy-urlto the readme.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.