@@ -44,13 +44,20 @@ func genSpaceErr(err error) error {
44
44
}
45
45
46
46
func BuildImage (w http.ResponseWriter , r * http.Request ) {
47
+ var multipart bool
47
48
if hdr , found := r .Header ["Content-Type" ]; found && len (hdr ) > 0 {
48
49
contentType := hdr [0 ]
50
+ multipart = strings .HasPrefix (contentType , "multipart/form-data" )
51
+ if multipart {
52
+ contentType = "multipart/form-data"
53
+ }
49
54
switch contentType {
50
55
case "application/tar" :
51
56
logrus .Infof ("tar file content type is %s, should use \" application/x-tar\" content type" , contentType )
52
57
case "application/x-tar" :
53
58
break
59
+ case "multipart/form-data" :
60
+ logrus .Debugf ("Received multipart/form-data: %s" , contentType )
54
61
default :
55
62
if utils .IsLibpodRequest (r ) {
56
63
utils .BadRequest (w , "Content-Type" , hdr [0 ],
@@ -81,10 +88,21 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
81
88
}
82
89
}()
83
90
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
+ }
88
106
}
89
107
90
108
runtime := r .Context ().Value (api .RuntimeKey ).(* libpod.Runtime )
@@ -448,6 +466,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
448
466
}
449
467
}
450
468
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
+
451
477
var idMappingOptions buildahDefine.IDMappingOptions
452
478
if _ , found := r .URL .Query ()["idmappingoptions" ]; found {
453
479
if err := json .Unmarshal ([]byte (query .IDMappingOptions ), & idMappingOptions ); err != nil {
@@ -920,6 +946,122 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
920
946
}
921
947
}
922
948
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
+
923
1065
func parseNetworkConfigurationPolicy (network string ) buildah.NetworkConfigurationPolicy {
924
1066
if val , err := strconv .Atoi (network ); err == nil {
925
1067
return buildah .NetworkConfigurationPolicy (val )
0 commit comments