Skip to content

Commit 03aea55

Browse files
authored
Auto publish draft on deploy and skip user confirm (#30)
Add new flags on deploy command: - Flag --publish-draft which takes the latest draft and publishes it before deployment. - Flag --skip-confirmation which skips the user confirmation dialog in deploy command, which is a manual step that can be eliminated for automation. Other changes: - Fix case of panic when no release is found, LastRelease now returns an error even on 404 instead of a nil release. - Remove pkg/errors and replace with standard error wrapping. - Remove Mock prefix from methods of mock package.
1 parent 18a6b72 commit 03aea55

File tree

22 files changed

+371
-726
lines changed

22 files changed

+371
-726
lines changed

ACKNOWLEDGMENTS.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Ergo makes primarily use of the following OSS projects:
77
* [github](https://github.com/google/go-github)
88
* [go-version](https://github.com/hashicorp/go-version)
99
* [go-homedir](https://github.com/mitchellh/go-homedir)
10-
* [errors](https://github.com/pkg/errors)
1110
* [table](https://github.com/rodaine/table)
1211
* [cobra](https://github.com/spf13/cobra)
1312
* [viper](https://github.com/spf13/viper)

commands/deploy.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ func defineDeployCommand() *cobra.Command {
1616
releaseInterval string
1717
allowForcePush bool
1818
branchesString string
19+
skipConfirm bool
20+
publishDraft bool
1921
)
2022

2123
deployCmd := &cobra.Command{
@@ -28,16 +30,18 @@ func defineDeployCommand() *cobra.Command {
2830
deployCmd.Flags().StringVar(&releaseInterval, "releaseInterval", "25m", "Duration to wait between releases. ('5m', '1h25m', '30s')")
2931
deployCmd.Flags().BoolVar(&allowForcePush, "force", false, "Allow force push if deploy branch has diverged from base")
3032
deployCmd.Flags().StringVar(&branchesString, "branches", "", "Comma separated list of branches")
33+
deployCmd.Flags().BoolVar(&skipConfirm, "skip-confirmation", false, "Create the draft without asking for user confirmation.")
34+
deployCmd.Flags().BoolVar(&publishDraft, "publish-draft", false, "Publish the latest draft release before deployment.")
3135

3236
deployCmd.RunE = func(cmd *cobra.Command, args []string) error {
33-
return defineDeployCommandRun(releaseInterval, releaseOffset, branchesString, allowForcePush)
37+
return defineDeployCommandRun(releaseInterval, releaseOffset, branchesString, allowForcePush, skipConfirm, publishDraft)
3438
}
3539

3640
return deployCmd
3741
}
3842

3943
// defineDeployCommandRun defines the deploy command run actions.
40-
func defineDeployCommandRun(releaseInterval, releaseOffset, branchesString string, allowForcePush bool) error {
44+
func defineDeployCommandRun(releaseInterval, releaseOffset, branchesString string, allowForcePush, skipConfirm, publishDraft bool) error {
4145
ctx := context.Background()
4246

4347
if branchesString != "" {
@@ -57,5 +61,5 @@ func defineDeployCommandRun(releaseInterval, releaseOffset, branchesString strin
5761
opts.ReleaseBodyReplace,
5862
opts.ReleaseBranches,
5963
opts.ReleaseBodyBranches,
60-
).Do(ctx, releaseInterval, releaseOffset, allowForcePush)
64+
).Do(ctx, releaseInterval, releaseOffset, allowForcePush, skipConfirm, publishDraft)
6165
}

ergo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type Host interface {
1010
CreateDraftRelease(ctx context.Context, name, tagName, releaseBody, targetBranch string) error
1111
LastRelease(ctx context.Context) (*Release, error)
1212
EditRelease(ctx context.Context, release *Release) (*Release, error)
13+
PublishRelease(ctx context.Context, releaseID int64) error
1314
CompareBranch(ctx context.Context, baseBranch, branch string) (*StatusReport, error)
1415
DiffCommits(ctx context.Context, releaseBranches []string, baseBranch string) ([]*StatusReport, error)
1516
CreateTag(ctx context.Context, versionName, sha, m string) (*Tag, error)
@@ -44,6 +45,7 @@ type Release struct {
4445
Body string
4546
TagName string
4647
ReleaseURL string
48+
Draft bool
4749
}
4850

4951
// StatusReport struct is responsible to keep the information about current status.

github/github.go

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ type RepositoryClient struct {
1616
organization string
1717
repo string
1818
client *github.Client
19-
curRelease *github.RepositoryRelease
2019
}
2120

2221
// NewGithubClient set up a github client.
@@ -49,60 +48,66 @@ func (gc *RepositoryClient) CreateDraftRelease(ctx context.Context, name, tagNam
4948
TargetCommitish: &targetBranch,
5049
}
5150

52-
githubRelease, _, err := gc.client.Repositories.CreateRelease(
51+
_, _, err := gc.client.Repositories.CreateRelease(
5352
ctx,
5453
gc.organization,
5554
gc.repo,
5655
githubRelease,
5756
)
5857

59-
gc.curRelease = githubRelease
60-
6158
return err
6259
}
6360

6461
// LastRelease fetches the latest release for a repository.
6562
func (gc *RepositoryClient) LastRelease(ctx context.Context) (*ergo.Release, error) {
6663
githubRelease, _, err := gc.client.Repositories.GetLatestRelease(
6764
ctx, gc.organization, gc.repo)
68-
69-
errResponse, ok := err.(*github.ErrorResponse)
70-
71-
errHasResponse := ok
72-
if errHasResponse && errResponse.Response.StatusCode == http.StatusNotFound {
73-
return nil, nil
74-
}
75-
7665
if err != nil {
66+
var errorResponse *github.ErrorResponse
67+
if errors.As(err, &errorResponse) && errorResponse.Response.StatusCode == http.StatusNotFound {
68+
return nil, fmt.Errorf("latest release not found: %w", errorResponse)
69+
}
7770
return nil, err
7871
}
7972

80-
gc.curRelease = githubRelease
81-
8273
return &ergo.Release{
8374
ID: *githubRelease.ID,
8475
Body: *githubRelease.Body,
8576
TagName: githubRelease.GetTagName(),
8677
ReleaseURL: githubRelease.GetHTMLURL(),
78+
Draft: githubRelease.GetDraft(),
8779
}, nil
8880
}
8981

9082
// EditRelease allows to edit a repository release.
9183
func (gc *RepositoryClient) EditRelease(ctx context.Context, release *ergo.Release) (*ergo.Release, error) {
92-
93-
if gc.curRelease == nil {
94-
return nil, errors.New("curRelease is empty")
84+
if release == nil {
85+
return nil, errors.New("nothing to release: input release is nil")
86+
}
87+
releasePayload := &github.RepositoryRelease{
88+
Body: &release.Body,
9589
}
9690

97-
githubRelease := gc.curRelease
98-
githubRelease.Body = &release.Body
91+
githubRelease, _, err := gc.client.Repositories.EditRelease(ctx, gc.organization, gc.repo, release.ID, releasePayload)
92+
if err != nil {
93+
return nil, err
94+
}
9995

100-
githubRelease, _, err := gc.client.Repositories.EditRelease(
101-
ctx, gc.organization, gc.repo, release.ID, githubRelease)
96+
release.Body = githubRelease.GetBody()
10297

103-
release.Body = *githubRelease.Body
98+
return release, nil
99+
}
104100

105-
return release, err
101+
// PublishRelease changes the draft flag of a repo release to false.
102+
func (gc *RepositoryClient) PublishRelease(ctx context.Context, releaseID int64) error {
103+
editReleasePayload := &github.RepositoryRelease{
104+
Draft: github.Bool(false),
105+
}
106+
_, _, err := gc.client.Repositories.EditRelease(ctx, gc.organization, gc.repo, releaseID, editReleasePayload)
107+
if err != nil {
108+
return fmt.Errorf("editing draft flag of release (ID=%d): %w", releaseID, err)
109+
}
110+
return nil
106111
}
107112

108113
// GetRef branch reference object given the branch name.

github/github_test.go

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package github
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"io"
68
"net/http"
79
"net/http/httptest"
810
"net/url"
@@ -29,6 +31,23 @@ func setup() (client *github.Client, mux *http.ServeMux, teardown func()) {
2931
return client, mux, server.Close
3032
}
3133

34+
func testBody(t *testing.T, r *http.Request, want string) {
35+
t.Helper()
36+
b, err := io.ReadAll(r.Body)
37+
if err != nil {
38+
t.Fatalf("Error reading request body: %v", err)
39+
}
40+
if got := string(b); got != want {
41+
t.Errorf("request body\nhave: %q\nwant: %q", got, want)
42+
}
43+
}
44+
45+
func testMethod(t *testing.T, r *http.Request, want string) {
46+
if want != r.Method {
47+
t.Errorf("Request method = %v, want %v", r.Method, want)
48+
}
49+
}
50+
3251
func TestNewGithubClient(t *testing.T) {
3352
ctx := context.Background()
3453
client := NewGithubClient(ctx, "access_token")
@@ -176,7 +195,7 @@ func TestLastReleaseShouldReturnTheLastRelease(t *testing.T) {
176195
}
177196
}
178197

179-
func TestLastReleaseShouldNotReturnErrorForInvalidStatusCode(t *testing.T) {
198+
func TestLastReleaseShouldReturnWrappedErrorForCodeStatusNotFound(t *testing.T) {
180199
ctx := context.Background()
181200
client, mux, tearDown := setup()
182201
defer tearDown()
@@ -187,12 +206,14 @@ func TestLastReleaseShouldNotReturnErrorForInvalidStatusCode(t *testing.T) {
187206

188207
repClient := NewRepositoryClient("o", "r", client)
189208

190-
got, err := repClient.LastRelease(ctx)
191-
if err != nil {
192-
t.Error("Should not return error for 404 status code")
209+
_, err := repClient.LastRelease(ctx)
210+
var errorResponse *github.ErrorResponse
211+
if !errors.As(err, &errorResponse) {
212+
t.Fatalf("LastRelease(GitHub API responds with 404) returned wrapped error of type %T, want: %T,", err, errorResponse)
193213
}
194-
if got != nil {
195-
t.Error("Release should be nil")
214+
if got, want := errorResponse.Response.StatusCode, http.StatusNotFound; got != want {
215+
t.Errorf("LastRelease(GitHub API responds with 404) returned wrapped error of type %T with StatusCode %d, want: %d",
216+
errorResponse, got, want)
196217
}
197218
}
198219

@@ -274,19 +295,75 @@ func TestEditReleaseShouldEditTheRelease(t *testing.T) {
274295
}
275296
}
276297

277-
func TestEditReleaseShouldReturnErrorForNilCurrentRelease(t *testing.T) {
298+
func TestEditReleaseShouldReturnErrorForNilInputRelease(t *testing.T) {
278299
ctx := context.Background()
279300
client, _, tearDown := setup()
280301
defer tearDown()
281302

282303
repClient := NewRepositoryClient("o", "r", client)
283304

284-
rel, err := repClient.EditRelease(ctx, nil)
305+
_, err := repClient.EditRelease(ctx, nil)
285306
if err == nil {
286-
t.Fatalf("Should return eror ")
307+
t.Fatalf("EditRelease should return error when input release is nil")
287308
}
288-
if rel != nil {
289-
t.Errorf("got = %v; want nil", rel)
309+
}
310+
311+
func TestEditReleaseShouldReturnErrorOnGitHubAPIError(t *testing.T) {
312+
ctx := context.Background()
313+
client, mux, tearDown := setup()
314+
defer tearDown()
315+
316+
mux.HandleFunc("/repos/o/r/releases/12", func(w http.ResponseWriter, r *http.Request) {
317+
w.WriteHeader(http.StatusServiceUnavailable)
318+
})
319+
320+
repClient := NewRepositoryClient("o", "r", client)
321+
322+
editRelease := &ergo.Release{
323+
ID: 12,
324+
Body: "release_body",
325+
TagName: "tag_name",
326+
ReleaseURL: "release_url",
327+
}
328+
_, err := repClient.EditRelease(ctx, editRelease)
329+
if err == nil {
330+
t.Errorf("EditRelease should return error when GitHub API sends code 503")
331+
}
332+
}
333+
334+
func TestPublishReleaseSuccess(t *testing.T) {
335+
client, mux, tearDown := setup()
336+
defer tearDown()
337+
338+
mux.HandleFunc("/repos/o/r/releases/12", func(w http.ResponseWriter, r *http.Request) {
339+
testMethod(t, r, "PATCH")
340+
testBody(t, r, `{"draft":false}`+"\n")
341+
fmt.Fprintf(w, `{}`)
342+
})
343+
344+
repoClient := NewRepositoryClient("o", "r", client)
345+
346+
ctx := context.Background()
347+
if err := repoClient.PublishRelease(ctx, 12); err != nil {
348+
t.Errorf("PublishRelease returned error: %v", err)
349+
}
350+
}
351+
352+
func TestPublishReleaseError(t *testing.T) {
353+
client, mux, tearDown := setup()
354+
defer tearDown()
355+
356+
mux.HandleFunc("/repos/o/r/releases/12", func(w http.ResponseWriter, r *http.Request) {
357+
testMethod(t, r, "PATCH")
358+
testBody(t, r, `{"draft":false}`+"\n")
359+
w.WriteHeader(http.StatusServiceUnavailable)
360+
})
361+
362+
repoClient := NewRepositoryClient("o", "r", client)
363+
364+
ctx := context.Background()
365+
if err := repoClient.PublishRelease(ctx, 12); err == nil {
366+
t.Error("PublishRelease expected to return error")
290367
}
291368
}
292369

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/mattn/go-isatty v0.0.7 // indirect
1414
github.com/mattn/go-runewidth v0.0.4 // indirect
1515
github.com/mitchellh/go-homedir v1.1.0
16-
github.com/pkg/errors v0.8.1
16+
github.com/pkg/errors v0.8.1 // indirect
1717
github.com/rodaine/table v1.0.0
1818
github.com/spf13/cobra v0.0.3
1919
github.com/spf13/viper v1.3.2

mock/cli.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,28 @@ import (
88

99
// CLI is a mock implementation.
1010
type CLI struct {
11-
MockConfirmation func() (bool, error)
11+
ConfirmationFn func() (bool, error)
1212

1313
mu sync.Mutex
1414
ConfirmationCalls int
1515
}
1616

1717
// PrintTable is a mock implementation.
18-
func (c *CLI) PrintTable(header []string, values [][]string) {
19-
}
18+
func (c *CLI) PrintTable(header []string, values [][]string) {}
2019

2120
// PrintColorizedLine is a mock implementation.
22-
func (c *CLI) PrintColorizedLine(title, content string, level ergo.MessageLevel) {
23-
}
21+
func (c *CLI) PrintColorizedLine(title, content string, level ergo.MessageLevel) {}
2422

2523
// PrintLine is a mock implementation.
26-
func (c *CLI) PrintLine(content ...interface{}) {
27-
}
24+
func (c *CLI) PrintLine(content ...interface{}) {}
2825

2926
// Confirmation is a mock implementation.
3027
func (c *CLI) Confirmation(actionText, cancellationMessage, successMessage string) (bool, error) {
3128
c.mu.Lock()
3229
c.ConfirmationCalls++
3330
c.mu.Unlock()
34-
if c.MockConfirmation != nil {
35-
return c.MockConfirmation()
31+
if c.ConfirmationFn != nil {
32+
return c.ConfirmationFn()
3633
}
3734
return true, nil
3835
}

0 commit comments

Comments
 (0)