mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
Bumps the github-actions group with 4 updates: [crate-ci/typos](https://github.com/crate-ci/typos), [google-github-actions/auth](https://github.com/google-github-actions/auth), [google-github-actions/setup-gcloud](https://github.com/google-github-actions/setup-gcloud) and [google-github-actions/get-gke-credentials](https://github.com/google-github-actions/get-gke-credentials). Updates `crate-ci/typos` from 1.26.8 to 1.27.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/crate-ci/typos/releases">crate-ci/typos's releases</a>.</em></p> <blockquote> <h2>v1.27.0</h2> <h2>[1.27.0] - 2024-11-01</h2> <h3>Features</h3> <ul> <li>Updated the dictionary with the <a href="https://redirect.github.com/crate-ci/typos/issues/1106">October 2024</a> changes</li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/crate-ci/typos/blob/master/CHANGELOG.md">crate-ci/typos's changelog</a>.</em></p> <blockquote> <h1>Change Log</h1> <p>All notable changes to this project will be documented in this file.</p> <p>The format is based on <a href="http://keepachangelog.com/">Keep a Changelog</a> and this project adheres to <a href="http://semver.org/">Semantic Versioning</a>.</p> <!-- raw HTML omitted --> <h2>[Unreleased] - ReleaseDate</h2> <h2>[1.27.0] - 2024-11-01</h2> <h3>Features</h3> <ul> <li>Updated the dictionary with the <a href="https://redirect.github.com/crate-ci/typos/issues/1106">October 2024</a> changes</li> </ul> <h2>[1.26.8] - 2024-10-24</h2> <h2>[1.26.7] - 2024-10-24</h2> <h2>[1.26.6] - 2024-10-24</h2> <h2>[1.26.5] - 2024-10-24</h2> <h2>[1.26.4] - 2024-10-24</h2> <h2>[1.26.3] - 2024-10-24</h2> <h3>Fixes</h3> <ul> <li>Accept <code>additionals</code></li> </ul> <h2>[1.26.2] - 2024-10-24</h2> <h3>Fixes</h3> <ul> <li>Accept <code>tesselate</code> variants</li> </ul> <h2>[1.26.1] - 2024-10-23</h2> <h3>Fixes</h3> <ul> <li>Respect <code>--force-exclude</code> for binary files</li> </ul> <h2>[1.26.0] - 2024-10-07</h2> <h3>Compatibility</h3> <ul> <li><em>(pre-commit)</em> Requires 3.2+</li> </ul> <h3>Fixes</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="d01f29c66d
"><code>d01f29c</code></a> chore: Release</li> <li><a href="52e950bb13
"><code>52e950b</code></a> chore: Release</li> <li><a href="19cfc03ea4
"><code>19cfc03</code></a> docs: Update changelog</li> <li><a href="f80b1564bd
"><code>f80b156</code></a> Merge pull request <a href="https://redirect.github.com/crate-ci/typos/issues/1140">#1140</a> from epage/oct</li> <li><a href="6b5c8079a9
"><code>6b5c807</code></a> feat(dict): Oct updates</li> <li><a href="d64f202a88
"><code>d64f202</code></a> chore(deps): Update compatible (<a href="https://redirect.github.com/crate-ci/typos/issues/1137">#1137</a>)</li> <li><a href="e903c46287
"><code>e903c46</code></a> Merge pull request <a href="https://redirect.github.com/crate-ci/typos/issues/1136">#1136</a> from PigeonF/PigeonF/push-mlqnlvmswwmp</li> <li><a href="b994765ef9
"><code>b994765</code></a> chore: Fix typo "potemtial" -> "potential"</li> <li>See full diff in <a href="0d9e0c2c1b...d01f29c66d
">compare view</a></li> </ul> </details> <br /> Updates `google-github-actions/auth` from 2.1.6 to 2.1.7 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/google-github-actions/auth/releases">google-github-actions/auth's releases</a>.</em></p> <blockquote> <h2>v2.1.7</h2> <h2>What's Changed</h2> <ul> <li>fix: update relase workflows by <a href="https://github.com/verbanicm"><code>@verbanicm</code></a> in <a href="https://redirect.github.com/google-github-actions/auth/pull/452">google-github-actions/auth#452</a></li> <li>Release: v2.1.7 by <a href="https://github.com/google-github-actions-bot"><code>@google-github-actions-bot</code></a> in <a href="https://redirect.github.com/google-github-actions/auth/pull/453">google-github-actions/auth#453</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/google-github-actions/auth/compare/v2.1.6...v2.1.7">https://github.com/google-github-actions/auth/compare/v2.1.6...v2.1.7</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="6fc4af4b14
"><code>6fc4af4</code></a> Release: v2.1.7 (<a href="https://redirect.github.com/google-github-actions/auth/issues/453">#453</a>)</li> <li><a href="212f83afe8
"><code>212f83a</code></a> fix: update relase workflows (<a href="https://redirect.github.com/google-github-actions/auth/issues/452">#452</a>)</li> <li>See full diff in <a href="8254fb75a3...6fc4af4b14
">compare view</a></li> </ul> </details> <br /> Updates `google-github-actions/setup-gcloud` from 2.1.1 to 2.1.2 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/google-github-actions/setup-gcloud/releases">google-github-actions/setup-gcloud's releases</a>.</em></p> <blockquote> <h2>v2.1.2</h2> <h2>What's Changed</h2> <ul> <li>fix: update release workflows by <a href="https://github.com/verbanicm"><code>@verbanicm</code></a> in <a href="https://redirect.github.com/google-github-actions/setup-gcloud/pull/698">google-github-actions/setup-gcloud#698</a></li> <li>Release: v2.1.2 by <a href="https://github.com/google-github-actions-bot"><code>@google-github-actions-bot</code></a> in <a href="https://redirect.github.com/google-github-actions/setup-gcloud/pull/699">google-github-actions/setup-gcloud#699</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/google-github-actions/setup-gcloud/compare/v2.1.1...v2.1.2">https://github.com/google-github-actions/setup-gcloud/compare/v2.1.1...v2.1.2</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="6189d56e40
"><code>6189d56</code></a> Release: v2.1.2 (<a href="https://redirect.github.com/google-github-actions/setup-gcloud/issues/699">#699</a>)</li> <li><a href="413dc083dd
"><code>413dc08</code></a> fix: update release workflows (<a href="https://redirect.github.com/google-github-actions/setup-gcloud/issues/698">#698</a>)</li> <li>See full diff in <a href="f0990588f1...6189d56e40
">compare view</a></li> </ul> </details> <br /> Updates `google-github-actions/get-gke-credentials` from 2.2.1 to 2.2.2 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/google-github-actions/get-gke-credentials/releases">google-github-actions/get-gke-credentials's releases</a>.</em></p> <blockquote> <h2>v2.2.2</h2> <h2>What's Changed</h2> <ul> <li>Fix package name by <a href="https://github.com/sethvargo"><code>@sethvargo</code></a> in <a href="https://redirect.github.com/google-github-actions/get-gke-credentials/pull/312">google-github-actions/get-gke-credentials#312</a></li> <li>fix: update release workflows by <a href="https://github.com/verbanicm"><code>@verbanicm</code></a> in <a href="https://redirect.github.com/google-github-actions/get-gke-credentials/pull/313">google-github-actions/get-gke-credentials#313</a></li> <li>Release: v2.2.2 by <a href="https://github.com/google-github-actions-bot"><code>@google-github-actions-bot</code></a> in <a href="https://redirect.github.com/google-github-actions/get-gke-credentials/pull/315">google-github-actions/get-gke-credentials#315</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/google-github-actions/get-gke-credentials/compare/v2.2.1...v2.2.2">https://github.com/google-github-actions/get-gke-credentials/compare/v2.2.1...v2.2.2</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="206d64b64b
"><code>206d64b</code></a> Release: v2.2.2 (<a href="https://redirect.github.com/google-github-actions/get-gke-credentials/issues/315">#315</a>)</li> <li><a href="0fead37d80
"><code>0fead37</code></a> fix: update release workflows (<a href="https://redirect.github.com/google-github-actions/get-gke-credentials/issues/313">#313</a>)</li> <li><a href="d7d8311fd5
"><code>d7d8311</code></a> Fix package name (<a href="https://redirect.github.com/google-github-actions/get-gke-credentials/issues/312">#312</a>)</li> <li>See full diff in <a href="6051de21ad...206d64b64b
">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Muhammad Atif Ali <me@matifali.dev>
374 lines
11 KiB
Go
374 lines
11 KiB
Go
package unhanger
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand" //#nosec // this is only used for shuffling an array to pick random jobs to unhang
|
|
"time"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"cdr.dev/slog"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/coderd/database/pubsub"
|
|
"github.com/coder/coder/v2/provisionersdk"
|
|
)
|
|
|
|
const (
|
|
// HungJobDuration is the duration of time since the last update to a job
|
|
// before it is considered hung.
|
|
HungJobDuration = 5 * time.Minute
|
|
|
|
// HungJobExitTimeout is the duration of time that provisioners should allow
|
|
// for a graceful exit upon cancellation due to failing to send an update to
|
|
// a job.
|
|
//
|
|
// Provisioners should avoid keeping a job "running" for longer than this
|
|
// time after failing to send an update to the job.
|
|
HungJobExitTimeout = 3 * time.Minute
|
|
|
|
// MaxJobsPerRun is the maximum number of hung jobs that the detector will
|
|
// terminate in a single run.
|
|
MaxJobsPerRun = 10
|
|
)
|
|
|
|
// HungJobLogMessages are written to provisioner job logs when a job is hung and
|
|
// terminated.
|
|
var HungJobLogMessages = []string{
|
|
"",
|
|
"====================",
|
|
"Coder: Build has been detected as hung for 5 minutes and will be terminated.",
|
|
"====================",
|
|
"",
|
|
}
|
|
|
|
// acquireLockError is returned when the detector fails to acquire a lock and
|
|
// cancels the current run.
|
|
type acquireLockError struct{}
|
|
|
|
// Error implements error.
|
|
func (acquireLockError) Error() string {
|
|
return "lock is held by another client"
|
|
}
|
|
|
|
// jobIneligibleError is returned when a job is not eligible to be terminated
|
|
// anymore.
|
|
type jobIneligibleError struct {
|
|
Err error
|
|
}
|
|
|
|
// Error implements error.
|
|
func (e jobIneligibleError) Error() string {
|
|
return fmt.Sprintf("job is no longer eligible to be terminated: %s", e.Err)
|
|
}
|
|
|
|
// Detector automatically detects hung provisioner jobs, sends messages into the
|
|
// build log and terminates them as failed.
|
|
type Detector struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
done chan struct{}
|
|
|
|
db database.Store
|
|
pubsub pubsub.Pubsub
|
|
log slog.Logger
|
|
tick <-chan time.Time
|
|
stats chan<- Stats
|
|
}
|
|
|
|
// Stats contains statistics about the last run of the detector.
|
|
type Stats struct {
|
|
// TerminatedJobIDs contains the IDs of all jobs that were detected as hung and
|
|
// terminated.
|
|
TerminatedJobIDs []uuid.UUID
|
|
// Error is the fatal error that occurred during the last run of the
|
|
// detector, if any. Error may be set to AcquireLockError if the detector
|
|
// failed to acquire a lock.
|
|
Error error
|
|
}
|
|
|
|
// New returns a new hang detector.
|
|
func New(ctx context.Context, db database.Store, pub pubsub.Pubsub, log slog.Logger, tick <-chan time.Time) *Detector {
|
|
//nolint:gocritic // Hang detector has a limited set of permissions.
|
|
ctx, cancel := context.WithCancel(dbauthz.AsHangDetector(ctx))
|
|
d := &Detector{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
done: make(chan struct{}),
|
|
db: db,
|
|
pubsub: pub,
|
|
log: log,
|
|
tick: tick,
|
|
stats: nil,
|
|
}
|
|
return d
|
|
}
|
|
|
|
// WithStatsChannel will cause Executor to push a RunStats to ch after
|
|
// every tick. This push is blocking, so if ch is not read, the detector will
|
|
// hang. This should only be used in tests.
|
|
func (d *Detector) WithStatsChannel(ch chan<- Stats) *Detector {
|
|
d.stats = ch
|
|
return d
|
|
}
|
|
|
|
// Start will cause the detector to detect and unhang provisioner jobs on every
|
|
// tick from its channel. It will stop when its context is Done, or when its
|
|
// channel is closed.
|
|
//
|
|
// Start should only be called once.
|
|
func (d *Detector) Start() {
|
|
go func() {
|
|
defer close(d.done)
|
|
defer d.cancel()
|
|
|
|
for {
|
|
select {
|
|
case <-d.ctx.Done():
|
|
return
|
|
case t, ok := <-d.tick:
|
|
if !ok {
|
|
return
|
|
}
|
|
stats := d.run(t)
|
|
if stats.Error != nil && !xerrors.As(stats.Error, &acquireLockError{}) {
|
|
d.log.Warn(d.ctx, "error running workspace build hang detector once", slog.Error(stats.Error))
|
|
}
|
|
if d.stats != nil {
|
|
select {
|
|
case <-d.ctx.Done():
|
|
return
|
|
case d.stats <- stats:
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Wait will block until the detector is stopped.
|
|
func (d *Detector) Wait() {
|
|
<-d.done
|
|
}
|
|
|
|
// Close will stop the detector.
|
|
func (d *Detector) Close() {
|
|
d.cancel()
|
|
<-d.done
|
|
}
|
|
|
|
func (d *Detector) run(t time.Time) Stats {
|
|
ctx, cancel := context.WithTimeout(d.ctx, 5*time.Minute)
|
|
defer cancel()
|
|
|
|
stats := Stats{
|
|
TerminatedJobIDs: []uuid.UUID{},
|
|
Error: nil,
|
|
}
|
|
|
|
// Find all provisioner jobs that are currently running but have not
|
|
// received an update in the last 5 minutes.
|
|
jobs, err := d.db.GetHungProvisionerJobs(ctx, t.Add(-HungJobDuration))
|
|
if err != nil {
|
|
stats.Error = xerrors.Errorf("get hung provisioner jobs: %w", err)
|
|
return stats
|
|
}
|
|
|
|
// Limit the number of jobs we'll unhang in a single run to avoid
|
|
// timing out.
|
|
if len(jobs) > MaxJobsPerRun {
|
|
// Pick a random subset of the jobs to unhang.
|
|
rand.Shuffle(len(jobs), func(i, j int) {
|
|
jobs[i], jobs[j] = jobs[j], jobs[i]
|
|
})
|
|
jobs = jobs[:MaxJobsPerRun]
|
|
}
|
|
|
|
// Send a message into the build log for each hung job saying that it
|
|
// has been detected and will be terminated, then mark the job as
|
|
// failed.
|
|
for _, job := range jobs {
|
|
log := d.log.With(slog.F("job_id", job.ID))
|
|
|
|
err := unhangJob(ctx, log, d.db, d.pubsub, job.ID)
|
|
if err != nil {
|
|
if !(xerrors.As(err, &acquireLockError{}) || xerrors.As(err, &jobIneligibleError{})) {
|
|
log.Error(ctx, "error forcefully terminating hung provisioner job", slog.Error(err))
|
|
}
|
|
continue
|
|
}
|
|
|
|
stats.TerminatedJobIDs = append(stats.TerminatedJobIDs, job.ID)
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
func unhangJob(ctx context.Context, log slog.Logger, db database.Store, pub pubsub.Pubsub, jobID uuid.UUID) error {
|
|
var lowestLogID int64
|
|
|
|
err := db.InTx(func(db database.Store) error {
|
|
locked, err := db.TryAcquireLock(ctx, database.GenLockID(fmt.Sprintf("hang-detector:%s", jobID)))
|
|
if err != nil {
|
|
return xerrors.Errorf("acquire lock: %w", err)
|
|
}
|
|
if !locked {
|
|
// This error is ignored.
|
|
return acquireLockError{}
|
|
}
|
|
|
|
// Refetch the job while we hold the lock.
|
|
job, err := db.GetProvisionerJobByID(ctx, jobID)
|
|
if err != nil {
|
|
return xerrors.Errorf("get provisioner job: %w", err)
|
|
}
|
|
|
|
// Check if we should still unhang it.
|
|
if !job.StartedAt.Valid {
|
|
// This shouldn't be possible to hit because the query only selects
|
|
// started and not completed jobs, and a job can't be "un-started".
|
|
return jobIneligibleError{
|
|
Err: xerrors.New("job is not started"),
|
|
}
|
|
}
|
|
if job.CompletedAt.Valid {
|
|
return jobIneligibleError{
|
|
Err: xerrors.Errorf("job is completed (status %s)", job.JobStatus),
|
|
}
|
|
}
|
|
if job.UpdatedAt.After(time.Now().Add(-HungJobDuration)) {
|
|
return jobIneligibleError{
|
|
Err: xerrors.New("job has been updated recently"),
|
|
}
|
|
}
|
|
|
|
log.Warn(
|
|
ctx, "detected hung provisioner job, forcefully terminating",
|
|
"threshold", HungJobDuration,
|
|
)
|
|
|
|
// First, get the latest logs from the build so we can make sure
|
|
// our messages are in the latest stage.
|
|
logs, err := db.GetProvisionerLogsAfterID(ctx, database.GetProvisionerLogsAfterIDParams{
|
|
JobID: job.ID,
|
|
CreatedAfter: 0,
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("get logs for hung job: %w", err)
|
|
}
|
|
logStage := ""
|
|
if len(logs) != 0 {
|
|
logStage = logs[len(logs)-1].Stage
|
|
}
|
|
if logStage == "" {
|
|
logStage = "Unknown"
|
|
}
|
|
|
|
// Insert the messages into the build log.
|
|
insertParams := database.InsertProvisionerJobLogsParams{
|
|
JobID: job.ID,
|
|
CreatedAt: nil,
|
|
Source: nil,
|
|
Level: nil,
|
|
Stage: nil,
|
|
Output: nil,
|
|
}
|
|
now := dbtime.Now()
|
|
for i, msg := range HungJobLogMessages {
|
|
// Set the created at in a way that ensures each message has
|
|
// a unique timestamp so they will be sorted correctly.
|
|
insertParams.CreatedAt = append(insertParams.CreatedAt, now.Add(time.Millisecond*time.Duration(i)))
|
|
insertParams.Level = append(insertParams.Level, database.LogLevelError)
|
|
insertParams.Stage = append(insertParams.Stage, logStage)
|
|
insertParams.Source = append(insertParams.Source, database.LogSourceProvisionerDaemon)
|
|
insertParams.Output = append(insertParams.Output, msg)
|
|
}
|
|
newLogs, err := db.InsertProvisionerJobLogs(ctx, insertParams)
|
|
if err != nil {
|
|
return xerrors.Errorf("insert logs for hung job: %w", err)
|
|
}
|
|
lowestLogID = newLogs[0].ID
|
|
|
|
// Mark the job as failed.
|
|
now = dbtime.Now()
|
|
err = db.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{
|
|
ID: job.ID,
|
|
UpdatedAt: now,
|
|
CompletedAt: sql.NullTime{
|
|
Time: now,
|
|
Valid: true,
|
|
},
|
|
Error: sql.NullString{
|
|
String: "Coder: Build has been detected as hung for 5 minutes and has been terminated by hang detector.",
|
|
Valid: true,
|
|
},
|
|
ErrorCode: sql.NullString{
|
|
Valid: false,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("mark job as failed: %w", err)
|
|
}
|
|
|
|
// If the provisioner job is a workspace build, copy the
|
|
// provisioner state from the previous build to this workspace
|
|
// build.
|
|
if job.Type == database.ProvisionerJobTypeWorkspaceBuild {
|
|
build, err := db.GetWorkspaceBuildByJobID(ctx, job.ID)
|
|
if err != nil {
|
|
return xerrors.Errorf("get workspace build for workspace build job by job id: %w", err)
|
|
}
|
|
|
|
// Only copy the provisioner state if there's no state in
|
|
// the current build.
|
|
if len(build.ProvisionerState) == 0 {
|
|
// Get the previous build if it exists.
|
|
prevBuild, err := db.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{
|
|
WorkspaceID: build.WorkspaceID,
|
|
BuildNumber: build.BuildNumber - 1,
|
|
})
|
|
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
|
return xerrors.Errorf("get previous workspace build: %w", err)
|
|
}
|
|
if err == nil {
|
|
err = db.UpdateWorkspaceBuildProvisionerStateByID(ctx, database.UpdateWorkspaceBuildProvisionerStateByIDParams{
|
|
ID: build.ID,
|
|
UpdatedAt: dbtime.Now(),
|
|
ProvisionerState: prevBuild.ProvisionerState,
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("update workspace build by id: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}, nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("in tx: %w", err)
|
|
}
|
|
|
|
// Publish the new log notification to pubsub. Use the lowest log ID
|
|
// inserted so the log stream will fetch everything after that point.
|
|
data, err := json.Marshal(provisionersdk.ProvisionerJobLogsNotifyMessage{
|
|
CreatedAfter: lowestLogID - 1,
|
|
EndOfLogs: true,
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("marshal log notification: %w", err)
|
|
}
|
|
err = pub.Publish(provisionersdk.ProvisionerJobLogsNotifyChannel(jobID), data)
|
|
if err != nil {
|
|
return xerrors.Errorf("publish log notification: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|