/
export.go
170 lines (159 loc) · 4.14 KB
/
export.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package cnbp2llb
import (
"bytes"
"context"
"fmt"
"os"
"path"
"strings"
"github.com/BurntSushi/toml"
"github.com/EricHripko/buildkit-fdk/pkg/cib"
cnbp "github.com/buildpacks/lifecycle"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/pkg/errors"
fsutil "github.com/tonistiigi/fsutil/types"
)
// Export the produced layers into an OCI image. Unlike other high-level
// functions in this package, we have to manage the export manually (without
// lifecycle) to fit in with the BuildKit model of the world.
func Export(ctx context.Context, build cib.Service, built llb.State, cache llb.RunOption) (ref client.Reference, img *dockerfile2llb.Image, err error) {
// Shall write the contents of all cached layers to the cache
// Shall record the diffID and layer content metadata of all cached layers
// in the cache
// Relying on an external image since BuildKit doesn't provide a way to
// inject the binary from the frontend.
// See: https://github.com/moby/buildkit/issues/2063
built = built.Run(
llb.Args([]string{"/frontend/go/bin/cacher"}),
llb.WithCustomName("Populating cache"),
// Mount frontend for the cacher binary
llb.AddMount(
"/frontend",
// TODO: can we make this dynamic
llb.Image("erichripko/cnbp"),
),
cache,
).Root()
ref, err = build.Solve(ctx, built)
if err != nil {
return
}
// Read the stack and group
var groups cnbp.BuildpackGroup
err = readToml(ctx, ref, path.Join(LayersDir, GroupPath), &groups)
if err != nil {
return
}
// Find launch layers
var launchLayers []string
for _, group := range groups.Group {
id := strings.ReplaceAll(group.ID, "/", "_")
groupPath := path.Join(LayersDir, id)
var files []*fsutil.Stat
files, err = ref.ReadDir(ctx, client.ReadDirRequest{Path: groupPath})
if err != nil {
return
}
for _, file := range files {
mode := os.FileMode(file.Mode)
if !mode.IsDir() {
continue
}
// Maybe found a layer, attempt to read its metadata
var metadata cnbp.BuildpackLayerMetadata
err = readToml(
ctx,
ref,
path.Join(groupPath, path.Base(file.Path)+".toml"),
&metadata,
)
if err == nil && metadata.Launch {
// Found a launch layer
launchLayers = append(
launchLayers,
path.Join(groupPath, file.Path),
)
}
}
if err != nil {
return
}
}
// Produce the end OCI image
var stack cnbp.StackMetadata
err = readToml(ctx, ref, StackPath, &stack)
if err != nil {
return
}
platform, err := built.GetPlatform(ctx)
if err != nil {
return
}
// Must be an extension of the <run-image>
state, img, err := build.From(
stack.RunImage.Image,
platform,
fmt.Sprintf("Run image is %s", stack.RunImage.Image),
)
if err != nil {
return
}
// Must contain one or more launcher layers
state = state.File(
llb.Copy(
built,
LauncherPath,
LauncherPath,
&llb.CopyInfo{CreateDestPath: true},
),
llb.WithCustomName("Exporting launcher"),
)
// Must contain all buildpack-provided launch layers
for _, layer := range launchLayers {
state = state.File(
llb.Copy(
built,
layer,
layer,
&llb.CopyInfo{CreateDestPath: true},
),
llb.WithCustomNamef("Exporting buildpack layer %s", layer),
)
}
// Must contain one or more app layers
state = state.File(
llb.Copy(
built,
AppDir,
AppDir,
&llb.CopyInfo{CopyDirContentsOnly: true},
),
llb.WithCustomName("Exporting app layer"),
)
// Must contain a layer that includes metadata.toml
metadata := path.Join(LayersDir, MetadataPath)
state = state.File(
llb.Copy(
built,
metadata,
metadata,
&llb.CopyInfo{CreateDestPath: true},
),
llb.WithCustomName("Exporting build metadata"),
)
ref, err = build.Solve(ctx, state)
return
}
func readToml(ctx context.Context, ref client.Reference, path string, v interface{}) error {
data, err := ref.ReadFile(ctx, client.ReadRequest{Filename: path})
if err != nil {
return errors.Wrapf(err, "failed to read %s", path)
}
_, err = toml.DecodeReader(bytes.NewReader(data), v)
if err != nil {
return errors.Wrapf(err, "failed to decode %s", path)
}
return nil
}