Skip to content

Commit 6c0cbc5

Browse files
committed
Refactor the way we annotate and set the config of the final image
Change the way we set up the configuration of the final image in order to support the reuse of the base image's config. In that way, we can maintain the env variables, labels and other information from the base image without overwriting them. Also, improve the readability of the code and create smaller helper functions with a particular purpose. Signed-off-by: Charalampos Mainas <cmainas@nubificus.co.uk>
1 parent dfbed1e commit 6c0cbc5

File tree

2 files changed

+135
-43
lines changed

2 files changed

+135
-43
lines changed

cmd/main.go

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,16 @@ package main
1616

1717
import (
1818
"context"
19-
"encoding/json"
2019
"flag"
2120
"fmt"
2221
"os"
23-
"runtime"
24-
"strings"
2522

2623
"bunny/hops"
2724

2825
"github.com/moby/buildkit/client/llb"
29-
"github.com/moby/buildkit/exporter/containerimage/exptypes"
3026
"github.com/moby/buildkit/frontend/gateway/client"
3127
"github.com/moby/buildkit/frontend/gateway/grpcclient"
3228
"github.com/moby/buildkit/util/appcontext"
33-
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
3429
)
3530

3631
const (
@@ -105,40 +100,6 @@ func readFileFromLLB(ctx context.Context, c client.Client, filename string) ([]b
105100
return fileBytes, nil
106101
}
107102

108-
func annotateRes(annots map[string]string, res *client.Result) (*client.Result, error) {
109-
ref, err := res.SingleRef()
110-
if err != nil {
111-
return nil, fmt.Errorf("Failed te get reference build result: %v", err)
112-
}
113-
114-
config := ocispecs.Image{
115-
Platform: ocispecs.Platform{
116-
Architecture: runtime.GOARCH,
117-
OS: "linux",
118-
},
119-
RootFS: ocispecs.RootFS{
120-
Type: "layers",
121-
},
122-
Config: ocispecs.ImageConfig{
123-
WorkingDir: "/",
124-
Cmd: strings.Fields(annots["com.urunc.unikernel.cmdline"]),
125-
Labels: annots,
126-
},
127-
}
128-
129-
imageConfig, err := json.Marshal(config)
130-
if err != nil {
131-
return nil, fmt.Errorf("Failed to marshal image config: %v", err)
132-
}
133-
res.AddMeta(exptypes.ExporterImageConfigKey, imageConfig)
134-
for annot, val := range annots {
135-
res.AddMeta(exptypes.AnnotationManifestKey(nil, annot), []byte(val))
136-
}
137-
res.SetRef(ref)
138-
139-
return res, nil
140-
}
141-
142103
func bunnyBuilder(ctx context.Context, c client.Client) (*client.Result, error) {
143104
// Get the Build options from buildkit
144105
buildOpts := c.BuildOpts().Opts
@@ -167,21 +128,33 @@ func bunnyBuilder(ctx context.Context, c client.Client) (*client.Result, error)
167128
return nil, fmt.Errorf("Could not create LLB definition: %v", err)
168129
}
169130

131+
rc := &hops.ResultAndConfig{}
132+
170133
// Pass LLB to buildkit
171-
result, err := c.Solve(ctx, client.SolveRequest{
134+
rc.Res, err = c.Solve(ctx, client.SolveRequest{
172135
Definition: dt.ToPB(),
173136
})
174137
if err != nil {
175138
return nil, fmt.Errorf("Failed to resolve LLB: %v", err)
176139
}
177140

178-
// Add annotations and Labels in output image
179-
result, err = annotateRes(packInst.Annots, result)
141+
// Get the OCI Image config of the base Image if there is any
142+
err = rc.GetBaseConfig(ctx, c, packInst.Config.BaseRef, packInst.Config.Monitor)
143+
if err != nil {
144+
return nil, fmt.Errorf("Failed to get OCI config of base image: %v", err)
145+
}
146+
147+
// Set some default values in the Image config
148+
// and add cmdline and Labels
149+
rc.UpdateConfig(packInst.Annots)
150+
151+
// Apply annotations and the new config to the solver's result
152+
err = rc.ApplyConfig(packInst.Annots)
180153
if err != nil {
181154
return nil, fmt.Errorf("Failed to annotate final image: %v", err)
182155
}
183156

184-
return result, nil
157+
return rc.Res, nil
185158
}
186159

187160
func main() {

hops/image_config.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright (c) 2023-2025, Nubificus LTD
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package hops
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"fmt"
21+
"runtime"
22+
"strings"
23+
24+
"github.com/distribution/reference"
25+
"github.com/moby/buildkit/client/llb/sourceresolver"
26+
"github.com/moby/buildkit/exporter/containerimage/exptypes"
27+
"github.com/moby/buildkit/frontend/gateway/client"
28+
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
29+
)
30+
31+
type ResultAndConfig struct {
32+
// The result
33+
Res *client.Result
34+
// The OCI config of the final image
35+
OCIConfig ocispecs.Image
36+
}
37+
38+
func (rc *ResultAndConfig) GetBaseConfig(ctx context.Context, c client.Client, ref string, mon string) error {
39+
if ref == "" || ref == "scratch" {
40+
return nil
41+
}
42+
43+
baseRef, err := reference.ParseNormalizedNamed(ref)
44+
if err != nil {
45+
return fmt.Errorf("Failed to parse image name %s: %v", ref, err)
46+
}
47+
baseImageName := reference.TagNameOnly(baseRef).String()
48+
49+
plat := ocispecs.Platform{
50+
Architecture: runtime.GOARCH,
51+
}
52+
if strings.HasPrefix(ref, unikraftHub) {
53+
// Define the platform to qemu/amd64 so we can pull unikraft images
54+
plat.OS = mon
55+
} else {
56+
plat.OS = "linux"
57+
}
58+
_, _, config, err := c.ResolveImageConfig(ctx, baseImageName,
59+
sourceresolver.Opt{
60+
LogName: "resolving image metadata for " + baseImageName,
61+
Platform: &plat,
62+
})
63+
if err != nil {
64+
return fmt.Errorf("Failed to get image config from %s: %v", baseImageName, err)
65+
}
66+
67+
err = json.Unmarshal(config, &rc.OCIConfig)
68+
if err != nil {
69+
return fmt.Errorf("Failed to unmarshal image config of %ss: %v", baseImageName, err)
70+
}
71+
72+
return nil
73+
}
74+
75+
func (rc *ResultAndConfig) UpdateConfig(annots map[string]string) {
76+
plat := ocispecs.Platform{
77+
Architecture: runtime.GOARCH,
78+
OS: "linux",
79+
}
80+
rfs := ocispecs.RootFS{
81+
Type: "layers",
82+
}
83+
84+
// Overwrite platform and rootfs to remove unikraft specific platform
85+
// and initialize empty configs.
86+
rc.OCIConfig.Platform = plat
87+
rc.OCIConfig.RootFS = rfs
88+
// Overwrite Cmd and entrypoint based on the values of bunnyfile
89+
rc.OCIConfig.Config.Cmd = strings.Fields(annots["com.urunc.unikernel.cmdline"])
90+
rc.OCIConfig.Config.Entrypoint = []string{}
91+
92+
if rc.OCIConfig.Config.Labels == nil {
93+
rc.OCIConfig.Config.Labels = make(map[string]string)
94+
}
95+
for k, v := range annots {
96+
rc.OCIConfig.Config.Labels[k] = v
97+
}
98+
}
99+
100+
func (rc *ResultAndConfig) ApplyConfig(annots map[string]string) error {
101+
res := rc.Res
102+
ref, err := res.SingleRef()
103+
if err != nil {
104+
return fmt.Errorf("Failed te get reference build result: %v", err)
105+
}
106+
107+
imageConfig, err := json.Marshal(rc.OCIConfig)
108+
if err != nil {
109+
return fmt.Errorf("Failed to marshal image config: %v", err)
110+
}
111+
res.AddMeta(exptypes.ExporterImageConfigKey, imageConfig)
112+
for annot, val := range annots {
113+
res.AddMeta(exptypes.AnnotationManifestKey(nil, annot), []byte(val))
114+
}
115+
res.SetRef(ref)
116+
117+
rc.Res = res
118+
return nil
119+
}

0 commit comments

Comments
 (0)