1package db
2
3import (
4	"errors"
5	"time"
6
7	sq "github.com/Masterminds/squirrel"
8	"github.com/concourse/concourse/atc"
9)
10
11var ErrContainerDisappeared = errors.New("container disappeared from db")
12
13type ContainerState string
14
15//go:generate counterfeiter . Container
16
17type Container interface {
18	ID() int
19	State() string
20	Handle() string
21	WorkerName() string
22	Metadata() ContainerMetadata
23}
24
25//go:generate counterfeiter . CreatingContainer
26
27type CreatingContainer interface {
28	Container
29
30	Created() (CreatedContainer, error)
31	Failed() (FailedContainer, error)
32}
33
34type creatingContainer struct {
35	id         int
36	handle     string
37	workerName string
38	metadata   ContainerMetadata
39	conn       Conn
40}
41
42func newCreatingContainer(
43	id int,
44	handle string,
45	workerName string,
46	metadata ContainerMetadata,
47	conn Conn,
48) *creatingContainer {
49	return &creatingContainer{
50		id:         id,
51		handle:     handle,
52		workerName: workerName,
53		metadata:   metadata,
54		conn:       conn,
55	}
56}
57
58func (container *creatingContainer) ID() int                     { return container.id }
59func (container *creatingContainer) State() string               { return atc.ContainerStateCreating }
60func (container *creatingContainer) Handle() string              { return container.handle }
61func (container *creatingContainer) WorkerName() string          { return container.workerName }
62func (container *creatingContainer) Metadata() ContainerMetadata { return container.metadata }
63
64func (container *creatingContainer) Created() (CreatedContainer, error) {
65	rows, err := psql.Update("containers").
66		Set("state", atc.ContainerStateCreated).
67		Where(sq.And{
68			sq.Eq{"id": container.id},
69			sq.Or{
70				sq.Eq{"state": string(atc.ContainerStateCreating)},
71				sq.Eq{"state": string(atc.ContainerStateCreated)},
72			},
73		}).
74		RunWith(container.conn).
75		Exec()
76	if err != nil {
77		return nil, err
78	}
79
80	affected, err := rows.RowsAffected()
81	if err != nil {
82		return nil, err
83	}
84
85	if affected == 0 {
86		return nil, ErrContainerDisappeared
87	}
88
89	return newCreatedContainer(
90		container.id,
91		container.handle,
92		container.workerName,
93		container.metadata,
94		time.Time{},
95		container.conn,
96	), nil
97}
98
99func (container *creatingContainer) Failed() (FailedContainer, error) {
100	rows, err := psql.Update("containers").
101		Set("state", atc.ContainerStateFailed).
102		Where(sq.And{
103			sq.Eq{"id": container.id},
104			sq.Or{
105				sq.Eq{"state": string(atc.ContainerStateCreating)},
106				sq.Eq{"state": string(atc.ContainerStateFailed)},
107			},
108		}).
109		RunWith(container.conn).
110		Exec()
111	if err != nil {
112		return nil, err
113	}
114
115	affected, err := rows.RowsAffected()
116	if err != nil {
117		return nil, err
118	}
119
120	if affected == 0 {
121		return nil, ErrContainerDisappeared
122	}
123
124	return newFailedContainer(
125		container.id,
126		container.handle,
127		container.workerName,
128		container.metadata,
129		container.conn,
130	), nil
131}
132
133//go:generate counterfeiter . CreatedContainer
134
135type CreatedContainer interface {
136	Container
137
138	Destroying() (DestroyingContainer, error)
139	LastHijack() time.Time
140	UpdateLastHijack() error
141}
142
143type createdContainer struct {
144	id         int
145	handle     string
146	workerName string
147	metadata   ContainerMetadata
148
149	lastHijack time.Time
150
151	conn Conn
152}
153
154func newCreatedContainer(
155	id int,
156	handle string,
157	workerName string,
158	metadata ContainerMetadata,
159	lastHijack time.Time,
160	conn Conn,
161) *createdContainer {
162	return &createdContainer{
163		id:         id,
164		handle:     handle,
165		workerName: workerName,
166		metadata:   metadata,
167		lastHijack: lastHijack,
168		conn:       conn,
169	}
170}
171
172func (container *createdContainer) ID() int                     { return container.id }
173func (container *createdContainer) State() string               { return atc.ContainerStateCreated }
174func (container *createdContainer) Handle() string              { return container.handle }
175func (container *createdContainer) WorkerName() string          { return container.workerName }
176func (container *createdContainer) Metadata() ContainerMetadata { return container.metadata }
177
178func (container *createdContainer) LastHijack() time.Time { return container.lastHijack }
179
180func (container *createdContainer) Destroying() (DestroyingContainer, error) {
181
182	rows, err := psql.Update("containers").
183		Set("state", atc.ContainerStateDestroying).
184		Where(sq.And{
185			sq.Eq{"id": container.id},
186			sq.Or{
187				sq.Eq{"state": string(atc.ContainerStateDestroying)},
188				sq.Eq{"state": string(atc.ContainerStateCreated)},
189			},
190		}).
191		RunWith(container.conn).
192		Exec()
193	if err != nil {
194		return nil, err
195	}
196
197	affected, err := rows.RowsAffected()
198	if err != nil {
199		return nil, err
200	}
201
202	if affected == 0 {
203		return nil, ErrContainerDisappeared
204	}
205
206	return newDestroyingContainer(
207		container.id,
208		container.handle,
209		container.workerName,
210		container.metadata,
211		container.conn,
212	), nil
213}
214
215func (container *createdContainer) UpdateLastHijack() error {
216
217	rows, err := psql.Update("containers").
218		Set("last_hijack", sq.Expr("now()")).
219		Where(sq.Eq{
220			"id":    container.id,
221			"state": atc.ContainerStateCreated,
222		}).
223		RunWith(container.conn).
224		Exec()
225	if err != nil {
226		return err
227	}
228
229	affected, err := rows.RowsAffected()
230	if err != nil {
231		return err
232	}
233
234	if affected == 0 {
235		return ErrContainerDisappeared
236	}
237
238	return nil
239}
240
241//go:generate counterfeiter . DestroyingContainer
242
243type DestroyingContainer interface {
244	Container
245
246	Destroy() (bool, error)
247}
248
249type destroyingContainer struct {
250	id         int
251	handle     string
252	workerName string
253	metadata   ContainerMetadata
254
255	conn Conn
256}
257
258func newDestroyingContainer(
259	id int,
260	handle string,
261	workerName string,
262	metadata ContainerMetadata,
263	conn Conn,
264) *destroyingContainer {
265	return &destroyingContainer{
266		id:         id,
267		handle:     handle,
268		workerName: workerName,
269		metadata:   metadata,
270		conn:       conn,
271	}
272}
273
274func (container *destroyingContainer) ID() int                     { return container.id }
275func (container *destroyingContainer) State() string               { return atc.ContainerStateDestroying }
276func (container *destroyingContainer) Handle() string              { return container.handle }
277func (container *destroyingContainer) WorkerName() string          { return container.workerName }
278func (container *destroyingContainer) Metadata() ContainerMetadata { return container.metadata }
279
280func (container *destroyingContainer) Destroy() (bool, error) {
281	rows, err := psql.Delete("containers").
282		Where(sq.Eq{
283			"id":    container.id,
284			"state": atc.ContainerStateDestroying,
285		}).
286		RunWith(container.conn).
287		Exec()
288	if err != nil {
289		return false, err
290	}
291
292	affected, err := rows.RowsAffected()
293	if err != nil {
294		return false, err
295	}
296
297	if affected == 0 {
298		return false, ErrContainerDisappeared
299	}
300
301	return true, nil
302}
303
304//go:generate counterfeiter . FailedContainer
305
306type FailedContainer interface {
307	Container
308
309	Destroy() (bool, error)
310}
311
312type failedContainer struct {
313	id         int
314	handle     string
315	workerName string
316	metadata   ContainerMetadata
317	conn       Conn
318}
319
320func newFailedContainer(
321	id int,
322	handle string,
323	workerName string,
324	metadata ContainerMetadata,
325	conn Conn,
326) *failedContainer {
327	return &failedContainer{
328		id:         id,
329		handle:     handle,
330		workerName: workerName,
331		metadata:   metadata,
332		conn:       conn,
333	}
334}
335
336func (container *failedContainer) ID() int                     { return container.id }
337func (container *failedContainer) State() string               { return atc.ContainerStateFailed }
338func (container *failedContainer) Handle() string              { return container.handle }
339func (container *failedContainer) WorkerName() string          { return container.workerName }
340func (container *failedContainer) Metadata() ContainerMetadata { return container.metadata }
341
342func (container *failedContainer) Destroy() (bool, error) {
343	rows, err := psql.Delete("containers").
344		Where(sq.Eq{
345			"id":    container.id,
346			"state": atc.ContainerStateFailed,
347		}).
348		RunWith(container.conn).
349		Exec()
350	if err != nil {
351		return false, err
352	}
353
354	affected, err := rows.RowsAffected()
355	if err != nil {
356		return false, err
357	}
358
359	if affected == 0 {
360		return false, ErrContainerDisappeared
361	}
362
363	return true, nil
364}
365