-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.go
252 lines (210 loc) · 5.01 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package main
import (
"context"
"flag"
"log"
"os"
"os/signal"
"sort"
"time"
codeship "github.com/codeship/codeship-go"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
type allocatedAtSort []codeship.Build
func (s allocatedAtSort) Len() int {
return len(s)
}
func (s allocatedAtSort) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s allocatedAtSort) Less(i, j int) bool {
return s[i].AllocatedAt.Before(s[j].AllocatedAt)
}
func main() {
log.SetFlags(0)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
err := viper.BindPFlags(pflag.CommandLine)
if err != nil {
log.Fatal(err)
}
viper.SetEnvPrefix("codeship")
// CODESHIP_USERNAME
err = viper.BindEnv("username")
if err != nil {
log.Fatal(err)
}
// CODESHIP_PASSWORD
err = viper.BindEnv("password")
if err != nil {
log.Fatal(err)
}
// CODESHIP_ORGANIZATION
err = viper.BindEnv("organization")
if err != nil {
log.Fatal(err)
}
// CI_PROJECT_ID
err = viper.BindEnv("project_id", "CI_PROJECT_ID")
if err != nil {
log.Fatal(err)
}
// CI_BUILD_ID
err = viper.BindEnv("build_id", "CI_BUILD_ID")
if err != nil {
log.Fatal(err)
}
user := viper.GetString("username")
if user == "" {
log.Fatal("CODESHIP_USERNAME required")
}
password := viper.GetString("password")
if password == "" {
log.Fatal("CODESHIP_PASSWORD required")
}
orgName := viper.GetString("organization")
if orgName == "" {
log.Fatal("CODESHIP_ORGANIZATION required")
}
projectUUID := viper.GetString("project_id")
if projectUUID == "" {
log.Fatal("CI_PROJECT_ID required")
}
buildUUID := viper.GetString("build_id")
if buildUUID == "" {
log.Fatal("CI_BUILD_ID required")
}
ctx := context.Background()
// trap Ctrl+C and call cancel on the context
ctx, cancel := context.WithCancel(ctx)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
defer func() {
signal.Stop(c)
close(c)
cancel()
}()
go func() {
<-c
cancel()
}()
auth := codeship.NewBasicAuth(user, password)
client, err := codeship.New(auth)
if err != nil {
log.Fatal(err)
}
org, err := client.Organization(ctx, orgName)
if err != nil {
log.Fatal(err)
}
build, _, err := org.GetBuild(ctx, projectUUID, buildUUID)
if err != nil {
log.Fatal(err)
}
m := monitor{
buildGetter: org,
}
err = m.waitOnPreviousBuilds(ctx, projectUUID, buildUUID, build.Branch)
if err != nil {
log.Fatal(err)
}
}
type buildGetter interface {
ListBuilds(ctx context.Context, projectUUID string, opts ...codeship.PaginationOption) (codeship.BuildList, codeship.Response, error)
GetBuild(context.Context, string, string) (codeship.Build, codeship.Response, error)
}
type monitor struct {
buildGetter
}
func (m monitor) waitOnPreviousBuilds(ctx context.Context, projectUUID, buildUUID, branch string) error {
// Find a list all builds running for the branch
watching, err := m.buildsToWatch(ctx, projectUUID, branch)
if err != nil {
return err
}
// Sort builds by oldest allocated time
sort.Sort(allocatedAtSort(watching))
// Loop through list of builds on branch.
// Check every 30 seconds to see if build has completed
// exit out of loop when we reach out build
ticker := time.NewTicker(30 * time.Second)
for _, b := range watching {
if b.UUID == buildUUID {
// It is our turn to run --exit
log.Println("Resuming build")
break
} else {
// wait for the build ahead of us to finish
finished, err := m.buildFinished(ctx, b)
if err != nil {
return err
}
if finished {
continue
} else {
log.Println("Waiting on build", b.UUID)
}
BuildWait:
for {
select {
case <-ctx.Done():
return nil // user has hit ctrl+c
case <-ticker.C:
finished, err := m.buildFinished(ctx, b)
if err != nil {
return err
}
if finished {
break BuildWait
} else {
log.Println("Waiting on build", b.UUID)
}
}
}
}
}
return nil
}
func (m monitor) buildFinished(ctx context.Context, b codeship.Build) (bool, error) {
build, _, err := m.GetBuild(ctx, b.ProjectUUID, b.UUID)
if err != nil {
return false, err
}
// a build is considered finished if it is not testing
return (build.Status != "testing"), nil
}
func (m monitor) buildsToWatch(ctx context.Context, projectUUID, branch string) ([]codeship.Build, error) {
var (
pageWithRunningBuild bool
watching []codeship.Build
)
builds, resp, err := m.ListBuilds(ctx, projectUUID)
if err != nil {
return nil, err
}
// loop through builds until we get to a page without any running builds or we reach the last page
for {
pageWithRunningBuild = false
for _, b := range builds.Builds {
if b.Status == "testing" {
pageWithRunningBuild = true
if b.Branch == branch {
watching = append(watching, b)
}
}
}
if resp.IsLastPage() || resp.Next == "" {
break
}
if !pageWithRunningBuild {
break
}
next, _ := resp.NextPage()
builds, resp, err = m.ListBuilds(ctx, projectUUID, codeship.Page(next), codeship.PerPage(50))
if err != nil {
return nil, err
}
}
return watching, nil
}