release create: clean up leftover draft release on upload/publish failure
This commit is contained in:
parent
36ffbe18de
commit
cbeed671dd
3 changed files with 136 additions and 3 deletions
|
|
@ -426,6 +426,7 @@ func createRun(opts *CreateOptions) error {
|
|||
}
|
||||
|
||||
hasAssets := len(opts.Assets) > 0
|
||||
draftWhileUploading := false
|
||||
|
||||
if hasAssets && !opts.Draft {
|
||||
// Check for an existing release
|
||||
|
|
@ -437,6 +438,7 @@ func createRun(opts *CreateOptions) error {
|
|||
}
|
||||
}
|
||||
// Save the release initially as draft and publish it after all assets have finished uploading
|
||||
draftWhileUploading = true
|
||||
params["draft"] = true
|
||||
}
|
||||
|
||||
|
|
@ -445,6 +447,16 @@ func createRun(opts *CreateOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
cleanupDraftRelease := func(err error) error {
|
||||
if !draftWhileUploading {
|
||||
return err
|
||||
}
|
||||
if cleanupErr := deleteRelease(httpClient, newRelease); cleanupErr != nil {
|
||||
return fmt.Errorf("%w\ncleaning up draft failed: %v", err, cleanupErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if hasAssets {
|
||||
uploadURL := newRelease.UploadURL
|
||||
if idx := strings.IndexRune(uploadURL, '{'); idx > 0 {
|
||||
|
|
@ -455,13 +467,13 @@ func createRun(opts *CreateOptions) error {
|
|||
err = shared.ConcurrentUpload(httpClient, uploadURL, opts.Concurrency, opts.Assets)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
return cleanupDraftRelease(err)
|
||||
}
|
||||
|
||||
if !opts.Draft {
|
||||
if draftWhileUploading {
|
||||
rel, err := publishRelease(httpClient, newRelease.APIURL, opts.DiscussionCategory)
|
||||
if err != nil {
|
||||
return err
|
||||
return cleanupDraftRelease(err)
|
||||
}
|
||||
newRelease = rel
|
||||
}
|
||||
|
|
|
|||
|
|
@ -728,6 +728,102 @@ func Test_createRun(t *testing.T) {
|
|||
wantStderr: ``,
|
||||
wantErr: `a release with the same tag name already exists: v1.2.3`,
|
||||
},
|
||||
{
|
||||
name: "clean up draft after uploading files fails",
|
||||
isTTY: false,
|
||||
opts: CreateOptions{
|
||||
TagName: "v1.2.3",
|
||||
Name: "",
|
||||
Body: "",
|
||||
BodyProvided: true,
|
||||
Draft: false,
|
||||
Target: "",
|
||||
Assets: []*shared.AssetForUpload{
|
||||
{
|
||||
Name: "ball.tgz",
|
||||
Open: func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewBufferString(`TARBALL`)), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``))
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.StatusStringResponse(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
"upload_url": "https://api.github.com/assets/upload",
|
||||
"html_url": "https://github.com/OWNER/REPO/releases/tag/v1.2.3"
|
||||
}`))
|
||||
reg.Register(httpmock.REST("POST", "assets/upload"), httpmock.StatusStringResponse(500, `{}`))
|
||||
reg.Register(httpmock.REST("DELETE", "releases/123"), httpmock.StatusStringResponse(204, ``))
|
||||
},
|
||||
wantStdout: ``,
|
||||
wantStderr: ``,
|
||||
wantErr: `HTTP 500 (https://api.github.com/assets/upload?label=&name=ball.tgz)`,
|
||||
},
|
||||
{
|
||||
name: "clean up draft after publishing fails",
|
||||
isTTY: false,
|
||||
opts: CreateOptions{
|
||||
TagName: "v1.2.3",
|
||||
Name: "",
|
||||
Body: "",
|
||||
BodyProvided: true,
|
||||
Draft: false,
|
||||
Target: "",
|
||||
Assets: []*shared.AssetForUpload{
|
||||
{
|
||||
Name: "ball.tgz",
|
||||
Open: func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewBufferString(`TARBALL`)), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(404, ``))
|
||||
reg.Register(httpmock.REST("POST", "repos/OWNER/REPO/releases"), httpmock.StatusStringResponse(201, `{
|
||||
"url": "https://api.github.com/releases/123",
|
||||
"upload_url": "https://api.github.com/assets/upload",
|
||||
"html_url": "https://github.com/OWNER/REPO/releases/tag/v1.2.3"
|
||||
}`))
|
||||
reg.Register(httpmock.REST("POST", "assets/upload"), httpmock.StatusStringResponse(201, `{}`))
|
||||
reg.Register(httpmock.REST("PATCH", "releases/123"), httpmock.StatusStringResponse(500, `{}`))
|
||||
reg.Register(httpmock.REST("DELETE", "releases/123"), httpmock.StatusStringResponse(204, ``))
|
||||
},
|
||||
wantStdout: ``,
|
||||
wantStderr: ``,
|
||||
wantErr: `HTTP 500 (https://api.github.com/releases/123)`,
|
||||
},
|
||||
{
|
||||
name: "upload files but release already exists",
|
||||
isTTY: true,
|
||||
opts: CreateOptions{
|
||||
TagName: "v1.2.3",
|
||||
Name: "",
|
||||
Body: "",
|
||||
BodyProvided: true,
|
||||
Draft: false,
|
||||
Target: "",
|
||||
Assets: []*shared.AssetForUpload{
|
||||
{
|
||||
Name: "ball.tgz",
|
||||
Open: func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewBufferString(`TARBALL`)), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
Concurrency: 1,
|
||||
},
|
||||
httpStubs: func(t *testing.T, reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("HEAD", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StatusStringResponse(200, ``))
|
||||
},
|
||||
wantStdout: ``,
|
||||
wantStderr: ``,
|
||||
wantErr: `a release with the same tag name already exists: v1.2.3`,
|
||||
},
|
||||
{
|
||||
name: "upload files and create discussion",
|
||||
isTTY: true,
|
||||
|
|
|
|||
|
|
@ -225,3 +225,28 @@ func publishRelease(httpClient *http.Client, releaseURL string, discussionCatego
|
|||
err = json.Unmarshal(b, &release)
|
||||
return &release, err
|
||||
}
|
||||
|
||||
func deleteRelease(httpClient *http.Client, release *shared.Release) error {
|
||||
req, err := http.NewRequest("DELETE", release.APIURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
success := resp.StatusCode >= 200 && resp.StatusCode < 300
|
||||
if !success {
|
||||
return api.HandleHTTPError(resp)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 204 {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue