Skip to content

Commit 40dafaf

Browse files
rdntEquanox
andauthored
Lazily initialize docker registry client (#328)
* Lazily attempt to initialize docker registry client * Exit early if connection to docker daemon fails * Fix mutex bugs * Fix image buildinfo getting "stuck" and requiring killing of the process * change execution order --------- Co-authored-by: equanox <[email protected]>
1 parent 36ec344 commit 40dafaf

File tree

13 files changed

+64
-59
lines changed

13 files changed

+64
-59
lines changed

bob/aggregate.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"path/filepath"
88
"strings"
99

10+
"github.com/benchkram/bob/pkg/dockermobyutil"
11+
1012
"github.com/benchkram/bob/bob/bobfile"
1113
"github.com/benchkram/bob/bob/bobfile/project"
1214
"github.com/benchkram/bob/bob/global"
@@ -158,7 +160,6 @@ func (b *B) Aggregate() (aggregate *bobfile.Bobfile, err error) {
158160
task.WithLocalstore(b.local)
159161
task.WithEnvStore(b.nix.EnvStore())
160162
task.WithBuildinfoStore(b.buildInfoStore)
161-
task.WithDockerRegistryClient(b.dockerRegistryClient)
162163

163164
// a task must always-rebuild when caching is disabled
164165
if !b.enableCaching {
@@ -218,6 +219,32 @@ func (b *B) Aggregate() (aggregate *bobfile.Bobfile, err error) {
218219
aggregate.Project = aggregate.Dir()
219220
}
220221

222+
var dockerRegistryClientInitialized bool
223+
224+
// Assure tasks are correctly initialised with a docker registry client.
225+
// Only one registry client must be created and shared between tasks,
226+
// this reduces the pressure on garbage collection for big repos.
227+
for i, task := range aggregate.BTasks {
228+
target, err := task.Target()
229+
errz.Fatal(err)
230+
231+
if target != nil && len(target.DockerImages()) > 0 {
232+
if !dockerRegistryClientInitialized {
233+
b.dockerRegistryClient, err = dockermobyutil.NewRegistryClient()
234+
if errors.Is(err, dockermobyutil.ErrConnectionFailed) {
235+
errz.Fatal(usererror.Wrapm(err, fmt.Sprintf("task `%s` exports an image, but docker is not reachable", task.Name())))
236+
}
237+
errz.Fatal(err)
238+
239+
dockerRegistryClientInitialized = true
240+
}
241+
242+
task.WithDockerRegistryClient(b.dockerRegistryClient)
243+
// modify index on map since tasks are passed by value
244+
aggregate.BTasks[i] = task
245+
}
246+
}
247+
221248
err = aggregate.Verify()
222249
errz.Fatal(err)
223250

bob/bob.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ func newBob(opts ...Option) *B {
8181
enableCaching: true,
8282
allowInsecure: false,
8383
maxParallel: runtime.NumCPU(),
84-
85-
dockerRegistryClient: dockermobyutil.NewRegistryClient(),
8684
}
8785

8886
for _, opt := range opts {

bob/bobfile/bobfile.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"path/filepath"
99
"strings"
1010

11-
"github.com/benchkram/bob/pkg/dockermobyutil"
1211
"github.com/benchkram/bob/pkg/nix"
1312
storeclient "github.com/benchkram/bob/pkg/store-client"
1413

@@ -139,9 +138,6 @@ func bobfileRead(dir string) (_ *Bobfile, err error) {
139138
bobfile.RTasks = bobrun.RunMap{}
140139
}
141140

142-
// a shared registry clients for all tasks.
143-
dockerRegistryClient := dockermobyutil.NewRegistryClient()
144-
145141
// Assure tasks are initialized with their defaults
146142
for key, task := range bobfile.BTasks {
147143
task.SetDir(bobfile.dir)
@@ -153,7 +149,6 @@ func bobfileRead(dir string) (_ *Bobfile, err error) {
153149
// This means switching to pointer types for most members.
154150
task.SetEnv([]string{})
155151
task.SetRebuildStrategy(bobtask.RebuildOnChange)
156-
task.WithDockerRegistryClient(dockerRegistryClient)
157152

158153
// initialize docker registry for task
159154
task.SetDependencies(initializeDependencies(dir, task.DependenciesDirty, bobfile))

bob/playbook/build_internal.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,10 @@ func (p *Playbook) build(ctx context.Context, task *bobtask.Task) (pt *processed
130130
taskSuccessFul = true
131131

132132
err = p.TaskCompleted(task.TaskID)
133-
if err != nil {
134-
if errors.Is(err, ErrFailed) {
135-
return pt, err
136-
}
133+
if errors.Is(err, ErrFailed) {
134+
return pt, err
137135
}
136+
errz.Log(err)
138137
errz.Fatal(err)
139138

140139
taskStatus, err := p.TaskStatus(task.Name())

bobtask/target.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ import (
1010
)
1111

1212
// Target takes care of populating the targets members correctly.
13-
// It returns a nil in case of a non existing target and a nil error.
13+
// It returns a nil in case of a non-existing target and a nil error.
1414
func (t *Task) Target() (empty target.Target, _ error) {
1515
if t.target == nil {
1616
return empty, nil
1717
}
1818

19+
// attach docker registry client (if set) to target itself
20+
t.target.WithDockerRegistryClient(t.dockerRegistryClient)
21+
1922
// ReadBuildInfo is dependent on the inputHash of the task.
2023
// For this reason we cannot read build info on target creation,
2124
// as this happens right after parsing the config.
@@ -41,8 +44,10 @@ func (t *Task) Target() (empty target.Target, _ error) {
4144
return t.target, t.target.Resolve()
4245
}
4346

44-
tt := t.target.WithExpected(&buildInfo.Target)
45-
return tt, tt.Resolve()
47+
// attach expected buildinfo
48+
t.target.WithExpected(&buildInfo.Target)
49+
50+
return t.target, t.target.Resolve()
4651
}
4752

4853
func (t *Task) TargetExists() bool {

bobtask/target/options.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package target
22

3-
import (
4-
"github.com/benchkram/bob/bobtask/buildinfo"
5-
"github.com/benchkram/bob/pkg/dockermobyutil"
6-
)
7-
83
type Option func(t *T)
94

105
func WithDir(dir string) Option {
@@ -24,15 +19,3 @@ func WithDockerImages(images []string) Option {
2419
t.dockerImages = images
2520
}
2621
}
27-
28-
func WithDockerRegistryClient(dockerRegistryClient dockermobyutil.RegistryClient) Option {
29-
return func(t *T) {
30-
t.dockerRegistryClient = dockerRegistryClient
31-
}
32-
}
33-
34-
func WithExpected(bi *buildinfo.Targets) Option {
35-
return func(t *T) {
36-
t.expected = bi
37-
}
38-
}

bobtask/target/target.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type Target interface {
1919
FilesystemEntriesRaw() []string
2020
FilesystemEntriesRawPlain() []string
2121

22-
WithExpected(*buildinfo.Targets) *T
22+
WithExpected(*buildinfo.Targets)
2323
DockerImages() []string
2424

2525
// AsInvalidFiles returns all FilesystemEntriesRaw as invalid with the specified reason
@@ -68,10 +68,6 @@ func New(opts ...Option) *T {
6868
opt(t)
6969
}
7070

71-
if t.dockerRegistryClient == nil {
72-
t.dockerRegistryClient = dockermobyutil.NewRegistryClient()
73-
}
74-
7571
return t
7672
}
7773

@@ -109,9 +105,12 @@ func (t *T) FilesystemEntriesRawPlain() []string {
109105
return append([]string{}, t.filesystemEntriesRaw...)
110106
}
111107

112-
func (t *T) WithExpected(expected *buildinfo.Targets) *T {
108+
func (t *T) WithExpected(expected *buildinfo.Targets) {
113109
t.expected = expected
114-
return t
110+
}
111+
112+
func (t *T) WithDockerRegistryClient(c dockermobyutil.RegistryClient) {
113+
t.dockerRegistryClient = c
115114
}
116115

117116
func (t *T) DockerImages() []string {

bobtask/target_parse.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ func (t *Task) parseTargets() error {
7575
target.WithFilesystemEntries(filesystemEntries),
7676
target.WithDockerImages(dockerImages),
7777
target.WithDir(t.dir),
78-
target.WithDockerRegistryClient(t.dockerRegistryClient),
7978
)
8079
}
8180

bobtask/task.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,6 @@ func Make(opts ...TaskOption) Task {
135135
opt(&t)
136136
}
137137

138-
if t.dockerRegistryClient == nil {
139-
t.dockerRegistryClient = dockermobyutil.NewRegistryClient()
140-
}
141-
142138
return t
143139
}
144140

pkg/dockermobyutil/registry.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import (
1818
)
1919

2020
var (
21-
ErrImageNotFound = fmt.Errorf("image not found")
21+
ErrImageNotFound = fmt.Errorf("image not found")
22+
ErrConnectionFailed = errors.New("connection to docker daemon failed")
2223
)
2324

2425
type RegistryClient interface {
@@ -45,7 +46,7 @@ type R struct {
4546
mutex *sync.Mutex
4647
}
4748

48-
func NewRegistryClient() RegistryClient {
49+
func NewRegistryClient() (RegistryClient, error) {
4950
cli, err := client.NewClientWithOpts(
5051
client.FromEnv,
5152
client.WithAPIVersionNegotiation(),
@@ -59,14 +60,19 @@ func NewRegistryClient() RegistryClient {
5960
archiveDir: os.TempDir(),
6061
}
6162

62-
// Use a lock to supress parallel image reads on zfs.
63+
// Use a lock to suppress parallel image reads on zfs.
6364
info, err := r.client.Info(context.Background())
64-
errz.Log(err)
65+
if client.IsErrConnectionFailed(err) {
66+
return nil, ErrConnectionFailed
67+
} else if err != nil {
68+
return nil, err
69+
}
70+
6571
if info.Driver == "zfs" {
6672
r.mutex = &sync.Mutex{}
6773
}
6874

69-
return r
75+
return r, nil
7076
}
7177

7278
func (r *R) ImageExists(image string) (bool, error) {
@@ -107,17 +113,12 @@ func (r *R) ImageHash(image string) (string, error) {
107113
func (r *R) imageSaveToPath(image string, savedir string) (pathToArchive string, _ error) {
108114
if r.mutex != nil {
109115
r.mutex.Lock()
116+
defer r.mutex.Unlock()
110117
}
111118
reader, err := r.client.ImageSave(context.Background(), []string{image})
112119
if err != nil {
113-
if r.mutex != nil {
114-
r.mutex.Unlock()
115-
}
116120
return "", err
117121
}
118-
if r.mutex != nil {
119-
r.mutex.Unlock()
120-
}
121122
defer reader.Close()
122123

123124
body, err := io.ReadAll(reader)

test/e2e/artifacts/artifacts_extraction_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ var _ = Describe("Test artifact creation and extraction", func() {
7777
var _ = Describe("Test artifact creation and extraction from docker targets", func() {
7878
Context("in a fresh playground", func() {
7979

80-
mobyClient := dockermobyutil.NewRegistryClient()
80+
mobyClient, err := dockermobyutil.NewRegistryClient()
81+
Expect(err).NotTo(HaveOccurred())
8182

8283
It("should initialize bob playground", func() {
8384
Expect(bob.CreatePlayground(bob.PlaygroundOptions{Dir: dir})).NotTo(HaveOccurred())

test/e2e/artifacts/artifacts_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,12 @@ var _ = Describe("Test artifact and target invalidation", func() {
102102
})
103103
})
104104

105-
// docker targets
105+
// docker targets
106106
var _ = Describe("Test artifact and docker-target invalidation", func() {
107107
Context("in a fresh playground", func() {
108108

109-
mobyClient := dockermobyutil.NewRegistryClient()
109+
mobyClient, err := dockermobyutil.NewRegistryClient()
110+
Expect(err).NotTo(HaveOccurred())
110111

111112
It("should initialize bob playground", func() {
112113
Expect(bob.CreatePlayground(bob.PlaygroundOptions{Dir: dir})).NotTo(HaveOccurred())

test/e2e/artifacts/nobuildinfo_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ var _ = Describe("Test artifact and target lifecycle without existing buildinfo"
104104
var _ = Describe("Test artifact and target lifecycle for docker images without existing buildinfo", func() {
105105
Context("in a fresh playground", func() {
106106

107-
mobyClient := dockermobyutil.NewRegistryClient()
107+
mobyClient, err := dockermobyutil.NewRegistryClient()
108+
Expect(err).NotTo(HaveOccurred())
108109

109110
It("should initialize bob playground", func() {
110111
Expect(bob.CreatePlayground(bob.PlaygroundOptions{Dir: dir})).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)