Skip to content

Commit 5b62920

Browse files
committed
Feat: send additional build contexts for remote builds
Fixed the --build-context flag to properly send files for remote builds. Previously only the main context was sent over as a tar while additional contexts were passed as local paths and this would cause builds to fail since the files wouldn't exist. New changes modifies the Build API to use multipart HTTP requests allowing multiple build contexts to be used. Each additional context is packaged and transferred based on its type: - Local Directories: Sent as tar archives - Git Repositories: link sent to the server where its then cloned - Container Images: Image reference sent to the server, it then pulls the image there - URLs/archives: URL sent to the server, which handles the download Fixes: #23433 Signed-off-by: Joshua Arrevillaga <[email protected]>
1 parent 7efa0b8 commit 5b62920

File tree

4 files changed

+705
-17
lines changed

4 files changed

+705
-17
lines changed

pkg/api/handlers/compat/images_build.go

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,20 @@ func genSpaceErr(err error) error {
4444
}
4545

4646
func BuildImage(w http.ResponseWriter, r *http.Request) {
47+
var multipart bool
4748
if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 {
4849
contentType := hdr[0]
50+
multipart = strings.HasPrefix(contentType, "multipart/form-data")
51+
if multipart {
52+
contentType = "multipart/form-data"
53+
}
4954
switch contentType {
5055
case "application/tar":
5156
logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType)
5257
case "application/x-tar":
5358
break
59+
case "multipart/form-data":
60+
logrus.Debugf("Received multipart/form-data: %s", contentType)
5461
default:
5562
if utils.IsLibpodRequest(r) {
5663
utils.BadRequest(w, "Content-Type", hdr[0],
@@ -81,10 +88,21 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
8188
}
8289
}()
8390

84-
contextDirectory, err := extractTarFile(anchorDir, r)
85-
if err != nil {
86-
utils.InternalServerError(w, genSpaceErr(err))
87-
return
91+
var contextDirectory string
92+
var additionalBuildContextsFromMultipart map[string]*buildahDefine.AdditionalBuildContext
93+
94+
if multipart {
95+
contextDirectory, additionalBuildContextsFromMultipart, err = handleMultipartBuild(anchorDir, r)
96+
if err != nil {
97+
utils.InternalServerError(w, fmt.Errorf("handling multipart request: %w", err))
98+
return
99+
}
100+
} else {
101+
contextDirectory, err = extractTarFile(anchorDir, r)
102+
if err != nil {
103+
utils.InternalServerError(w, genSpaceErr(err))
104+
return
105+
}
88106
}
89107

90108
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
@@ -448,6 +466,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
448466
}
449467
}
450468

469+
if additionalBuildContextsFromMultipart != nil {
470+
logrus.Debugf("Merging %d additional contexts from multipart", len(additionalBuildContextsFromMultipart))
471+
for name, ctx := range additionalBuildContextsFromMultipart {
472+
additionalBuildContexts[name] = ctx
473+
logrus.Debugf("Added multipart context %q with path %q", name, ctx.Value)
474+
}
475+
}
476+
451477
var idMappingOptions buildahDefine.IDMappingOptions
452478
if _, found := r.URL.Query()["idmappingoptions"]; found {
453479
if err := json.Unmarshal([]byte(query.IDMappingOptions), &idMappingOptions); err != nil {
@@ -920,6 +946,122 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
920946
}
921947
}
922948

949+
func handleMultipartBuild(anchorDir string, r *http.Request) (contextDir string, additionalContexts map[string]*buildahDefine.AdditionalBuildContext, err error) {
950+
reader, err := r.MultipartReader()
951+
if err != nil {
952+
return "", nil, fmt.Errorf("failed to create multipart reader: %w", err)
953+
}
954+
955+
additionalContexts = make(map[string]*buildahDefine.AdditionalBuildContext)
956+
for {
957+
part, err := reader.NextPart()
958+
if err == io.EOF {
959+
break
960+
}
961+
if err != nil {
962+
return "", nil, fmt.Errorf("failed to read part: %w", err)
963+
}
964+
965+
fieldName := part.FormName()
966+
fileName := part.FileName()
967+
968+
logrus.Debugf("Processing part: field=%q, file=%q", fieldName, fileName)
969+
970+
switch {
971+
case fieldName == "context" && fileName != "":
972+
contextPath := filepath.Join(anchorDir, "context")
973+
if err := os.MkdirAll(contextPath, 0755); err != nil {
974+
part.Close()
975+
return "", nil, fmt.Errorf("creating context directory: %w", err)
976+
}
977+
978+
if err := archive.Untar(part, contextPath, nil); err != nil {
979+
part.Close()
980+
return "", nil, fmt.Errorf("extracting main context: %w", err)
981+
}
982+
contextDir = contextPath
983+
logrus.Debugf("Extracted main context to %q", contextDir)
984+
part.Close()
985+
986+
case strings.HasPrefix(fieldName, "buildcontext-url-"):
987+
contextName := strings.TrimPrefix(fieldName, "buildcontext-url-")
988+
urlBytes, err := io.ReadAll(part)
989+
part.Close()
990+
if err != nil {
991+
return "", nil, fmt.Errorf("reading URL value: %w", err)
992+
}
993+
urlValue := string(urlBytes)
994+
995+
logrus.Debugf("Found URL context %q: %s", contextName, urlValue)
996+
997+
tempDir, subDir, err := buildahDefine.TempDirForURL(anchorDir, "buildah", urlValue)
998+
if err != nil {
999+
return "", nil, fmt.Errorf("downloading URL context %q: %w", contextName, err)
1000+
}
1001+
1002+
contextPath := filepath.Join(tempDir, subDir)
1003+
additionalContexts[contextName] = &buildahDefine.AdditionalBuildContext{
1004+
IsURL: true,
1005+
IsImage: false,
1006+
Value: contextPath,
1007+
DownloadedCache: contextPath,
1008+
}
1009+
logrus.Debugf("Downloaded URL context %q to %q", contextName, contextPath)
1010+
1011+
case strings.HasPrefix(fieldName, "buildcontext-image-"):
1012+
contextName := strings.TrimPrefix(fieldName, "buildcontext-image-")
1013+
imageBytes, err := io.ReadAll(part)
1014+
part.Close()
1015+
if err != nil {
1016+
return "", nil, fmt.Errorf("reading image reference: %w", err)
1017+
}
1018+
imageRef := string(imageBytes)
1019+
1020+
logrus.Debugf("Found image context %q: %s", contextName, imageRef)
1021+
1022+
additionalContexts[contextName] = &buildahDefine.AdditionalBuildContext{
1023+
IsImage: true,
1024+
IsURL: false,
1025+
Value: imageRef,
1026+
}
1027+
logrus.Debugf("Added image context %q with reference %q", contextName, imageRef)
1028+
1029+
case strings.HasPrefix(fieldName, "buildcontext-local-") && fileName != "":
1030+
contextName := strings.TrimPrefix(fieldName, "buildcontext-local-")
1031+
additionalAnchor := filepath.Join(anchorDir, "additional", contextName)
1032+
1033+
if err := os.MkdirAll(additionalAnchor, 0700); err != nil {
1034+
part.Close()
1035+
return "", nil, fmt.Errorf("creating additional context directory %q: %w", contextName, err)
1036+
}
1037+
1038+
if err := archive.Untar(part, additionalAnchor, nil); err != nil {
1039+
part.Close()
1040+
return "", nil, fmt.Errorf("extracting additional context %q: %w", contextName, err)
1041+
}
1042+
1043+
additionalContexts[contextName] = &buildahDefine.AdditionalBuildContext{
1044+
IsURL: false,
1045+
IsImage: false,
1046+
Value: additionalAnchor,
1047+
}
1048+
logrus.Debugf("Extracted additional context %q to %q", contextName, additionalAnchor)
1049+
part.Close()
1050+
1051+
default:
1052+
part.Close()
1053+
}
1054+
}
1055+
1056+
if contextDir == "" {
1057+
return "", nil, fmt.Errorf("no main context provided in multipart form")
1058+
}
1059+
1060+
logrus.Debugf("Successfully parsed multipart form, main context: %q and additional contexts: %v", contextDir, additionalContexts)
1061+
1062+
return contextDir, additionalContexts, nil
1063+
}
1064+
9231065
func parseNetworkConfigurationPolicy(network string) buildah.NetworkConfigurationPolicy {
9241066
if val, err := strconv.Atoi(network); err == nil {
9251067
return buildah.NetworkConfigurationPolicy(val)

0 commit comments

Comments
 (0)