This repository has been archived by the owner on Apr 28, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 36
/
hat_builder.go
131 lines (108 loc) · 3.41 KB
/
hat_builder.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
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/client"
"golang.org/x/xerrors"
"go.coder.com/flog"
"go.coder.com/sail/internal/hat"
"go.coder.com/sail/internal/xexec"
)
// hatBuilder is responsible for applying a hat to a base image.
// The hatBuilder passes any sail labels to the runner through
// setting them in the image. Besides this, the runner should
// have no knowledge of the hatBuilder existing.
type hatBuilder struct {
// hatPath is the path to the hat file.
hatPath string
// baseImage is the image before the hat is applied.
baseImage string
}
// dockerClient returns an instantiated docker client that
// is using the correct API version. If the client can't be
// constructed, this will panic.
func dockerClient() *client.Client {
cli, err := client.NewEnvClient()
if err != nil {
panicf("failed to make docker client: %v", err)
}
// Update the API version of the client to match
// what the server is running.
cli.NegotiateAPIVersion(context.Background())
return cli
}
func (b *hatBuilder) resolveHatPath() (string, error) {
const ghPrefix = "github:"
hatPath := b.hatPath
if strings.HasPrefix(b.hatPath, ghPrefix) {
hatPath = strings.TrimLeft(b.hatPath, ghPrefix)
return hat.ResolveGitHubPath(hatPath)
}
hostHomeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return resolvePath(hostHomeDir, hatPath), nil
}
// applyHat applies the hat to the base image.
func (b *hatBuilder) applyHat() (string, error) {
if b.hatPath == "" {
return "", xerrors.New("unable to apply hat, none specified")
}
hatPath, err := b.resolveHatPath()
if err != nil {
return "", xerrors.Errorf("failed to resolve hat path: %w", err)
}
dockerFilePath := hatPath
if base := filepath.Base(hatPath); strings.ToLower(base) != "dockerfile" {
dockerFilePath = filepath.Join(hatPath, "Dockerfile")
}
dockerFileByt, err := ioutil.ReadFile(dockerFilePath)
if err != nil {
return "", xerrors.Errorf("failed to read %v: %w", dockerFilePath, err)
}
dockerFileByt = hat.DockerReplaceFrom(dockerFileByt, b.baseImage)
fi, err := ioutil.TempFile("", "hat")
if err != nil {
return "", xerrors.Errorf("failed to create temp file: %w", err)
}
defer fi.Close()
defer os.Remove(fi.Name())
_, err = fi.Write(dockerFileByt)
if err != nil {
return "", xerrors.Errorf("failed to write to %v: %w", fi.Name(), err)
}
// We tag based on the checksum of the Dockerfile to avoid spamming
// images.
csm := sha256.Sum256(dockerFileByt)
imageName := b.baseImage + "-hat-" + hex.EncodeToString(csm[:])[:16]
flog.Info("building hat image %v", imageName)
cmd := xexec.Fmt("docker build --network=host -t %v -f %v %v --label %v=%v --label %v=%v",
imageName, fi.Name(), hatPath, baseImageLabel, b.baseImage, hatLabel, b.hatPath,
)
xexec.Attach(cmd)
err = cmd.Run()
if err != nil {
return "", xerrors.Errorf("failed to build hatted baseImage: %w", err)
}
return imageName, nil
}
// hatBuilderFromContainer gets a hatBuilder from container named
// name.
func hatBuilderFromContainer(name string) (*hatBuilder, error) {
cli := dockerClient()
defer cli.Close()
cnt, err := cli.ContainerInspect(context.Background(), name)
if err != nil {
return nil, xerrors.Errorf("failed to inspect %v: %w", name, err)
}
return &hatBuilder{
baseImage: cnt.Config.Labels[baseImageLabel],
hatPath: cnt.Config.Labels[hatLabel],
}, nil
}