-
Notifications
You must be signed in to change notification settings - Fork 379
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
CI: enable errorlint #2512
CI: enable errorlint #2512
Changes from all commits
7092582
1d4025f
aa038a5
61852e6
c7da673
1adc5c4
2f691ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,7 @@ | |
run: | ||
concurrency: 6 | ||
timeout: 5m | ||
|
||
linters: | ||
enable: | ||
- errorlint |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,22 @@ | ||
package directory | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"syscall" | ||
|
||
"github.com/containers/image/v5/internal/imagedestination/impl" | ||
"github.com/containers/image/v5/internal/imagedestination/stubs" | ||
"github.com/containers/image/v5/internal/private" | ||
"github.com/containers/image/v5/internal/putblobdigest" | ||
"github.com/containers/image/v5/internal/signature" | ||
"github.com/containers/image/v5/types" | ||
"github.com/containers/storage/pkg/fileutils" | ||
"github.com/opencontainers/go-digest" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
@@ -54,49 +55,40 @@ func newImageDestination(sys *types.SystemContext, ref dirReference) (private.Im | |
// If directory exists check if it is empty | ||
// if not empty, check whether the contents match that of a container image directory and overwrite the contents | ||
// if the contents don't match throw an error | ||
dirExists, err := pathExists(ref.resolvedPath) | ||
if err != nil { | ||
return nil, fmt.Errorf("checking for path %q: %w", ref.resolvedPath, err) | ||
} | ||
if dirExists { | ||
isEmpty, err := isDirEmpty(ref.resolvedPath) | ||
if err != nil { | ||
dir, err := os.OpenFile(ref.resolvedPath, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|os.O_RDONLY, 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code must remain cross-platform. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strictly speaking, neither syscall flags are needed here.
Do you want me to drop these, or have these two flags used when on Unix only? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading a bit more carefully: in the first place, it’s not defined at all that I don’t see what threat the So I’m fine with dropping both. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done that in #2520 |
||
switch { | ||
case err == nil: // Directory exists. | ||
contents, err := dir.Readdirnames(-1) | ||
_ = dir.Close() | ||
if err != nil { // Unexpected error. | ||
return nil, err | ||
} | ||
|
||
if !isEmpty { | ||
versionExists, err := pathExists(ref.versionPath()) | ||
if err != nil { | ||
return nil, fmt.Errorf("checking if path exists %q: %w", ref.versionPath(), err) | ||
} | ||
if versionExists { | ||
contents, err := os.ReadFile(ref.versionPath()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// check if contents of version file is what we expect it to be | ||
if string(contents) != version { | ||
return nil, ErrNotContainerImageDir | ||
if len(contents) > 0 { // Not empty. | ||
// Check if contents of version file is what we expect it to be. | ||
ver, err := os.ReadFile(ref.versionPath()) | ||
if err == nil && bytes.Equal(ver, []byte(version)) { | ||
// Definitely an image directory. Reuse by removing all the old contents. | ||
logrus.Debugf("overwriting existing container image directory %q", ref.resolvedPath) | ||
for _, name := range contents { | ||
if os.RemoveAll(filepath.Join(ref.resolvedPath, name)) != nil { | ||
return nil, err | ||
} | ||
} | ||
} else { | ||
return nil, ErrNotContainerImageDir | ||
} | ||
// delete directory contents so that only one image is in the directory at a time | ||
if err = removeDirContents(ref.resolvedPath); err != nil { | ||
return nil, fmt.Errorf("erasing contents in %q: %w", ref.resolvedPath, err) | ||
} | ||
logrus.Debugf("overwriting existing container image directory %q", ref.resolvedPath) | ||
} | ||
} else { | ||
// create directory if it doesn't exist | ||
if err := os.MkdirAll(ref.resolvedPath, 0755); err != nil { | ||
return nil, fmt.Errorf("unable to create directory %q: %w", ref.resolvedPath, err) | ||
case errors.Is(err, os.ErrNotExist): // Directory does not exist; create it. | ||
if err := os.MkdirAll(ref.resolvedPath, 0o755); err != nil { | ||
return nil, err | ||
} | ||
default: | ||
// Unexpected error. | ||
return nil, err | ||
} | ||
// create version file | ||
err = os.WriteFile(ref.versionPath(), []byte(version), 0644) | ||
|
||
// Create version file. | ||
err = os.WriteFile(ref.versionPath(), []byte(version), 0o644) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating version file %q: %w", ref.versionPath(), err) | ||
return nil, err | ||
} | ||
|
||
d := &dirImageDestination{ | ||
|
@@ -261,39 +253,3 @@ func (d *dirImageDestination) PutSignaturesWithFormat(ctx context.Context, signa | |
func (d *dirImageDestination) Commit(context.Context, types.UnparsedImage) error { | ||
return nil | ||
} | ||
|
||
// returns true if path exists | ||
func pathExists(path string) (bool, error) { | ||
err := fileutils.Exists(path) | ||
if err == nil { | ||
return true, nil | ||
} | ||
if os.IsNotExist(err) { | ||
return false, nil | ||
} | ||
return false, err | ||
} | ||
|
||
// returns true if directory is empty | ||
func isDirEmpty(path string) (bool, error) { | ||
files, err := os.ReadDir(path) | ||
if err != nil { | ||
return false, err | ||
} | ||
return len(files) == 0, nil | ||
} | ||
|
||
// deletes the contents of a directory | ||
func removeDirContents(path string) error { | ||
files, err := os.ReadDir(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, file := range files { | ||
if err := os.RemoveAll(filepath.Join(path, file.Name())); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ func NewWriter(sys *types.SystemContext, path string) (*Writer, error) { | |
// only in a different way. Either way, it’s up to the user to not have two writers to the same path.) | ||
fh, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644) | ||
if err != nil { | ||
return nil, fmt.Errorf("opening file %q: %w", path, err) | ||
return nil, err | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh, I just realized Can’t be helped, really — even if we wanted to wrap all @$#!@$#!@ Go and its worse-is-better designs @$#!@$#!@ So, ultimately, I think all these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that using So I guess if we use %q for errors everywhere we log/print them (which is probably a doable task), we can omit wrapping errors from Oh BTW this was not caused by warnings from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Except, of course, double quotes will not be needed in this case. Hmm... Overall, I think, while it's nice to have non-printable characters quoted, it's not crucial to have. If we're worried about attacks with weird inputs, those inputs need to be validated. Do you think opening a Go proposal to quote the file name in '(*PathError).Error` makes sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don’t think that’s ever going to happen at the top level, e.g. because printing an error which does use
Consider There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, wrapping and joining indeed makes things less comprehensible. The only real solution to that, I guess, is structured logging. I also played with having an |
||
} | ||
succeeded := false | ||
defer func() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -165,7 +165,7 @@ func (br *bodyReader) Read(p []byte) (int, error) { | |
} | ||
res, err := br.c.makeRequest(br.ctx, http.MethodGet, br.path, headers, nil, v2Auth, nil) | ||
if err != nil { | ||
return n, fmt.Errorf("%w (while reconnecting: %v)", originalErr, err) | ||
return n, fmt.Errorf("%w (while reconnecting: %w)", originalErr, err) | ||
} | ||
consumedBody := false | ||
defer func() { | ||
|
@@ -179,7 +179,7 @@ func (br *bodyReader) Read(p []byte) (int, error) { | |
// The recipient of an invalid Content-Range MUST NOT attempt to recombine the received content with a stored representation. | ||
first, last, completeLength, err := parseContentRange(res) | ||
if err != nil { | ||
return n, fmt.Errorf("%w (after reconnecting, invalid Content-Range header: %v)", originalErr, err) | ||
return n, fmt.Errorf("%w (after reconnecting, invalid Content-Range header: %w)", originalErr, err) | ||
} | ||
// We don’t handle responses that start at an unrequested offset, nor responses that terminate before the end of the full blob. | ||
if first != br.offset || (completeLength != -1 && last+1 != completeLength) { | ||
|
@@ -190,7 +190,7 @@ func (br *bodyReader) Read(p []byte) (int, error) { | |
return n, fmt.Errorf("%w (after reconnecting, server did not process a Range: header, status %d)", originalErr, http.StatusOK) | ||
default: | ||
err := registryHTTPResponseToError(res) | ||
return n, fmt.Errorf("%w (after reconnecting, fetching blob: %v)", originalErr, err) | ||
return n, fmt.Errorf("%w (after reconnecting, fetching blob: %w)", originalErr, err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See |
||
} | ||
|
||
logrus.Debugf("Successfully reconnected to %s", redactedURL) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,7 +99,7 @@ func parseHTTPErrorResponse(statusCode int, r io.Reader) error { | |
} | ||
|
||
func makeErrorList(err error) []error { | ||
if errL, ok := err.(errcode.Errors); ok { | ||
if errL, ok := err.(errcode.Errors); ok { //nolint:errorlint | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this should happen at all, please include the actual warning being silenced, throughout. |
||
return []error(errL) | ||
} | ||
return []error{err} | ||
|
@@ -139,7 +139,7 @@ func handleErrorResponse(resp *http.Response) error { | |
} | ||
} | ||
err := parseHTTPErrorResponse(resp.StatusCode, resp.Body) | ||
if uErr, ok := err.(*unexpectedHTTPResponseError); ok && resp.StatusCode == 401 { | ||
if uErr, ok := err.(*unexpectedHTTPResponseError); ok && resp.StatusCode == 401 { //nolint:errorlint | ||
return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) | ||
} | ||
return err | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -186,7 +186,7 @@ func newImageSourceAttempt(ctx context.Context, sys *types.SystemContext, logica | |
cmd.Stdin = bytes.NewReader(acfD) | ||
if err := cmd.Run(); err != nil { | ||
var stderr string | ||
if ee, ok := err.(*exec.ExitError); ok { | ||
if ee, ok := err.(*exec.ExitError); ok { //nolint:errorlint | ||
stderr = string(ee.Stderr) | ||
} | ||
logrus.Warnf("Failed to call additional-layer-store-auth-helper (stderr:%s): %v", stderr, err) | ||
|
@@ -357,7 +357,7 @@ var multipartByteRangesRe = regexp.Delayed("multipart/byteranges; boundary=([A-Z | |
func parseMediaType(contentType string) (string, map[string]string, error) { | ||
mediaType, params, err := mime.ParseMediaType(contentType) | ||
if err != nil { | ||
if err == mime.ErrInvalidMediaParameter { | ||
if err == mime.ErrInvalidMediaParameter { //nolint:errorlint // TODO remove this (https://github.com/polyfloyd/go-errorlint/pull/79). | ||
// CloudFront returns an invalid MIME type, that contains an unquoted ":" in the boundary | ||
// param, let's handle it here. | ||
matches := multipartByteRangesRe.FindStringSubmatch(contentType) | ||
|
@@ -798,7 +798,7 @@ func (n *bufferedNetworkReader) read(p []byte) (int, error) { | |
select { | ||
case b = <-n.readyBuffer: | ||
if b.err != nil { | ||
if b.err != io.EOF { | ||
if b.err != io.EOF { //nolint:errorlint | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning exactly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. errorlint has a list of exceptions for that, listing functions that can return literal |
||
return b.len, b.err | ||
} | ||
n.gotEOF = true | ||
|
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 using the standard-library
errors.Join
is basically never valuable.errors.Join
is not wrapped itself (because that wrap-around-Join
probably reformats the an error message and the CLI layer has no idea whether it is correct to discard that updated error message formatting).errors.Join
should be replaced by carefully designing the resulting text. (Compareinternal/multierr.Format
).errors.Is(errors.Join(…))
means. The only reasonable case I can think of is that there is a single underlying error cause, and a multi-errorIs
is used to match that cause against multiple error types, e.g. for compatibility with various APIs. If the error value actually reports multiple independent error situations / causes (as in here), I don’t know useful action the caller could ever take based on anerrors.Is
operation.errors.Is(err, RemoteServerError) && !errors.Is(err, RemoteServerOverloadedErr)
breaks if the two branches can match two different error causes.And in particular, in these cleanup situations, the caller almost certainly doesn’t want to react to the cause of the
Close
failure ifretErr
has already been set. So I think type-erasing theerr
and only including the string is the more correct thing to do.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.
Done that in #2519.