-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Feat: send additional build contexts as tar files for remote builds #26628
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: 2004joshua The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a full review, but reading into memory does not seem viable to me.
Have a look at ManifestModify() and the bindings manifests Modify() functions which AFAICT to such a multipart request without reading all into memory
Git Repositories: cloned locally then sent as a tar
URLs/archives: downloaded locally then sent as a tar
It would be best if just the server downloads them instead of the client, sending them as tar via the API just sounds slower.
const maxMemory = 256 << 20 | ||
|
||
if err := r.ParseMultipartForm(maxMemory); err != nil { | ||
return "", nil, fmt.Errorf("failed to parse multipart form: %w", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This read everything into memory which does not seem good and the limit is likely to low for some contexts
pkg/bindings/images/build.go
Outdated
var body bytes.Buffer | ||
writer := multipart.NewWriter(&body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These contexts can be very large, much bigger than the available memory so reading everything into memory seems unacceptable to me
7ea602e
to
b2db9e2
Compare
[NON-BLOCKING] Packit jobs failed. @containers/packit-build please check. Everyone else, feel free to ignore. |
b2db9e2
to
71f5764
Compare
d9d6872
to
5b62920
Compare
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: containers#23433 Signed-off-by: Joshua Arrevillaga <[email protected]>
5b62920
to
d56b2a1
Compare
if err != nil { | ||
return err | ||
} | ||
|
||
header, err := tar.FileInfoHeader(info, "") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
relPath, err := filepath.Rel(context.Value, path) | ||
if err != nil { | ||
return err | ||
} | ||
if relPath == "." { | ||
relPath = filepath.Base(context.Value) | ||
} | ||
header.Name = relPath | ||
|
||
if err := tarWriter.WriteHeader(header); err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all the return err
here should we wrapped
with helper err message ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some unnecessary duplication of archive/extracting logic on both ends, probably still have an issue where the top-level directory of an additional build context has a different timestamp between otherwise-identical build requests made at different times.
return "", nil, fmt.Errorf("creating context directory: %w", err) | ||
} | ||
|
||
if err := archive.Untar(part, contextPath, nil); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should consider using extractTarFile()
here, since it's what handles the default build context archive when the request isn't a multipart form.
switch contentType { | ||
case "application/tar": | ||
logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType) | ||
case "application/x-tar": | ||
break | ||
case "multipart/form-data": | ||
logrus.Debugf("Received multipart/form-data: %s", contentType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this always going to log "Received multipart/form-data: multipart/form-data"?
} | ||
logrus.Debugf("Added image context %q with reference %q", contextName, imageRef) | ||
|
||
case strings.HasPrefix(fieldName, "buildcontext-local-") && fileName != "": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is fileName
used at all?
|
||
case strings.HasPrefix(fieldName, "buildcontext-local-") && fileName != "": | ||
contextName := strings.TrimPrefix(fieldName, "buildcontext-local-") | ||
additionalAnchor := filepath.Join(anchorDir, "additional", contextName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rule of thumb: when possible, avoid using values supplied by the client when constructing path names. os.MkdirTemp()
would be fine here.
case context.IsImage: | ||
metadataField := fmt.Sprintf("buildcontext-image-%s", name) | ||
if err := writer.WriteField(metadataField, context.Value); err != nil { | ||
pipeWriter.CloseWithError(fmt.Errorf("adding URL context %q: %w", name, err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pipeWriter.CloseWithError(fmt.Errorf("adding URL context %q: %w", name, err)) | |
pipeWriter.CloseWithError(fmt.Errorf("adding image context %q: %w", name, err)) |
return | ||
} | ||
} else { | ||
tarWriter := tar.NewWriter(part) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this calling nTar()
instead? This is missing logic for handling symbolic and hard links, and compressing the archive.
} | ||
|
||
// local context is already a tar | ||
if strings.HasSuffix(context.Value, ".tar") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expecting the file name to correctly indicate things can lead to errors. Use github.com/containers/storage/pkg/archive.IsArchivePath()
.
if relPath == "." { | ||
relPath = filepath.Base(context.Value) | ||
} | ||
header.Name = relPath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If my additional build context path is "/tmp/build-context", this adds an entry for a directory named "build-context" to the archive? Why?
return "", nil, fmt.Errorf("creating additional context directory %q: %w", contextName, err) | ||
} | ||
|
||
if err := archive.Untar(part, additionalAnchor, nil); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The archive content's coming from a client. Why isn't it extracted using chrootarchive.Untar()
, or extractTarFile()
?
logrus.Debugf("Processing part: field=%q, file=%q", fieldName, fileName) | ||
|
||
switch { | ||
case fieldName == "context" && fileName != "": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is fileName
used at all?
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 sent as tar files. Each additional context is packaged and transferred based on its type:
Skip("Waiting for VM images with Podman > 5.5.2 for multipart build context support") is in place until VM images are updated to podman 5.6.0
Version check is in place to make sure that the server is updated to at least the upcoming 5.6.0 update or else the fix wont be usable.
Fixes: #23433
Does this PR introduce a user-facing change?