From 7e36010cb75f578b70eea3ea771c01c3cffd7f16 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 7 Aug 2024 13:44:02 +0200 Subject: [PATCH 01/22] Initial implementation Signed-off-by: Danny Kopping --- coderd/database/dbauthz/dbauthz.go | 8 + coderd/database/dbmem/dbmem.go | 9 + coderd/database/dbmetrics/dbmetrics.go | 7 + coderd/database/dbmock/dbmock.go | 15 + coderd/database/dump.sql | 12 + coderd/database/foreign_key_constraint.go | 1 + .../000242_provisioner_job_timings.down.sql | 1 + .../000242_provisioner_job_timings.up.sql | 9 + coderd/database/models.go | 9 + coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 58 + coderd/database/queries/provisionerjobs.sql | 11 + .../provisionerdserver/provisionerdserver.go | 25 + go.mod | 1 + go.sum | 2 + provisioner/terraform/executor.go | 48 +- provisioner/terraform/timings.go | 146 ++ provisionerd/proto/provisionerd.pb.go | 419 +++--- provisionerd/proto/provisionerd.proto | 2 + provisionerd/proto/provisionerd_drpc.pb.go | 2 +- provisionerd/runner/runner.go | 3 + provisionersdk/proto/provisioner.pb.go | 1251 ++++++++++------- provisionersdk/proto/provisioner.proto | 20 + provisionersdk/proto/provisioner_drpc.pb.go | 2 +- .../e2e/google/protobuf/timestampGenerated.ts | 123 ++ site/e2e/provisionerGenerated.ts | 66 + 26 files changed, 1520 insertions(+), 731 deletions(-) create mode 100644 coderd/database/migrations/000242_provisioner_job_timings.down.sql create mode 100644 coderd/database/migrations/000242_provisioner_job_timings.up.sql create mode 100644 provisioner/terraform/timings.go create mode 100644 site/e2e/google/protobuf/timestampGenerated.ts diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index d089d94226ddc..23a50799c122b 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -2787,6 +2787,14 @@ func (q *querier) InsertProvisionerJobLogs(ctx context.Context, arg database.Ins return q.db.InsertProvisionerJobLogs(ctx, arg) } +// TODO: We need to create a ProvisionerJob resource type +func (q *querier) InsertProvisionerJobTimings(ctx context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) { + // if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil { + // return nil, err + // } + return q.db.InsertProvisionerJobTimings(ctx, arg) +} + func (q *querier) InsertProvisionerKey(ctx context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) { return insert(q.log, q.auth, rbac.ResourceProvisionerKeys.InOrg(arg.OrganizationID).WithID(arg.ID), q.db.InsertProvisionerKey)(ctx, arg) } diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index aa2665d3c3257..30e12f724c496 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6633,6 +6633,15 @@ func (q *FakeQuerier) InsertProvisionerJobLogs(_ context.Context, arg database.I return logs, nil } +func (q *FakeQuerier) InsertProvisionerJobTimings(ctx context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) { + err := validateDatabaseType(arg) + if err != nil { + return nil, err + } + + panic("not implemented") +} + func (q *FakeQuerier) InsertProvisionerKey(_ context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) { err := validateDatabaseType(arg) if err != nil { diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 5189f43400d0c..d24bd1bb508bc 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -1719,6 +1719,13 @@ func (m metricsStore) InsertProvisionerJobLogs(ctx context.Context, arg database return logs, err } +func (m metricsStore) InsertProvisionerJobTimings(ctx context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) { + start := time.Now() + r0, r1 := m.s.InsertProvisionerJobTimings(ctx, arg) + m.queryLatencies.WithLabelValues("InsertProvisionerJobTimings").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) InsertProvisionerKey(ctx context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) { start := time.Now() r0, r1 := m.s.InsertProvisionerKey(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index af8e610d8e569..1c03ffd43218a 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -3619,6 +3619,21 @@ func (mr *MockStoreMockRecorder) InsertProvisionerJobLogs(arg0, arg1 any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerJobLogs", reflect.TypeOf((*MockStore)(nil).InsertProvisionerJobLogs), arg0, arg1) } +// InsertProvisionerJobTimings mocks base method. +func (m *MockStore) InsertProvisionerJobTimings(arg0 context.Context, arg1 database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertProvisionerJobTimings", arg0, arg1) + ret0, _ := ret[0].([]database.ProvisionerJobTiming) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertProvisionerJobTimings indicates an expected call of InsertProvisionerJobTimings. +func (mr *MockStoreMockRecorder) InsertProvisionerJobTimings(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerJobTimings", reflect.TypeOf((*MockStore)(nil).InsertProvisionerJobTimings), arg0, arg1) +} + // InsertProvisionerKey mocks base method. func (m *MockStore) InsertProvisionerKey(arg0 context.Context, arg1 database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index b34362a33432a..780ae5bd9b441 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -828,6 +828,15 @@ CREATE SEQUENCE provisioner_job_logs_id_seq ALTER SEQUENCE provisioner_job_logs_id_seq OWNED BY provisioner_job_logs.id; +CREATE TABLE provisioner_job_timings ( + provisioner_job_id uuid NOT NULL, + started_at timestamp with time zone NOT NULL, + ended_at timestamp with time zone NOT NULL, + context text NOT NULL, + action text NOT NULL, + resource text NOT NULL +); + CREATE TABLE provisioner_jobs ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, @@ -1987,6 +1996,9 @@ ALTER TABLE ONLY provisioner_daemons ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; +ALTER TABLE ONLY provisioner_job_timings + ADD CONSTRAINT provisioner_job_timings_provisioner_job_id_fkey FOREIGN KEY (provisioner_job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; + ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index 011d39bdc5b91..c7843b9991231 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -29,6 +29,7 @@ const ( ForeignKeyParameterSchemasJobID ForeignKeyConstraint = "parameter_schemas_job_id_fkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ForeignKeyProvisionerDaemonsOrganizationID ForeignKeyConstraint = "provisioner_daemons_organization_id_fkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyProvisionerJobLogsJobID ForeignKeyConstraint = "provisioner_job_logs_job_id_fkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; + ForeignKeyProvisionerJobTimingsProvisionerJobID ForeignKeyConstraint = "provisioner_job_timings_provisioner_job_id_fkey" // ALTER TABLE ONLY provisioner_job_timings ADD CONSTRAINT provisioner_job_timings_provisioner_job_id_fkey FOREIGN KEY (provisioner_job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ForeignKeyProvisionerJobsOrganizationID ForeignKeyConstraint = "provisioner_jobs_organization_id_fkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyProvisionerKeysOrganizationID ForeignKeyConstraint = "provisioner_keys_organization_id_fkey" // ALTER TABLE ONLY provisioner_keys ADD CONSTRAINT provisioner_keys_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyTailnetAgentsCoordinatorID ForeignKeyConstraint = "tailnet_agents_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000242_provisioner_job_timings.down.sql b/coderd/database/migrations/000242_provisioner_job_timings.down.sql new file mode 100644 index 0000000000000..af3b9497d9595 --- /dev/null +++ b/coderd/database/migrations/000242_provisioner_job_timings.down.sql @@ -0,0 +1 @@ +-- TODO \ No newline at end of file diff --git a/coderd/database/migrations/000242_provisioner_job_timings.up.sql b/coderd/database/migrations/000242_provisioner_job_timings.up.sql new file mode 100644 index 0000000000000..8ee97620c355a --- /dev/null +++ b/coderd/database/migrations/000242_provisioner_job_timings.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE provisioner_job_timings +( + provisioner_job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE, + started_at timestamp with time zone not null, + ended_at timestamp with time zone not null, + context text not null, -- TODO: enum? + action text not null, -- TODO: enum? + resource text not null -- TODO: enum? +); \ No newline at end of file diff --git a/coderd/database/models.go b/coderd/database/models.go index a120d16cf9dc8..a433d435bece4 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2280,6 +2280,15 @@ type ProvisionerJobLog struct { ID int64 `db:"id" json:"id"` } +type ProvisionerJobTiming struct { + ProvisionerJobID uuid.UUID `db:"provisioner_job_id" json:"provisioner_job_id"` + StartedAt time.Time `db:"started_at" json:"started_at"` + EndedAt time.Time `db:"ended_at" json:"ended_at"` + Context string `db:"context" json:"context"` + Action string `db:"action" json:"action"` + Resource string `db:"resource" json:"resource"` +} + type ProvisionerKey struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 1355e41894fc4..4ae5973a6a98a 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -358,6 +358,7 @@ type sqlcQuerier interface { InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error) InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error) InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error) + InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error) InsertProvisionerKey(ctx context.Context, arg InsertProvisionerKeyParams) (ProvisionerKey, error) InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error) InsertTemplate(ctx context.Context, arg InsertTemplateParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 58202c5928dda..ca88a9373412d 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5604,6 +5604,64 @@ func (q *sqlQuerier) InsertProvisionerJob(ctx context.Context, arg InsertProvisi return i, err } +const insertProvisionerJobTimings = `-- name: InsertProvisionerJobTimings :many +INSERT INTO provisioner_job_timings (provisioner_job_id, started_at, ended_at, context, action, resource) +SELECT + $1::uuid AS provisioner_job_id, + unnest($2::timestamptz[]) AS started_at, + unnest($3::timestamptz[]) AS ended_at, + unnest($4::text[]) AS context, + unnest($5::text[]) AS action, + unnest($6::text[]) AS resource +RETURNING provisioner_job_id, started_at, ended_at, context, action, resource +` + +type InsertProvisionerJobTimingsParams struct { + JobID uuid.UUID `db:"job_id" json:"job_id"` + StartedAt []time.Time `db:"started_at" json:"started_at"` + EndedAt []time.Time `db:"ended_at" json:"ended_at"` + Context []string `db:"context" json:"context"` + Action []string `db:"action" json:"action"` + Resource []string `db:"resource" json:"resource"` +} + +func (q *sqlQuerier) InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error) { + rows, err := q.db.QueryContext(ctx, insertProvisionerJobTimings, + arg.JobID, + pq.Array(arg.StartedAt), + pq.Array(arg.EndedAt), + pq.Array(arg.Context), + pq.Array(arg.Action), + pq.Array(arg.Resource), + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ProvisionerJobTiming + for rows.Next() { + var i ProvisionerJobTiming + if err := rows.Scan( + &i.ProvisionerJobID, + &i.StartedAt, + &i.EndedAt, + &i.Context, + &i.Action, + &i.Resource, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const updateProvisionerJobByID = `-- name: UpdateProvisionerJobByID :exec UPDATE provisioner_jobs diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index d1d5f68ce750e..b02b19c7e2fee 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -144,3 +144,14 @@ WHERE updated_at < $1 AND started_at IS NOT NULL AND completed_at IS NULL; + +-- name: InsertProvisionerJobTimings :many +INSERT INTO provisioner_job_timings (provisioner_job_id, started_at, ended_at, context, action, resource) +SELECT + @job_id::uuid AS provisioner_job_id, + unnest(@started_at::timestamptz[]) AS started_at, + unnest(@ended_at::timestamptz[]) AS ended_at, + unnest(@context::text[]) AS context, + unnest(@action::text[]) AS action, + unnest(@resource::text[]) AS resource +RETURNING *; \ No newline at end of file diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 458f79ca348e6..4ec1cf8a3bc90 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1441,6 +1441,31 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) return nil, xerrors.Errorf("complete job: %w", err) } + // Insert timings outside transaction since it is metadata. + params := database.InsertProvisionerJobTimingsParams{ + JobID: jobID, + } + for _, t := range completed.GetWorkspaceBuild().GetTimings() { + var start, end time.Time + if t.Start != nil { + start = t.Start.AsTime() + } + if t.End != nil { + end = t.End.AsTime() + } + + params.Context = append(params.Context, t.Provider) + params.Resource = append(params.Resource, t.Resource) + params.Action = append(params.Action, t.Action) + params.StartedAt = append(params.StartedAt, start) + params.EndedAt = append(params.EndedAt, end) + } + _, err = s.Database.InsertProvisionerJobTimings(ctx, params) + if err != nil { + // Don't fail the transaction for non-critical data. + s.Logger.Warn(ctx, "failed to update provisioner job timings", slog.Error(err)) + } + // audit the outcome of the workspace build if getWorkspaceError == nil { // If the workspace has been deleted, notify the owner about it. diff --git a/go.mod b/go.mod index 9b2dfcd4f4b46..1e05b0a8eaee4 100644 --- a/go.mod +++ b/go.mod @@ -196,6 +196,7 @@ require ( require go.uber.org/mock v0.4.0 require ( + github.com/cespare/xxhash v1.1.0 github.com/coder/serpent v0.7.0 github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 github.com/emersion/go-smtp v0.21.2 diff --git a/go.sum b/go.sum index ce4034a7da5a5..c1fecc99e3633 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,7 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= @@ -849,6 +850,7 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index b1a3ecadb5203..c97b096d6563d 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -14,12 +14,12 @@ import ( "strings" "sync" + "cdr.dev/slog" "github.com/hashicorp/go-version" tfjson "github.com/hashicorp/terraform-json" "go.opentelemetry.io/otel/attribute" "golang.org/x/xerrors" - "cdr.dev/slog" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/provisionersdk/proto" ) @@ -252,7 +252,8 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l args = append(args, "-var", variable) } - outWriter, doneOut := provisionLogWriter(logr) + timingsAgg := newTimingsAggregator() + outWriter, doneOut := provisionLogWriter(logr, timingsAgg) errWriter, doneErr := logWriter(logr, proto.LogLevel_ERROR) defer func() { _ = outWriter.Close() @@ -269,10 +270,17 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l if err != nil { return nil, err } + + timings, err := timingsAgg.aggregate() + if err != nil { + e.logger.Warn(ctx, "failed to aggregate timings", slog.Error(err)) + } + return &proto.PlanComplete{ Parameters: state.Parameters, Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, + Timings: timings, }, nil } @@ -399,7 +407,8 @@ func (e *executor) apply( getPlanFilePath(e.workdir), } - outWriter, doneOut := provisionLogWriter(logr) + timingsAgg := newTimingsAggregator() + outWriter, doneOut := provisionLogWriter(logr, timingsAgg) errWriter, doneErr := logWriter(logr, proto.LogLevel_ERROR) defer func() { _ = outWriter.Close() @@ -421,11 +430,18 @@ func (e *executor) apply( if err != nil { return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err) } + + timings, err := timingsAgg.aggregate() + if err != nil { + e.logger.Warn(ctx, "failed to aggregate timings", slog.Error(err)) + } + return &proto.ApplyComplete{ Parameters: state.Parameters, Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, State: stateContent, + Timings: timings, }, nil } @@ -539,14 +555,15 @@ func readAndLog(sink logSink, r io.Reader, done chan<- any, level proto.LogLevel // provisionLogWriter creates a WriteCloser that will log each JSON formatted terraform log. The WriteCloser must be // closed by the caller to end logging, after which the returned channel will be closed to indicate that logging of the // written data has finished. Failure to close the WriteCloser will leak a goroutine. -func provisionLogWriter(sink logSink) (io.WriteCloser, <-chan any) { +func provisionLogWriter(sink logSink, timings *timingsAggregator) (io.WriteCloser, <-chan any) { r, w := io.Pipe() done := make(chan any) - go provisionReadAndLog(sink, r, done) + + go provisionReadAndLog(sink, r, timings, done) return w, done } -func provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) { +func provisionReadAndLog(sink logSink, r io.Reader, timings *timingsAggregator, done chan<- any) { defer close(done) scanner := bufio.NewScanner(r) for scanner.Scan() { @@ -579,6 +596,8 @@ func provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) { logLevel := convertTerraformLogLevel(log.Level, sink) sink.ProvisionLog(logLevel, log.Message) + timings.ingest(log) + // If the diagnostic is provided, let's provide a bit more info! if log.Diagnostic == nil { continue @@ -609,12 +628,25 @@ func convertTerraformLogLevel(logLevel string, sink logSink) proto.LogLevel { } type terraformProvisionLog struct { - Level string `json:"@level"` - Message string `json:"@message"` + Level string `json:"@level"` + Message string `json:"@message"` + Timestamp string `json:"@timestamp"` + Type string `json:"type"` + Hook terraformProvisionLogHook `json:"hook"` Diagnostic *tfjson.Diagnostic `json:"diagnostic,omitempty"` } +type terraformProvisionLogHook struct { + Action string `json:"action"` + Resource terraformProvisionLogHookResource `json:"resource"` +} + +type terraformProvisionLogHookResource struct { + Addr string `json:"addr"` + Provider string `json:"implied_provider"` +} + // syncWriter wraps an io.Writer in a sync.Mutex. type syncWriter struct { mut *sync.Mutex diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go new file mode 100644 index 0000000000000..0dcc0bc02967e --- /dev/null +++ b/provisioner/terraform/timings.go @@ -0,0 +1,146 @@ +package terraform + +import ( + "fmt" + "slices" + "time" + + "github.com/cespare/xxhash" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/coder/coder/v2/provisionersdk/proto" +) + +type logType string + +// Copied from https://github.com/hashicorp/terraform/blob/ffbcaf8bef12bb1f4d79f06437f414e280d08761/internal/command/views/json/message_types.go +// We cannot reference these because they're in an internal package. +const ( + applyStart logType = "apply_start" + applyProgress logType = "apply_progress" + applyComplete logType = "apply_complete" + applyErrored logType = "apply_errored" + provisionStart logType = "provision_start" + provisionProgress logType = "provision_progress" + provisionComplete logType = "provision_complete" + provisionErrored logType = "provision_errored" + refreshStart logType = "refresh_start" + refreshComplete logType = "refresh_complete" +) + +type timingsAggregator struct { + stateLookup map[uint64]*entry +} + +type entry struct { + kind logType + start, end time.Time + action, provider, resource string + state proto.TimingState +} + +func newTimingsAggregator() *timingsAggregator { + return &timingsAggregator{ + stateLookup: make(map[uint64]*entry), + } +} + +func (t *timingsAggregator) ingest(log terraformProvisionLog) { + // Input is not well-formed, bail out. + if log.Type == "" { + return + } + + typ := logType(log.Type) + if !typ.Valid() { + // TODO: log + return + } + + ts, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", log.Timestamp) + if err != nil { + // TODO: log + ts = time.Now() + } + ts = ts.UTC() + + e := &entry{ + kind: typ, + action: log.Hook.Action, + provider: log.Hook.Resource.Provider, + resource: log.Hook.Resource.Addr, + } + + switch typ { + case applyStart, provisionStart, refreshStart: + e.start = ts + e.state = proto.TimingState_INCOMPLETE + case applyComplete, provisionComplete, refreshComplete: + e.end = ts + e.state = proto.TimingState_COMPLETED + case applyErrored, provisionErrored: + e.end = ts + e.state = proto.TimingState_FAILED + case applyProgress, provisionProgress: + // Don't capture progress messages; we just want start/end timings. + return + } + + t.stateLookup[e.hashByState(e.state)] = e +} + +func (t *timingsAggregator) aggregate() ([]*proto.Timing, error) { + out := make([]*proto.Timing, 0, len(t.stateLookup)) + + for _, e := range t.stateLookup { + switch e.state { + case proto.TimingState_FAILED, proto.TimingState_COMPLETED: + i, ok := t.stateLookup[e.hashByState(proto.TimingState_INCOMPLETE)] + if !ok { + // could not find corresponding "incomplete" event, marking as unknown. + e.state = proto.TimingState_UNKNOWN + } else { + e.start = i.start + } + default: + continue + } + + out = append(out, e.toProto()) + } + + return out, nil +} + +func (l logType) Valid() bool { + return slices.Contains([]logType{ + applyStart, + applyProgress, + applyComplete, + applyErrored, + provisionStart, + provisionProgress, + provisionComplete, + provisionErrored, + refreshStart, + refreshComplete, + }, l) +} + +// hashState computes a hash based on an entry's unique properties and state. +// The combination of resource and provider names MUST be unique across entries. +func (e *entry) hashByState(state proto.TimingState) uint64 { + id := fmt.Sprintf("%s:%s:%s", state.String(), e.resource, e.provider) + return xxhash.Sum64String(id) +} + +func (e *entry) toProto() *proto.Timing { + return &proto.Timing{ + Start: timestamppb.New(e.start), + End: timestamppb.New(e.end), + Action: e.action, + Provider: e.provider, + Resource: e.resource, + State: e.state, + } +} diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index f2a8123ff780d..9f51702f67802 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v4.23.3 +// protoc-gen-go v1.34.2 +// protoc v4.25.3 // source: provisionerd/proto/provisionerd.proto package proto @@ -1081,7 +1081,8 @@ type FailedJob_WorkspaceBuild struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - State []byte `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` + State []byte `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` + Timings []*proto.Timing `protobuf:"bytes,2,rep,name=timings,proto3" json:"timings,omitempty"` } func (x *FailedJob_WorkspaceBuild) Reset() { @@ -1123,6 +1124,13 @@ func (x *FailedJob_WorkspaceBuild) GetState() []byte { return nil } +func (x *FailedJob_WorkspaceBuild) GetTimings() []*proto.Timing { + if x != nil { + return x.Timings + } + return nil +} + type FailedJob_TemplateImport struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1206,6 +1214,7 @@ type CompletedJob_WorkspaceBuild struct { State []byte `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` Resources []*proto.Resource `protobuf:"bytes,2,rep,name=resources,proto3" json:"resources,omitempty"` + Timings []*proto.Timing `protobuf:"bytes,3,rep,name=timings,proto3" json:"timings,omitempty"` } func (x *CompletedJob_WorkspaceBuild) Reset() { @@ -1254,6 +1263,13 @@ func (x *CompletedJob_WorkspaceBuild) GetResources() []*proto.Resource { return nil } +func (x *CompletedJob_WorkspaceBuild) GetTimings() []*proto.Timing { + if x != nil { + return x.Timings + } + return nil +} + type CompletedJob_TemplateImport struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1479,7 +1495,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xa5, 0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd4, 0x03, 0x0a, 0x09, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, @@ -1500,159 +1516,165 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x26, 0x0a, 0x0e, 0x57, 0x6f, 0x72, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x1a, 0x55, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, - 0x72, 0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd0, 0x06, - 0x0a, 0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, - 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, - 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, - 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, - 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x5b, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, + 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, + 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, + 0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x80, 0x07, 0x0a, + 0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a, + 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, + 0x6f, 0x62, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, + 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x12, 0x55, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x79, + 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, + 0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x8a, 0x01, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0xf9, 0x02, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, - 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, + 0x69, 0x6e, 0x67, 0x73, 0x1a, 0xf9, 0x02, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, - 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, - 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x65, - 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x1a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x61, - 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x29, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x73, 0x1a, 0x45, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, - 0x52, 0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, - 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x22, 0xa6, 0x03, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, - 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, - 0x25, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, - 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, - 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, - 0x75, 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, + 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, 0x68, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x1a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x61, 0x0a, + 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x1a, 0x45, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52, + 0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, + 0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x22, 0xa6, 0x03, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x25, + 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x52, + 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x73, 0x12, 0x4c, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x12, 0x75, + 0x73, 0x65, 0x72, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, + 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a, 0x11, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0f, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, + 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, + 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, + 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, + 0x6f, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, + 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x72, + 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x43, 0x6f, 0x6e, + 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x22, 0x0f, 0x0a, + 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x2a, 0x34, + 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x50, + 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, 0x4f, + 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, + 0x45, 0x52, 0x10, 0x01, 0x32, 0xc5, 0x03, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0a, 0x41, 0x63, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x52, 0x0a, + 0x14, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x57, 0x69, 0x74, 0x68, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, + 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, + 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x7a, 0x0a, 0x11, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x12, 0x43, 0x0a, - 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, - 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, - 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, - 0x43, 0x6f, 0x73, 0x74, 0x22, 0x68, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, - 0x6f, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, - 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x29, 0x0a, 0x10, 0x63, - 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x73, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x22, 0x0f, - 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x2a, - 0x34, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x12, - 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x44, 0x41, 0x45, 0x4d, - 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, - 0x4e, 0x45, 0x52, 0x10, 0x01, 0x32, 0xc5, 0x03, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x41, 0x0a, 0x0a, 0x41, - 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x41, 0x63, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x22, 0x03, 0x88, 0x02, 0x01, 0x12, 0x52, - 0x0a, 0x14, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x4a, 0x6f, 0x62, 0x57, 0x69, 0x74, 0x68, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x63, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x64, 0x2e, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x28, 0x01, - 0x30, 0x01, 0x12, 0x52, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, - 0x61, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4a, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62, 0x12, - 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, - 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3e, 0x0a, - 0x0b, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, - 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2e, 0x5a, - 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x46, 0x61, 0x69, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x17, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x46, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3e, 0x0a, 0x0b, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x2e, 0x5a, 0x2c, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1669,7 +1691,7 @@ func file_provisionerd_proto_provisionerd_proto_rawDescGZIP() []byte { var file_provisionerd_proto_provisionerd_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 21) -var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ +var file_provisionerd_proto_provisionerd_proto_goTypes = []any{ (LogSource)(0), // 0: provisionerd.LogSource (*Empty)(nil), // 1: provisionerd.Empty (*AcquiredJob)(nil), // 2: provisionerd.AcquiredJob @@ -1698,9 +1720,10 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (*proto.RichParameterValue)(nil), // 25: provisioner.RichParameterValue (*proto.ExternalAuthProvider)(nil), // 26: provisioner.ExternalAuthProvider (*proto.Metadata)(nil), // 27: provisioner.Metadata - (*proto.Resource)(nil), // 28: provisioner.Resource - (*proto.RichParameter)(nil), // 29: provisioner.RichParameter - (*proto.ExternalAuthProviderResource)(nil), // 30: provisioner.ExternalAuthProviderResource + (*proto.Timing)(nil), // 28: provisioner.Timing + (*proto.Resource)(nil), // 29: provisioner.Resource + (*proto.RichParameter)(nil), // 30: provisioner.RichParameter + (*proto.ExternalAuthProviderResource)(nil), // 31: provisioner.ExternalAuthProviderResource } var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{ 11, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild @@ -1729,29 +1752,31 @@ var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{ 25, // 23: provisionerd.AcquiredJob.TemplateDryRun.rich_parameter_values:type_name -> provisioner.RichParameterValue 24, // 24: provisionerd.AcquiredJob.TemplateDryRun.variable_values:type_name -> provisioner.VariableValue 27, // 25: provisionerd.AcquiredJob.TemplateDryRun.metadata:type_name -> provisioner.Metadata - 28, // 26: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource - 28, // 27: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource - 28, // 28: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource - 29, // 29: provisionerd.CompletedJob.TemplateImport.rich_parameters:type_name -> provisioner.RichParameter - 30, // 30: provisionerd.CompletedJob.TemplateImport.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource - 28, // 31: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource - 1, // 32: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty - 10, // 33: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:input_type -> provisionerd.CancelAcquire - 8, // 34: provisionerd.ProvisionerDaemon.CommitQuota:input_type -> provisionerd.CommitQuotaRequest - 6, // 35: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest - 3, // 36: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob - 4, // 37: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob - 2, // 38: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob - 2, // 39: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:output_type -> provisionerd.AcquiredJob - 9, // 40: provisionerd.ProvisionerDaemon.CommitQuota:output_type -> provisionerd.CommitQuotaResponse - 7, // 41: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse - 1, // 42: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty - 1, // 43: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty - 38, // [38:44] is the sub-list for method output_type - 32, // [32:38] is the sub-list for method input_type - 32, // [32:32] is the sub-list for extension type_name - 32, // [32:32] is the sub-list for extension extendee - 0, // [0:32] is the sub-list for field type_name + 28, // 26: provisionerd.FailedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing + 29, // 27: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource + 28, // 28: provisionerd.CompletedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing + 29, // 29: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource + 29, // 30: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource + 30, // 31: provisionerd.CompletedJob.TemplateImport.rich_parameters:type_name -> provisioner.RichParameter + 31, // 32: provisionerd.CompletedJob.TemplateImport.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource + 29, // 33: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource + 1, // 34: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty + 10, // 35: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:input_type -> provisionerd.CancelAcquire + 8, // 36: provisionerd.ProvisionerDaemon.CommitQuota:input_type -> provisionerd.CommitQuotaRequest + 6, // 37: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest + 3, // 38: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob + 4, // 39: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob + 2, // 40: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob + 2, // 41: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:output_type -> provisionerd.AcquiredJob + 9, // 42: provisionerd.ProvisionerDaemon.CommitQuota:output_type -> provisionerd.CommitQuotaResponse + 7, // 43: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse + 1, // 44: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty + 1, // 45: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty + 40, // [40:46] is the sub-list for method output_type + 34, // [34:40] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name } func init() { file_provisionerd_proto_provisionerd_proto_init() } @@ -1760,7 +1785,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_provisionerd_proto_provisionerd_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Empty); i { case 0: return &v.state @@ -1772,7 +1797,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*AcquiredJob); i { case 0: return &v.state @@ -1784,7 +1809,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*FailedJob); i { case 0: return &v.state @@ -1796,7 +1821,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*CompletedJob); i { case 0: return &v.state @@ -1808,7 +1833,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*Log); i { case 0: return &v.state @@ -1820,7 +1845,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*UpdateJobRequest); i { case 0: return &v.state @@ -1832,7 +1857,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*UpdateJobResponse); i { case 0: return &v.state @@ -1844,7 +1869,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*CommitQuotaRequest); i { case 0: return &v.state @@ -1856,7 +1881,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*CommitQuotaResponse); i { case 0: return &v.state @@ -1868,7 +1893,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*CancelAcquire); i { case 0: return &v.state @@ -1880,7 +1905,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*AcquiredJob_WorkspaceBuild); i { case 0: return &v.state @@ -1892,7 +1917,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*AcquiredJob_TemplateImport); i { case 0: return &v.state @@ -1904,7 +1929,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*AcquiredJob_TemplateDryRun); i { case 0: return &v.state @@ -1916,7 +1941,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*FailedJob_WorkspaceBuild); i { case 0: return &v.state @@ -1928,7 +1953,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*FailedJob_TemplateImport); i { case 0: return &v.state @@ -1940,7 +1965,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*FailedJob_TemplateDryRun); i { case 0: return &v.state @@ -1952,7 +1977,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*CompletedJob_WorkspaceBuild); i { case 0: return &v.state @@ -1964,7 +1989,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*CompletedJob_TemplateImport); i { case 0: return &v.state @@ -1976,7 +2001,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + file_provisionerd_proto_provisionerd_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*CompletedJob_TemplateDryRun); i { case 0: return &v.state @@ -1989,17 +2014,17 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } } - file_provisionerd_proto_provisionerd_proto_msgTypes[1].OneofWrappers = []interface{}{ + file_provisionerd_proto_provisionerd_proto_msgTypes[1].OneofWrappers = []any{ (*AcquiredJob_WorkspaceBuild_)(nil), (*AcquiredJob_TemplateImport_)(nil), (*AcquiredJob_TemplateDryRun_)(nil), } - file_provisionerd_proto_provisionerd_proto_msgTypes[2].OneofWrappers = []interface{}{ + file_provisionerd_proto_provisionerd_proto_msgTypes[2].OneofWrappers = []any{ (*FailedJob_WorkspaceBuild_)(nil), (*FailedJob_TemplateImport_)(nil), (*FailedJob_TemplateDryRun_)(nil), } - file_provisionerd_proto_provisionerd_proto_msgTypes[3].OneofWrappers = []interface{}{ + file_provisionerd_proto_provisionerd_proto_msgTypes[3].OneofWrappers = []any{ (*CompletedJob_WorkspaceBuild_)(nil), (*CompletedJob_TemplateImport_)(nil), (*CompletedJob_TemplateDryRun_)(nil), diff --git a/provisionerd/proto/provisionerd.proto b/provisionerd/proto/provisionerd.proto index 426ba63e2f98e..f061ae801efd7 100644 --- a/provisionerd/proto/provisionerd.proto +++ b/provisionerd/proto/provisionerd.proto @@ -53,6 +53,7 @@ message AcquiredJob { message FailedJob { message WorkspaceBuild { bytes state = 1; + repeated provisioner.Timing timings = 2; } message TemplateImport {} message TemplateDryRun {} @@ -72,6 +73,7 @@ message CompletedJob { message WorkspaceBuild { bytes state = 1; repeated provisioner.Resource resources = 2; + repeated provisioner.Timing timings = 3; } message TemplateImport { repeated provisioner.Resource start_resources = 1; diff --git a/provisionerd/proto/provisionerd_drpc.pb.go b/provisionerd/proto/provisionerd_drpc.pb.go index 60d78a86acb17..998a56b6eeda0 100644 --- a/provisionerd/proto/provisionerd_drpc.pb.go +++ b/provisionerd/proto/provisionerd_drpc.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-go-drpc. DO NOT EDIT. -// protoc-gen-go-drpc version: v0.0.33 +// protoc-gen-go-drpc version: (devel) // source: provisionerd/proto/provisionerd.proto package proto diff --git a/provisionerd/runner/runner.go b/provisionerd/runner/runner.go index 08230a80051d0..afa31615bba31 100644 --- a/provisionerd/runner/runner.go +++ b/provisionerd/runner/runner.go @@ -19,6 +19,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/provisionerd/proto" @@ -1008,6 +1009,7 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p Type: &proto.FailedJob_WorkspaceBuild_{ WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{ State: applyComplete.State, + Timings: applyComplete.Timings, }, }, } @@ -1026,6 +1028,7 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{ State: applyComplete.State, Resources: applyComplete.Resources, + Timings: applyComplete.Timings, }, }, }, nil diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 39fe557ae43e1..f47ca6c4e277b 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 -// protoc v4.23.3 +// protoc-gen-go v1.34.2 +// protoc v4.25.3 // source: provisionersdk/proto/provisioner.proto package proto @@ -9,6 +9,7 @@ package proto import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -175,6 +176,58 @@ func (WorkspaceTransition) EnumDescriptor() ([]byte, []int) { return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{2} } +type TimingState int32 + +const ( + TimingState_COMPLETED TimingState = 0 + TimingState_FAILED TimingState = 1 + TimingState_INCOMPLETE TimingState = 2 + TimingState_UNKNOWN TimingState = 3 +) + +// Enum value maps for TimingState. +var ( + TimingState_name = map[int32]string{ + 0: "COMPLETED", + 1: "FAILED", + 2: "INCOMPLETE", + 3: "UNKNOWN", + } + TimingState_value = map[string]int32{ + "COMPLETED": 0, + "FAILED": 1, + "INCOMPLETE": 2, + "UNKNOWN": 3, + } +) + +func (x TimingState) Enum() *TimingState { + p := new(TimingState) + *p = x + return p +} + +func (x TimingState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TimingState) Descriptor() protoreflect.EnumDescriptor { + return file_provisionersdk_proto_provisioner_proto_enumTypes[3].Descriptor() +} + +func (TimingState) Type() protoreflect.EnumType { + return &file_provisionersdk_proto_provisioner_proto_enumTypes[3] +} + +func (x TimingState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TimingState.Descriptor instead. +func (TimingState) EnumDescriptor() ([]byte, []int) { + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{3} +} + // Empty indicates a successful request/response. type Empty struct { state protoimpl.MessageState @@ -2053,6 +2106,7 @@ type PlanComplete struct { Resources []*Resource `protobuf:"bytes,2,rep,name=resources,proto3" json:"resources,omitempty"` Parameters []*RichParameter `protobuf:"bytes,3,rep,name=parameters,proto3" json:"parameters,omitempty"` ExternalAuthProviders []*ExternalAuthProviderResource `protobuf:"bytes,4,rep,name=external_auth_providers,json=externalAuthProviders,proto3" json:"external_auth_providers,omitempty"` + Timings []*Timing `protobuf:"bytes,6,rep,name=timings,proto3" json:"timings,omitempty"` } func (x *PlanComplete) Reset() { @@ -2115,6 +2169,13 @@ func (x *PlanComplete) GetExternalAuthProviders() []*ExternalAuthProviderResourc return nil } +func (x *PlanComplete) GetTimings() []*Timing { + if x != nil { + return x.Timings + } + return nil +} + // ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response // in the same Session. The plan data is not transmitted over the wire and is cached by the provisioner in the Session. type ApplyRequest struct { @@ -2175,6 +2236,7 @@ type ApplyComplete struct { Resources []*Resource `protobuf:"bytes,3,rep,name=resources,proto3" json:"resources,omitempty"` Parameters []*RichParameter `protobuf:"bytes,4,rep,name=parameters,proto3" json:"parameters,omitempty"` ExternalAuthProviders []*ExternalAuthProviderResource `protobuf:"bytes,5,rep,name=external_auth_providers,json=externalAuthProviders,proto3" json:"external_auth_providers,omitempty"` + Timings []*Timing `protobuf:"bytes,6,rep,name=timings,proto3" json:"timings,omitempty"` } func (x *ApplyComplete) Reset() { @@ -2244,6 +2306,100 @@ func (x *ApplyComplete) GetExternalAuthProviders() []*ExternalAuthProviderResour return nil } +func (x *ApplyComplete) GetTimings() []*Timing { + if x != nil { + return x.Timings + } + return nil +} + +type Timing struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Start *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` + End *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` + Action string `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"` // TODO: enum? try not be terraform-specific + Provider string `protobuf:"bytes,4,opt,name=provider,proto3" json:"provider,omitempty"` + Resource string `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty"` + State TimingState `protobuf:"varint,6,opt,name=state,proto3,enum=provisioner.TimingState" json:"state,omitempty"` +} + +func (x *Timing) Reset() { + *x = Timing{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Timing) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Timing) ProtoMessage() {} + +func (x *Timing) ProtoReflect() protoreflect.Message { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Timing.ProtoReflect.Descriptor instead. +func (*Timing) Descriptor() ([]byte, []int) { + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{25} +} + +func (x *Timing) GetStart() *timestamppb.Timestamp { + if x != nil { + return x.Start + } + return nil +} + +func (x *Timing) GetEnd() *timestamppb.Timestamp { + if x != nil { + return x.End + } + return nil +} + +func (x *Timing) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +func (x *Timing) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *Timing) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *Timing) GetState() TimingState { + if x != nil { + return x.State + } + return TimingState_COMPLETED +} + // CancelRequest requests that the previous request be canceled gracefully. type CancelRequest struct { state protoimpl.MessageState @@ -2254,7 +2410,7 @@ type CancelRequest struct { func (x *CancelRequest) Reset() { *x = CancelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2267,7 +2423,7 @@ func (x *CancelRequest) String() string { func (*CancelRequest) ProtoMessage() {} func (x *CancelRequest) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2280,7 +2436,7 @@ func (x *CancelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelRequest.ProtoReflect.Descriptor instead. func (*CancelRequest) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{25} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{26} } type Request struct { @@ -2301,7 +2457,7 @@ type Request struct { func (x *Request) Reset() { *x = Request{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2314,7 +2470,7 @@ func (x *Request) String() string { func (*Request) ProtoMessage() {} func (x *Request) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2327,7 +2483,7 @@ func (x *Request) ProtoReflect() protoreflect.Message { // Deprecated: Use Request.ProtoReflect.Descriptor instead. func (*Request) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{26} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{27} } func (m *Request) GetType() isRequest_Type { @@ -2423,7 +2579,7 @@ type Response struct { func (x *Response) Reset() { *x = Response{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2436,7 +2592,7 @@ func (x *Response) String() string { func (*Response) ProtoMessage() {} func (x *Response) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2449,7 +2605,7 @@ func (x *Response) ProtoReflect() protoreflect.Message { // Deprecated: Use Response.ProtoReflect.Descriptor instead. func (*Response) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{27} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{28} } func (m *Response) GetType() isResponse_Type { @@ -2531,7 +2687,7 @@ type Agent_Metadata struct { func (x *Agent_Metadata) Reset() { *x = Agent_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2544,7 +2700,7 @@ func (x *Agent_Metadata) String() string { func (*Agent_Metadata) ProtoMessage() {} func (x *Agent_Metadata) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2616,7 +2772,7 @@ type Resource_Metadata struct { func (x *Resource_Metadata) Reset() { *x = Resource_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2629,7 +2785,7 @@ func (x *Resource_Metadata) String() string { func (*Resource_Metadata) ProtoMessage() {} func (x *Resource_Metadata) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[30] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2679,359 +2835,364 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x0a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xbb, - 0x01, 0x0a, 0x10, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, - 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, - 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x1c, - 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x75, 0x0a, 0x13, - 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, - 0x63, 0x6f, 0x6e, 0x22, 0xfe, 0x04, 0x0a, 0x0d, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x07, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, - 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, - 0x0a, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x67, - 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x0d, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6e, 0x88, 0x01, 0x01, - 0x12, 0x2a, 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, - 0x61, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x0d, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x14, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x6f, 0x74, - 0x6f, 0x6e, 0x69, 0x63, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x6f, 0x74, 0x6f, 0x6e, 0x69, 0x63, 0x12, - 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, - 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, - 0x61, 0x6c, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x6d, 0x69, 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, 0x14, - 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3e, 0x0a, 0x12, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x57, 0x0a, 0x0d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0xbb, 0x01, 0x0a, 0x10, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x75, 0x0a, + 0x13, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x69, 0x63, 0x6f, 0x6e, 0x22, 0xfe, 0x04, 0x0a, 0x0d, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x6d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, + 0x63, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x29, 0x0a, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, + 0x67, 0x65, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, + 0x0d, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x2a, 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6d, 0x61, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x0d, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, + 0x14, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x6e, 0x6f, + 0x74, 0x6f, 0x6e, 0x69, 0x63, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x6e, 0x6f, 0x74, 0x6f, 0x6e, 0x69, 0x63, + 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, + 0x61, 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, + 0x72, 0x61, 0x6c, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6e, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, + 0x14, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3e, 0x0a, 0x12, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x57, 0x0a, 0x0d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x4a, + 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x37, 0x0a, 0x14, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x75, + 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x1c, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, + 0x49, 0x0a, 0x14, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xa0, 0x07, 0x0a, 0x05, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, + 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x08, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, + 0x6f, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x12, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, 0x6f, 0x74, 0x69, 0x6e, + 0x67, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x6f, 0x74, 0x64, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x74, 0x64, 0x46, 0x69, 0x6c, + 0x65, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0c, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x07, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, + 0x65, 0x6e, 0x76, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x6e, 0x76, 0x52, 0x09, 0x65, 0x78, + 0x74, 0x72, 0x61, 0x45, 0x6e, 0x76, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x1a, 0xa3, 0x01, + 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, + 0x75, 0x74, 0x68, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, 0x12, 0x6c, 0x6f, 0x67, 0x69, 0x6e, + 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x22, 0xc6, 0x01, + 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, + 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x5f, + 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, + 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x21, + 0x0a, 0x0c, 0x77, 0x65, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x65, 0x62, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, + 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x73, 0x68, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, + 0x12, 0x34, 0x0a, 0x16, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x14, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, + 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x03, 0x45, 0x6e, 0x76, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x06, 0x53, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x75, 0x6e, 0x4f, 0x6e, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, + 0x73, 0x74, 0x6f, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x4f, + 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x22, 0xcb, 0x02, 0x0a, 0x03, 0x41, 0x70, + 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, + 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, + 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, + 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, + 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, + 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x4a, 0x0a, - 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x37, 0x0a, 0x14, 0x49, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x75, 0x74, - 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x1c, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, - 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0x49, - 0x0a, 0x14, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xa0, 0x07, 0x0a, 0x05, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, - 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, - 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, - 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x12, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, 0x6f, 0x74, 0x69, 0x6e, 0x67, - 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x6f, 0x74, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x74, 0x64, 0x46, 0x69, 0x6c, 0x65, - 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0c, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x07, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x65, - 0x6e, 0x76, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x6e, 0x76, 0x52, 0x09, 0x65, 0x78, 0x74, - 0x72, 0x61, 0x45, 0x6e, 0x76, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, - 0x17, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x1a, 0xa3, 0x01, 0x0a, - 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x14, 0x0a, 0x05, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, - 0x74, 0x68, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, 0x12, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, - 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x22, 0xc6, 0x01, 0x0a, - 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x06, - 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x73, - 0x63, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x69, - 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x76, - 0x73, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, - 0x0c, 0x77, 0x65, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x65, 0x62, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x73, 0x68, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x12, - 0x34, 0x0a, 0x16, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, - 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x14, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, - 0x65, 0x6c, 0x70, 0x65, 0x72, 0x22, 0x2f, 0x0a, 0x03, 0x45, 0x6e, 0x76, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x06, 0x53, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x75, 0x6e, 0x4f, 0x6e, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, - 0x74, 0x6f, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x4f, 0x6e, - 0x53, 0x74, 0x6f, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, - 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x74, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x19, 0x0a, - 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 0x74, 0x68, 0x22, 0xcb, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, - 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, - 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, - 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, - 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, - 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, - 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, - 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xef, 0x06, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, - 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, - 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, - 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, - 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, - 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, - 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, - 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, - 0x42, 0x0a, 0x1e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x77, 0x6f, - 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, 0x18, - 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, - 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, - 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, - 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, - 0x6d, 0x65, 0x12, 0x54, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb5, 0x02, 0x0a, 0x0b, 0x50, - 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, 0x0a, - 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, - 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, - 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, - 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, - 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x22, 0xf8, 0x01, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, - 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, - 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x41, 0x0a, + 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, + 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, + 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xef, 0x06, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, + 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, + 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, + 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x52, 0x14, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x12, 0x42, 0x0a, 0x1e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, + 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x73, 0x68, + 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x64, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x64, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, + 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, + 0x64, 0x6d, 0x65, 0x12, 0x54, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x1a, 0x40, 0x0a, 0x12, 0x57, 0x6f, 0x72, + 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb5, 0x02, 0x0a, 0x0b, + 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x53, + 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, + 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x59, 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x15, 0x65, 0x78, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x72, 0x73, 0x22, 0xa7, 0x02, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, + 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, + 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x61, 0x0a, 0x17, 0x65, + 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x2d, + 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, + 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x22, 0x8f, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x22, 0xbe, 0x02, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, @@ -3048,58 +3209,80 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x73, 0x22, 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, - 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, - 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, - 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, - 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, - 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, - 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, - 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, - 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, - 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, - 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, - 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, - 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, - 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, - 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, - 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, - 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0x49, 0x0a, - 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, - 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, + 0x73, 0x22, 0xe8, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, + 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x0f, 0x0a, 0x0d, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, + 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, + 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, + 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, + 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, + 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, + 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, + 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, + 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, + 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, + 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, + 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, + 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, + 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, + 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, + 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, + 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, + 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, + 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, + 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x45, 0x0a, 0x0b, 0x54, 0x69, 0x6d, 0x69, 0x6e, + 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, + 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, + 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x10, + 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x32, 0x49, + 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, + 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -3114,88 +3297,96 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte { return file_provisionersdk_proto_provisioner_proto_rawDescData } -var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 32) -var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ +var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 33) +var file_provisionersdk_proto_provisioner_proto_goTypes = []any{ (LogLevel)(0), // 0: provisioner.LogLevel (AppSharingLevel)(0), // 1: provisioner.AppSharingLevel (WorkspaceTransition)(0), // 2: provisioner.WorkspaceTransition - (*Empty)(nil), // 3: provisioner.Empty - (*TemplateVariable)(nil), // 4: provisioner.TemplateVariable - (*RichParameterOption)(nil), // 5: provisioner.RichParameterOption - (*RichParameter)(nil), // 6: provisioner.RichParameter - (*RichParameterValue)(nil), // 7: provisioner.RichParameterValue - (*VariableValue)(nil), // 8: provisioner.VariableValue - (*Log)(nil), // 9: provisioner.Log - (*InstanceIdentityAuth)(nil), // 10: provisioner.InstanceIdentityAuth - (*ExternalAuthProviderResource)(nil), // 11: provisioner.ExternalAuthProviderResource - (*ExternalAuthProvider)(nil), // 12: provisioner.ExternalAuthProvider - (*Agent)(nil), // 13: provisioner.Agent - (*DisplayApps)(nil), // 14: provisioner.DisplayApps - (*Env)(nil), // 15: provisioner.Env - (*Script)(nil), // 16: provisioner.Script - (*App)(nil), // 17: provisioner.App - (*Healthcheck)(nil), // 18: provisioner.Healthcheck - (*Resource)(nil), // 19: provisioner.Resource - (*Metadata)(nil), // 20: provisioner.Metadata - (*Config)(nil), // 21: provisioner.Config - (*ParseRequest)(nil), // 22: provisioner.ParseRequest - (*ParseComplete)(nil), // 23: provisioner.ParseComplete - (*PlanRequest)(nil), // 24: provisioner.PlanRequest - (*PlanComplete)(nil), // 25: provisioner.PlanComplete - (*ApplyRequest)(nil), // 26: provisioner.ApplyRequest - (*ApplyComplete)(nil), // 27: provisioner.ApplyComplete - (*CancelRequest)(nil), // 28: provisioner.CancelRequest - (*Request)(nil), // 29: provisioner.Request - (*Response)(nil), // 30: provisioner.Response - (*Agent_Metadata)(nil), // 31: provisioner.Agent.Metadata - nil, // 32: provisioner.Agent.EnvEntry - (*Resource_Metadata)(nil), // 33: provisioner.Resource.Metadata - nil, // 34: provisioner.ParseComplete.WorkspaceTagsEntry + (TimingState)(0), // 3: provisioner.TimingState + (*Empty)(nil), // 4: provisioner.Empty + (*TemplateVariable)(nil), // 5: provisioner.TemplateVariable + (*RichParameterOption)(nil), // 6: provisioner.RichParameterOption + (*RichParameter)(nil), // 7: provisioner.RichParameter + (*RichParameterValue)(nil), // 8: provisioner.RichParameterValue + (*VariableValue)(nil), // 9: provisioner.VariableValue + (*Log)(nil), // 10: provisioner.Log + (*InstanceIdentityAuth)(nil), // 11: provisioner.InstanceIdentityAuth + (*ExternalAuthProviderResource)(nil), // 12: provisioner.ExternalAuthProviderResource + (*ExternalAuthProvider)(nil), // 13: provisioner.ExternalAuthProvider + (*Agent)(nil), // 14: provisioner.Agent + (*DisplayApps)(nil), // 15: provisioner.DisplayApps + (*Env)(nil), // 16: provisioner.Env + (*Script)(nil), // 17: provisioner.Script + (*App)(nil), // 18: provisioner.App + (*Healthcheck)(nil), // 19: provisioner.Healthcheck + (*Resource)(nil), // 20: provisioner.Resource + (*Metadata)(nil), // 21: provisioner.Metadata + (*Config)(nil), // 22: provisioner.Config + (*ParseRequest)(nil), // 23: provisioner.ParseRequest + (*ParseComplete)(nil), // 24: provisioner.ParseComplete + (*PlanRequest)(nil), // 25: provisioner.PlanRequest + (*PlanComplete)(nil), // 26: provisioner.PlanComplete + (*ApplyRequest)(nil), // 27: provisioner.ApplyRequest + (*ApplyComplete)(nil), // 28: provisioner.ApplyComplete + (*Timing)(nil), // 29: provisioner.Timing + (*CancelRequest)(nil), // 30: provisioner.CancelRequest + (*Request)(nil), // 31: provisioner.Request + (*Response)(nil), // 32: provisioner.Response + (*Agent_Metadata)(nil), // 33: provisioner.Agent.Metadata + nil, // 34: provisioner.Agent.EnvEntry + (*Resource_Metadata)(nil), // 35: provisioner.Resource.Metadata + nil, // 36: provisioner.ParseComplete.WorkspaceTagsEntry + (*timestamppb.Timestamp)(nil), // 37: google.protobuf.Timestamp } var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ - 5, // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption + 6, // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption 0, // 1: provisioner.Log.level:type_name -> provisioner.LogLevel - 32, // 2: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry - 17, // 3: provisioner.Agent.apps:type_name -> provisioner.App - 31, // 4: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata - 14, // 5: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps - 16, // 6: provisioner.Agent.scripts:type_name -> provisioner.Script - 15, // 7: provisioner.Agent.extra_envs:type_name -> provisioner.Env - 18, // 8: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck + 34, // 2: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry + 18, // 3: provisioner.Agent.apps:type_name -> provisioner.App + 33, // 4: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata + 15, // 5: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps + 17, // 6: provisioner.Agent.scripts:type_name -> provisioner.Script + 16, // 7: provisioner.Agent.extra_envs:type_name -> provisioner.Env + 19, // 8: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck 1, // 9: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel - 13, // 10: provisioner.Resource.agents:type_name -> provisioner.Agent - 33, // 11: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata + 14, // 10: provisioner.Resource.agents:type_name -> provisioner.Agent + 35, // 11: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata 2, // 12: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition - 4, // 13: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable - 34, // 14: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry - 20, // 15: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata - 7, // 16: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue - 8, // 17: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue - 12, // 18: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider - 19, // 19: provisioner.PlanComplete.resources:type_name -> provisioner.Resource - 6, // 20: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter - 11, // 21: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource - 20, // 22: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata - 19, // 23: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource - 6, // 24: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter - 11, // 25: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource - 21, // 26: provisioner.Request.config:type_name -> provisioner.Config - 22, // 27: provisioner.Request.parse:type_name -> provisioner.ParseRequest - 24, // 28: provisioner.Request.plan:type_name -> provisioner.PlanRequest - 26, // 29: provisioner.Request.apply:type_name -> provisioner.ApplyRequest - 28, // 30: provisioner.Request.cancel:type_name -> provisioner.CancelRequest - 9, // 31: provisioner.Response.log:type_name -> provisioner.Log - 23, // 32: provisioner.Response.parse:type_name -> provisioner.ParseComplete - 25, // 33: provisioner.Response.plan:type_name -> provisioner.PlanComplete - 27, // 34: provisioner.Response.apply:type_name -> provisioner.ApplyComplete - 29, // 35: provisioner.Provisioner.Session:input_type -> provisioner.Request - 30, // 36: provisioner.Provisioner.Session:output_type -> provisioner.Response - 36, // [36:37] is the sub-list for method output_type - 35, // [35:36] is the sub-list for method input_type - 35, // [35:35] is the sub-list for extension type_name - 35, // [35:35] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 5, // 13: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable + 36, // 14: provisioner.ParseComplete.workspace_tags:type_name -> provisioner.ParseComplete.WorkspaceTagsEntry + 21, // 15: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata + 8, // 16: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue + 9, // 17: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue + 13, // 18: provisioner.PlanRequest.external_auth_providers:type_name -> provisioner.ExternalAuthProvider + 20, // 19: provisioner.PlanComplete.resources:type_name -> provisioner.Resource + 7, // 20: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter + 12, // 21: provisioner.PlanComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource + 29, // 22: provisioner.PlanComplete.timings:type_name -> provisioner.Timing + 21, // 23: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata + 20, // 24: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource + 7, // 25: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter + 12, // 26: provisioner.ApplyComplete.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource + 29, // 27: provisioner.ApplyComplete.timings:type_name -> provisioner.Timing + 37, // 28: provisioner.Timing.start:type_name -> google.protobuf.Timestamp + 37, // 29: provisioner.Timing.end:type_name -> google.protobuf.Timestamp + 3, // 30: provisioner.Timing.state:type_name -> provisioner.TimingState + 22, // 31: provisioner.Request.config:type_name -> provisioner.Config + 23, // 32: provisioner.Request.parse:type_name -> provisioner.ParseRequest + 25, // 33: provisioner.Request.plan:type_name -> provisioner.PlanRequest + 27, // 34: provisioner.Request.apply:type_name -> provisioner.ApplyRequest + 30, // 35: provisioner.Request.cancel:type_name -> provisioner.CancelRequest + 10, // 36: provisioner.Response.log:type_name -> provisioner.Log + 24, // 37: provisioner.Response.parse:type_name -> provisioner.ParseComplete + 26, // 38: provisioner.Response.plan:type_name -> provisioner.PlanComplete + 28, // 39: provisioner.Response.apply:type_name -> provisioner.ApplyComplete + 31, // 40: provisioner.Provisioner.Session:input_type -> provisioner.Request + 32, // 41: provisioner.Provisioner.Session:output_type -> provisioner.Response + 41, // [41:42] is the sub-list for method output_type + 40, // [40:41] is the sub-list for method input_type + 40, // [40:40] is the sub-list for extension type_name + 40, // [40:40] is the sub-list for extension extendee + 0, // [0:40] is the sub-list for field type_name } func init() { file_provisionersdk_proto_provisioner_proto_init() } @@ -3204,7 +3395,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_provisionersdk_proto_provisioner_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Empty); i { case 0: return &v.state @@ -3216,7 +3407,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*TemplateVariable); i { case 0: return &v.state @@ -3228,7 +3419,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*RichParameterOption); i { case 0: return &v.state @@ -3240,7 +3431,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*RichParameter); i { case 0: return &v.state @@ -3252,7 +3443,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*RichParameterValue); i { case 0: return &v.state @@ -3264,7 +3455,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*VariableValue); i { case 0: return &v.state @@ -3276,7 +3467,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*Log); i { case 0: return &v.state @@ -3288,7 +3479,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*InstanceIdentityAuth); i { case 0: return &v.state @@ -3300,7 +3491,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[8].Exporter = func(v any, i int) any { switch v := v.(*ExternalAuthProviderResource); i { case 0: return &v.state @@ -3312,7 +3503,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[9].Exporter = func(v any, i int) any { switch v := v.(*ExternalAuthProvider); i { case 0: return &v.state @@ -3324,7 +3515,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[10].Exporter = func(v any, i int) any { switch v := v.(*Agent); i { case 0: return &v.state @@ -3336,7 +3527,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v any, i int) any { switch v := v.(*DisplayApps); i { case 0: return &v.state @@ -3348,7 +3539,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[12].Exporter = func(v any, i int) any { switch v := v.(*Env); i { case 0: return &v.state @@ -3360,7 +3551,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v any, i int) any { switch v := v.(*Script); i { case 0: return &v.state @@ -3372,7 +3563,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v any, i int) any { switch v := v.(*App); i { case 0: return &v.state @@ -3384,7 +3575,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v any, i int) any { switch v := v.(*Healthcheck); i { case 0: return &v.state @@ -3396,7 +3587,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v any, i int) any { switch v := v.(*Resource); i { case 0: return &v.state @@ -3408,7 +3599,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v any, i int) any { switch v := v.(*Metadata); i { case 0: return &v.state @@ -3420,7 +3611,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v any, i int) any { switch v := v.(*Config); i { case 0: return &v.state @@ -3432,7 +3623,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v any, i int) any { switch v := v.(*ParseRequest); i { case 0: return &v.state @@ -3444,7 +3635,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v any, i int) any { switch v := v.(*ParseComplete); i { case 0: return &v.state @@ -3456,7 +3647,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v any, i int) any { switch v := v.(*PlanRequest); i { case 0: return &v.state @@ -3468,7 +3659,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v any, i int) any { switch v := v.(*PlanComplete); i { case 0: return &v.state @@ -3480,7 +3671,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v any, i int) any { switch v := v.(*ApplyRequest); i { case 0: return &v.state @@ -3492,7 +3683,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[24].Exporter = func(v any, i int) any { switch v := v.(*ApplyComplete); i { case 0: return &v.state @@ -3504,7 +3695,19 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[25].Exporter = func(v any, i int) any { + switch v := v.(*Timing); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionersdk_proto_provisioner_proto_msgTypes[26].Exporter = func(v any, i int) any { switch v := v.(*CancelRequest); i { case 0: return &v.state @@ -3516,7 +3719,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[27].Exporter = func(v any, i int) any { switch v := v.(*Request); i { case 0: return &v.state @@ -3528,7 +3731,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[28].Exporter = func(v any, i int) any { switch v := v.(*Response); i { case 0: return &v.state @@ -3540,7 +3743,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[29].Exporter = func(v any, i int) any { switch v := v.(*Agent_Metadata); i { case 0: return &v.state @@ -3552,7 +3755,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[31].Exporter = func(v any, i int) any { switch v := v.(*Resource_Metadata); i { case 0: return &v.state @@ -3565,19 +3768,19 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } } - file_provisionersdk_proto_provisioner_proto_msgTypes[3].OneofWrappers = []interface{}{} - file_provisionersdk_proto_provisioner_proto_msgTypes[10].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[3].OneofWrappers = []any{} + file_provisionersdk_proto_provisioner_proto_msgTypes[10].OneofWrappers = []any{ (*Agent_Token)(nil), (*Agent_InstanceId)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[26].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[27].OneofWrappers = []any{ (*Request_Config)(nil), (*Request_Parse)(nil), (*Request_Plan)(nil), (*Request_Apply)(nil), (*Request_Cancel)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[27].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[28].OneofWrappers = []any{ (*Response_Log)(nil), (*Response_Parse)(nil), (*Response_Plan)(nil), @@ -3588,8 +3791,8 @@ func file_provisionersdk_proto_provisioner_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc, - NumEnums: 3, - NumMessages: 32, + NumEnums: 4, + NumMessages: 33, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 71d8bd12e8d1e..1c1336b067047 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -2,6 +2,8 @@ syntax = "proto3"; option go_package = "github.com/coder/coder/v2/provisionersdk/proto"; +import "google/protobuf/timestamp.proto"; + package provisioner; // Empty indicates a successful request/response. @@ -266,6 +268,7 @@ message PlanComplete { repeated Resource resources = 2; repeated RichParameter parameters = 3; repeated ExternalAuthProviderResource external_auth_providers = 4; + repeated Timing timings = 6; } // ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response @@ -281,6 +284,23 @@ message ApplyComplete { repeated Resource resources = 3; repeated RichParameter parameters = 4; repeated ExternalAuthProviderResource external_auth_providers = 5; + repeated Timing timings = 6; +} + +message Timing { + google.protobuf.Timestamp start = 1; + google.protobuf.Timestamp end = 2; + string action = 3; // TODO: enum? try not be terraform-specific + string provider = 4; + string resource = 5; + TimingState state = 6; +} + +enum TimingState { + COMPLETED = 0; + FAILED = 1; + INCOMPLETE = 2; + UNKNOWN = 3; } // CancelRequest requests that the previous request be canceled gracefully. diff --git a/provisionersdk/proto/provisioner_drpc.pb.go b/provisionersdk/proto/provisioner_drpc.pb.go index de310e779dcaa..c9c54002439c2 100644 --- a/provisionersdk/proto/provisioner_drpc.pb.go +++ b/provisionersdk/proto/provisioner_drpc.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-go-drpc. DO NOT EDIT. -// protoc-gen-go-drpc version: v0.0.33 +// protoc-gen-go-drpc version: (devel) // source: provisionersdk/proto/provisioner.proto package proto diff --git a/site/e2e/google/protobuf/timestampGenerated.ts b/site/e2e/google/protobuf/timestampGenerated.ts new file mode 100644 index 0000000000000..6dd4b08e96087 --- /dev/null +++ b/site/e2e/google/protobuf/timestampGenerated.ts @@ -0,0 +1,123 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; + +export const protobufPackage = "google.protobuf"; + +/** + * A Timestamp represents a point in time independent of any time zone or local + * calendar, encoded as a count of seconds and fractions of seconds at + * nanosecond resolution. The count is relative to an epoch at UTC midnight on + * January 1, 1970, in the proleptic Gregorian calendar which extends the + * Gregorian calendar backwards to year one. + * + * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap + * second table is needed for interpretation, using a [24-hour linear + * smear](https://developers.google.com/time/smear). + * + * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By + * restricting to that range, we ensure that we can convert to and from [RFC + * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. + * + * # Examples + * + * Example 1: Compute Timestamp from POSIX `time()`. + * + * Timestamp timestamp; + * timestamp.set_seconds(time(NULL)); + * timestamp.set_nanos(0); + * + * Example 2: Compute Timestamp from POSIX `gettimeofday()`. + * + * struct timeval tv; + * gettimeofday(&tv, NULL); + * + * Timestamp timestamp; + * timestamp.set_seconds(tv.tv_sec); + * timestamp.set_nanos(tv.tv_usec * 1000); + * + * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + * + * FILETIME ft; + * GetSystemTimeAsFileTime(&ft); + * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + * + * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z + * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. + * Timestamp timestamp; + * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); + * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); + * + * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + * + * long millis = System.currentTimeMillis(); + * + * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + * .setNanos((int) ((millis % 1000) * 1000000)).build(); + * + * Example 5: Compute Timestamp from Java `Instant.now()`. + * + * Instant now = Instant.now(); + * + * Timestamp timestamp = + * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + * .setNanos(now.getNano()).build(); + * + * Example 6: Compute Timestamp from current time in Python. + * + * timestamp = Timestamp() + * timestamp.GetCurrentTime() + * + * # JSON Mapping + * + * In JSON format, the Timestamp type is encoded as a string in the + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" + * where {year} is always expressed using four digits while {month}, {day}, + * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional + * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + * is required. A proto3 JSON serializer should always use UTC (as indicated by + * "Z") when printing the Timestamp type and a proto3 JSON parser should be + * able to accept both UTC and other timezones (as indicated by an offset). + * + * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past + * 01:30 UTC on January 15, 2017. + * + * In JavaScript, one can convert a Date object to this format using the + * standard + * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) + * method. In Python, a standard `datetime.datetime` object can be converted + * to this format using + * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with + * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use + * the Joda Time's [`ISODateTimeFormat.dateTime()`]( + * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() + * ) to obtain a formatter capable of generating timestamps in this format. + */ +export interface Timestamp { + /** + * Represents seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + */ + seconds: number; + /** + * Non-negative fractions of a second at nanosecond resolution. Negative + * second values with fractions must still have non-negative nanos values + * that count forward in time. Must be from 0 to 999,999,999 + * inclusive. + */ + nanos: number; +} + +export const Timestamp = { + encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.seconds !== 0) { + writer.uint32(8).int64(message.seconds); + } + if (message.nanos !== 0) { + writer.uint32(16).int32(message.nanos); + } + return writer; + }, +}; diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index fa98b21b7fd37..669102f6d52e9 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -1,6 +1,7 @@ /* eslint-disable */ import * as _m0 from "protobufjs/minimal"; import { Observable } from "rxjs"; +import { Timestamp } from "./google/protobuf/timestampGenerated"; export const protobufPackage = "provisioner"; @@ -29,6 +30,14 @@ export enum WorkspaceTransition { UNRECOGNIZED = -1, } +export enum TimingState { + COMPLETED = 0, + FAILED = 1, + INCOMPLETE = 2, + UNKNOWN = 3, + UNRECOGNIZED = -1, +} + /** Empty indicates a successful request/response. */ export interface Empty {} @@ -275,6 +284,7 @@ export interface PlanComplete { resources: Resource[]; parameters: RichParameter[]; externalAuthProviders: ExternalAuthProviderResource[]; + timings: Timing[]; } /** @@ -292,6 +302,17 @@ export interface ApplyComplete { resources: Resource[]; parameters: RichParameter[]; externalAuthProviders: ExternalAuthProviderResource[]; + timings: Timing[]; +} + +export interface Timing { + start: Date | undefined; + end: Date | undefined; + /** TODO: enum? try not be terraform-specific */ + action: string; + provider: string; + resource: string; + state: TimingState; } /** CancelRequest requests that the previous request be canceled gracefully. */ @@ -965,6 +986,9 @@ export const PlanComplete = { writer.uint32(34).fork(), ).ldelim(); } + for (const v of message.timings) { + Timing.encode(v!, writer.uint32(50).fork()).ldelim(); + } return writer; }, }; @@ -1004,6 +1028,42 @@ export const ApplyComplete = { writer.uint32(42).fork(), ).ldelim(); } + for (const v of message.timings) { + Timing.encode(v!, writer.uint32(50).fork()).ldelim(); + } + return writer; + }, +}; + +export const Timing = { + encode( + message: Timing, + writer: _m0.Writer = _m0.Writer.create(), + ): _m0.Writer { + if (message.start !== undefined) { + Timestamp.encode( + toTimestamp(message.start), + writer.uint32(10).fork(), + ).ldelim(); + } + if (message.end !== undefined) { + Timestamp.encode( + toTimestamp(message.end), + writer.uint32(18).fork(), + ).ldelim(); + } + if (message.action !== "") { + writer.uint32(26).string(message.action); + } + if (message.provider !== "") { + writer.uint32(34).string(message.provider); + } + if (message.resource !== "") { + writer.uint32(42).string(message.resource); + } + if (message.state !== 0) { + writer.uint32(48).int32(message.state); + } return writer; }, }; @@ -1077,3 +1137,9 @@ export interface Provisioner { */ Session(request: Observable): Observable; } + +function toTimestamp(date: Date): Timestamp { + const seconds = date.getTime() / 1_000; + const nanos = (date.getTime() % 1_000) * 1_000_000; + return { seconds, nanos }; +} From 45b7fb4a41d3b0d2b0c54b9c214ab17864817e46 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Tue, 13 Aug 2024 15:38:41 +0200 Subject: [PATCH 02/22] More hacking, now including a job-wide view of timings Signed-off-by: Danny Kopping --- coderd/database/dump.sql | 57 ++++++- coderd/database/foreign_key_constraint.go | 2 +- .../000242_provisioner_job_timings.down.sql | 6 +- .../000242_provisioner_job_timings.up.sql | 38 ++++- coderd/database/models.go | 89 ++++++++++- coderd/database/queries.sql.go | 32 ++-- coderd/database/queries/provisionerjobs.sql | 5 +- coderd/database/sqlc.yaml | 3 + .../provisionerdserver/provisionerdserver.go | 9 +- provisioner/terraform/executor.go | 65 +++++--- provisioner/terraform/provision.go | 22 +++ provisioner/terraform/timings.go | 63 +++----- provisionerd/runner/runner.go | 4 + provisionersdk/proto/provisioner.pb.go | 147 ++++++++++-------- provisionersdk/proto/provisioner.proto | 7 +- site/e2e/provisionerGenerated.ts | 13 +- 16 files changed, 391 insertions(+), 171 deletions(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 780ae5bd9b441..939c24df07ce8 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -136,6 +136,12 @@ CREATE TYPE provisioner_job_status AS ENUM ( COMMENT ON TYPE provisioner_job_status IS 'Computed status of a provisioner job. Jobs could be stuck in a hung state, these states do not guarantee any transition to another state.'; +CREATE TYPE provisioner_job_timing_stage AS ENUM ( + 'init', + 'plan', + 'apply' +); + CREATE TYPE provisioner_job_type AS ENUM ( 'template_version_import', 'workspace_build', @@ -828,11 +834,27 @@ CREATE SEQUENCE provisioner_job_logs_id_seq ALTER SEQUENCE provisioner_job_logs_id_seq OWNED BY provisioner_job_logs.id; +CREATE VIEW provisioner_job_stats AS +SELECT + NULL::uuid AS id, + NULL::provisioner_job_status AS job_status, + NULL::uuid AS worker_id, + NULL::text AS error, + NULL::text AS error_code, + NULL::timestamp with time zone AS updated_at, + NULL::double precision AS queued_secs, + NULL::double precision AS completion_secs, + NULL::double precision AS canceled_secs, + NULL::double precision AS init_secs, + NULL::double precision AS plan_secs, + NULL::double precision AS apply_secs; + CREATE TABLE provisioner_job_timings ( - provisioner_job_id uuid NOT NULL, + job_id uuid NOT NULL, started_at timestamp with time zone NOT NULL, ended_at timestamp with time zone NOT NULL, - context text NOT NULL, + stage provisioner_job_timing_stage NOT NULL, + source text NOT NULL, action text NOT NULL, resource text NOT NULL ); @@ -1901,6 +1923,35 @@ CREATE INDEX workspace_resources_job_id_idx ON workspace_resources USING btree ( CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false); +CREATE OR REPLACE VIEW provisioner_job_stats AS + SELECT pj.id, + pj.job_status, + pj.worker_id, + pj.error, + pj.error_code, + pj.updated_at, + GREATEST(date_part('epoch'::text, (pj.started_at - pj.created_at)), (0)::double precision) AS queued_secs, + GREATEST(date_part('epoch'::text, (pj.completed_at - pj.started_at)), (0)::double precision) AS completion_secs, + GREATEST(date_part('epoch'::text, (pj.canceled_at - pj.started_at)), (0)::double precision) AS canceled_secs, + GREATEST(max( + CASE + WHEN (pjt.stage = 'init'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) + ELSE NULL::double precision + END), (0)::double precision) AS init_secs, + GREATEST(max( + CASE + WHEN (pjt.stage = 'plan'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) + ELSE NULL::double precision + END), (0)::double precision) AS plan_secs, + GREATEST(max( + CASE + WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) + ELSE NULL::double precision + END), (0)::double precision) AS apply_secs + FROM (provisioner_jobs pj + LEFT JOIN provisioner_job_timings pjt ON ((pjt.job_id = pj.id))) + GROUP BY pj.id; + CREATE TRIGGER inhibit_enqueue_if_disabled BEFORE INSERT ON notification_messages FOR EACH ROW EXECUTE FUNCTION inhibit_enqueue_if_disabled(); CREATE TRIGGER remove_organization_member_custom_role BEFORE DELETE ON custom_roles FOR EACH ROW EXECUTE FUNCTION remove_organization_member_role(); @@ -1997,7 +2048,7 @@ ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ALTER TABLE ONLY provisioner_job_timings - ADD CONSTRAINT provisioner_job_timings_provisioner_job_id_fkey FOREIGN KEY (provisioner_job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; + ADD CONSTRAINT provisioner_job_timings_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index c7843b9991231..0c578255f091c 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -29,7 +29,7 @@ const ( ForeignKeyParameterSchemasJobID ForeignKeyConstraint = "parameter_schemas_job_id_fkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ForeignKeyProvisionerDaemonsOrganizationID ForeignKeyConstraint = "provisioner_daemons_organization_id_fkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyProvisionerJobLogsJobID ForeignKeyConstraint = "provisioner_job_logs_job_id_fkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; - ForeignKeyProvisionerJobTimingsProvisionerJobID ForeignKeyConstraint = "provisioner_job_timings_provisioner_job_id_fkey" // ALTER TABLE ONLY provisioner_job_timings ADD CONSTRAINT provisioner_job_timings_provisioner_job_id_fkey FOREIGN KEY (provisioner_job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; + ForeignKeyProvisionerJobTimingsJobID ForeignKeyConstraint = "provisioner_job_timings_job_id_fkey" // ALTER TABLE ONLY provisioner_job_timings ADD CONSTRAINT provisioner_job_timings_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE; ForeignKeyProvisionerJobsOrganizationID ForeignKeyConstraint = "provisioner_jobs_organization_id_fkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyProvisionerKeysOrganizationID ForeignKeyConstraint = "provisioner_keys_organization_id_fkey" // ALTER TABLE ONLY provisioner_keys ADD CONSTRAINT provisioner_keys_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE; ForeignKeyTailnetAgentsCoordinatorID ForeignKeyConstraint = "tailnet_agents_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE; diff --git a/coderd/database/migrations/000242_provisioner_job_timings.down.sql b/coderd/database/migrations/000242_provisioner_job_timings.down.sql index af3b9497d9595..ab6caab5f60c7 100644 --- a/coderd/database/migrations/000242_provisioner_job_timings.down.sql +++ b/coderd/database/migrations/000242_provisioner_job_timings.down.sql @@ -1 +1,5 @@ --- TODO \ No newline at end of file +DROP VIEW IF EXISTS provisioner_job_stats; + +DROP TYPE IF EXISTS provisioner_job_timing_stage CASCADE; + +DROP TABLE IF EXISTS provisioner_job_timings; diff --git a/coderd/database/migrations/000242_provisioner_job_timings.up.sql b/coderd/database/migrations/000242_provisioner_job_timings.up.sql index 8ee97620c355a..3f540ab005b4f 100644 --- a/coderd/database/migrations/000242_provisioner_job_timings.up.sql +++ b/coderd/database/migrations/000242_provisioner_job_timings.up.sql @@ -1,9 +1,33 @@ +CREATE TYPE provisioner_job_timing_stage AS ENUM ( + 'init', + 'plan', + 'apply' + ); + CREATE TABLE provisioner_job_timings ( - provisioner_job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE, - started_at timestamp with time zone not null, - ended_at timestamp with time zone not null, - context text not null, -- TODO: enum? - action text not null, -- TODO: enum? - resource text not null -- TODO: enum? -); \ No newline at end of file + job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE, + started_at timestamp with time zone not null, + ended_at timestamp with time zone not null, + stage provisioner_job_timing_stage not null, + source text not null, + action text not null, + resource text not null +); + +CREATE VIEW provisioner_job_stats AS +SELECT pj.id, + pj.job_status, + pj.worker_id, + pj.error, + pj.error_code, + pj.updated_at, + GREATEST(EXTRACT(EPOCH FROM (pj.started_at - pj.created_at)), 0) AS queued_secs, + GREATEST(EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at)), 0) AS completion_secs, + GREATEST(EXTRACT(EPOCH FROM (pj.canceled_at - pj.started_at)), 0) AS canceled_secs, + GREATEST(MAX(CASE WHEN pjt.stage = 'init' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS init_secs, + GREATEST(MAX(CASE WHEN pjt.stage = 'plan' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS plan_secs, + GREATEST(MAX(CASE WHEN pjt.stage = 'apply' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS apply_secs +FROM provisioner_jobs pj + LEFT JOIN provisioner_job_timings pjt ON pjt.job_id = pj.id +GROUP BY pj.id; diff --git a/coderd/database/models.go b/coderd/database/models.go index a433d435bece4..902d7371a13f3 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1216,6 +1216,67 @@ func AllProvisionerJobStatusValues() []ProvisionerJobStatus { } } +type ProvisionerJobTimingStage string + +const ( + ProvisionerJobTimingStageInit ProvisionerJobTimingStage = "init" + ProvisionerJobTimingStagePlan ProvisionerJobTimingStage = "plan" + ProvisionerJobTimingStageApply ProvisionerJobTimingStage = "apply" +) + +func (e *ProvisionerJobTimingStage) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = ProvisionerJobTimingStage(s) + case string: + *e = ProvisionerJobTimingStage(s) + default: + return fmt.Errorf("unsupported scan type for ProvisionerJobTimingStage: %T", src) + } + return nil +} + +type NullProvisionerJobTimingStage struct { + ProvisionerJobTimingStage ProvisionerJobTimingStage `json:"provisioner_job_timing_stage"` + Valid bool `json:"valid"` // Valid is true if ProvisionerJobTimingStage is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullProvisionerJobTimingStage) Scan(value interface{}) error { + if value == nil { + ns.ProvisionerJobTimingStage, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.ProvisionerJobTimingStage.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullProvisionerJobTimingStage) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.ProvisionerJobTimingStage), nil +} + +func (e ProvisionerJobTimingStage) Valid() bool { + switch e { + case ProvisionerJobTimingStageInit, + ProvisionerJobTimingStagePlan, + ProvisionerJobTimingStageApply: + return true + } + return false +} + +func AllProvisionerJobTimingStageValues() []ProvisionerJobTimingStage { + return []ProvisionerJobTimingStage{ + ProvisionerJobTimingStageInit, + ProvisionerJobTimingStagePlan, + ProvisionerJobTimingStageApply, + } +} + type ProvisionerJobType string const ( @@ -2280,13 +2341,29 @@ type ProvisionerJobLog struct { ID int64 `db:"id" json:"id"` } +type ProvisionerJobStat struct { + ID uuid.UUID `db:"id" json:"id"` + JobStatus ProvisionerJobStatus `db:"job_status" json:"job_status"` + WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"` + Error sql.NullString `db:"error" json:"error"` + ErrorCode sql.NullString `db:"error_code" json:"error_code"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + QueuedSecs float64 `db:"queued_secs" json:"queued_secs"` + CompletionSecs float64 `db:"completion_secs" json:"completion_secs"` + CanceledSecs float64 `db:"canceled_secs" json:"canceled_secs"` + InitSecs float64 `db:"init_secs" json:"init_secs"` + PlanSecs float64 `db:"plan_secs" json:"plan_secs"` + ApplySecs float64 `db:"apply_secs" json:"apply_secs"` +} + type ProvisionerJobTiming struct { - ProvisionerJobID uuid.UUID `db:"provisioner_job_id" json:"provisioner_job_id"` - StartedAt time.Time `db:"started_at" json:"started_at"` - EndedAt time.Time `db:"ended_at" json:"ended_at"` - Context string `db:"context" json:"context"` - Action string `db:"action" json:"action"` - Resource string `db:"resource" json:"resource"` + JobID uuid.UUID `db:"job_id" json:"job_id"` + StartedAt time.Time `db:"started_at" json:"started_at"` + EndedAt time.Time `db:"ended_at" json:"ended_at"` + Stage ProvisionerJobTimingStage `db:"stage" json:"stage"` + Source string `db:"source" json:"source"` + Action string `db:"action" json:"action"` + Resource string `db:"resource" json:"resource"` } type ProvisionerKey struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ca88a9373412d..1bc875022d845 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5605,24 +5605,26 @@ func (q *sqlQuerier) InsertProvisionerJob(ctx context.Context, arg InsertProvisi } const insertProvisionerJobTimings = `-- name: InsertProvisionerJobTimings :many -INSERT INTO provisioner_job_timings (provisioner_job_id, started_at, ended_at, context, action, resource) +INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource) SELECT $1::uuid AS provisioner_job_id, unnest($2::timestamptz[]) AS started_at, unnest($3::timestamptz[]) AS ended_at, - unnest($4::text[]) AS context, - unnest($5::text[]) AS action, - unnest($6::text[]) AS resource -RETURNING provisioner_job_id, started_at, ended_at, context, action, resource + unnest($4::provisioner_job_timing_stage[]) AS context, + unnest($5::text[]) AS context, + unnest($6::text[]) AS action, + unnest($7::text[]) AS resource +RETURNING job_id, started_at, ended_at, stage, source, action, resource ` type InsertProvisionerJobTimingsParams struct { - JobID uuid.UUID `db:"job_id" json:"job_id"` - StartedAt []time.Time `db:"started_at" json:"started_at"` - EndedAt []time.Time `db:"ended_at" json:"ended_at"` - Context []string `db:"context" json:"context"` - Action []string `db:"action" json:"action"` - Resource []string `db:"resource" json:"resource"` + JobID uuid.UUID `db:"job_id" json:"job_id"` + StartedAt []time.Time `db:"started_at" json:"started_at"` + EndedAt []time.Time `db:"ended_at" json:"ended_at"` + Stage []ProvisionerJobTimingStage `db:"stage" json:"stage"` + Source []string `db:"source" json:"source"` + Action []string `db:"action" json:"action"` + Resource []string `db:"resource" json:"resource"` } func (q *sqlQuerier) InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error) { @@ -5630,7 +5632,8 @@ func (q *sqlQuerier) InsertProvisionerJobTimings(ctx context.Context, arg Insert arg.JobID, pq.Array(arg.StartedAt), pq.Array(arg.EndedAt), - pq.Array(arg.Context), + pq.Array(arg.Stage), + pq.Array(arg.Source), pq.Array(arg.Action), pq.Array(arg.Resource), ) @@ -5642,10 +5645,11 @@ func (q *sqlQuerier) InsertProvisionerJobTimings(ctx context.Context, arg Insert for rows.Next() { var i ProvisionerJobTiming if err := rows.Scan( - &i.ProvisionerJobID, + &i.JobID, &i.StartedAt, &i.EndedAt, - &i.Context, + &i.Stage, + &i.Source, &i.Action, &i.Resource, ); err != nil { diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index b02b19c7e2fee..786c5766dbff6 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -146,12 +146,13 @@ WHERE AND completed_at IS NULL; -- name: InsertProvisionerJobTimings :many -INSERT INTO provisioner_job_timings (provisioner_job_id, started_at, ended_at, context, action, resource) +INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource) SELECT @job_id::uuid AS provisioner_job_id, unnest(@started_at::timestamptz[]) AS started_at, unnest(@ended_at::timestamptz[]) AS ended_at, - unnest(@context::text[]) AS context, + unnest(@stage::provisioner_job_timing_stage[]) AS context, + unnest(@source::text[]) AS context, unnest(@action::text[]) AS action, unnest(@resource::text[]) AS resource RETURNING *; \ No newline at end of file diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index 9eeecd6b21d00..7ef860e0b36ce 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -73,6 +73,9 @@ sql: - column: "notification_messages.payload" go_type: type: "[]byte" + - column: "provisioner_job_stats.*_secs" + go_type: + type: "float64" rename: group_member: GroupMemberTable group_members_expanded: GroupMember diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 4ec1cf8a3bc90..80e9f329c1979 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1454,7 +1454,14 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) end = t.End.AsTime() } - params.Context = append(params.Context, t.Provider) + var stg database.ProvisionerJobTimingStage + if err := stg.Scan(t.Stage); err != nil { + s.Logger.Warn(ctx, "failed to parse timings stage, skipping", slog.F("value", t.Stage)) + continue + } + + params.Stage = append(params.Stage, stg) + params.Source = append(params.Source, t.Source) params.Resource = append(params.Resource, t.Resource) params.Action = append(params.Action, t.Action) params.StartedAt = append(params.StartedAt, start) diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index c97b096d6563d..80aa3ad95931a 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -13,6 +13,7 @@ import ( "runtime" "strings" "sync" + "time" "cdr.dev/slog" "github.com/hashicorp/go-version" @@ -20,6 +21,7 @@ import ( "go.opentelemetry.io/otel/attribute" "golang.org/x/xerrors" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/provisionersdk/proto" ) @@ -252,8 +254,8 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l args = append(args, "-var", variable) } - timingsAgg := newTimingsAggregator() - outWriter, doneOut := provisionLogWriter(logr, timingsAgg) + timingsAgg := newTimingsAggregator(database.ProvisionerJobTimingStagePlan) + outWriter, doneOut := e.provisionLogWriter(logr, timingsAgg) errWriter, doneErr := logWriter(logr, proto.LogLevel_ERROR) defer func() { _ = outWriter.Close() @@ -271,16 +273,11 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l return nil, err } - timings, err := timingsAgg.aggregate() - if err != nil { - e.logger.Warn(ctx, "failed to aggregate timings", slog.Error(err)) - } - return &proto.PlanComplete{ Parameters: state.Parameters, Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, - Timings: timings, + Timings: timingsAgg.aggregate(), }, nil } @@ -407,8 +404,8 @@ func (e *executor) apply( getPlanFilePath(e.workdir), } - timingsAgg := newTimingsAggregator() - outWriter, doneOut := provisionLogWriter(logr, timingsAgg) + timingsAgg := newTimingsAggregator(database.ProvisionerJobTimingStageApply) + outWriter, doneOut := e.provisionLogWriter(logr, timingsAgg) errWriter, doneErr := logWriter(logr, proto.LogLevel_ERROR) defer func() { _ = outWriter.Close() @@ -431,17 +428,12 @@ func (e *executor) apply( return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err) } - timings, err := timingsAgg.aggregate() - if err != nil { - e.logger.Warn(ctx, "failed to aggregate timings", slog.Error(err)) - } - return &proto.ApplyComplete{ Parameters: state.Parameters, Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, State: stateContent, - Timings: timings, + Timings: timingsAgg.aggregate(), }, nil } @@ -555,15 +547,15 @@ func readAndLog(sink logSink, r io.Reader, done chan<- any, level proto.LogLevel // provisionLogWriter creates a WriteCloser that will log each JSON formatted terraform log. The WriteCloser must be // closed by the caller to end logging, after which the returned channel will be closed to indicate that logging of the // written data has finished. Failure to close the WriteCloser will leak a goroutine. -func provisionLogWriter(sink logSink, timings *timingsAggregator) (io.WriteCloser, <-chan any) { +func (e *executor) provisionLogWriter(sink logSink, timings *timingsAggregator) (io.WriteCloser, <-chan any) { r, w := io.Pipe() done := make(chan any) - go provisionReadAndLog(sink, r, timings, done) + go e.provisionReadAndLog(sink, r, timings, done) return w, done } -func provisionReadAndLog(sink logSink, r io.Reader, timings *timingsAggregator, done chan<- any) { +func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, timings *timingsAggregator, done chan<- any) { defer close(done) scanner := bufio.NewScanner(r) for scanner.Scan() { @@ -596,7 +588,14 @@ func provisionReadAndLog(sink logSink, r io.Reader, timings *timingsAggregator, logLevel := convertTerraformLogLevel(log.Level, sink) sink.ProvisionLog(logLevel, log.Message) - timings.ingest(log) + ts, te, err := extractTimingsEntry(log) + if err != nil { + e.logger.Debug(context.Background(), "failed to extract timings entry from log line", + slog.F("line", log.Message), slog.Error(err)) + } else { + // Only ingest valid timings. + timings.ingest(ts, te) + } // If the diagnostic is provided, let's provide a bit more info! if log.Diagnostic == nil { @@ -609,6 +608,32 @@ func provisionReadAndLog(sink logSink, r io.Reader, timings *timingsAggregator, } } +func extractTimingsEntry(log terraformProvisionLog) (time.Time, *timingsEntry, error) { + // Input is not well-formed, bail out. + if log.Type == "" { + return time.Time{}, nil, xerrors.Errorf("invalid type: %q", log.Type) + } + + typ := logType(log.Type) + if !typ.Valid() { + return time.Time{}, nil, xerrors.Errorf("invalid type: %q", log.Type) + } + + ts, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", log.Timestamp) + if err != nil { + // TODO: log + ts = time.Now() + } + ts = ts.UTC() + + return ts, &timingsEntry{ + kind: typ, + action: log.Hook.Action, + provider: log.Hook.Resource.Provider, + resource: log.Hook.Resource.Addr, + }, nil +} + func convertTerraformLogLevel(logLevel string, sink logSink) proto.LogLevel { switch strings.ToLower(logLevel) { case "trace": diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index cd28f93854c0f..d6b6a403f2968 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -14,6 +14,8 @@ import ( "cdr.dev/slog" "github.com/coder/terraform-provider-coder/provider" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk/proto" @@ -101,11 +103,20 @@ func (s *server) Plan( } s.logger.Debug(ctx, "running initialization") + + timingsAgg := newTimingsAggregator(database.ProvisionerJobTimingStageInit) + timingsAgg.ingest(createInitTimingsEvent(initStart)) + err = e.init(ctx, killCtx, sess) if err != nil { + timingsAgg.ingest(createInitTimingsEvent(initErrored)) + s.logger.Debug(ctx, "init failed", slog.Error(err)) return provisionersdk.PlanErrorf("initialize terraform: %s", err) } + + timingsAgg.ingest(createInitTimingsEvent(initComplete)) + s.logger.Debug(ctx, "ran initialization") env, err := provisionEnv(sess.Config, request.Metadata, request.RichParameterValues, request.ExternalAuthProviders) @@ -125,9 +136,20 @@ func (s *server) Plan( if err != nil { return provisionersdk.PlanErrorf(err.Error()) } + + resp.Timings = append(resp.Timings, timingsAgg.aggregate()...) return resp } +func createInitTimingsEvent(event logType) (time.Time, *timingsEntry) { + return dbtime.Now(), &timingsEntry{ + kind: event, + action: "initialize terraform", + provider: "terraform", + resource: "state file", + } +} + func (s *server) Apply( sess *provisionersdk.Session, request *proto.ApplyRequest, canceledOrComplete <-chan struct{}, ) *proto.ApplyComplete { diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index 0dcc0bc02967e..478928db98350 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -8,6 +8,7 @@ import ( "github.com/cespare/xxhash" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/provisionersdk/proto" ) @@ -26,59 +27,42 @@ const ( provisionErrored logType = "provision_errored" refreshStart logType = "refresh_start" refreshComplete logType = "refresh_complete" + initStart logType = "init_start" + initComplete logType = "init_complete" + initErrored logType = "init_errored" ) type timingsAggregator struct { - stateLookup map[uint64]*entry + stage database.ProvisionerJobTimingStage + stateLookup map[uint64]*timingsEntry } -type entry struct { +type timingsEntry struct { kind logType start, end time.Time + stage database.ProvisionerJobTimingStage action, provider, resource string state proto.TimingState } -func newTimingsAggregator() *timingsAggregator { +func newTimingsAggregator(stage database.ProvisionerJobTimingStage) *timingsAggregator { return &timingsAggregator{ - stateLookup: make(map[uint64]*entry), + stage: stage, + stateLookup: make(map[uint64]*timingsEntry), } } -func (t *timingsAggregator) ingest(log terraformProvisionLog) { - // Input is not well-formed, bail out. - if log.Type == "" { - return - } - - typ := logType(log.Type) - if !typ.Valid() { - // TODO: log - return - } - - ts, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", log.Timestamp) - if err != nil { - // TODO: log - ts = time.Now() - } - ts = ts.UTC() - - e := &entry{ - kind: typ, - action: log.Hook.Action, - provider: log.Hook.Resource.Provider, - resource: log.Hook.Resource.Addr, - } +func (t *timingsAggregator) ingest(ts time.Time, e *timingsEntry) { + e.stage = t.stage - switch typ { - case applyStart, provisionStart, refreshStart: + switch e.kind { + case applyStart, provisionStart, refreshStart, initStart: e.start = ts e.state = proto.TimingState_INCOMPLETE - case applyComplete, provisionComplete, refreshComplete: + case applyComplete, provisionComplete, refreshComplete, initComplete: e.end = ts e.state = proto.TimingState_COMPLETED - case applyErrored, provisionErrored: + case applyErrored, provisionErrored, initErrored: e.end = ts e.state = proto.TimingState_FAILED case applyProgress, provisionProgress: @@ -89,7 +73,7 @@ func (t *timingsAggregator) ingest(log terraformProvisionLog) { t.stateLookup[e.hashByState(e.state)] = e } -func (t *timingsAggregator) aggregate() ([]*proto.Timing, error) { +func (t *timingsAggregator) aggregate() []*proto.Timing { out := make([]*proto.Timing, 0, len(t.stateLookup)) for _, e := range t.stateLookup { @@ -109,7 +93,7 @@ func (t *timingsAggregator) aggregate() ([]*proto.Timing, error) { out = append(out, e.toProto()) } - return out, nil + return out } func (l logType) Valid() bool { @@ -127,19 +111,20 @@ func (l logType) Valid() bool { }, l) } -// hashState computes a hash based on an entry's unique properties and state. +// hashState computes a hash based on a timingsEntry's unique properties and state. // The combination of resource and provider names MUST be unique across entries. -func (e *entry) hashByState(state proto.TimingState) uint64 { +func (e *timingsEntry) hashByState(state proto.TimingState) uint64 { id := fmt.Sprintf("%s:%s:%s", state.String(), e.resource, e.provider) return xxhash.Sum64String(id) } -func (e *entry) toProto() *proto.Timing { +func (e *timingsEntry) toProto() *proto.Timing { return &proto.Timing{ Start: timestamppb.New(e.start), End: timestamppb.New(e.end), Action: e.action, - Provider: e.provider, + Stage: string(e.stage), + Source: e.provider, Resource: e.resource, State: e.state, } diff --git a/provisionerd/runner/runner.go b/provisionerd/runner/runner.go index afa31615bba31..7ec959bde9a46 100644 --- a/provisionerd/runner/runner.go +++ b/provisionerd/runner/runner.go @@ -997,6 +997,10 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p if applyComplete == nil { return nil, r.failedWorkspaceBuildf("invalid message type %T received from provisioner", resp.Type) } + + // Prepend the plan timings (since they occurred first). + applyComplete.Timings = append(planComplete.Timings, applyComplete.Timings...) + if applyComplete.Error != "" { r.logger.Warn(context.Background(), "apply failed; updating state", slog.F("error", applyComplete.Error), diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index f47ca6c4e277b..659809dcf14fe 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -2320,10 +2320,11 @@ type Timing struct { Start *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` End *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` - Action string `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"` // TODO: enum? try not be terraform-specific - Provider string `protobuf:"bytes,4,opt,name=provider,proto3" json:"provider,omitempty"` + Action string `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"` + Source string `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` Resource string `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty"` - State TimingState `protobuf:"varint,6,opt,name=state,proto3,enum=provisioner.TimingState" json:"state,omitempty"` + Stage string `protobuf:"bytes,6,opt,name=stage,proto3" json:"stage,omitempty"` + State TimingState `protobuf:"varint,7,opt,name=state,proto3,enum=provisioner.TimingState" json:"state,omitempty"` } func (x *Timing) Reset() { @@ -2379,9 +2380,9 @@ func (x *Timing) GetAction() string { return "" } -func (x *Timing) GetProvider() string { +func (x *Timing) GetSource() string { if x != nil { - return x.Provider + return x.Source } return "" } @@ -2393,6 +2394,13 @@ func (x *Timing) GetResource() string { return "" } +func (x *Timing) GetStage() string { + if x != nil { + return x.Stage + } + return "" +} + func (x *Timing) GetState() TimingState { if x != nil { return x.State @@ -3212,7 +3220,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x72, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, - 0x73, 0x22, 0xe8, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, + 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x06, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, @@ -3220,69 +3228,70 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x0f, 0x0a, 0x0d, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, - 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, - 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, - 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, - 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, - 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 0x0a, - 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, - 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, - 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, - 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, - 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, - 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, - 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, - 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, - 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, - 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, - 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, - 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, - 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, - 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x45, 0x0a, 0x0b, 0x54, 0x69, 0x6d, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, - 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, - 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x10, - 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x32, 0x49, - 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, - 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x2e, + 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x0f, + 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, + 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, + 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, + 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, + 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, + 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, + 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, + 0x67, 0x12, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, + 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, + 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, + 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, + 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, + 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, + 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, + 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, + 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, + 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x45, 0x0a, 0x0b, 0x54, 0x69, 0x6d, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, + 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, + 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, + 0x32, 0x49, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, + 0x3a, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 1c1336b067047..0a28bb22a1755 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -290,10 +290,11 @@ message ApplyComplete { message Timing { google.protobuf.Timestamp start = 1; google.protobuf.Timestamp end = 2; - string action = 3; // TODO: enum? try not be terraform-specific - string provider = 4; + string action = 3; + string source = 4; string resource = 5; - TimingState state = 6; + string stage = 6; + TimingState state = 7; } enum TimingState { diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index 669102f6d52e9..6e41c285b5ddc 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -308,10 +308,10 @@ export interface ApplyComplete { export interface Timing { start: Date | undefined; end: Date | undefined; - /** TODO: enum? try not be terraform-specific */ action: string; - provider: string; + source: string; resource: string; + stage: string; state: TimingState; } @@ -1055,14 +1055,17 @@ export const Timing = { if (message.action !== "") { writer.uint32(26).string(message.action); } - if (message.provider !== "") { - writer.uint32(34).string(message.provider); + if (message.source !== "") { + writer.uint32(34).string(message.source); } if (message.resource !== "") { writer.uint32(42).string(message.resource); } + if (message.stage !== "") { + writer.uint32(50).string(message.stage); + } if (message.state !== 0) { - writer.uint32(48).int32(message.state); + writer.uint32(56).int32(message.state); } return writer; }, From 38c41974c9e79867cf33331ac8fcdf865d25aa99 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Tue, 13 Aug 2024 16:29:12 +0200 Subject: [PATCH 03/22] API Signed-off-by: Danny Kopping --- coderd/apidoc/docs.go | 82 +++++++++++++++++++ coderd/apidoc/swagger.json | 78 ++++++++++++++++++ coderd/coderd.go | 1 + coderd/database/dbauthz/dbauthz.go | 9 ++ coderd/database/dbmem/dbmem.go | 9 ++ coderd/database/dbmetrics/dbmetrics.go | 7 ++ coderd/database/dbmock/dbmock.go | 15 ++++ coderd/database/dump.sql | 11 ++- .../000242_provisioner_job_timings.up.sql | 8 +- coderd/database/models.go | 3 +- coderd/database/querier.go | 2 + coderd/database/queries.sql.go | 33 ++++++++ coderd/database/queries/provisionerjobs.sql | 8 +- coderd/workspacebuilds.go | 52 ++++++++++++ codersdk/workspacebuilds.go | 17 ++++ docs/reference/api/builds.md | 49 +++++++++++ docs/reference/api/schemas.md | 38 +++++++++ site/src/api/typesGenerated.ts | 17 ++++ 18 files changed, 430 insertions(+), 9 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 86c79d772abf3..060b9b402637f 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7150,6 +7150,40 @@ const docTemplate = `{ } } }, + "/workspacebuilds/{workspacebuild}/stats": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Builds" + ], + "summary": "Get stats for workspace build", + "operationId": "get-stats-for-workspace-build", + "parameters": [ + { + "type": "string", + "description": "Workspace build ID", + "name": "workspacebuild", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.WorkspaceBuildStats" + } + } + } + } + }, "/workspaceproxies": { "get": { "security": [ @@ -14083,6 +14117,54 @@ const docTemplate = `{ } } }, + "codersdk.WorkspaceBuildStats": { + "type": "object", + "properties": { + "apply_secs": { + "type": "number" + }, + "canceled_secs": { + "type": "number" + }, + "completion_secs": { + "type": "number" + }, + "error": { + "type": "string" + }, + "error_code": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "init_secs": { + "type": "number" + }, + "job_status": { + "$ref": "#/definitions/codersdk.ProvisionerJobStatus" + }, + "plan_secs": { + "type": "number" + }, + "queued_secs": { + "type": "number" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "worker_id": { + "type": "string", + "format": "uuid" + }, + "workspace_id": { + "type": "string", + "format": "uuid" + } + } + }, "codersdk.WorkspaceConnectionLatencyMS": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 61967f9d5096c..40fe662f1dba7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6322,6 +6322,36 @@ } } }, + "/workspacebuilds/{workspacebuild}/stats": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Builds"], + "summary": "Get stats for workspace build", + "operationId": "get-stats-for-workspace-build", + "parameters": [ + { + "type": "string", + "description": "Workspace build ID", + "name": "workspacebuild", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.WorkspaceBuildStats" + } + } + } + } + }, "/workspaceproxies": { "get": { "security": [ @@ -12812,6 +12842,54 @@ } } }, + "codersdk.WorkspaceBuildStats": { + "type": "object", + "properties": { + "apply_secs": { + "type": "number" + }, + "canceled_secs": { + "type": "number" + }, + "completion_secs": { + "type": "number" + }, + "error": { + "type": "string" + }, + "error_code": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "init_secs": { + "type": "number" + }, + "job_status": { + "$ref": "#/definitions/codersdk.ProvisionerJobStatus" + }, + "plan_secs": { + "type": "number" + }, + "queued_secs": { + "type": "number" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "worker_id": { + "type": "string", + "format": "uuid" + }, + "workspace_id": { + "type": "string", + "format": "uuid" + } + } + }, "codersdk.WorkspaceConnectionLatencyMS": { "type": "object", "properties": { diff --git a/coderd/coderd.go b/coderd/coderd.go index a4b36e8fec698..fac3219406941 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1153,6 +1153,7 @@ func New(options *Options) *API { r.Get("/parameters", api.workspaceBuildParameters) r.Get("/resources", api.workspaceBuildResourcesDeprecated) r.Get("/state", api.workspaceBuildState) + r.Get("/stats", api.workspaceBuildStats) }) r.Route("/authcheck", func(r chi.Router) { r.Use(apiKeyMiddleware) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 23a50799c122b..42883b919cf3d 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1775,6 +1775,15 @@ func (q *querier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (data return job, nil } +func (q *querier) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { + // Use the workspace ID to authorize the request. + if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspace.WithID(arg.WorkspaceID)); err != nil { + return database.ProvisionerJobStat{}, err + } + + return q.db.GetProvisionerJobStatsByWorkspace(ctx, arg) +} + // TODO: we need to add a provisioner job resource func (q *querier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { // if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 30e12f724c496..7e7516f080d7b 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3172,6 +3172,15 @@ func (q *FakeQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) ( return q.getProvisionerJobByIDNoLock(ctx, id) } +func (q *FakeQuerier) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { + err := validateDatabaseType(arg) + if err != nil { + return database.ProvisionerJobStat{}, err + } + + panic("not implemented") +} + func (q *FakeQuerier) GetProvisionerJobsByIDs(_ context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index d24bd1bb508bc..16d1c746d7921 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -921,6 +921,13 @@ func (m metricsStore) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) ( return job, err } +func (m metricsStore) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { + start := time.Now() + r0, r1 := m.s.GetProvisionerJobStatsByWorkspace(ctx, arg) + m.queryLatencies.WithLabelValues("GetProvisionerJobStatsByWorkspace").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { start := time.Now() jobs, err := m.s.GetProvisionerJobsByIDs(ctx, ids) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 1c03ffd43218a..1b20ec6914bee 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1869,6 +1869,21 @@ func (mr *MockStoreMockRecorder) GetProvisionerJobByID(arg0, arg1 any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobByID", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobByID), arg0, arg1) } +// GetProvisionerJobStatsByWorkspace mocks base method. +func (m *MockStore) GetProvisionerJobStatsByWorkspace(arg0 context.Context, arg1 database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProvisionerJobStatsByWorkspace", arg0, arg1) + ret0, _ := ret[0].(database.ProvisionerJobStat) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProvisionerJobStatsByWorkspace indicates an expected call of GetProvisionerJobStatsByWorkspace. +func (mr *MockStoreMockRecorder) GetProvisionerJobStatsByWorkspace(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobStatsByWorkspace", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobStatsByWorkspace), arg0, arg1) +} + // GetProvisionerJobsByIDs mocks base method. func (m *MockStore) GetProvisionerJobsByIDs(arg0 context.Context, arg1 []uuid.UUID) ([]database.ProvisionerJob, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 939c24df07ce8..86befa72d5b16 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -836,8 +836,9 @@ ALTER SEQUENCE provisioner_job_logs_id_seq OWNED BY provisioner_job_logs.id; CREATE VIEW provisioner_job_stats AS SELECT - NULL::uuid AS id, + NULL::uuid AS job_id, NULL::provisioner_job_status AS job_status, + NULL::uuid AS workspace_id, NULL::uuid AS worker_id, NULL::text AS error, NULL::text AS error_code, @@ -1924,8 +1925,9 @@ CREATE INDEX workspace_resources_job_id_idx ON workspace_resources USING btree ( CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false); CREATE OR REPLACE VIEW provisioner_job_stats AS - SELECT pj.id, + SELECT pj.id AS job_id, pj.job_status, + wb.workspace_id, pj.worker_id, pj.error, pj.error_code, @@ -1948,9 +1950,10 @@ CREATE OR REPLACE VIEW provisioner_job_stats AS WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) ELSE NULL::double precision END), (0)::double precision) AS apply_secs - FROM (provisioner_jobs pj + FROM ((provisioner_jobs pj + JOIN workspace_builds wb ON ((wb.job_id = pj.id))) LEFT JOIN provisioner_job_timings pjt ON ((pjt.job_id = pj.id))) - GROUP BY pj.id; + GROUP BY pj.id, wb.workspace_id; CREATE TRIGGER inhibit_enqueue_if_disabled BEFORE INSERT ON notification_messages FOR EACH ROW EXECUTE FUNCTION inhibit_enqueue_if_disabled(); diff --git a/coderd/database/migrations/000242_provisioner_job_timings.up.sql b/coderd/database/migrations/000242_provisioner_job_timings.up.sql index 3f540ab005b4f..58b078c821da3 100644 --- a/coderd/database/migrations/000242_provisioner_job_timings.up.sql +++ b/coderd/database/migrations/000242_provisioner_job_timings.up.sql @@ -16,8 +16,9 @@ CREATE TABLE provisioner_job_timings ); CREATE VIEW provisioner_job_stats AS -SELECT pj.id, +SELECT pj.id AS job_id, pj.job_status, + wb.workspace_id, pj.worker_id, pj.error, pj.error_code, @@ -29,5 +30,6 @@ SELECT pj.id, GREATEST(MAX(CASE WHEN pjt.stage = 'plan' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS plan_secs, GREATEST(MAX(CASE WHEN pjt.stage = 'apply' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS apply_secs FROM provisioner_jobs pj - LEFT JOIN provisioner_job_timings pjt ON pjt.job_id = pj.id -GROUP BY pj.id; + JOIN workspace_builds wb ON wb.job_id = pj.id + LEFT JOIN provisioner_job_timings pjt ON pjt.job_id = pj.id +GROUP BY pj.id, wb.workspace_id; diff --git a/coderd/database/models.go b/coderd/database/models.go index 902d7371a13f3..03f1b2824ef82 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2342,8 +2342,9 @@ type ProvisionerJobLog struct { } type ProvisionerJobStat struct { - ID uuid.UUID `db:"id" json:"id"` + JobID uuid.UUID `db:"job_id" json:"job_id"` JobStatus ProvisionerJobStatus `db:"job_status" json:"job_status"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"` Error sql.NullString `db:"error" json:"error"` ErrorCode sql.NullString `db:"error_code" json:"error_code"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 4ae5973a6a98a..efabf0a1b22cc 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -188,6 +188,8 @@ type sqlcQuerier interface { GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) GetProvisionerDaemonsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerDaemon, error) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) + // We include the workspace here so we can authorize the request correctly. + GetProvisionerJobStatsByWorkspace(ctx context.Context, arg GetProvisionerJobStatsByWorkspaceParams) (ProvisionerJobStat, error) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 1bc875022d845..11bf06875afb9 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5346,6 +5346,39 @@ func (q *sqlQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (P return i, err } +const getProvisionerJobStatsByWorkspace = `-- name: GetProvisionerJobStatsByWorkspace :one +SELECT job_id, job_status, workspace_id, worker_id, error, error_code, updated_at, queued_secs, completion_secs, canceled_secs, init_secs, plan_secs, apply_secs FROM provisioner_job_stats +WHERE job_id = $1::uuid AND workspace_id = $2::uuid +LIMIT 1 +` + +type GetProvisionerJobStatsByWorkspaceParams struct { + JobID uuid.UUID `db:"job_id" json:"job_id"` + WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` +} + +// We include the workspace here so we can authorize the request correctly. +func (q *sqlQuerier) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg GetProvisionerJobStatsByWorkspaceParams) (ProvisionerJobStat, error) { + row := q.db.QueryRowContext(ctx, getProvisionerJobStatsByWorkspace, arg.JobID, arg.WorkspaceID) + var i ProvisionerJobStat + err := row.Scan( + &i.JobID, + &i.JobStatus, + &i.WorkspaceID, + &i.WorkerID, + &i.Error, + &i.ErrorCode, + &i.UpdatedAt, + &i.QueuedSecs, + &i.CompletionSecs, + &i.CanceledSecs, + &i.InitSecs, + &i.PlanSecs, + &i.ApplySecs, + ) + return i, err +} + const getProvisionerJobsByIDs = `-- name: GetProvisionerJobsByIDs :many SELECT id, created_at, updated_at, started_at, canceled_at, completed_at, error, organization_id, initiator_id, provisioner, storage_method, type, input, worker_id, file_id, tags, error_code, trace_metadata, job_status diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index 786c5766dbff6..f2c872d2dfb1b 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -155,4 +155,10 @@ SELECT unnest(@source::text[]) AS context, unnest(@action::text[]) AS action, unnest(@resource::text[]) AS resource -RETURNING *; \ No newline at end of file +RETURNING *; + +-- We include the workspace here so we can authorize the request correctly. +-- name: GetProvisionerJobStatsByWorkspace :one +SELECT * FROM provisioner_job_stats +WHERE job_id = @job_id::uuid AND workspace_id = @workspace_id::uuid +LIMIT 1; \ No newline at end of file diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index e04e585d4aa53..86123c4947b92 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -635,6 +635,7 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { return } + // TODO: why? // You must have update permissions on the template to get the state. // This matches a push! if !api.Authorize(r, policy.ActionUpdate, template.RBACObject()) { @@ -647,6 +648,39 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { _, _ = rw.Write(workspaceBuild.ProvisionerState) } +// @Summary Get stats for workspace build +// @ID get-stats-for-workspace-build +// @Security CoderSessionToken +// @Produce json +// @Tags Builds +// @Param workspacebuild path string true "Workspace build ID" +// @Success 200 {object} codersdk.WorkspaceBuildStats +// @Router /workspacebuilds/{workspacebuild}/stats [get] +func (api *API) workspaceBuildStats(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + workspaceBuild := httpmw.WorkspaceBuildParam(r) + + if workspaceBuild.JobID == uuid.Nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Failed to retrieve provisioner job ID from workspace build", + }) + return + } + + pj, err := api.Database.GetProvisionerJobStatsByWorkspace(ctx, database.GetProvisionerJobStatsByWorkspaceParams{ + JobID: workspaceBuild.JobID, + WorkspaceID: workspaceBuild.WorkspaceID, + }) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Failed to get workspace build stats", + Detail: err.Error(), + }) + } + + httpapi.Write(ctx, rw, http.StatusOK, convertWorkspaceBuildStats(pj)) +} + type workspaceBuildsData struct { users []database.User jobs []database.GetProvisionerJobsByIDsWithQueuePositionRow @@ -1010,3 +1044,21 @@ func convertWorkspaceStatus(jobStatus codersdk.ProvisionerJobStatus, transition // return error status since we should never get here return codersdk.WorkspaceStatusFailed } + +func convertWorkspaceBuildStats(stats database.ProvisionerJobStat) codersdk.WorkspaceBuildStats { + return codersdk.WorkspaceBuildStats{ + ID: stats.JobID, + JobStatus: codersdk.ProvisionerJobStatus(stats.JobStatus), + WorkerID: stats.WorkspaceID, + WorkspaceID: stats.WorkspaceID, + Error: stats.Error.String, + ErrorCode: stats.ErrorCode.String, + UpdatedAt: stats.UpdatedAt, + QueuedSecs: stats.QueuedSecs, + CompletionSecs: stats.CompletionSecs, + CanceledSecs: stats.CanceledSecs, + InitSecs: stats.InitSecs, + PlanSecs: stats.PlanSecs, + ApplySecs: stats.ApplySecs, + } +} diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go index 682cb424af1b1..c7d18b49b4e3e 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -74,6 +74,23 @@ type WorkspaceBuild struct { DailyCost int32 `json:"daily_cost"` } +// WorkspaceBuildStats is a point-in-time representation of a workspace build, including timing information. +type WorkspaceBuildStats struct { + ID uuid.UUID `json:"id" format:"uuid"` + JobStatus ProvisionerJobStatus `json:"job_status"` + WorkerID uuid.UUID `json:"worker_id" format:"uuid"` + WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"` + Error string `json:"error"` + ErrorCode string `json:"error_code"` + UpdatedAt time.Time `json:"updated_at" format:"date-time"` + QueuedSecs float64 `json:"queued_secs"` + CompletionSecs float64 `json:"completion_secs"` + CanceledSecs float64 `json:"canceled_secs"` + InitSecs float64 `json:"init_secs"` + PlanSecs float64 `json:"plan_secs"` + ApplySecs float64 `json:"apply_secs"` +} + // WorkspaceResource describes resources used to create a workspace, for instance: // containers, images, volumes. type WorkspaceResource struct { diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index 8cad5b3a73bec..881df6559fedc 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -976,6 +976,55 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get stats for workspace build + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/stats \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /workspacebuilds/{workspacebuild}/stats` + +### Parameters + +| Name | In | Type | Required | Description | +| ---------------- | ---- | ------ | -------- | ------------------ | +| `workspacebuild` | path | string | true | Workspace build ID | + +### Example responses + +> 200 Response + +```json +{ + "apply_secs": 0, + "canceled_secs": 0, + "completion_secs": 0, + "error": "string", + "error_code": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "init_secs": 0, + "job_status": "pending", + "plan_secs": 0, + "queued_secs": 0, + "updated_at": "2019-08-24T14:15:22Z", + "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceBuildStats](schemas.md#codersdkworkspacebuildstats) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get workspace builds by workspace ID ### Code samples diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index cb7c88af83f2b..3f6e065981a01 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -7104,6 +7104,44 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `name` | string | false | | | | `value` | string | false | | | +## codersdk.WorkspaceBuildStats + +```json +{ + "apply_secs": 0, + "canceled_secs": 0, + "completion_secs": 0, + "error": "string", + "error_code": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "init_secs": 0, + "job_status": "pending", + "plan_secs": 0, + "queued_secs": 0, + "updated_at": "2019-08-24T14:15:22Z", + "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ----------------- | -------------------------------------------------------------- | -------- | ------------ | ----------- | +| `apply_secs` | number | false | | | +| `canceled_secs` | number | false | | | +| `completion_secs` | number | false | | | +| `error` | string | false | | | +| `error_code` | string | false | | | +| `id` | string | false | | | +| `init_secs` | number | false | | | +| `job_status` | [codersdk.ProvisionerJobStatus](#codersdkprovisionerjobstatus) | false | | | +| `plan_secs` | number | false | | | +| `queued_secs` | number | false | | | +| `updated_at` | string | false | | | +| `worker_id` | string | false | | | +| `workspace_id` | string | false | | | + ## codersdk.WorkspaceConnectionLatencyMS ```json diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 49e0774edfcdb..12085382a3145 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1904,6 +1904,23 @@ export interface WorkspaceBuildParameter { readonly value: string; } +// From codersdk/workspacebuilds.go +export interface WorkspaceBuildStats { + readonly id: string; + readonly job_status: ProvisionerJobStatus; + readonly worker_id: string; + readonly workspace_id: string; + readonly error: string; + readonly error_code: string; + readonly updated_at: string; + readonly queued_secs: number; + readonly completion_secs: number; + readonly canceled_secs: number; + readonly init_secs: number; + readonly plan_secs: number; + readonly apply_secs: number; +} + // From codersdk/workspaces.go export interface WorkspaceBuildsRequest extends Pagination { readonly since?: string; From 803a9d47458f62f1b5398d80d43c9fdf968c4b6e Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 14 Aug 2024 10:23:43 +0200 Subject: [PATCH 04/22] Smol refactor Signed-off-by: Danny Kopping --- coderd/database/dbtime/dbtime.go | 4 +- ...> 000244_provisioner_job_timings.down.sql} | 0 ... => 000244_provisioner_job_timings.up.sql} | 0 provisioner/terraform/executor.go | 30 ++-- provisioner/terraform/provision.go | 20 +-- provisioner/terraform/serve.go | 5 +- provisioner/terraform/timings.go | 141 +++++++++++------- provisionersdk/proto/provisioner.pb.go | 32 ++-- provisionersdk/proto/provisioner.proto | 7 +- site/e2e/provisionerGenerated.ts | 7 +- 10 files changed, 141 insertions(+), 105 deletions(-) rename coderd/database/migrations/{000242_provisioner_job_timings.down.sql => 000244_provisioner_job_timings.down.sql} (100%) rename coderd/database/migrations/{000242_provisioner_job_timings.up.sql => 000244_provisioner_job_timings.up.sql} (100%) diff --git a/coderd/database/dbtime/dbtime.go b/coderd/database/dbtime/dbtime.go index f242ccff6e0fe..8970423864d94 100644 --- a/coderd/database/dbtime/dbtime.go +++ b/coderd/database/dbtime/dbtime.go @@ -10,5 +10,5 @@ func Now() time.Time { // Time returns a time compatible with Postgres. Postgres only stores dates with // microsecond precision. func Time(t time.Time) time.Time { - return t.Round(time.Microsecond) -} + return t.UTC().Round(time.Microsecond) +} \ No newline at end of file diff --git a/coderd/database/migrations/000242_provisioner_job_timings.down.sql b/coderd/database/migrations/000244_provisioner_job_timings.down.sql similarity index 100% rename from coderd/database/migrations/000242_provisioner_job_timings.down.sql rename to coderd/database/migrations/000244_provisioner_job_timings.down.sql diff --git a/coderd/database/migrations/000242_provisioner_job_timings.up.sql b/coderd/database/migrations/000244_provisioner_job_timings.up.sql similarity index 100% rename from coderd/database/migrations/000242_provisioner_job_timings.up.sql rename to coderd/database/migrations/000244_provisioner_job_timings.up.sql diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index 80aa3ad95931a..399303c593667 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -21,7 +21,6 @@ import ( "go.opentelemetry.io/otel/attribute" "golang.org/x/xerrors" - "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/provisionersdk/proto" ) @@ -36,6 +35,8 @@ type executor struct { // cachePath and workdir must not be used by multiple processes at once. cachePath string workdir string + // used to capture execution times at various stages + timings *timingAggregator } func (e *executor) basicEnv() []string { @@ -254,8 +255,7 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l args = append(args, "-var", variable) } - timingsAgg := newTimingsAggregator(database.ProvisionerJobTimingStagePlan) - outWriter, doneOut := e.provisionLogWriter(logr, timingsAgg) + outWriter, doneOut := e.provisionLogWriter(logr) errWriter, doneErr := logWriter(logr, proto.LogLevel_ERROR) defer func() { _ = outWriter.Close() @@ -277,7 +277,7 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l Parameters: state.Parameters, Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, - Timings: timingsAgg.aggregate(), + Timings: e.timings.aggregate(), }, nil } @@ -404,8 +404,7 @@ func (e *executor) apply( getPlanFilePath(e.workdir), } - timingsAgg := newTimingsAggregator(database.ProvisionerJobTimingStageApply) - outWriter, doneOut := e.provisionLogWriter(logr, timingsAgg) + outWriter, doneOut := e.provisionLogWriter(logr) errWriter, doneErr := logWriter(logr, proto.LogLevel_ERROR) defer func() { _ = outWriter.Close() @@ -433,7 +432,7 @@ func (e *executor) apply( Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, State: stateContent, - Timings: timingsAgg.aggregate(), + Timings: e.timings.aggregate(), }, nil } @@ -547,15 +546,15 @@ func readAndLog(sink logSink, r io.Reader, done chan<- any, level proto.LogLevel // provisionLogWriter creates a WriteCloser that will log each JSON formatted terraform log. The WriteCloser must be // closed by the caller to end logging, after which the returned channel will be closed to indicate that logging of the // written data has finished. Failure to close the WriteCloser will leak a goroutine. -func (e *executor) provisionLogWriter(sink logSink, timings *timingsAggregator) (io.WriteCloser, <-chan any) { +func (e *executor) provisionLogWriter(sink logSink) (io.WriteCloser, <-chan any) { r, w := io.Pipe() done := make(chan any) - go e.provisionReadAndLog(sink, r, timings, done) + go e.provisionReadAndLog(sink, r, done) return w, done } -func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, timings *timingsAggregator, done chan<- any) { +func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) { defer close(done) scanner := bufio.NewScanner(r) for scanner.Scan() { @@ -588,13 +587,13 @@ func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, timings *timin logLevel := convertTerraformLogLevel(log.Level, sink) sink.ProvisionLog(logLevel, log.Message) - ts, te, err := extractTimingsEntry(log) + ts, span, err := extractTimingSpan(log) if err != nil { e.logger.Debug(context.Background(), "failed to extract timings entry from log line", slog.F("line", log.Message), slog.Error(err)) } else { // Only ingest valid timings. - timings.ingest(ts, te) + e.timings.ingest(ts, span) } // If the diagnostic is provided, let's provide a bit more info! @@ -608,13 +607,13 @@ func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, timings *timin } } -func extractTimingsEntry(log terraformProvisionLog) (time.Time, *timingsEntry, error) { +func extractTimingSpan(log terraformProvisionLog) (time.Time, *timingSpan, error) { // Input is not well-formed, bail out. if log.Type == "" { return time.Time{}, nil, xerrors.Errorf("invalid type: %q", log.Type) } - typ := logType(log.Type) + typ := timingKind(log.Type) if !typ.Valid() { return time.Time{}, nil, xerrors.Errorf("invalid type: %q", log.Type) } @@ -624,9 +623,8 @@ func extractTimingsEntry(log terraformProvisionLog) (time.Time, *timingsEntry, e // TODO: log ts = time.Now() } - ts = ts.UTC() - return ts, &timingsEntry{ + return ts, &timingSpan{ kind: typ, action: log.Hook.Action, provider: log.Hook.Resource.Provider, diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index d6b6a403f2968..18bb6a41252d5 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -73,7 +73,7 @@ func (s *server) Plan( defer cancel() defer kill() - e := s.executor(sess.WorkDirectory) + e := s.executor(sess.WorkDirectory, database.ProvisionerJobTimingStagePlan) if err := e.checkMinVersion(ctx); err != nil { return provisionersdk.PlanErrorf(err.Error()) } @@ -104,18 +104,20 @@ func (s *server) Plan( s.logger.Debug(ctx, "running initialization") - timingsAgg := newTimingsAggregator(database.ProvisionerJobTimingStageInit) - timingsAgg.ingest(createInitTimingsEvent(initStart)) + // The JSON output of `terraform init` doesn't include discrete fields for capturing timings of each plugin, + // so we capture the whole init process. + initTimings := newTimingAggregator(database.ProvisionerJobTimingStageInit) + initTimings.ingest(createInitTimingsEvent(initStart)) err = e.init(ctx, killCtx, sess) if err != nil { - timingsAgg.ingest(createInitTimingsEvent(initErrored)) + initTimings.ingest(createInitTimingsEvent(initErrored)) s.logger.Debug(ctx, "init failed", slog.Error(err)) return provisionersdk.PlanErrorf("initialize terraform: %s", err) } - timingsAgg.ingest(createInitTimingsEvent(initComplete)) + initTimings.ingest(createInitTimingsEvent(initComplete)) s.logger.Debug(ctx, "ran initialization") @@ -137,12 +139,12 @@ func (s *server) Plan( return provisionersdk.PlanErrorf(err.Error()) } - resp.Timings = append(resp.Timings, timingsAgg.aggregate()...) + resp.Timings = append(resp.Timings, initTimings.aggregate()...) return resp } -func createInitTimingsEvent(event logType) (time.Time, *timingsEntry) { - return dbtime.Now(), &timingsEntry{ +func createInitTimingsEvent(event timingKind) (time.Time, *timingSpan) { + return dbtime.Now(), &timingSpan{ kind: event, action: "initialize terraform", provider: "terraform", @@ -159,7 +161,7 @@ func (s *server) Apply( defer cancel() defer kill() - e := s.executor(sess.WorkDirectory) + e := s.executor(sess.WorkDirectory, database.ProvisionerJobTimingStageApply) if err := e.checkMinVersion(ctx); err != nil { return provisionersdk.ApplyErrorf(err.Error()) } diff --git a/provisioner/terraform/serve.go b/provisioner/terraform/serve.go index 3a2dde7f809a7..7a3b033bf2bba 100644 --- a/provisioner/terraform/serve.go +++ b/provisioner/terraform/serve.go @@ -12,6 +12,8 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/unhanger" "github.com/coder/coder/v2/provisionersdk" ) @@ -138,7 +140,7 @@ func (s *server) startTrace(ctx context.Context, name string, opts ...trace.Span ))...) } -func (s *server) executor(workdir string) *executor { +func (s *server) executor(workdir string, stage database.ProvisionerJobTimingStage) *executor { return &executor{ server: s, mut: s.execMut, @@ -146,5 +148,6 @@ func (s *server) executor(workdir string) *executor { cachePath: s.cachePath, workdir: workdir, logger: s.logger.Named("executor"), + timings: newTimingAggregator(stage), } } diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index 478928db98350..413c228521078 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -3,101 +3,137 @@ package terraform import ( "fmt" "slices" + "sync" "time" "github.com/cespare/xxhash" "google.golang.org/protobuf/types/known/timestamppb" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/provisionersdk/proto" ) -type logType string +type timingKind string // Copied from https://github.com/hashicorp/terraform/blob/ffbcaf8bef12bb1f4d79f06437f414e280d08761/internal/command/views/json/message_types.go // We cannot reference these because they're in an internal package. const ( - applyStart logType = "apply_start" - applyProgress logType = "apply_progress" - applyComplete logType = "apply_complete" - applyErrored logType = "apply_errored" - provisionStart logType = "provision_start" - provisionProgress logType = "provision_progress" - provisionComplete logType = "provision_complete" - provisionErrored logType = "provision_errored" - refreshStart logType = "refresh_start" - refreshComplete logType = "refresh_complete" - initStart logType = "init_start" - initComplete logType = "init_complete" - initErrored logType = "init_errored" + applyStart timingKind = "apply_start" + applyProgress timingKind = "apply_progress" + applyComplete timingKind = "apply_complete" + applyErrored timingKind = "apply_errored" + provisionStart timingKind = "provision_start" + provisionProgress timingKind = "provision_progress" + provisionComplete timingKind = "provision_complete" + provisionErrored timingKind = "provision_errored" + refreshStart timingKind = "refresh_start" + refreshComplete timingKind = "refresh_complete" + // These are not part of message_types, but we want to track init timings as well. + initStart timingKind = "init_start" + initComplete timingKind = "init_complete" + initErrored timingKind = "init_errored" ) -type timingsAggregator struct { +type timingAggregator struct { + mu sync.Mutex + stage database.ProvisionerJobTimingStage - stateLookup map[uint64]*timingsEntry + stateLookup map[uint64]*timingSpan } -type timingsEntry struct { - kind logType +type timingSpan struct { + kind timingKind start, end time.Time stage database.ProvisionerJobTimingStage action, provider, resource string state proto.TimingState } -func newTimingsAggregator(stage database.ProvisionerJobTimingStage) *timingsAggregator { - return &timingsAggregator{ +// newTimingAggregator creates a new aggregator which measures the duration of resource init/plan/apply actions; stage +// represents the stage of provisioning the timings are occurring within. +func newTimingAggregator(stage database.ProvisionerJobTimingStage) *timingAggregator { + return &timingAggregator{ stage: stage, - stateLookup: make(map[uint64]*timingsEntry), + stateLookup: make(map[uint64]*timingSpan), } } -func (t *timingsAggregator) ingest(ts time.Time, e *timingsEntry) { - e.stage = t.stage +// ingest accepts a timing span at a certain timestamp and assigns it a state according to the kind of timing event. +// We memoize start & completion events, and then calculate their total duration in aggregate. +// We ignore progress events because we only care about the full duration of the action (delta between *_start and *_complete events). +func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { + if s == nil { + return + } + + s.stage = t.stage + ts = dbtime.Time(ts) - switch e.kind { + switch s.kind { case applyStart, provisionStart, refreshStart, initStart: - e.start = ts - e.state = proto.TimingState_INCOMPLETE + s.start = ts + s.state = proto.TimingState_STARTED case applyComplete, provisionComplete, refreshComplete, initComplete: - e.end = ts - e.state = proto.TimingState_COMPLETED + s.end = ts + s.state = proto.TimingState_COMPLETED case applyErrored, provisionErrored, initErrored: - e.end = ts - e.state = proto.TimingState_FAILED - case applyProgress, provisionProgress: - // Don't capture progress messages; we just want start/end timings. + s.end = ts + s.state = proto.TimingState_FAILED + default: + // Don't capture progress messages (or unhandled kinds); we just want start/end timings. return } - t.stateLookup[e.hashByState(e.state)] = e + t.mu.Lock() + // Memoize this span by its unique attributes and the determined state. + // This will be used in aggregate() to determine the duration of the resource action. + t.stateLookup[s.hashByState(s.state)] = s + t.mu.Unlock() } -func (t *timingsAggregator) aggregate() []*proto.Timing { +// aggregate performs a pass through all memoized events to build up a set of *proto.Timing instances which represent +// the total time taken to perform a certain action. +func (t *timingAggregator) aggregate() []*proto.Timing { + t.mu.Lock() + defer t.mu.Unlock() + out := make([]*proto.Timing, 0, len(t.stateLookup)) - for _, e := range t.stateLookup { - switch e.state { - case proto.TimingState_FAILED, proto.TimingState_COMPLETED: - i, ok := t.stateLookup[e.hashByState(proto.TimingState_INCOMPLETE)] - if !ok { - // could not find corresponding "incomplete" event, marking as unknown. - e.state = proto.TimingState_UNKNOWN - } else { - e.start = i.start - } - default: + for _, s := range t.stateLookup { + // We are only concerned here with failed or completed events. + if s.state != proto.TimingState_FAILED && s.state != proto.TimingState_COMPLETED { + continue + } + + // Look for a corresponding span for the STARTED state. + startSpan, ok := t.stateLookup[s.hashByState(proto.TimingState_STARTED)] + if !ok { + // Not found, we'll ignore this span. + continue + } + s.start = startSpan.start + + // Until faster-than-light travel is a possibility, let's prevent this. + // Better to capture a zero delta than a negative one. + if s.start.After(s.end) { + s.start = s.end + } + + // Let's only aggregate valid entries. + // Later we can add support for partial / failed applies, perhaps. + if s.start.IsZero() || s.end.IsZero() { continue } - out = append(out, e.toProto()) + out = append(out, s.toProto()) } return out } -func (l logType) Valid() bool { - return slices.Contains([]logType{ +func (l timingKind) Valid() bool { + return slices.Contains([]timingKind{ applyStart, applyProgress, applyComplete, @@ -108,17 +144,20 @@ func (l logType) Valid() bool { provisionErrored, refreshStart, refreshComplete, + initStart, + initComplete, + initErrored, }, l) } -// hashState computes a hash based on a timingsEntry's unique properties and state. +// hashState computes a hash based on a timingSpan's unique properties and state. // The combination of resource and provider names MUST be unique across entries. -func (e *timingsEntry) hashByState(state proto.TimingState) uint64 { +func (e *timingSpan) hashByState(state proto.TimingState) uint64 { id := fmt.Sprintf("%s:%s:%s", state.String(), e.resource, e.provider) return xxhash.Sum64String(id) } -func (e *timingsEntry) toProto() *proto.Timing { +func (e *timingSpan) toProto() *proto.Timing { return &proto.Timing{ Start: timestamppb.New(e.start), End: timestamppb.New(e.end), diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 659809dcf14fe..6708edbbcf024 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -179,25 +179,22 @@ func (WorkspaceTransition) EnumDescriptor() ([]byte, []int) { type TimingState int32 const ( - TimingState_COMPLETED TimingState = 0 - TimingState_FAILED TimingState = 1 - TimingState_INCOMPLETE TimingState = 2 - TimingState_UNKNOWN TimingState = 3 + TimingState_STARTED TimingState = 0 + TimingState_COMPLETED TimingState = 1 + TimingState_FAILED TimingState = 2 ) // Enum value maps for TimingState. var ( TimingState_name = map[int32]string{ - 0: "COMPLETED", - 1: "FAILED", - 2: "INCOMPLETE", - 3: "UNKNOWN", + 0: "STARTED", + 1: "COMPLETED", + 2: "FAILED", } TimingState_value = map[string]int32{ - "COMPLETED": 0, - "FAILED": 1, - "INCOMPLETE": 2, - "UNKNOWN": 3, + "STARTED": 0, + "COMPLETED": 1, + "FAILED": 2, } ) @@ -2405,7 +2402,7 @@ func (x *Timing) GetState() TimingState { if x != nil { return x.State } - return TimingState_COMPLETED + return TimingState_STARTED } // CancelRequest requests that the previous request be canceled gracefully. @@ -3278,11 +3275,10 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, - 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x45, 0x0a, 0x0b, 0x54, 0x69, 0x6d, - 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, - 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, - 0x44, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, - 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, + 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x2a, 0x35, 0x0a, 0x0b, 0x54, 0x69, 0x6d, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x41, 0x52, + 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, + 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x32, 0x49, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 0a28bb22a1755..9cfba2896b18d 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -298,10 +298,9 @@ message Timing { } enum TimingState { - COMPLETED = 0; - FAILED = 1; - INCOMPLETE = 2; - UNKNOWN = 3; + STARTED = 0; + COMPLETED = 1; + FAILED = 2; } // CancelRequest requests that the previous request be canceled gracefully. diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index 6e41c285b5ddc..516663e8f522a 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -31,10 +31,9 @@ export enum WorkspaceTransition { } export enum TimingState { - COMPLETED = 0, - FAILED = 1, - INCOMPLETE = 2, - UNKNOWN = 3, + STARTED = 0, + COMPLETED = 1, + FAILED = 2, UNRECOGNIZED = -1, } From 973ec6d0aa106366a9ca04d49f54c2929bee2d48 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 14 Aug 2024 14:44:22 +0200 Subject: [PATCH 05/22] Capture dependency graph timings Signed-off-by: Danny Kopping --- coderd/database/dump.sql | 7 ++++ .../000244_provisioner_job_timings.up.sql | 28 ++++++++++------ coderd/database/models.go | 4 +++ coderd/database/queries.sql.go | 3 +- provisioner/terraform/executor.go | 11 ++++++- provisioner/terraform/provision.go | 14 ++------ provisioner/terraform/timings.go | 32 ++++++++++++++++--- 7 files changed, 73 insertions(+), 26 deletions(-) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 86befa72d5b16..be044c5ab45a9 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -139,6 +139,7 @@ COMMENT ON TYPE provisioner_job_status IS 'Computed status of a provisioner job. CREATE TYPE provisioner_job_timing_stage AS ENUM ( 'init', 'plan', + 'graph', 'apply' ); @@ -848,6 +849,7 @@ SELECT NULL::double precision AS canceled_secs, NULL::double precision AS init_secs, NULL::double precision AS plan_secs, + NULL::double precision AS graph_secs, NULL::double precision AS apply_secs; CREATE TABLE provisioner_job_timings ( @@ -1945,6 +1947,11 @@ CREATE OR REPLACE VIEW provisioner_job_stats AS WHEN (pjt.stage = 'plan'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) ELSE NULL::double precision END), (0)::double precision) AS plan_secs, + GREATEST(max( + CASE + WHEN (pjt.stage = 'graph'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) + ELSE NULL::double precision + END), (0)::double precision) AS graph_secs, GREATEST(max( CASE WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) diff --git a/coderd/database/migrations/000244_provisioner_job_timings.up.sql b/coderd/database/migrations/000244_provisioner_job_timings.up.sql index 58b078c821da3..bfd6c71cea9dd 100644 --- a/coderd/database/migrations/000244_provisioner_job_timings.up.sql +++ b/coderd/database/migrations/000244_provisioner_job_timings.up.sql @@ -1,6 +1,7 @@ CREATE TYPE provisioner_job_timing_stage AS ENUM ( 'init', 'plan', + 'graph', 'apply' ); @@ -16,20 +17,29 @@ CREATE TABLE provisioner_job_timings ); CREATE VIEW provisioner_job_stats AS -SELECT pj.id AS job_id, +SELECT pj.id AS job_id, pj.job_status, wb.workspace_id, pj.worker_id, pj.error, pj.error_code, pj.updated_at, - GREATEST(EXTRACT(EPOCH FROM (pj.started_at - pj.created_at)), 0) AS queued_secs, - GREATEST(EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at)), 0) AS completion_secs, - GREATEST(EXTRACT(EPOCH FROM (pj.canceled_at - pj.started_at)), 0) AS canceled_secs, - GREATEST(MAX(CASE WHEN pjt.stage = 'init' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS init_secs, - GREATEST(MAX(CASE WHEN pjt.stage = 'plan' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS plan_secs, - GREATEST(MAX(CASE WHEN pjt.stage = 'apply' THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS apply_secs + GREATEST(EXTRACT(EPOCH FROM (pj.started_at - pj.created_at)), 0) AS queued_secs, + GREATEST(EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at)), 0) AS completion_secs, + GREATEST(EXTRACT(EPOCH FROM (pj.canceled_at - pj.started_at)), 0) AS canceled_secs, + GREATEST(MAX(CASE + WHEN pjt.stage = 'init'::provisioner_job_timing_stage + THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS init_secs, + GREATEST(MAX(CASE + WHEN pjt.stage = 'plan'::provisioner_job_timing_stage + THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS plan_secs, + GREATEST(MAX(CASE + WHEN pjt.stage = 'graph'::provisioner_job_timing_stage + THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS graph_secs, + GREATEST(MAX(CASE + WHEN pjt.stage = 'apply'::provisioner_job_timing_stage + THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS apply_secs FROM provisioner_jobs pj - JOIN workspace_builds wb ON wb.job_id = pj.id - LEFT JOIN provisioner_job_timings pjt ON pjt.job_id = pj.id + JOIN workspace_builds wb ON wb.job_id = pj.id + LEFT JOIN provisioner_job_timings pjt ON pjt.job_id = pj.id GROUP BY pj.id, wb.workspace_id; diff --git a/coderd/database/models.go b/coderd/database/models.go index 03f1b2824ef82..78e8a68fe20fc 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1221,6 +1221,7 @@ type ProvisionerJobTimingStage string const ( ProvisionerJobTimingStageInit ProvisionerJobTimingStage = "init" ProvisionerJobTimingStagePlan ProvisionerJobTimingStage = "plan" + ProvisionerJobTimingStageGraph ProvisionerJobTimingStage = "graph" ProvisionerJobTimingStageApply ProvisionerJobTimingStage = "apply" ) @@ -1263,6 +1264,7 @@ func (e ProvisionerJobTimingStage) Valid() bool { switch e { case ProvisionerJobTimingStageInit, ProvisionerJobTimingStagePlan, + ProvisionerJobTimingStageGraph, ProvisionerJobTimingStageApply: return true } @@ -1273,6 +1275,7 @@ func AllProvisionerJobTimingStageValues() []ProvisionerJobTimingStage { return []ProvisionerJobTimingStage{ ProvisionerJobTimingStageInit, ProvisionerJobTimingStagePlan, + ProvisionerJobTimingStageGraph, ProvisionerJobTimingStageApply, } } @@ -2354,6 +2357,7 @@ type ProvisionerJobStat struct { CanceledSecs float64 `db:"canceled_secs" json:"canceled_secs"` InitSecs float64 `db:"init_secs" json:"init_secs"` PlanSecs float64 `db:"plan_secs" json:"plan_secs"` + GraphSecs float64 `db:"graph_secs" json:"graph_secs"` ApplySecs float64 `db:"apply_secs" json:"apply_secs"` } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 11bf06875afb9..cfbd632fa5400 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5347,7 +5347,7 @@ func (q *sqlQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (P } const getProvisionerJobStatsByWorkspace = `-- name: GetProvisionerJobStatsByWorkspace :one -SELECT job_id, job_status, workspace_id, worker_id, error, error_code, updated_at, queued_secs, completion_secs, canceled_secs, init_secs, plan_secs, apply_secs FROM provisioner_job_stats +SELECT job_id, job_status, workspace_id, worker_id, error, error_code, updated_at, queued_secs, completion_secs, canceled_secs, init_secs, plan_secs, graph_secs, apply_secs FROM provisioner_job_stats WHERE job_id = $1::uuid AND workspace_id = $2::uuid LIMIT 1 ` @@ -5374,6 +5374,7 @@ func (q *sqlQuerier) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg &i.CanceledSecs, &i.InitSecs, &i.PlanSecs, + &i.GraphSecs, &i.ApplySecs, ) return i, err diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index 399303c593667..230a67fd5c1be 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -21,6 +21,7 @@ import ( "go.opentelemetry.io/otel/attribute" "golang.org/x/xerrors" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/provisionersdk/proto" ) @@ -268,16 +269,24 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l if err != nil { return nil, xerrors.Errorf("terraform plan: %w", err) } + + // Capture the duration of the call to `terraform graph`. + graphTimings := newTimingAggregator(database.ProvisionerJobTimingStageGraph) + graphTimings.ingest(createGraphTimingsEvent(graphStart)) + state, err := e.planResources(ctx, killCtx, planfilePath) if err != nil { + graphTimings.ingest(createGraphTimingsEvent(graphErrored)) return nil, err } + graphTimings.ingest(createGraphTimingsEvent(graphComplete)) + return &proto.PlanComplete{ Parameters: state.Parameters, Resources: state.Resources, ExternalAuthProviders: state.ExternalAuthProviders, - Timings: e.timings.aggregate(), + Timings: append(e.timings.aggregate(), graphTimings.aggregate()...), }, nil } diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 18bb6a41252d5..58f17f2a50150 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -15,7 +15,6 @@ import ( "github.com/coder/terraform-provider-coder/provider" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/provisionersdk/proto" @@ -139,19 +138,12 @@ func (s *server) Plan( return provisionersdk.PlanErrorf(err.Error()) } - resp.Timings = append(resp.Timings, initTimings.aggregate()...) + // Prepend init timings since they occur prior to plan timings. + // Order is irrelevant; this is merely indicative. + resp.Timings = append(initTimings.aggregate(), resp.Timings...) return resp } -func createInitTimingsEvent(event timingKind) (time.Time, *timingSpan) { - return dbtime.Now(), &timingSpan{ - kind: event, - action: "initialize terraform", - provider: "terraform", - resource: "state file", - } -} - func (s *server) Apply( sess *provisionersdk.Session, request *proto.ApplyRequest, canceledOrComplete <-chan struct{}, ) *proto.ApplyComplete { diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index 413c228521078..92cb577eee653 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -29,10 +29,13 @@ const ( provisionErrored timingKind = "provision_errored" refreshStart timingKind = "refresh_start" refreshComplete timingKind = "refresh_complete" - // These are not part of message_types, but we want to track init timings as well. + // These are not part of message_types, but we want to track init/graph timings as well. initStart timingKind = "init_start" initComplete timingKind = "init_complete" initErrored timingKind = "init_errored" + graphStart timingKind = "graph_start" + graphComplete timingKind = "graph_complete" + graphErrored timingKind = "graph_errored" ) type timingAggregator struct { @@ -71,13 +74,13 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { ts = dbtime.Time(ts) switch s.kind { - case applyStart, provisionStart, refreshStart, initStart: + case applyStart, provisionStart, refreshStart, initStart, graphStart: s.start = ts s.state = proto.TimingState_STARTED - case applyComplete, provisionComplete, refreshComplete, initComplete: + case applyComplete, provisionComplete, refreshComplete, initComplete, graphComplete: s.end = ts s.state = proto.TimingState_COMPLETED - case applyErrored, provisionErrored, initErrored: + case applyErrored, provisionErrored, initErrored, graphErrored: s.end = ts s.state = proto.TimingState_FAILED default: @@ -147,6 +150,9 @@ func (l timingKind) Valid() bool { initStart, initComplete, initErrored, + graphStart, + graphComplete, + graphErrored, }, l) } @@ -168,3 +174,21 @@ func (e *timingSpan) toProto() *proto.Timing { State: e.state, } } + +func createInitTimingsEvent(event timingKind) (time.Time, *timingSpan) { + return dbtime.Now(), &timingSpan{ + kind: event, + action: "initializing terraform", + provider: "terraform", + resource: "state file", + } +} + +func createGraphTimingsEvent(event timingKind) (time.Time, *timingSpan) { + return dbtime.Now(), &timingSpan{ + kind: event, + action: "building terraform dependency graph", + provider: "terraform", + resource: "state file", + } +} \ No newline at end of file From a070e07daeda18170df042c08aab70834a4e21f6 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Wed, 14 Aug 2024 15:10:03 +0200 Subject: [PATCH 06/22] Expand hash to include span category so multiple operations on the same resource are captured Signed-off-by: Danny Kopping --- provisioner/terraform/executor.go | 6 +- provisioner/terraform/provision.go | 6 +- provisioner/terraform/timings.go | 99 +++++++++++++++++++----------- 3 files changed, 68 insertions(+), 43 deletions(-) diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index 230a67fd5c1be..eae1e90194129 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -272,15 +272,15 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l // Capture the duration of the call to `terraform graph`. graphTimings := newTimingAggregator(database.ProvisionerJobTimingStageGraph) - graphTimings.ingest(createGraphTimingsEvent(graphStart)) + graphTimings.ingest(createGraphTimingsEvent(timingGraphStart)) state, err := e.planResources(ctx, killCtx, planfilePath) if err != nil { - graphTimings.ingest(createGraphTimingsEvent(graphErrored)) + graphTimings.ingest(createGraphTimingsEvent(timingGraphErrored)) return nil, err } - graphTimings.ingest(createGraphTimingsEvent(graphComplete)) + graphTimings.ingest(createGraphTimingsEvent(timingGraphComplete)) return &proto.PlanComplete{ Parameters: state.Parameters, diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 58f17f2a50150..9ff56de111662 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -106,17 +106,17 @@ func (s *server) Plan( // The JSON output of `terraform init` doesn't include discrete fields for capturing timings of each plugin, // so we capture the whole init process. initTimings := newTimingAggregator(database.ProvisionerJobTimingStageInit) - initTimings.ingest(createInitTimingsEvent(initStart)) + initTimings.ingest(createInitTimingsEvent(timingInitStart)) err = e.init(ctx, killCtx, sess) if err != nil { - initTimings.ingest(createInitTimingsEvent(initErrored)) + initTimings.ingest(createInitTimingsEvent(timingInitErrored)) s.logger.Debug(ctx, "init failed", slog.Error(err)) return provisionersdk.PlanErrorf("initialize terraform: %s", err) } - initTimings.ingest(createInitTimingsEvent(initComplete)) + initTimings.ingest(createInitTimingsEvent(timingInitComplete)) s.logger.Debug(ctx, "ran initialization") diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index 92cb577eee653..25927bc93e2e6 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -19,23 +19,23 @@ type timingKind string // Copied from https://github.com/hashicorp/terraform/blob/ffbcaf8bef12bb1f4d79f06437f414e280d08761/internal/command/views/json/message_types.go // We cannot reference these because they're in an internal package. const ( - applyStart timingKind = "apply_start" - applyProgress timingKind = "apply_progress" - applyComplete timingKind = "apply_complete" - applyErrored timingKind = "apply_errored" - provisionStart timingKind = "provision_start" - provisionProgress timingKind = "provision_progress" - provisionComplete timingKind = "provision_complete" - provisionErrored timingKind = "provision_errored" - refreshStart timingKind = "refresh_start" - refreshComplete timingKind = "refresh_complete" + timingApplyStart timingKind = "apply_start" + timingApplyProgress timingKind = "apply_progress" + timingApplyComplete timingKind = "apply_complete" + timingApplyErrored timingKind = "apply_errored" + timingProvisionStart timingKind = "provision_start" + timingProvisionProgress timingKind = "provision_progress" + timingProvisionComplete timingKind = "provision_complete" + timingProvisionErrored timingKind = "provision_errored" + timingRefreshStart timingKind = "refresh_start" + timingRefreshComplete timingKind = "refresh_complete" // These are not part of message_types, but we want to track init/graph timings as well. - initStart timingKind = "init_start" - initComplete timingKind = "init_complete" - initErrored timingKind = "init_errored" - graphStart timingKind = "graph_start" - graphComplete timingKind = "graph_complete" - graphErrored timingKind = "graph_errored" + timingInitStart timingKind = "init_start" + timingInitComplete timingKind = "init_complete" + timingInitErrored timingKind = "init_errored" + timingGraphStart timingKind = "graph_start" + timingGraphComplete timingKind = "graph_complete" + timingGraphErrored timingKind = "graph_errored" ) type timingAggregator struct { @@ -74,13 +74,13 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { ts = dbtime.Time(ts) switch s.kind { - case applyStart, provisionStart, refreshStart, initStart, graphStart: + case timingApplyStart, timingProvisionStart, timingRefreshStart, timingInitStart, timingGraphStart: s.start = ts s.state = proto.TimingState_STARTED - case applyComplete, provisionComplete, refreshComplete, initComplete, graphComplete: + case timingApplyComplete, timingProvisionComplete, timingRefreshComplete, timingInitComplete, timingGraphComplete: s.end = ts s.state = proto.TimingState_COMPLETED - case applyErrored, provisionErrored, initErrored, graphErrored: + case timingApplyErrored, timingProvisionErrored, timingInitErrored, timingGraphErrored: s.end = ts s.state = proto.TimingState_FAILED default: @@ -137,33 +137,58 @@ func (t *timingAggregator) aggregate() []*proto.Timing { func (l timingKind) Valid() bool { return slices.Contains([]timingKind{ - applyStart, - applyProgress, - applyComplete, - applyErrored, - provisionStart, - provisionProgress, - provisionComplete, - provisionErrored, - refreshStart, - refreshComplete, - initStart, - initComplete, - initErrored, - graphStart, - graphComplete, - graphErrored, + timingApplyStart, + timingApplyProgress, + timingApplyComplete, + timingApplyErrored, + timingProvisionStart, + timingProvisionProgress, + timingProvisionComplete, + timingProvisionErrored, + timingRefreshStart, + timingRefreshComplete, + timingInitStart, + timingInitComplete, + timingInitErrored, + timingGraphStart, + timingGraphComplete, + timingGraphErrored, }, l) } +// Category returns the category for a giving timing state so that timings can be aggregated by this category. +// We can't use the state itself because we need an `apply_start` and an `apply_complete` to both hash to the same entry +// if all other attributes are identical. +func (l timingKind) Category() string { + switch l { + case timingInitStart, timingInitComplete, timingInitErrored: + return "init" + case timingGraphStart, timingGraphComplete, timingGraphErrored: + return "graph" + case timingApplyStart, timingApplyProgress, timingApplyComplete, timingApplyErrored: + return "apply" + case timingProvisionStart, timingProvisionProgress, timingProvisionComplete, timingProvisionErrored: + return "provision" + case timingRefreshStart, timingRefreshComplete: + return "state refresh" + default: + return "?" + } +} + // hashState computes a hash based on a timingSpan's unique properties and state. // The combination of resource and provider names MUST be unique across entries. func (e *timingSpan) hashByState(state proto.TimingState) uint64 { - id := fmt.Sprintf("%s:%s:%s", state.String(), e.resource, e.provider) + id := fmt.Sprintf("%s:%s:%s:%s", e.kind.Category(), state.String(), e.resource, e.provider) return xxhash.Sum64String(id) } func (e *timingSpan) toProto() *proto.Timing { + // Some log entries, like state refreshes, don't have any "action" logged. + if e.action == "" { + e.action = e.kind.Category() + } + return &proto.Timing{ Start: timestamppb.New(e.start), End: timestamppb.New(e.end), @@ -191,4 +216,4 @@ func createGraphTimingsEvent(event timingKind) (time.Time, *timingSpan) { provider: "terraform", resource: "state file", } -} \ No newline at end of file +} From 4a29b964dbcc92b2f3c8d71c894e03bdd3b1d36b Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 11:11:07 +0200 Subject: [PATCH 07/22] Tests Signed-off-by: Danny Kopping --- provisioner/terraform/executor.go | 73 +++--- .../timings-aggregation/complete.txtar | 39 ++++ .../testdata/timings-aggregation/error.txtar | 113 +++++++++ .../testdata/timings-aggregation/init.txtar | 38 ++++ .../testdata/timings-aggregation/simple.txtar | 7 + provisioner/terraform/timings.go | 25 +- .../terraform/timings_internal_test.go | 215 ++++++++++++++++++ 7 files changed, 478 insertions(+), 32 deletions(-) create mode 100644 provisioner/terraform/testdata/timings-aggregation/complete.txtar create mode 100644 provisioner/terraform/testdata/timings-aggregation/error.txtar create mode 100644 provisioner/terraform/testdata/timings-aggregation/init.txtar create mode 100644 provisioner/terraform/testdata/timings-aggregation/simple.txtar create mode 100644 provisioner/terraform/timings_internal_test.go diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index eae1e90194129..06a6edaeb5b08 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -565,32 +565,14 @@ func (e *executor) provisionLogWriter(sink logSink) (io.WriteCloser, <-chan any) func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) { defer close(done) + + errCount := 0 + scanner := bufio.NewScanner(r) for scanner.Scan() { - var log terraformProvisionLog - err := json.Unmarshal(scanner.Bytes(), &log) - if err != nil { - // Sometimes terraform doesn't log JSON, even though we asked it to. - // The terraform maintainers have said on the issue tracker that - // they don't guarantee that non-JSON lines won't get printed. - // https://github.com/hashicorp/terraform/issues/29252#issuecomment-887710001 - // - // > I think as a practical matter it isn't possible for us to - // > promise that the output will always be entirely JSON, because - // > there's plenty of code that runs before command line arguments - // > are parsed and thus before we even know we're in JSON mode. - // > Given that, I'd suggest writing code that consumes streaming - // > JSON output from Terraform in such a way that it can tolerate - // > the output not having JSON in it at all. - // - // Log lines such as: - // - Acquiring state lock. This may take a few moments... - // - Releasing state lock. This may take a few moments... - if strings.TrimSpace(scanner.Text()) == "" { - continue - } - log.Level = "info" - log.Message = scanner.Text() + log := parseTerraformLogLine(scanner.Bytes()) + if log == nil { + continue } logLevel := convertTerraformLogLevel(log.Level, sink) @@ -598,8 +580,12 @@ func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, done chan<- an ts, span, err := extractTimingSpan(log) if err != nil { - e.logger.Debug(context.Background(), "failed to extract timings entry from log line", - slog.F("line", log.Message), slog.Error(err)) + // It's too noisy to log all of these as timings are not an essential feature, but we do need to log *some*. + if errCount%10 == 0 { + e.logger.Warn(context.Background(), "(sampled) failed to extract timings entry from log line", + slog.F("line", log.Message), slog.Error(err)) + } + errCount++ } else { // Only ingest valid timings. e.timings.ingest(ts, span) @@ -616,15 +602,44 @@ func (e *executor) provisionReadAndLog(sink logSink, r io.Reader, done chan<- an } } -func extractTimingSpan(log terraformProvisionLog) (time.Time, *timingSpan, error) { +func parseTerraformLogLine(line []byte) *terraformProvisionLog { + var log terraformProvisionLog + err := json.Unmarshal(line, &log) + if err != nil { + // Sometimes terraform doesn't log JSON, even though we asked it to. + // The terraform maintainers have said on the issue tracker that + // they don't guarantee that non-JSON lines won't get printed. + // https://github.com/hashicorp/terraform/issues/29252#issuecomment-887710001 + // + // > I think as a practical matter it isn't possible for us to + // > promise that the output will always be entirely JSON, because + // > there's plenty of code that runs before command line arguments + // > are parsed and thus before we even know we're in JSON mode. + // > Given that, I'd suggest writing code that consumes streaming + // > JSON output from Terraform in such a way that it can tolerate + // > the output not having JSON in it at all. + // + // Log lines such as: + // - Acquiring state lock. This may take a few moments... + // - Releasing state lock. This may take a few moments... + if len(bytes.TrimSpace(line)) == 0 { + return nil + } + log.Level = "info" + log.Message = string(line) + } + return &log +} + +func extractTimingSpan(log *terraformProvisionLog) (time.Time, *timingSpan, error) { // Input is not well-formed, bail out. if log.Type == "" { - return time.Time{}, nil, xerrors.Errorf("invalid type: %q", log.Type) + return time.Time{}, nil, xerrors.Errorf("invalid timing kind: %q", log.Type) } typ := timingKind(log.Type) if !typ.Valid() { - return time.Time{}, nil, xerrors.Errorf("invalid type: %q", log.Type) + return time.Time{}, nil, xerrors.Errorf("unexpected timing kind: %q", log.Type) } ts, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", log.Timestamp) diff --git a/provisioner/terraform/testdata/timings-aggregation/complete.txtar b/provisioner/terraform/testdata/timings-aggregation/complete.txtar new file mode 100644 index 0000000000000..40acb9ae06a65 --- /dev/null +++ b/provisioner/terraform/testdata/timings-aggregation/complete.txtar @@ -0,0 +1,39 @@ +A successful build which results in successful plan and apply timings. + +-- plan -- +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:38.097648+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"data.coder_workspace.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_parameter.memory_size: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_parameter.memory_size","module":"","resource":"data.coder_parameter.memory_size","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"memory_size","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_provisioner.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_provisioner.me: Refresh complete after 0s [id=2470b3d2-32f4-4f95-ac70-0971efdb8338]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.195712+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"2470b3d2-32f4-4f95-ac70-0971efdb8338","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_workspace.me: Refresh complete after 0s [id=feb06d32-3252-4cd8-b7db-ea0c5145747f]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.195820+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"feb06d32-3252-4cd8-b7db-ea0c5145747f","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_parameter.memory_size: Refresh complete after 0s [id=b136c86c-1be0-43b4-9d78-e492918c5de0]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.195836+02:00","hook":{"resource":{"addr":"data.coder_parameter.memory_size","module":"","resource":"data.coder_parameter.memory_size","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"memory_size","resource_key":null},"action":"read","id_key":"id","id_value":"b136c86c-1be0-43b4-9d78-e492918c5de0","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221555+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221574+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_volume.home_volume: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221580+02:00","change":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_container.workspace[0]: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221584+02:00","change":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"Plan: 4 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221589+02:00","changes":{"add":4,"change":0,"import":0,"remove":0,"operation":"plan"},"type":"change_summary"} +-- apply -- +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.507006+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572335+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572411+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_volume.home_volume: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572416+02:00","change":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_container.workspace[0]: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572424+02:00","change":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_agent.main: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.616546+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"coder_agent.main: Creation complete after 0s [id=a23083da-4679-4396-a306-f7b466237883]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.618045+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"a23083da-4679-4396-a306-f7b466237883","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_image.main: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.626722+02:00","hook":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"docker_volume.home_volume: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.627335+02:00","hook":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"docker_volume.home_volume: Creation complete after 0s [id=coder-feb06d32-3252-4cd8-b7db-ea0c5145747f-home]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.660616+02:00","hook":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create","id_key":"id","id_value":"coder-feb06d32-3252-4cd8-b7db-ea0c5145747f-home","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_image.main: Creation complete after 0s [id=sha256:443d199e8bfcce69c2aa494b36b5f8b04c3b183277cd19190e9589fd8552d618nginx:latest]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.669954+02:00","hook":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"sha256:443d199e8bfcce69c2aa494b36b5f8b04c3b183277cd19190e9589fd8552d618nginx:latest","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_container.workspace[0]: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.682223+02:00","hook":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"docker_container.workspace[0]: Creation complete after 0s [id=e39f34233fe1f6d18a33eaed8ad47ef1ae19ccf8cf6841858d5f2dafe4e3c8c9]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:40.186482+02:00","hook":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create","id_key":"id","id_value":"e39f34233fe1f6d18a33eaed8ad47ef1ae19ccf8cf6841858d5f2dafe4e3c8c9","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"Apply complete! Resources: 4 added, 0 changed, 0 destroyed.","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:40.204593+02:00","changes":{"add":4,"change":0,"import":0,"remove":0,"operation":"apply"},"type":"change_summary"} +{"@level":"info","@message":"Outputs: 0","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:40.205051+02:00","outputs":{},"type":"outputs"} +-- timings -- +{"start":"2024-08-15T08:26:39.194726Z","end":"2024-08-15T08:26:39.195820Z","action":"read","source":"coder","resource":"data.coder_workspace.me","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.194726Z","end":"2024-08-15T08:26:39.195712Z","action":"read","source":"coder","resource":"data.coder_provisioner.me","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.194726Z","end":"2024-08-15T08:26:39.195836Z","action":"read","source":"coder","resource":"data.coder_parameter.memory_size","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.616546Z","end":"2024-08-15T08:26:39.618045Z","action":"create","source":"coder","resource":"coder_agent.main","stage":"apply","state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.626722Z","end":"2024-08-15T08:26:39.669954Z","action":"create","source":"docker","resource":"docker_image.main","stage":"apply","state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.627335Z","end":"2024-08-15T08:26:39.660616Z","action":"create","source":"docker","resource":"docker_volume.home_volume","stage":"apply","state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.682223Z","end":"2024-08-15T08:26:40.186482Z","action":"create","source":"docker","resource":"docker_container.workspace[0]","stage":"apply","state":"COMPLETED"} \ No newline at end of file diff --git a/provisioner/terraform/testdata/timings-aggregation/error.txtar b/provisioner/terraform/testdata/timings-aggregation/error.txtar new file mode 100644 index 0000000000000..a71db9ca41e18 --- /dev/null +++ b/provisioner/terraform/testdata/timings-aggregation/error.txtar @@ -0,0 +1,113 @@ +Logs of an attempt to apply a resource which encounters an error. + +-- plan -- +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:22.823175+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"data.coder_provisioner.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.209992+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_parameter.argument: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.210000+02:00","hook":{"resource":{"addr":"data.coder_parameter.argument","module":"","resource":"data.coder_parameter.argument","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"argument","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.210004+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide","module":"module.jetbrains_gateway","resource":"data.coder_parameter.jetbrains_ide","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"jetbrains_ide","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_workspace.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.210006+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_workspace.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.210008+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_workspace.me","module":"module.jetbrains_gateway","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_provisioner.me: Refresh complete after 0s [id=3893952e-e5f2-4a98-a65d-3ee06e0e2f12]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.211454+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"3893952e-e5f2-4a98-a65d-3ee06e0e2f12","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_workspace.me: Refresh complete after 0s [id=82090a02-f4e3-46bd-9c84-7a1b2bd7f8c8]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.211697+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_workspace.me","module":"module.jetbrains_gateway","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"82090a02-f4e3-46bd-9c84-7a1b2bd7f8c8","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_parameter.argument: Refresh complete after 0s [id=f2a0c8f2-527f-4b2a-b388-0c490d37e728]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.211745+02:00","hook":{"resource":{"addr":"data.coder_parameter.argument","module":"","resource":"data.coder_parameter.argument","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"argument","resource_key":null},"action":"read","id_key":"id","id_value":"f2a0c8f2-527f-4b2a-b388-0c490d37e728","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide: Refresh complete after 0s [id=228fd650-0c83-4b1a-82a7-d110e5ffa141]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.212438+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide","module":"module.jetbrains_gateway","resource":"data.coder_parameter.jetbrains_ide","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"jetbrains_ide","resource_key":null},"action":"read","id_key":"id","id_value":"228fd650-0c83-4b1a-82a7-d110e5ffa141","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_workspace.me: Refresh complete after 0s [id=82090a02-f4e3-46bd-9c84-7a1b2bd7f8c8]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.212607+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"82090a02-f4e3-46bd-9c84-7a1b2bd7f8c8","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"null_resource.force_apply: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.227631+02:00","change":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.227652+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_script.oops: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.227655+02:00","change":{"resource":{"addr":"coder_script.oops","module":"","resource":"coder_script.oops","implied_provider":"coder","resource_type":"coder_script","resource_name":"oops","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"module.jetbrains_gateway.coder_app.gateway: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.227659+02:00","change":{"resource":{"addr":"module.jetbrains_gateway.coder_app.gateway","module":"module.jetbrains_gateway","resource":"coder_app.gateway","implied_provider":"coder","resource_type":"coder_app","resource_name":"gateway","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.227662+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_container.workspace[0]: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.227674+02:00","change":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"Plan: 6 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.227684+02:00","changes":{"add":6,"change":0,"import":0,"remove":0,"operation":"plan"},"type":"change_summary"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.228625+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":80,"column":42,"byte":1659},"end":{"line":80,"column":48,"byte":1665}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" name = \"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}\"","start_line":80,"highlight_start_offset":41,"highlight_end_offset":47,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.229002+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":99,"column":36,"byte":2293},"end":{"line":99,"column":42,"byte":2299}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner","start_line":99,"highlight_start_offset":35,"highlight_end_offset":41,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.229350+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner_id\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":103,"column":36,"byte":2379},"end":{"line":103,"column":45,"byte":2388}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner_id","start_line":103,"highlight_start_offset":35,"highlight_end_offset":44,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.229687+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":80,"column":42,"byte":1659},"end":{"line":80,"column":48,"byte":1665}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" name = \"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}\"","start_line":80,"highlight_start_offset":41,"highlight_end_offset":47,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.230027+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":99,"column":36,"byte":2293},"end":{"line":99,"column":42,"byte":2299}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner","start_line":99,"highlight_start_offset":35,"highlight_end_offset":41,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:24.230363+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner_id\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":103,"column":36,"byte":2379},"end":{"line":103,"column":45,"byte":2388}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner_id","start_line":103,"highlight_start_offset":35,"highlight_end_offset":44,"values":[]}},"type":"diagnostic"} +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:34.917480+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"data.coder_parameter.argument: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.198205+02:00","hook":{"resource":{"addr":"data.coder_parameter.argument","module":"","resource":"data.coder_parameter.argument","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"argument","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_workspace.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.198207+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_parameter.argument: Refresh complete after 0s [id=271583ea-f59b-4a41-81e7-81e4285de037]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.199241+02:00","hook":{"resource":{"addr":"data.coder_parameter.argument","module":"","resource":"data.coder_parameter.argument","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"argument","resource_key":null},"action":"read","id_key":"id","id_value":"271583ea-f59b-4a41-81e7-81e4285de037","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_workspace.me: Refresh complete after 0s [id=e1a65799-9978-43e8-a752-bea95d15a68e]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.200058+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"e1a65799-9978-43e8-a752-bea95d15a68e","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_provisioner.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.200671+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_workspace.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.200993+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_workspace.me","module":"module.jetbrains_gateway","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_provisioner.me: Refresh complete after 0s [id=0465a592-aa80-49e9-b511-88ef42937f19]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.201032+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"0465a592-aa80-49e9-b511-88ef42937f19","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.201143+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide","module":"module.jetbrains_gateway","resource":"data.coder_parameter.jetbrains_ide","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"jetbrains_ide","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_workspace.me: Refresh complete after 0s [id=e1a65799-9978-43e8-a752-bea95d15a68e]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.201434+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_workspace.me","module":"module.jetbrains_gateway","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"e1a65799-9978-43e8-a752-bea95d15a68e","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide: Refresh complete after 0s [id=ab94c14a-e63e-40f9-9ac2-da9dd32dba69]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.201824+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide","module":"module.jetbrains_gateway","resource":"data.coder_parameter.jetbrains_ide","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"jetbrains_ide","resource_key":null},"action":"read","id_key":"id","id_value":"ab94c14a-e63e-40f9-9ac2-da9dd32dba69","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212508+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"null_resource.force_apply: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212529+02:00","change":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212533+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_script.oops: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212539+02:00","change":{"resource":{"addr":"coder_script.oops","module":"","resource":"coder_script.oops","implied_provider":"coder","resource_type":"coder_script","resource_name":"oops","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"module.jetbrains_gateway.coder_app.gateway: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212543+02:00","change":{"resource":{"addr":"module.jetbrains_gateway.coder_app.gateway","module":"module.jetbrains_gateway","resource":"coder_app.gateway","implied_provider":"coder","resource_type":"coder_app","resource_name":"gateway","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_container.workspace[0]: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212548+02:00","change":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"Plan: 6 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212554+02:00","changes":{"add":6,"change":0,"import":0,"remove":0,"operation":"plan"},"type":"change_summary"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.212995+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":80,"column":42,"byte":1659},"end":{"line":80,"column":48,"byte":1665}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" name = \"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}\"","start_line":80,"highlight_start_offset":41,"highlight_end_offset":47,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.213403+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":99,"column":36,"byte":2293},"end":{"line":99,"column":42,"byte":2299}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner","start_line":99,"highlight_start_offset":35,"highlight_end_offset":41,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.213773+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner_id\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":103,"column":36,"byte":2379},"end":{"line":103,"column":45,"byte":2388}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner_id","start_line":103,"highlight_start_offset":35,"highlight_end_offset":44,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.214120+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":80,"column":42,"byte":1659},"end":{"line":80,"column":48,"byte":1665}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" name = \"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}\"","start_line":80,"highlight_start_offset":41,"highlight_end_offset":47,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.214471+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":99,"column":36,"byte":2293},"end":{"line":99,"column":42,"byte":2299}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner","start_line":99,"highlight_start_offset":35,"highlight_end_offset":41,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.215417+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner_id\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":103,"column":36,"byte":2379},"end":{"line":103,"column":45,"byte":2388}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner_id","start_line":103,"highlight_start_offset":35,"highlight_end_offset":44,"values":[]}},"type":"diagnostic"} +-- apply -- +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.551400+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.657040+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"null_resource.force_apply: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.657084+02:00","change":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.657091+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_script.oops: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.657102+02:00","change":{"resource":{"addr":"coder_script.oops","module":"","resource":"coder_script.oops","implied_provider":"coder","resource_type":"coder_script","resource_name":"oops","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"module.jetbrains_gateway.coder_app.gateway: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.657112+02:00","change":{"resource":{"addr":"module.jetbrains_gateway.coder_app.gateway","module":"module.jetbrains_gateway","resource":"coder_app.gateway","implied_provider":"coder","resource_type":"coder_app","resource_name":"gateway","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_container.workspace[0]: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.657117+02:00","change":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"null_resource.force_apply: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.733209+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"coder_agent.main: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.733249+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"null_resource.force_apply: Provisioning with 'local-exec'...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.734017+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec"},"type":"provision_start"} +{"@level":"info","@message":"coder_agent.main: Creation complete after 0s [id=ddf1ac02-0871-4ce6-8586-2ae40dedd108]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.734361+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"ddf1ac02-0871-4ce6-8586-2ae40dedd108","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): Executing: [\"/bin/sh\" \"-c\" \"terraform refresh -target=data.http.example\"]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.734443+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"Executing: [\"/bin/sh\" \"-c\" \"terraform refresh -target=data.http.example\"]"},"type":"provision_progress"} +{"@level":"info","@message":"docker_image.main: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.743474+02:00","hook":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"coder_script.oops: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.743860+02:00","hook":{"resource":{"addr":"coder_script.oops","module":"","resource":"coder_script.oops","implied_provider":"coder","resource_type":"coder_script","resource_name":"oops","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"module.jetbrains_gateway.coder_app.gateway: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.744322+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.coder_app.gateway","module":"module.jetbrains_gateway","resource":"coder_app.gateway","implied_provider":"coder","resource_type":"coder_app","resource_name":"gateway","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"coder_script.oops: Creation complete after 0s [id=fbb33fcb-70b8-4c35-b8d9-0f861a4d94dc]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.744685+02:00","hook":{"resource":{"addr":"coder_script.oops","module":"","resource":"coder_script.oops","implied_provider":"coder","resource_type":"coder_script","resource_name":"oops","resource_key":null},"action":"create","id_key":"id","id_value":"fbb33fcb-70b8-4c35-b8d9-0f861a4d94dc","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"module.jetbrains_gateway.coder_app.gateway: Creation complete after 0s [id=b18ccb68-1b14-4b79-9711-2fd5231f0ea0]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.749897+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.coder_app.gateway","module":"module.jetbrains_gateway","resource":"coder_app.gateway","implied_provider":"coder","resource_type":"coder_app","resource_name":"gateway","resource_key":null},"action":"create","id_key":"id","id_value":"b18ccb68-1b14-4b79-9711-2fd5231f0ea0","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_image.main: Creation complete after 0s [id=sha256:3fba0c87fcc8ba126bf99e4ee205b43c91ffc6b15bb052315312e71bc6296551busybox]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.764011+02:00","hook":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"sha256:3fba0c87fcc8ba126bf99e4ee205b43c91ffc6b15bb052315312e71bc6296551busybox","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_container.workspace[0]: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.772885+02:00","hook":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m╷\u001b[0m\u001b[0m","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.810643+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m╷\u001b[0m\u001b[0m"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m\u001b[1m\u001b[31mError: \u001b[0m\u001b[0m\u001b[1mError acquiring the state lock\u001b[0m","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.810824+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m\u001b[1m\u001b[31mError: \u001b[0m\u001b[0m\u001b[1mError acquiring the state lock\u001b[0m"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.810884+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m\u001b[0mError message: resource temporarily unavailable","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.810897+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m\u001b[0mError message: resource temporarily unavailable"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0mLock Info:","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.810931+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0mLock Info:"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m ID: ee05d38d-92ba-31b5-549c-9fb1da816f40","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.810988+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m ID: ee05d38d-92ba-31b5-549c-9fb1da816f40"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m Path: terraform.tfstate","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811118+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m Path: terraform.tfstate"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m Operation: OperationTypeApply","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811150+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m Operation: OperationTypeApply"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m Who: danny@Dannys-MBP.Dlink","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811189+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m Who: danny@Dannys-MBP.Dlink"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m Version: 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811205+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m Version: 1.9.2"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m Created: 2024-08-15 08:09:36.581953 +0000 UTC","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811244+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m Created: 2024-08-15 08:09:36.581953 +0000 UTC"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m Info:","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811270+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m Info:"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811372+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0m","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811438+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0m"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0mTerraform acquires a state lock to protect the state from being written","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811526+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0mTerraform acquires a state lock to protect the state from being written"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0mby multiple users at the same time. Please resolve the issue above and try","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811660+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0mby multiple users at the same time. Please resolve the issue above and try"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0magain. For most commands, you can disable locking with the \"-lock=false\"","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811756+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0magain. For most commands, you can disable locking with the \"-lock=false\""},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m│\u001b[0m \u001b[0mflag, but this is not recommended.","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811808+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m│\u001b[0m \u001b[0mflag, but this is not recommended."},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec): \u001b[31m╵\u001b[0m\u001b[0m","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811859+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec","output":"\u001b[31m╵\u001b[0m\u001b[0m"},"type":"provision_progress"} +{"@level":"info","@message":"null_resource.force_apply: (local-exec) Provisioning errored","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.811901+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"provisioner":"local-exec"},"type":"provision_errored"} +{"@level":"info","@message":"null_resource.force_apply: Creation errored after 0s","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:36.812108+02:00","hook":{"resource":{"addr":"null_resource.force_apply","module":"","resource":"null_resource.force_apply","implied_provider":"null","resource_type":"null_resource","resource_name":"force_apply","resource_key":null},"action":"create","elapsed_seconds":0},"type":"apply_errored"} +{"@level":"info","@message":"docker_container.workspace[0]: Creation complete after 0s [id=d1b7a49ed5999b9d04b9ccf399988906f39e8c760c9dd853f9bd0aac9c1c7676]","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.307787+02:00","hook":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create","id_key":"id","id_value":"d1b7a49ed5999b9d04b9ccf399988906f39e8c760c9dd853f9bd0aac9c1c7676","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.321624+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":80,"column":42,"byte":1659},"end":{"line":80,"column":48,"byte":1665}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" name = \"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}\"","start_line":80,"highlight_start_offset":41,"highlight_end_offset":47,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.322400+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":99,"column":36,"byte":2293},"end":{"line":99,"column":42,"byte":2299}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner","start_line":99,"highlight_start_offset":35,"highlight_end_offset":41,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.322919+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner_id\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":103,"column":36,"byte":2379},"end":{"line":103,"column":45,"byte":2388}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner_id","start_line":103,"highlight_start_offset":35,"highlight_end_offset":44,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.323286+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":80,"column":42,"byte":1659},"end":{"line":80,"column":48,"byte":1665}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" name = \"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}\"","start_line":80,"highlight_start_offset":41,"highlight_end_offset":47,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.323631+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":99,"column":36,"byte":2293},"end":{"line":99,"column":42,"byte":2299}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner","start_line":99,"highlight_start_offset":35,"highlight_end_offset":41,"values":[]}},"type":"diagnostic"} +{"@level":"warn","@message":"Warning: Deprecated attribute","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.323976+02:00","diagnostic":{"severity":"warning","summary":"Deprecated attribute","detail":"The attribute \"owner_id\" is deprecated. Refer to the provider documentation for details.","range":{"filename":"main.tf","start":{"line":103,"column":36,"byte":2379},"end":{"line":103,"column":45,"byte":2388}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" value = data.coder_workspace.me.owner_id","start_line":103,"highlight_start_offset":35,"highlight_end_offset":44,"values":[]}},"type":"diagnostic"} +{"@level":"error","@message":"Error: local-exec provisioner error","@module":"terraform.ui","@timestamp":"2024-08-15T10:09:37.324321+02:00","diagnostic":{"severity":"error","summary":"local-exec provisioner error","detail":"Error running command 'terraform refresh -target=data.http.example': exit status 1. Output: \u001b[31m╷\u001b[0m\u001b[0m\n\u001b[31m│\u001b[0m \u001b[0m\u001b[1m\u001b[31mError: \u001b[0m\u001b[0m\u001b[1mError acquiring the state lock\u001b[0m\n\u001b[31m│\u001b[0m \u001b[0m\n\u001b[31m│\u001b[0m \u001b[0m\u001b[0mError message: resource temporarily unavailable\n\u001b[31m│\u001b[0m \u001b[0mLock Info:\n\u001b[31m│\u001b[0m \u001b[0m ID: ee05d38d-92ba-31b5-549c-9fb1da816f40\n\u001b[31m│\u001b[0m \u001b[0m Path: terraform.tfstate\n\u001b[31m│\u001b[0m \u001b[0m Operation: OperationTypeApply\n\u001b[31m│\u001b[0m \u001b[0m Who: danny@Dannys-MBP.Dlink\n\u001b[31m│\u001b[0m \u001b[0m Version: 1.9.2\n\u001b[31m│\u001b[0m \u001b[0m Created: 2024-08-15 08:09:36.581953 +0000 UTC\n\u001b[31m│\u001b[0m \u001b[0m Info: \n\u001b[31m│\u001b[0m \u001b[0m\n\u001b[31m│\u001b[0m \u001b[0m\n\u001b[31m│\u001b[0m \u001b[0mTerraform acquires a state lock to protect the state from being written\n\u001b[31m│\u001b[0m \u001b[0mby multiple users at the same time. Please resolve the issue above and try\n\u001b[31m│\u001b[0m \u001b[0magain. For most commands, you can disable locking with the \"-lock=false\"\n\u001b[31m│\u001b[0m \u001b[0mflag, but this is not recommended.\n\u001b[31m╵\u001b[0m\u001b[0m\n","address":"null_resource.force_apply","range":{"filename":"main.tf","start":{"line":55,"column":28,"byte":1074},"end":{"line":55,"column":29,"byte":1075}},"snippet":{"context":"resource \"null_resource\" \"force_apply\"","code":" provisioner \"local-exec\" {","start_line":55,"highlight_start_offset":27,"highlight_end_offset":28,"values":[]}},"type":"diagnostic"} +-- timings -- +{"start":"2024-08-15T08:09:36.198205Z","end":"2024-08-15T08:09:36.199241Z","action":"read","source":"coder","resource":"data.coder_parameter.argument","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.198207Z","end":"2024-08-15T08:09:36.200058Z","action":"read","source":"coder","resource":"data.coder_workspace.me","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.200671Z","end":"2024-08-15T08:09:36.201032Z","action":"read","source":"coder","resource":"data.coder_provisioner.me","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.200993Z","end":"2024-08-15T08:09:36.201434Z","action":"read","source":"coder","resource":"module.jetbrains_gateway.data.coder_workspace.me","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.201143Z","end":"2024-08-15T08:09:36.201824Z","action":"read","source":"coder","resource":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide","stage":"plan","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.733209Z","end":"2024-08-15T08:09:36.812108Z","action":"create","source":"null","resource":"null_resource.force_apply","stage":"apply","state":"FAILED"} +{"start":"2024-08-15T08:09:36.733249Z","end":"2024-08-15T08:09:36.734361Z","action":"create","source":"coder","resource":"coder_agent.main","stage":"apply","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.734017Z","end":"2024-08-15T08:09:36.811901Z","action":"provision","source":"null","resource":"null_resource.force_apply","stage":"apply","state":"FAILED"} +{"start":"2024-08-15T08:09:36.743474Z","end":"2024-08-15T08:09:36.764011Z","action":"create","source":"docker","resource":"docker_image.main","stage":"apply","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.743860Z","end":"2024-08-15T08:09:36.744685Z","action":"create","source":"coder","resource":"coder_script.oops","stage":"apply","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.744322Z","end":"2024-08-15T08:09:36.749897Z","action":"create","source":"coder","resource":"module.jetbrains_gateway.coder_app.gateway","stage":"apply","state":"COMPLETED"} +{"start":"2024-08-15T08:09:36.772885Z","end":"2024-08-15T08:09:37.307787Z","action":"create","source":"docker","resource":"docker_container.workspace[0]","stage":"apply","state":"COMPLETED"} \ No newline at end of file diff --git a/provisioner/terraform/testdata/timings-aggregation/init.txtar b/provisioner/terraform/testdata/timings-aggregation/init.txtar new file mode 100644 index 0000000000000..df9db78255d51 --- /dev/null +++ b/provisioner/terraform/testdata/timings-aggregation/init.txtar @@ -0,0 +1,38 @@ +Init produces JSON logs, but not with discrete fields which we can parse. +It only gained the ability to output JSON logs in v1.9.0 (https://github.com/hashicorp/terraform/blob/v1.9/CHANGELOG.md#190-june-26-2024), +so I've included the non-JSON logs as well. + +Neither one produces any timings. + +-- init -- +# Before v1.9.0 +Initializing the backend... +Initializing modules... +Initializing provider plugins... +- Reusing previous version of hashicorp/http from the dependency lock file +- Reusing previous version of coder/coder from the dependency lock file +- Using previously-installed hashicorp/http v3.4.4 +- Using previously-installed coder/coder v1.0.1 + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. + +# After v1.9.0 +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T09:19:30.835464+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"Initializing the backend...","@module":"terraform.ui","@timestamp":"2024-08-15T07:19:30Z","message_code":"initializing_backend_message","type":"init_output"} +{"@level":"info","@message":"Initializing modules...","@module":"terraform.ui","@timestamp":"2024-08-15T07:19:30Z","message_code":"initializing_modules_message","type":"init_output"} +{"@level":"info","@message":"Initializing provider plugins...","@module":"terraform.ui","@timestamp":"2024-08-15T07:19:30Z","message_code":"initializing_provider_plugin_message","type":"init_output"} +{"@level":"info","@message":"coder/coder: Reusing previous version from the dependency lock file","@module":"terraform.ui","@timestamp":"2024-08-15T09:19:30.870861+02:00","type":"log"} +{"@level":"info","@message":"hashicorp/http: Reusing previous version from the dependency lock file","@module":"terraform.ui","@timestamp":"2024-08-15T09:19:31.282247+02:00","type":"log"} +{"@level":"info","@message":"coder/coder v1.0.1: Using previously-installed provider version","@module":"terraform.ui","@timestamp":"2024-08-15T09:19:31.466355+02:00","type":"log"} +{"@level":"info","@message":"hashicorp/http v3.4.4: Using previously-installed provider version","@module":"terraform.ui","@timestamp":"2024-08-15T09:19:31.479221+02:00","type":"log"} +{"@level":"info","@message":"Terraform has been successfully initialized!","@module":"terraform.ui","@timestamp":"2024-08-15T07:19:31Z","message_code":"output_init_success_message","type":"init_output"} +{"@level":"info","@message":"You may now begin working with Terraform. Try running \"terraform plan\" to see\nany changes that are required for your infrastructure. All Terraform commands\nshould now work.\n\nIf you ever set or change modules or backend configuration for Terraform,\nrerun this command to reinitialize your working directory. If you forget, other\ncommands will detect it and remind you to do so if necessary.","@module":"terraform.ui","@timestamp":"2024-08-15T07:19:31Z","message_code":"output_init_success_cli_message","type":"init_output"} +-- timings -- \ No newline at end of file diff --git a/provisioner/terraform/testdata/timings-aggregation/simple.txtar b/provisioner/terraform/testdata/timings-aggregation/simple.txtar new file mode 100644 index 0000000000000..79ed62de83dd1 --- /dev/null +++ b/provisioner/terraform/testdata/timings-aggregation/simple.txtar @@ -0,0 +1,7 @@ +The presence of an apply_start and apply_complete on the same resource results in a complete timing. + +-- plan -- +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-14T16:29:29.953727+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide","module":"module.jetbrains_gateway","resource":"data.coder_parameter.jetbrains_ide","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"jetbrains_ide","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide: Refresh complete after 0s [id=60e62a98-97e4-459b-9af2-617ea9ccc385]","@module":"terraform.ui","@timestamp":"2024-08-14T16:29:29.955272+02:00","hook":{"resource":{"addr":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide","module":"module.jetbrains_gateway","resource":"data.coder_parameter.jetbrains_ide","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"jetbrains_ide","resource_key":null},"action":"read","id_key":"id","id_value":"60e62a98-97e4-459b-9af2-617ea9ccc385","elapsed_seconds":0},"type":"apply_complete"} +-- timings -- +{"start":"2024-08-14T14:29:29.953727Z", "end":"2024-08-14T14:29:29.955272Z", "action":"read", "source":"coder", "resource":"module.jetbrains_gateway.data.coder_parameter.jetbrains_ide", "stage":"plan", "state":"COMPLETED"} \ No newline at end of file diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index 25927bc93e2e6..d6a2ac96e9987 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -16,7 +16,7 @@ import ( type timingKind string -// Copied from https://github.com/hashicorp/terraform/blob/ffbcaf8bef12bb1f4d79f06437f414e280d08761/internal/command/views/json/message_types.go +// Copied from https://github.com/hashicorp/terraform/blob/01c0480e77263933b2b086dc8d600a69f80fad2d/internal/command/jsonformat/renderer.go // We cannot reference these because they're in an internal package. const ( timingApplyStart timingKind = "apply_start" @@ -29,6 +29,13 @@ const ( timingProvisionErrored timingKind = "provision_errored" timingRefreshStart timingKind = "refresh_start" timingRefreshComplete timingKind = "refresh_complete" + // Ignored. + timingChangeSummary timingKind = "change_summary" + timingDiagnostic timingKind = "diagnostic" + timingPlannedChange timingKind = "planned_change" + timingOutputs timingKind = "outputs" + timingResourceDrift timingKind = "resource_drift" + timingVersion timingKind = "version" // These are not part of message_types, but we want to track init/graph timings as well. timingInitStart timingKind = "init_start" timingInitComplete timingKind = "init_complete" @@ -36,6 +43,9 @@ const ( timingGraphStart timingKind = "graph_start" timingGraphComplete timingKind = "graph_complete" timingGraphErrored timingKind = "graph_errored" + // Other terraform log types which we ignore. + timingLog timingKind = "log" + timingInitOutput timingKind = "init_output" ) type timingAggregator struct { @@ -84,7 +94,7 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { s.end = ts s.state = proto.TimingState_FAILED default: - // Don't capture progress messages (or unhandled kinds); we just want start/end timings. + // We just want start/end timings, ignore all other events. return } @@ -95,8 +105,9 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { t.mu.Unlock() } -// aggregate performs a pass through all memoized events to build up a set of *proto.Timing instances which represent +// aggregate performs a pass through all memoized events to build up a slice of *proto.Timing instances which represent // the total time taken to perform a certain action. +// The resulting slice of *proto.Timing is NOT sorted. func (t *timingAggregator) aggregate() []*proto.Timing { t.mu.Lock() defer t.mu.Unlock() @@ -147,12 +158,20 @@ func (l timingKind) Valid() bool { timingProvisionErrored, timingRefreshStart, timingRefreshComplete, + timingChangeSummary, + timingDiagnostic, + timingPlannedChange, + timingOutputs, + timingResourceDrift, + timingVersion, timingInitStart, timingInitComplete, timingInitErrored, timingGraphStart, timingGraphComplete, timingGraphErrored, + timingLog, + timingInitOutput, }, l) } diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go new file mode 100644 index 0000000000000..0444c18013519 --- /dev/null +++ b/provisioner/terraform/timings_internal_test.go @@ -0,0 +1,215 @@ +package terraform + +import ( + "bufio" + "bytes" + _ "embed" + "slices" + "testing" + + "github.com/cespare/xxhash" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/tools/txtar" + "google.golang.org/protobuf/encoding/protojson" + protobuf "google.golang.org/protobuf/proto" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/provisionersdk/proto" +) + +var ( + //go:embed testdata/timings-aggregation/simple.txtar + inputSimple []byte + //go:embed testdata/timings-aggregation/init.txtar + inputInit []byte + //go:embed testdata/timings-aggregation/error.txtar + inputError []byte + //go:embed testdata/timings-aggregation/complete.txtar + inputComplete []byte +) + +func TestAggregation(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input []byte + }{ + { + name: "init", + input: inputInit, + }, + { + name: "simple", + input: inputSimple, + }, + { + name: "error", + input: inputError, + }, + { + name: "complete", + input: inputComplete, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // txtar is a text-based archive format used in the stdlib for simple and elegant tests. + // + // We ALWAYS expect that the archive contains two or more "files": + // 1. JSON logs generated by a terraform execution, one per line, *one file per stage* + // N. Expected resulting timings in JSON form, one per line + arc := txtar.Parse(tc.input) + require.GreaterOrEqual(t, len(arc.Files), 2) + + t.Logf("%s: %s", t.Name(), arc.Comment) + + var actualTimings []*proto.Timing + expectedTimings := arc.Files[len(arc.Files)-1] + + for i := 0; i < len(arc.Files)-1; i++ { + file := arc.Files[i] + stage := database.ProvisionerJobTimingStage(file.Name) + require.Truef(t, stage.Valid(), "%q is not a valid stage name; acceptable values: %v", + file.Name, database.AllProvisionerJobTimingStageValues()) + + agg := newTimingAggregator(stage) + extractAllSpans(t, file.Data, agg) + actualTimings = append(actualTimings, agg.aggregate()...) + } + + stableSortTimings(t, actualTimings) // To reduce flakiness. + require.True(t, timingsAreEqual(t, expectedTimings.Data, actualTimings)) + }) + } +} + +func timingsAreEqual(t *testing.T, input []byte, actual []*proto.Timing) bool { + t.Helper() + + // Parse the input into *proto.Timing structs. + var expected []*proto.Timing + scanner := bufio.NewScanner(bytes.NewBuffer(input)) + for scanner.Scan() { + line := scanner.Bytes() + + var msg proto.Timing + require.NoError(t, protojson.Unmarshal(line, &msg)) + + expected = append(expected, &msg) + } + require.NoError(t, scanner.Err()) + + // Shortcut check. + if len(expected)+len(actual) == 0 { + t.Logf("both timings are empty") + return true + } + + // Shortcut check. + if len(expected) != len(actual) { + t.Logf("timings lengths are not equal: %d != %d", len(expected), len(actual)) + printExpectation(t, actual) + return false + } + + // Compare each element; both are expected to be sorted in a stable manner. + for i := 0; i < len(expected); i++ { + ex := expected[i] + ac := actual[i] + if !protobuf.Equal(ex, ac) { + t.Logf("timings are not equivalent: %q != %q", ex.String(), ac.String()) + printExpectation(t, actual) + return false + } + } + + return true +} + +func extractAllSpans(t *testing.T, input []byte, aggregator *timingAggregator) { + t.Helper() + + scanner := bufio.NewScanner(bytes.NewBuffer(input)) + for scanner.Scan() { + line := scanner.Bytes() + log := parseTerraformLogLine(line) + if log == nil { + continue + } + + ts, span, err := extractTimingSpan(log) + if err != nil { + // t.Logf("%s: failed span extraction on line: %q", err, line) + continue + } + + require.NotZerof(t, ts, "failed on line: %q", line) + require.NotNilf(t, span, "failed on line: %q", line) + + aggregator.ingest(ts, span) + } + + require.NoError(t, scanner.Err()) +} + +func printExpectation(t *testing.T, actual []*proto.Timing) { + t.Helper() + + t.Log("expected:") + for _, a := range actual { + printTiming(t, a) + } +} + +func printTiming(t *testing.T, timing *proto.Timing) { + t.Helper() + + marshaler := protojson.MarshalOptions{ + Multiline: false, // Ensure it's set to false for single-line JSON + Indent: "", // No indentation + } + + out, err := marshaler.Marshal(timing) + assert.NoError(t, err) + t.Logf("%s", out) +} + +func stableSortTimings(t *testing.T, timings []*proto.Timing) { + slices.SortStableFunc(timings, func(a, b *proto.Timing) int { + if a == nil || b == nil || a.Start == nil || b.Start == nil { + return 0 + } + + if a.Start.AsTime().Equal(b.Start.AsTime()) { + // Special case: when start times are equal, we need to keep the ordering stable, so we hash both entries + // and sort based on that (since end times could be equal too, in principle). + ah := xxhash.Sum64String(a.String()) + bh := xxhash.Sum64String(b.String()) + + if ah == bh { + // WTF. + t.Logf("identical timings detected!") + printTiming(t, a) + printTiming(t, b) + return 0 + } + + if ah < bh { + return -1 + } + + return 1 + } + + if a.Start.AsTime().Before(b.Start.AsTime()) { + return -1 + } + + return 1 + }) +} From 275bfcadd6878dcaa1eb789a78dc0968f25be63d Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 11:39:17 +0200 Subject: [PATCH 08/22] lint/fmt Signed-off-by: Danny Kopping --- coderd/database/dbmem/dbmem.go | 23 +++++++++++++++---- coderd/database/dbtime/dbtime.go | 2 +- .../provisionerdserver/provisionerdserver.go | 1 + coderd/workspacebuilds.go | 1 + provisioner/terraform/executor.go | 3 ++- .../terraform/timings_internal_test.go | 1 + provisionerd/runner/runner.go | 4 ++-- 7 files changed, 27 insertions(+), 8 deletions(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 7e7516f080d7b..69b4005ba36d3 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3172,13 +3172,28 @@ func (q *FakeQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) ( return q.getProvisionerJobByIDNoLock(ctx, id) } -func (q *FakeQuerier) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { +func (*FakeQuerier) GetProvisionerJobStatsByWorkspace(_ context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { err := validateDatabaseType(arg) if err != nil { return database.ProvisionerJobStat{}, err } - panic("not implemented") + return database.ProvisionerJobStat{ + JobID: arg.JobID, + JobStatus: database.ProvisionerJobStatusSucceeded, + WorkspaceID: arg.WorkspaceID, + WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, + Error: sql.NullString{}, + ErrorCode: sql.NullString{}, + UpdatedAt: dbtime.Now(), + QueuedSecs: 0.1, + CompletionSecs: 10, + CanceledSecs: 0, + InitSecs: 1, + PlanSecs: 2, + GraphSecs: 3, + ApplySecs: 4, + }, nil } func (q *FakeQuerier) GetProvisionerJobsByIDs(_ context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { @@ -6642,13 +6657,13 @@ func (q *FakeQuerier) InsertProvisionerJobLogs(_ context.Context, arg database.I return logs, nil } -func (q *FakeQuerier) InsertProvisionerJobTimings(ctx context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) { +func (*FakeQuerier) InsertProvisionerJobTimings(_ context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) { err := validateDatabaseType(arg) if err != nil { return nil, err } - panic("not implemented") + return nil, nil } func (q *FakeQuerier) InsertProvisionerKey(_ context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) { diff --git a/coderd/database/dbtime/dbtime.go b/coderd/database/dbtime/dbtime.go index 8970423864d94..a2f88fcdb8e91 100644 --- a/coderd/database/dbtime/dbtime.go +++ b/coderd/database/dbtime/dbtime.go @@ -11,4 +11,4 @@ func Now() time.Time { // microsecond precision. func Time(t time.Time) time.Time { return t.UTC().Round(time.Microsecond) -} \ No newline at end of file +} diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 80e9f329c1979..04903395162a4 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1442,6 +1442,7 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) } // Insert timings outside transaction since it is metadata. + // nolint:exhaustruct // The other fields are set further down. params := database.InsertProvisionerJobTimingsParams{ JobID: jobID, } diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 86123c4947b92..be8b37f460cd8 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -676,6 +676,7 @@ func (api *API) workspaceBuildStats(rw http.ResponseWriter, r *http.Request) { Message: "Failed to get workspace build stats", Detail: err.Error(), }) + return } httpapi.Write(ctx, rw, http.StatusOK, convertWorkspaceBuildStats(pj)) diff --git a/provisioner/terraform/executor.go b/provisioner/terraform/executor.go index 06a6edaeb5b08..08231489f40a2 100644 --- a/provisioner/terraform/executor.go +++ b/provisioner/terraform/executor.go @@ -15,12 +15,13 @@ import ( "sync" "time" - "cdr.dev/slog" "github.com/hashicorp/go-version" tfjson "github.com/hashicorp/terraform-json" "go.opentelemetry.io/otel/attribute" "golang.org/x/xerrors" + "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/provisionersdk/proto" diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go index 0444c18013519..bf30f59187740 100644 --- a/provisioner/terraform/timings_internal_test.go +++ b/provisioner/terraform/timings_internal_test.go @@ -54,6 +54,7 @@ func TestAggregation(t *testing.T) { }, } + // nolint:paralleltest // Not since go v1.22. for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() diff --git a/provisionerd/runner/runner.go b/provisionerd/runner/runner.go index 7ec959bde9a46..edeb5d80f3194 100644 --- a/provisionerd/runner/runner.go +++ b/provisionerd/runner/runner.go @@ -1012,7 +1012,7 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p Error: applyComplete.Error, Type: &proto.FailedJob_WorkspaceBuild_{ WorkspaceBuild: &proto.FailedJob_WorkspaceBuild{ - State: applyComplete.State, + State: applyComplete.State, Timings: applyComplete.Timings, }, }, @@ -1032,7 +1032,7 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{ State: applyComplete.State, Resources: applyComplete.Resources, - Timings: applyComplete.Timings, + Timings: applyComplete.Timings, }, }, }, nil From 73bac3f4f020432f38a158ca81d9b593aa1844ad Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 14:42:23 +0200 Subject: [PATCH 09/22] Moar tests Signed-off-by: Danny Kopping --- ...> 000245_provisioner_job_timings.down.sql} | 0 ... => 000245_provisioner_job_timings.up.sql} | 0 .../timings-aggregation/fake-terraform.sh | 148 ++++++++++++++++++ provisioner/terraform/testutil.go | 140 +++++++++++++++++ .../terraform/timings_internal_test.go | 134 +--------------- provisioner/terraform/timings_test.go | 131 ++++++++++++++++ 6 files changed, 426 insertions(+), 127 deletions(-) rename coderd/database/migrations/{000244_provisioner_job_timings.down.sql => 000245_provisioner_job_timings.down.sql} (100%) rename coderd/database/migrations/{000244_provisioner_job_timings.up.sql => 000245_provisioner_job_timings.up.sql} (100%) create mode 100755 provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh create mode 100644 provisioner/terraform/testutil.go create mode 100644 provisioner/terraform/timings_test.go diff --git a/coderd/database/migrations/000244_provisioner_job_timings.down.sql b/coderd/database/migrations/000245_provisioner_job_timings.down.sql similarity index 100% rename from coderd/database/migrations/000244_provisioner_job_timings.down.sql rename to coderd/database/migrations/000245_provisioner_job_timings.down.sql diff --git a/coderd/database/migrations/000244_provisioner_job_timings.up.sql b/coderd/database/migrations/000245_provisioner_job_timings.up.sql similarity index 100% rename from coderd/database/migrations/000244_provisioner_job_timings.up.sql rename to coderd/database/migrations/000245_provisioner_job_timings.up.sql diff --git a/provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh b/provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh new file mode 100755 index 0000000000000..cf4949d1d0d6d --- /dev/null +++ b/provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh @@ -0,0 +1,148 @@ +#!/bin/bash + +function terraform_version() { + cat <<'EOL' +{ + "terraform_version": "1.9.2", + "platform": "darwin_arm64", + "provider_selections": {}, + "terraform_outdated": true +} +EOL +} + +function terraform_show() { + cat <<'EOL' +{"format_version":"1.2","terraform_version":"1.5.7","planned_values":{"root_module":{"resources":[{"address":"coder_agent.main","mode":"managed","type":"coder_agent","name":"main","provider_name":"registry.terraform.io/coder/coder","schema_version":1,"values":{"arch":"arm64","auth":"token","connection_timeout":120,"dir":null,"env":null,"login_before_ready":true,"metadata":[{"display_name":"CPU Usage","interval":10,"key":"0_cpu_usage","order":null,"script":"coder stat cpu","timeout":1},{"display_name":"RAM Usage","interval":10,"key":"1_ram_usage","order":null,"script":"coder stat mem","timeout":1}],"motd_file":null,"order":null,"os":"linux","shutdown_script":null,"shutdown_script_timeout":300,"startup_script":null,"startup_script_behavior":null,"startup_script_timeout":300,"troubleshooting_url":null},"sensitive_values":{"display_apps":[],"metadata":[{},{}]}},{"address":"docker_container.workspace[0]","mode":"managed","type":"docker_container","name":"workspace","index":0,"provider_name":"registry.terraform.io/kreuzwerker/docker","schema_version":2,"values":{"attach":false,"capabilities":[],"cgroupns_mode":null,"container_read_refresh_timeout_milliseconds":15000,"cpu_set":null,"cpu_shares":null,"destroy_grace_seconds":null,"devices":[],"dns":null,"dns_opts":null,"dns_search":null,"domainname":null,"gpus":null,"group_add":null,"host":[{"host":"host.docker.internal","ip":"host-gateway"}],"hostname":"barry1723722791","image":"nginx:latest","labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name","value":"barry1723722791"}],"log_opts":null,"logs":false,"max_retry_count":null,"memory":100,"memory_swap":null,"mounts":[],"must_run":true,"name":"coder-danny-barry1723722791","network_mode":null,"networks_advanced":[],"pid_mode":null,"ports":[],"privileged":null,"publish_all_ports":null,"read_only":false,"remove_volumes":true,"restart":"always","rm":false,"start":true,"stdin_open":false,"storage_opts":null,"sysctls":null,"tmpfs":null,"tty":false,"ulimit":[],"upload":[],"user":null,"userns_mode":null,"volumes":[{"container_path":"/home/danny","from_container":"","host_path":"","read_only":false,"volume_name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"}],"wait":false,"wait_timeout":60,"working_dir":null},"sensitive_values":{"capabilities":[],"command":[],"devices":[],"entrypoint":[],"env":true,"healthcheck":[],"host":[{}],"labels":[{},{},{},{}],"mounts":[],"network_data":[],"networks_advanced":[],"ports":[],"security_opts":[],"ulimit":[],"upload":[],"volumes":[{}]}},{"address":"docker_image.main","mode":"managed","type":"docker_image","name":"main","provider_name":"registry.terraform.io/kreuzwerker/docker","schema_version":0,"values":{"build":[],"force_remove":null,"keep_locally":true,"name":"nginx:latest","platform":null,"pull_triggers":null,"triggers":null},"sensitive_values":{"build":[]}},{"address":"docker_volume.home_volume","mode":"managed","type":"docker_volume","name":"home_volume","provider_name":"registry.terraform.io/kreuzwerker/docker","schema_version":1,"values":{"driver_opts":null,"labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name_at_creation","value":"barry1723722791"}],"name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"},"sensitive_values":{"labels":[{},{},{},{}]}}]}},"resource_changes":[{"address":"coder_agent.main","mode":"managed","type":"coder_agent","name":"main","provider_name":"registry.terraform.io/coder/coder","change":{"actions":["create"],"before":null,"after":{"arch":"arm64","auth":"token","connection_timeout":120,"dir":null,"env":null,"login_before_ready":true,"metadata":[{"display_name":"CPU Usage","interval":10,"key":"0_cpu_usage","order":null,"script":"coder stat cpu","timeout":1},{"display_name":"RAM Usage","interval":10,"key":"1_ram_usage","order":null,"script":"coder stat mem","timeout":1}],"motd_file":null,"order":null,"os":"linux","shutdown_script":null,"shutdown_script_timeout":300,"startup_script":null,"startup_script_behavior":null,"startup_script_timeout":300,"troubleshooting_url":null},"after_unknown":{"display_apps":true,"id":true,"init_script":true,"metadata":[{},{}],"token":true},"before_sensitive":false,"after_sensitive":{"display_apps":[],"metadata":[{},{}],"token":true}}},{"address":"docker_container.workspace[0]","mode":"managed","type":"docker_container","name":"workspace","index":0,"provider_name":"registry.terraform.io/kreuzwerker/docker","change":{"actions":["create"],"before":null,"after":{"attach":false,"capabilities":[],"cgroupns_mode":null,"container_read_refresh_timeout_milliseconds":15000,"cpu_set":null,"cpu_shares":null,"destroy_grace_seconds":null,"devices":[],"dns":null,"dns_opts":null,"dns_search":null,"domainname":null,"gpus":null,"group_add":null,"host":[{"host":"host.docker.internal","ip":"host-gateway"}],"hostname":"barry1723722791","image":"nginx:latest","labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name","value":"barry1723722791"}],"log_opts":null,"logs":false,"max_retry_count":null,"memory":100,"memory_swap":null,"mounts":[],"must_run":true,"name":"coder-danny-barry1723722791","network_mode":null,"networks_advanced":[],"pid_mode":null,"ports":[],"privileged":null,"publish_all_ports":null,"read_only":false,"remove_volumes":true,"restart":"always","rm":false,"start":true,"stdin_open":false,"storage_opts":null,"sysctls":null,"tmpfs":null,"tty":false,"ulimit":[],"upload":[],"user":null,"userns_mode":null,"volumes":[{"container_path":"/home/danny","from_container":"","host_path":"","read_only":false,"volume_name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"}],"wait":false,"wait_timeout":60,"working_dir":null},"after_unknown":{"bridge":true,"capabilities":[],"command":true,"container_logs":true,"devices":[],"entrypoint":true,"env":true,"exit_code":true,"healthcheck":true,"host":[{}],"id":true,"init":true,"ipc_mode":true,"labels":[{},{},{},{}],"log_driver":true,"mounts":[],"network_data":true,"networks_advanced":[],"ports":[],"runtime":true,"security_opts":true,"shm_size":true,"stop_signal":true,"stop_timeout":true,"ulimit":[],"upload":[],"volumes":[{}]},"before_sensitive":false,"after_sensitive":{"capabilities":[],"command":[],"devices":[],"entrypoint":[],"env":true,"healthcheck":[],"host":[{}],"labels":[{},{},{},{}],"mounts":[],"network_data":[],"networks_advanced":[],"ports":[],"security_opts":[],"ulimit":[],"upload":[],"volumes":[{}]}}},{"address":"docker_image.main","mode":"managed","type":"docker_image","name":"main","provider_name":"registry.terraform.io/kreuzwerker/docker","change":{"actions":["create"],"before":null,"after":{"build":[],"force_remove":null,"keep_locally":true,"name":"nginx:latest","platform":null,"pull_triggers":null,"triggers":null},"after_unknown":{"build":[],"id":true,"image_id":true,"repo_digest":true},"before_sensitive":false,"after_sensitive":{"build":[]}}},{"address":"docker_volume.home_volume","mode":"managed","type":"docker_volume","name":"home_volume","provider_name":"registry.terraform.io/kreuzwerker/docker","change":{"actions":["create"],"before":null,"after":{"driver_opts":null,"labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name_at_creation","value":"barry1723722791"}],"name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"},"after_unknown":{"driver":true,"id":true,"labels":[{},{},{},{}],"mountpoint":true},"before_sensitive":false,"after_sensitive":{"labels":[{},{},{},{}]}}}],"prior_state":{"format_version":"1.0","terraform_version":"1.5.7","values":{"root_module":{"resources":[{"address":"data.coder_parameter.memory_size","mode":"data","type":"coder_parameter","name":"memory_size","provider_name":"registry.terraform.io/coder/coder","schema_version":0,"values":{"default":"100","description":null,"display_name":null,"ephemeral":false,"icon":null,"id":"88f32e48-320b-4b67-a9ef-053150c3f6a7","mutable":true,"name":"Memory Allocation","option":null,"optional":true,"order":null,"type":"number","validation":[],"value":"100"},"sensitive_values":{"validation":[]}},{"address":"data.coder_provisioner.me","mode":"data","type":"coder_provisioner","name":"me","provider_name":"registry.terraform.io/coder/coder","schema_version":0,"values":{"arch":"arm64","id":"5e8c4561-b101-4c60-88e9-097c5c0f73de","os":"darwin"},"sensitive_values":{}},{"address":"data.coder_workspace.me","mode":"data","type":"coder_workspace","name":"me","provider_name":"registry.terraform.io/coder/coder","schema_version":0,"values":{"access_port":3000,"access_url":"http://localhost:3000","id":"1b0cd26b-9e35-4107-8aab-5827419bac68","name":"barry1723722791","owner":"danny","owner_email":"default@example.com","owner_groups":[],"owner_id":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e","owner_name":"default","owner_oidc_access_token":"","owner_session_token":"","start_count":1,"template_id":"","template_name":"","template_version":"","transition":"start"},"sensitive_values":{"owner_groups":[]}}]}}},"configuration":{"provider_config":{"coder":{"name":"coder","full_name":"registry.terraform.io/coder/coder"},"docker":{"name":"docker","full_name":"registry.terraform.io/kreuzwerker/docker"}},"root_module":{"resources":[{"address":"coder_agent.main","mode":"managed","type":"coder_agent","name":"main","provider_config_key":"coder","expressions":{"arch":{"references":["data.coder_provisioner.me.arch","data.coder_provisioner.me"]},"metadata":[{"display_name":{"constant_value":"CPU Usage"},"interval":{"constant_value":10},"key":{"constant_value":"0_cpu_usage"},"script":{"constant_value":"coder stat cpu"},"timeout":{"constant_value":1}},{"display_name":{"constant_value":"RAM Usage"},"interval":{"constant_value":10},"key":{"constant_value":"1_ram_usage"},"script":{"constant_value":"coder stat mem"},"timeout":{"constant_value":1}}],"os":{"constant_value":"linux"}},"schema_version":1},{"address":"docker_container.workspace","mode":"managed","type":"docker_container","name":"workspace","provider_config_key":"docker","expressions":{"entrypoint":{"references":["coder_agent.main.init_script","coder_agent.main"]},"env":{"references":["coder_agent.main.token","coder_agent.main"]},"host":[{"host":{"constant_value":"host.docker.internal"},"ip":{"constant_value":"host-gateway"}}],"hostname":{"references":["data.coder_workspace.me.name","data.coder_workspace.me"]},"image":{"references":["docker_image.main.name","docker_image.main"]},"labels":[{"label":{"constant_value":"coder.owner"},"value":{"references":["data.coder_workspace.me.owner","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.owner_id"},"value":{"references":["data.coder_workspace.me.owner_id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_id"},"value":{"references":["data.coder_workspace.me.id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_name"},"value":{"references":["data.coder_workspace.me.name","data.coder_workspace.me"]}}],"memory":{"references":["data.coder_parameter.memory_size.value","data.coder_parameter.memory_size"]},"name":{"references":["data.coder_workspace.me.owner","data.coder_workspace.me","data.coder_workspace.me.name","data.coder_workspace.me"]},"restart":{"constant_value":"always"},"volumes":[{"container_path":{"references":["local.username"]},"read_only":{"constant_value":false},"volume_name":{"references":["docker_volume.home_volume.name","docker_volume.home_volume"]}}]},"schema_version":2,"count_expression":{"references":["data.coder_workspace.me.start_count","data.coder_workspace.me"]}},{"address":"docker_image.main","mode":"managed","type":"docker_image","name":"main","provider_config_key":"docker","expressions":{"keep_locally":{"constant_value":true},"name":{"constant_value":"nginx:latest"}},"schema_version":0},{"address":"docker_volume.home_volume","mode":"managed","type":"docker_volume","name":"home_volume","provider_config_key":"docker","expressions":{"labels":[{"label":{"constant_value":"coder.owner"},"value":{"references":["data.coder_workspace.me.owner","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.owner_id"},"value":{"references":["data.coder_workspace.me.owner_id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_id"},"value":{"references":["data.coder_workspace.me.id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_name_at_creation"},"value":{"references":["data.coder_workspace.me.name","data.coder_workspace.me"]}}],"name":{"references":["data.coder_workspace.me.id","data.coder_workspace.me"]}},"schema_version":1},{"address":"data.coder_parameter.memory_size","mode":"data","type":"coder_parameter","name":"memory_size","provider_config_key":"coder","expressions":{"default":{"constant_value":"100"},"mutable":{"constant_value":true},"name":{"constant_value":"Memory Allocation"},"type":{"constant_value":"number"}},"schema_version":0},{"address":"data.coder_provisioner.me","mode":"data","type":"coder_provisioner","name":"me","provider_config_key":"coder","schema_version":0},{"address":"data.coder_workspace.me","mode":"data","type":"coder_workspace","name":"me","provider_config_key":"coder","schema_version":0}]}},"relevant_attributes":[{"resource":"docker_volume.home_volume","attribute":["name"]},{"resource":"data.coder_workspace.me","attribute":["owner"]},{"resource":"data.coder_workspace.me","attribute":["owner_id"]},{"resource":"data.coder_workspace.me","attribute":["name"]},{"resource":"data.coder_parameter.memory_size","attribute":["value"]},{"resource":"coder_agent.main","attribute":["init_script"]},{"resource":"docker_image.main","attribute":["name"]},{"resource":"data.coder_provisioner.me","attribute":["arch"]},{"resource":"data.coder_workspace.me","attribute":["id"]},{"resource":"coder_agent.main","attribute":["token"]}],"timestamp":"2024-08-15T11:53:22Z"} +EOL +} + +function terraform_graph() { + cat <<'EOL' +digraph { + compound = "true" + newrank = "true" + subgraph "root" { + "[root] coder_agent.main (expand)" [label = "coder_agent.main", shape = "box"] + "[root] data.coder_parameter.memory_size (expand)" [label = "data.coder_parameter.memory_size", shape = "box"] + "[root] data.coder_provisioner.me (expand)" [label = "data.coder_provisioner.me", shape = "box"] + "[root] data.coder_workspace.me (expand)" [label = "data.coder_workspace.me", shape = "box"] + "[root] docker_container.workspace (expand)" [label = "docker_container.workspace", shape = "box"] + "[root] docker_image.main (expand)" [label = "docker_image.main", shape = "box"] + "[root] docker_volume.home_volume (expand)" [label = "docker_volume.home_volume", shape = "box"] + "[root] provider[\"registry.terraform.io/coder/coder\"]" [label = "provider[\"registry.terraform.io/coder/coder\"]", shape = "diamond"] + "[root] provider[\"registry.terraform.io/kreuzwerker/docker\"]" [label = "provider[\"registry.terraform.io/kreuzwerker/docker\"]", shape = "diamond"] + "[root] coder_agent.main (expand)" -> "[root] data.coder_provisioner.me (expand)" + "[root] data.coder_parameter.memory_size (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]" + "[root] data.coder_provisioner.me (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]" + "[root] data.coder_workspace.me (expand)" -> "[root] provider[\"registry.terraform.io/coder/coder\"]" + "[root] docker_container.workspace (expand)" -> "[root] coder_agent.main (expand)" + "[root] docker_container.workspace (expand)" -> "[root] data.coder_parameter.memory_size (expand)" + "[root] docker_container.workspace (expand)" -> "[root] docker_image.main (expand)" + "[root] docker_container.workspace (expand)" -> "[root] docker_volume.home_volume (expand)" + "[root] docker_container.workspace (expand)" -> "[root] local.username (expand)" + "[root] docker_image.main (expand)" -> "[root] provider[\"registry.terraform.io/kreuzwerker/docker\"]" + "[root] docker_volume.home_volume (expand)" -> "[root] data.coder_workspace.me (expand)" + "[root] docker_volume.home_volume (expand)" -> "[root] provider[\"registry.terraform.io/kreuzwerker/docker\"]" + "[root] local.username (expand)" -> "[root] data.coder_workspace.me (expand)" + "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] coder_agent.main (expand)" + "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] data.coder_parameter.memory_size (expand)" + "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" -> "[root] data.coder_workspace.me (expand)" + "[root] provider[\"registry.terraform.io/kreuzwerker/docker\"] (close)" -> "[root] docker_container.workspace (expand)" + "[root] root" -> "[root] provider[\"registry.terraform.io/coder/coder\"] (close)" + "[root] root" -> "[root] provider[\"registry.terraform.io/kreuzwerker/docker\"] (close)" + } +} +EOL +} + +function terraform_init() { + cat <<'EOL' + +Initializing the backend... + +Initializing provider plugins... +- Reusing previous version of coder/coder from the dependency lock file +- Reusing previous version of kreuzwerker/docker from the dependency lock file +- Using previously-installed coder/coder v1.0.1 +- Using previously-installed kreuzwerker/docker v3.0.2 + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. +EOL +} + +function terraform_plan() { + cat <<'EOL' +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:38.097648+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"data.coder_workspace.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_parameter.memory_size: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_parameter.memory_size","module":"","resource":"data.coder_parameter.memory_size","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"memory_size","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_provisioner.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +{"@level":"info","@message":"data.coder_provisioner.me: Refresh complete after 0s [id=2470b3d2-32f4-4f95-ac70-0971efdb8338]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.195712+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"2470b3d2-32f4-4f95-ac70-0971efdb8338","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_workspace.me: Refresh complete after 0s [id=feb06d32-3252-4cd8-b7db-ea0c5145747f]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.195820+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read","id_key":"id","id_value":"feb06d32-3252-4cd8-b7db-ea0c5145747f","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"data.coder_parameter.memory_size: Refresh complete after 0s [id=b136c86c-1be0-43b4-9d78-e492918c5de0]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.195836+02:00","hook":{"resource":{"addr":"data.coder_parameter.memory_size","module":"","resource":"data.coder_parameter.memory_size","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"memory_size","resource_key":null},"action":"read","id_key":"id","id_value":"b136c86c-1be0-43b4-9d78-e492918c5de0","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221555+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221574+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_volume.home_volume: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221580+02:00","change":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_container.workspace[0]: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221584+02:00","change":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"Plan: 4 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221589+02:00","changes":{"add":4,"change":0,"import":0,"remove":0,"operation":"plan"},"type":"change_summary"} +EOL + + # fake writing the state file + terraform_show > terraform.tfstate +} + +function terraform_apply() { + cat <<'EOL' +{"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.507006+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} +{"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572335+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572411+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_volume.home_volume: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572416+02:00","change":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"docker_container.workspace[0]: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572424+02:00","change":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"planned_change"} +{"@level":"info","@message":"coder_agent.main: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.616546+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"coder_agent.main: Creation complete after 0s [id=a23083da-4679-4396-a306-f7b466237883]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.618045+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"a23083da-4679-4396-a306-f7b466237883","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_image.main: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.626722+02:00","hook":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"docker_volume.home_volume: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.627335+02:00","hook":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"docker_volume.home_volume: Creation complete after 0s [id=coder-feb06d32-3252-4cd8-b7db-ea0c5145747f-home]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.660616+02:00","hook":{"resource":{"addr":"docker_volume.home_volume","module":"","resource":"docker_volume.home_volume","implied_provider":"docker","resource_type":"docker_volume","resource_name":"home_volume","resource_key":null},"action":"create","id_key":"id","id_value":"coder-feb06d32-3252-4cd8-b7db-ea0c5145747f-home","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_image.main: Creation complete after 0s [id=sha256:443d199e8bfcce69c2aa494b36b5f8b04c3b183277cd19190e9589fd8552d618nginx:latest]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.669954+02:00","hook":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"sha256:443d199e8bfcce69c2aa494b36b5f8b04c3b183277cd19190e9589fd8552d618nginx:latest","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"docker_container.workspace[0]: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.682223+02:00","hook":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"docker_container.workspace[0]: Creation complete after 0s [id=e39f34233fe1f6d18a33eaed8ad47ef1ae19ccf8cf6841858d5f2dafe4e3c8c9]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:40.186482+02:00","hook":{"resource":{"addr":"docker_container.workspace[0]","module":"","resource":"docker_container.workspace[0]","implied_provider":"docker","resource_type":"docker_container","resource_name":"workspace","resource_key":0},"action":"create","id_key":"id","id_value":"e39f34233fe1f6d18a33eaed8ad47ef1ae19ccf8cf6841858d5f2dafe4e3c8c9","elapsed_seconds":0},"type":"apply_complete"} +{"@level":"info","@message":"Apply complete! Resources: 4 added, 0 changed, 0 destroyed.","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:40.204593+02:00","changes":{"add":4,"change":0,"import":0,"remove":0,"operation":"apply"},"type":"change_summary"} +{"@level":"info","@message":"Outputs: 0","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:40.205051+02:00","outputs":{},"type":"outputs"} +EOL +} + +# TODO: remove +echo "$@" >> /tmp/blah + +case "$1" in + version) + terraform_version + ;; + show) + terraform_show + ;; + graph) + terraform_graph + ;; + init) + terraform_init + ;; + plan) + terraform_plan + ;; + apply) + terraform_apply + ;; + *) + echo "Usage: $0 {version|show|graph|init|plan|apply}" + exit 1 + ;; +esac \ No newline at end of file diff --git a/provisioner/terraform/testutil.go b/provisioner/terraform/testutil.go new file mode 100644 index 0000000000000..cab8a5f888966 --- /dev/null +++ b/provisioner/terraform/testutil.go @@ -0,0 +1,140 @@ +package terraform + +import ( + "bufio" + "bytes" + "slices" + "testing" + + "github.com/cespare/xxhash" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protojson" + protobuf "google.golang.org/protobuf/proto" + + "github.com/coder/coder/v2/provisionersdk/proto" +) + +func ParseTimingLines(t *testing.T, input []byte) []*proto.Timing { + t.Helper() + + // Parse the input into *proto.Timing structs. + var expected []*proto.Timing + scanner := bufio.NewScanner(bytes.NewBuffer(input)) + for scanner.Scan() { + line := scanner.Bytes() + + var msg proto.Timing + require.NoError(t, protojson.Unmarshal(line, &msg)) + + expected = append(expected, &msg) + } + require.NoError(t, scanner.Err()) + StableSortTimings(t, expected) // To reduce flakiness. + + return expected +} + +func TimingsAreEqual(t *testing.T, expected []*proto.Timing, actual []*proto.Timing) bool { + t.Helper() + + // Shortcut check. + if len(expected)+len(actual) == 0 { + t.Logf("both timings are empty") + return true + } + + // Shortcut check. + if len(expected) != len(actual) { + t.Logf("timings lengths are not equal: %d != %d", len(expected), len(actual)) + return false + } + + // Compare each element; both are expected to be sorted in a stable manner. + for i := 0; i < len(expected); i++ { + ex := expected[i] + ac := actual[i] + if !protobuf.Equal(ex, ac) { + t.Logf("timings are not equivalent: %q != %q", ex.String(), ac.String()) + return false + } + } + + return true +} + +func IngestAllSpans(t *testing.T, input []byte, aggregator *timingAggregator) { + t.Helper() + + scanner := bufio.NewScanner(bytes.NewBuffer(input)) + for scanner.Scan() { + line := scanner.Bytes() + log := parseTerraformLogLine(line) + if log == nil { + continue + } + + ts, span, err := extractTimingSpan(log) + if err != nil { + // t.Logf("%s: failed span extraction on line: %q", err, line) + continue + } + + require.NotZerof(t, ts, "failed on line: %q", line) + require.NotNilf(t, span, "failed on line: %q", line) + + aggregator.ingest(ts, span) + } + + require.NoError(t, scanner.Err()) +} + +func PrintTiming(t *testing.T, timing *proto.Timing) { + t.Helper() + + marshaler := protojson.MarshalOptions{ + Multiline: false, // Ensure it's set to false for single-line JSON + Indent: "", // No indentation + } + + out, err := marshaler.Marshal(timing) + assert.NoError(t, err) + t.Logf("%s", out) +} + +func StableSortTimings(t *testing.T, timings []*proto.Timing) { + t.Helper() + + slices.SortStableFunc(timings, func(a, b *proto.Timing) int { + if a == nil || b == nil || a.Start == nil || b.Start == nil { + return 0 + } + + if a.Start.AsTime().Equal(b.Start.AsTime()) { + // Special case: when start times are equal, we need to keep the ordering stable, so we hash both entries + // and sort based on that (since end times could be equal too, in principle). + ah := xxhash.Sum64String(a.String()) + bh := xxhash.Sum64String(b.String()) + + if ah == bh { + // WTF. + t.Logf("identical timings detected!") + PrintTiming(t, a) + PrintTiming(t, b) + return 0 + } + + if ah < bh { + return -1 + } + + return 1 + } + + if a.Start.AsTime().Before(b.Start.AsTime()) { + return -1 + } + + return 1 + }) +} diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go index bf30f59187740..fc03aa10ef9f2 100644 --- a/provisioner/terraform/timings_internal_test.go +++ b/provisioner/terraform/timings_internal_test.go @@ -1,18 +1,12 @@ package terraform import ( - "bufio" - "bytes" _ "embed" - "slices" "testing" - "github.com/cespare/xxhash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/tools/txtar" - "google.golang.org/protobuf/encoding/protojson" - protobuf "google.golang.org/protobuf/proto" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/provisionersdk/proto" @@ -79,138 +73,24 @@ func TestAggregation(t *testing.T) { file.Name, database.AllProvisionerJobTimingStageValues()) agg := newTimingAggregator(stage) - extractAllSpans(t, file.Data, agg) + IngestAllSpans(t, file.Data, agg) actualTimings = append(actualTimings, agg.aggregate()...) } - stableSortTimings(t, actualTimings) // To reduce flakiness. - require.True(t, timingsAreEqual(t, expectedTimings.Data, actualTimings)) + expected := ParseTimingLines(t, expectedTimings.Data) + StableSortTimings(t, actualTimings) // To reduce flakiness. + if !assert.True(t, TimingsAreEqual(t, expected, actualTimings)) { + printExpectation(t, expected) + } }) } } -func timingsAreEqual(t *testing.T, input []byte, actual []*proto.Timing) bool { - t.Helper() - - // Parse the input into *proto.Timing structs. - var expected []*proto.Timing - scanner := bufio.NewScanner(bytes.NewBuffer(input)) - for scanner.Scan() { - line := scanner.Bytes() - - var msg proto.Timing - require.NoError(t, protojson.Unmarshal(line, &msg)) - - expected = append(expected, &msg) - } - require.NoError(t, scanner.Err()) - - // Shortcut check. - if len(expected)+len(actual) == 0 { - t.Logf("both timings are empty") - return true - } - - // Shortcut check. - if len(expected) != len(actual) { - t.Logf("timings lengths are not equal: %d != %d", len(expected), len(actual)) - printExpectation(t, actual) - return false - } - - // Compare each element; both are expected to be sorted in a stable manner. - for i := 0; i < len(expected); i++ { - ex := expected[i] - ac := actual[i] - if !protobuf.Equal(ex, ac) { - t.Logf("timings are not equivalent: %q != %q", ex.String(), ac.String()) - printExpectation(t, actual) - return false - } - } - - return true -} - -func extractAllSpans(t *testing.T, input []byte, aggregator *timingAggregator) { - t.Helper() - - scanner := bufio.NewScanner(bytes.NewBuffer(input)) - for scanner.Scan() { - line := scanner.Bytes() - log := parseTerraformLogLine(line) - if log == nil { - continue - } - - ts, span, err := extractTimingSpan(log) - if err != nil { - // t.Logf("%s: failed span extraction on line: %q", err, line) - continue - } - - require.NotZerof(t, ts, "failed on line: %q", line) - require.NotNilf(t, span, "failed on line: %q", line) - - aggregator.ingest(ts, span) - } - - require.NoError(t, scanner.Err()) -} - func printExpectation(t *testing.T, actual []*proto.Timing) { t.Helper() t.Log("expected:") for _, a := range actual { - printTiming(t, a) - } -} - -func printTiming(t *testing.T, timing *proto.Timing) { - t.Helper() - - marshaler := protojson.MarshalOptions{ - Multiline: false, // Ensure it's set to false for single-line JSON - Indent: "", // No indentation + PrintTiming(t, a) } - - out, err := marshaler.Marshal(timing) - assert.NoError(t, err) - t.Logf("%s", out) -} - -func stableSortTimings(t *testing.T, timings []*proto.Timing) { - slices.SortStableFunc(timings, func(a, b *proto.Timing) int { - if a == nil || b == nil || a.Start == nil || b.Start == nil { - return 0 - } - - if a.Start.AsTime().Equal(b.Start.AsTime()) { - // Special case: when start times are equal, we need to keep the ordering stable, so we hash both entries - // and sort based on that (since end times could be equal too, in principle). - ah := xxhash.Sum64String(a.String()) - bh := xxhash.Sum64String(b.String()) - - if ah == bh { - // WTF. - t.Logf("identical timings detected!") - printTiming(t, a) - printTiming(t, b) - return 0 - } - - if ah < bh { - return -1 - } - - return 1 - } - - if a.Start.AsTime().Before(b.Start.AsTime()) { - return -1 - } - - return 1 - }) } diff --git a/provisioner/terraform/timings_test.go b/provisioner/terraform/timings_test.go new file mode 100644 index 0000000000000..508958f1d962c --- /dev/null +++ b/provisioner/terraform/timings_test.go @@ -0,0 +1,131 @@ +package terraform_test + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/provisioner/terraform" + "github.com/coder/coder/v2/provisionersdk/proto" + "github.com/coder/coder/v2/testutil" +) + +// TestTimingsFromProvision uses a fake terraform binary which spits out expected log content. +// This log content is then used to usher the provisioning process along as if terraform has run, and consequently +// the timing data is extracted from the log content and validated against the expected values. +func TestTimingsFromProvision(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + + // Given: a fake terraform bin that behaves as we expect it to. + fakeBin := filepath.Join(cwd, "testdata", "timings-aggregation/fake-terraform.sh") + + t.Logf(fakeBin) + + ctx, api := setupProvisioner(t, &provisionerServeOptions{ + binaryPath: fakeBin, + }) + sess := configure(ctx, t, api, &proto.Config{ + TemplateSourceArchive: makeTar(t, nil), + }) + + ctx, cancel := context.WithTimeout(ctx, testutil.WaitLong) + t.Cleanup(cancel) + + // When: a plan is executed in the provisioner, our fake terraform will be executed and will produce a + // state file and some log content. + err = sendPlan(sess, proto.WorkspaceTransition_START) + require.NoError(t, err) + + var timings []*proto.Timing + + for { + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + default: + } + + msg, err := sess.Recv() + require.NoError(t, err) + + if log := msg.GetLog(); log != nil { + t.Logf("%s: %s: %s", "plan", log.Level.String(), log.Output) + } + if c := msg.GetPlan(); c != nil { + require.Empty(t, c.Error) + // Capture the timing information returned by the plan process. + timings = append(timings, c.GetTimings()...) + break + } + } + + // When: the plan has completed, let's trigger an apply. + err = sendApply(sess, proto.WorkspaceTransition_START) + require.NoError(t, err) + + for { + select { + case <-ctx.Done(): + t.Fatal(ctx.Err()) + default: + } + + msg, err := sess.Recv() + require.NoError(t, err) + + if log := msg.GetLog(); log != nil { + t.Logf("%s: %s: %s", "apply", log.Level.String(), log.Output) + } + if c := msg.GetApply(); c != nil { + require.Empty(t, c.Error) + // Capture the timing information returned by the apply process. + timings = append(timings, c.GetTimings()...) + break + } + } + + // Sort the timings stably to keep reduce flakiness. + terraform.StableSortTimings(t, timings) + + // NOTE: These timings have been encoded to JSON format to make the tests more readable. + planTimings := terraform.ParseTimingLines(t, []byte(`{"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195836Z", "action":"read", "source":"coder", "resource":"data.coder_parameter.memory_size", "stage":"plan", "state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195712Z", "action":"read", "source":"coder", "resource":"data.coder_provisioner.me", "stage":"plan", "state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195820Z", "action":"read", "source":"coder", "resource":"data.coder_workspace.me", "stage":"plan", "state":"COMPLETED"}`)) + applyTimings := terraform.ParseTimingLines(t, []byte(`{"start":"2024-08-15T08:26:39.616546Z", "end":"2024-08-15T08:26:39.618045Z", "action":"create", "source":"coder", "resource":"coder_agent.main", "stage":"apply", "state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.626722Z", "end":"2024-08-15T08:26:39.669954Z", "action":"create", "source":"docker", "resource":"docker_image.main", "stage":"apply", "state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.627335Z", "end":"2024-08-15T08:26:39.660616Z", "action":"create", "source":"docker", "resource":"docker_volume.home_volume", "stage":"apply", "state":"COMPLETED"} +{"start":"2024-08-15T08:26:39.682223Z", "end":"2024-08-15T08:26:40.186482Z", "action":"create", "source":"docker", "resource":"docker_container.workspace[0]", "stage":"apply", "state":"COMPLETED"}`)) + initTiming := terraform.ParseTimingLines(t, []byte(`{"start":"2000-01-01T01:01:01.123456Z", "end":"2000-01-01T01:01:01.123456Z", "action":"initializing terraform", "source":"terraform", "resource":"state file", "stage":"init", "state":"COMPLETED"}`))[0] + graphTiming := terraform.ParseTimingLines(t, []byte(`{"start":"2000-01-01T01:01:01.123456Z", "end":"2000-01-01T01:01:01.123456Z", "action":"building terraform dependency graph", "source":"terraform", "resource":"state file", "stage":"graph", "state":"COMPLETED"}`))[0] + + require.Len(t, timings, len(planTimings)+len(applyTimings)+2) + + // init/graph timings are computed dynamically during provisioning whereas plan/apply come from the logs (fixtures) in + // provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh. + // + // This walks the timings, keeping separate cursors for plan and apply. + // We manually override the init/graph timings' timestamps so that the equality check works (all other fields should be as expected). + pCursor := 0 + aCursor := 0 + for _, tim := range timings { + switch tim.Stage { + case string(database.ProvisionerJobTimingStageInit): + tim.Start, tim.End = initTiming.Start, initTiming.End + require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{initTiming}, []*proto.Timing{tim})) + case string(database.ProvisionerJobTimingStageGraph): + tim.Start, tim.End = graphTiming.Start, graphTiming.End + require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{graphTiming}, []*proto.Timing{tim})) + case string(database.ProvisionerJobTimingStagePlan): + require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{planTimings[pCursor]}, []*proto.Timing{tim})) + pCursor++ + case string(database.ProvisionerJobTimingStageApply): + require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{applyTimings[aCursor]}, []*proto.Timing{tim})) + aCursor++ + } + } +} From 3d77c63961865ef56107bdfe07a9d3c74f6891be Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 14:52:22 +0200 Subject: [PATCH 10/22] Improve coverage Signed-off-by: Danny Kopping --- .../timings-aggregation/faster-than-light.txtar | 7 +++++++ .../timings-aggregation/incomplete.txtar | 7 +++++++ provisioner/terraform/timings_internal_test.go | 16 ++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 provisioner/terraform/testdata/timings-aggregation/faster-than-light.txtar create mode 100644 provisioner/terraform/testdata/timings-aggregation/incomplete.txtar diff --git a/provisioner/terraform/testdata/timings-aggregation/faster-than-light.txtar b/provisioner/terraform/testdata/timings-aggregation/faster-than-light.txtar new file mode 100644 index 0000000000000..3f9d9b2355cf5 --- /dev/null +++ b/provisioner/terraform/testdata/timings-aggregation/faster-than-light.txtar @@ -0,0 +1,7 @@ +A provisioning which appears to complete before it started has its start and end times aligned. + +-- apply -- +{"@level":"info","@message":"coder_agent.main: Creating...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.616546+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"apply_start"} +{"@level":"info","@message":"coder_agent.main: Creation complete after 0s [id=a23083da-4679-4396-a306-f7b466237883]","@module":"terraform.ui","@timestamp":"2024-08-15T10:21:39.618045+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"a23083da-4679-4396-a306-f7b466237883","elapsed_seconds":0},"type":"apply_complete"} +-- timings -- +{"start":"2024-08-15T08:21:39.618045Z","end":"2024-08-15T08:21:39.618045Z","action":"create","source":"coder","resource":"coder_agent.main","stage":"apply","state":"COMPLETED"} \ No newline at end of file diff --git a/provisioner/terraform/testdata/timings-aggregation/incomplete.txtar b/provisioner/terraform/testdata/timings-aggregation/incomplete.txtar new file mode 100644 index 0000000000000..a1caeb9999f66 --- /dev/null +++ b/provisioner/terraform/testdata/timings-aggregation/incomplete.txtar @@ -0,0 +1,7 @@ +An apply_start without a corresponding apply_complete will not produce a timing (and vice-versa). + +-- plan -- +{"@level":"info","@message":"data.coder_provisioner.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_provisioner.me","module":"","resource":"data.coder_provisioner.me","implied_provider":"coder","resource_type":"coder_provisioner","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} +-- apply -- +{"@level":"info","@message":"coder_agent.main: Creation complete after 0s [id=a23083da-4679-4396-a306-f7b466237883]","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.618045+02:00","hook":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create","id_key":"id","id_value":"a23083da-4679-4396-a306-f7b466237883","elapsed_seconds":0},"type":"apply_complete"} +-- timings -- \ No newline at end of file diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go index fc03aa10ef9f2..2c0f934393662 100644 --- a/provisioner/terraform/timings_internal_test.go +++ b/provisioner/terraform/timings_internal_test.go @@ -21,6 +21,10 @@ var ( inputError []byte //go:embed testdata/timings-aggregation/complete.txtar inputComplete []byte + //go:embed testdata/timings-aggregation/incomplete.txtar + inputIncomplete []byte + //go:embed testdata/timings-aggregation/faster-than-light.txtar + inputFasterThanLight []byte ) func TestAggregation(t *testing.T) { @@ -46,6 +50,14 @@ func TestAggregation(t *testing.T) { name: "complete", input: inputComplete, }, + { + name: "incomplete", + input: inputIncomplete, + }, + { + name: "faster-than-light", + input: inputFasterThanLight, + }, } // nolint:paralleltest // Not since go v1.22. @@ -64,8 +76,11 @@ func TestAggregation(t *testing.T) { t.Logf("%s: %s", t.Name(), arc.Comment) var actualTimings []*proto.Timing + + // The last "file" MUST contain the expected timings. expectedTimings := arc.Files[len(arc.Files)-1] + // Iterate over the initial "files" and extract their timings according to their stage. for i := 0; i < len(arc.Files)-1; i++ { file := arc.Files[i] stage := database.ProvisionerJobTimingStage(file.Name) @@ -77,6 +92,7 @@ func TestAggregation(t *testing.T) { actualTimings = append(actualTimings, agg.aggregate()...) } + // Ensure that the expected timings were produced. expected := ParseTimingLines(t, expectedTimings.Data) StableSortTimings(t, actualTimings) // To reduce flakiness. if !assert.True(t, TimingsAreEqual(t, expected, actualTimings)) { From c0ae1ba1897239d89a85804e86e785068fc3a2e0 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 15:19:29 +0200 Subject: [PATCH 11/22] Remove stats API call, will follow up in another PR Signed-off-by: Danny Kopping --- coderd/apidoc/docs.go | 82 --------------------- coderd/apidoc/swagger.json | 78 -------------------- coderd/coderd.go | 1 - coderd/database/dbauthz/dbauthz.go | 9 --- coderd/database/dbmem/dbmem.go | 24 ------ coderd/database/dbmetrics/dbmetrics.go | 7 -- coderd/database/dbmock/dbmock.go | 15 ---- coderd/database/querier.go | 2 - coderd/database/queries.sql.go | 34 --------- coderd/database/queries/provisionerjobs.sql | 6 -- coderd/workspacebuilds.go | 34 --------- docs/reference/api/builds.md | 49 ------------ docs/reference/api/schemas.md | 38 ---------- 13 files changed, 379 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 060b9b402637f..86c79d772abf3 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7150,40 +7150,6 @@ const docTemplate = `{ } } }, - "/workspacebuilds/{workspacebuild}/stats": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "Builds" - ], - "summary": "Get stats for workspace build", - "operationId": "get-stats-for-workspace-build", - "parameters": [ - { - "type": "string", - "description": "Workspace build ID", - "name": "workspacebuild", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.WorkspaceBuildStats" - } - } - } - } - }, "/workspaceproxies": { "get": { "security": [ @@ -14117,54 +14083,6 @@ const docTemplate = `{ } } }, - "codersdk.WorkspaceBuildStats": { - "type": "object", - "properties": { - "apply_secs": { - "type": "number" - }, - "canceled_secs": { - "type": "number" - }, - "completion_secs": { - "type": "number" - }, - "error": { - "type": "string" - }, - "error_code": { - "type": "string" - }, - "id": { - "type": "string", - "format": "uuid" - }, - "init_secs": { - "type": "number" - }, - "job_status": { - "$ref": "#/definitions/codersdk.ProvisionerJobStatus" - }, - "plan_secs": { - "type": "number" - }, - "queued_secs": { - "type": "number" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "worker_id": { - "type": "string", - "format": "uuid" - }, - "workspace_id": { - "type": "string", - "format": "uuid" - } - } - }, "codersdk.WorkspaceConnectionLatencyMS": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 40fe662f1dba7..61967f9d5096c 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6322,36 +6322,6 @@ } } }, - "/workspacebuilds/{workspacebuild}/stats": { - "get": { - "security": [ - { - "CoderSessionToken": [] - } - ], - "produces": ["application/json"], - "tags": ["Builds"], - "summary": "Get stats for workspace build", - "operationId": "get-stats-for-workspace-build", - "parameters": [ - { - "type": "string", - "description": "Workspace build ID", - "name": "workspacebuild", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/codersdk.WorkspaceBuildStats" - } - } - } - } - }, "/workspaceproxies": { "get": { "security": [ @@ -12842,54 +12812,6 @@ } } }, - "codersdk.WorkspaceBuildStats": { - "type": "object", - "properties": { - "apply_secs": { - "type": "number" - }, - "canceled_secs": { - "type": "number" - }, - "completion_secs": { - "type": "number" - }, - "error": { - "type": "string" - }, - "error_code": { - "type": "string" - }, - "id": { - "type": "string", - "format": "uuid" - }, - "init_secs": { - "type": "number" - }, - "job_status": { - "$ref": "#/definitions/codersdk.ProvisionerJobStatus" - }, - "plan_secs": { - "type": "number" - }, - "queued_secs": { - "type": "number" - }, - "updated_at": { - "type": "string", - "format": "date-time" - }, - "worker_id": { - "type": "string", - "format": "uuid" - }, - "workspace_id": { - "type": "string", - "format": "uuid" - } - } - }, "codersdk.WorkspaceConnectionLatencyMS": { "type": "object", "properties": { diff --git a/coderd/coderd.go b/coderd/coderd.go index fac3219406941..a4b36e8fec698 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -1153,7 +1153,6 @@ func New(options *Options) *API { r.Get("/parameters", api.workspaceBuildParameters) r.Get("/resources", api.workspaceBuildResourcesDeprecated) r.Get("/state", api.workspaceBuildState) - r.Get("/stats", api.workspaceBuildStats) }) r.Route("/authcheck", func(r chi.Router) { r.Use(apiKeyMiddleware) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 42883b919cf3d..23a50799c122b 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1775,15 +1775,6 @@ func (q *querier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (data return job, nil } -func (q *querier) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { - // Use the workspace ID to authorize the request. - if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceWorkspace.WithID(arg.WorkspaceID)); err != nil { - return database.ProvisionerJobStat{}, err - } - - return q.db.GetProvisionerJobStatsByWorkspace(ctx, arg) -} - // TODO: we need to add a provisioner job resource func (q *querier) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { // if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 69b4005ba36d3..9e8a4cee05667 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -3172,30 +3172,6 @@ func (q *FakeQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) ( return q.getProvisionerJobByIDNoLock(ctx, id) } -func (*FakeQuerier) GetProvisionerJobStatsByWorkspace(_ context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { - err := validateDatabaseType(arg) - if err != nil { - return database.ProvisionerJobStat{}, err - } - - return database.ProvisionerJobStat{ - JobID: arg.JobID, - JobStatus: database.ProvisionerJobStatusSucceeded, - WorkspaceID: arg.WorkspaceID, - WorkerID: uuid.NullUUID{UUID: uuid.New(), Valid: true}, - Error: sql.NullString{}, - ErrorCode: sql.NullString{}, - UpdatedAt: dbtime.Now(), - QueuedSecs: 0.1, - CompletionSecs: 10, - CanceledSecs: 0, - InitSecs: 1, - PlanSecs: 2, - GraphSecs: 3, - ApplySecs: 4, - }, nil -} - func (q *FakeQuerier) GetProvisionerJobsByIDs(_ context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 16d1c746d7921..d24bd1bb508bc 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -921,13 +921,6 @@ func (m metricsStore) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) ( return job, err } -func (m metricsStore) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { - start := time.Now() - r0, r1 := m.s.GetProvisionerJobStatsByWorkspace(ctx, arg) - m.queryLatencies.WithLabelValues("GetProvisionerJobStatsByWorkspace").Observe(time.Since(start).Seconds()) - return r0, r1 -} - func (m metricsStore) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.ProvisionerJob, error) { start := time.Now() jobs, err := m.s.GetProvisionerJobsByIDs(ctx, ids) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 1b20ec6914bee..1c03ffd43218a 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1869,21 +1869,6 @@ func (mr *MockStoreMockRecorder) GetProvisionerJobByID(arg0, arg1 any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobByID", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobByID), arg0, arg1) } -// GetProvisionerJobStatsByWorkspace mocks base method. -func (m *MockStore) GetProvisionerJobStatsByWorkspace(arg0 context.Context, arg1 database.GetProvisionerJobStatsByWorkspaceParams) (database.ProvisionerJobStat, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetProvisionerJobStatsByWorkspace", arg0, arg1) - ret0, _ := ret[0].(database.ProvisionerJobStat) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetProvisionerJobStatsByWorkspace indicates an expected call of GetProvisionerJobStatsByWorkspace. -func (mr *MockStoreMockRecorder) GetProvisionerJobStatsByWorkspace(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisionerJobStatsByWorkspace", reflect.TypeOf((*MockStore)(nil).GetProvisionerJobStatsByWorkspace), arg0, arg1) -} - // GetProvisionerJobsByIDs mocks base method. func (m *MockStore) GetProvisionerJobsByIDs(arg0 context.Context, arg1 []uuid.UUID) ([]database.ProvisionerJob, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index efabf0a1b22cc..4ae5973a6a98a 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -188,8 +188,6 @@ type sqlcQuerier interface { GetProvisionerDaemons(ctx context.Context) ([]ProvisionerDaemon, error) GetProvisionerDaemonsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]ProvisionerDaemon, error) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (ProvisionerJob, error) - // We include the workspace here so we can authorize the request correctly. - GetProvisionerJobStatsByWorkspace(ctx context.Context, arg GetProvisionerJobStatsByWorkspaceParams) (ProvisionerJobStat, error) GetProvisionerJobsByIDs(ctx context.Context, ids []uuid.UUID) ([]ProvisionerJob, error) GetProvisionerJobsByIDsWithQueuePosition(ctx context.Context, ids []uuid.UUID) ([]GetProvisionerJobsByIDsWithQueuePositionRow, error) GetProvisionerJobsCreatedAfter(ctx context.Context, createdAt time.Time) ([]ProvisionerJob, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index cfbd632fa5400..1bc875022d845 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5346,40 +5346,6 @@ func (q *sqlQuerier) GetProvisionerJobByID(ctx context.Context, id uuid.UUID) (P return i, err } -const getProvisionerJobStatsByWorkspace = `-- name: GetProvisionerJobStatsByWorkspace :one -SELECT job_id, job_status, workspace_id, worker_id, error, error_code, updated_at, queued_secs, completion_secs, canceled_secs, init_secs, plan_secs, graph_secs, apply_secs FROM provisioner_job_stats -WHERE job_id = $1::uuid AND workspace_id = $2::uuid -LIMIT 1 -` - -type GetProvisionerJobStatsByWorkspaceParams struct { - JobID uuid.UUID `db:"job_id" json:"job_id"` - WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"` -} - -// We include the workspace here so we can authorize the request correctly. -func (q *sqlQuerier) GetProvisionerJobStatsByWorkspace(ctx context.Context, arg GetProvisionerJobStatsByWorkspaceParams) (ProvisionerJobStat, error) { - row := q.db.QueryRowContext(ctx, getProvisionerJobStatsByWorkspace, arg.JobID, arg.WorkspaceID) - var i ProvisionerJobStat - err := row.Scan( - &i.JobID, - &i.JobStatus, - &i.WorkspaceID, - &i.WorkerID, - &i.Error, - &i.ErrorCode, - &i.UpdatedAt, - &i.QueuedSecs, - &i.CompletionSecs, - &i.CanceledSecs, - &i.InitSecs, - &i.PlanSecs, - &i.GraphSecs, - &i.ApplySecs, - ) - return i, err -} - const getProvisionerJobsByIDs = `-- name: GetProvisionerJobsByIDs :many SELECT id, created_at, updated_at, started_at, canceled_at, completed_at, error, organization_id, initiator_id, provisioner, storage_method, type, input, worker_id, file_id, tags, error_code, trace_metadata, job_status diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index f2c872d2dfb1b..6fe5f126c467a 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -156,9 +156,3 @@ SELECT unnest(@action::text[]) AS action, unnest(@resource::text[]) AS resource RETURNING *; - --- We include the workspace here so we can authorize the request correctly. --- name: GetProvisionerJobStatsByWorkspace :one -SELECT * FROM provisioner_job_stats -WHERE job_id = @job_id::uuid AND workspace_id = @workspace_id::uuid -LIMIT 1; \ No newline at end of file diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index be8b37f460cd8..3e4b6001b9895 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -648,40 +648,6 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { _, _ = rw.Write(workspaceBuild.ProvisionerState) } -// @Summary Get stats for workspace build -// @ID get-stats-for-workspace-build -// @Security CoderSessionToken -// @Produce json -// @Tags Builds -// @Param workspacebuild path string true "Workspace build ID" -// @Success 200 {object} codersdk.WorkspaceBuildStats -// @Router /workspacebuilds/{workspacebuild}/stats [get] -func (api *API) workspaceBuildStats(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - workspaceBuild := httpmw.WorkspaceBuildParam(r) - - if workspaceBuild.JobID == uuid.Nil { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Failed to retrieve provisioner job ID from workspace build", - }) - return - } - - pj, err := api.Database.GetProvisionerJobStatsByWorkspace(ctx, database.GetProvisionerJobStatsByWorkspaceParams{ - JobID: workspaceBuild.JobID, - WorkspaceID: workspaceBuild.WorkspaceID, - }) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to get workspace build stats", - Detail: err.Error(), - }) - return - } - - httpapi.Write(ctx, rw, http.StatusOK, convertWorkspaceBuildStats(pj)) -} - type workspaceBuildsData struct { users []database.User jobs []database.GetProvisionerJobsByIDsWithQueuePositionRow diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index 881df6559fedc..8cad5b3a73bec 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -976,55 +976,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get stats for workspace build - -### Code samples - -```shell -# Example request using curl -curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/stats \ - -H 'Accept: application/json' \ - -H 'Coder-Session-Token: API_KEY' -``` - -`GET /workspacebuilds/{workspacebuild}/stats` - -### Parameters - -| Name | In | Type | Required | Description | -| ---------------- | ---- | ------ | -------- | ------------------ | -| `workspacebuild` | path | string | true | Workspace build ID | - -### Example responses - -> 200 Response - -```json -{ - "apply_secs": 0, - "canceled_secs": 0, - "completion_secs": 0, - "error": "string", - "error_code": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "init_secs": 0, - "job_status": "pending", - "plan_secs": 0, - "queued_secs": 0, - "updated_at": "2019-08-24T14:15:22Z", - "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" -} -``` - -### Responses - -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceBuildStats](schemas.md#codersdkworkspacebuildstats) | - -To perform this operation, you must be authenticated. [Learn more](authentication.md). - ## Get workspace builds by workspace ID ### Code samples diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 3f6e065981a01..cb7c88af83f2b 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -7104,44 +7104,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `name` | string | false | | | | `value` | string | false | | | -## codersdk.WorkspaceBuildStats - -```json -{ - "apply_secs": 0, - "canceled_secs": 0, - "completion_secs": 0, - "error": "string", - "error_code": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "init_secs": 0, - "job_status": "pending", - "plan_secs": 0, - "queued_secs": 0, - "updated_at": "2019-08-24T14:15:22Z", - "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ----------------- | -------------------------------------------------------------- | -------- | ------------ | ----------- | -| `apply_secs` | number | false | | | -| `canceled_secs` | number | false | | | -| `completion_secs` | number | false | | | -| `error` | string | false | | | -| `error_code` | string | false | | | -| `id` | string | false | | | -| `init_secs` | number | false | | | -| `job_status` | [codersdk.ProvisionerJobStatus](#codersdkprovisionerjobstatus) | false | | | -| `plan_secs` | number | false | | | -| `queued_secs` | number | false | | | -| `updated_at` | string | false | | | -| `worker_id` | string | false | | | -| `workspace_id` | string | false | | | - ## codersdk.WorkspaceConnectionLatencyMS ```json From 28fa2f7868dd651bc1510b6bf23c339e34b74020 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 15:24:28 +0200 Subject: [PATCH 12/22] Fixing tests Signed-off-by: Danny Kopping --- coderd/database/dbauthz/dbauthz_test.go | 7 + coderd/workspacebuilds.go | 18 --- provisioner/terraform/timings_test.go | 2 + .../e2e/google/protobuf/timestampGenerated.ts | 123 ------------------ 4 files changed, 9 insertions(+), 141 deletions(-) delete mode 100644 site/e2e/google/protobuf/timestampGenerated.ts diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index f1ee12f64579f..e0392e1906cb7 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2463,6 +2463,13 @@ func (s *MethodTestSuite) TestSystemFunctions() { JobID: j.ID, }).Asserts( /*rbac.ResourceSystem, policy.ActionCreate*/ ) })) + s.Run("InsertProvisionerJobTimings", s.Subtest(func(db database.Store, check *expects) { + // TODO: we need to create a ProvisionerJob resource + j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{}) + check.Args(database.InsertProvisionerJobTimingsParams{ + JobID: j.ID, + }).Asserts( /*rbac.ResourceSystem, policy.ActionCreate*/ ) + })) s.Run("UpsertProvisionerDaemon", s.Subtest(func(db database.Store, check *expects) { org := dbgen.Organization(s.T(), db, database.Organization{}) pd := rbac.ResourceProvisionerDaemon.InOrg(org.ID) diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 3e4b6001b9895..e5b362de4a802 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -1011,21 +1011,3 @@ func convertWorkspaceStatus(jobStatus codersdk.ProvisionerJobStatus, transition // return error status since we should never get here return codersdk.WorkspaceStatusFailed } - -func convertWorkspaceBuildStats(stats database.ProvisionerJobStat) codersdk.WorkspaceBuildStats { - return codersdk.WorkspaceBuildStats{ - ID: stats.JobID, - JobStatus: codersdk.ProvisionerJobStatus(stats.JobStatus), - WorkerID: stats.WorkspaceID, - WorkspaceID: stats.WorkspaceID, - Error: stats.Error.String, - ErrorCode: stats.ErrorCode.String, - UpdatedAt: stats.UpdatedAt, - QueuedSecs: stats.QueuedSecs, - CompletionSecs: stats.CompletionSecs, - CanceledSecs: stats.CanceledSecs, - InitSecs: stats.InitSecs, - PlanSecs: stats.PlanSecs, - ApplySecs: stats.ApplySecs, - } -} diff --git a/provisioner/terraform/timings_test.go b/provisioner/terraform/timings_test.go index 508958f1d962c..21f4d96fb43ba 100644 --- a/provisioner/terraform/timings_test.go +++ b/provisioner/terraform/timings_test.go @@ -18,6 +18,8 @@ import ( // This log content is then used to usher the provisioning process along as if terraform has run, and consequently // the timing data is extracted from the log content and validated against the expected values. func TestTimingsFromProvision(t *testing.T) { + t.Parallel() + cwd, err := os.Getwd() require.NoError(t, err) diff --git a/site/e2e/google/protobuf/timestampGenerated.ts b/site/e2e/google/protobuf/timestampGenerated.ts deleted file mode 100644 index 6dd4b08e96087..0000000000000 --- a/site/e2e/google/protobuf/timestampGenerated.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* eslint-disable */ -import * as _m0 from "protobufjs/minimal"; - -export const protobufPackage = "google.protobuf"; - -/** - * A Timestamp represents a point in time independent of any time zone or local - * calendar, encoded as a count of seconds and fractions of seconds at - * nanosecond resolution. The count is relative to an epoch at UTC midnight on - * January 1, 1970, in the proleptic Gregorian calendar which extends the - * Gregorian calendar backwards to year one. - * - * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap - * second table is needed for interpretation, using a [24-hour linear - * smear](https://developers.google.com/time/smear). - * - * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By - * restricting to that range, we ensure that we can convert to and from [RFC - * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. - * - * # Examples - * - * Example 1: Compute Timestamp from POSIX `time()`. - * - * Timestamp timestamp; - * timestamp.set_seconds(time(NULL)); - * timestamp.set_nanos(0); - * - * Example 2: Compute Timestamp from POSIX `gettimeofday()`. - * - * struct timeval tv; - * gettimeofday(&tv, NULL); - * - * Timestamp timestamp; - * timestamp.set_seconds(tv.tv_sec); - * timestamp.set_nanos(tv.tv_usec * 1000); - * - * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. - * - * FILETIME ft; - * GetSystemTimeAsFileTime(&ft); - * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; - * - * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z - * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. - * Timestamp timestamp; - * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); - * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); - * - * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. - * - * long millis = System.currentTimeMillis(); - * - * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) - * .setNanos((int) ((millis % 1000) * 1000000)).build(); - * - * Example 5: Compute Timestamp from Java `Instant.now()`. - * - * Instant now = Instant.now(); - * - * Timestamp timestamp = - * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) - * .setNanos(now.getNano()).build(); - * - * Example 6: Compute Timestamp from current time in Python. - * - * timestamp = Timestamp() - * timestamp.GetCurrentTime() - * - * # JSON Mapping - * - * In JSON format, the Timestamp type is encoded as a string in the - * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the - * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" - * where {year} is always expressed using four digits while {month}, {day}, - * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional - * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), - * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone - * is required. A proto3 JSON serializer should always use UTC (as indicated by - * "Z") when printing the Timestamp type and a proto3 JSON parser should be - * able to accept both UTC and other timezones (as indicated by an offset). - * - * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past - * 01:30 UTC on January 15, 2017. - * - * In JavaScript, one can convert a Date object to this format using the - * standard - * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) - * method. In Python, a standard `datetime.datetime` object can be converted - * to this format using - * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with - * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use - * the Joda Time's [`ISODateTimeFormat.dateTime()`]( - * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() - * ) to obtain a formatter capable of generating timestamps in this format. - */ -export interface Timestamp { - /** - * Represents seconds of UTC time since Unix epoch - * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - * 9999-12-31T23:59:59Z inclusive. - */ - seconds: number; - /** - * Non-negative fractions of a second at nanosecond resolution. Negative - * second values with fractions must still have non-negative nanos values - * that count forward in time. Must be from 0 to 999,999,999 - * inclusive. - */ - nanos: number; -} - -export const Timestamp = { - encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { - if (message.seconds !== 0) { - writer.uint32(8).int64(message.seconds); - } - if (message.nanos !== 0) { - writer.uint32(16).int32(message.nanos); - } - return writer; - }, -}; From 68b16ffe047510bcd35ecca06acd9bf82cf4c420 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 16:01:27 +0200 Subject: [PATCH 13/22] Use max(end)-min(start) as stage timings, not local maximum Signed-off-by: Danny Kopping --- .../000245_provisioner_job_timings.up.sql | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/coderd/database/migrations/000245_provisioner_job_timings.up.sql b/coderd/database/migrations/000245_provisioner_job_timings.up.sql index bfd6c71cea9dd..26496232e9f1d 100644 --- a/coderd/database/migrations/000245_provisioner_job_timings.up.sql +++ b/coderd/database/migrations/000245_provisioner_job_timings.up.sql @@ -17,28 +17,28 @@ CREATE TABLE provisioner_job_timings ); CREATE VIEW provisioner_job_stats AS -SELECT pj.id AS job_id, +SELECT pj.id AS job_id, pj.job_status, wb.workspace_id, pj.worker_id, pj.error, pj.error_code, pj.updated_at, - GREATEST(EXTRACT(EPOCH FROM (pj.started_at - pj.created_at)), 0) AS queued_secs, - GREATEST(EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at)), 0) AS completion_secs, - GREATEST(EXTRACT(EPOCH FROM (pj.canceled_at - pj.started_at)), 0) AS canceled_secs, - GREATEST(MAX(CASE - WHEN pjt.stage = 'init'::provisioner_job_timing_stage - THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS init_secs, - GREATEST(MAX(CASE - WHEN pjt.stage = 'plan'::provisioner_job_timing_stage - THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS plan_secs, - GREATEST(MAX(CASE - WHEN pjt.stage = 'graph'::provisioner_job_timing_stage - THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS graph_secs, - GREATEST(MAX(CASE - WHEN pjt.stage = 'apply'::provisioner_job_timing_stage - THEN EXTRACT(EPOCH FROM (pjt.ended_at - pjt.started_at)) END), 0) AS apply_secs + GREATEST(EXTRACT(EPOCH FROM (pj.started_at - pj.created_at)), 0) AS queued_secs, + GREATEST(EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at)), 0) AS completion_secs, + GREATEST(EXTRACT(EPOCH FROM (pj.canceled_at - pj.started_at)), 0) AS canceled_secs, + GREATEST(EXTRACT(EPOCH FROM ( + MAX(CASE WHEN pjt.stage = 'init'::provisioner_job_timing_stage THEN pjt.ended_at END) - + MIN(CASE WHEN pjt.stage = 'init'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS init_secs, + GREATEST(EXTRACT(EPOCH FROM ( + MAX(CASE WHEN pjt.stage = 'plan'::provisioner_job_timing_stage THEN pjt.ended_at END) - + MIN(CASE WHEN pjt.stage = 'plan'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS plan_secs, + GREATEST(EXTRACT(EPOCH FROM ( + MAX(CASE WHEN pjt.stage = 'graph'::provisioner_job_timing_stage THEN pjt.ended_at END) - + MIN(CASE WHEN pjt.stage = 'graph'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS graph_secs, + GREATEST(EXTRACT(EPOCH FROM ( + MAX(CASE WHEN pjt.stage = 'apply'::provisioner_job_timing_stage THEN pjt.ended_at END) - + MIN(CASE WHEN pjt.stage = 'apply'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS apply_secs FROM provisioner_jobs pj JOIN workspace_builds wb ON wb.job_id = pj.id LEFT JOIN provisioner_job_timings pjt ON pjt.job_id = pj.id From 6f0b8f82b4c4287db0cdb9d8718db4ecab09e29f Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 14:03:50 +0000 Subject: [PATCH 14/22] make fmt Signed-off-by: Danny Kopping --- .../timings-aggregation/fake-terraform.sh | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh b/provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh index cf4949d1d0d6d..4eb0d11ad0ec6 100755 --- a/provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh +++ b/provisioner/terraform/testdata/timings-aggregation/fake-terraform.sh @@ -1,7 +1,7 @@ #!/bin/bash function terraform_version() { - cat <<'EOL' + cat <<'EOL' { "terraform_version": "1.9.2", "platform": "darwin_arm64", @@ -12,13 +12,13 @@ EOL } function terraform_show() { - cat <<'EOL' + cat <<'EOL' {"format_version":"1.2","terraform_version":"1.5.7","planned_values":{"root_module":{"resources":[{"address":"coder_agent.main","mode":"managed","type":"coder_agent","name":"main","provider_name":"registry.terraform.io/coder/coder","schema_version":1,"values":{"arch":"arm64","auth":"token","connection_timeout":120,"dir":null,"env":null,"login_before_ready":true,"metadata":[{"display_name":"CPU Usage","interval":10,"key":"0_cpu_usage","order":null,"script":"coder stat cpu","timeout":1},{"display_name":"RAM Usage","interval":10,"key":"1_ram_usage","order":null,"script":"coder stat mem","timeout":1}],"motd_file":null,"order":null,"os":"linux","shutdown_script":null,"shutdown_script_timeout":300,"startup_script":null,"startup_script_behavior":null,"startup_script_timeout":300,"troubleshooting_url":null},"sensitive_values":{"display_apps":[],"metadata":[{},{}]}},{"address":"docker_container.workspace[0]","mode":"managed","type":"docker_container","name":"workspace","index":0,"provider_name":"registry.terraform.io/kreuzwerker/docker","schema_version":2,"values":{"attach":false,"capabilities":[],"cgroupns_mode":null,"container_read_refresh_timeout_milliseconds":15000,"cpu_set":null,"cpu_shares":null,"destroy_grace_seconds":null,"devices":[],"dns":null,"dns_opts":null,"dns_search":null,"domainname":null,"gpus":null,"group_add":null,"host":[{"host":"host.docker.internal","ip":"host-gateway"}],"hostname":"barry1723722791","image":"nginx:latest","labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name","value":"barry1723722791"}],"log_opts":null,"logs":false,"max_retry_count":null,"memory":100,"memory_swap":null,"mounts":[],"must_run":true,"name":"coder-danny-barry1723722791","network_mode":null,"networks_advanced":[],"pid_mode":null,"ports":[],"privileged":null,"publish_all_ports":null,"read_only":false,"remove_volumes":true,"restart":"always","rm":false,"start":true,"stdin_open":false,"storage_opts":null,"sysctls":null,"tmpfs":null,"tty":false,"ulimit":[],"upload":[],"user":null,"userns_mode":null,"volumes":[{"container_path":"/home/danny","from_container":"","host_path":"","read_only":false,"volume_name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"}],"wait":false,"wait_timeout":60,"working_dir":null},"sensitive_values":{"capabilities":[],"command":[],"devices":[],"entrypoint":[],"env":true,"healthcheck":[],"host":[{}],"labels":[{},{},{},{}],"mounts":[],"network_data":[],"networks_advanced":[],"ports":[],"security_opts":[],"ulimit":[],"upload":[],"volumes":[{}]}},{"address":"docker_image.main","mode":"managed","type":"docker_image","name":"main","provider_name":"registry.terraform.io/kreuzwerker/docker","schema_version":0,"values":{"build":[],"force_remove":null,"keep_locally":true,"name":"nginx:latest","platform":null,"pull_triggers":null,"triggers":null},"sensitive_values":{"build":[]}},{"address":"docker_volume.home_volume","mode":"managed","type":"docker_volume","name":"home_volume","provider_name":"registry.terraform.io/kreuzwerker/docker","schema_version":1,"values":{"driver_opts":null,"labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name_at_creation","value":"barry1723722791"}],"name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"},"sensitive_values":{"labels":[{},{},{},{}]}}]}},"resource_changes":[{"address":"coder_agent.main","mode":"managed","type":"coder_agent","name":"main","provider_name":"registry.terraform.io/coder/coder","change":{"actions":["create"],"before":null,"after":{"arch":"arm64","auth":"token","connection_timeout":120,"dir":null,"env":null,"login_before_ready":true,"metadata":[{"display_name":"CPU Usage","interval":10,"key":"0_cpu_usage","order":null,"script":"coder stat cpu","timeout":1},{"display_name":"RAM Usage","interval":10,"key":"1_ram_usage","order":null,"script":"coder stat mem","timeout":1}],"motd_file":null,"order":null,"os":"linux","shutdown_script":null,"shutdown_script_timeout":300,"startup_script":null,"startup_script_behavior":null,"startup_script_timeout":300,"troubleshooting_url":null},"after_unknown":{"display_apps":true,"id":true,"init_script":true,"metadata":[{},{}],"token":true},"before_sensitive":false,"after_sensitive":{"display_apps":[],"metadata":[{},{}],"token":true}}},{"address":"docker_container.workspace[0]","mode":"managed","type":"docker_container","name":"workspace","index":0,"provider_name":"registry.terraform.io/kreuzwerker/docker","change":{"actions":["create"],"before":null,"after":{"attach":false,"capabilities":[],"cgroupns_mode":null,"container_read_refresh_timeout_milliseconds":15000,"cpu_set":null,"cpu_shares":null,"destroy_grace_seconds":null,"devices":[],"dns":null,"dns_opts":null,"dns_search":null,"domainname":null,"gpus":null,"group_add":null,"host":[{"host":"host.docker.internal","ip":"host-gateway"}],"hostname":"barry1723722791","image":"nginx:latest","labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name","value":"barry1723722791"}],"log_opts":null,"logs":false,"max_retry_count":null,"memory":100,"memory_swap":null,"mounts":[],"must_run":true,"name":"coder-danny-barry1723722791","network_mode":null,"networks_advanced":[],"pid_mode":null,"ports":[],"privileged":null,"publish_all_ports":null,"read_only":false,"remove_volumes":true,"restart":"always","rm":false,"start":true,"stdin_open":false,"storage_opts":null,"sysctls":null,"tmpfs":null,"tty":false,"ulimit":[],"upload":[],"user":null,"userns_mode":null,"volumes":[{"container_path":"/home/danny","from_container":"","host_path":"","read_only":false,"volume_name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"}],"wait":false,"wait_timeout":60,"working_dir":null},"after_unknown":{"bridge":true,"capabilities":[],"command":true,"container_logs":true,"devices":[],"entrypoint":true,"env":true,"exit_code":true,"healthcheck":true,"host":[{}],"id":true,"init":true,"ipc_mode":true,"labels":[{},{},{},{}],"log_driver":true,"mounts":[],"network_data":true,"networks_advanced":[],"ports":[],"runtime":true,"security_opts":true,"shm_size":true,"stop_signal":true,"stop_timeout":true,"ulimit":[],"upload":[],"volumes":[{}]},"before_sensitive":false,"after_sensitive":{"capabilities":[],"command":[],"devices":[],"entrypoint":[],"env":true,"healthcheck":[],"host":[{}],"labels":[{},{},{},{}],"mounts":[],"network_data":[],"networks_advanced":[],"ports":[],"security_opts":[],"ulimit":[],"upload":[],"volumes":[{}]}}},{"address":"docker_image.main","mode":"managed","type":"docker_image","name":"main","provider_name":"registry.terraform.io/kreuzwerker/docker","change":{"actions":["create"],"before":null,"after":{"build":[],"force_remove":null,"keep_locally":true,"name":"nginx:latest","platform":null,"pull_triggers":null,"triggers":null},"after_unknown":{"build":[],"id":true,"image_id":true,"repo_digest":true},"before_sensitive":false,"after_sensitive":{"build":[]}}},{"address":"docker_volume.home_volume","mode":"managed","type":"docker_volume","name":"home_volume","provider_name":"registry.terraform.io/kreuzwerker/docker","change":{"actions":["create"],"before":null,"after":{"driver_opts":null,"labels":[{"label":"coder.owner","value":"danny"},{"label":"coder.owner_id","value":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e"},{"label":"coder.workspace_id","value":"1b0cd26b-9e35-4107-8aab-5827419bac68"},{"label":"coder.workspace_name_at_creation","value":"barry1723722791"}],"name":"coder-1b0cd26b-9e35-4107-8aab-5827419bac68-home"},"after_unknown":{"driver":true,"id":true,"labels":[{},{},{},{}],"mountpoint":true},"before_sensitive":false,"after_sensitive":{"labels":[{},{},{},{}]}}}],"prior_state":{"format_version":"1.0","terraform_version":"1.5.7","values":{"root_module":{"resources":[{"address":"data.coder_parameter.memory_size","mode":"data","type":"coder_parameter","name":"memory_size","provider_name":"registry.terraform.io/coder/coder","schema_version":0,"values":{"default":"100","description":null,"display_name":null,"ephemeral":false,"icon":null,"id":"88f32e48-320b-4b67-a9ef-053150c3f6a7","mutable":true,"name":"Memory Allocation","option":null,"optional":true,"order":null,"type":"number","validation":[],"value":"100"},"sensitive_values":{"validation":[]}},{"address":"data.coder_provisioner.me","mode":"data","type":"coder_provisioner","name":"me","provider_name":"registry.terraform.io/coder/coder","schema_version":0,"values":{"arch":"arm64","id":"5e8c4561-b101-4c60-88e9-097c5c0f73de","os":"darwin"},"sensitive_values":{}},{"address":"data.coder_workspace.me","mode":"data","type":"coder_workspace","name":"me","provider_name":"registry.terraform.io/coder/coder","schema_version":0,"values":{"access_port":3000,"access_url":"http://localhost:3000","id":"1b0cd26b-9e35-4107-8aab-5827419bac68","name":"barry1723722791","owner":"danny","owner_email":"default@example.com","owner_groups":[],"owner_id":"ec669dd6-ecf6-4da3-b1c6-fbc60c782e0e","owner_name":"default","owner_oidc_access_token":"","owner_session_token":"","start_count":1,"template_id":"","template_name":"","template_version":"","transition":"start"},"sensitive_values":{"owner_groups":[]}}]}}},"configuration":{"provider_config":{"coder":{"name":"coder","full_name":"registry.terraform.io/coder/coder"},"docker":{"name":"docker","full_name":"registry.terraform.io/kreuzwerker/docker"}},"root_module":{"resources":[{"address":"coder_agent.main","mode":"managed","type":"coder_agent","name":"main","provider_config_key":"coder","expressions":{"arch":{"references":["data.coder_provisioner.me.arch","data.coder_provisioner.me"]},"metadata":[{"display_name":{"constant_value":"CPU Usage"},"interval":{"constant_value":10},"key":{"constant_value":"0_cpu_usage"},"script":{"constant_value":"coder stat cpu"},"timeout":{"constant_value":1}},{"display_name":{"constant_value":"RAM Usage"},"interval":{"constant_value":10},"key":{"constant_value":"1_ram_usage"},"script":{"constant_value":"coder stat mem"},"timeout":{"constant_value":1}}],"os":{"constant_value":"linux"}},"schema_version":1},{"address":"docker_container.workspace","mode":"managed","type":"docker_container","name":"workspace","provider_config_key":"docker","expressions":{"entrypoint":{"references":["coder_agent.main.init_script","coder_agent.main"]},"env":{"references":["coder_agent.main.token","coder_agent.main"]},"host":[{"host":{"constant_value":"host.docker.internal"},"ip":{"constant_value":"host-gateway"}}],"hostname":{"references":["data.coder_workspace.me.name","data.coder_workspace.me"]},"image":{"references":["docker_image.main.name","docker_image.main"]},"labels":[{"label":{"constant_value":"coder.owner"},"value":{"references":["data.coder_workspace.me.owner","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.owner_id"},"value":{"references":["data.coder_workspace.me.owner_id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_id"},"value":{"references":["data.coder_workspace.me.id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_name"},"value":{"references":["data.coder_workspace.me.name","data.coder_workspace.me"]}}],"memory":{"references":["data.coder_parameter.memory_size.value","data.coder_parameter.memory_size"]},"name":{"references":["data.coder_workspace.me.owner","data.coder_workspace.me","data.coder_workspace.me.name","data.coder_workspace.me"]},"restart":{"constant_value":"always"},"volumes":[{"container_path":{"references":["local.username"]},"read_only":{"constant_value":false},"volume_name":{"references":["docker_volume.home_volume.name","docker_volume.home_volume"]}}]},"schema_version":2,"count_expression":{"references":["data.coder_workspace.me.start_count","data.coder_workspace.me"]}},{"address":"docker_image.main","mode":"managed","type":"docker_image","name":"main","provider_config_key":"docker","expressions":{"keep_locally":{"constant_value":true},"name":{"constant_value":"nginx:latest"}},"schema_version":0},{"address":"docker_volume.home_volume","mode":"managed","type":"docker_volume","name":"home_volume","provider_config_key":"docker","expressions":{"labels":[{"label":{"constant_value":"coder.owner"},"value":{"references":["data.coder_workspace.me.owner","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.owner_id"},"value":{"references":["data.coder_workspace.me.owner_id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_id"},"value":{"references":["data.coder_workspace.me.id","data.coder_workspace.me"]}},{"label":{"constant_value":"coder.workspace_name_at_creation"},"value":{"references":["data.coder_workspace.me.name","data.coder_workspace.me"]}}],"name":{"references":["data.coder_workspace.me.id","data.coder_workspace.me"]}},"schema_version":1},{"address":"data.coder_parameter.memory_size","mode":"data","type":"coder_parameter","name":"memory_size","provider_config_key":"coder","expressions":{"default":{"constant_value":"100"},"mutable":{"constant_value":true},"name":{"constant_value":"Memory Allocation"},"type":{"constant_value":"number"}},"schema_version":0},{"address":"data.coder_provisioner.me","mode":"data","type":"coder_provisioner","name":"me","provider_config_key":"coder","schema_version":0},{"address":"data.coder_workspace.me","mode":"data","type":"coder_workspace","name":"me","provider_config_key":"coder","schema_version":0}]}},"relevant_attributes":[{"resource":"docker_volume.home_volume","attribute":["name"]},{"resource":"data.coder_workspace.me","attribute":["owner"]},{"resource":"data.coder_workspace.me","attribute":["owner_id"]},{"resource":"data.coder_workspace.me","attribute":["name"]},{"resource":"data.coder_parameter.memory_size","attribute":["value"]},{"resource":"coder_agent.main","attribute":["init_script"]},{"resource":"docker_image.main","attribute":["name"]},{"resource":"data.coder_provisioner.me","attribute":["arch"]},{"resource":"data.coder_workspace.me","attribute":["id"]},{"resource":"coder_agent.main","attribute":["token"]}],"timestamp":"2024-08-15T11:53:22Z"} EOL } function terraform_graph() { - cat <<'EOL' + cat <<'EOL' digraph { compound = "true" newrank = "true" @@ -57,7 +57,7 @@ EOL } function terraform_init() { - cat <<'EOL' + cat <<'EOL' Initializing the backend... @@ -80,7 +80,7 @@ EOL } function terraform_plan() { - cat <<'EOL' + cat <<'EOL' {"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:38.097648+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} {"@level":"info","@message":"data.coder_workspace.me: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_workspace.me","module":"","resource":"data.coder_workspace.me","implied_provider":"coder","resource_type":"coder_workspace","resource_name":"me","resource_key":null},"action":"read"},"type":"apply_start"} {"@level":"info","@message":"data.coder_parameter.memory_size: Refreshing...","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.194726+02:00","hook":{"resource":{"addr":"data.coder_parameter.memory_size","module":"","resource":"data.coder_parameter.memory_size","implied_provider":"coder","resource_type":"coder_parameter","resource_name":"memory_size","resource_key":null},"action":"read"},"type":"apply_start"} @@ -95,12 +95,12 @@ function terraform_plan() { {"@level":"info","@message":"Plan: 4 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.221589+02:00","changes":{"add":4,"change":0,"import":0,"remove":0,"operation":"plan"},"type":"change_summary"} EOL - # fake writing the state file - terraform_show > terraform.tfstate + # fake writing the state file + terraform_show >terraform.tfstate } function terraform_apply() { - cat <<'EOL' + cat <<'EOL' {"@level":"info","@message":"Terraform 1.9.2","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.507006+02:00","terraform":"1.9.2","type":"version","ui":"1.2"} {"@level":"info","@message":"coder_agent.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572335+02:00","change":{"resource":{"addr":"coder_agent.main","module":"","resource":"coder_agent.main","implied_provider":"coder","resource_type":"coder_agent","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} {"@level":"info","@message":"docker_image.main: Plan to create","@module":"terraform.ui","@timestamp":"2024-08-15T10:26:39.572411+02:00","change":{"resource":{"addr":"docker_image.main","module":"","resource":"docker_image.main","implied_provider":"docker","resource_type":"docker_image","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"} @@ -120,29 +120,29 @@ EOL } # TODO: remove -echo "$@" >> /tmp/blah +echo "$@" >>/tmp/blah case "$1" in - version) - terraform_version - ;; - show) - terraform_show - ;; - graph) - terraform_graph - ;; - init) - terraform_init - ;; - plan) - terraform_plan - ;; - apply) - terraform_apply - ;; - *) - echo "Usage: $0 {version|show|graph|init|plan|apply}" - exit 1 - ;; -esac \ No newline at end of file +version) + terraform_version + ;; +show) + terraform_show + ;; +graph) + terraform_graph + ;; +init) + terraform_init + ;; +plan) + terraform_plan + ;; +apply) + terraform_apply + ;; +*) + echo "Usage: $0 {version|show|graph|init|plan|apply}" + exit 1 + ;; +esac From 724f13929a155f393bc29cdc143ca62b1df13e6d Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 14:14:51 +0000 Subject: [PATCH 15/22] Minor fix-ups Signed-off-by: Danny Kopping --- coderd/database/dump.sql | 48 ++++--- .../000245_provisioner_job_timings.up.sql | 13 ++ .../provisionerdserver/provisionerdserver.go | 2 +- codersdk/workspacebuilds.go | 17 --- flake.nix | 2 +- .../terraform/timings_internal_test.go | 2 +- provisioner/terraform/timings_test.go | 1 + .../e2e/google/protobuf/timestampGenerated.ts | 123 ++++++++++++++++++ site/src/api/typesGenerated.ts | 17 --- 9 files changed, 172 insertions(+), 53 deletions(-) create mode 100644 coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql create mode 100644 site/e2e/google/protobuf/timestampGenerated.ts diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index be044c5ab45a9..add50b1e023dd 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1937,26 +1937,42 @@ CREATE OR REPLACE VIEW provisioner_job_stats AS GREATEST(date_part('epoch'::text, (pj.started_at - pj.created_at)), (0)::double precision) AS queued_secs, GREATEST(date_part('epoch'::text, (pj.completed_at - pj.started_at)), (0)::double precision) AS completion_secs, GREATEST(date_part('epoch'::text, (pj.canceled_at - pj.started_at)), (0)::double precision) AS canceled_secs, - GREATEST(max( + GREATEST(date_part('epoch'::text, (max( CASE - WHEN (pjt.stage = 'init'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) - ELSE NULL::double precision - END), (0)::double precision) AS init_secs, - GREATEST(max( + WHEN (pjt.stage = 'init'::provisioner_job_timing_stage) THEN pjt.ended_at + ELSE NULL::timestamp with time zone + END) - min( CASE - WHEN (pjt.stage = 'plan'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) - ELSE NULL::double precision - END), (0)::double precision) AS plan_secs, - GREATEST(max( + WHEN (pjt.stage = 'init'::provisioner_job_timing_stage) THEN pjt.started_at + ELSE NULL::timestamp with time zone + END))), (0)::double precision) AS init_secs, + GREATEST(date_part('epoch'::text, (max( CASE - WHEN (pjt.stage = 'graph'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) - ELSE NULL::double precision - END), (0)::double precision) AS graph_secs, - GREATEST(max( + WHEN (pjt.stage = 'plan'::provisioner_job_timing_stage) THEN pjt.ended_at + ELSE NULL::timestamp with time zone + END) - min( CASE - WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN date_part('epoch'::text, (pjt.ended_at - pjt.started_at)) - ELSE NULL::double precision - END), (0)::double precision) AS apply_secs + WHEN (pjt.stage = 'plan'::provisioner_job_timing_stage) THEN pjt.started_at + ELSE NULL::timestamp with time zone + END))), (0)::double precision) AS plan_secs, + GREATEST(date_part('epoch'::text, (max( + CASE + WHEN (pjt.stage = 'graph'::provisioner_job_timing_stage) THEN pjt.ended_at + ELSE NULL::timestamp with time zone + END) - min( + CASE + WHEN (pjt.stage = 'graph'::provisioner_job_timing_stage) THEN pjt.started_at + ELSE NULL::timestamp with time zone + END))), (0)::double precision) AS graph_secs, + GREATEST(date_part('epoch'::text, (max( + CASE + WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN pjt.ended_at + ELSE NULL::timestamp with time zone + END) - min( + CASE + WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN pjt.started_at + ELSE NULL::timestamp with time zone + END))), (0)::double precision) AS apply_secs FROM ((provisioner_jobs pj JOIN workspace_builds wb ON ((wb.job_id = pj.id))) LEFT JOIN provisioner_job_timings pjt ON ((pjt.job_id = pj.id))) diff --git a/coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql b/coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql new file mode 100644 index 0000000000000..c98ce6d96a5d1 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql @@ -0,0 +1,13 @@ +INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource) +VALUES + -- Job 1 - init stage + ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 55 minutes', NOW() - INTERVAL '1 hour 50 minutes', 'init', 'source1', 'action1', 'resource1'), + + -- Job 1 - plan stage + ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 50 minutes', NOW() - INTERVAL '1 hour 40 minutes', 'plan', 'source2', 'action2', 'resource2'), + + -- Job 1 - graph stage + ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 40 minutes', NOW() - INTERVAL '1 hour 30 minutes', 'graph', 'source3', 'action3', 'resource3'), + + -- Job 1 - apply stage + ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 30 minutes', NOW() - INTERVAL '1 hour 20 minutes', 'apply', 'source4', 'action4', 'resource4'); diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 04903395162a4..4baa8d96d46fd 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1471,7 +1471,7 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) _, err = s.Database.InsertProvisionerJobTimings(ctx, params) if err != nil { // Don't fail the transaction for non-critical data. - s.Logger.Warn(ctx, "failed to update provisioner job timings", slog.Error(err)) + s.Logger.Warn(ctx, "failed to update provisioner job timings", slog.F("job_id", jobID), slog.Error(err)) } // audit the outcome of the workspace build diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go index c7d18b49b4e3e..682cb424af1b1 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -74,23 +74,6 @@ type WorkspaceBuild struct { DailyCost int32 `json:"daily_cost"` } -// WorkspaceBuildStats is a point-in-time representation of a workspace build, including timing information. -type WorkspaceBuildStats struct { - ID uuid.UUID `json:"id" format:"uuid"` - JobStatus ProvisionerJobStatus `json:"job_status"` - WorkerID uuid.UUID `json:"worker_id" format:"uuid"` - WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"` - Error string `json:"error"` - ErrorCode string `json:"error_code"` - UpdatedAt time.Time `json:"updated_at" format:"date-time"` - QueuedSecs float64 `json:"queued_secs"` - CompletionSecs float64 `json:"completion_secs"` - CanceledSecs float64 `json:"canceled_secs"` - InitSecs float64 `json:"init_secs"` - PlanSecs float64 `json:"plan_secs"` - ApplySecs float64 `json:"apply_secs"` -} - // WorkspaceResource describes resources used to create a workspace, for instance: // containers, images, volumes. type WorkspaceResource struct { diff --git a/flake.nix b/flake.nix index 079d895e482a9..ef438a7581985 100644 --- a/flake.nix +++ b/flake.nix @@ -117,7 +117,7 @@ name = "coder-${osArch}"; # Updated with ./scripts/update-flake.sh`. # This should be updated whenever go.mod changes! - vendorHash = "sha256-AZ0qzh7H+UwnZNyg2iaNMSUWlGgomI/mo70T+FdF7ws="; + vendorHash = "sha256-i3sseokxA5MoSJDzrWJI+GdS5HOYG1fPIRdr239Zx30="; proxyVendor = true; src = ./.; nativeBuildInputs = with pkgs; [ getopt openssl zstd ]; diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go index 2c0f934393662..0fdb43b36d36c 100644 --- a/provisioner/terraform/timings_internal_test.go +++ b/provisioner/terraform/timings_internal_test.go @@ -68,7 +68,7 @@ func TestAggregation(t *testing.T) { // txtar is a text-based archive format used in the stdlib for simple and elegant tests. // // We ALWAYS expect that the archive contains two or more "files": - // 1. JSON logs generated by a terraform execution, one per line, *one file per stage* + // 1. JSON logs generated by a terraform execution, one per line, *one file per stage* // N. Expected resulting timings in JSON form, one per line arc := txtar.Parse(tc.input) require.GreaterOrEqual(t, len(arc.Files), 2) diff --git a/provisioner/terraform/timings_test.go b/provisioner/terraform/timings_test.go index 21f4d96fb43ba..2bd29beb3f096 100644 --- a/provisioner/terraform/timings_test.go +++ b/provisioner/terraform/timings_test.go @@ -94,6 +94,7 @@ func TestTimingsFromProvision(t *testing.T) { // Sort the timings stably to keep reduce flakiness. terraform.StableSortTimings(t, timings) + // Then: the received timings should match the expected values below. // NOTE: These timings have been encoded to JSON format to make the tests more readable. planTimings := terraform.ParseTimingLines(t, []byte(`{"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195836Z", "action":"read", "source":"coder", "resource":"data.coder_parameter.memory_size", "stage":"plan", "state":"COMPLETED"} {"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195712Z", "action":"read", "source":"coder", "resource":"data.coder_provisioner.me", "stage":"plan", "state":"COMPLETED"} diff --git a/site/e2e/google/protobuf/timestampGenerated.ts b/site/e2e/google/protobuf/timestampGenerated.ts new file mode 100644 index 0000000000000..6dd4b08e96087 --- /dev/null +++ b/site/e2e/google/protobuf/timestampGenerated.ts @@ -0,0 +1,123 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; + +export const protobufPackage = "google.protobuf"; + +/** + * A Timestamp represents a point in time independent of any time zone or local + * calendar, encoded as a count of seconds and fractions of seconds at + * nanosecond resolution. The count is relative to an epoch at UTC midnight on + * January 1, 1970, in the proleptic Gregorian calendar which extends the + * Gregorian calendar backwards to year one. + * + * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap + * second table is needed for interpretation, using a [24-hour linear + * smear](https://developers.google.com/time/smear). + * + * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By + * restricting to that range, we ensure that we can convert to and from [RFC + * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. + * + * # Examples + * + * Example 1: Compute Timestamp from POSIX `time()`. + * + * Timestamp timestamp; + * timestamp.set_seconds(time(NULL)); + * timestamp.set_nanos(0); + * + * Example 2: Compute Timestamp from POSIX `gettimeofday()`. + * + * struct timeval tv; + * gettimeofday(&tv, NULL); + * + * Timestamp timestamp; + * timestamp.set_seconds(tv.tv_sec); + * timestamp.set_nanos(tv.tv_usec * 1000); + * + * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + * + * FILETIME ft; + * GetSystemTimeAsFileTime(&ft); + * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + * + * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z + * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. + * Timestamp timestamp; + * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); + * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); + * + * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + * + * long millis = System.currentTimeMillis(); + * + * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + * .setNanos((int) ((millis % 1000) * 1000000)).build(); + * + * Example 5: Compute Timestamp from Java `Instant.now()`. + * + * Instant now = Instant.now(); + * + * Timestamp timestamp = + * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + * .setNanos(now.getNano()).build(); + * + * Example 6: Compute Timestamp from current time in Python. + * + * timestamp = Timestamp() + * timestamp.GetCurrentTime() + * + * # JSON Mapping + * + * In JSON format, the Timestamp type is encoded as a string in the + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" + * where {year} is always expressed using four digits while {month}, {day}, + * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional + * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + * is required. A proto3 JSON serializer should always use UTC (as indicated by + * "Z") when printing the Timestamp type and a proto3 JSON parser should be + * able to accept both UTC and other timezones (as indicated by an offset). + * + * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past + * 01:30 UTC on January 15, 2017. + * + * In JavaScript, one can convert a Date object to this format using the + * standard + * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) + * method. In Python, a standard `datetime.datetime` object can be converted + * to this format using + * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with + * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use + * the Joda Time's [`ISODateTimeFormat.dateTime()`]( + * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() + * ) to obtain a formatter capable of generating timestamps in this format. + */ +export interface Timestamp { + /** + * Represents seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + */ + seconds: number; + /** + * Non-negative fractions of a second at nanosecond resolution. Negative + * second values with fractions must still have non-negative nanos values + * that count forward in time. Must be from 0 to 999,999,999 + * inclusive. + */ + nanos: number; +} + +export const Timestamp = { + encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.seconds !== 0) { + writer.uint32(8).int64(message.seconds); + } + if (message.nanos !== 0) { + writer.uint32(16).int32(message.nanos); + } + return writer; + }, +}; diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 12085382a3145..49e0774edfcdb 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1904,23 +1904,6 @@ export interface WorkspaceBuildParameter { readonly value: string; } -// From codersdk/workspacebuilds.go -export interface WorkspaceBuildStats { - readonly id: string; - readonly job_status: ProvisionerJobStatus; - readonly worker_id: string; - readonly workspace_id: string; - readonly error: string; - readonly error_code: string; - readonly updated_at: string; - readonly queued_secs: number; - readonly completion_secs: number; - readonly canceled_secs: number; - readonly init_secs: number; - readonly plan_secs: number; - readonly apply_secs: number; -} - // From codersdk/workspaces.go export interface WorkspaceBuildsRequest extends Pagination { readonly since?: string; From c30a90028f75702737848fb77a2ef53b706262cc Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 15 Aug 2024 14:56:02 +0000 Subject: [PATCH 16/22] Pls god let this work Signed-off-by: Danny Kopping --- .../fixtures/000245_provisioner_job_timings.up.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql b/coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql index c98ce6d96a5d1..ef05eee51e807 100644 --- a/coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql +++ b/coderd/database/migrations/testdata/fixtures/000245_provisioner_job_timings.up.sql @@ -1,13 +1,13 @@ INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource) VALUES -- Job 1 - init stage - ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 55 minutes', NOW() - INTERVAL '1 hour 50 minutes', 'init', 'source1', 'action1', 'resource1'), + ('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 55 minutes', NOW() - INTERVAL '1 hour 50 minutes', 'init', 'source1', 'action1', 'resource1'), -- Job 1 - plan stage - ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 50 minutes', NOW() - INTERVAL '1 hour 40 minutes', 'plan', 'source2', 'action2', 'resource2'), + ('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 50 minutes', NOW() - INTERVAL '1 hour 40 minutes', 'plan', 'source2', 'action2', 'resource2'), -- Job 1 - graph stage - ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 40 minutes', NOW() - INTERVAL '1 hour 30 minutes', 'graph', 'source3', 'action3', 'resource3'), + ('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 40 minutes', NOW() - INTERVAL '1 hour 30 minutes', 'graph', 'source3', 'action3', 'resource3'), -- Job 1 - apply stage - ('d09b6083-e482-41ac-ad06-3aa731ec4fc6', NOW() - INTERVAL '1 hour 30 minutes', NOW() - INTERVAL '1 hour 20 minutes', 'apply', 'source4', 'action4', 'resource4'); + ('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 30 minutes', NOW() - INTERVAL '1 hour 20 minutes', 'apply', 'source4', 'action4', 'resource4'); From 0d68e69800bae3078e4bebf133dbafce7706705e Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Mon, 19 Aug 2024 10:34:10 +0200 Subject: [PATCH 17/22] Move terraform test helpers into internal package Signed-off-by: Danny Kopping --- .../timings_test_utils.go} | 26 ------------- .../terraform/timings_internal_test.go | 39 ++++++++++++++++--- provisioner/terraform/timings_test.go | 20 +++++----- 3 files changed, 44 insertions(+), 41 deletions(-) rename provisioner/terraform/{testutil.go => internal/timings_test_utils.go} (81%) diff --git a/provisioner/terraform/testutil.go b/provisioner/terraform/internal/timings_test_utils.go similarity index 81% rename from provisioner/terraform/testutil.go rename to provisioner/terraform/internal/timings_test_utils.go index cab8a5f888966..59582a1c4b750 100644 --- a/provisioner/terraform/testutil.go +++ b/provisioner/terraform/internal/timings_test_utils.go @@ -63,32 +63,6 @@ func TimingsAreEqual(t *testing.T, expected []*proto.Timing, actual []*proto.Tim return true } -func IngestAllSpans(t *testing.T, input []byte, aggregator *timingAggregator) { - t.Helper() - - scanner := bufio.NewScanner(bytes.NewBuffer(input)) - for scanner.Scan() { - line := scanner.Bytes() - log := parseTerraformLogLine(line) - if log == nil { - continue - } - - ts, span, err := extractTimingSpan(log) - if err != nil { - // t.Logf("%s: failed span extraction on line: %q", err, line) - continue - } - - require.NotZerof(t, ts, "failed on line: %q", line) - require.NotNilf(t, span, "failed on line: %q", line) - - aggregator.ingest(ts, span) - } - - require.NoError(t, scanner.Err()) -} - func PrintTiming(t *testing.T, timing *proto.Timing) { t.Helper() diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go index 0fdb43b36d36c..df6fd9e32863b 100644 --- a/provisioner/terraform/timings_internal_test.go +++ b/provisioner/terraform/timings_internal_test.go @@ -1,6 +1,8 @@ package terraform import ( + "bufio" + "bytes" _ "embed" "testing" @@ -9,6 +11,7 @@ import ( "golang.org/x/tools/txtar" "github.com/coder/coder/v2/coderd/database" + terraform_internal "github.com/coder/coder/v2/provisioner/terraform/internal" "github.com/coder/coder/v2/provisionersdk/proto" ) @@ -88,25 +91,51 @@ func TestAggregation(t *testing.T) { file.Name, database.AllProvisionerJobTimingStageValues()) agg := newTimingAggregator(stage) - IngestAllSpans(t, file.Data, agg) + ingestAllSpans(t, file.Data, agg) actualTimings = append(actualTimings, agg.aggregate()...) } // Ensure that the expected timings were produced. - expected := ParseTimingLines(t, expectedTimings.Data) - StableSortTimings(t, actualTimings) // To reduce flakiness. - if !assert.True(t, TimingsAreEqual(t, expected, actualTimings)) { + expected := terraform_internal.ParseTimingLines(t, expectedTimings.Data) + terraform_internal.StableSortTimings(t, actualTimings) // To reduce flakiness. + if !assert.True(t, terraform_internal.TimingsAreEqual(t, expected, actualTimings)) { printExpectation(t, expected) } }) } } +func ingestAllSpans(t *testing.T, input []byte, aggregator *timingAggregator) { + t.Helper() + + scanner := bufio.NewScanner(bytes.NewBuffer(input)) + for scanner.Scan() { + line := scanner.Bytes() + log := parseTerraformLogLine(line) + if log == nil { + continue + } + + ts, span, err := extractTimingSpan(log) + if err != nil { + // t.Logf("%s: failed span extraction on line: %q", err, line) + continue + } + + require.NotZerof(t, ts, "failed on line: %q", line) + require.NotNilf(t, span, "failed on line: %q", line) + + aggregator.ingest(ts, span) + } + + require.NoError(t, scanner.Err()) +} + func printExpectation(t *testing.T, actual []*proto.Timing) { t.Helper() t.Log("expected:") for _, a := range actual { - PrintTiming(t, a) + terraform_internal.PrintTiming(t, a) } } diff --git a/provisioner/terraform/timings_test.go b/provisioner/terraform/timings_test.go index 2bd29beb3f096..21c06eb9137f2 100644 --- a/provisioner/terraform/timings_test.go +++ b/provisioner/terraform/timings_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/provisioner/terraform" + terraform_internal "github.com/coder/coder/v2/provisioner/terraform/internal" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" ) @@ -92,19 +92,19 @@ func TestTimingsFromProvision(t *testing.T) { } // Sort the timings stably to keep reduce flakiness. - terraform.StableSortTimings(t, timings) + terraform_internal.StableSortTimings(t, timings) // Then: the received timings should match the expected values below. // NOTE: These timings have been encoded to JSON format to make the tests more readable. - planTimings := terraform.ParseTimingLines(t, []byte(`{"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195836Z", "action":"read", "source":"coder", "resource":"data.coder_parameter.memory_size", "stage":"plan", "state":"COMPLETED"} + planTimings := terraform_internal.ParseTimingLines(t, []byte(`{"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195836Z", "action":"read", "source":"coder", "resource":"data.coder_parameter.memory_size", "stage":"plan", "state":"COMPLETED"} {"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195712Z", "action":"read", "source":"coder", "resource":"data.coder_provisioner.me", "stage":"plan", "state":"COMPLETED"} {"start":"2024-08-15T08:26:39.194726Z", "end":"2024-08-15T08:26:39.195820Z", "action":"read", "source":"coder", "resource":"data.coder_workspace.me", "stage":"plan", "state":"COMPLETED"}`)) - applyTimings := terraform.ParseTimingLines(t, []byte(`{"start":"2024-08-15T08:26:39.616546Z", "end":"2024-08-15T08:26:39.618045Z", "action":"create", "source":"coder", "resource":"coder_agent.main", "stage":"apply", "state":"COMPLETED"} + applyTimings := terraform_internal.ParseTimingLines(t, []byte(`{"start":"2024-08-15T08:26:39.616546Z", "end":"2024-08-15T08:26:39.618045Z", "action":"create", "source":"coder", "resource":"coder_agent.main", "stage":"apply", "state":"COMPLETED"} {"start":"2024-08-15T08:26:39.626722Z", "end":"2024-08-15T08:26:39.669954Z", "action":"create", "source":"docker", "resource":"docker_image.main", "stage":"apply", "state":"COMPLETED"} {"start":"2024-08-15T08:26:39.627335Z", "end":"2024-08-15T08:26:39.660616Z", "action":"create", "source":"docker", "resource":"docker_volume.home_volume", "stage":"apply", "state":"COMPLETED"} {"start":"2024-08-15T08:26:39.682223Z", "end":"2024-08-15T08:26:40.186482Z", "action":"create", "source":"docker", "resource":"docker_container.workspace[0]", "stage":"apply", "state":"COMPLETED"}`)) - initTiming := terraform.ParseTimingLines(t, []byte(`{"start":"2000-01-01T01:01:01.123456Z", "end":"2000-01-01T01:01:01.123456Z", "action":"initializing terraform", "source":"terraform", "resource":"state file", "stage":"init", "state":"COMPLETED"}`))[0] - graphTiming := terraform.ParseTimingLines(t, []byte(`{"start":"2000-01-01T01:01:01.123456Z", "end":"2000-01-01T01:01:01.123456Z", "action":"building terraform dependency graph", "source":"terraform", "resource":"state file", "stage":"graph", "state":"COMPLETED"}`))[0] + initTiming := terraform_internal.ParseTimingLines(t, []byte(`{"start":"2000-01-01T01:01:01.123456Z", "end":"2000-01-01T01:01:01.123456Z", "action":"initializing terraform", "source":"terraform", "resource":"state file", "stage":"init", "state":"COMPLETED"}`))[0] + graphTiming := terraform_internal.ParseTimingLines(t, []byte(`{"start":"2000-01-01T01:01:01.123456Z", "end":"2000-01-01T01:01:01.123456Z", "action":"building terraform dependency graph", "source":"terraform", "resource":"state file", "stage":"graph", "state":"COMPLETED"}`))[0] require.Len(t, timings, len(planTimings)+len(applyTimings)+2) @@ -119,15 +119,15 @@ func TestTimingsFromProvision(t *testing.T) { switch tim.Stage { case string(database.ProvisionerJobTimingStageInit): tim.Start, tim.End = initTiming.Start, initTiming.End - require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{initTiming}, []*proto.Timing{tim})) + require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{initTiming}, []*proto.Timing{tim})) case string(database.ProvisionerJobTimingStageGraph): tim.Start, tim.End = graphTiming.Start, graphTiming.End - require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{graphTiming}, []*proto.Timing{tim})) + require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{graphTiming}, []*proto.Timing{tim})) case string(database.ProvisionerJobTimingStagePlan): - require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{planTimings[pCursor]}, []*proto.Timing{tim})) + require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{planTimings[pCursor]}, []*proto.Timing{tim})) pCursor++ case string(database.ProvisionerJobTimingStageApply): - require.True(t, terraform.TimingsAreEqual(t, []*proto.Timing{applyTimings[aCursor]}, []*proto.Timing{tim})) + require.True(t, terraform_internal.TimingsAreEqual(t, []*proto.Timing{applyTimings[aCursor]}, []*proto.Timing{tim})) aCursor++ } } From 15282bbf97879dc6db3b62fee5fa74c11881645b Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Mon, 19 Aug 2024 11:04:11 +0200 Subject: [PATCH 18/22] Review comments Signed-off-by: Danny Kopping --- coderd/database/dbtime/dbtime.go | 5 ++++- coderd/database/queries.sql.go | 12 ++++++------ coderd/database/queries/provisionerjobs.sql | 12 ++++++------ .../provisionerdserver/provisionerdserver.go | 13 +++++-------- coderd/workspacebuilds.go | 1 - provisioner/terraform/timings.go | 18 ++++++++++-------- site/.eslintignore | 1 + site/.prettierignore | 1 + 8 files changed, 33 insertions(+), 30 deletions(-) diff --git a/coderd/database/dbtime/dbtime.go b/coderd/database/dbtime/dbtime.go index a2f88fcdb8e91..c0e9a13027eb4 100644 --- a/coderd/database/dbtime/dbtime.go +++ b/coderd/database/dbtime/dbtime.go @@ -9,6 +9,9 @@ func Now() time.Time { // Time returns a time compatible with Postgres. Postgres only stores dates with // microsecond precision. +// FIXME(dannyk): refactor all calls to Time() to expect the input time to be modified to UTC; there are currently a +// few calls whose behaviour would change subtly. +// See https://github.com/coder/coder/pull/14274#discussion_r1718427461 func Time(t time.Time) time.Time { - return t.UTC().Round(time.Microsecond) + return t.Round(time.Microsecond) } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 1bc875022d845..a2358db0fd70c 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5608,12 +5608,12 @@ const insertProvisionerJobTimings = `-- name: InsertProvisionerJobTimings :many INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource) SELECT $1::uuid AS provisioner_job_id, - unnest($2::timestamptz[]) AS started_at, - unnest($3::timestamptz[]) AS ended_at, - unnest($4::provisioner_job_timing_stage[]) AS context, - unnest($5::text[]) AS context, - unnest($6::text[]) AS action, - unnest($7::text[]) AS resource + unnest($2::timestamptz[]), + unnest($3::timestamptz[]), + unnest($4::provisioner_job_timing_stage[]), + unnest($5::text[]), + unnest($6::text[]), + unnest($7::text[]) RETURNING job_id, started_at, ended_at, stage, source, action, resource ` diff --git a/coderd/database/queries/provisionerjobs.sql b/coderd/database/queries/provisionerjobs.sql index 6fe5f126c467a..687176d3c255b 100644 --- a/coderd/database/queries/provisionerjobs.sql +++ b/coderd/database/queries/provisionerjobs.sql @@ -149,10 +149,10 @@ WHERE INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource) SELECT @job_id::uuid AS provisioner_job_id, - unnest(@started_at::timestamptz[]) AS started_at, - unnest(@ended_at::timestamptz[]) AS ended_at, - unnest(@stage::provisioner_job_timing_stage[]) AS context, - unnest(@source::text[]) AS context, - unnest(@action::text[]) AS action, - unnest(@resource::text[]) AS resource + unnest(@started_at::timestamptz[]), + unnest(@ended_at::timestamptz[]), + unnest(@stage::provisioner_job_timing_stage[]), + unnest(@source::text[]), + unnest(@action::text[]), + unnest(@resource::text[]) RETURNING *; diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 4baa8d96d46fd..403b114a74e37 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1447,12 +1447,9 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) JobID: jobID, } for _, t := range completed.GetWorkspaceBuild().GetTimings() { - var start, end time.Time - if t.Start != nil { - start = t.Start.AsTime() - } - if t.End != nil { - end = t.End.AsTime() + if t.Start == nil || t.End == nil { + s.Logger.Warn(ctx, "timings entry has nil start or end time", slog.F("entry", t.String())) + continue } var stg database.ProvisionerJobTimingStage @@ -1465,8 +1462,8 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) params.Source = append(params.Source, t.Source) params.Resource = append(params.Resource, t.Resource) params.Action = append(params.Action, t.Action) - params.StartedAt = append(params.StartedAt, start) - params.EndedAt = append(params.EndedAt, end) + params.StartedAt = append(params.StartedAt, t.Start.AsTime()) + params.EndedAt = append(params.EndedAt, t.End.AsTime()) } _, err = s.Database.InsertProvisionerJobTimings(ctx, params) if err != nil { diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index e5b362de4a802..e04e585d4aa53 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -635,7 +635,6 @@ func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { return } - // TODO: why? // You must have update permissions on the template to get the state. // This matches a push! if !api.Authorize(r, policy.ActionUpdate, template.RBACObject()) { diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index d6a2ac96e9987..d7d4f57d13b21 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -49,9 +49,10 @@ const ( ) type timingAggregator struct { - mu sync.Mutex - stage database.ProvisionerJobTimingStage + + // Protects the stateLookup map. + lookupMu sync.Mutex stateLookup map[uint64]*timingSpan } @@ -81,7 +82,7 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { } s.stage = t.stage - ts = dbtime.Time(ts) + ts = dbtime.Time(ts.UTC()) switch s.kind { case timingApplyStart, timingProvisionStart, timingRefreshStart, timingInitStart, timingGraphStart: @@ -98,21 +99,22 @@ func (t *timingAggregator) ingest(ts time.Time, s *timingSpan) { return } - t.mu.Lock() + t.lookupMu.Lock() // Memoize this span by its unique attributes and the determined state. // This will be used in aggregate() to determine the duration of the resource action. t.stateLookup[s.hashByState(s.state)] = s - t.mu.Unlock() + t.lookupMu.Unlock() } // aggregate performs a pass through all memoized events to build up a slice of *proto.Timing instances which represent // the total time taken to perform a certain action. // The resulting slice of *proto.Timing is NOT sorted. func (t *timingAggregator) aggregate() []*proto.Timing { - t.mu.Lock() - defer t.mu.Unlock() + t.lookupMu.Lock() + defer t.lookupMu.Unlock() - out := make([]*proto.Timing, 0, len(t.stateLookup)) + // Pre-allocate len(measurements)/2 since each timing will have one STARTED and one FAILED/COMPLETED entry. + out := make([]*proto.Timing, 0, len(t.stateLookup)/2) for _, s := range t.stateLookup { // We are only concerned here with failed or completed events. diff --git a/site/.eslintignore b/site/.eslintignore index 919493e42a19c..6b774063694c5 100644 --- a/site/.eslintignore +++ b/site/.eslintignore @@ -90,6 +90,7 @@ result # Generated files shouldn't be formatted. e2e/provisionerGenerated.ts +site/e2e/google/protobuf/timestampGenerated.ts **/pnpm-lock.yaml diff --git a/site/.prettierignore b/site/.prettierignore index 919493e42a19c..6b774063694c5 100644 --- a/site/.prettierignore +++ b/site/.prettierignore @@ -90,6 +90,7 @@ result # Generated files shouldn't be formatted. e2e/provisionerGenerated.ts +site/e2e/google/protobuf/timestampGenerated.ts **/pnpm-lock.yaml From 597ec850440114064ae68cebe9295a162dcec2e4 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Mon, 19 Aug 2024 10:21:10 +0000 Subject: [PATCH 19/22] More CI happiness Signed-off-by: Danny Kopping --- coderd/database/dbtime/dbtime.go | 5 +- provisioner/terraform/timings.go | 2 +- provisioner/terraform/timings_test.go | 2 + provisionerd/proto/provisionerd_drpc.pb.go | 2 +- site/e2e/provisionerGenerated.ts | 68 ++++++++++++++++++++++ 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/coderd/database/dbtime/dbtime.go b/coderd/database/dbtime/dbtime.go index c0e9a13027eb4..4d740ba941345 100644 --- a/coderd/database/dbtime/dbtime.go +++ b/coderd/database/dbtime/dbtime.go @@ -10,8 +10,9 @@ func Now() time.Time { // Time returns a time compatible with Postgres. Postgres only stores dates with // microsecond precision. // FIXME(dannyk): refactor all calls to Time() to expect the input time to be modified to UTC; there are currently a -// few calls whose behaviour would change subtly. -// See https://github.com/coder/coder/pull/14274#discussion_r1718427461 +// +// few calls whose behavior would change subtly. +// See https://github.com/coder/coder/pull/14274#discussion_r1718427461 func Time(t time.Time) time.Time { return t.Round(time.Microsecond) } diff --git a/provisioner/terraform/timings.go b/provisioner/terraform/timings.go index d7d4f57d13b21..0bf6d537d4304 100644 --- a/provisioner/terraform/timings.go +++ b/provisioner/terraform/timings.go @@ -49,7 +49,7 @@ const ( ) type timingAggregator struct { - stage database.ProvisionerJobTimingStage + stage database.ProvisionerJobTimingStage // Protects the stateLookup map. lookupMu sync.Mutex diff --git a/provisioner/terraform/timings_test.go b/provisioner/terraform/timings_test.go index 21c06eb9137f2..0d11040db9bfc 100644 --- a/provisioner/terraform/timings_test.go +++ b/provisioner/terraform/timings_test.go @@ -1,3 +1,5 @@ +//go:build linux || darwin + package terraform_test import ( diff --git a/provisionerd/proto/provisionerd_drpc.pb.go b/provisionerd/proto/provisionerd_drpc.pb.go index 998a56b6eeda0..60d78a86acb17 100644 --- a/provisionerd/proto/provisionerd_drpc.pb.go +++ b/provisionerd/proto/provisionerd_drpc.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-go-drpc. DO NOT EDIT. -// protoc-gen-go-drpc version: (devel) +// protoc-gen-go-drpc version: v0.0.33 // source: provisionerd/proto/provisionerd.proto package proto diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index 9168d4b69bb3b..acea867faaae9 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -1,6 +1,7 @@ /* eslint-disable */ import * as _m0 from "protobufjs/minimal"; import { Observable } from "rxjs"; +import { Timestamp } from "./google/protobuf/timestampGenerated"; export const protobufPackage = "provisioner"; @@ -29,6 +30,13 @@ export enum WorkspaceTransition { UNRECOGNIZED = -1, } +export enum TimingState { + STARTED = 0, + COMPLETED = 1, + FAILED = 2, + UNRECOGNIZED = -1, +} + /** Empty indicates a successful request/response. */ export interface Empty {} @@ -275,6 +283,7 @@ export interface PlanComplete { resources: Resource[]; parameters: RichParameter[]; externalAuthProviders: ExternalAuthProviderResource[]; + timings: Timing[]; } /** @@ -292,6 +301,17 @@ export interface ApplyComplete { resources: Resource[]; parameters: RichParameter[]; externalAuthProviders: ExternalAuthProviderResource[]; + timings: Timing[]; +} + +export interface Timing { + start: Date | undefined; + end: Date | undefined; + action: string; + source: string; + resource: string; + stage: string; + state: TimingState; } /** CancelRequest requests that the previous request be canceled gracefully. */ @@ -965,6 +985,9 @@ export const PlanComplete = { writer.uint32(34).fork(), ).ldelim(); } + for (const v of message.timings) { + Timing.encode(v!, writer.uint32(50).fork()).ldelim(); + } return writer; }, }; @@ -1004,6 +1027,45 @@ export const ApplyComplete = { writer.uint32(42).fork(), ).ldelim(); } + for (const v of message.timings) { + Timing.encode(v!, writer.uint32(50).fork()).ldelim(); + } + return writer; + }, +}; + +export const Timing = { + encode( + message: Timing, + writer: _m0.Writer = _m0.Writer.create(), + ): _m0.Writer { + if (message.start !== undefined) { + Timestamp.encode( + toTimestamp(message.start), + writer.uint32(10).fork(), + ).ldelim(); + } + if (message.end !== undefined) { + Timestamp.encode( + toTimestamp(message.end), + writer.uint32(18).fork(), + ).ldelim(); + } + if (message.action !== "") { + writer.uint32(26).string(message.action); + } + if (message.source !== "") { + writer.uint32(34).string(message.source); + } + if (message.resource !== "") { + writer.uint32(42).string(message.resource); + } + if (message.stage !== "") { + writer.uint32(50).string(message.stage); + } + if (message.state !== 0) { + writer.uint32(56).int32(message.state); + } return writer; }, }; @@ -1077,3 +1139,9 @@ export interface Provisioner { */ Session(request: Observable): Observable; } + +function toTimestamp(date: Date): Timestamp { + const seconds = date.getTime() / 1_000; + const nanos = (date.getTime() % 1_000) * 1_000_000; + return { seconds, nanos }; +} From 805c0f201ddf05cd0ed09551e776578087ffe20d Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Mon, 19 Aug 2024 10:48:49 +0000 Subject: [PATCH 20/22] Restrict timings tests to non-Windows Signed-off-by: Danny Kopping --- provisioner/terraform/timings_internal_test.go | 2 ++ provisionerd/proto/provisionerd.pb.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/provisioner/terraform/timings_internal_test.go b/provisioner/terraform/timings_internal_test.go index df6fd9e32863b..2dd19f7afbcf8 100644 --- a/provisioner/terraform/timings_internal_test.go +++ b/provisioner/terraform/timings_internal_test.go @@ -1,3 +1,5 @@ +//go:build linux || darwin + package terraform import ( diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index 9f51702f67802..7f455390b8bf2 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v4.25.3 +// protoc-gen-go v1.30.0 +// protoc v4.23.3 // source: provisionerd/proto/provisionerd.proto package proto From 46f33187e43629ac132b246b753a00c3af9950b2 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Mon, 19 Aug 2024 11:11:52 +0000 Subject: [PATCH 21/22] Give CI exactly what it wants FFS (see https://github.com/coder/coder/issues/14343) Signed-off-by: Danny Kopping --- provisionerd/proto/provisionerd.pb.go | 46 ++++++------- provisionersdk/proto/provisioner.pb.go | 76 ++++++++++----------- provisionersdk/proto/provisioner_drpc.pb.go | 2 +- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index 7f455390b8bf2..d026167eed757 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -1691,7 +1691,7 @@ func file_provisionerd_proto_provisionerd_proto_rawDescGZIP() []byte { var file_provisionerd_proto_provisionerd_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 21) -var file_provisionerd_proto_provisionerd_proto_goTypes = []any{ +var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (LogSource)(0), // 0: provisionerd.LogSource (*Empty)(nil), // 1: provisionerd.Empty (*AcquiredJob)(nil), // 2: provisionerd.AcquiredJob @@ -1785,7 +1785,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_provisionerd_proto_provisionerd_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Empty); i { case 0: return &v.state @@ -1797,7 +1797,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcquiredJob); i { case 0: return &v.state @@ -1809,7 +1809,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FailedJob); i { case 0: return &v.state @@ -1821,7 +1821,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob); i { case 0: return &v.state @@ -1833,7 +1833,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Log); i { case 0: return &v.state @@ -1845,7 +1845,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateJobRequest); i { case 0: return &v.state @@ -1857,7 +1857,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateJobResponse); i { case 0: return &v.state @@ -1869,7 +1869,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[7].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CommitQuotaRequest); i { case 0: return &v.state @@ -1881,7 +1881,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[8].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CommitQuotaResponse); i { case 0: return &v.state @@ -1893,7 +1893,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[9].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CancelAcquire); i { case 0: return &v.state @@ -1905,7 +1905,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcquiredJob_WorkspaceBuild); i { case 0: return &v.state @@ -1917,7 +1917,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[11].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcquiredJob_TemplateImport); i { case 0: return &v.state @@ -1929,7 +1929,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[12].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcquiredJob_TemplateDryRun); i { case 0: return &v.state @@ -1941,7 +1941,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[14].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FailedJob_WorkspaceBuild); i { case 0: return &v.state @@ -1953,7 +1953,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[15].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FailedJob_TemplateImport); i { case 0: return &v.state @@ -1965,7 +1965,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[16].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FailedJob_TemplateDryRun); i { case 0: return &v.state @@ -1977,7 +1977,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[17].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_WorkspaceBuild); i { case 0: return &v.state @@ -1989,7 +1989,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[18].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_TemplateImport); i { case 0: return &v.state @@ -2001,7 +2001,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { return nil } } - file_provisionerd_proto_provisionerd_proto_msgTypes[19].Exporter = func(v any, i int) any { + file_provisionerd_proto_provisionerd_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_TemplateDryRun); i { case 0: return &v.state @@ -2014,17 +2014,17 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } } - file_provisionerd_proto_provisionerd_proto_msgTypes[1].OneofWrappers = []any{ + file_provisionerd_proto_provisionerd_proto_msgTypes[1].OneofWrappers = []interface{}{ (*AcquiredJob_WorkspaceBuild_)(nil), (*AcquiredJob_TemplateImport_)(nil), (*AcquiredJob_TemplateDryRun_)(nil), } - file_provisionerd_proto_provisionerd_proto_msgTypes[2].OneofWrappers = []any{ + file_provisionerd_proto_provisionerd_proto_msgTypes[2].OneofWrappers = []interface{}{ (*FailedJob_WorkspaceBuild_)(nil), (*FailedJob_TemplateImport_)(nil), (*FailedJob_TemplateDryRun_)(nil), } - file_provisionerd_proto_provisionerd_proto_msgTypes[3].OneofWrappers = []any{ + file_provisionerd_proto_provisionerd_proto_msgTypes[3].OneofWrappers = []interface{}{ (*CompletedJob_WorkspaceBuild_)(nil), (*CompletedJob_TemplateImport_)(nil), (*CompletedJob_TemplateDryRun_)(nil), diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 6708edbbcf024..c53abebe67fc8 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v4.25.3 +// protoc-gen-go v1.30.0 +// protoc v4.23.3 // source: provisionersdk/proto/provisioner.proto package proto @@ -3304,7 +3304,7 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte { var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 33) -var file_provisionersdk_proto_provisioner_proto_goTypes = []any{ +var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: provisioner.LogLevel (AppSharingLevel)(0), // 1: provisioner.AppSharingLevel (WorkspaceTransition)(0), // 2: provisioner.WorkspaceTransition @@ -3400,7 +3400,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_provisionersdk_proto_provisioner_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Empty); i { case 0: return &v.state @@ -3412,7 +3412,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TemplateVariable); i { case 0: return &v.state @@ -3424,7 +3424,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RichParameterOption); i { case 0: return &v.state @@ -3436,7 +3436,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RichParameter); i { case 0: return &v.state @@ -3448,7 +3448,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RichParameterValue); i { case 0: return &v.state @@ -3460,7 +3460,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VariableValue); i { case 0: return &v.state @@ -3472,7 +3472,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Log); i { case 0: return &v.state @@ -3484,7 +3484,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[7].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InstanceIdentityAuth); i { case 0: return &v.state @@ -3496,7 +3496,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[8].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExternalAuthProviderResource); i { case 0: return &v.state @@ -3508,7 +3508,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[9].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExternalAuthProvider); i { case 0: return &v.state @@ -3520,7 +3520,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[10].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Agent); i { case 0: return &v.state @@ -3532,7 +3532,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DisplayApps); i { case 0: return &v.state @@ -3544,7 +3544,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[12].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Env); i { case 0: return &v.state @@ -3556,7 +3556,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Script); i { case 0: return &v.state @@ -3568,7 +3568,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*App); i { case 0: return &v.state @@ -3580,7 +3580,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Healthcheck); i { case 0: return &v.state @@ -3592,7 +3592,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Resource); i { case 0: return &v.state @@ -3604,7 +3604,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Metadata); i { case 0: return &v.state @@ -3616,7 +3616,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state @@ -3628,7 +3628,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ParseRequest); i { case 0: return &v.state @@ -3640,7 +3640,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ParseComplete); i { case 0: return &v.state @@ -3652,7 +3652,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PlanRequest); i { case 0: return &v.state @@ -3664,7 +3664,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PlanComplete); i { case 0: return &v.state @@ -3676,7 +3676,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ApplyRequest); i { case 0: return &v.state @@ -3688,7 +3688,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[24].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ApplyComplete); i { case 0: return &v.state @@ -3700,7 +3700,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[25].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Timing); i { case 0: return &v.state @@ -3712,7 +3712,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[26].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CancelRequest); i { case 0: return &v.state @@ -3724,7 +3724,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[27].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Request); i { case 0: return &v.state @@ -3736,7 +3736,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[28].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Response); i { case 0: return &v.state @@ -3748,7 +3748,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[29].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Agent_Metadata); i { case 0: return &v.state @@ -3760,7 +3760,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[31].Exporter = func(v any, i int) any { + file_provisionersdk_proto_provisioner_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Resource_Metadata); i { case 0: return &v.state @@ -3773,19 +3773,19 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } } - file_provisionersdk_proto_provisioner_proto_msgTypes[3].OneofWrappers = []any{} - file_provisionersdk_proto_provisioner_proto_msgTypes[10].OneofWrappers = []any{ + file_provisionersdk_proto_provisioner_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_provisionersdk_proto_provisioner_proto_msgTypes[10].OneofWrappers = []interface{}{ (*Agent_Token)(nil), (*Agent_InstanceId)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[27].OneofWrappers = []any{ + file_provisionersdk_proto_provisioner_proto_msgTypes[27].OneofWrappers = []interface{}{ (*Request_Config)(nil), (*Request_Parse)(nil), (*Request_Plan)(nil), (*Request_Apply)(nil), (*Request_Cancel)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[28].OneofWrappers = []any{ + file_provisionersdk_proto_provisioner_proto_msgTypes[28].OneofWrappers = []interface{}{ (*Response_Log)(nil), (*Response_Parse)(nil), (*Response_Plan)(nil), diff --git a/provisionersdk/proto/provisioner_drpc.pb.go b/provisionersdk/proto/provisioner_drpc.pb.go index c9c54002439c2..de310e779dcaa 100644 --- a/provisionersdk/proto/provisioner_drpc.pb.go +++ b/provisionersdk/proto/provisioner_drpc.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-go-drpc. DO NOT EDIT. -// protoc-gen-go-drpc version: (devel) +// protoc-gen-go-drpc version: v0.0.33 // source: provisionersdk/proto/provisioner.proto package proto From ebbaf31fa8a2b21095ae5352177dd8e1dbd2df11 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Mon, 19 Aug 2024 15:42:11 +0000 Subject: [PATCH 22/22] @mtojek you legend :) Signed-off-by: Danny Kopping --- site/e2e/helpers.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index f435af516879b..b8af0ac27a0a6 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -484,6 +484,7 @@ const createTemplateVersionTar = async ( resources: response.apply?.resources ?? [], parameters: response.apply?.parameters ?? [], externalAuthProviders: response.apply?.externalAuthProviders ?? [], + timings: response.apply?.timings ?? [], }, }; }); @@ -585,6 +586,7 @@ const createTemplateVersionTar = async ( resources: [], parameters: [], externalAuthProviders: [], + timings: [], ...response.apply, } as ApplyComplete; response.apply.resources = response.apply.resources?.map(fillResource); @@ -600,6 +602,7 @@ const createTemplateVersionTar = async ( resources: [], parameters: [], externalAuthProviders: [], + timings: [], ...response.plan, } as PlanComplete; response.plan.resources = response.plan.resources?.map(fillResource);