@@ -21,6 +21,7 @@ import (
21
21
"github.com/containers/buildah"
22
22
buildahDefine "github.com/containers/buildah/define"
23
23
"github.com/containers/buildah/pkg/parse"
24
+ "github.com/containers/podman/v5/internal/localapi"
24
25
"github.com/containers/podman/v5/libpod"
25
26
"github.com/containers/podman/v5/pkg/api/handlers/utils"
26
27
api "github.com/containers/podman/v5/pkg/api/types"
@@ -135,6 +136,29 @@ type BuildContext struct {
135
136
IgnoreFile string
136
137
}
137
138
139
+ func (b * BuildContext ) validateLocalAPIPaths () error {
140
+ if err := localapi .ValidatePathForLocalAPI (b .ContextDirectory ); err != nil {
141
+ return err
142
+ }
143
+
144
+ for _ , containerfile := range b .ContainerFiles {
145
+ if err := localapi .ValidatePathForLocalAPI (containerfile ); err != nil {
146
+ return err
147
+ }
148
+ }
149
+
150
+ for _ , ctx := range b .AdditionalBuildContexts {
151
+ if ctx .IsURL || ctx .IsImage {
152
+ continue
153
+ }
154
+ if err := localapi .ValidatePathForLocalAPI (ctx .Value ); err != nil {
155
+ return err
156
+ }
157
+ }
158
+
159
+ return nil
160
+ }
161
+
138
162
// genSpaceErr wraps filesystem errors to provide more context for disk space issues.
139
163
func genSpaceErr (err error ) error {
140
164
if errors .Is (err , syscall .ENOSPC ) {
@@ -188,7 +212,7 @@ func validateContentType(r *http.Request) (bool, error) {
188
212
logrus .Infof ("Received %s" , hdr [0 ])
189
213
multipart = true
190
214
default :
191
- if utils .IsLibpodRequest (r ) {
215
+ if utils .IsLibpodRequest (r ) && ! utils . IsLibpodLocalRequest ( r ) {
192
216
return false , utils .GetBadRequestError ("Content-Type" , hdr [0 ],
193
217
fmt .Errorf ("Content-Type: %s is not supported. Should be \" application/x-tar\" " , hdr [0 ]))
194
218
}
@@ -256,10 +280,14 @@ func processBuildContext(query url.Values, r *http.Request, buildContext *BuildC
256
280
}
257
281
258
282
for _ , containerfile := range m {
259
- // Add path to containerfile iff it is not URL
283
+ // Add path to containerfile if it is not URL
260
284
if ! strings .HasPrefix (containerfile , "http://" ) && ! strings .HasPrefix (containerfile , "https://" ) {
261
- containerfile = filepath .Join (buildContext .ContextDirectory ,
262
- filepath .Clean (filepath .FromSlash (containerfile )))
285
+ if filepath .IsAbs (containerfile ) {
286
+ containerfile = filepath .Clean (filepath .FromSlash (containerfile ))
287
+ } else {
288
+ containerfile = filepath .Join (buildContext .ContextDirectory ,
289
+ filepath .Clean (filepath .FromSlash (containerfile )))
290
+ }
263
291
}
264
292
buildContext .ContainerFiles = append (buildContext .ContainerFiles , containerfile )
265
293
}
@@ -579,7 +607,7 @@ func createBuildOptions(query *BuildQuery, buildCtx *BuildContext, queryValues u
579
607
// Credential value(s) not returned as their value is not human readable
580
608
return nil , nil , utils .GetGenericBadRequestError (err )
581
609
}
582
- // this smells
610
+
583
611
cleanup := func () {
584
612
auth .RemoveAuthfile (authfile )
585
613
}
@@ -819,7 +847,128 @@ func executeBuild(runtime *libpod.Runtime, w http.ResponseWriter, r *http.Reques
819
847
}
820
848
}
821
849
850
+ // handleLocalBuildContexts processes build contexts for local API builds and validates local paths.
851
+ //
852
+ // This function handles the main build context and any additional build contexts specified in the request:
853
+ // - Validates that the main context directory (localcontextdir) exists and is accessible for local API usage
854
+ // - Processes additional build contexts which can be:
855
+ // - URLs (url:) - downloads content to temporary directories under anchorDir
856
+ // - Container images (image:) - records image references for later resolution during build
857
+ // - Local paths (localpath:) - validates and cleans local filesystem paths
858
+ //
859
+ // Returns a BuildContext struct with the main context directory and a map of additional build contexts,
860
+ // or an error if validation fails or required parameters are missing.
861
+ func handleLocalBuildContexts (query url.Values , anchorDir string ) (* BuildContext , error ) {
862
+ localContextDir := query .Get ("localcontextdir" )
863
+ if localContextDir == "" {
864
+ return nil , utils .GetBadRequestError ("localcontextdir" , localContextDir , fmt .Errorf ("localcontextdir cannot be empty" ))
865
+ }
866
+ localContextDir = filepath .Clean (localContextDir )
867
+ if err := localapi .ValidatePathForLocalAPI (localContextDir ); err != nil {
868
+ if errors .Is (err , os .ErrNotExist ) {
869
+ return nil , utils .GetFileNotFoundError (err )
870
+ }
871
+ return nil , utils .GetGenericBadRequestError (err )
872
+ }
873
+
874
+ out := & BuildContext {
875
+ ContextDirectory : localContextDir ,
876
+ AdditionalBuildContexts : make (map [string ]* buildahDefine.AdditionalBuildContext ),
877
+ }
878
+
879
+ for _ , url := range query ["additionalbuildcontexts" ] {
880
+ name , value , found := strings .Cut (url , "=" )
881
+ if ! found {
882
+ return nil , utils .GetInternalServerError (fmt .Errorf ("additionalbuildcontexts must be in name=value format: %q" , url ))
883
+ }
884
+
885
+ logrus .Debugf ("Processing additional build context: name=%q, value=%q" , name , value )
886
+
887
+ switch {
888
+ case strings .HasPrefix (value , "url:" ):
889
+ value = strings .TrimPrefix (value , "url:" )
890
+ tempDir , subdir , err := buildahDefine .TempDirForURL (anchorDir , "buildah" , value )
891
+ if err != nil {
892
+ return nil , utils .GetInternalServerError (genSpaceErr (err ))
893
+ }
894
+
895
+ contextPath := filepath .Join (tempDir , subdir )
896
+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
897
+ IsURL : true ,
898
+ IsImage : false ,
899
+ Value : contextPath ,
900
+ DownloadedCache : contextPath ,
901
+ }
902
+ case strings .HasPrefix (value , "image:" ):
903
+ value = strings .TrimPrefix (value , "image:" )
904
+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
905
+ IsURL : false ,
906
+ IsImage : true ,
907
+ Value : value ,
908
+ }
909
+ case strings .HasPrefix (value , "localpath:" ):
910
+ value = strings .TrimPrefix (value , "localpath:" )
911
+ out .AdditionalBuildContexts [name ] = & buildahDefine.AdditionalBuildContext {
912
+ IsURL : false ,
913
+ IsImage : false ,
914
+ Value : filepath .Clean (value ),
915
+ }
916
+ }
917
+ }
918
+ return out , nil
919
+ }
920
+
921
+ // getLocalBuildContext processes build contexts from Local API HTTP request to a BuildContext struct.
922
+ func getLocalBuildContext (r * http.Request , query url.Values , anchorDir string , _ bool ) (* BuildContext , error ) {
923
+ // Handle build contexts
924
+ buildContext , err := handleLocalBuildContexts (query , anchorDir )
925
+ if err != nil {
926
+ return nil , err
927
+ }
928
+
929
+ // Process build context and container files
930
+ buildContext , err = processBuildContext (query , r , buildContext , anchorDir )
931
+ if err != nil {
932
+ return nil , err
933
+ }
934
+
935
+ if err := buildContext .validateLocalAPIPaths (); err != nil {
936
+ if errors .Is (err , os .ErrNotExist ) {
937
+ return nil , utils .GetFileNotFoundError (err )
938
+ }
939
+ return nil , utils .GetGenericBadRequestError (err )
940
+ }
941
+
942
+ // Process dockerignore
943
+ _ , ignoreFile , err := util .ParseDockerignore (buildContext .ContainerFiles , buildContext .ContextDirectory )
944
+ if err != nil {
945
+ return nil , utils .GetInternalServerError (fmt .Errorf ("processing ignore file: %w" , err ))
946
+ }
947
+ buildContext .IgnoreFile = ignoreFile
948
+
949
+ return buildContext , nil
950
+ }
951
+
952
+ // LocalBuildImage handles HTTP requests for building container images using the Local API.
953
+ //
954
+ // Uses localcontextdir and additionalbuildcontexts query parameters to specify build contexts
955
+ // from the server's local filesystem. All paths must be absolute and exist on the server.
956
+ // Processes build parameters, executes the build using buildah, and streams output to the client.
957
+ func LocalBuildImage (w http.ResponseWriter , r * http.Request ) {
958
+ buildImage (w , r , getLocalBuildContext )
959
+ }
960
+
961
+ // BuildImage handles HTTP requests for building container images using the Docker-compatible API.
962
+ //
963
+ // Extracts build contexts from the request body (tar/multipart), processes build parameters,
964
+ // executes the build using buildah, and streams output back to the client.
822
965
func BuildImage (w http.ResponseWriter , r * http.Request ) {
966
+ buildImage (w , r , getBuildContext )
967
+ }
968
+
969
+ type getBuildContextFunc func (r * http.Request , query url.Values , anchorDir string , multipart bool ) (* BuildContext , error )
970
+
971
+ func buildImage (w http.ResponseWriter , r * http.Request , getBuildContextFunc getBuildContextFunc ) {
823
972
// Create temporary directory for build context
824
973
anchorDir , err := os .MkdirTemp (parse .GetTempDir (), "libpod_builder" )
825
974
if err != nil {
@@ -850,7 +999,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
850
999
}
851
1000
queryValues := r .URL .Query ()
852
1001
853
- buildContext , err := getBuildContext (r , queryValues , anchorDir , multipart )
1002
+ buildContext , err := getBuildContextFunc (r , queryValues , anchorDir , multipart )
854
1003
if err != nil {
855
1004
utils .ProcessBuildError (w , err )
856
1005
return
0 commit comments