Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c36b984ee4 | ||
|
|
f14fe02011 |
+5
-4
@@ -274,11 +274,12 @@ EXPOSE 8080
|
|||||||
# port.
|
# port.
|
||||||
ENV BIND_ADDRESS=0.0.0.0
|
ENV BIND_ADDRESS=0.0.0.0
|
||||||
|
|
||||||
# The address is 127.0.0.1 because wget tries only the first address
|
# The probe requests the container's own address, not loopback, so a
|
||||||
# localhost resolves to. That can be ::1, which a 0.0.0.0 bind does
|
# listener that answers only on loopback (an empty or loopback
|
||||||
# not answer.
|
# BIND_ADDRESS) fails it. hostname -i prints one IPv4 address per
|
||||||
|
# network the container is on; the first is enough.
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/.well-known/healthcheck || exit 1
|
CMD wget --no-verbose --tries=1 --spider "http://$(hostname -i | cut -d ' ' -f 1):8080/.well-known/healthcheck" || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
CMD ["/app/webhooker"]
|
CMD ["/app/webhooker"]
|
||||||
|
|||||||
@@ -667,10 +667,10 @@ Either set a literal or leave the variable out entirely.
|
|||||||
|
|
||||||
Overriding `BIND_ADDRESS` to a loopback address in a container — by that route
|
Overriding `BIND_ADDRESS` to a loopback address in a container — by that route
|
||||||
or deliberately — makes the container unreachable from outside its namespace
|
or deliberately — makes the container unreachable from outside its namespace
|
||||||
even with `-p`. The published port answers nothing. The health check requests
|
even with `-p`. The published port answers nothing, and the health check fails
|
||||||
`http://127.0.0.1:8080` from inside the container, so a `127.0.0.1` bind passes
|
too: it requests the container's own address (the first one `hostname -i`
|
||||||
it and the container stays `healthy`. A `::1` bind fails it, and the container
|
prints), where a loopback bind is not listening. The container then goes
|
||||||
goes `unhealthy` about **65 seconds** after start — from
|
`unhealthy` about **65 seconds** after start — from
|
||||||
`HEALTHCHECK --start-period=5s --interval=30s --retries=3`, so failing probes at
|
`HEALTHCHECK --start-period=5s --interval=30s --retries=3`, so failing probes at
|
||||||
5s, 35s and 65s, and `unhealthy` on the third. (Docker's probe cadence during
|
5s, 35s and 65s, and `unhealthy` on the third. (Docker's probe cadence during
|
||||||
the start period has changed between versions; re-derive from those three values
|
the start period has changed between versions; re-derive from those three values
|
||||||
@@ -726,8 +726,8 @@ the credentials.
|
|||||||
address (directly, or through anything that can rewrite source addresses,
|
address (directly, or through anything that can rewrite source addresses,
|
||||||
such as NAT or a published container port). The `remoteIP` field of the
|
such as NAT or a published container port). The `remoteIP` field of the
|
||||||
`http request` log line for a request that came through the proxy shows
|
`http request` log line for a request that came through the proxy shows
|
||||||
that address; the health check's own lines show `127.0.0.1`. See
|
that address; the health check's own lines show the container's own
|
||||||
[Trusted proxies](#trusted-proxies).
|
address. See [Trusted proxies](#trusted-proxies).
|
||||||
- Leave `BIND_ADDRESS` and `DATA_DIR` unset: the image sets `BIND_ADDRESS`
|
- Leave `BIND_ADDRESS` and `DATA_DIR` unset: the image sets `BIND_ADDRESS`
|
||||||
to `0.0.0.0`, and `DATA_DIR` defaults to `/var/lib/webhooker`.
|
to `0.0.0.0`, and `DATA_DIR` defaults to `/var/lib/webhooker`.
|
||||||
- Everything else is optional; see [Configuration](#configuration).
|
- Everything else is optional; see [Configuration](#configuration).
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ func targetTotals(
|
|||||||
|
|
||||||
// TestUpdateDeliveryStatus_FinishTimeAndTargetTotals pins what a status
|
// TestUpdateDeliveryStatus_FinishTimeAndTargetTotals pins what a status
|
||||||
// write records for the webhook page's statistics: the time a delivery
|
// write records for the webhook page's statistics: the time a delivery
|
||||||
// finished, set only when it becomes delivered or failed, and one more
|
// finished, set only when it becomes delivered or failed and stored in
|
||||||
// on its target's delivered or failed total.
|
// the same form as created_at, and one more on its target's delivered
|
||||||
|
// or failed total.
|
||||||
func TestUpdateDeliveryStatus_FinishTimeAndTargetTotals(t *testing.T) {
|
func TestUpdateDeliveryStatus_FinishTimeAndTargetTotals(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -75,6 +76,16 @@ func TestUpdateDeliveryStatus_FinishTimeAndTargetTotals(t *testing.T) {
|
|||||||
if tt.finished {
|
if tt.finished {
|
||||||
require.NotNil(t, stored.FinishedAt)
|
require.NotNil(t, stored.FinishedAt)
|
||||||
assert.False(t, stored.FinishedAt.Before(before))
|
assert.False(t, stored.FinishedAt.Before(before))
|
||||||
|
|
||||||
|
var createdAt, finishedAt string
|
||||||
|
|
||||||
|
require.NoError(t, db.Raw(
|
||||||
|
`SELECT CAST(created_at AS TEXT),
|
||||||
|
CAST(finished_at AS TEXT)
|
||||||
|
FROM deliveries WHERE id = ?`, d.ID,
|
||||||
|
).Row().Scan(&createdAt, &finishedAt))
|
||||||
|
assertStoredTime(t, createdAt)
|
||||||
|
assertStoredTime(t, finishedAt)
|
||||||
} else {
|
} else {
|
||||||
assert.Nil(t, stored.FinishedAt)
|
assert.Nil(t, stored.FinishedAt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1701,8 +1701,11 @@ func writeDeliveryStatus(
|
|||||||
database.DeliveryStatusFailed,
|
database.DeliveryStatusFailed,
|
||||||
}).
|
}).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"status": status,
|
"status": status,
|
||||||
"finished_at": time.Now(),
|
// GORM's clock, the one that stamps created_at. A bare
|
||||||
|
// time.Now() carries a monotonic clock reading, which the
|
||||||
|
// driver would store as an " m=+…" suffix.
|
||||||
|
"finished_at": tx.NowFunc(),
|
||||||
})
|
})
|
||||||
if res.Error != nil || res.RowsAffected == 0 {
|
if res.Error != nil || res.RowsAffected == 0 {
|
||||||
return res.Error
|
return res.Error
|
||||||
@@ -1906,12 +1909,13 @@ func (e *Engine) takeForRedispatch(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GORM's clock, as writeDeliveryStatus explains.
|
||||||
res := webhookDB.
|
res := webhookDB.
|
||||||
Model(&database.Delivery{}).
|
Model(&database.Delivery{}).
|
||||||
Where(
|
Where(
|
||||||
"id = ? AND status = ?", deliveryID, eligible,
|
"id = ? AND status = ?", deliveryID, eligible,
|
||||||
).
|
).
|
||||||
UpdateColumn("updated_at", time.Now())
|
UpdateColumn("updated_at", webhookDB.NowFunc())
|
||||||
|
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
e.log.Error(
|
e.log.Error(
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ func seedDelivery(
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assertStoredTime asserts that text, a timestamp as SQLite holds it,
|
||||||
|
// is in the form GORM stores created_at in: the layout of
|
||||||
|
// time.Time.String, with no " m=+…" monotonic clock reading after it.
|
||||||
|
func assertStoredTime(t *testing.T, text string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, err := time.Parse(
|
||||||
|
"2006-01-02 15:04:05.999999999 -0700 MST", text,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err, "stored as %q", text)
|
||||||
|
}
|
||||||
|
|
||||||
// httpDeliveryFixture holds the task and delivery needed
|
// httpDeliveryFixture holds the task and delivery needed
|
||||||
// for HTTP delivery tests.
|
// for HTTP delivery tests.
|
||||||
type httpDeliveryFixture struct {
|
type httpDeliveryFixture struct {
|
||||||
|
|||||||
@@ -462,7 +462,8 @@ func TestScheduledRetryIsNotSweptDuringBackoff(t *testing.T) {
|
|||||||
|
|
||||||
// TestRedispatchStampsTheRow pins the cadence control: a stranded
|
// TestRedispatchStampsTheRow pins the cadence control: a stranded
|
||||||
// delivery that has just been handed out is not selected again by the
|
// delivery that has just been handed out is not selected again by the
|
||||||
// next tick a minute later.
|
// next tick a minute later. The stamp is stored in the same form as
|
||||||
|
// created_at.
|
||||||
func TestRedispatchStampsTheRow(t *testing.T) {
|
func TestRedispatchStampsTheRow(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -493,4 +494,13 @@ func TestRedispatchStampsTheRow(t *testing.T) {
|
|||||||
"a re-dispatched delivery must be stamped so the "+
|
"a re-dispatched delivery must be stamped so the "+
|
||||||
"next tick does not select it again",
|
"next tick does not select it again",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var createdAt, updatedAt string
|
||||||
|
|
||||||
|
require.NoError(t, s.WebhookDB.Raw(
|
||||||
|
`SELECT CAST(created_at AS TEXT), CAST(updated_at AS TEXT)
|
||||||
|
FROM deliveries WHERE id = ?`, d.ID,
|
||||||
|
).Row().Scan(&createdAt, &updatedAt))
|
||||||
|
assertStoredTime(t, createdAt)
|
||||||
|
assertStoredTime(t, updatedAt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,11 @@ func (w *archiveWriter) write(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
row.ArchivedAt = time.Now()
|
// GORM's clock, the one that stamps created_at in the other
|
||||||
|
// databases, so this is stored in the same form. A bare time.Now()
|
||||||
|
// carries a monotonic clock reading, which the driver would store
|
||||||
|
// as an " m=+…" suffix.
|
||||||
|
row.ArchivedAt = w.db.NowFunc()
|
||||||
|
|
||||||
err := w.db.Create(&row).Error
|
err := w.db.Create(&row).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -150,6 +150,13 @@ func TestArchiveWriter_WritesRow(t *testing.T) {
|
|||||||
assert.Equal(t, row.ContentType, got[0].ContentType)
|
assert.Equal(t, row.ContentType, got[0].ContentType)
|
||||||
assert.JSONEq(t, `{"hello":"world"}`, got[0].Body)
|
assert.JSONEq(t, `{"hello":"world"}`, got[0].Body)
|
||||||
assert.False(t, got[0].ArchivedAt.IsZero())
|
assert.False(t, got[0].ArchivedAt.IsZero())
|
||||||
|
|
||||||
|
var archivedAt string
|
||||||
|
|
||||||
|
require.NoError(t, w.DB().Raw(
|
||||||
|
"SELECT CAST(archived_at AS TEXT) FROM archived_events",
|
||||||
|
).Row().Scan(&archivedAt))
|
||||||
|
assertStoredTime(t, archivedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArchiveWriter_RecreatesAfterRemoval(
|
func TestArchiveWriter_RecreatesAfterRemoval(
|
||||||
|
|||||||
Reference in New Issue
Block a user