blob: 3df53025f92599abb9006acd41893e9e3faa7e54 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can
// found in the LICENSE file.
package artifactory
import (
"context"
"io"
"strings"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
const (
// Err412Msg is substring of an error message returned that indicates a 412
// HTTP response.
err412Msg = "Error 412"
)
// Copy abstracts writing a reader to the Cloud, `w` canonically being a
// storage.Writer. It handles cases relating to the asynchronously
// returning on error on Close() or returning a 412, which represents the case
// where an object fails the precondition of not existing at the time of the
// write.
func Copy(ctx context.Context, objName string, r io.Reader, w io.WriteCloser, chunkSize int64) error {
var err error
for {
if _, err = io.CopyN(w, r, chunkSize); err != nil {
break
}
}
// If the precondition of the object not existing is not met on write (i.e.,
// at the time of the write the object is there), then the server will respond
// with a 412. (See
// https://cloud.google.com/storage/docs/json_api/v1/status-codes and
// https://tools.ietf.org/html/rfc7232#section-4.2.)
// We do not report this as an error, however, as the associated object might
// have been created after having checked its non-existence - and we wish to
// be resilient in the event of such a race.
handleOkayError := func(err error) error {
preconditionNotMet := err != nil && strings.Contains(err.Error(), err412Msg)
if err == io.EOF || preconditionNotMet {
if preconditionNotMet {
logger.Warningf(ctx, "object %q: created after its non-existence check", objName)
}
return nil
}
return err
}
// Writes happen asynchronously, and so a nil may be returned while the write
// goes on to fail. It is recommended in
// https://godoc.org/cloud.google.com/go/storage#Writer.Write
// to return the value of Close() to detect the success of the write.
if handleOkayError(err) == nil {
return handleOkayError(w.Close())
}
w.Close()
return err
}