Skip to content

Commit 254403d

Browse files
authored
Merge pull request #27426 from Honny1/local-api-artifact-add
Artifact add optimization on macOS and Windows
2 parents 0056ff4 + d889aeb commit 254403d

File tree

12 files changed

+336
-49
lines changed

12 files changed

+336
-49
lines changed

internal/localapi/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package localapi
22

3+
import "errors"
4+
35
// LocalAPIMap is a map of local paths to their target paths in the VM
46
type LocalAPIMap struct {
57
ClientPath string `json:"ClientPath,omitempty"`
68
RemotePath string `json:"RemotePath,omitempty"`
79
}
10+
11+
var ErrPathNotAbsolute = errors.New("path is not absolute")

internal/localapi/utils.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,29 +237,41 @@ func CheckIfImageBuildPathsOnRunningMachine(ctx context.Context, containerFiles
237237
return translatedContainerFiles, options, true
238238
}
239239

240-
// IsHyperVProvider checks if the current machine provider is Hyper-V.
241-
// It returns true if the provider is Hyper-V, false otherwise, or an error if the check fails.
242-
func IsHyperVProvider(ctx context.Context) (bool, error) {
240+
func getVmProviderType(ctx context.Context) (define.VMType, error) {
243241
conn, err := bindings.GetClient(ctx)
244242
if err != nil {
245243
logrus.Debugf("Failed to get client connection: %v", err)
246-
return false, err
244+
return define.UnknownVirt, err
247245
}
248246

249247
_, vmProvider, err := FindMachineByPort(conn.URI.String(), conn.URI)
250248
if err != nil {
251249
logrus.Debugf("Failed to get machine hypervisor type: %v", err)
252-
return false, err
250+
return define.UnknownVirt, err
253251
}
254252

255-
return vmProvider.VMType() == define.HyperVVirt, nil
253+
return vmProvider.VMType(), nil
254+
}
255+
256+
// IsHyperVProvider checks if the current machine provider is Hyper-V.
257+
// It returns true if the provider is Hyper-V, false otherwise, or an error if the check fails.
258+
func IsHyperVProvider(ctx context.Context) (bool, error) {
259+
providerType, err := getVmProviderType(ctx)
260+
return providerType == define.HyperVVirt, err
261+
}
262+
263+
// IsWSLProvider checks if the current machine provider is WSL.
264+
// It returns true if the provider is WSL, false otherwise, or an error if the check fails.
265+
func IsWSLProvider(ctx context.Context) (bool, error) {
266+
providerType, err := getVmProviderType(ctx)
267+
return providerType == define.WSLVirt, err
256268
}
257269

258270
// ValidatePathForLocalAPI checks if the provided path satisfies requirements for local API usage.
259271
// It returns an error if the path is not absolute or does not exist on the filesystem.
260272
func ValidatePathForLocalAPI(path string) error {
261273
if !filepath.IsAbs(path) {
262-
return fmt.Errorf("path %q is not absolute", path)
274+
return fmt.Errorf("%w: %q", ErrPathNotAbsolute, path)
263275
}
264276

265277
if err := fileutils.Exists(path); err != nil {

internal/localapi/utils_unsupported.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ func IsHyperVProvider(ctx context.Context) (bool, error) {
2424
return false, nil
2525
}
2626

27+
func IsWSLProvider(ctx context.Context) (bool, error) {
28+
logrus.Debug("IsWSLProvider is not supported")
29+
return false, nil
30+
}
31+
2732
func ValidatePathForLocalAPI(path string) error {
2833
logrus.Debug("ValidatePathForLocalAPI is not supported")
2934
return nil

pkg/api/handlers/libpod/artifacts.go

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ package libpod
55
import (
66
"errors"
77
"fmt"
8+
"io/fs"
89
"net/http"
10+
"path/filepath"
911

12+
"github.com/containers/podman/v6/internal/localapi"
1013
"github.com/containers/podman/v6/libpod"
1114
"github.com/containers/podman/v6/pkg/api/handlers/utils"
1215
api "github.com/containers/podman/v6/pkg/api/types"
@@ -212,19 +215,21 @@ func BatchRemoveArtifact(w http.ResponseWriter, r *http.Request) {
212215
utils.WriteResponse(w, http.StatusOK, artifacts)
213216
}
214217

218+
type artifactAddRequestQuery struct {
219+
Name string `schema:"name"`
220+
FileName string `schema:"fileName"`
221+
FileMIMEType string `schema:"fileMIMEType"`
222+
Annotations []string `schema:"annotations"`
223+
ArtifactMIMEType string `schema:"artifactMIMEType"`
224+
Append bool `schema:"append"`
225+
Replace bool `schema:"replace"`
226+
Path string `schema:"path"`
227+
}
228+
215229
func AddArtifact(w http.ResponseWriter, r *http.Request) {
216-
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
217230
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
218231

219-
query := struct {
220-
Name string `schema:"name"`
221-
FileName string `schema:"fileName"`
222-
FileMIMEType string `schema:"fileMIMEType"`
223-
Annotations []string `schema:"annotations"`
224-
ArtifactMIMEType string `schema:"artifactMIMEType"`
225-
Append bool `schema:"append"`
226-
Replace bool `schema:"replace"`
227-
}{}
232+
query := artifactAddRequestQuery{}
228233

229234
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
230235
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
@@ -236,6 +241,56 @@ func AddArtifact(w http.ResponseWriter, r *http.Request) {
236241
return
237242
}
238243

244+
artifactBlobs := []entities.ArtifactBlob{{
245+
BlobReader: r.Body,
246+
FileName: query.FileName,
247+
}}
248+
249+
addArtifactHelper(query, artifactBlobs, w, r)
250+
}
251+
252+
func AddLocalArtifact(w http.ResponseWriter, r *http.Request) {
253+
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
254+
255+
query := artifactAddRequestQuery{}
256+
257+
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
258+
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
259+
return
260+
}
261+
262+
if query.Name == "" || query.FileName == "" {
263+
utils.Error(w, http.StatusBadRequest, errors.New("name and file parameters are required"))
264+
return
265+
}
266+
267+
cleanPath := filepath.Clean(query.Path)
268+
// Check if the path exists on server side.
269+
// Note: localapi.ValidatePathForLocalAPI returns nil if the file exists and path is absolute, not an error.
270+
switch err := localapi.ValidatePathForLocalAPI(cleanPath); {
271+
case err == nil:
272+
// no error -> continue
273+
case errors.Is(err, localapi.ErrPathNotAbsolute):
274+
utils.Error(w, http.StatusBadRequest, err)
275+
return
276+
case errors.Is(err, fs.ErrNotExist):
277+
utils.Error(w, http.StatusNotFound, fmt.Errorf("file does not exist: %q", cleanPath))
278+
return
279+
default:
280+
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to access file: %w", err))
281+
return
282+
}
283+
284+
artifactBlobs := []entities.ArtifactBlob{{
285+
BlobFilePath: cleanPath,
286+
FileName: query.FileName,
287+
}}
288+
289+
addArtifactHelper(query, artifactBlobs, w, r)
290+
}
291+
292+
func addArtifactHelper(query artifactAddRequestQuery, artifactBlobs []entities.ArtifactBlob, w http.ResponseWriter, r *http.Request) {
293+
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
239294
annotations, err := domain_utils.ParseAnnotations(query.Annotations)
240295
if err != nil {
241296
utils.Error(w, http.StatusBadRequest, err)
@@ -250,13 +305,7 @@ func AddArtifact(w http.ResponseWriter, r *http.Request) {
250305
Replace: query.Replace,
251306
}
252307

253-
artifactBlobs := []entities.ArtifactBlob{{
254-
BlobReader: r.Body,
255-
FileName: query.FileName,
256-
}}
257-
258308
imageEngine := abi.ImageEngine{Libpod: runtime}
259-
260309
artifacts, err := imageEngine.ArtifactAdd(r.Context(), query.Name, artifactBlobs, artifactAddOptions)
261310
if err != nil {
262311
if errors.Is(err, libartifact_types.ErrArtifactNotExist) {

pkg/api/handlers/libpod/images.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"strings"
1717

1818
"github.com/containers/buildah"
19+
"github.com/containers/podman/v6/internal/localapi"
1920
"github.com/containers/podman/v6/libpod"
2021
"github.com/containers/podman/v6/libpod/define"
2122
"github.com/containers/podman/v6/pkg/api/handlers"
@@ -41,7 +42,6 @@ import (
4142
"go.podman.io/storage"
4243
"go.podman.io/storage/pkg/archive"
4344
"go.podman.io/storage/pkg/chrootarchive"
44-
"go.podman.io/storage/pkg/fileutils"
4545
"go.podman.io/storage/pkg/idtools"
4646
)
4747

@@ -396,10 +396,13 @@ func ImagesLocalLoad(w http.ResponseWriter, r *http.Request) {
396396

397397
cleanPath := filepath.Clean(query.Path)
398398
// Check if the path exists on server side.
399-
// Note: fileutils.Exists returns nil if the file exists, not an error.
400-
switch err := fileutils.Exists(cleanPath); {
399+
// Note: localapi.ValidatePathForLocalAPI returns nil if the file exists and path is absolute, not an error.
400+
switch err := localapi.ValidatePathForLocalAPI(cleanPath); {
401401
case err == nil:
402402
// no error -> continue
403+
case errors.Is(err, localapi.ErrPathNotAbsolute):
404+
utils.Error(w, http.StatusBadRequest, err)
405+
return
403406
case errors.Is(err, fs.ErrNotExist):
404407
utils.Error(w, http.StatusNotFound, fmt.Errorf("file does not exist: %q", cleanPath))
405408
return

pkg/api/server/register_artifacts.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,65 @@ func (s *APIServer) registerArtifactHandlers(r *mux.Router) error {
212212
// 500:
213213
// $ref: "#/responses/internalError"
214214
r.Handle(VersionedPath("/libpod/artifacts/add"), s.APIHandler(libpod.AddArtifact)).Methods(http.MethodPost)
215+
// swagger:operation POST /libpod/artifacts/local/add libpod ArtifactLocalLibpod
216+
// ---
217+
// tags:
218+
// - artifacts
219+
// summary: Add a local file as an artifact
220+
// description: |
221+
// Add a file from the local filesystem as a new OCI artifact, or append to an existing artifact if 'append' is true.
222+
// produces:
223+
// - application/json
224+
// parameters:
225+
// - name: name
226+
// in: query
227+
// description: Mandatory reference to the artifact (e.g., quay.io/image/artifact:tag)
228+
// required: true
229+
// type: string
230+
// - name: path
231+
// in: query
232+
// description: Absolute path to the local file on the server filesystem to be added
233+
// required: true
234+
// type: string
235+
// - name: fileName
236+
// in: query
237+
// description: Name/title of the file within the artifact
238+
// required: true
239+
// type: string
240+
// - name: fileMIMEType
241+
// in: query
242+
// description: Optionally set the MIME type of the file
243+
// type: string
244+
// - name: annotations
245+
// in: query
246+
// description: Array of annotation strings e.g "test=true"
247+
// type: array
248+
// items:
249+
// type: string
250+
// - name: artifactMIMEType
251+
// in: query
252+
// description: Use type to describe an artifact
253+
// type: string
254+
// - name: append
255+
// in: query
256+
// description: Append files to an existing artifact
257+
// type: boolean
258+
// default: false
259+
// - name: replace
260+
// in: query
261+
// description: Replace an existing artifact with the same name
262+
// type: boolean
263+
// default: false
264+
// responses:
265+
// 201:
266+
// $ref: "#/responses/artifactAddResponse"
267+
// 400:
268+
// $ref: "#/responses/badParamError"
269+
// 404:
270+
// $ref: "#/responses/artifactNotFound"
271+
// 500:
272+
// $ref: "#/responses/internalError"
273+
r.Handle(VersionedPath("/libpod/artifacts/local/add"), s.APIHandler(libpod.AddLocalArtifact)).Methods(http.MethodPost)
215274
// swagger:operation POST /libpod/artifacts/{name}/push libpod ArtifactPushLibpod
216275
// ---
217276
// tags:

pkg/api/server/register_images.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
952952
// name: path
953953
// type: string
954954
// required: true
955-
// description: Path to the image archive file on the server filesystem
955+
// description: Absolute path to the image archive file on the server filesystem
956956
// produces:
957957
// - application/json
958958
// responses:

pkg/bindings/artifacts/add.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,31 @@ import (
44
"context"
55
"io"
66
"net/http"
7+
"net/url"
78

89
"github.com/containers/podman/v6/pkg/bindings"
10+
"github.com/containers/podman/v6/pkg/domain/entities"
911
entitiesTypes "github.com/containers/podman/v6/pkg/domain/entities/types"
1012
)
1113

1214
func Add(ctx context.Context, artifactName string, blobName string, artifactBlob io.Reader, options *AddOptions) (*entitiesTypes.ArtifactAddReport, error) {
13-
conn, err := bindings.GetClient(ctx)
15+
params, err := prepareParams(artifactName, blobName, options)
1416
if err != nil {
1517
return nil, err
1618
}
19+
return helperAdd(ctx, "/artifacts/add", params, artifactBlob)
20+
}
21+
22+
func AddLocal(ctx context.Context, artifactName string, blobName string, blobPath string, options *AddOptions) (*entitiesTypes.ArtifactAddReport, error) {
23+
params, err := prepareParams(artifactName, blobName, options)
24+
if err != nil {
25+
return nil, err
26+
}
27+
params.Set("path", blobPath)
28+
return helperAdd(ctx, "/artifacts/local/add", params, nil)
29+
}
1730

31+
func prepareParams(name string, fileName string, options *AddOptions) (url.Values, error) {
1832
if options == nil {
1933
options = new(AddOptions)
2034
}
@@ -24,16 +38,25 @@ func Add(ctx context.Context, artifactName string, blobName string, artifactBlob
2438
return nil, err
2539
}
2640

27-
params.Set("name", artifactName)
28-
params.Set("fileName", blobName)
41+
params.Set("name", name)
42+
params.Set("fileName", fileName)
43+
44+
return params, nil
45+
}
46+
47+
func helperAdd(ctx context.Context, endpoint string, params url.Values, artifactBlob io.Reader) (*entities.ArtifactAddReport, error) {
48+
conn, err := bindings.GetClient(ctx)
49+
if err != nil {
50+
return nil, err
51+
}
2952

30-
response, err := conn.DoRequest(ctx, artifactBlob, http.MethodPost, "/artifacts/add", params, nil)
53+
response, err := conn.DoRequest(ctx, artifactBlob, http.MethodPost, endpoint, params, nil)
3154
if err != nil {
3255
return nil, err
3356
}
3457
defer response.Body.Close()
3558

36-
var artifactAddReport entitiesTypes.ArtifactAddReport
59+
var artifactAddReport entities.ArtifactAddReport
3760
if err := response.Process(&artifactAddReport); err != nil {
3861
return nil, err
3962
}

0 commit comments

Comments
 (0)