Skip to content
Snippets Groups Projects
Unverified Commit e4110548 authored by Bob Callaway's avatar Bob Callaway Committed by GitHub
Browse files

Return location of existing entry if upload tried (#226)


Adds a Location response header when a 409 Conflict error is returned
from the server when a duplicate entry is sent for insertion into the
log. Also changes message printed by CLI to improve usability.

Fixes #222

Signed-off-by: default avatarBob Callaway <bcallawa@redhat.com>
parent ba7db48f
No related branches found
No related tags found
No related merge requests found
......@@ -30,15 +30,16 @@ import (
)
type uploadCmdOutput struct {
Location string
Index int64
AlreadyExists bool
Location string
Index int64
}
func (u *uploadCmdOutput) String() string {
if u.Location != "" {
return fmt.Sprintf("Created entry at index %d, available at: %v%v\n", u.Index, viper.GetString("rekor_server"), u.Location)
if u.AlreadyExists {
return fmt.Sprintf("Entry already exists; available at: %v%v\n", viper.GetString("rekor_server"), u.Location)
}
return "Entry already exists.\n"
return fmt.Sprintf("Created entry at index %d, available at: %v%v\n", u.Index, viper.GetString("rekor_server"), u.Location)
}
// uploadCmd represents the upload command
......@@ -84,9 +85,12 @@ var uploadCmd = &cobra.Command{
resp, err := rekorClient.Entries.CreateLogEntry(params)
if err != nil {
switch err.(type) {
switch e := err.(type) {
case *entries.CreateLogEntryConflict:
return &uploadCmdOutput{Location: ""}, nil
return &uploadCmdOutput{
Location: e.Location.String(),
AlreadyExists: true,
}, nil
default:
return nil, err
}
......
......@@ -432,6 +432,10 @@ responses:
description: The request conflicts with the current state of the transparency log
schema:
$ref: "#/definitions/Error"
headers:
Location:
type: string
format: uri
NotFound:
description: The content requested could not be found
InternalServerError:
......
......@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"github.com/google/trillian"
"github.com/spf13/viper"
......@@ -102,7 +103,8 @@ func CreateLogEntryHandler(params entries.CreateLogEntryParams) middleware.Respo
switch insertionStatus.Code {
case int32(code.Code_OK):
case int32(code.Code_ALREADY_EXISTS), int32(code.Code_FAILED_PRECONDITION):
return handleRekorAPIError(params, http.StatusConflict, fmt.Errorf("grpc error: %v", insertionStatus.String()), entryAlreadyExists)
existingUUID := hex.EncodeToString(rfc6962.DefaultHasher.HashLeaf(leaf))
return handleRekorAPIError(params, http.StatusConflict, fmt.Errorf("grpc error: %v", insertionStatus.String()), fmt.Sprintf(entryAlreadyExists, existingUUID), "entryURL", getEntryURL(*httpReq.URL, existingUUID))
default:
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("grpc error: %v", insertionStatus.String()), trillianUnexpectedResult)
}
......@@ -131,13 +133,17 @@ func CreateLogEntryHandler(params entries.CreateLogEntryParams) middleware.Respo
}()
}
locationURL := httpReq.URL
return entries.NewCreateLogEntryCreated().WithPayload(logEntry).WithLocation(getEntryURL(*httpReq.URL, uuid)).WithETag(uuid)
}
func getEntryURL(locationURL url.URL, uuid string) strfmt.URI {
// remove API key from output
query := locationURL.Query()
query.Del("apiKey")
locationURL.RawQuery = query.Encode()
locationURL.Path = fmt.Sprintf("%v/%v", locationURL.Path, uuid)
return entries.NewCreateLogEntryCreated().WithPayload(logEntry).WithLocation(strfmt.URI(locationURL.String())).WithETag(uuid)
return strfmt.URI(locationURL.String())
}
func GetLogEntryByUUIDHandler(params entries.GetLogEntryByUUIDParams) middleware.Responder {
......
......@@ -21,6 +21,7 @@ import (
"regexp"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/mitchellh/mapstructure"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/generated/restapi/operations/entries"
......@@ -33,7 +34,7 @@ const (
trillianCommunicationError = "Unexpected error communicating with transparency log"
trillianUnexpectedResult = "Unexpected result from transparency log"
failedToGenerateCanonicalEntry = "Error generating canonicalized entry"
entryAlreadyExists = "An equivalent entry already exists in the transparency log"
entryAlreadyExists = "An equivalent entry already exists in the transparency log with UUID %v"
firstSizeLessThanLastSize = "firstSize(%d) must be less than lastSize(%d)"
malformedUUID = "UUID must be a 64-character hexadecimal string"
malformedHash = "Hash must be a 64-character hexadecimal string created from SHA256 algorithm"
......@@ -98,7 +99,19 @@ func handleRekorAPIError(params interface{}, code int, err error, message string
case http.StatusBadRequest:
return entries.NewCreateLogEntryBadRequest().WithPayload(errorMsg(message, code))
case http.StatusConflict:
return entries.NewCreateLogEntryConflict().WithPayload(errorMsg(message, code))
resp := entries.NewCreateLogEntryConflict().WithPayload(errorMsg(message, code))
locationFound := false
for _, field := range fields {
if locationFound {
existingURL := field.(strfmt.URI)
resp.SetLocation(existingURL)
break
} else if field.(string) == "entryURL" {
locationFound = true
continue
}
}
return resp
default:
return entries.NewCreateLogEntryDefault(code).WithPayload(errorMsg(message, code))
}
......
......@@ -163,6 +163,8 @@ func NewCreateLogEntryConflict() *CreateLogEntryConflict {
The request conflicts with the current state of the transparency log
*/
type CreateLogEntryConflict struct {
Location strfmt.URI
Payload *models.Error
}
......@@ -176,6 +178,14 @@ func (o *CreateLogEntryConflict) GetPayload() *models.Error {
func (o *CreateLogEntryConflict) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response header Location
location, err := formats.Parse("uri", response.GetHeader("Location"))
if err != nil {
return errors.InvalidType("Location", "header", "strfmt.URI", response.GetHeader("Location"))
}
o.Location = *(location.(*strfmt.URI))
o.Payload = new(models.Error)
// response payload
......
......@@ -633,6 +633,12 @@ func init() {
"description": "The request conflicts with the current state of the transparency log",
"schema": {
"$ref": "#/definitions/Error"
},
"headers": {
"Location": {
"type": "string",
"format": "uri"
}
}
},
"InternalServerError": {
......@@ -814,6 +820,12 @@ func init() {
"description": "The request conflicts with the current state of the transparency log",
"schema": {
"$ref": "#/definitions/Error"
},
"headers": {
"Location": {
"type": "string",
"format": "uri"
}
}
},
"default": {
......@@ -1879,6 +1891,12 @@ func init() {
"description": "The request conflicts with the current state of the transparency log",
"schema": {
"$ref": "#/definitions/Error"
},
"headers": {
"Location": {
"type": "string",
"format": "uri"
}
}
},
"InternalServerError": {
......
......@@ -174,6 +174,10 @@ const CreateLogEntryConflictCode int = 409
swagger:response createLogEntryConflict
*/
type CreateLogEntryConflict struct {
/*
*/
Location strfmt.URI `json:"Location"`
/*
In: Body
......@@ -187,6 +191,17 @@ func NewCreateLogEntryConflict() *CreateLogEntryConflict {
return &CreateLogEntryConflict{}
}
// WithLocation adds the location to the create log entry conflict response
func (o *CreateLogEntryConflict) WithLocation(location strfmt.URI) *CreateLogEntryConflict {
o.Location = location
return o
}
// SetLocation sets the location to the create log entry conflict response
func (o *CreateLogEntryConflict) SetLocation(location strfmt.URI) {
o.Location = location
}
// WithPayload adds the payload to the create log entry conflict response
func (o *CreateLogEntryConflict) WithPayload(payload *models.Error) *CreateLogEntryConflict {
o.Payload = payload
......@@ -201,6 +216,13 @@ func (o *CreateLogEntryConflict) SetPayload(payload *models.Error) {
// WriteResponse to the client
func (o *CreateLogEntryConflict) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
// response header Location
location := o.Location.String()
if location != "" {
rw.Header().Set("Location", location)
}
rw.WriteHeader(409)
if o.Payload != nil {
payload := o.Payload
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment