| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
 | package main
import (
	"context"
	"log"
	"path"
	"sync"
	"time"
)
// A PackageCache holds information about Go packages and their upstream
// repositories.
//
// Given a package name, it looks for a similarly named repository in one of
// the upstream hosts and holds on to the result for quicker subsequent access.
type PackageCache struct {
	// ExpireAfter is how long to consider a found repository valid for. As
	// repositories do not frequently move around, this could be rather long,
	// though it also determines how long a user might have to wait for a new
	// repository to become available if they previously got a 404.
	ExpireAfter time.Duration
	// UpstreamTimeout is how long we should allow HTTP requests to upstream
	// hosts to carry on before they are canceled.
	UpstreamTimeout time.Duration
	// Hosts are the RepoHosts which should be checked for a repository matching
	// the package name. Hosts are checked in order; if two hosts both have a
	// repo with the desired name, the repo host which is listed first will
	// "win".
	Hosts []RepoHost
	// Logger is a logger for diagnostics
	Logger *log.Logger
	// CanonicalPrefix is the package prefix to use when naming packages. E.g.,
	// when looking for a package named "conf", we'll treat the package name as
	// CanonicalPrefix + "/conf", e.g. burwell.io/conf.
	CanonicalPrefix string
	entries sync.Map // concurrency-safe map[string]entry
}
type entry struct {
	t   time.Time
	pkg *Package
}
// A Package represents a Go package's canonical name and its source git
// repository.
type Package struct {
	Name string
	Repo string
}
// Get loads package name from the cache, consulting upstream repositories to
// find a matching repo if not present. As this method is meant to be called in
// the critical path of an incoming HTTP request, it can also be cancelled with
// the supplied context.
func (c *PackageCache) Get(ctx context.Context, name string) (Package, bool) {
	val, ok := c.entries.Load(name)
	if !ok {
		return c.load(ctx, name)
	}
	ent := val.(entry)
	if time.Now().After(ent.t.Add(c.ExpireAfter)) {
		return c.load(ctx, name)
	}
	if ent.pkg == nil {
		return Package{}, false
	}
	return *ent.pkg, true
}
// load looks for a package in the upstream repos, and if found, stores it in
// the cache. If no matching repo is found, a nil entry is stored in the map so
// we don't immediately re-check the upstreams for subsequent requests.
func (c *PackageCache) load(ctx context.Context, name string) (Package, bool) {
	pkg, ok := c.find(ctx, name)
	if !ok {
		c.entries.Store(name, entry{t: time.Now()})
		return Package{}, false
	}
	c.entries.Store(name, entry{t: time.Now(), pkg: &pkg})
	return pkg, true
}
// requestContext creates a context for making HTTP requests to upstream hosts.
func (c *PackageCache) requestContext(ctx context.Context) (context.Context, context.CancelFunc) {
	if c.UpstreamTimeout == 0 {
		return ctx, func() {}
	}
	return context.WithTimeout(ctx, c.UpstreamTimeout)
}
// logf logs a message using the configured logger, if any.
func (c *PackageCache) logf(msg string, args ...interface{}) {
	if c.Logger == nil {
		return
	}
	c.Logger.Printf(msg, args...)
}
// find consults the configured upstream hosts to try to find a matching repo
// for package name.
func (c *PackageCache) find(ctx context.Context, name string) (Package, bool) {
	c.logf("loading %s", name)
	for _, h := range c.Hosts {
		reqCtx, cancel := c.requestContext(ctx)
		defer cancel()
		repo, ok, err := h.HostsRepo(reqCtx, name)
		if err != nil {
			c.logf("error checking upstream for repo %s: %v", name, err)
			return Package{}, false
		}
		if ok {
			c.logf("found repo %s: %s", name, repo)
			return Package{
				Name: path.Join(c.CanonicalPrefix, name),
				Repo: repo,
			}, true
		}
	}
	c.logf("could not find repo %s on any host", name)
	return Package{}, false
}
 |