@@ -21,6 +21,8 @@ import (
21
21
"os"
22
22
"os/exec"
23
23
"path/filepath"
24
+ "regexp"
25
+ "strings"
24
26
"time"
25
27
26
28
"github.com/pkg/errors"
@@ -138,13 +140,143 @@ func ImportImage(bc *bits.BuildContext, tar string) error {
138
140
return nil
139
141
}
140
142
141
- // PreLoadInitImages preload images required by kubeadm-init into the containerd runtime that exists inside a kind(er) node
143
+ func getImageNamesFromTarInContainer (bc * bits.BuildContext , tarPath string ) ([]string , error ) {
144
+ cmd := fmt .Sprintf ("tar -xf '%s' manifest.json -O | jq -r '.[].RepoTags[]'" , tarPath )
145
+ imageNames , err := bc .CombinedOutputLinesInContainer ("bash" , "-c" , cmd )
146
+ if err != nil {
147
+ return nil , fmt .Errorf ("failed to get image names from tar %s: %w" , tarPath , err )
148
+ }
149
+ return imageNames , nil
150
+ }
151
+
152
+ // PreLoadInitImages preloads images from .tar files in srcFolder into containerd,
153
+ // renaming images to remove architecture suffixes matching the host architecture.
142
154
func PreLoadInitImages (bc * bits.BuildContext , srcFolder string ) error {
143
- // NB. this code is an extract from "sigs.k8s.io/kind/pkg/build/node"
144
- return bc .RunInContainer (
145
- "bash" , "-c" ,
146
- `find ` + srcFolder + ` -name *.tar -print0 | xargs -0 -n 1 -P $(nproc) ctr --namespace=k8s.io images import --all-platforms --no-unpack && rm -rf ` + srcFolder + `/*.tar` ,
147
- )
155
+ arch , err := getHostArch (bc )
156
+ if err != nil {
157
+ return err
158
+ }
159
+
160
+ tarFiles , err := listTarFiles (bc , srcFolder )
161
+ if err != nil {
162
+ return err
163
+ }
164
+ if len (tarFiles ) == 0 {
165
+ log .Infof ("No .tar files found in %s" , srcFolder )
166
+ return nil
167
+ }
168
+
169
+ var importErrors []string
170
+ for _ , tarFilePath := range tarFiles {
171
+ log .Infof ("Importing image from: %s" , tarFilePath )
172
+ imageNames , err := getImageNamesFromTarInContainer (bc , tarFilePath )
173
+ if err != nil {
174
+ errMsg := fmt .Sprintf ("failed to read image name from tar %s: %v" , tarFilePath , err )
175
+ log .Errorf ("Error: %s" , errMsg )
176
+ importErrors = append (importErrors , errMsg )
177
+ continue
178
+ }
179
+ if len (imageNames ) == 0 {
180
+ errMsg := fmt .Sprintf ("no image name found in tar %s" , tarFilePath )
181
+ log .Errorf ("Error: %s" , errMsg )
182
+ importErrors = append (importErrors , errMsg )
183
+ continue
184
+ }
185
+
186
+ importCmd := fmt .Sprintf ("ctr --namespace=k8s.io images import --all-platforms '%s'" , tarFilePath )
187
+ _ , err = bc .CombinedOutputLinesInContainer ("bash" , "-c" , importCmd )
188
+ if err != nil {
189
+ errMsg := fmt .Sprintf ("failed to import image from %s: %v" , tarFilePath , err )
190
+ log .Errorf ("Error: %s" , errMsg )
191
+ importErrors = append (importErrors , errMsg )
192
+ continue
193
+ }
194
+
195
+ for _ , imageName := range imageNames {
196
+ log .Infof ("Successfully imported image: %s" , imageName )
197
+ base , archSuffix , tag := parseImageArchSuffix (imageName )
198
+ if archSuffix != "" && archSuffix == arch {
199
+ newImageName := fmt .Sprintf ("%s:%s" , base , tag )
200
+ tagCmd := fmt .Sprintf ("ctr --namespace=k8s.io images tag '%s' '%s'" , imageName , newImageName )
201
+ _ , err := bc .CombinedOutputLinesInContainer ("bash" , "-c" , tagCmd )
202
+ if err != nil {
203
+ log .Warnf ("Warning: failed to tag image %s to %s: %v" , imageName , newImageName , err )
204
+ } else {
205
+ log .Infof ("Tagged image %s as %s" , imageName , newImageName )
206
+ }
207
+ }
208
+ }
209
+
210
+ rmCmd := fmt .Sprintf ("rm -f '%s'" , tarFilePath )
211
+ _ , err = bc .CombinedOutputLinesInContainer ("bash" , "-c" , rmCmd )
212
+ if err != nil {
213
+ log .Infof ("Warning: failed to remove %s: %v" , tarFilePath , err )
214
+ }
215
+ }
216
+
217
+ if len (importErrors ) > 0 {
218
+ return fmt .Errorf ("some images failed to import: %s" , strings .Join (importErrors , "; " ))
219
+ }
220
+ log .Infof ("Successfully imported all images from %s" , srcFolder )
221
+ return nil
222
+ }
223
+
224
+ // getHostArch returns the normalized host architecture string (e.g. amd64, arm64).
225
+ func getHostArch (bc * bits.BuildContext ) (string , error ) {
226
+ archLines , err := bc .CombinedOutputLinesInContainer ("uname" , "-m" )
227
+ if err != nil {
228
+ return "" , fmt .Errorf ("failed to get host architecture: %w" , err )
229
+ }
230
+ if len (archLines ) == 0 {
231
+ return "" , fmt .Errorf ("failed to get host architecture: empty output" )
232
+ }
233
+ arch := strings .TrimSpace (archLines [0 ])
234
+ if arch == "" {
235
+ return "" , fmt .Errorf ("failed to get host architecture: empty architecture string" )
236
+ }
237
+ archMap := map [string ]string {
238
+ "x86_64" : "amd64" ,
239
+ "amd64" : "amd64" ,
240
+ "aarch64" : "arm64" ,
241
+ "arm64" : "arm64" ,
242
+ }
243
+ if stdArch , ok := archMap [arch ]; ok {
244
+ return stdArch , nil
245
+ }
246
+ log .Warnf ("Warning: unsupported architecture %s, proceeding with original name" , arch )
247
+ return arch , nil
248
+ }
249
+
250
+ // listTarFiles returns a list of .tar file paths in the given srcFolder inside the container.
251
+ func listTarFiles (bc * bits.BuildContext , srcFolder string ) ([]string , error ) {
252
+ tarFilesLines , err := bc .CombinedOutputLinesInContainer ("find" , srcFolder , "-name" , "*.tar" )
253
+ if err != nil {
254
+ return nil , fmt .Errorf ("failed to list .tar files: %w" , err )
255
+ }
256
+ var validTarFiles []string
257
+ for _ , tarFilePath := range tarFilesLines {
258
+ tarFilePath = strings .TrimSpace (tarFilePath )
259
+ if tarFilePath != "" {
260
+ validTarFiles = append (validTarFiles , tarFilePath )
261
+ }
262
+ }
263
+ return validTarFiles , nil
264
+ }
265
+
266
+ // parseImageArchSuffix parses image name and returns base, archSuffix, tag.
267
+ // e.g. kube-apiserver-arm64:v1.34 => (kube-apiserver, arm64, v1.34)
268
+ func parseImageArchSuffix (imageName string ) (base , archSuffix , tag string ) {
269
+ re := regexp .MustCompile (`^(.+)-(amd64|arm64):(.+)$` )
270
+ matches := re .FindStringSubmatch (imageName )
271
+ if len (matches ) == 4 {
272
+ return matches [1 ], matches [2 ], matches [3 ]
273
+ }
274
+
275
+ parts := strings .SplitN (imageName , ":" , 2 )
276
+ if len (parts ) == 2 {
277
+ return parts [0 ], "" , parts [1 ]
278
+ }
279
+ return imageName , "" , ""
148
280
}
149
281
150
282
// Commit a kind(er) node image that uses the containerd runtime internally
0 commit comments