-
Notifications
You must be signed in to change notification settings - Fork 388
/
docker.go
148 lines (131 loc) · 4.26 KB
/
docker.go
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package dockerutil
import (
"context"
"fmt"
"io"
"strings"
"time"
"github.com/earthly/earthly/conslogging"
"github.com/earthly/earthly/util/containerutil"
"github.com/earthly/earthly/util/platutil"
"golang.org/x/sync/errgroup"
"github.com/pkg/errors"
)
// Manifest contains docker manifest data
type Manifest struct {
ImageName string
Platform platutil.Platform
}
// LoadDockerManifest loads docker manifests
func LoadDockerManifest(ctx context.Context, console conslogging.ConsoleLogger, fe containerutil.ContainerFrontend, parentImageName string, children []Manifest, platr *platutil.Resolver) error {
if len(children) == 0 {
return errors.Errorf("no images in manifest list for %s", parentImageName)
}
// Check if any child has the platform as the default platform
defaultChild := 0
foundPlatform := false
for i, child := range children {
if platr.PlatformEquals(child.Platform, platutil.DefaultPlatform) {
defaultChild = i
foundPlatform = true
break
}
}
if !foundPlatform {
// fall back to using first defined platform (and display a warning)
console.Warnf(
"Failed to find default platform (%s) of multi-platform image %s; defaulting to the first platform type: %s\n",
platr.Materialize(platutil.DefaultPlatform).String(), parentImageName, children[defaultChild].Platform)
}
var childImgs []string
for i, child := range children {
if i == defaultChild {
childImgs = append(childImgs, fmt.Sprintf("%s (=%s)", child.ImageName, parentImageName))
} else {
childImgs = append(childImgs, child.ImageName)
}
}
const noteDetail = "Note that when pushing a multi-platform image, " +
"it is pushed as a single multi-manifest image. " +
"Separate per-platform image tags are only available locally."
console.Printf(
"Image %s is a multi-platform image. The following per-platform images have been produced:\n\t%s\n%s\n",
parentImageName, strings.Join(childImgs, "\n\t"), noteDetail)
err := fe.ImageTag(ctx, containerutil.ImageTag{
SourceRef: children[defaultChild].ImageName,
TargetRef: parentImageName,
})
if err != nil {
return errors.Wrap(err, "docker tag default platform image")
}
return nil
}
// LoadDockerTar loads a docker image via a tar
func LoadDockerTar(ctx context.Context, fe containerutil.ContainerFrontend, r io.ReadCloser) error {
err := fe.ImageLoad(ctx, r)
if err != nil {
return errors.Wrapf(err, "load tar")
}
return nil
}
// DockerPullLocalImages pulls a docker image from a local registry
func DockerPullLocalImages(ctx context.Context, fe containerutil.ContainerFrontend, localRegistryAddr string, pullMap map[string]string) error {
eg, ctx := errgroup.WithContext(ctx)
for pullName, finalName := range pullMap {
pn := pullName
fn := finalName
eg.Go(func() error {
return dockerPullLocalImage(ctx, fe, localRegistryAddr, pn, fn)
})
}
return eg.Wait()
}
func dockerPullLocalImage(ctx context.Context, fe containerutil.ContainerFrontend, localRegistryAddr string, pullName string, finalName string) error {
fullPullName := fmt.Sprintf("%s/%s", localRegistryAddr, pullName)
err := fe.ImagePull(ctx, fullPullName)
if err != nil {
return errors.Wrap(err, "image pull")
}
// Fix for #2471 where Podman pulls seem exit before the image is available
// for tagging. Wait for the image to become available.
err = waitForImage(ctx, fe, fullPullName)
if err != nil {
return err
}
err = fe.ImageTag(ctx, containerutil.ImageTag{
SourceRef: fullPullName,
TargetRef: finalName,
})
if err != nil {
return errors.Wrap(err, "image tag after pull")
}
force := true // Sometimes Docker GCs images automatically (force prevents an error).
err = fe.ImageRemove(ctx, force, fullPullName)
if err != nil {
return errors.Wrap(err, "image rmi after pull and retag")
}
return nil
}
func waitForImage(ctx context.Context, fe containerutil.ContainerFrontend, fullName string) error {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
m, err := fe.ImageInfo(ctx, fullName)
if err != nil {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(100 * time.Millisecond):
continue // Not available. Retry.
}
}
if info, ok := m[fullName]; ok && info.ID != "" {
return nil
}
}
}
}