Build a change once: the check builds what its merge moves, the walk takes it by key
mesh/merge-gate out of date: judged against trunk bff5b7f0e4f8, the trunk is now 372faebf2824; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk bff5b7f0e4f8, the trunk is now 372faebf2824; judged again on a recheck or when it is merged
mesh/delivery-group group feat/350-one-build-per-change checking: composed check out of date: novox/mesh-controller@cd49b50c4075 was judged against trunk bff5b…
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate out of date: judged against trunk bff5b7f0e4f8, the trunk is now 372faebf2824; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk bff5b7f0e4f8, the trunk is now 372faebf2824; judged again on a recheck or when it is merged
mesh/delivery-group group feat/350-one-build-per-change checking: composed check out of date: novox/mesh-controller@cd49b50c4075 was judged against trunk bff5b…
mesh/delivery superseded: a newer head of the same pull request
A merge built every moved module a second time although its check had judged the same tree (issue 649). The check now builds into the scratch namespace under a reuse key (source fingerprint plus the builder's own digest), and the walk of the trunk commit copies a build of the same key by digest instead of building, saying per module why when it cannot (novox/hq ADR 0350).
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package artifacts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Listing and letting go of tagged documents: the check builds the scratch namespace keeps (novox/hq ADR 0350
|
||||
// §5). A check build is found by its tag, and the build seat's holder lets each go when its pull request closes
|
||||
// or it is seven days old, so the store keeps a week of check builds and no more.
|
||||
|
||||
// Tags is every tag repository holds, in the store's order. None for a repository the store does not hold.
|
||||
func (s Store) Tags(ctx context.Context, repository string) ([]string, error) {
|
||||
if s.Address == "" {
|
||||
return nil, fmt.Errorf("this mesh has no artifact store on its network to list %s in", repository)
|
||||
}
|
||||
var tags []string
|
||||
next := "http://" + s.Address + "/v2/" + repository + "/tags/list?n=1000"
|
||||
for next != "" {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, next, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := s.client().Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot reach the artifact store at %s: %w", s.Address, err)
|
||||
}
|
||||
switch response.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusNotFound:
|
||||
response.Body.Close()
|
||||
return tags, nil
|
||||
default:
|
||||
response.Body.Close()
|
||||
return nil, fmt.Errorf("the artifact store answered %s for the tags of %s", response.Status, repository)
|
||||
}
|
||||
var page struct {
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
err = json.NewDecoder(io.LimitReader(response.Body, 8<<20)).Decode(&page)
|
||||
link := response.Header.Get("Link")
|
||||
response.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the tags of %s could not be read: %w", repository, err)
|
||||
}
|
||||
tags = append(tags, page.Tags...)
|
||||
next = nextPage(link, s.Address)
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// nextPage is the registry's `Link: </v2/…>; rel="next"`, made whole; empty when there is no next page.
|
||||
func nextPage(link, address string) string {
|
||||
if !strings.Contains(link, `rel="next"`) {
|
||||
return ""
|
||||
}
|
||||
start, end := strings.Index(link, "<"), strings.Index(link, ">")
|
||||
if start < 0 || end <= start+1 {
|
||||
return ""
|
||||
}
|
||||
target := link[start+1 : end]
|
||||
if strings.HasPrefix(target, "/") {
|
||||
return "http://" + address + target
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// DeleteTagged lets go of the document tag names in repository: its manifest, by digest, so the next
|
||||
// collection takes its layer. Gone when the tag names nothing.
|
||||
func (s Store) DeleteTagged(ctx context.Context, repository, tag string) error {
|
||||
if s.Address == "" {
|
||||
return fmt.Errorf("this mesh has no artifact store on its network to let go of %s:%s in", repository, tag)
|
||||
}
|
||||
digest, err := s.manifestDigest(ctx, repository, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if digest == "" {
|
||||
return Gone
|
||||
}
|
||||
err = s.remove(ctx, s.url(repository, "manifests", digest), repository+":"+tag)
|
||||
if errors.Is(err, Gone) {
|
||||
return Gone
|
||||
}
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user