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).
92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
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
|
|
}
|