@@ -22,6 +22,7 @@ import (
22
22
"github.com/containers/buildah"
23
23
buildahDefine "github.com/containers/buildah/define"
24
24
"github.com/containers/buildah/pkg/parse"
25
+ "github.com/containers/podman/v5/internal/localapi"
25
26
"github.com/containers/podman/v5/libpod"
26
27
"github.com/containers/podman/v5/pkg/api/handlers/utils"
27
28
api "github.com/containers/podman/v5/pkg/api/types"
@@ -143,6 +144,29 @@ type BuildContext struct {
143
144
IgnoreFile string
144
145
}
145
146
147
+ func (b * BuildContext ) validateLocalAPIPaths () error {
148
+ if err := localapi .ValidatePathForLocalAPI (b .ContextDirectory ); err != nil {
149
+ return err
150
+ }
151
+
152
+ for _ , containerfile := range b .ContainerFiles {
153
+ if err := localapi .ValidatePathForLocalAPI (containerfile ); err != nil {
154
+ return err
155
+ }
156
+ }
157
+
158
+ for _ , ctx := range b .AdditionalBuildContexts {
159
+ if ctx .IsURL || ctx .IsImage {
160
+ continue
161
+ }
162
+ if err := localapi .ValidatePathForLocalAPI (ctx .Value ); err != nil {
163
+ return err
164
+ }
165
+ }
166
+
167
+ return nil
168
+ }
169
+
146
170
// genSpaceErr wraps filesystem errors to provide more context for disk space issues.
147
171
func genSpaceErr (err error ) error {
148
172
if errors .Is (err , syscall .ENOSPC ) {
@@ -196,7 +220,7 @@ func validateContentType(r *http.Request) (bool, error) {
196
220
logrus .Infof ("Received %s" , hdr [0 ])
197
221
multipart = true
198
222
default :
199
- if utils .IsLibpodRequest (r ) {
223
+ if utils .IsLibpodRequest (r ) && ! utils . IsLibpodLocalRequest ( r ) {
200
224
return false , utils .GetBadRequestError ("Content-Type" , hdr [0 ],
201
225
fmt .Errorf ("Content-Type: %s is not supported. Should be \" application/x-tar\" " , hdr [0 ]))
202
226
}
@@ -264,10 +288,14 @@ func processBuildContext(query url.Values, r *http.Request, buildContext *BuildC
264
288
}
265
289
266
290
for _ , containerfile := range m {
267
- // Add path to containerfile iff it is not URL
291
+ // Add path to containerfile if it is not URL
268
292
if ! strings .HasPrefix (containerfile , "http://" ) && ! strings .HasPrefix (containerfile , "https://" ) {
269
- containerfile = filepath .Join (buildContext .ContextDirectory ,
270
- filepath .Clean (filepath .FromSlash (containerfile )))
293
+ if filepath .IsAbs (containerfile ) {
294
+ containerfile = filepath .Clean (filepath .FromSlash (containerfile ))
295
+ } else {
296
+ containerfile = filepath .Join (buildContext .ContextDirectory ,
297
+ filepath .Clean (filepath .FromSlash (containerfile )))
298
+ }
271
299
}
272
300
buildContext .ContainerFiles = append (buildContext .ContainerFiles , containerfile )
273
301
}
@@ -587,7 +615,7 @@ func createBuildOptions(query *BuildQuery, buildCtx *BuildContext, queryValues u
587
615
// Credential value(s) not returned as their value is not human readable
588
616
return nil , nil , utils .GetGenericBadRequestError (err )
589
617
}
590
- // this smells
618
+
591
619
cleanup := func () {
592
620
auth .RemoveAuthfile (authfile )
593
621
}
@@ -866,7 +894,128 @@ func executeBuild(runtime *libpod.Runtime, w http.ResponseWriter, r *http.Reques
866
894
}
867
895
}
868
896
897
+ // handleLocalBuildContexts processes build contexts for local API builds and validates local paths.
898
+ //
899
+ // This function handles the main build context and any additional build contexts specified in the request:
900
+ // - Validates that the main context directory (localcontextdir) exists and is accessible for local API usage
901
+ // - Processes additional build contexts which can be:
902
+ // - URLs (url:) - downloads content to temporary directories under anchorDir
903
+ // - Container images (image:) - records image references for later resolution during build
904
+ // - Local paths (localpath:) - validates and cleans local filesystem paths
905
+ //
906
+ // Returns a BuildContext struct with the main context directory and a map of additional build contexts,
907
+ // or an error if validation fails or required parameters are missing.
908
+ func handleLocalBuildContexts (query url.Values , anchorDir string ) (* BuildContext , error ) {
909
+ localContextDir := query .Get ("localcontextdir" )
910
+ if localContextDir == "" {
911
+ return nil , utils .GetBadRequestError ("localcontextdir" , localContextDir , fmt .Errorf ("localcontextdir cannot be empty" ))
912
+ }
913
+ localContextDir = filepath .Clean (localContextDir )
914
+ if err := localapi .ValidatePathForLocalAPI (localContextDir ); err != nil {
915
+ if errors .Is (err , os .ErrNotExist ) {
916
+ return nil , utils .GetFileNotFoundError (err )
917
+ }
918
+ return nil , utils .GetGenericBadRequestError (err )
919
+ }
920
+
921
+ out := & BuildContext {
922
+ ContextDirectory : localContextDir ,
923
+ AdditionalBuildContexts : make (map [string ]* buildahDefine.AdditionalBuildContext ),
924
+ }
925
+
926
+ for _ , url := range query ["additionalbuildcontexts" ] {
927
+ name , value , found := strings .Cut (url , "=" )
928
+ if ! found {
929
+ return nil , utils .GetInternalServerError (fmt .Errorf ("additionalbuildcontexts must be in name=value format: %q" , url ))
930
+ }
931
+
932
+ logrus .Debugf ("Processing additional build context: name=%q, value=%q" , name , value )
933
+
934
+ switch {
935
+ case strings .HasPrefix (value , "url:" ):
936
+ value = strings .TrimPrefix (value , "url:" )
937
+ tempDir , subdir , err := buildahDefine .TempDirForURL (anchorDir , "buildah" , value )
938
+ if err != nil {
939
+ return nil , utils .GetInternalServerError (genSpaceErr (err ))
940
+ }
941
+
942
+ contextPath := filepath .Join (tempDir , subdir )
943
+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
944
+ IsURL : true ,
945
+ IsImage : false ,
946
+ Value : contextPath ,
947
+ DownloadedCache : contextPath ,
948
+ }
949
+ case strings .HasPrefix (value , "image:" ):
950
+ value = strings .TrimPrefix (value , "image:" )
951
+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
952
+ IsURL : false ,
953
+ IsImage : true ,
954
+ Value : value ,
955
+ }
956
+ case strings .HasPrefix (value , "localpath:" ):
957
+ value = strings .TrimPrefix (value , "localpath:" )
958
+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
959
+ IsURL : false ,
960
+ IsImage : false ,
961
+ Value : filepath .Clean (value ),
962
+ }
963
+ }
964
+ }
965
+ return out , nil
966
+ }
967
+
968
+ // getLocalBuildContext processes build contexts from Local API HTTP request to a BuildContext struct.
969
+ func getLocalBuildContext (r * http.Request , query url.Values , anchorDir string , _ bool ) (* BuildContext , error ) {
970
+ // Handle build contexts
971
+ buildContext , err := handleLocalBuildContexts (query , anchorDir )
972
+ if err != nil {
973
+ return nil , err
974
+ }
975
+
976
+ // Process build context and container files
977
+ buildContext , err = processBuildContext (query , r , buildContext , anchorDir )
978
+ if err != nil {
979
+ return nil , err
980
+ }
981
+
982
+ if err := buildContext .validateLocalAPIPaths (); err != nil {
983
+ if errors .Is (err , os .ErrNotExist ) {
984
+ return nil , utils .GetFileNotFoundError (err )
985
+ }
986
+ return nil , utils .GetGenericBadRequestError (err )
987
+ }
988
+
989
+ // Process dockerignore
990
+ _ , ignoreFile , err := util .ParseDockerignore (buildContext .ContainerFiles , buildContext .ContextDirectory )
991
+ if err != nil {
992
+ return nil , utils .GetInternalServerError (fmt .Errorf ("processing ignore file: %w" , err ))
993
+ }
994
+ buildContext .IgnoreFile = ignoreFile
995
+
996
+ return buildContext , nil
997
+ }
998
+
999
+ // LocalBuildImage handles HTTP requests for building container images using the Local API.
1000
+ //
1001
+ // Uses localcontextdir and additionalbuildcontexts query parameters to specify build contexts
1002
+ // from the server's local filesystem. All paths must be absolute and exist on the server.
1003
+ // Processes build parameters, executes the build using buildah, and streams output to the client.
1004
+ func LocalBuildImage (w http.ResponseWriter , r * http.Request ) {
1005
+ buildImage (w , r , getLocalBuildContext )
1006
+ }
1007
+
1008
+ // BuildImage handles HTTP requests for building container images using the Docker-compatible API.
1009
+ //
1010
+ // Extracts build contexts from the request body (tar/multipart), processes build parameters,
1011
+ // executes the build using buildah, and streams output back to the client.
869
1012
func BuildImage (w http.ResponseWriter , r * http.Request ) {
1013
+ buildImage (w , r , getBuildContext )
1014
+ }
1015
+
1016
+ type getBuildContextFunc func (r * http.Request , query url.Values , anchorDir string , multipart bool ) (* BuildContext , error )
1017
+
1018
+ func buildImage (w http.ResponseWriter , r * http.Request , getBuildContextFunc getBuildContextFunc ) {
870
1019
// Create temporary directory for build context
871
1020
anchorDir , err := os .MkdirTemp (parse .GetTempDir (), "libpod_builder" )
872
1021
if err != nil {
@@ -897,7 +1046,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
897
1046
}
898
1047
queryValues := r .URL .Query ()
899
1048
900
- buildContext , err := getBuildContext (r , queryValues , anchorDir , multipart )
1049
+ buildContext , err := getBuildContextFunc (r , queryValues , anchorDir , multipart )
901
1050
if err != nil {
902
1051
utils .ProcessBuildError (w , err )
903
1052
return
0 commit comments