Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: record who created a run #556

Merged
merged 1 commit into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/http/html/static/templates/partials/run_item.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
| <a id="vcs-username" href="{{ $.IngressAttributes.SenderHTMLURL }}">@{{ . }}</a>
</span>
{{ end }}
{{ else }}
{{ with .CreatedBy }}
<span class="inline-block max-w-[16rem] truncate" id="run-created-by">
| @{{ . }}
</span>
{{ end }}
{{ end }}
<span>{{ durationRound .CreatedAt }} ago</span>
</div>
Expand Down
13 changes: 12 additions & 1 deletion internal/integration/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/leg100/otf/internal"
"github.com/leg100/otf/internal/auth"
"github.com/leg100/otf/internal/cloud"
"github.com/leg100/otf/internal/configversion"
"github.com/leg100/otf/internal/daemon"
Expand All @@ -23,8 +24,13 @@ func TestRun(t *testing.T) {
svc, _, ctx := setup(t, &config{Config: daemon.Config{DisableScheduler: true}})
cv := svc.createConfigurationVersion(t, ctx, nil, nil)

_, err := svc.CreateRun(ctx, cv.WorkspaceID, run.RunCreateOptions{})
run, err := svc.CreateRun(ctx, cv.WorkspaceID, run.RunCreateOptions{})
require.NoError(t, err)

user, err := auth.UserFromContext(ctx)
require.NoError(t, err)
assert.NotNil(t, run.CreatedBy)
assert.Equal(t, user.Username, *run.CreatedBy)
})

// test the "magic string" behaviour specific to OTF: if
Expand Down Expand Up @@ -115,6 +121,11 @@ func TestRun(t *testing.T) {
require.NoError(t, err)

assert.Equal(t, want, got)

user, err := auth.UserFromContext(ctx)
require.NoError(t, err)
assert.NotNil(t, got.CreatedBy)
assert.Equal(t, user.Username, *got.CreatedBy)
})

t.Run("list", func(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions internal/run/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type (
ConfigurationVersionID pgtype.Text `json:"configuration_version_id"`
WorkspaceID pgtype.Text `json:"workspace_id"`
PlanOnly bool `json:"plan_only"`
CreatedBy pgtype.Text `json:"created_by"`
ExecutionMode pgtype.Text `json:"execution_mode"`
Latest bool `json:"latest"`
OrganizationName pgtype.Text `json:"organization_name"`
Expand Down Expand Up @@ -87,6 +88,9 @@ func (result pgresult) toRun() *Run {
ResourceReport: reportFromDB(result.ApplyResourceReport),
},
}
if result.CreatedBy.Status == pgtype.Present {
run.CreatedBy = &result.CreatedBy.String
}
if result.ForceCancelAvailableAt.Status == pgtype.Present {
run.ForceCancelAvailableAt = internal.Time(result.ForceCancelAvailableAt.Time.UTC())
}
Expand All @@ -113,6 +117,7 @@ func (db *pgdb) CreateRun(ctx context.Context, run *Run) error {
PlanOnly: run.PlanOnly,
ConfigurationVersionID: sql.String(run.ConfigurationVersionID),
WorkspaceID: sql.String(run.WorkspaceID),
CreatedBy: sql.StringPtr(run.CreatedBy),
})
if err != nil {
return fmt.Errorf("inserting run: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion internal/run/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (f *factory) NewRun(ctx context.Context, workspaceID string, opts RunCreate
return nil, err
}

return newRun(cv, ws, opts), nil
return newRun(ctx, cv, ws, opts), nil
}

// createConfigVersionFromVCS creates a config version from the vcs repo
Expand Down
12 changes: 11 additions & 1 deletion internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
package run

import (
"context"
"errors"
"fmt"
"time"

"github.com/leg100/otf/internal"
"github.com/leg100/otf/internal/auth"
"github.com/leg100/otf/internal/configversion"
"github.com/leg100/otf/internal/rbac"
"github.com/leg100/otf/internal/resource"
Expand Down Expand Up @@ -68,6 +70,10 @@ type (

// IngressAttributes is non-nil if run was triggered by a VCS event.
IngressAttributes *configversion.IngressAttributes `json:"ingress_attributes"`

// Username of user who created the run. This is nil if the run was
// instead triggered by a VCS event.
CreatedBy *string
}

// RunList represents a list of runs.
Expand Down Expand Up @@ -133,7 +139,7 @@ type (
)

// newRun creates a new run with defaults.
func newRun(cv *configversion.ConfigurationVersion, ws *workspace.Workspace, opts RunCreateOptions) *Run {
func newRun(ctx context.Context, cv *configversion.ConfigurationVersion, ws *workspace.Workspace, opts RunCreateOptions) *Run {
run := Run{
ID: internal.NewID("run"),
CreatedAt: internal.CurrentTimestamp(),
Expand All @@ -152,6 +158,10 @@ func newRun(cv *configversion.ConfigurationVersion, ws *workspace.Workspace, opt
run.Apply = NewPhase(run.ID, internal.ApplyPhase)
run.updateStatus(internal.RunPending)

if user, _ := auth.UserFromContext(ctx); user != nil {
run.CreatedBy = &user.Username
}

if opts.IsDestroy != nil {
run.IsDestroy = *opts.IsDestroy
}
Expand Down
48 changes: 30 additions & 18 deletions internal/run/run_test.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
package run

import (
"context"
"testing"

"github.com/leg100/otf/internal"
"github.com/leg100/otf/internal/auth"
"github.com/leg100/otf/internal/configversion"
"github.com/leg100/otf/internal/workspace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRun_New_CreatedBy(t *testing.T) {
ctx := context.Background()
ctx = internal.AddSubjectToContext(ctx, &auth.User{Username: "terry"})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
assert.NotNil(t, run.CreatedBy)
assert.Equal(t, "terry", *run.CreatedBy)
}

func TestRun_States(t *testing.T) {
ctx := context.Background()

t.Run("pending", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})

require.Equal(t, internal.RunPending, run.Status)
require.Equal(t, PhasePending, run.Plan.Status)
require.Equal(t, PhasePending, run.Apply.Status)
})

t.Run("enqueue plan", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})

require.NoError(t, run.EnqueuePlan())

Expand All @@ -30,7 +42,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("start plan", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunPlanQueued

require.NoError(t, run.Start(internal.PlanPhase))
Expand All @@ -41,7 +53,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("finish plan", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunPlanning

require.NoError(t, run.Finish(internal.PlanPhase, PhaseFinishOptions{}))
Expand All @@ -52,7 +64,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("finish plan with errors", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunPlanning

require.NoError(t, run.Finish(internal.PlanPhase, PhaseFinishOptions{Errored: true}))
Expand All @@ -63,7 +75,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("finish plan with resource changes", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunPlanning

run.Plan.ResourceReport = &Report{Additions: 1}
Expand All @@ -76,7 +88,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("finish plan with output changes", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunPlanning

run.Plan.OutputReport = &Report{Additions: 1}
Expand All @@ -89,7 +101,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("finish plan with changes on run with autoapply enabled", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{
AutoApply: internal.Bool(true),
})
run.Status = internal.RunPlanning
Expand All @@ -104,7 +116,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("enqueue apply", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunPlanned

require.NoError(t, run.EnqueueApply())
Expand All @@ -114,7 +126,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("start apply", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunApplyQueued

require.NoError(t, run.Start(internal.ApplyPhase))
Expand All @@ -124,7 +136,7 @@ func TestRun_States(t *testing.T) {
})

t.Run("finish apply", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunApplying

require.NoError(t, run.Finish(internal.ApplyPhase, PhaseFinishOptions{}))
Expand All @@ -134,19 +146,19 @@ func TestRun_States(t *testing.T) {
})

t.Run("finish apply with errors", func(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
run.Status = internal.RunApplying

require.NoError(t, run.Finish(internal.ApplyPhase, PhaseFinishOptions{Errored: true}))

require.Equal(t, internal.RunErrored, run.Status)
require.Equal(t, PhaseErrored, run.Apply.Status)
})
}

func TestRun_Cancel(t *testing.T) {
run := newRun(&configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
err := run.Cancel()
require.NoError(t, err)
assert.NotZero(t, run.ForceCancelAvailableAt)
t.Run("cancel run", func(t *testing.T) {
run := newRun(ctx, &configversion.ConfigurationVersion{}, &workspace.Workspace{}, RunCreateOptions{})
err := run.Cancel()
require.NoError(t, err)
assert.NotZero(t, run.ForceCancelAvailableAt)
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- +goose Up
ALTER TABLE runs ADD COLUMN created_by TEXT;

-- +goose Down
ALTER TABLE runs DROP COLUMN created_by;
2 changes: 2 additions & 0 deletions internal/sql/pggen/agent_token.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading