From 7167fa0cc5f4f9cd5ac809fd802dc33850ba994a Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 15:55:37 +0000 Subject: [PATCH 01/20] Refactor parameter parsing to return nil values if none computed --- cli/projectcreate.go | 12 +- coderd/coderd.go | 2 +- coderd/parameter/parameter.go | 199 +++++++++++++++ .../parameter_test.go} | 108 ++++---- coderd/parameters.go | 41 +++ coderd/projectparameter/projectparameter.go | 233 ------------------ coderd/projectversion.go | 85 ------- coderd/projectversion_test.go | 58 ----- coderd/provisionerdaemons.go | 49 +++- coderd/provisionerjobs.go | 38 +++ coderd/provisionerjobs_test.go | 39 +++ codersdk/projects.go | 14 -- codersdk/projects_test.go | 22 -- codersdk/provisioners.go | 14 ++ codersdk/provisioners_test.go | 7 + templates/null/main.tf | 5 + 16 files changed, 441 insertions(+), 485 deletions(-) create mode 100644 coderd/parameter/parameter.go rename coderd/{projectparameter/projectparameter_test.go => parameter/parameter_test.go} (66%) delete mode 100644 coderd/projectparameter/projectparameter.go create mode 100644 templates/null/main.tf diff --git a/cli/projectcreate.go b/cli/projectcreate.go index eb5219cb816cb..4cde2024053a4 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -3,7 +3,6 @@ package cli import ( "archive/tar" "bytes" - "context" "fmt" "io" "os" @@ -91,7 +90,7 @@ func projectCreate() *cobra.Command { } spin.Stop() - logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), organization.Name, job.ID, time.Time{}) + logs, err := client.FollowProvisionerJobLogsAfter(cmd.Context(), organization.Name, job.ID, time.Time{}) if err != nil { return err } @@ -103,6 +102,15 @@ func projectCreate() *cobra.Command { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[parse]"), log.Output) } + schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) + if err != nil { + return err + } + + for _, schema := range schemas { + fmt.Printf("Schema: %+v\n", schema) + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Create project %q!\n", name) return nil }, diff --git a/coderd/coderd.go b/coderd/coderd.go index fcc6530c6811c..f0df2fdb6d119 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -73,7 +73,6 @@ func New(options *Options) http.Handler { r.Route("/{projectversion}", func(r chi.Router) { r.Use(httpmw.ExtractProjectVersionParam(api.Database)) r.Get("/", api.projectVersionByOrganizationAndName) - r.Get("/parameters", api.projectVersionParametersByOrganizationAndName) }) }) }) @@ -122,6 +121,7 @@ func New(options *Options) http.Handler { r.Route("/{provisionerjob}", func(r chi.Router) { r.Use(httpmw.ExtractProvisionerJobParam(options.Database)) r.Get("/", api.provisionerJobByOrganization) + r.Get("/parameters", api.provisionerJobParametersByID) r.Get("/logs", api.provisionerJobLogsByID) }) }) diff --git a/coderd/parameter/parameter.go b/coderd/parameter/parameter.go new file mode 100644 index 0000000000000..d8c7fe73778fc --- /dev/null +++ b/coderd/parameter/parameter.go @@ -0,0 +1,199 @@ +package parameter + +import ( + "context" + "database/sql" + "errors" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/coder/database" +) + +// Scope targets identifiers to pull parameters from. +type Scope struct { + ImportJobID uuid.UUID + OrganizationID string + UserID string + ProjectID uuid.NullUUID + WorkspaceID uuid.NullUUID +} + +type Value struct { + DestinationScheme database.ParameterDestinationScheme + Name string + Value string + // Default is whether the default value from the schema + // was consumed. + Default bool + Scope database.ParameterScope + ScopeID string +} + +type Computed struct { + Schema database.ParameterSchema + // Value is nil when no value was computed for the schema. + Value *Value +} + +// Compute accepts a scope in which parameter values are sourced. +// These sources are iterated in a hierarchical fashion to determine +// the runtime parameter values for a project. +func Compute(ctx context.Context, db database.Store, scope Scope, additional ...database.ParameterValue) ([]Computed, error) { + compute := &compute{ + db: db, + parametersByName: map[string]Computed{}, + } + + // All parameters for the import job ID! + parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.ImportJobID) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + return nil, xerrors.Errorf("get project parameters: %w", err) + } + for _, parameterSchema := range parameterSchemas { + compute.parametersByName[parameterSchema.Name] = Computed{ + Schema: parameterSchema, + } + } + + // Organization parameters come first! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeOrganization, + ScopeID: scope.OrganizationID, + }) + if err != nil { + return nil, err + } + + // Default project parameter values come second! + for _, parameterSchema := range parameterSchemas { + if !parameterSchema.DefaultSourceValue.Valid { + continue + } + if !parameterSchema.DefaultDestinationValue.Valid { + continue + } + + switch parameterSchema.DefaultSourceScheme { + case database.ParameterSourceSchemeData: + compute.parametersByName[parameterSchema.Name] = Computed{ + Value: &Value{ + DestinationScheme: parameterSchema.DefaultDestinationScheme, + Name: parameterSchema.DefaultDestinationValue.String, + Value: parameterSchema.DefaultSourceValue.String, + Default: true, + Scope: database.ParameterScopeProject, + ScopeID: scope.ProjectID.UUID.String(), + }, + Schema: parameterSchema, + } + default: + return nil, xerrors.Errorf("unsupported source scheme for project version parameter %q: %q", parameterSchema.Name, string(parameterSchema.DefaultSourceScheme)) + } + } + + if scope.ProjectID.Valid { + // Project parameters come third! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeProject, + ScopeID: scope.ProjectID.UUID.String(), + }) + if err != nil { + return nil, err + } + } + + // User parameters come fourth! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeUser, + ScopeID: scope.UserID, + }) + if err != nil { + return nil, err + } + + if scope.WorkspaceID.Valid { + // Workspace parameters come last! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeWorkspace, + ScopeID: scope.WorkspaceID.UUID.String(), + }) + if err != nil { + return nil, err + } + } + + for _, parameterValue := range additional { + err = compute.injectSingle(parameterValue) + if err != nil { + return nil, xerrors.Errorf("inject %q: %w", parameterValue.Name, err) + } + } + + values := make([]Computed, 0, len(compute.parametersByName)) + for _, value := range compute.parametersByName { + values = append(values, value) + } + return values, nil +} + +type compute struct { + db database.Store + parametersByName map[string]Computed +} + +// Validates and computes the value for parameters; setting the value on "parameterByName". +func (c *compute) injectScope(ctx context.Context, scopeParams database.GetParameterValuesByScopeParams) error { + scopedParameters, err := c.db.GetParameterValuesByScope(ctx, scopeParams) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + return xerrors.Errorf("get %s parameters: %w", scopeParams.Scope, err) + } + + for _, scopedParameter := range scopedParameters { + err = c.injectSingle(scopedParameter) + if err != nil { + return xerrors.Errorf("inject single %q: %w", scopedParameter.Name, err) + } + } + return nil +} + +func (c *compute) injectSingle(scopedParameter database.ParameterValue) error { + computed, hasComputed := c.parametersByName[scopedParameter.Name] + if !hasComputed { + // Don't inject parameters that aren't defined by the project. + return nil + } + + if computed.Value != nil { + // If a parameter already exists, check if this variable can override it. + // Injection hierarchy is the responsibility of the caller. This check ensures + // project parameters cannot be overridden if already set. + if !computed.Schema.AllowOverrideSource && scopedParameter.Scope != database.ParameterScopeProject { + return nil + } + } + + switch scopedParameter.SourceScheme { + case database.ParameterSourceSchemeData: + computed.Value = &Value{ + DestinationScheme: scopedParameter.DestinationScheme, + Name: scopedParameter.SourceValue, + Value: scopedParameter.DestinationValue, + Scope: scopedParameter.Scope, + ScopeID: scopedParameter.ScopeID, + Default: false, + } + c.parametersByName[scopedParameter.Name] = computed + default: + return xerrors.Errorf("unsupported source scheme: %q", string(computed.Schema.DefaultSourceScheme)) + } + return nil +} diff --git a/coderd/projectparameter/projectparameter_test.go b/coderd/parameter/parameter_test.go similarity index 66% rename from coderd/projectparameter/projectparameter_test.go rename to coderd/parameter/parameter_test.go index 6fb04701c606c..aa064683c64f3 100644 --- a/coderd/projectparameter/projectparameter_test.go +++ b/coderd/parameter/parameter_test.go @@ -1,4 +1,4 @@ -package projectparameter_test +package parameter_test import ( "context" @@ -8,17 +8,16 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" - "github.com/coder/coder/coderd/projectparameter" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/cryptorand" "github.com/coder/coder/database" "github.com/coder/coder/database/databasefake" - "github.com/coder/coder/provisionersdk/proto" ) func TestCompute(t *testing.T) { t.Parallel() - generateScope := func() projectparameter.Scope { - return projectparameter.Scope{ + generateScope := func() parameter.Scope { + return parameter.Scope{ ImportJobID: uuid.New(), OrganizationID: uuid.NewString(), ProjectID: uuid.NullUUID{ @@ -29,19 +28,16 @@ func TestCompute(t *testing.T) { UUID: uuid.New(), Valid: true, }, - UserID: sql.NullString{ - String: uuid.NewString(), - Valid: true, - }, + UserID: uuid.NewString(), } } - type projectParameterOptions struct { + type parameterOptions struct { AllowOverrideSource bool AllowOverrideDestination bool DefaultDestinationScheme database.ParameterDestinationScheme ImportJobID uuid.UUID } - generateProjectParameter := func(t *testing.T, db database.Store, opts projectParameterOptions) database.ParameterSchema { + generateParameter := func(t *testing.T, db database.Store, opts parameterOptions) database.ParameterSchema { if opts.DefaultDestinationScheme == "" { opts.DefaultDestinationScheme = database.ParameterDestinationSchemeEnvironmentVariable } @@ -76,50 +72,48 @@ func TestCompute(t *testing.T) { t.Parallel() db := databasefake.New() scope := generateScope() - parameter, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{ + parameterSchema, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{ ID: uuid.New(), JobID: scope.ImportJobID, Name: "hey", }) require.NoError(t, err) - - _, err = projectparameter.Compute(context.Background(), db, scope) - var noValueErr projectparameter.NoValueError - require.ErrorAs(t, err, &noValueErr) - require.Equal(t, parameter.ID.String(), noValueErr.ParameterID.String()) - require.Equal(t, parameter.Name, noValueErr.ParameterName) + computed, err := parameter.Compute(context.Background(), db, scope) + require.NoError(t, err) + require.Equal(t, parameterSchema.ID.String(), computed[0].Schema.ID.String()) + require.Nil(t, computed[0].Value) }) t.Run("UseDefaultProjectValue", func(t *testing.T) { t.Parallel() db := databasefake.New() scope := generateScope() - parameter := generateProjectParameter(t, db, projectParameterOptions{ + parameterSchema := generateParameter(t, db, parameterOptions{ ImportJobID: scope.ImportJobID, DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, }) - values, err := projectparameter.Compute(context.Background(), db, scope) - require.NoError(t, err) - require.Len(t, values, 1) - value := values[0] - require.True(t, value.DefaultValue) - require.Equal(t, database.ParameterScopeProject, value.Scope) - require.Equal(t, scope.ProjectID.UUID.String(), value.ScopeID) - require.Equal(t, value.Proto.Name, parameter.DefaultDestinationValue.String) - require.Equal(t, value.Proto.DestinationScheme, proto.ParameterDestination_PROVISIONER_VARIABLE) - require.Equal(t, value.Proto.Value, parameter.DefaultSourceValue.String) + computed, err := parameter.Compute(context.Background(), db, scope) + require.NoError(t, err) + require.Len(t, computed, 1) + computedValue := computed[0] + require.True(t, computedValue.Value.Default) + require.Equal(t, database.ParameterScopeProject, computedValue.Value.Scope) + require.Equal(t, scope.ProjectID.UUID.String(), computedValue.Value.ScopeID) + require.Equal(t, computedValue.Value.Name, parameterSchema.DefaultDestinationValue.String) + require.Equal(t, computedValue.Value.DestinationScheme, database.ParameterDestinationSchemeProvisionerVariable) + require.Equal(t, computedValue.Value.Value, parameterSchema.DefaultSourceValue.String) }) t.Run("OverrideOrganizationWithProjectDefault", func(t *testing.T) { t.Parallel() db := databasefake.New() scope := generateScope() - parameter := generateProjectParameter(t, db, projectParameterOptions{ + parameterSchema := generateParameter(t, db, parameterOptions{ ImportJobID: scope.ImportJobID, }) _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ ID: uuid.New(), - Name: parameter.Name, + Name: parameterSchema.Name, Scope: database.ParameterScopeOrganization, ScopeID: scope.OrganizationID, SourceScheme: database.ParameterSourceSchemeData, @@ -129,23 +123,23 @@ func TestCompute(t *testing.T) { }) require.NoError(t, err) - values, err := projectparameter.Compute(context.Background(), db, scope) + computed, err := parameter.Compute(context.Background(), db, scope) require.NoError(t, err) - require.Len(t, values, 1) - require.Equal(t, true, values[0].DefaultValue) - require.Equal(t, parameter.DefaultSourceValue.String, values[0].Proto.Value) + require.Len(t, computed, 1) + require.Equal(t, true, computed[0].Value.Default) + require.Equal(t, parameterSchema.DefaultSourceValue.String, computed[0].Value.Value) }) t.Run("ProjectOverridesProjectDefault", func(t *testing.T) { t.Parallel() db := databasefake.New() scope := generateScope() - parameter := generateProjectParameter(t, db, projectParameterOptions{ + parameterSchema := generateParameter(t, db, parameterOptions{ ImportJobID: scope.ImportJobID, }) value, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ ID: uuid.New(), - Name: parameter.Name, + Name: parameterSchema.Name, Scope: database.ParameterScopeProject, ScopeID: scope.ProjectID.UUID.String(), SourceScheme: database.ParameterSourceSchemeData, @@ -155,23 +149,23 @@ func TestCompute(t *testing.T) { }) require.NoError(t, err) - values, err := projectparameter.Compute(context.Background(), db, scope) + computed, err := parameter.Compute(context.Background(), db, scope) require.NoError(t, err) - require.Len(t, values, 1) - require.Equal(t, false, values[0].DefaultValue) - require.Equal(t, value.DestinationValue, values[0].Proto.Value) + require.Len(t, computed, 1) + require.Equal(t, false, computed[0].Value.Default) + require.Equal(t, value.DestinationValue, computed[0].Value.Value) }) t.Run("WorkspaceCannotOverwriteProjectDefault", func(t *testing.T) { t.Parallel() db := databasefake.New() scope := generateScope() - parameter := generateProjectParameter(t, db, projectParameterOptions{ + parameterSchema := generateParameter(t, db, parameterOptions{ ImportJobID: scope.ImportJobID, }) _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ ID: uuid.New(), - Name: parameter.Name, + Name: parameterSchema.Name, Scope: database.ParameterScopeWorkspace, ScopeID: scope.WorkspaceID.UUID.String(), SourceScheme: database.ParameterSourceSchemeData, @@ -181,23 +175,23 @@ func TestCompute(t *testing.T) { }) require.NoError(t, err) - values, err := projectparameter.Compute(context.Background(), db, scope) + computed, err := parameter.Compute(context.Background(), db, scope) require.NoError(t, err) - require.Len(t, values, 1) - require.Equal(t, true, values[0].DefaultValue) + require.Len(t, computed, 1) + require.Equal(t, true, computed[0].Value.Default) }) t.Run("WorkspaceOverwriteProjectDefault", func(t *testing.T) { t.Parallel() db := databasefake.New() scope := generateScope() - parameter := generateProjectParameter(t, db, projectParameterOptions{ + parameterSchema := generateParameter(t, db, parameterOptions{ AllowOverrideSource: true, ImportJobID: scope.ImportJobID, }) _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ ID: uuid.New(), - Name: parameter.Name, + Name: parameterSchema.Name, Scope: database.ParameterScopeWorkspace, ScopeID: scope.WorkspaceID.UUID.String(), SourceScheme: database.ParameterSourceSchemeData, @@ -207,23 +201,23 @@ func TestCompute(t *testing.T) { }) require.NoError(t, err) - values, err := projectparameter.Compute(context.Background(), db, scope) + computed, err := parameter.Compute(context.Background(), db, scope) require.NoError(t, err) - require.Len(t, values, 1) - require.Equal(t, false, values[0].DefaultValue) + require.Len(t, computed, 1) + require.Equal(t, false, computed[0].Value.Default) }) t.Run("AdditionalOverwriteWorkspace", func(t *testing.T) { t.Parallel() db := databasefake.New() scope := generateScope() - parameter := generateProjectParameter(t, db, projectParameterOptions{ + parameterSchema := generateParameter(t, db, parameterOptions{ AllowOverrideSource: true, ImportJobID: scope.ImportJobID, }) _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ ID: uuid.New(), - Name: parameter.Name, + Name: parameterSchema.Name, Scope: database.ParameterScopeWorkspace, ScopeID: scope.WorkspaceID.UUID.String(), SourceScheme: database.ParameterSourceSchemeData, @@ -233,8 +227,8 @@ func TestCompute(t *testing.T) { }) require.NoError(t, err) - values, err := projectparameter.Compute(context.Background(), db, scope, database.ParameterValue{ - Name: parameter.Name, + computed, err := parameter.Compute(context.Background(), db, scope, database.ParameterValue{ + Name: parameterSchema.Name, Scope: database.ParameterScopeUser, SourceScheme: database.ParameterSourceSchemeData, SourceValue: "nop", @@ -242,7 +236,7 @@ func TestCompute(t *testing.T) { DestinationValue: "testing", }) require.NoError(t, err) - require.Len(t, values, 1) - require.Equal(t, "testing", values[0].Proto.Value) + require.Len(t, computed, 1) + require.Equal(t, "testing", computed[0].Value.Value) }) } diff --git a/coderd/parameters.go b/coderd/parameters.go index b3aa1d4967bd9..b7672a22031f9 100644 --- a/coderd/parameters.go +++ b/coderd/parameters.go @@ -23,6 +23,26 @@ type CreateParameterValueRequest struct { DestinationValue string `json:"destination_value" validate:"required"` } +// ParameterSchema represents a parameter parsed from project version source. +type ParameterSchema struct { + ID uuid.UUID `json:"id"` + CreatedAt time.Time `json:"created_at"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + DefaultSourceScheme database.ParameterSourceScheme `json:"default_source_scheme,omitempty"` + DefaultSourceValue string `json:"default_source_value,omitempty"` + AllowOverrideSource bool `json:"allow_override_source"` + DefaultDestinationScheme database.ParameterDestinationScheme `json:"default_destination_scheme,omitempty"` + DefaultDestinationValue string `json:"default_destination_value,omitempty"` + AllowOverrideDestination bool `json:"allow_override_destination"` + DefaultRefresh string `json:"default_refresh"` + RedisplayValue bool `json:"redisplay_value"` + ValidationError string `json:"validation_error,omitempty"` + ValidationCondition string `json:"validation_condition,omitempty"` + ValidationTypeSystem database.ParameterTypeSystem `json:"validation_type_system,omitempty"` + ValidationValueType string `json:"validation_value_type,omitempty"` +} + // ParameterValue represents a set value for the scope. type ParameterValue struct { ID uuid.UUID `json:"id"` @@ -92,6 +112,27 @@ func parametersForScope(rw http.ResponseWriter, r *http.Request, db database.Sto render.JSON(rw, r, apiParameterValues) } +func convertParameterSchema(parameter database.ParameterSchema) ParameterSchema { + return ParameterSchema{ + ID: parameter.ID, + CreatedAt: parameter.CreatedAt, + Name: parameter.Name, + Description: parameter.Description, + DefaultSourceScheme: parameter.DefaultSourceScheme, + DefaultSourceValue: parameter.DefaultSourceValue.String, + AllowOverrideSource: parameter.AllowOverrideSource, + DefaultDestinationScheme: parameter.DefaultDestinationScheme, + DefaultDestinationValue: parameter.DefaultDestinationValue.String, + AllowOverrideDestination: parameter.AllowOverrideDestination, + DefaultRefresh: parameter.DefaultRefresh, + RedisplayValue: parameter.RedisplayValue, + ValidationError: parameter.ValidationError, + ValidationCondition: parameter.ValidationCondition, + ValidationTypeSystem: parameter.ValidationTypeSystem, + ValidationValueType: parameter.ValidationValueType, + } +} + func convertParameterValue(parameterValue database.ParameterValue) ParameterValue { return ParameterValue{ ID: parameterValue.ID, diff --git a/coderd/projectparameter/projectparameter.go b/coderd/projectparameter/projectparameter.go deleted file mode 100644 index 7288c8086c9ca..0000000000000 --- a/coderd/projectparameter/projectparameter.go +++ /dev/null @@ -1,233 +0,0 @@ -package projectparameter - -import ( - "context" - "database/sql" - "errors" - "fmt" - - "github.com/google/uuid" - "golang.org/x/xerrors" - - "github.com/coder/coder/database" - "github.com/coder/coder/provisionersdk/proto" -) - -// Scope targets identifiers to pull parameters from. -type Scope struct { - ImportJobID uuid.UUID - OrganizationID string - ProjectID uuid.NullUUID - UserID sql.NullString - WorkspaceID uuid.NullUUID -} - -// Value represents a computed parameter. -type Value struct { - Proto *proto.ParameterValue - // DefaultValue is whether a default value for the scope - // was consumed. This can only be true for projects. - DefaultValue bool - Scope database.ParameterScope - ScopeID string -} - -// Compute accepts a scope in which parameter values are sourced. -// These sources are iterated in a hierarchical fashion to determine -// the runtime parameter values for a project. -func Compute(ctx context.Context, db database.Store, scope Scope, additional ...database.ParameterValue) ([]Value, error) { - compute := &compute{ - db: db, - computedParameterByName: map[string]Value{}, - parameterSchemasByName: map[string]database.ParameterSchema{}, - } - - // All parameters for the import job ID! - parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.ImportJobID) - if errors.Is(err, sql.ErrNoRows) { - err = nil - } - if err != nil { - return nil, xerrors.Errorf("get project parameters: %w", err) - } - for _, projectVersionParameter := range parameterSchemas { - compute.parameterSchemasByName[projectVersionParameter.Name] = projectVersionParameter - } - - // Organization parameters come first! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeOrganization, - ScopeID: scope.OrganizationID, - }) - if err != nil { - return nil, err - } - - // Default project parameter values come second! - for _, projectVersionParameter := range parameterSchemas { - if !projectVersionParameter.DefaultSourceValue.Valid { - continue - } - if !projectVersionParameter.DefaultDestinationValue.Valid { - continue - } - - destinationScheme, err := convertDestinationScheme(projectVersionParameter.DefaultDestinationScheme) - if err != nil { - return nil, xerrors.Errorf("convert default destination scheme for project version parameter %q: %w", projectVersionParameter.Name, err) - } - - switch projectVersionParameter.DefaultSourceScheme { - case database.ParameterSourceSchemeData: - compute.computedParameterByName[projectVersionParameter.Name] = Value{ - Proto: &proto.ParameterValue{ - DestinationScheme: destinationScheme, - Name: projectVersionParameter.DefaultDestinationValue.String, - Value: projectVersionParameter.DefaultSourceValue.String, - }, - DefaultValue: true, - Scope: database.ParameterScopeProject, - ScopeID: scope.ProjectID.UUID.String(), - } - default: - return nil, xerrors.Errorf("unsupported source scheme for project version parameter %q: %q", projectVersionParameter.Name, string(projectVersionParameter.DefaultSourceScheme)) - } - } - - if scope.ProjectID.Valid { - // Project parameters come third! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeProject, - ScopeID: scope.ProjectID.UUID.String(), - }) - if err != nil { - return nil, err - } - } - - if scope.UserID.Valid { - // User parameters come fourth! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeUser, - ScopeID: scope.UserID.String, - }) - if err != nil { - return nil, err - } - } - - if scope.WorkspaceID.Valid { - // Workspace parameters come last! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), - }) - if err != nil { - return nil, err - } - } - - for _, parameterValue := range additional { - err = compute.injectSingle(parameterValue) - if err != nil { - return nil, xerrors.Errorf("inject %q: %w", parameterValue.Name, err) - } - } - - for _, projectVersionParameter := range compute.parameterSchemasByName { - if _, ok := compute.computedParameterByName[projectVersionParameter.Name]; ok { - continue - } - return nil, NoValueError{ - ParameterID: projectVersionParameter.ID, - ParameterName: projectVersionParameter.Name, - } - } - - values := make([]Value, 0, len(compute.computedParameterByName)) - for _, value := range compute.computedParameterByName { - values = append(values, value) - } - return values, nil -} - -type compute struct { - db database.Store - computedParameterByName map[string]Value - parameterSchemasByName map[string]database.ParameterSchema -} - -// Validates and computes the value for parameters; setting the value on "parameterByName". -func (c *compute) injectScope(ctx context.Context, scopeParams database.GetParameterValuesByScopeParams) error { - scopedParameters, err := c.db.GetParameterValuesByScope(ctx, scopeParams) - if errors.Is(err, sql.ErrNoRows) { - err = nil - } - if err != nil { - return xerrors.Errorf("get %s parameters: %w", scopeParams.Scope, err) - } - - for _, scopedParameter := range scopedParameters { - err = c.injectSingle(scopedParameter) - if err != nil { - return xerrors.Errorf("inject single %q: %w", scopedParameter.Name, err) - } - } - return nil -} - -func (c *compute) injectSingle(scopedParameter database.ParameterValue) error { - parameterSchema, hasParameterSchema := c.parameterSchemasByName[scopedParameter.Name] - if hasParameterSchema { - // Don't inject parameters that aren't defined by the project. - _, hasExistingParameter := c.computedParameterByName[scopedParameter.Name] - if hasExistingParameter { - // If a parameter already exists, check if this variable can override it. - // Injection hierarchy is the responsibility of the caller. This check ensures - // project parameters cannot be overridden if already set. - if !parameterSchema.AllowOverrideSource && scopedParameter.Scope != database.ParameterScopeProject { - return nil - } - } - } - - destinationScheme, err := convertDestinationScheme(scopedParameter.DestinationScheme) - if err != nil { - return xerrors.Errorf("convert destination scheme: %w", err) - } - - switch scopedParameter.SourceScheme { - case database.ParameterSourceSchemeData: - c.computedParameterByName[scopedParameter.Name] = Value{ - Proto: &proto.ParameterValue{ - DestinationScheme: destinationScheme, - Name: scopedParameter.SourceValue, - Value: scopedParameter.DestinationValue, - }, - } - default: - return xerrors.Errorf("unsupported source scheme: %q", string(parameterSchema.DefaultSourceScheme)) - } - return nil -} - -// Converts the database destination scheme to the protobuf version. -func convertDestinationScheme(scheme database.ParameterDestinationScheme) (proto.ParameterDestination_Scheme, error) { - switch scheme { - case database.ParameterDestinationSchemeEnvironmentVariable: - return proto.ParameterDestination_ENVIRONMENT_VARIABLE, nil - case database.ParameterDestinationSchemeProvisionerVariable: - return proto.ParameterDestination_PROVISIONER_VARIABLE, nil - default: - return 0, xerrors.Errorf("unsupported destination scheme: %q", scheme) - } -} - -type NoValueError struct { - ParameterID uuid.UUID - ParameterName string -} - -func (e NoValueError) Error() string { - return fmt.Sprintf("no value for parameter %q found", e.ParameterName) -} diff --git a/coderd/projectversion.go b/coderd/projectversion.go index 2b603e2e10665..c1b3ecdd29ace 100644 --- a/coderd/projectversion.go +++ b/coderd/projectversion.go @@ -26,26 +26,6 @@ type ProjectVersion struct { ImportJobID uuid.UUID `json:"import_job_id"` } -// ProjectVersionParameter represents a parameter parsed from project version source on creation. -type ProjectVersionParameter struct { - ID uuid.UUID `json:"id"` - CreatedAt time.Time `json:"created_at"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - DefaultSourceScheme database.ParameterSourceScheme `json:"default_source_scheme,omitempty"` - DefaultSourceValue string `json:"default_source_value,omitempty"` - AllowOverrideSource bool `json:"allow_override_source"` - DefaultDestinationScheme database.ParameterDestinationScheme `json:"default_destination_scheme,omitempty"` - DefaultDestinationValue string `json:"default_destination_value,omitempty"` - AllowOverrideDestination bool `json:"allow_override_destination"` - DefaultRefresh string `json:"default_refresh"` - RedisplayValue bool `json:"redisplay_value"` - ValidationError string `json:"validation_error,omitempty"` - ValidationCondition string `json:"validation_condition,omitempty"` - ValidationTypeSystem database.ParameterTypeSystem `json:"validation_type_system,omitempty"` - ValidationValueType string `json:"validation_value_type,omitempty"` -} - // CreateProjectVersionRequest enables callers to create a new Project Version. type CreateProjectVersionRequest struct { ImportJobID uuid.UUID `json:"import_job_id" validate:"required"` @@ -121,50 +101,6 @@ func (api *api) postProjectVersionByOrganization(rw http.ResponseWriter, r *http render.JSON(rw, r, convertProjectVersion(projectVersion)) } -func (api *api) projectVersionParametersByOrganizationAndName(rw http.ResponseWriter, r *http.Request) { - projectVersion := httpmw.ProjectVersionParam(r) - job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.ImportJobID) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("get provisioner job: %s", err), - }) - return - } - apiJob := convertProvisionerJob(job) - if !apiJob.Status.Completed() { - httpapi.Write(rw, http.StatusPreconditionRequired, httpapi.Response{ - Message: fmt.Sprintf("import job hasn't completed: %s", apiJob.Status), - }) - return - } - if apiJob.Status != ProvisionerJobStatusSucceeded { - httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ - Message: "import job wasn't successful. no parameters were parsed", - }) - return - } - - parameters, err := api.Database.GetParameterSchemasByJobID(r.Context(), projectVersion.ImportJobID) - if errors.Is(err, sql.ErrNoRows) { - err = nil - parameters = []database.ParameterSchema{} - } - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("get project parameters: %s", err), - }) - return - } - - apiParameters := make([]ProjectVersionParameter, 0, len(parameters)) - for _, parameter := range parameters { - apiParameters = append(apiParameters, convertProjectParameter(parameter)) - } - - render.Status(r, http.StatusOK) - render.JSON(rw, r, apiParameters) -} - func convertProjectVersion(version database.ProjectVersion) ProjectVersion { return ProjectVersion{ ID: version.ID, @@ -175,24 +111,3 @@ func convertProjectVersion(version database.ProjectVersion) ProjectVersion { ImportJobID: version.ImportJobID, } } - -func convertProjectParameter(parameter database.ParameterSchema) ProjectVersionParameter { - return ProjectVersionParameter{ - ID: parameter.ID, - CreatedAt: parameter.CreatedAt, - Name: parameter.Name, - Description: parameter.Description, - DefaultSourceScheme: parameter.DefaultSourceScheme, - DefaultSourceValue: parameter.DefaultSourceValue.String, - AllowOverrideSource: parameter.AllowOverrideSource, - DefaultDestinationScheme: parameter.DefaultDestinationScheme, - DefaultDestinationValue: parameter.DefaultDestinationValue.String, - AllowOverrideDestination: parameter.AllowOverrideDestination, - DefaultRefresh: parameter.DefaultRefresh, - RedisplayValue: parameter.RedisplayValue, - ValidationError: parameter.ValidationError, - ValidationCondition: parameter.ValidationCondition, - ValidationTypeSystem: parameter.ValidationTypeSystem, - ValidationValueType: parameter.ValidationValueType, - } -} diff --git a/coderd/projectversion_test.go b/coderd/projectversion_test.go index 467574cfa74e9..a842f3ec277cf 100644 --- a/coderd/projectversion_test.go +++ b/coderd/projectversion_test.go @@ -2,16 +2,12 @@ package coderd_test import ( "context" - "net/http" "testing" "github.com/stretchr/testify/require" "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/coderdtest" - "github.com/coder/coder/codersdk" - "github.com/coder/coder/provisioner/echo" - "github.com/coder/coder/provisionersdk/proto" ) func TestProjectVersionsByOrganization(t *testing.T) { @@ -56,57 +52,3 @@ func TestPostProjectVersionByOrganization(t *testing.T) { require.NoError(t, err) }) } - -func TestProjectVersionParametersByOrganizationAndName(t *testing.T) { - t.Parallel() - t.Run("NotImported", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) - project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - _, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String()) - var apiErr *codersdk.Error - require.ErrorAs(t, err, &apiErr) - require.Equal(t, http.StatusPreconditionRequired, apiErr.StatusCode()) - }) - - t.Run("FailedImport", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ - Provision: []*proto.Provision_Response{{}}, - }) - project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - _, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String()) - var apiErr *codersdk.Error - require.ErrorAs(t, err, &apiErr) - require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) - }) - t.Run("List", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ - Parse: []*proto.Parse_Response{{ - Type: &proto.Parse_Response_Complete{ - Complete: &proto.Parse_Complete{ - ParameterSchemas: []*proto.ParameterSchema{{ - Name: "example", - }}, - }, - }, - }}, - Provision: echo.ProvisionComplete, - }) - project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - params, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String()) - require.NoError(t, err) - require.Len(t, params, 1) - }) -} diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 0d51966f62a7c..7d2dbcb005903 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -22,7 +22,7 @@ import ( "cdr.dev/slog" - "github.com/coder/coder/coderd/projectparameter" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/database" "github.com/coder/coder/httpapi" "github.com/coder/coder/provisionerd/proto" @@ -211,17 +211,14 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty } // Compute parameters for the workspace to consume. - parameters, err := projectparameter.Compute(ctx, server.Database, projectparameter.Scope{ + parameters, err := parameter.Compute(ctx, server.Database, parameter.Scope{ ImportJobID: projectVersion.ImportJobID, OrganizationID: organization.ID, ProjectID: uuid.NullUUID{ UUID: project.ID, Valid: true, }, - UserID: sql.NullString{ - String: user.ID, - Valid: true, - }, + UserID: user.ID, WorkspaceID: uuid.NullUUID{ UUID: workspace.ID, Valid: true, @@ -233,7 +230,11 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty // Convert parameters to the protobuf type. protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) for _, parameter := range parameters { - protoParameters = append(protoParameters, parameter.Proto) + converted, err := convertComputedParameter(parameter) + if err != nil { + return nil, failJob(fmt.Sprintf("convert parameter: %s", err)) + } + protoParameters = append(protoParameters, converted) } protoJob.Type = &proto.AcquiredJob_WorkspaceProvision_{ @@ -252,17 +253,14 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty } // Compute parameters for the workspace to consume. - parameters, err := projectparameter.Compute(ctx, server.Database, projectparameter.Scope{ + parameters, err := parameter.Compute(ctx, server.Database, parameter.Scope{ ImportJobID: job.ID, OrganizationID: input.OrganizationID, ProjectID: uuid.NullUUID{ UUID: input.ProjectID, Valid: input.ProjectID.String() != uuid.Nil.String(), }, - UserID: sql.NullString{ - String: user.ID, - Valid: true, - }, + UserID: user.ID, }, input.AdditionalParameters...) if err != nil { return nil, failJob(fmt.Sprintf("compute parameters: %s", err)) @@ -270,7 +268,11 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty // Convert parameters to the protobuf type. protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) for _, parameter := range parameters { - protoParameters = append(protoParameters, parameter.Proto) + converted, err := convertComputedParameter(parameter) + if err != nil { + return nil, failJob(fmt.Sprintf("convert parameter: %s", err)) + } + protoParameters = append(protoParameters, converted) } protoJob.Type = &proto.AcquiredJob_ProjectImport_{ @@ -614,3 +616,24 @@ func convertLogSource(logSource proto.LogSource) (database.LogSource, error) { return database.LogSource(""), xerrors.Errorf("unknown log source: %d", logSource) } } + +func convertComputedParameter(param parameter.Computed) (*sdkproto.ParameterValue, error) { + if param.Value == nil { + return nil, xerrors.Errorf("no value for computed parameter: %s", param.Schema.Name) + } + scheme := sdkproto.ParameterDestination_ENVIRONMENT_VARIABLE + switch param.Value.DestinationScheme { + case database.ParameterDestinationSchemeEnvironmentVariable: + scheme = sdkproto.ParameterDestination_ENVIRONMENT_VARIABLE + case database.ParameterDestinationSchemeProvisionerVariable: + scheme = sdkproto.ParameterDestination_PROVISIONER_VARIABLE + default: + return nil, xerrors.Errorf("unrecognized destination scheme: %q", param.Value.DestinationScheme) + } + + return &sdkproto.ParameterValue{ + DestinationScheme: scheme, + Name: param.Value.Name, + Value: param.Value.Value, + }, nil +} diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 2eb4d69c34b7d..32e811ac89364 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -11,6 +11,7 @@ import ( "github.com/go-chi/render" "github.com/google/uuid" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/database" "github.com/coder/coder/httpapi" "github.com/coder/coder/httpmw" @@ -44,6 +45,8 @@ type ProvisionerJob struct { WorkerID *uuid.UUID `json:"worker_id,omitempty"` } +type ComputedParameter struct{} + type CreateProjectImportJobRequest struct { StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"` StorageSource string `json:"storage_source" validate:"required"` @@ -118,6 +121,41 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r render.JSON(rw, r, convertProvisionerJob(job)) } +// Return parsed parameter schemas for a job. +func (api *api) provisionerJobParametersByID(rw http.ResponseWriter, r *http.Request) { + apiKey := httpmw.APIKey(r) + job := httpmw.ProvisionerJobParam(r) + if convertProvisionerJob(job).Status != ProvisionerJobStatusSucceeded { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: fmt.Sprintf("Job is in state %q! Must be %q.", convertProvisionerJob(job).Status, ProvisionerJobStatusSucceeded), + }) + return + } + + parameter.Compute(r.Context(), api.Database, parameter.Scope{ + ImportJobID: job.ID, + OrganizationID: job.OrganizationID, + UserID: apiKey.UserID, + }) + + schemas, err := api.Database.GetParameterSchemasByJobID(r.Context(), job.ID) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get parameter schemas: %s", err), + }) + return + } + apiSchemas := make([]ParameterSchema, 0, len(schemas)) + for _, parameter := range schemas { + apiSchemas = append(apiSchemas, convertParameterSchema(parameter)) + } + render.Status(r, http.StatusOK) + render.JSON(rw, r, apiSchemas) +} + func convertProvisionerJob(provisionerJob database.ProvisionerJob) ProvisionerJob { job := ProvisionerJob{ ID: provisionerJob.ID, diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index d16e4e976ba04..e16d1ad993e79 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -2,12 +2,14 @@ package coderd_test import ( "context" + "net/http" "testing" "time" "github.com/stretchr/testify/require" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" ) @@ -50,3 +52,40 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { } }) } + +func TestProvisionerJobParametersByID(t *testing.T) { + t.Parallel() + t.Run("NotImported", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + _, err := client.ProvisionerJobParameterSchemas(context.Background(), user.Organization, job.ID) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) + }) + + t.Run("List", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "example", + }}, + }, + }, + }}, + Provision: echo.ProvisionComplete, + }) + coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + params, err := client.ProvisionerJobParameterSchemas(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + require.Len(t, params, 1) + }) +} diff --git a/codersdk/projects.go b/codersdk/projects.go index b58825778c922..3e627ae571ed4 100644 --- a/codersdk/projects.go +++ b/codersdk/projects.go @@ -99,20 +99,6 @@ func (c *Client) CreateProjectVersion(ctx context.Context, organization, project return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion) } -// ProjectVersionParameters returns project parameters for a version by name. -func (c *Client) ProjectVersionParameters(ctx context.Context, organization, project, version string) ([]coderd.ProjectVersionParameter, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/%s/versions/%s/parameters", organization, project, version), nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, readBodyAsError(res) - } - var params []coderd.ProjectVersionParameter - return params, json.NewDecoder(res.Body).Decode(¶ms) -} - // ProjectParameters returns parameters scoped to a project. func (c *Client) ProjectParameters(ctx context.Context, organization, project string) ([]coderd.ParameterValue, error) { res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/%s/parameters", organization, project), nil) diff --git a/codersdk/projects_test.go b/codersdk/projects_test.go index f106d3a42652c..9fd765c7f138d 100644 --- a/codersdk/projects_test.go +++ b/codersdk/projects_test.go @@ -133,28 +133,6 @@ func TestCreateProjectVersion(t *testing.T) { }) } -func TestProjectVersionParameters(t *testing.T) { - t.Parallel() - t.Run("Error", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - _, err := client.ProjectVersionParameters(context.Background(), "some", "project", "version") - require.Error(t, err) - }) - - t.Run("List", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) - project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - _, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String()) - require.NoError(t, err) - }) -} - func TestProjectParameters(t *testing.T) { t.Parallel() t.Run("Error", func(t *testing.T) { diff --git a/codersdk/provisioners.go b/codersdk/provisioners.go index 1aea254aeba7a..d9aea0471d90f 100644 --- a/codersdk/provisioners.go +++ b/codersdk/provisioners.go @@ -151,3 +151,17 @@ func (c *Client) FollowProvisionerJobLogsAfter(ctx context.Context, organization }() return logs, nil } + +// ProvisionerJobParameterSchemas returns project parameters for a version by name. +func (c *Client) ProvisionerJobParameterSchemas(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ParameterSchema, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/parameters", organization, job), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, readBodyAsError(res) + } + var params []coderd.ParameterSchema + return params, json.NewDecoder(res.Body).Decode(¶ms) +} diff --git a/codersdk/provisioners_test.go b/codersdk/provisioners_test.go index 6808dd19a3b78..60c3ad49f8b9d 100644 --- a/codersdk/provisioners_test.go +++ b/codersdk/provisioners_test.go @@ -106,3 +106,10 @@ func TestFollowProvisionerJobLogsAfter(t *testing.T) { require.False(t, ok) }) } + +func TestProvisionerJobParameterSchemas(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + + }) +} diff --git a/templates/null/main.tf b/templates/null/main.tf new file mode 100644 index 0000000000000..9bb3f2042e2a4 --- /dev/null +++ b/templates/null/main.tf @@ -0,0 +1,5 @@ +variable "bananas" { + description = "hello!" +} + +resource "null_resource" "example" {} From 65380db9dca036e1b82ac7a34b2b042a48608374 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 19:37:07 +0000 Subject: [PATCH 02/20] Refactor parameter to allow for hiding redisplay --- coderd/parameter/compute.go | 210 +++++++++++++++++++++++++++++++ coderd/parameter/compute_test.go | 204 ++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 coderd/parameter/compute.go create mode 100644 coderd/parameter/compute_test.go diff --git a/coderd/parameter/compute.go b/coderd/parameter/compute.go new file mode 100644 index 0000000000000..493d72edf9ee9 --- /dev/null +++ b/coderd/parameter/compute.go @@ -0,0 +1,210 @@ +package parameter + +import ( + "context" + "database/sql" + "errors" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/coder/database" +) + +// ComputeScope targets identifiers to pull parameters from. +type ComputeScope struct { + ProjectImportJobID uuid.UUID + OrganizationID string + UserID string + ProjectID uuid.NullUUID + WorkspaceID uuid.NullUUID +} + +type ComputeOptions struct { + // HideRedisplayValues removes the value from parameters that + // come from schemas with RedisplayValue set to false. + HideRedisplayValues bool +} + +// ComputedValue represents a computed parameter value. +type ComputedValue struct { + database.ParameterValue + SchemaID uuid.UUID `json:"schema_id"` + DefaultSourceValue bool `json:"default_source_value"` +} + +// Compute accepts a scope in which parameter values are sourced. +// These sources are iterated in a hierarchical fashion to determine +// the runtime parameter values for schemas provided. +func Compute(ctx context.Context, db database.Store, scope ComputeScope, options *ComputeOptions) ([]ComputedValue, error) { + if options == nil { + options = &ComputeOptions{} + } + compute := &compute{ + options: options, + db: db, + computedParameterByName: map[string]ComputedValue{}, + parameterSchemasByName: map[string]database.ParameterSchema{}, + } + + // All parameters for the import job ID! + parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.ProjectImportJobID) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + return nil, xerrors.Errorf("get project parameters: %w", err) + } + for _, parameterSchema := range parameterSchemas { + compute.parameterSchemasByName[parameterSchema.Name] = parameterSchema + } + + // Organization parameters come first! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeOrganization, + ScopeID: scope.OrganizationID, + }) + if err != nil { + return nil, err + } + + // Job parameters come second! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeImportJob, + ScopeID: scope.ProjectImportJobID.String(), + }) + if err != nil { + return nil, err + } + + // Default project parameter values come second! + for _, parameterSchema := range parameterSchemas { + if parameterSchema.DefaultSourceScheme == database.ParameterSourceSchemeNone { + continue + } + if _, ok := compute.computedParameterByName[parameterSchema.Name]; ok { + // We already have a value! No need to use th default. + continue + } + + switch parameterSchema.DefaultSourceScheme { + case database.ParameterSourceSchemeData: + // Inject a default value scoped to the import job ID. + // This doesn't need to be inserted into the database, + // because it's a dynamic value associated with the schema. + err = compute.injectSingle(database.ParameterValue{ + ID: uuid.New(), + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + SourceScheme: database.ParameterSourceSchemeData, + Name: parameterSchema.Name, + DestinationScheme: parameterSchema.DefaultDestinationScheme, + SourceValue: parameterSchema.DefaultSourceValue, + Scope: database.ParameterScopeImportJob, + ScopeID: scope.ProjectImportJobID.String(), + }, true) + if err != nil { + return nil, xerrors.Errorf("insert default value: %w", err) + } + default: + return nil, xerrors.Errorf("unsupported source scheme for project version parameter %q: %q", parameterSchema.Name, string(parameterSchema.DefaultSourceScheme)) + } + } + + if scope.ProjectID.Valid { + // Project parameters come third! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeProject, + ScopeID: scope.ProjectID.UUID.String(), + }) + if err != nil { + return nil, err + } + } + + // User parameters come fourth! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeUser, + ScopeID: scope.UserID, + }) + if err != nil { + return nil, err + } + + if scope.WorkspaceID.Valid { + // Workspace parameters come last! + err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ + Scope: database.ParameterScopeWorkspace, + ScopeID: scope.WorkspaceID.UUID.String(), + }) + if err != nil { + return nil, err + } + } + + values := make([]ComputedValue, 0, len(compute.computedParameterByName)) + for _, value := range compute.computedParameterByName { + values = append(values, value) + } + return values, nil +} + +type compute struct { + options *ComputeOptions + db database.Store + computedParameterByName map[string]ComputedValue + parameterSchemasByName map[string]database.ParameterSchema +} + +// Validates and computes the value for parameters; setting the value on "parameterByName". +func (c *compute) injectScope(ctx context.Context, scopeParams database.GetParameterValuesByScopeParams) error { + scopedParameters, err := c.db.GetParameterValuesByScope(ctx, scopeParams) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + return xerrors.Errorf("get %s parameters: %w", scopeParams.Scope, err) + } + + for _, scopedParameter := range scopedParameters { + err = c.injectSingle(scopedParameter, false) + if err != nil { + return xerrors.Errorf("inject single %q: %w", scopedParameter.Name, err) + } + } + return nil +} + +func (c *compute) injectSingle(scopedParameter database.ParameterValue, defaultValue bool) error { + parameterSchema, hasParameterSchema := c.parameterSchemasByName[scopedParameter.Name] + if !hasParameterSchema { + // Don't inject parameters that aren't defined by the project. + return nil + } + + _, hasParameterValue := c.computedParameterByName[scopedParameter.Name] + if hasParameterValue { + if !parameterSchema.AllowOverrideSource && + // Users and workspaces cannot override anything on a project! + (scopedParameter.Scope == database.ParameterScopeUser || + scopedParameter.Scope == database.ParameterScopeWorkspace) { + return nil + } + } + + switch scopedParameter.SourceScheme { + case database.ParameterSourceSchemeData: + value := ComputedValue{ + ParameterValue: scopedParameter, + SchemaID: parameterSchema.ID, + DefaultSourceValue: defaultValue, + } + if c.options.HideRedisplayValues && !parameterSchema.RedisplayValue { + value.SourceValue = "" + } + c.computedParameterByName[scopedParameter.Name] = value + default: + return xerrors.Errorf("unsupported source scheme: %q", string(parameterSchema.DefaultSourceScheme)) + } + return nil +} diff --git a/coderd/parameter/compute_test.go b/coderd/parameter/compute_test.go new file mode 100644 index 0000000000000..c616c48f815e4 --- /dev/null +++ b/coderd/parameter/compute_test.go @@ -0,0 +1,204 @@ +package parameter_test + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/coderd/parameter" + "github.com/coder/coder/cryptorand" + "github.com/coder/coder/database" + "github.com/coder/coder/database/databasefake" +) + +func TestCompute(t *testing.T) { + t.Parallel() + generateScope := func() parameter.ComputeScope { + return parameter.ComputeScope{ + ProjectImportJobID: uuid.New(), + OrganizationID: uuid.NewString(), + ProjectID: uuid.NullUUID{ + UUID: uuid.New(), + Valid: true, + }, + WorkspaceID: uuid.NullUUID{ + UUID: uuid.New(), + Valid: true, + }, + UserID: uuid.NewString(), + } + } + type parameterOptions struct { + AllowOverrideSource bool + AllowOverrideDestination bool + DefaultDestinationScheme database.ParameterDestinationScheme + ProjectImportJobID uuid.UUID + } + generateParameter := func(t *testing.T, db database.Store, opts parameterOptions) database.ParameterSchema { + if opts.DefaultDestinationScheme == "" { + opts.DefaultDestinationScheme = database.ParameterDestinationSchemeEnvironmentVariable + } + name, err := cryptorand.String(8) + require.NoError(t, err) + sourceValue, err := cryptorand.String(8) + require.NoError(t, err) + param, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{ + ID: uuid.New(), + Name: name, + JobID: opts.ProjectImportJobID, + DefaultSourceScheme: database.ParameterSourceSchemeData, + DefaultSourceValue: sourceValue, + AllowOverrideSource: opts.AllowOverrideSource, + AllowOverrideDestination: opts.AllowOverrideDestination, + DefaultDestinationScheme: opts.DefaultDestinationScheme, + }) + require.NoError(t, err) + return param + } + + t.Run("NoValue", func(t *testing.T) { + t.Parallel() + db := databasefake.New() + scope := generateScope() + _, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{ + ID: uuid.New(), + JobID: scope.ProjectImportJobID, + Name: "hey", + DefaultSourceScheme: database.ParameterSourceSchemeNone, + }) + require.NoError(t, err) + computed, err := parameter.Compute(context.Background(), db, scope, nil) + require.NoError(t, err) + require.Len(t, computed, 0) + }) + + t.Run("UseDefaultProjectValue", func(t *testing.T) { + t.Parallel() + db := databasefake.New() + scope := generateScope() + parameterSchema := generateParameter(t, db, parameterOptions{ + ProjectImportJobID: scope.ProjectImportJobID, + DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, + }) + computed, err := parameter.Compute(context.Background(), db, scope, nil) + require.NoError(t, err) + require.Len(t, computed, 1) + computedValue := computed[0] + require.True(t, computedValue.DefaultSourceValue) + require.Equal(t, database.ParameterScopeImportJob, computedValue.Scope) + require.Equal(t, scope.ProjectImportJobID.String(), computedValue.ScopeID) + require.Equal(t, computedValue.SourceValue, parameterSchema.DefaultSourceValue) + }) + + t.Run("OverrideOrganizationWithImportJob", func(t *testing.T) { + t.Parallel() + db := databasefake.New() + scope := generateScope() + parameterSchema := generateParameter(t, db, parameterOptions{ + ProjectImportJobID: scope.ProjectImportJobID, + }) + _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: parameterSchema.Name, + Scope: database.ParameterScopeOrganization, + ScopeID: scope.OrganizationID, + SourceScheme: database.ParameterSourceSchemeData, + SourceValue: "firstnop", + DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, + }) + require.NoError(t, err) + + value, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: parameterSchema.Name, + Scope: database.ParameterScopeImportJob, + ScopeID: scope.ProjectImportJobID.String(), + SourceScheme: database.ParameterSourceSchemeData, + SourceValue: "secondnop", + DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, + }) + require.NoError(t, err) + + computed, err := parameter.Compute(context.Background(), db, scope, nil) + require.NoError(t, err) + require.Len(t, computed, 1) + require.Equal(t, false, computed[0].DefaultSourceValue) + require.Equal(t, value.SourceValue, computed[0].SourceValue) + }) + + t.Run("ProjectOverridesProjectDefault", func(t *testing.T) { + t.Parallel() + db := databasefake.New() + scope := generateScope() + parameterSchema := generateParameter(t, db, parameterOptions{ + ProjectImportJobID: scope.ProjectImportJobID, + }) + value, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: parameterSchema.Name, + Scope: database.ParameterScopeProject, + ScopeID: scope.ProjectID.UUID.String(), + SourceScheme: database.ParameterSourceSchemeData, + SourceValue: "nop", + DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, + }) + require.NoError(t, err) + + computed, err := parameter.Compute(context.Background(), db, scope, nil) + require.NoError(t, err) + require.Len(t, computed, 1) + require.Equal(t, false, computed[0].DefaultSourceValue) + require.Equal(t, value.SourceValue, computed[0].SourceValue) + }) + + t.Run("WorkspaceCannotOverwriteProjectDefault", func(t *testing.T) { + t.Parallel() + db := databasefake.New() + scope := generateScope() + parameterSchema := generateParameter(t, db, parameterOptions{ + ProjectImportJobID: scope.ProjectImportJobID, + }) + _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: parameterSchema.Name, + Scope: database.ParameterScopeWorkspace, + ScopeID: scope.WorkspaceID.UUID.String(), + SourceScheme: database.ParameterSourceSchemeData, + SourceValue: "nop", + DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, + }) + require.NoError(t, err) + + computed, err := parameter.Compute(context.Background(), db, scope, nil) + require.NoError(t, err) + require.Len(t, computed, 1) + require.Equal(t, true, computed[0].DefaultSourceValue) + }) + + t.Run("WorkspaceOverwriteProjectDefault", func(t *testing.T) { + t.Parallel() + db := databasefake.New() + scope := generateScope() + parameterSchema := generateParameter(t, db, parameterOptions{ + AllowOverrideSource: true, + ProjectImportJobID: scope.ProjectImportJobID, + }) + _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: parameterSchema.Name, + Scope: database.ParameterScopeWorkspace, + ScopeID: scope.WorkspaceID.UUID.String(), + SourceScheme: database.ParameterSourceSchemeData, + SourceValue: "nop", + DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, + }) + require.NoError(t, err) + + computed, err := parameter.Compute(context.Background(), db, scope, nil) + require.NoError(t, err) + require.Len(t, computed, 1) + require.Equal(t, false, computed[0].DefaultSourceValue) + }) +} From 94eb484655a33d5e5d6899886330e84c83eae9c3 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 20:05:31 +0000 Subject: [PATCH 03/20] Refactor parameters to enable schema matching --- cli/clitest/clitest.go | 19 ++ cli/projectcreate.go | 93 ++++++++-- cli/projectcreate_test.go | 42 +++++ cli/projects.go | 5 +- coderd/coderd.go | 3 +- coderd/parameter/compute_test.go | 18 ++ coderd/parameter/parameter.go | 199 -------------------- coderd/parameter/parameter_test.go | 242 ------------------------- coderd/parameters.go | 91 +++------- coderd/projects_test.go | 2 - coderd/provisionerdaemons.go | 47 ++--- coderd/provisionerjobs.go | 65 +++++-- coderd/provisionerjobs_test.go | 79 +++++++- codersdk/projects_test.go | 1 - codersdk/provisioners.go | 18 +- database/databasefake/databasefake.go | 2 - database/dump.sql | 9 +- database/migrations/000004_jobs.up.sql | 36 ++-- database/models.go | 5 +- database/query.sql | 9 +- database/query.sql.go | 25 +-- go.mod | 1 + go.sum | 2 + provisioner/terraform/parse.go | 2 +- provisioner/terraform/parse_test.go | 9 +- 25 files changed, 390 insertions(+), 634 deletions(-) create mode 100644 cli/projectcreate_test.go delete mode 100644 coderd/parameter/parameter.go delete mode 100644 coderd/parameter/parameter_test.go diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index 7166843e93dac..dc390485b0eff 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -2,13 +2,18 @@ package clitest import ( "bufio" + "context" "io" "testing" "github.com/spf13/cobra" + "github.com/stretchr/testify/require" "github.com/coder/coder/cli" "github.com/coder/coder/cli/config" + "github.com/coder/coder/coderd" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" ) func New(t *testing.T, args ...string) (*cobra.Command, config.Root) { @@ -19,6 +24,20 @@ func New(t *testing.T, args ...string) (*cobra.Command, config.Root) { return cmd, root } +func CreateInitialUser(t *testing.T, client *codersdk.Client, root config.Root) coderd.CreateInitialUserRequest { + user := coderdtest.CreateInitialUser(t, client) + resp, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{ + Email: user.Email, + Password: user.Password, + }) + require.NoError(t, err) + err = root.Session().Write(resp.SessionToken) + require.NoError(t, err) + err = root.URL().Write(client.URL.String()) + require.NoError(t, err) + return user +} + func StdoutLogs(t *testing.T) io.Writer { reader, writer := io.Pipe() scanner := bufio.NewScanner(reader) diff --git a/cli/projectcreate.go b/cli/projectcreate.go index 4cde2024053a4..48b0ea3040548 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -21,7 +21,10 @@ import ( ) func projectCreate() *cobra.Command { - return &cobra.Command{ + var ( + directory string + ) + cmd := &cobra.Command{ Use: "create", Short: "Create a project from the current directory", RunE: func(cmd *cobra.Command, args []string) error { @@ -33,23 +36,17 @@ func projectCreate() *cobra.Command { if err != nil { return err } - - workingDir, err := os.Getwd() - if err != nil { - return err - } - _, err = runPrompt(cmd, &promptui.Prompt{ Default: "y", IsConfirm: true, - Label: fmt.Sprintf("Set up %s in your organization?", color.New(color.FgHiCyan).Sprintf("%q", workingDir)), + Label: fmt.Sprintf("Set up %s in your organization?", color.New(color.FgHiCyan).Sprintf("%q", directory)), }) if err != nil { return err } name, err := runPrompt(cmd, &promptui.Prompt{ - Default: filepath.Base(workingDir), + Default: filepath.Base(directory), Label: "What's your project's name?", Validate: func(s string) error { _, err = client.Project(cmd.Context(), organization.Name, s) @@ -63,12 +60,12 @@ func projectCreate() *cobra.Command { return err } - spin := spinner.New(spinner.CharSets[0], 50*time.Millisecond) + spin := spinner.New(spinner.CharSets[0], 25*time.Millisecond) spin.Suffix = " Uploading current directory..." spin.Start() defer spin.Stop() - bytes, err := tarDirectory(workingDir) + bytes, err := tarDirectory(directory) if err != nil { return err } @@ -84,6 +81,12 @@ func projectCreate() *cobra.Command { Provisioner: database.ProvisionerTypeTerraform, // SkipResources on first import to detect variables defined by the project. SkipResources: true, + // ParameterValues: []coderd.CreateParameterValueRequest{{ + // Name: "aws_access_key", + // SourceValue: "tomato", + // SourceScheme: database.ParameterSourceSchemeData, + // DestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, + // }}, }) if err != nil { return err @@ -102,19 +105,85 @@ func projectCreate() *cobra.Command { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[parse]"), log.Output) } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Parsed project source... displaying parameters:") + schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) if err != nil { return err } + values, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, job.ID) + if err != nil { + return err + } + valueBySchemaID := map[string]coderd.ComputedParameterValue{} + for _, value := range values { + valueBySchemaID[value.SchemaID.String()] = value + } + for _, schema := range schemas { - fmt.Printf("Schema: %+v\n", schema) + if value, ok := valueBySchemaID[schema.ID.String()]; ok { + fmt.Printf("Value for: %s %s\n", value.Name, value.SourceValue) + continue + } + fmt.Printf("No value for: %s\n", schema.Name) } + // schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) + // if err != nil { + // return err + // } + // _, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n %s\n\n", color.HiBlackString("Parameters")) + + // for _, param := range params { + // if param.Value == nil { + // _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s = must be set\n", color.HiRedString(param.Schema.Name)) + // continue + // } + // value := param.Value.DestinationValue + // if !param.Schema.RedisplayValue { + // value = "" + // } + // output := fmt.Sprintf(" %s = %s", color.HiGreenString(param.Value.SourceValue), color.CyanString(value)) + // param.Value.DefaultSourceValue = false + // param.Value.Scope = database.ParameterScopeOrganization + // param.Value.ScopeID = organization.ID + // if param.Value.DefaultSourceValue { + // output += " (default value)" + // } else { + // output += fmt.Sprintf(" (inherited from %s)", param.Value.Scope) + // } + // root := treeprint.NewWithRoot(output) + // root.AddNode(color.HiBlackString("Description") + "\n" + param.Schema.Description) + // fmt.Fprintln(cmd.OutOrStdout(), strings.Join(strings.Split(root.String(), "\n"), "\n ")) + // } + + // for _, param := range params { + // if param.Value != nil { + // continue + // } + + // value, err := runPrompt(cmd, &promptui.Prompt{ + // Label: "Specify value for " + color.HiCyanString(param.Schema.Name), + // Validate: func(s string) error { + // // param.Schema.Vali + // return nil + // }, + // }) + // if err != nil { + // continue + // } + // fmt.Printf(": %s\n", value) + // } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Create project %q!\n", name) return nil }, } + currentDirectory, _ := os.Getwd() + cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from") + + return cmd } func tarDirectory(directory string) ([]byte, error) { diff --git a/cli/projectcreate_test.go b/cli/projectcreate_test.go new file mode 100644 index 0000000000000..1d4a83288ddbf --- /dev/null +++ b/cli/projectcreate_test.go @@ -0,0 +1,42 @@ +package cli_test + +import ( + "testing" + + "github.com/Netflix/go-expect" + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" + "github.com/stretchr/testify/require" +) + +func TestProjectCreate(t *testing.T) { + t.Parallel() + t.Run("InitialUserTTY", func(t *testing.T) { + t.Parallel() + console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) + require.NoError(t, err) + client := coderdtest.New(t) + directory := t.TempDir() + cmd, root := clitest.New(t, "projects", "create", "--directory", directory) + _ = clitest.CreateInitialUser(t, client, root) + cmd.SetIn(console.Tty()) + cmd.SetOut(console.Tty()) + go func() { + err := cmd.Execute() + require.NoError(t, err) + }() + + matches := []string{ + "organization?", "y", + "name?", "", + } + for i := 0; i < len(matches); i += 2 { + match := matches[i] + value := matches[i+1] + _, err = console.ExpectString(match) + require.NoError(t, err) + _, err = console.SendLine(value) + require.NoError(t, err) + } + }) +} diff --git a/cli/projects.go b/cli/projects.go index 07d68c0155967..e0963459a6ed6 100644 --- a/cli/projects.go +++ b/cli/projects.go @@ -7,8 +7,9 @@ import ( func projects() *cobra.Command { cmd := &cobra.Command{ - Use: "projects", - Long: "Testing something", + Use: "projects", + Aliases: []string{"project"}, + Long: "Testing something", Example: ` - Create a project for developers to create workspaces diff --git a/coderd/coderd.go b/coderd/coderd.go index f0df2fdb6d119..3bd7ff54404de 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -121,7 +121,8 @@ func New(options *Options) http.Handler { r.Route("/{provisionerjob}", func(r chi.Router) { r.Use(httpmw.ExtractProvisionerJobParam(options.Database)) r.Get("/", api.provisionerJobByOrganization) - r.Get("/parameters", api.provisionerJobParametersByID) + r.Get("/schemas", api.provisionerJobParameterSchemasByID) + r.Get("/computed", api.provisionerJobComputedParametersByID) r.Get("/logs", api.provisionerJobLogsByID) }) }) diff --git a/coderd/parameter/compute_test.go b/coderd/parameter/compute_test.go index c616c48f815e4..660be359975a5 100644 --- a/coderd/parameter/compute_test.go +++ b/coderd/parameter/compute_test.go @@ -201,4 +201,22 @@ func TestCompute(t *testing.T) { require.Len(t, computed, 1) require.Equal(t, false, computed[0].DefaultSourceValue) }) + + t.Run("HideRedisplay", func(t *testing.T) { + t.Parallel() + db := databasefake.New() + scope := generateScope() + _ = generateParameter(t, db, parameterOptions{ + ProjectImportJobID: scope.ProjectImportJobID, + DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, + }) + computed, err := parameter.Compute(context.Background(), db, scope, ¶meter.ComputeOptions{ + HideRedisplayValues: true, + }) + require.NoError(t, err) + require.Len(t, computed, 1) + computedValue := computed[0] + require.True(t, computedValue.DefaultSourceValue) + require.Equal(t, computedValue.SourceValue, "") + }) } diff --git a/coderd/parameter/parameter.go b/coderd/parameter/parameter.go deleted file mode 100644 index d8c7fe73778fc..0000000000000 --- a/coderd/parameter/parameter.go +++ /dev/null @@ -1,199 +0,0 @@ -package parameter - -import ( - "context" - "database/sql" - "errors" - - "github.com/google/uuid" - "golang.org/x/xerrors" - - "github.com/coder/coder/database" -) - -// Scope targets identifiers to pull parameters from. -type Scope struct { - ImportJobID uuid.UUID - OrganizationID string - UserID string - ProjectID uuid.NullUUID - WorkspaceID uuid.NullUUID -} - -type Value struct { - DestinationScheme database.ParameterDestinationScheme - Name string - Value string - // Default is whether the default value from the schema - // was consumed. - Default bool - Scope database.ParameterScope - ScopeID string -} - -type Computed struct { - Schema database.ParameterSchema - // Value is nil when no value was computed for the schema. - Value *Value -} - -// Compute accepts a scope in which parameter values are sourced. -// These sources are iterated in a hierarchical fashion to determine -// the runtime parameter values for a project. -func Compute(ctx context.Context, db database.Store, scope Scope, additional ...database.ParameterValue) ([]Computed, error) { - compute := &compute{ - db: db, - parametersByName: map[string]Computed{}, - } - - // All parameters for the import job ID! - parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.ImportJobID) - if errors.Is(err, sql.ErrNoRows) { - err = nil - } - if err != nil { - return nil, xerrors.Errorf("get project parameters: %w", err) - } - for _, parameterSchema := range parameterSchemas { - compute.parametersByName[parameterSchema.Name] = Computed{ - Schema: parameterSchema, - } - } - - // Organization parameters come first! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeOrganization, - ScopeID: scope.OrganizationID, - }) - if err != nil { - return nil, err - } - - // Default project parameter values come second! - for _, parameterSchema := range parameterSchemas { - if !parameterSchema.DefaultSourceValue.Valid { - continue - } - if !parameterSchema.DefaultDestinationValue.Valid { - continue - } - - switch parameterSchema.DefaultSourceScheme { - case database.ParameterSourceSchemeData: - compute.parametersByName[parameterSchema.Name] = Computed{ - Value: &Value{ - DestinationScheme: parameterSchema.DefaultDestinationScheme, - Name: parameterSchema.DefaultDestinationValue.String, - Value: parameterSchema.DefaultSourceValue.String, - Default: true, - Scope: database.ParameterScopeProject, - ScopeID: scope.ProjectID.UUID.String(), - }, - Schema: parameterSchema, - } - default: - return nil, xerrors.Errorf("unsupported source scheme for project version parameter %q: %q", parameterSchema.Name, string(parameterSchema.DefaultSourceScheme)) - } - } - - if scope.ProjectID.Valid { - // Project parameters come third! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeProject, - ScopeID: scope.ProjectID.UUID.String(), - }) - if err != nil { - return nil, err - } - } - - // User parameters come fourth! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeUser, - ScopeID: scope.UserID, - }) - if err != nil { - return nil, err - } - - if scope.WorkspaceID.Valid { - // Workspace parameters come last! - err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{ - Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), - }) - if err != nil { - return nil, err - } - } - - for _, parameterValue := range additional { - err = compute.injectSingle(parameterValue) - if err != nil { - return nil, xerrors.Errorf("inject %q: %w", parameterValue.Name, err) - } - } - - values := make([]Computed, 0, len(compute.parametersByName)) - for _, value := range compute.parametersByName { - values = append(values, value) - } - return values, nil -} - -type compute struct { - db database.Store - parametersByName map[string]Computed -} - -// Validates and computes the value for parameters; setting the value on "parameterByName". -func (c *compute) injectScope(ctx context.Context, scopeParams database.GetParameterValuesByScopeParams) error { - scopedParameters, err := c.db.GetParameterValuesByScope(ctx, scopeParams) - if errors.Is(err, sql.ErrNoRows) { - err = nil - } - if err != nil { - return xerrors.Errorf("get %s parameters: %w", scopeParams.Scope, err) - } - - for _, scopedParameter := range scopedParameters { - err = c.injectSingle(scopedParameter) - if err != nil { - return xerrors.Errorf("inject single %q: %w", scopedParameter.Name, err) - } - } - return nil -} - -func (c *compute) injectSingle(scopedParameter database.ParameterValue) error { - computed, hasComputed := c.parametersByName[scopedParameter.Name] - if !hasComputed { - // Don't inject parameters that aren't defined by the project. - return nil - } - - if computed.Value != nil { - // If a parameter already exists, check if this variable can override it. - // Injection hierarchy is the responsibility of the caller. This check ensures - // project parameters cannot be overridden if already set. - if !computed.Schema.AllowOverrideSource && scopedParameter.Scope != database.ParameterScopeProject { - return nil - } - } - - switch scopedParameter.SourceScheme { - case database.ParameterSourceSchemeData: - computed.Value = &Value{ - DestinationScheme: scopedParameter.DestinationScheme, - Name: scopedParameter.SourceValue, - Value: scopedParameter.DestinationValue, - Scope: scopedParameter.Scope, - ScopeID: scopedParameter.ScopeID, - Default: false, - } - c.parametersByName[scopedParameter.Name] = computed - default: - return xerrors.Errorf("unsupported source scheme: %q", string(computed.Schema.DefaultSourceScheme)) - } - return nil -} diff --git a/coderd/parameter/parameter_test.go b/coderd/parameter/parameter_test.go deleted file mode 100644 index aa064683c64f3..0000000000000 --- a/coderd/parameter/parameter_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package parameter_test - -import ( - "context" - "database/sql" - "testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/require" - - "github.com/coder/coder/coderd/parameter" - "github.com/coder/coder/cryptorand" - "github.com/coder/coder/database" - "github.com/coder/coder/database/databasefake" -) - -func TestCompute(t *testing.T) { - t.Parallel() - generateScope := func() parameter.Scope { - return parameter.Scope{ - ImportJobID: uuid.New(), - OrganizationID: uuid.NewString(), - ProjectID: uuid.NullUUID{ - UUID: uuid.New(), - Valid: true, - }, - WorkspaceID: uuid.NullUUID{ - UUID: uuid.New(), - Valid: true, - }, - UserID: uuid.NewString(), - } - } - type parameterOptions struct { - AllowOverrideSource bool - AllowOverrideDestination bool - DefaultDestinationScheme database.ParameterDestinationScheme - ImportJobID uuid.UUID - } - generateParameter := func(t *testing.T, db database.Store, opts parameterOptions) database.ParameterSchema { - if opts.DefaultDestinationScheme == "" { - opts.DefaultDestinationScheme = database.ParameterDestinationSchemeEnvironmentVariable - } - name, err := cryptorand.String(8) - require.NoError(t, err) - sourceValue, err := cryptorand.String(8) - require.NoError(t, err) - destinationValue, err := cryptorand.String(8) - require.NoError(t, err) - param, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{ - ID: uuid.New(), - Name: name, - JobID: opts.ImportJobID, - DefaultSourceScheme: database.ParameterSourceSchemeData, - DefaultSourceValue: sql.NullString{ - String: sourceValue, - Valid: true, - }, - DefaultDestinationValue: sql.NullString{ - String: destinationValue, - Valid: true, - }, - AllowOverrideSource: opts.AllowOverrideSource, - AllowOverrideDestination: opts.AllowOverrideDestination, - DefaultDestinationScheme: opts.DefaultDestinationScheme, - }) - require.NoError(t, err) - return param - } - - t.Run("NoValue", func(t *testing.T) { - t.Parallel() - db := databasefake.New() - scope := generateScope() - parameterSchema, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{ - ID: uuid.New(), - JobID: scope.ImportJobID, - Name: "hey", - }) - require.NoError(t, err) - computed, err := parameter.Compute(context.Background(), db, scope) - require.NoError(t, err) - require.Equal(t, parameterSchema.ID.String(), computed[0].Schema.ID.String()) - require.Nil(t, computed[0].Value) - }) - - t.Run("UseDefaultProjectValue", func(t *testing.T) { - t.Parallel() - db := databasefake.New() - scope := generateScope() - parameterSchema := generateParameter(t, db, parameterOptions{ - ImportJobID: scope.ImportJobID, - DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, - }) - computed, err := parameter.Compute(context.Background(), db, scope) - require.NoError(t, err) - require.Len(t, computed, 1) - computedValue := computed[0] - require.True(t, computedValue.Value.Default) - require.Equal(t, database.ParameterScopeProject, computedValue.Value.Scope) - require.Equal(t, scope.ProjectID.UUID.String(), computedValue.Value.ScopeID) - require.Equal(t, computedValue.Value.Name, parameterSchema.DefaultDestinationValue.String) - require.Equal(t, computedValue.Value.DestinationScheme, database.ParameterDestinationSchemeProvisionerVariable) - require.Equal(t, computedValue.Value.Value, parameterSchema.DefaultSourceValue.String) - }) - - t.Run("OverrideOrganizationWithProjectDefault", func(t *testing.T) { - t.Parallel() - db := databasefake.New() - scope := generateScope() - parameterSchema := generateParameter(t, db, parameterOptions{ - ImportJobID: scope.ImportJobID, - }) - _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ - ID: uuid.New(), - Name: parameterSchema.Name, - Scope: database.ParameterScopeOrganization, - ScopeID: scope.OrganizationID, - SourceScheme: database.ParameterSourceSchemeData, - SourceValue: "nop", - DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "organizationvalue", - }) - require.NoError(t, err) - - computed, err := parameter.Compute(context.Background(), db, scope) - require.NoError(t, err) - require.Len(t, computed, 1) - require.Equal(t, true, computed[0].Value.Default) - require.Equal(t, parameterSchema.DefaultSourceValue.String, computed[0].Value.Value) - }) - - t.Run("ProjectOverridesProjectDefault", func(t *testing.T) { - t.Parallel() - db := databasefake.New() - scope := generateScope() - parameterSchema := generateParameter(t, db, parameterOptions{ - ImportJobID: scope.ImportJobID, - }) - value, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ - ID: uuid.New(), - Name: parameterSchema.Name, - Scope: database.ParameterScopeProject, - ScopeID: scope.ProjectID.UUID.String(), - SourceScheme: database.ParameterSourceSchemeData, - SourceValue: "nop", - DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "projectvalue", - }) - require.NoError(t, err) - - computed, err := parameter.Compute(context.Background(), db, scope) - require.NoError(t, err) - require.Len(t, computed, 1) - require.Equal(t, false, computed[0].Value.Default) - require.Equal(t, value.DestinationValue, computed[0].Value.Value) - }) - - t.Run("WorkspaceCannotOverwriteProjectDefault", func(t *testing.T) { - t.Parallel() - db := databasefake.New() - scope := generateScope() - parameterSchema := generateParameter(t, db, parameterOptions{ - ImportJobID: scope.ImportJobID, - }) - _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ - ID: uuid.New(), - Name: parameterSchema.Name, - Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), - SourceScheme: database.ParameterSourceSchemeData, - SourceValue: "nop", - DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "projectvalue", - }) - require.NoError(t, err) - - computed, err := parameter.Compute(context.Background(), db, scope) - require.NoError(t, err) - require.Len(t, computed, 1) - require.Equal(t, true, computed[0].Value.Default) - }) - - t.Run("WorkspaceOverwriteProjectDefault", func(t *testing.T) { - t.Parallel() - db := databasefake.New() - scope := generateScope() - parameterSchema := generateParameter(t, db, parameterOptions{ - AllowOverrideSource: true, - ImportJobID: scope.ImportJobID, - }) - _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ - ID: uuid.New(), - Name: parameterSchema.Name, - Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), - SourceScheme: database.ParameterSourceSchemeData, - SourceValue: "nop", - DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "projectvalue", - }) - require.NoError(t, err) - - computed, err := parameter.Compute(context.Background(), db, scope) - require.NoError(t, err) - require.Len(t, computed, 1) - require.Equal(t, false, computed[0].Value.Default) - }) - - t.Run("AdditionalOverwriteWorkspace", func(t *testing.T) { - t.Parallel() - db := databasefake.New() - scope := generateScope() - parameterSchema := generateParameter(t, db, parameterOptions{ - AllowOverrideSource: true, - ImportJobID: scope.ImportJobID, - }) - _, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{ - ID: uuid.New(), - Name: parameterSchema.Name, - Scope: database.ParameterScopeWorkspace, - ScopeID: scope.WorkspaceID.UUID.String(), - SourceScheme: database.ParameterSourceSchemeData, - SourceValue: "nop", - DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "projectvalue", - }) - require.NoError(t, err) - - computed, err := parameter.Compute(context.Background(), db, scope, database.ParameterValue{ - Name: parameterSchema.Name, - Scope: database.ParameterScopeUser, - SourceScheme: database.ParameterSourceSchemeData, - SourceValue: "nop", - DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "testing", - }) - require.NoError(t, err) - require.Len(t, computed, 1) - require.Equal(t, "testing", computed[0].Value.Value) - }) -} diff --git a/coderd/parameters.go b/coderd/parameters.go index b7672a22031f9..0df7f112aadda 100644 --- a/coderd/parameters.go +++ b/coderd/parameters.go @@ -5,55 +5,30 @@ import ( "errors" "fmt" "net/http" - "time" "github.com/go-chi/render" "github.com/google/uuid" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/database" "github.com/coder/coder/httpapi" ) +// ParameterSchema represents a parameter parsed from project version source. +type ParameterSchema database.ParameterSchema + +// ParameterValue represents a set value for the scope. +type ParameterValue database.ParameterValue + +// ComputedParameterValue represents a computed parameter value. +type ComputedParameterValue parameter.ComputedValue + // CreateParameterValueRequest is used to create a new parameter value for a scope. type CreateParameterValueRequest struct { Name string `json:"name" validate:"required"` SourceValue string `json:"source_value" validate:"required"` SourceScheme database.ParameterSourceScheme `json:"source_scheme" validate:"oneof=data,required"` DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme" validate:"oneof=environment_variable provisioner_variable,required"` - DestinationValue string `json:"destination_value" validate:"required"` -} - -// ParameterSchema represents a parameter parsed from project version source. -type ParameterSchema struct { - ID uuid.UUID `json:"id"` - CreatedAt time.Time `json:"created_at"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - DefaultSourceScheme database.ParameterSourceScheme `json:"default_source_scheme,omitempty"` - DefaultSourceValue string `json:"default_source_value,omitempty"` - AllowOverrideSource bool `json:"allow_override_source"` - DefaultDestinationScheme database.ParameterDestinationScheme `json:"default_destination_scheme,omitempty"` - DefaultDestinationValue string `json:"default_destination_value,omitempty"` - AllowOverrideDestination bool `json:"allow_override_destination"` - DefaultRefresh string `json:"default_refresh"` - RedisplayValue bool `json:"redisplay_value"` - ValidationError string `json:"validation_error,omitempty"` - ValidationCondition string `json:"validation_condition,omitempty"` - ValidationTypeSystem database.ParameterTypeSystem `json:"validation_type_system,omitempty"` - ValidationValueType string `json:"validation_value_type,omitempty"` -} - -// ParameterValue represents a set value for the scope. -type ParameterValue struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Scope database.ParameterScope `json:"scope"` - ScopeID string `json:"scope_id"` - SourceScheme database.ParameterSourceScheme `json:"source_scheme"` - DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme"` - DestinationValue string `json:"destination_value"` } // Abstracts creating parameters into a single request/response format. @@ -74,7 +49,6 @@ func postParameterValueForScope(rw http.ResponseWriter, r *http.Request, db data SourceScheme: createRequest.SourceScheme, SourceValue: createRequest.SourceValue, DestinationScheme: createRequest.DestinationScheme, - DestinationValue: createRequest.DestinationValue, }) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ @@ -112,37 +86,24 @@ func parametersForScope(rw http.ResponseWriter, r *http.Request, db database.Sto render.JSON(rw, r, apiParameterValues) } -func convertParameterSchema(parameter database.ParameterSchema) ParameterSchema { - return ParameterSchema{ - ID: parameter.ID, - CreatedAt: parameter.CreatedAt, - Name: parameter.Name, - Description: parameter.Description, - DefaultSourceScheme: parameter.DefaultSourceScheme, - DefaultSourceValue: parameter.DefaultSourceValue.String, - AllowOverrideSource: parameter.AllowOverrideSource, - DefaultDestinationScheme: parameter.DefaultDestinationScheme, - DefaultDestinationValue: parameter.DefaultDestinationValue.String, - AllowOverrideDestination: parameter.AllowOverrideDestination, - DefaultRefresh: parameter.DefaultRefresh, - RedisplayValue: parameter.RedisplayValue, - ValidationError: parameter.ValidationError, - ValidationCondition: parameter.ValidationCondition, - ValidationTypeSystem: parameter.ValidationTypeSystem, - ValidationValueType: parameter.ValidationValueType, +// Returns parameters for a specific scope. +func computedParametersForScope(rw http.ResponseWriter, r *http.Request, db database.Store, scope parameter.ComputeScope) { + values, err := parameter.Compute(r.Context(), db, scope, ¶meter.ComputeOptions{ + // We *never* want to send the client secret parameter values. + HideRedisplayValues: true, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("compute values: %s", err), + }) + return } + + render.Status(r, http.StatusOK) + render.JSON(rw, r, values) } func convertParameterValue(parameterValue database.ParameterValue) ParameterValue { - return ParameterValue{ - ID: parameterValue.ID, - Name: parameterValue.Name, - CreatedAt: parameterValue.CreatedAt, - UpdatedAt: parameterValue.UpdatedAt, - Scope: parameterValue.Scope, - ScopeID: parameterValue.ScopeID, - SourceScheme: parameterValue.SourceScheme, - DestinationScheme: parameterValue.DestinationScheme, - DestinationValue: parameterValue.DestinationValue, - } + parameterValue.SourceValue = "" + return ParameterValue(parameterValue) } diff --git a/coderd/projects_test.go b/coderd/projects_test.go index ee11e2b744ad6..42efedb939f83 100644 --- a/coderd/projects_test.go +++ b/coderd/projects_test.go @@ -114,7 +114,6 @@ func TestPostParametersByProject(t *testing.T) { SourceValue: "tomato", SourceScheme: database.ParameterSourceSchemeData, DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "moo", }) require.NoError(t, err) }) @@ -144,7 +143,6 @@ func TestParametersByProject(t *testing.T) { SourceValue: "source-value", SourceScheme: database.ParameterSourceSchemeData, DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "destination-value", }) require.NoError(t, err) params, err := client.ProjectParameters(context.Background(), user.Organization, project.Name) diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 7d2dbcb005903..1103dec88966c 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -114,9 +114,8 @@ type projectVersionImportJob struct { OrganizationID string `json:"organization_id"` ProjectID uuid.UUID `json:"project_id"` - AdditionalParameters []database.ParameterValue `json:"parameters"` - SkipParameterSchemas bool `json:"skip_parameter_schemas"` - SkipResources bool `json:"skip_resources"` + SkipParameterSchemas bool `json:"skip_parameter_schemas"` + SkipResources bool `json:"skip_resources"` } // Implementation of the provisioner daemon protobuf server. @@ -211,9 +210,9 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty } // Compute parameters for the workspace to consume. - parameters, err := parameter.Compute(ctx, server.Database, parameter.Scope{ - ImportJobID: projectVersion.ImportJobID, - OrganizationID: organization.ID, + parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{ + ProjectImportJobID: projectVersion.ImportJobID, + OrganizationID: organization.ID, ProjectID: uuid.NullUUID{ UUID: project.ID, Valid: true, @@ -223,14 +222,14 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty UUID: workspace.ID, Valid: true, }, - }) + }, nil) if err != nil { return nil, failJob(fmt.Sprintf("compute parameters: %s", err)) } // Convert parameters to the protobuf type. protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) for _, parameter := range parameters { - converted, err := convertComputedParameter(parameter) + converted, err := convertComputedParameterValue(parameter) if err != nil { return nil, failJob(fmt.Sprintf("convert parameter: %s", err)) } @@ -253,22 +252,22 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty } // Compute parameters for the workspace to consume. - parameters, err := parameter.Compute(ctx, server.Database, parameter.Scope{ - ImportJobID: job.ID, - OrganizationID: input.OrganizationID, + parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{ + ProjectImportJobID: job.ID, + OrganizationID: input.OrganizationID, ProjectID: uuid.NullUUID{ UUID: input.ProjectID, Valid: input.ProjectID.String() != uuid.Nil.String(), }, UserID: user.ID, - }, input.AdditionalParameters...) + }, nil) if err != nil { return nil, failJob(fmt.Sprintf("compute parameters: %s", err)) } // Convert parameters to the protobuf type. protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) for _, parameter := range parameters { - converted, err := convertComputedParameter(parameter) + converted, err := convertComputedParameterValue(parameter) if err != nil { return nil, failJob(fmt.Sprintf("convert parameter: %s", err)) } @@ -448,10 +447,7 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr return nil, xerrors.Errorf("convert parameter source scheme: %w", err) } parameterSchema.DefaultSourceScheme = parameterSourceScheme - parameterSchema.DefaultSourceValue = sql.NullString{ - String: protoParameter.DefaultSource.Value, - Valid: protoParameter.DefaultSource.Value != "", - } + parameterSchema.DefaultSourceValue = protoParameter.DefaultSource.Value } // It's possible a parameter doesn't define a default destination! @@ -461,10 +457,6 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr return nil, xerrors.Errorf("convert parameter destination scheme: %w", err) } parameterSchema.DefaultDestinationScheme = parameterDestinationScheme - parameterSchema.DefaultDestinationValue = sql.NullString{ - String: protoParameter.DefaultDestination.Value, - Valid: protoParameter.DefaultDestination.Value != "", - } } parameterSchemas = append(parameterSchemas, parameterSchema) @@ -617,23 +609,20 @@ func convertLogSource(logSource proto.LogSource) (database.LogSource, error) { } } -func convertComputedParameter(param parameter.Computed) (*sdkproto.ParameterValue, error) { - if param.Value == nil { - return nil, xerrors.Errorf("no value for computed parameter: %s", param.Schema.Name) - } +func convertComputedParameterValue(param parameter.ComputedValue) (*sdkproto.ParameterValue, error) { scheme := sdkproto.ParameterDestination_ENVIRONMENT_VARIABLE - switch param.Value.DestinationScheme { + switch param.DestinationScheme { case database.ParameterDestinationSchemeEnvironmentVariable: scheme = sdkproto.ParameterDestination_ENVIRONMENT_VARIABLE case database.ParameterDestinationSchemeProvisionerVariable: scheme = sdkproto.ParameterDestination_PROVISIONER_VARIABLE default: - return nil, xerrors.Errorf("unrecognized destination scheme: %q", param.Value.DestinationScheme) + return nil, xerrors.Errorf("unrecognized destination scheme: %q", param.DestinationScheme) } return &sdkproto.ParameterValue{ DestinationScheme: scheme, - Name: param.Value.Name, - Value: param.Value.Value, + Name: param.Name, + Value: param.SourceValue, }, nil } diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 32e811ac89364..09e38880ae172 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -45,16 +45,14 @@ type ProvisionerJob struct { WorkerID *uuid.UUID `json:"worker_id,omitempty"` } -type ComputedParameter struct{} - type CreateProjectImportJobRequest struct { StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"` StorageSource string `json:"storage_source" validate:"required"` Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"` - AdditionalParameters []ParameterValue `json:"parameter_values"` - SkipParameterSchemas bool `json:"skip_parameter_schemas"` - SkipResources bool `json:"skip_resources"` + ParameterValues []CreateParameterValueRequest `json:"parameter_values"` + SkipParameterSchemas bool `json:"skip_parameter_schemas"` + SkipResources bool `json:"skip_resources"` } func (*api) provisionerJobByOrganization(rw http.ResponseWriter, r *http.Request) { @@ -98,8 +96,29 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r return } + jobID := uuid.New() + for _, parameterValue := range req.ParameterValues { + _, err = api.Database.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: parameterValue.Name, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + Scope: database.ParameterScopeImportJob, + ScopeID: jobID.String(), + SourceScheme: parameterValue.SourceScheme, + SourceValue: parameterValue.SourceValue, + DestinationScheme: parameterValue.DestinationScheme, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("insert parameter value: %s", err), + }) + return + } + } + job, err := api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ - ID: uuid.New(), + ID: jobID, CreatedAt: database.Now(), UpdatedAt: database.Now(), OrganizationID: organization.ID, @@ -122,8 +141,7 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r } // Return parsed parameter schemas for a job. -func (api *api) provisionerJobParametersByID(rw http.ResponseWriter, r *http.Request) { - apiKey := httpmw.APIKey(r) +func (api *api) provisionerJobParameterSchemasByID(rw http.ResponseWriter, r *http.Request) { job := httpmw.ProvisionerJobParam(r) if convertProvisionerJob(job).Status != ProvisionerJobStatusSucceeded { httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ @@ -132,28 +150,35 @@ func (api *api) provisionerJobParametersByID(rw http.ResponseWriter, r *http.Req return } - parameter.Compute(r.Context(), api.Database, parameter.Scope{ - ImportJobID: job.ID, - OrganizationID: job.OrganizationID, - UserID: apiKey.UserID, - }) - schemas, err := api.Database.GetParameterSchemasByJobID(r.Context(), job.ID) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("get parameter schemas: %s", err), + Message: fmt.Sprintf("list parameter schemas: %s", err), }) return } - apiSchemas := make([]ParameterSchema, 0, len(schemas)) - for _, parameter := range schemas { - apiSchemas = append(apiSchemas, convertParameterSchema(parameter)) - } + render.Status(r, http.StatusOK) - render.JSON(rw, r, apiSchemas) + render.JSON(rw, r, schemas) +} + +func (api *api) provisionerJobComputedParametersByID(rw http.ResponseWriter, r *http.Request) { + apiKey := httpmw.APIKey(r) + job := httpmw.ProvisionerJobParam(r) + if convertProvisionerJob(job).Status != ProvisionerJobStatusSucceeded { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: fmt.Sprintf("Job is in state %q! Must be %q.", convertProvisionerJob(job).Status, ProvisionerJobStatusSucceeded), + }) + return + } + computedParametersForScope(rw, r, api.Database, parameter.ComputeScope{ + ProjectImportJobID: job.ID, + OrganizationID: job.OrganizationID, + UserID: apiKey.UserID, + }) } func convertProvisionerJob(provisionerJob database.ProvisionerJob) ProvisionerJob { diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index e16d1ad993e79..0ea1e1397a7aa 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -8,8 +8,10 @@ import ( "github.com/stretchr/testify/require" + "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" + "github.com/coder/coder/database" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" ) @@ -51,6 +53,45 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { t.Log(log.Output) } }) + + t.Run("CreateWithParameters", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + data, err := echo.Tar(&echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "test", + RedisplayValue: true, + }}, + }, + }, + }}, + }) + require.NoError(t, err) + file, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data) + require.NoError(t, err) + job, err := client.CreateProjectVersionImportProvisionerJob(context.Background(), user.Organization, coderd.CreateProjectImportJobRequest{ + StorageSource: file.Hash, + StorageMethod: database.ProvisionerStorageMethodFile, + Provisioner: database.ProvisionerTypeEcho, + ParameterValues: []coderd.CreateParameterValueRequest{{ + Name: "test", + SourceValue: "somevalue", + SourceScheme: database.ParameterSourceSchemeData, + DestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, + }}, + SkipResources: true, + }) + require.NoError(t, err) + coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + values, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + require.Equal(t, "somevalue", values[0].SourceValue) + }) } func TestProvisionerJobParametersByID(t *testing.T) { @@ -60,7 +101,7 @@ func TestProvisionerJobParametersByID(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) - _, err := client.ProvisionerJobParameterSchemas(context.Background(), user.Organization, job.ID) + _, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) @@ -84,8 +125,42 @@ func TestProvisionerJobParametersByID(t *testing.T) { Provision: echo.ProvisionComplete, }) coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - params, err := client.ProvisionerJobParameterSchemas(context.Background(), user.Organization, job.ID) + params, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + require.Len(t, params, 0) + }) + + t.Run("ListNoRedisplay", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "example", + DefaultSource: &proto.ParameterSource{ + Scheme: proto.ParameterSource_DATA, + Value: "tomato", + }, + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + Value: "example", + }, + RedisplayValue: false, + }}, + }, + }, + }}, + Provision: echo.ProvisionComplete, + }) + coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + params, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) require.NoError(t, err) require.Len(t, params, 1) + require.NotNil(t, params[0]) + require.Equal(t, params[0].SourceValue, "") }) } diff --git a/codersdk/projects_test.go b/codersdk/projects_test.go index 9fd765c7f138d..b280887ec84ae 100644 --- a/codersdk/projects_test.go +++ b/codersdk/projects_test.go @@ -173,7 +173,6 @@ func TestCreateProjectParameter(t *testing.T) { SourceValue: "source-value", SourceScheme: database.ParameterSourceSchemeData, DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable, - DestinationValue: "destination-value", }) require.NoError(t, err) }) diff --git a/codersdk/provisioners.go b/codersdk/provisioners.go index d9aea0471d90f..ddb84b9b34688 100644 --- a/codersdk/provisioners.go +++ b/codersdk/provisioners.go @@ -152,9 +152,9 @@ func (c *Client) FollowProvisionerJobLogsAfter(ctx context.Context, organization return logs, nil } -// ProvisionerJobParameterSchemas returns project parameters for a version by name. +// ProvisionerJobParameters returns computed project parameters for a job by ID. func (c *Client) ProvisionerJobParameterSchemas(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ParameterSchema, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/parameters", organization, job), nil) + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/schemas", organization, job), nil) if err != nil { return nil, err } @@ -165,3 +165,17 @@ func (c *Client) ProvisionerJobParameterSchemas(ctx context.Context, organizatio var params []coderd.ParameterSchema return params, json.NewDecoder(res.Body).Decode(¶ms) } + +// ProvisionerJobParameterValues returns computed parameters for a provisioner job. +func (c *Client) ProvisionerJobParameterValues(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ComputedParameterValue, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/computed", organization, job), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, readBodyAsError(res) + } + var params []coderd.ComputedParameterValue + return params, json.NewDecoder(res.Body).Decode(¶ms) +} diff --git a/database/databasefake/databasefake.go b/database/databasefake/databasefake.go index 99793bc7409a9..9d53254f7af0f 100644 --- a/database/databasefake/databasefake.go +++ b/database/databasefake/databasefake.go @@ -643,7 +643,6 @@ func (q *fakeQuerier) InsertParameterValue(_ context.Context, arg database.Inser SourceScheme: arg.SourceScheme, SourceValue: arg.SourceValue, DestinationScheme: arg.DestinationScheme, - DestinationValue: arg.DestinationValue, } q.parameterValue = append(q.parameterValue, parameterValue) return parameterValue, nil @@ -719,7 +718,6 @@ func (q *fakeQuerier) InsertParameterSchema(_ context.Context, arg database.Inse DefaultSourceValue: arg.DefaultSourceValue, AllowOverrideSource: arg.AllowOverrideSource, DefaultDestinationScheme: arg.DefaultDestinationScheme, - DefaultDestinationValue: arg.DefaultDestinationValue, AllowOverrideDestination: arg.AllowOverrideDestination, DefaultRefresh: arg.DefaultRefresh, RedisplayValue: arg.RedisplayValue, diff --git a/database/dump.sql b/database/dump.sql index c21c963db56f5..0fde40950ad7e 100644 --- a/database/dump.sql +++ b/database/dump.sql @@ -28,6 +28,7 @@ CREATE TYPE parameter_destination_scheme AS ENUM ( CREATE TYPE parameter_scope AS ENUM ( 'organization', 'project', + 'import_job', 'user', 'workspace' ); @@ -87,7 +88,7 @@ CREATE TABLE api_keys ( ); CREATE TABLE file ( - hash character varying(32) NOT NULL, + hash character varying(64) NOT NULL, created_at timestamp with time zone NOT NULL, created_by text NOT NULL, mimetype character varying(64) NOT NULL, @@ -128,10 +129,9 @@ CREATE TABLE parameter_schema ( name character varying(64) NOT NULL, description character varying(8192) DEFAULT ''::character varying NOT NULL, default_source_scheme parameter_source_scheme, - default_source_value text, + default_source_value text NOT NULL, allow_override_source boolean NOT NULL, default_destination_scheme parameter_destination_scheme, - default_destination_value text, allow_override_destination boolean NOT NULL, default_refresh text NOT NULL, redisplay_value boolean NOT NULL, @@ -150,8 +150,7 @@ CREATE TABLE parameter_value ( scope_id text NOT NULL, source_scheme parameter_source_scheme NOT NULL, source_value text NOT NULL, - destination_scheme parameter_destination_scheme NOT NULL, - destination_value text NOT NULL + destination_scheme parameter_destination_scheme NOT NULL ); CREATE TABLE project ( diff --git a/database/migrations/000004_jobs.up.sql b/database/migrations/000004_jobs.up.sql index d2fd0ecdd94cb..436847e55d1fa 100644 --- a/database/migrations/000004_jobs.up.sql +++ b/database/migrations/000004_jobs.up.sql @@ -58,6 +58,7 @@ CREATE TABLE IF NOT EXISTS provisioner_job_log ( CREATE TYPE parameter_scope AS ENUM ( 'organization', 'project', + 'import_job', 'user', 'workspace' ); @@ -71,22 +72,6 @@ CREATE TYPE parameter_source_scheme AS ENUM('none', 'data'); -- Supported schemes for a parameter destination. CREATE TYPE parameter_destination_scheme AS ENUM('none', 'environment_variable', 'provisioner_variable'); --- Parameters are provided to jobs for provisioning and to workspaces. -CREATE TABLE parameter_value ( - id uuid NOT NULL UNIQUE, - name varchar(64) NOT NULL, - created_at timestamptz NOT NULL, - updated_at timestamptz NOT NULL, - scope parameter_scope NOT NULL, - scope_id text NOT NULL, - source_scheme parameter_source_scheme NOT NULL, - source_value text NOT NULL, - destination_scheme parameter_destination_scheme NOT NULL, - destination_value text NOT NULL, - -- Prevents duplicates for parameters in the same scope. - UNIQUE(name, scope, scope_id) -); - -- Stores project version parameters parsed on import. -- No secrets are stored here. -- @@ -103,12 +88,10 @@ CREATE TABLE parameter_schema ( name varchar(64) NOT NULL, description varchar(8192) NOT NULL DEFAULT '', default_source_scheme parameter_source_scheme, - default_source_value text, + default_source_value text NOT NULL, -- Allows the user to override the source. allow_override_source boolean NOT null, - -- eg. env://SOME_VARIABLE, tfvars://example default_destination_scheme parameter_destination_scheme, - default_destination_value text, -- Allows the user to override the destination. allow_override_destination boolean NOT null, default_refresh text NOT NULL, @@ -121,3 +104,18 @@ CREATE TABLE parameter_schema ( validation_value_type varchar(64) NOT NULL, UNIQUE(job_id, name) ); + +-- Parameters are provided to jobs for provisioning and to workspaces. +CREATE TABLE parameter_value ( + id uuid NOT NULL UNIQUE, + name varchar(64) NOT NULL, + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL, + scope parameter_scope NOT NULL, + scope_id text NOT NULL, + source_scheme parameter_source_scheme NOT NULL, + source_value text NOT NULL, + destination_scheme parameter_destination_scheme NOT NULL, + -- Prevents duplicates for parameters in the same scope. + UNIQUE(name, scope, scope_id) +); diff --git a/database/models.go b/database/models.go index 04500cffd0005..ba4b57bbf0ed9 100644 --- a/database/models.go +++ b/database/models.go @@ -97,6 +97,7 @@ type ParameterScope string const ( ParameterScopeOrganization ParameterScope = "organization" ParameterScopeProject ParameterScope = "project" + ParameterScopeImportJob ParameterScope = "import_job" ParameterScopeUser ParameterScope = "user" ParameterScopeWorkspace ParameterScope = "workspace" ) @@ -307,10 +308,9 @@ type ParameterSchema struct { Name string `db:"name" json:"name"` Description string `db:"description" json:"description"` DefaultSourceScheme ParameterSourceScheme `db:"default_source_scheme" json:"default_source_scheme"` - DefaultSourceValue sql.NullString `db:"default_source_value" json:"default_source_value"` + DefaultSourceValue string `db:"default_source_value" json:"default_source_value"` AllowOverrideSource bool `db:"allow_override_source" json:"allow_override_source"` DefaultDestinationScheme ParameterDestinationScheme `db:"default_destination_scheme" json:"default_destination_scheme"` - DefaultDestinationValue sql.NullString `db:"default_destination_value" json:"default_destination_value"` AllowOverrideDestination bool `db:"allow_override_destination" json:"allow_override_destination"` DefaultRefresh string `db:"default_refresh" json:"default_refresh"` RedisplayValue bool `db:"redisplay_value" json:"redisplay_value"` @@ -330,7 +330,6 @@ type ParameterValue struct { SourceScheme ParameterSourceScheme `db:"source_scheme" json:"source_scheme"` SourceValue string `db:"source_value" json:"source_value"` DestinationScheme ParameterDestinationScheme `db:"destination_scheme" json:"destination_scheme"` - DestinationValue string `db:"destination_value" json:"destination_value"` } type Project struct { diff --git a/database/query.sql b/database/query.sql index 26abf1893f36a..0330df32aba12 100644 --- a/database/query.sql +++ b/database/query.sql @@ -408,11 +408,10 @@ INSERT INTO scope_id, source_scheme, source_value, - destination_scheme, - destination_value + destination_scheme ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; -- name: InsertProject :one INSERT INTO @@ -454,7 +453,6 @@ INSERT INTO default_source_value, allow_override_source, default_destination_scheme, - default_destination_value, allow_override_destination, default_refresh, redisplay_value, @@ -480,8 +478,7 @@ VALUES $13, $14, $15, - $16, - $17 + $16 ) RETURNING *; -- name: InsertProvisionerDaemon :one diff --git a/database/query.sql.go b/database/query.sql.go index 8b63ca467b94b..ed3bd76e7649c 100644 --- a/database/query.sql.go +++ b/database/query.sql.go @@ -271,7 +271,7 @@ func (q *sqlQuerier) GetOrganizationsByUserID(ctx context.Context, userID string const getParameterSchemasByJobID = `-- name: GetParameterSchemasByJobID :many SELECT - id, created_at, job_id, name, description, default_source_scheme, default_source_value, allow_override_source, default_destination_scheme, default_destination_value, allow_override_destination, default_refresh, redisplay_value, validation_error, validation_condition, validation_type_system, validation_value_type + id, created_at, job_id, name, description, default_source_scheme, default_source_value, allow_override_source, default_destination_scheme, allow_override_destination, default_refresh, redisplay_value, validation_error, validation_condition, validation_type_system, validation_value_type FROM parameter_schema WHERE @@ -297,7 +297,6 @@ func (q *sqlQuerier) GetParameterSchemasByJobID(ctx context.Context, jobID uuid. &i.DefaultSourceValue, &i.AllowOverrideSource, &i.DefaultDestinationScheme, - &i.DefaultDestinationValue, &i.AllowOverrideDestination, &i.DefaultRefresh, &i.RedisplayValue, @@ -321,7 +320,7 @@ func (q *sqlQuerier) GetParameterSchemasByJobID(ctx context.Context, jobID uuid. const getParameterValuesByScope = `-- name: GetParameterValuesByScope :many SELECT - id, name, created_at, updated_at, scope, scope_id, source_scheme, source_value, destination_scheme, destination_value + id, name, created_at, updated_at, scope, scope_id, source_scheme, source_value, destination_scheme FROM parameter_value WHERE @@ -353,7 +352,6 @@ func (q *sqlQuerier) GetParameterValuesByScope(ctx context.Context, arg GetParam &i.SourceScheme, &i.SourceValue, &i.DestinationScheme, - &i.DestinationValue, ); err != nil { return nil, err } @@ -1378,7 +1376,6 @@ INSERT INTO default_source_value, allow_override_source, default_destination_scheme, - default_destination_value, allow_override_destination, default_refresh, redisplay_value, @@ -1404,9 +1401,8 @@ VALUES $13, $14, $15, - $16, - $17 - ) RETURNING id, created_at, job_id, name, description, default_source_scheme, default_source_value, allow_override_source, default_destination_scheme, default_destination_value, allow_override_destination, default_refresh, redisplay_value, validation_error, validation_condition, validation_type_system, validation_value_type + $16 + ) RETURNING id, created_at, job_id, name, description, default_source_scheme, default_source_value, allow_override_source, default_destination_scheme, allow_override_destination, default_refresh, redisplay_value, validation_error, validation_condition, validation_type_system, validation_value_type ` type InsertParameterSchemaParams struct { @@ -1416,10 +1412,9 @@ type InsertParameterSchemaParams struct { Name string `db:"name" json:"name"` Description string `db:"description" json:"description"` DefaultSourceScheme ParameterSourceScheme `db:"default_source_scheme" json:"default_source_scheme"` - DefaultSourceValue sql.NullString `db:"default_source_value" json:"default_source_value"` + DefaultSourceValue string `db:"default_source_value" json:"default_source_value"` AllowOverrideSource bool `db:"allow_override_source" json:"allow_override_source"` DefaultDestinationScheme ParameterDestinationScheme `db:"default_destination_scheme" json:"default_destination_scheme"` - DefaultDestinationValue sql.NullString `db:"default_destination_value" json:"default_destination_value"` AllowOverrideDestination bool `db:"allow_override_destination" json:"allow_override_destination"` DefaultRefresh string `db:"default_refresh" json:"default_refresh"` RedisplayValue bool `db:"redisplay_value" json:"redisplay_value"` @@ -1440,7 +1435,6 @@ func (q *sqlQuerier) InsertParameterSchema(ctx context.Context, arg InsertParame arg.DefaultSourceValue, arg.AllowOverrideSource, arg.DefaultDestinationScheme, - arg.DefaultDestinationValue, arg.AllowOverrideDestination, arg.DefaultRefresh, arg.RedisplayValue, @@ -1460,7 +1454,6 @@ func (q *sqlQuerier) InsertParameterSchema(ctx context.Context, arg InsertParame &i.DefaultSourceValue, &i.AllowOverrideSource, &i.DefaultDestinationScheme, - &i.DefaultDestinationValue, &i.AllowOverrideDestination, &i.DefaultRefresh, &i.RedisplayValue, @@ -1483,11 +1476,10 @@ INSERT INTO scope_id, source_scheme, source_value, - destination_scheme, - destination_value + destination_scheme ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, name, created_at, updated_at, scope, scope_id, source_scheme, source_value, destination_scheme, destination_value + ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, name, created_at, updated_at, scope, scope_id, source_scheme, source_value, destination_scheme ` type InsertParameterValueParams struct { @@ -1500,7 +1492,6 @@ type InsertParameterValueParams struct { SourceScheme ParameterSourceScheme `db:"source_scheme" json:"source_scheme"` SourceValue string `db:"source_value" json:"source_value"` DestinationScheme ParameterDestinationScheme `db:"destination_scheme" json:"destination_scheme"` - DestinationValue string `db:"destination_value" json:"destination_value"` } func (q *sqlQuerier) InsertParameterValue(ctx context.Context, arg InsertParameterValueParams) (ParameterValue, error) { @@ -1514,7 +1505,6 @@ func (q *sqlQuerier) InsertParameterValue(ctx context.Context, arg InsertParamet arg.SourceScheme, arg.SourceValue, arg.DestinationScheme, - arg.DestinationValue, ) var i ParameterValue err := row.Scan( @@ -1527,7 +1517,6 @@ func (q *sqlQuerier) InsertParameterValue(ctx context.Context, arg InsertParamet &i.SourceScheme, &i.SourceValue, &i.DestinationScheme, - &i.DestinationValue, ) return i, err } diff --git a/go.mod b/go.mod index ffd3165824c4a..f84913c114df6 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/spf13/cobra v1.3.0 github.com/stretchr/testify v1.7.0 github.com/unrolled/secure v1.0.9 + github.com/xlab/treeprint v1.1.0 go.uber.org/atomic v1.9.0 go.uber.org/goleak v1.1.12 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 diff --git a/go.sum b/go.sum index d14ed9f0057d8..f39b53bdea85f 100644 --- a/go.sum +++ b/go.sum @@ -1240,6 +1240,8 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= +github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/provisioner/terraform/parse.go b/provisioner/terraform/parse.go index c0ab765c1bf67..24f4943c5bff5 100644 --- a/provisioner/terraform/parse.go +++ b/provisioner/terraform/parse.go @@ -44,7 +44,7 @@ func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSc schema := &proto.ParameterSchema{ Name: variable.Name, Description: variable.Description, - RedisplayValue: variable.Sensitive, + RedisplayValue: !variable.Sensitive, ValidationValueType: variable.Type, } diff --git a/provisioner/terraform/parse_test.go b/provisioner/terraform/parse_test.go index f1221de20e705..2a4ce6a8b2c8e 100644 --- a/provisioner/terraform/parse_test.go +++ b/provisioner/terraform/parse_test.go @@ -52,8 +52,9 @@ func TestParse(t *testing.T) { Type: &proto.Parse_Response_Complete{ Complete: &proto.Parse_Complete{ ParameterSchemas: []*proto.ParameterSchema{{ - Name: "A", - Description: "Testing!", + Name: "A", + RedisplayValue: true, + Description: "Testing!", }}, }, }, @@ -69,7 +70,8 @@ func TestParse(t *testing.T) { Type: &proto.Parse_Response_Complete{ Complete: &proto.Parse_Complete{ ParameterSchemas: []*proto.ParameterSchema{{ - Name: "A", + Name: "A", + RedisplayValue: true, DefaultSource: &proto.ParameterSource{ Scheme: proto.ParameterSource_DATA, Value: "\"wow\"", @@ -96,6 +98,7 @@ func TestParse(t *testing.T) { Complete: &proto.Parse_Complete{ ParameterSchemas: []*proto.ParameterSchema{{ Name: "A", + RedisplayValue: true, ValidationCondition: `var.A == "value"`, ValidationTypeSystem: proto.ParameterSchema_HCL, }, From a6ce22db9c657b99322e9f3de348fef1909ff231 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 20:43:13 +0000 Subject: [PATCH 04/20] Refactor provisionerd to dynamically update parameter schemas --- coderd/provisionerdaemons.go | 193 +++++------ coderd/provisionerjobs.go | 16 +- coderd/provisionerjobs_test.go | 6 +- provisionerd/proto/provisionerd.pb.go | 372 +++++++++++---------- provisionerd/proto/provisionerd.proto | 25 +- provisionerd/proto/provisionerd_drpc.pb.go | 70 +--- provisionerd/provisionerd.go | 99 +++--- provisionerd/provisionerd_test.go | 48 +-- 8 files changed, 390 insertions(+), 439 deletions(-) diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 1103dec88966c..f84da2ebcb8b8 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -113,9 +113,6 @@ type workspaceProvisionJob struct { type projectVersionImportJob struct { OrganizationID string `json:"organization_id"` ProjectID uuid.UUID `json:"project_id"` - - SkipParameterSchemas bool `json:"skip_parameter_schemas"` - SkipResources bool `json:"skip_resources"` } // Implementation of the provisioner daemon protobuf server. @@ -276,9 +273,7 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty protoJob.Type = &proto.AcquiredJob_ProjectImport_{ ProjectImport: &proto.AcquiredJob_ProjectImport{ - ParameterValues: protoParameters, - SkipParameterSchemas: input.SkipParameterSchemas, - SkipResources: input.SkipResources, + ParameterValues: protoParameters, }, } } @@ -324,35 +319,89 @@ func (server *provisionerdServer) UpdateJob(stream proto.DRPCProvisionerDaemon_U if err != nil { return xerrors.Errorf("update job: %w", err) } - insertParams := database.InsertProvisionerJobLogsParams{ - JobID: parsedID, - } - for _, log := range update.Logs { - logLevel, err := convertLogLevel(log.Level) + if len(update.Logs) > 0 { + insertParams := database.InsertProvisionerJobLogsParams{ + JobID: parsedID, + } + for _, log := range update.Logs { + logLevel, err := convertLogLevel(log.Level) + if err != nil { + return xerrors.Errorf("convert log level: %w", err) + } + logSource, err := convertLogSource(log.Source) + if err != nil { + return xerrors.Errorf("convert log source: %w", err) + } + insertParams.ID = append(insertParams.ID, uuid.New()) + insertParams.CreatedAt = append(insertParams.CreatedAt, time.UnixMilli(log.CreatedAt)) + insertParams.Level = append(insertParams.Level, logLevel) + insertParams.Source = append(insertParams.Source, logSource) + insertParams.Output = append(insertParams.Output, log.Output) + } + logs, err := server.Database.InsertProvisionerJobLogs(context.Background(), insertParams) if err != nil { - return xerrors.Errorf("convert log level: %w", err) + return xerrors.Errorf("insert job logs: %w", err) } - logSource, err := convertLogSource(log.Source) + data, err := json.Marshal(logs) if err != nil { - return xerrors.Errorf("convert log source: %w", err) + return xerrors.Errorf("marshal job log: %w", err) + } + err = server.Pubsub.Publish(provisionerJobLogsChannel(parsedID), data) + if err != nil { + return xerrors.Errorf("publish job log: %w", err) } - insertParams.ID = append(insertParams.ID, uuid.New()) - insertParams.CreatedAt = append(insertParams.CreatedAt, time.UnixMilli(log.CreatedAt)) - insertParams.Level = append(insertParams.Level, logLevel) - insertParams.Source = append(insertParams.Source, logSource) - insertParams.Output = append(insertParams.Output, log.Output) - } - logs, err := server.Database.InsertProvisionerJobLogs(context.Background(), insertParams) - if err != nil { - return xerrors.Errorf("insert job logs: %w", err) - } - data, err := json.Marshal(logs) - if err != nil { - return xerrors.Errorf("marshal job log: %w", err) } - err = server.Pubsub.Publish(provisionerJobLogsChannel(parsedID), data) - if err != nil { - return xerrors.Errorf("publish job log: %w", err) + + if update.GetProjectImport() != nil { + // Validate that all parameters send from the provisioner daemon + // follow the protocol. + parameterSchemas := make([]database.InsertParameterSchemaParams, 0, len(update.GetProjectImport().ParameterSchemas)) + for _, protoParameter := range update.GetProjectImport().ParameterSchemas { + validationTypeSystem, err := convertValidationTypeSystem(protoParameter.ValidationTypeSystem) + if err != nil { + return xerrors.Errorf("convert validation type system for %q: %w", protoParameter.Name, err) + } + + parameterSchema := database.InsertParameterSchemaParams{ + ID: uuid.New(), + CreatedAt: database.Now(), + JobID: job.ID, + Name: protoParameter.Name, + Description: protoParameter.Description, + RedisplayValue: protoParameter.RedisplayValue, + ValidationError: protoParameter.ValidationError, + ValidationCondition: protoParameter.ValidationCondition, + ValidationValueType: protoParameter.ValidationValueType, + ValidationTypeSystem: validationTypeSystem, + + DefaultSourceScheme: database.ParameterSourceSchemeNone, + DefaultDestinationScheme: database.ParameterDestinationSchemeNone, + + AllowOverrideDestination: protoParameter.AllowOverrideDestination, + AllowOverrideSource: protoParameter.AllowOverrideSource, + } + + // It's possible a parameter doesn't define a default source! + if protoParameter.DefaultSource != nil { + parameterSourceScheme, err := convertParameterSourceScheme(protoParameter.DefaultSource.Scheme) + if err != nil { + return xerrors.Errorf("convert parameter source scheme: %w", err) + } + parameterSchema.DefaultSourceScheme = parameterSourceScheme + parameterSchema.DefaultSourceValue = protoParameter.DefaultSource.Value + } + + // It's possible a parameter doesn't define a default destination! + if protoParameter.DefaultDestination != nil { + parameterDestinationScheme, err := convertParameterDestinationScheme(protoParameter.DefaultDestination.Scheme) + if err != nil { + return xerrors.Errorf("convert parameter destination scheme: %w", err) + } + parameterSchema.DefaultDestinationScheme = parameterDestinationScheme + } + + parameterSchemas = append(parameterSchemas, parameterSchema) + } } } } @@ -412,80 +461,18 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr return nil, xerrors.Errorf("unmarshal job data: %w", err) } - // Validate that all parameters send from the provisioner daemon - // follow the protocol. - parameterSchemas := make([]database.InsertParameterSchemaParams, 0, len(jobType.ProjectImport.ParameterSchemas)) - for _, protoParameter := range jobType.ProjectImport.ParameterSchemas { - validationTypeSystem, err := convertValidationTypeSystem(protoParameter.ValidationTypeSystem) - if err != nil { - return nil, xerrors.Errorf("convert validation type system for %q: %w", protoParameter.Name, err) - } - - parameterSchema := database.InsertParameterSchemaParams{ - ID: uuid.New(), - CreatedAt: database.Now(), - JobID: job.ID, - Name: protoParameter.Name, - Description: protoParameter.Description, - RedisplayValue: protoParameter.RedisplayValue, - ValidationError: protoParameter.ValidationError, - ValidationCondition: protoParameter.ValidationCondition, - ValidationValueType: protoParameter.ValidationValueType, - ValidationTypeSystem: validationTypeSystem, - - DefaultSourceScheme: database.ParameterSourceSchemeNone, - DefaultDestinationScheme: database.ParameterDestinationSchemeNone, - - AllowOverrideDestination: protoParameter.AllowOverrideDestination, - AllowOverrideSource: protoParameter.AllowOverrideSource, - } - - // It's possible a parameter doesn't define a default source! - if protoParameter.DefaultSource != nil { - parameterSourceScheme, err := convertParameterSourceScheme(protoParameter.DefaultSource.Scheme) - if err != nil { - return nil, xerrors.Errorf("convert parameter source scheme: %w", err) - } - parameterSchema.DefaultSourceScheme = parameterSourceScheme - parameterSchema.DefaultSourceValue = protoParameter.DefaultSource.Value - } - - // It's possible a parameter doesn't define a default destination! - if protoParameter.DefaultDestination != nil { - parameterDestinationScheme, err := convertParameterDestinationScheme(protoParameter.DefaultDestination.Scheme) - if err != nil { - return nil, xerrors.Errorf("convert parameter destination scheme: %w", err) - } - parameterSchema.DefaultDestinationScheme = parameterDestinationScheme - } - - parameterSchemas = append(parameterSchemas, parameterSchema) - } - - // This must occur in a transaction in case of failure. - err = server.Database.InTx(func(db database.Store) error { - err = db.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ - ID: jobID, - UpdatedAt: database.Now(), - CompletedAt: sql.NullTime{ - Time: database.Now(), - Valid: true, - }, - }) - if err != nil { - return xerrors.Errorf("update provisioner job: %w", err) - } - // This could be a bulk-insert operation to improve performance. - // See the "InsertWorkspaceHistoryLogs" query. - for _, parameterSchema := range parameterSchemas { - _, err = db.InsertParameterSchema(ctx, parameterSchema) - if err != nil { - return xerrors.Errorf("insert parameter schema %q: %w", parameterSchema.Name, err) - } - } - server.Logger.Debug(ctx, "marked import job as completed", slog.F("job_id", jobID)) - return nil + err = server.Database.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ + ID: jobID, + UpdatedAt: database.Now(), + CompletedAt: sql.NullTime{ + Time: database.Now(), + Valid: true, + }, }) + if err != nil { + return nil, xerrors.Errorf("update provisioner job: %w", err) + } + server.Logger.Debug(ctx, "marked import job as completed", slog.F("job_id", jobID)) if err != nil { return nil, xerrors.Errorf("complete job: %w", err) } diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 09e38880ae172..d5a446e0d57ad 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -46,13 +46,10 @@ type ProvisionerJob struct { } type CreateProjectImportJobRequest struct { - StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"` - StorageSource string `json:"storage_source" validate:"required"` - Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"` - - ParameterValues []CreateParameterValueRequest `json:"parameter_values"` - SkipParameterSchemas bool `json:"skip_parameter_schemas"` - SkipResources bool `json:"skip_resources"` + StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"` + StorageSource string `json:"storage_source" validate:"required"` + Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"` + ParameterValues []CreateParameterValueRequest `json:"parameter_values"` } func (*api) provisionerJobByOrganization(rw http.ResponseWriter, r *http.Request) { @@ -84,10 +81,7 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r } input, err := json.Marshal(projectVersionImportJob{ - // AdditionalParameters: req.AdditionalParameters, - OrganizationID: organization.ID, - SkipParameterSchemas: req.SkipParameterSchemas, - SkipResources: req.SkipResources, + OrganizationID: organization.ID, }) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index 0ea1e1397a7aa..89f90dbf0c07e 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -2,6 +2,7 @@ package coderd_test import ( "context" + "fmt" "net/http" "testing" "time" @@ -70,6 +71,7 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { }, }, }}, + Provision: echo.ProvisionComplete, }) require.NoError(t, err) file, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data) @@ -84,10 +86,10 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { SourceScheme: database.ParameterSourceSchemeData, DestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, }}, - SkipResources: true, }) require.NoError(t, err) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + job = coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + fmt.Printf("Job %+v\n", job) values, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) require.NoError(t, err) require.Equal(t, "somevalue", values[0].SourceValue) diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index d1cd2548b19fb..0322a2f163b05 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -504,20 +504,19 @@ func (x *Log) GetOutput() string { return "" } -// JobUpdate represents an update to a job. -// There may be no log output, but this message -// should still be sent periodically as a heartbeat. -type JobUpdate struct { +// This message should be sent periodically as a heartbeat. +type UpdateJobRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` - Logs []*Log `protobuf:"bytes,2,rep,name=logs,proto3" json:"logs,omitempty"` + JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Logs []*Log `protobuf:"bytes,2,rep,name=logs,proto3" json:"logs,omitempty"` + ParameterSchemas []*proto.ParameterSchema `protobuf:"bytes,3,rep,name=parameter_schemas,json=parameterSchemas,proto3" json:"parameter_schemas,omitempty"` } -func (x *JobUpdate) Reset() { - *x = JobUpdate{} +func (x *UpdateJobRequest) Reset() { + *x = UpdateJobRequest{} if protoimpl.UnsafeEnabled { mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -525,13 +524,13 @@ func (x *JobUpdate) Reset() { } } -func (x *JobUpdate) String() string { +func (x *UpdateJobRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*JobUpdate) ProtoMessage() {} +func (*UpdateJobRequest) ProtoMessage() {} -func (x *JobUpdate) ProtoReflect() protoreflect.Message { +func (x *UpdateJobRequest) ProtoReflect() protoreflect.Message { mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -543,25 +542,81 @@ func (x *JobUpdate) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use JobUpdate.ProtoReflect.Descriptor instead. -func (*JobUpdate) Descriptor() ([]byte, []int) { +// Deprecated: Use UpdateJobRequest.ProtoReflect.Descriptor instead. +func (*UpdateJobRequest) Descriptor() ([]byte, []int) { return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{6} } -func (x *JobUpdate) GetJobId() string { +func (x *UpdateJobRequest) GetJobId() string { if x != nil { return x.JobId } return "" } -func (x *JobUpdate) GetLogs() []*Log { +func (x *UpdateJobRequest) GetLogs() []*Log { if x != nil { return x.Logs } return nil } +func (x *UpdateJobRequest) GetParameterSchemas() []*proto.ParameterSchema { + if x != nil { + return x.ParameterSchemas + } + return nil +} + +type UpdateJobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // If parameter schemas are sent, the job will respond + // with resolved parameter values. + ParameterValues []*proto.ParameterValue `protobuf:"bytes,1,rep,name=parameter_values,json=parameterValues,proto3" json:"parameter_values,omitempty"` +} + +func (x *UpdateJobResponse) Reset() { + *x = UpdateJobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateJobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateJobResponse) ProtoMessage() {} + +func (x *UpdateJobResponse) ProtoReflect() protoreflect.Message { + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + 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 UpdateJobResponse.ProtoReflect.Descriptor instead. +func (*UpdateJobResponse) Descriptor() ([]byte, []int) { + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{7} +} + +func (x *UpdateJobResponse) GetParameterValues() []*proto.ParameterValue { + if x != nil { + return x.ParameterValues + } + return nil +} + type AcquiredJob_WorkspaceProvision struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -576,7 +631,7 @@ type AcquiredJob_WorkspaceProvision struct { func (x *AcquiredJob_WorkspaceProvision) Reset() { *x = AcquiredJob_WorkspaceProvision{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -589,7 +644,7 @@ func (x *AcquiredJob_WorkspaceProvision) String() string { func (*AcquiredJob_WorkspaceProvision) ProtoMessage() {} func (x *AcquiredJob_WorkspaceProvision) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -637,16 +692,12 @@ type AcquiredJob_ProjectImport struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - ParameterValues []*proto.ParameterValue `protobuf:"bytes,1,rep,name=parameter_values,json=parameterValues,proto3" json:"parameter_values,omitempty"` - SkipParameterSchemas bool `protobuf:"varint,2,opt,name=skip_parameter_schemas,json=skipParameterSchemas,proto3" json:"skip_parameter_schemas,omitempty"` - SkipResources bool `protobuf:"varint,3,opt,name=skip_resources,json=skipResources,proto3" json:"skip_resources,omitempty"` } func (x *AcquiredJob_ProjectImport) Reset() { *x = AcquiredJob_ProjectImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -659,7 +710,7 @@ func (x *AcquiredJob_ProjectImport) String() string { func (*AcquiredJob_ProjectImport) ProtoMessage() {} func (x *AcquiredJob_ProjectImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -675,27 +726,6 @@ func (*AcquiredJob_ProjectImport) Descriptor() ([]byte, []int) { return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{1, 1} } -func (x *AcquiredJob_ProjectImport) GetParameterValues() []*proto.ParameterValue { - if x != nil { - return x.ParameterValues - } - return nil -} - -func (x *AcquiredJob_ProjectImport) GetSkipParameterSchemas() bool { - if x != nil { - return x.SkipParameterSchemas - } - return false -} - -func (x *AcquiredJob_ProjectImport) GetSkipResources() bool { - if x != nil { - return x.SkipResources - } - return false -} - type CompletedJob_WorkspaceProvision struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -708,7 +738,7 @@ type CompletedJob_WorkspaceProvision struct { func (x *CompletedJob_WorkspaceProvision) Reset() { *x = CompletedJob_WorkspaceProvision{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -721,7 +751,7 @@ func (x *CompletedJob_WorkspaceProvision) String() string { func (*CompletedJob_WorkspaceProvision) ProtoMessage() {} func (x *CompletedJob_WorkspaceProvision) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -756,15 +786,14 @@ type CompletedJob_ProjectImport struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ParameterSchemas []*proto.ParameterSchema `protobuf:"bytes,1,rep,name=parameter_schemas,json=parameterSchemas,proto3" json:"parameter_schemas,omitempty"` - StartResources []*proto.Resource `protobuf:"bytes,2,rep,name=start_resources,json=startResources,proto3" json:"start_resources,omitempty"` - StopResources []*proto.Resource `protobuf:"bytes,3,rep,name=stop_resources,json=stopResources,proto3" json:"stop_resources,omitempty"` + StartResources []*proto.Resource `protobuf:"bytes,1,rep,name=start_resources,json=startResources,proto3" json:"start_resources,omitempty"` + StopResources []*proto.Resource `protobuf:"bytes,2,rep,name=stop_resources,json=stopResources,proto3" json:"stop_resources,omitempty"` } func (x *CompletedJob_ProjectImport) Reset() { *x = CompletedJob_ProjectImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -777,7 +806,7 @@ func (x *CompletedJob_ProjectImport) String() string { func (*CompletedJob_ProjectImport) ProtoMessage() {} func (x *CompletedJob_ProjectImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -793,13 +822,6 @@ func (*CompletedJob_ProjectImport) Descriptor() ([]byte, []int) { return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{4, 1} } -func (x *CompletedJob_ProjectImport) GetParameterSchemas() []*proto.ParameterSchema { - if x != nil { - return x.ParameterSchemas - } - return nil -} - func (x *CompletedJob_ProjectImport) GetStartResources() []*proto.Resource { if x != nil { return x.StartResources @@ -823,7 +845,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x1a, 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, 0x22, 0x07, 0x0a, - 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xf8, 0x05, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69, + 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0xd2, 0x04, 0x0a, 0x0b, 0x41, 0x63, 0x71, 0x75, 0x69, 0x72, 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, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, @@ -859,91 +881,88 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x1a, 0xb4, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, - 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x6b, - 0x69, 0x70, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b, 0x69, 0x70, + 0x74, 0x65, 0x1a, 0x0f, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3b, 0x0a, 0x0c, 0x43, + 0x61, 0x6e, 0x63, 0x65, 0x6c, 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, 0x22, 0x71, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 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, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x5f, 0x6f, + 0x6e, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x65, + 0x73, 0x74, 0x72, 0x6f, 0x79, 0x4f, 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x22, 0xd3, 0x03, 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, 0x60, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 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, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x48, + 0x00, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, + 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 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, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x5f, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 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, 0x8d, 0x01, 0x0a, 0x0d, 0x50, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 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, 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, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3b, 0x0a, 0x0c, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 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, 0x22, 0x71, - 0x0a, 0x14, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 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, - 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x65, 0x73, - 0x74, 0x72, 0x6f, 0x79, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0d, 0x64, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x4f, 0x6e, 0x53, 0x74, 0x6f, - 0x70, 0x22, 0x9e, 0x04, 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, 0x60, 0x0a, 0x13, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x0e, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x28, 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, - 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, - 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x5f, - 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 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, - 0xd8, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, + 0x65, 0x22, 0x9a, 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, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9b, + 0x01, 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, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x12, 0x3e, 0x0a, 0x0f, - 0x73, 0x74, 0x61, 0x72, 0x74, 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, 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, 0x03, - 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, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x9a, 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, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, - 0x49, 0x0a, 0x09, 0x4a, 0x6f, 0x62, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 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, 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, 0x8c, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 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, 0x12, 0x3b, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, - 0x62, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, - 0x2e, 0x4a, 0x6f, 0x62, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, - 0x01, 0x12, 0x3c, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x1a, + 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x22, 0x5b, 0x0a, 0x11, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 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, + 0x9d, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, + 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 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, 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, 0x3c, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 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, @@ -970,7 +989,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, 11) +var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (LogSource)(0), // 0: provisionerd.LogSource (*Empty)(nil), // 1: provisionerd.Empty @@ -979,37 +998,38 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (*TransitionedResource)(nil), // 4: provisionerd.TransitionedResource (*CompletedJob)(nil), // 5: provisionerd.CompletedJob (*Log)(nil), // 6: provisionerd.Log - (*JobUpdate)(nil), // 7: provisionerd.JobUpdate - (*AcquiredJob_WorkspaceProvision)(nil), // 8: provisionerd.AcquiredJob.WorkspaceProvision - (*AcquiredJob_ProjectImport)(nil), // 9: provisionerd.AcquiredJob.ProjectImport - (*CompletedJob_WorkspaceProvision)(nil), // 10: provisionerd.CompletedJob.WorkspaceProvision - (*CompletedJob_ProjectImport)(nil), // 11: provisionerd.CompletedJob.ProjectImport - (*proto.Resource)(nil), // 12: provisioner.Resource - (proto.LogLevel)(0), // 13: provisioner.LogLevel - (*proto.ParameterValue)(nil), // 14: provisioner.ParameterValue + (*UpdateJobRequest)(nil), // 7: provisionerd.UpdateJobRequest + (*UpdateJobResponse)(nil), // 8: provisionerd.UpdateJobResponse + (*AcquiredJob_WorkspaceProvision)(nil), // 9: provisionerd.AcquiredJob.WorkspaceProvision + (*AcquiredJob_ProjectImport)(nil), // 10: provisionerd.AcquiredJob.ProjectImport + (*CompletedJob_WorkspaceProvision)(nil), // 11: provisionerd.CompletedJob.WorkspaceProvision + (*CompletedJob_ProjectImport)(nil), // 12: provisionerd.CompletedJob.ProjectImport + (*proto.Resource)(nil), // 13: provisioner.Resource + (proto.LogLevel)(0), // 14: provisioner.LogLevel (*proto.ParameterSchema)(nil), // 15: provisioner.ParameterSchema + (*proto.ParameterValue)(nil), // 16: provisioner.ParameterValue } var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{ - 8, // 0: provisionerd.AcquiredJob.workspace_provision:type_name -> provisionerd.AcquiredJob.WorkspaceProvision - 9, // 1: provisionerd.AcquiredJob.project_import:type_name -> provisionerd.AcquiredJob.ProjectImport - 12, // 2: provisionerd.TransitionedResource.resource:type_name -> provisioner.Resource - 10, // 3: provisionerd.CompletedJob.workspace_provision:type_name -> provisionerd.CompletedJob.WorkspaceProvision - 11, // 4: provisionerd.CompletedJob.project_import:type_name -> provisionerd.CompletedJob.ProjectImport + 9, // 0: provisionerd.AcquiredJob.workspace_provision:type_name -> provisionerd.AcquiredJob.WorkspaceProvision + 10, // 1: provisionerd.AcquiredJob.project_import:type_name -> provisionerd.AcquiredJob.ProjectImport + 13, // 2: provisionerd.TransitionedResource.resource:type_name -> provisioner.Resource + 11, // 3: provisionerd.CompletedJob.workspace_provision:type_name -> provisionerd.CompletedJob.WorkspaceProvision + 12, // 4: provisionerd.CompletedJob.project_import:type_name -> provisionerd.CompletedJob.ProjectImport 0, // 5: provisionerd.Log.source:type_name -> provisionerd.LogSource - 13, // 6: provisionerd.Log.level:type_name -> provisioner.LogLevel - 6, // 7: provisionerd.JobUpdate.logs:type_name -> provisionerd.Log - 14, // 8: provisionerd.AcquiredJob.WorkspaceProvision.parameter_values:type_name -> provisioner.ParameterValue - 14, // 9: provisionerd.AcquiredJob.ProjectImport.parameter_values:type_name -> provisioner.ParameterValue - 12, // 10: provisionerd.CompletedJob.WorkspaceProvision.resources:type_name -> provisioner.Resource - 15, // 11: provisionerd.CompletedJob.ProjectImport.parameter_schemas:type_name -> provisioner.ParameterSchema - 12, // 12: provisionerd.CompletedJob.ProjectImport.start_resources:type_name -> provisioner.Resource - 12, // 13: provisionerd.CompletedJob.ProjectImport.stop_resources:type_name -> provisioner.Resource + 14, // 6: provisionerd.Log.level:type_name -> provisioner.LogLevel + 6, // 7: provisionerd.UpdateJobRequest.logs:type_name -> provisionerd.Log + 15, // 8: provisionerd.UpdateJobRequest.parameter_schemas:type_name -> provisioner.ParameterSchema + 16, // 9: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue + 16, // 10: provisionerd.AcquiredJob.WorkspaceProvision.parameter_values:type_name -> provisioner.ParameterValue + 13, // 11: provisionerd.CompletedJob.WorkspaceProvision.resources:type_name -> provisioner.Resource + 13, // 12: provisionerd.CompletedJob.ProjectImport.start_resources:type_name -> provisioner.Resource + 13, // 13: provisionerd.CompletedJob.ProjectImport.stop_resources:type_name -> provisioner.Resource 1, // 14: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty - 7, // 15: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.JobUpdate + 7, // 15: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest 3, // 16: provisionerd.ProvisionerDaemon.CancelJob:input_type -> provisionerd.CancelledJob 5, // 17: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob 2, // 18: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob - 1, // 19: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.Empty + 8, // 19: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse 1, // 20: provisionerd.ProvisionerDaemon.CancelJob:output_type -> provisionerd.Empty 1, // 21: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty 18, // [18:22] is the sub-list for method output_type @@ -1098,7 +1118,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JobUpdate); i { + switch v := v.(*UpdateJobRequest); i { case 0: return &v.state case 1: @@ -1110,7 +1130,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AcquiredJob_WorkspaceProvision); i { + switch v := v.(*UpdateJobResponse); i { case 0: return &v.state case 1: @@ -1122,7 +1142,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AcquiredJob_ProjectImport); i { + switch v := v.(*AcquiredJob_WorkspaceProvision); i { case 0: return &v.state case 1: @@ -1134,7 +1154,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CompletedJob_WorkspaceProvision); i { + switch v := v.(*AcquiredJob_ProjectImport); i { case 0: return &v.state case 1: @@ -1146,6 +1166,18 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompletedJob_WorkspaceProvision); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionerd_proto_provisionerd_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_ProjectImport); i { case 0: return &v.state @@ -1172,7 +1204,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionerd_proto_provisionerd_proto_rawDesc, NumEnums: 1, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionerd/proto/provisionerd.proto b/provisionerd/proto/provisionerd.proto index 35c7298693f77..620d579910e2d 100644 --- a/provisionerd/proto/provisionerd.proto +++ b/provisionerd/proto/provisionerd.proto @@ -18,9 +18,6 @@ message AcquiredJob { bytes state = 4; } message ProjectImport { - repeated provisioner.ParameterValue parameter_values = 1; - bool skip_parameter_schemas = 2; - bool skip_resources = 3; } string job_id = 1; int64 created_at = 2; @@ -55,9 +52,8 @@ message CompletedJob { repeated provisioner.Resource resources = 2; } message ProjectImport { - repeated provisioner.ParameterSchema parameter_schemas = 1; - repeated provisioner.Resource start_resources = 2; - repeated provisioner.Resource stop_resources = 3; + repeated provisioner.Resource start_resources = 1; + repeated provisioner.Resource stop_resources = 2; } string job_id = 1; oneof type { @@ -80,12 +76,17 @@ message Log { string output = 4; } -// JobUpdate represents an update to a job. -// There may be no log output, but this message -// should still be sent periodically as a heartbeat. -message JobUpdate { +// This message should be sent periodically as a heartbeat. +message UpdateJobRequest { string job_id = 1; repeated Log logs = 2; + repeated provisioner.ParameterSchema parameter_schemas = 3; +} + +message UpdateJobResponse { + // If parameter schemas are sent, the job will respond + // with resolved parameter values. + repeated provisioner.ParameterValue parameter_values = 1; } service ProvisionerDaemon { @@ -97,8 +98,8 @@ service ProvisionerDaemon { // UpdateJob streams periodic updates for a job. // Implementations should buffer logs so this stream // is non-blocking. - rpc UpdateJob(stream JobUpdate) returns (Empty); - + rpc UpdateJob(UpdateJobRequest) returns (UpdateJobResponse); + // CancelJob indicates a job has been cancelled with // an error message. rpc CancelJob(CancelledJob) returns (Empty); diff --git a/provisionerd/proto/provisionerd_drpc.pb.go b/provisionerd/proto/provisionerd_drpc.pb.go index 10ade5583bc32..6e5a116239df3 100644 --- a/provisionerd/proto/provisionerd_drpc.pb.go +++ b/provisionerd/proto/provisionerd_drpc.pb.go @@ -39,7 +39,7 @@ type DRPCProvisionerDaemonClient interface { DRPCConn() drpc.Conn AcquireJob(ctx context.Context, in *Empty) (*AcquiredJob, error) - UpdateJob(ctx context.Context) (DRPCProvisionerDaemon_UpdateJobClient, error) + UpdateJob(ctx context.Context, in *UpdateJobRequest) (*UpdateJobResponse, error) CancelJob(ctx context.Context, in *CancelledJob) (*Empty, error) CompleteJob(ctx context.Context, in *CompletedJob) (*Empty, error) } @@ -63,45 +63,13 @@ func (c *drpcProvisionerDaemonClient) AcquireJob(ctx context.Context, in *Empty) return out, nil } -func (c *drpcProvisionerDaemonClient) UpdateJob(ctx context.Context) (DRPCProvisionerDaemon_UpdateJobClient, error) { - stream, err := c.cc.NewStream(ctx, "/provisionerd.ProvisionerDaemon/UpdateJob", drpcEncoding_File_provisionerd_proto_provisionerd_proto{}) +func (c *drpcProvisionerDaemonClient) UpdateJob(ctx context.Context, in *UpdateJobRequest) (*UpdateJobResponse, error) { + out := new(UpdateJobResponse) + err := c.cc.Invoke(ctx, "/provisionerd.ProvisionerDaemon/UpdateJob", drpcEncoding_File_provisionerd_proto_provisionerd_proto{}, in, out) if err != nil { return nil, err } - x := &drpcProvisionerDaemon_UpdateJobClient{stream} - return x, nil -} - -type DRPCProvisionerDaemon_UpdateJobClient interface { - drpc.Stream - Send(*JobUpdate) error - CloseAndRecv() (*Empty, error) -} - -type drpcProvisionerDaemon_UpdateJobClient struct { - drpc.Stream -} - -func (x *drpcProvisionerDaemon_UpdateJobClient) Send(m *JobUpdate) error { - return x.MsgSend(m, drpcEncoding_File_provisionerd_proto_provisionerd_proto{}) -} - -func (x *drpcProvisionerDaemon_UpdateJobClient) CloseAndRecv() (*Empty, error) { - if err := x.CloseSend(); err != nil { - return nil, err - } - m := new(Empty) - if err := x.MsgRecv(m, drpcEncoding_File_provisionerd_proto_provisionerd_proto{}); err != nil { - return nil, err - } - return m, nil -} - -func (x *drpcProvisionerDaemon_UpdateJobClient) CloseAndRecvMsg(m *Empty) error { - if err := x.CloseSend(); err != nil { - return err - } - return x.MsgRecv(m, drpcEncoding_File_provisionerd_proto_provisionerd_proto{}) + return out, nil } func (c *drpcProvisionerDaemonClient) CancelJob(ctx context.Context, in *CancelledJob) (*Empty, error) { @@ -124,7 +92,7 @@ func (c *drpcProvisionerDaemonClient) CompleteJob(ctx context.Context, in *Compl type DRPCProvisionerDaemonServer interface { AcquireJob(context.Context, *Empty) (*AcquiredJob, error) - UpdateJob(DRPCProvisionerDaemon_UpdateJobStream) error + UpdateJob(context.Context, *UpdateJobRequest) (*UpdateJobResponse, error) CancelJob(context.Context, *CancelledJob) (*Empty, error) CompleteJob(context.Context, *CompletedJob) (*Empty, error) } @@ -135,8 +103,8 @@ func (s *DRPCProvisionerDaemonUnimplementedServer) AcquireJob(context.Context, * return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) } -func (s *DRPCProvisionerDaemonUnimplementedServer) UpdateJob(DRPCProvisionerDaemon_UpdateJobStream) error { - return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) +func (s *DRPCProvisionerDaemonUnimplementedServer) UpdateJob(context.Context, *UpdateJobRequest) (*UpdateJobResponse, error) { + return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented) } func (s *DRPCProvisionerDaemonUnimplementedServer) CancelJob(context.Context, *CancelledJob) (*Empty, error) { @@ -165,9 +133,10 @@ func (DRPCProvisionerDaemonDescription) Method(n int) (string, drpc.Encoding, dr case 1: return "/provisionerd.ProvisionerDaemon/UpdateJob", drpcEncoding_File_provisionerd_proto_provisionerd_proto{}, func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) { - return nil, srv.(DRPCProvisionerDaemonServer). + return srv.(DRPCProvisionerDaemonServer). UpdateJob( - &drpcProvisionerDaemon_UpdateJobStream{in1.(drpc.Stream)}, + ctx, + in1.(*UpdateJobRequest), ) }, DRPCProvisionerDaemonServer.UpdateJob, true case 2: @@ -215,33 +184,20 @@ func (x *drpcProvisionerDaemon_AcquireJobStream) SendAndClose(m *AcquiredJob) er type DRPCProvisionerDaemon_UpdateJobStream interface { drpc.Stream - SendAndClose(*Empty) error - Recv() (*JobUpdate, error) + SendAndClose(*UpdateJobResponse) error } type drpcProvisionerDaemon_UpdateJobStream struct { drpc.Stream } -func (x *drpcProvisionerDaemon_UpdateJobStream) SendAndClose(m *Empty) error { +func (x *drpcProvisionerDaemon_UpdateJobStream) SendAndClose(m *UpdateJobResponse) error { if err := x.MsgSend(m, drpcEncoding_File_provisionerd_proto_provisionerd_proto{}); err != nil { return err } return x.CloseSend() } -func (x *drpcProvisionerDaemon_UpdateJobStream) Recv() (*JobUpdate, error) { - m := new(JobUpdate) - if err := x.MsgRecv(m, drpcEncoding_File_provisionerd_proto_provisionerd_proto{}); err != nil { - return nil, err - } - return m, nil -} - -func (x *drpcProvisionerDaemon_UpdateJobStream) RecvMsg(m *JobUpdate) error { - return x.MsgRecv(m, drpcEncoding_File_provisionerd_proto_provisionerd_proto{}) -} - type DRPCProvisionerDaemon_CancelJobStream interface { drpc.Stream SendAndClose(*Empty) error diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index f771ad9094cd1..4fd11b6c9906f 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -71,7 +71,6 @@ type provisionerDaemon struct { clientDialer Dialer client proto.DRPCProvisionerDaemonClient - updateStream proto.DRPCProvisionerDaemon_UpdateJobClient // Locked when closing the daemon. closeMutex sync.Mutex @@ -104,17 +103,6 @@ func (p *provisionerDaemon) connect(ctx context.Context) { p.opts.Logger.Warn(context.Background(), "failed to dial", slog.Error(err)) continue } - p.updateStream, err = p.client.UpdateJob(ctx) - if err != nil { - if errors.Is(err, context.Canceled) { - return - } - if p.isClosed() { - return - } - p.opts.Logger.Warn(context.Background(), "create update job stream", slog.Error(err)) - continue - } p.opts.Logger.Debug(context.Background(), "connected") break } @@ -131,11 +119,11 @@ func (p *provisionerDaemon) connect(ctx context.Context) { select { case <-p.closed: return - case <-p.updateStream.Context().Done(): + case <-p.client.DRPCConn().Closed(): // We use the update stream to detect when the connection // has been interrupted. This works well, because logs need // to buffer if a job is running in the background. - p.opts.Logger.Debug(context.Background(), "update stream ended", slog.Error(p.updateStream.Context().Err())) + p.opts.Logger.Debug(context.Background(), "client stream ended") p.connect(ctx) } }() @@ -150,7 +138,7 @@ func (p *provisionerDaemon) connect(ctx context.Context) { select { case <-p.closed: return - case <-p.updateStream.Context().Done(): + case <-p.client.DRPCConn().Closed(): return case <-ticker.C: p.acquireJob(ctx) @@ -219,7 +207,7 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob) case <-ctx.Done(): return case <-ticker.C: - err := p.updateStream.Send(&proto.JobUpdate{ + _, err := p.client.UpdateJob(ctx, &proto.UpdateJobRequest{ JobId: job.JobId, }) if err != nil { @@ -344,48 +332,59 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob) } func (p *provisionerDaemon) runProjectImport(ctx context.Context, provisioner sdkproto.DRPCProvisionerClient, job *proto.AcquiredJob) { - var parameterSchemas []*sdkproto.ParameterSchema - var startResources []*sdkproto.Resource - var stopResources []*sdkproto.Resource - var err error + parameterSchemas, err := p.runProjectImportParse(ctx, provisioner, job) + if err != nil { + p.cancelActiveJobf("run parse: %s", err) + return + } - if !job.GetProjectImport().SkipParameterSchemas { - parameterSchemas, err = p.runProjectImportParse(ctx, provisioner, job) - if err != nil { - p.cancelActiveJobf("run parse: %s", err) - return - } + updateResponse, err := p.client.UpdateJob(ctx, &proto.UpdateJobRequest{ + JobId: job.JobId, + ParameterSchemas: parameterSchemas, + }) + if err != nil { + p.cancelActiveJobf("update job: %s", err) + return } - if !job.GetProjectImport().SkipResources { - startResources, err = p.runProjectImportProvision(ctx, provisioner, job, append(job.GetProjectImport().GetParameterValues(), &sdkproto.ParameterValue{ - DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, - // TODO: Make this a constant higher-up in the stack. - Name: "coder_workspace_transition", - Value: "start", - })) - if err != nil { - p.cancelActiveJobf("project import provision for start: %s", err) - return - } - stopResources, err = p.runProjectImportProvision(ctx, provisioner, job, append(job.GetProjectImport().GetParameterValues(), &sdkproto.ParameterValue{ - DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, - Name: "coder_workspace_transition", - Value: "stop", - })) - if err != nil { - p.cancelActiveJobf("project import provision for start: %s", err) + valueByName := map[string]*sdkproto.ParameterValue{} + for _, parameterValue := range updateResponse.ParameterValues { + valueByName[parameterValue.Name] = parameterValue + } + for _, parameterSchema := range parameterSchemas { + _, ok := valueByName[parameterSchema.Name] + if !ok { + p.cancelActiveJobf("missing parameter: %s", parameterSchema.Name) return } } + startResources, err := p.runProjectImportProvision(ctx, provisioner, job, append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ + DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, + // TODO: Make this a constant higher-up in the stack. + Name: "coder_workspace_transition", + Value: "start", + })) + if err != nil { + p.cancelActiveJobf("project import provision for start: %s", err) + return + } + stopResources, err := p.runProjectImportProvision(ctx, provisioner, job, append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ + DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, + Name: "coder_workspace_transition", + Value: "stop", + })) + if err != nil { + p.cancelActiveJobf("project import provision for start: %s", err) + return + } + _, err = p.client.CompleteJob(ctx, &proto.CompletedJob{ JobId: job.JobId, Type: &proto.CompletedJob_ProjectImport_{ ProjectImport: &proto.CompletedJob_ProjectImport{ - ParameterSchemas: parameterSchemas, - StartResources: startResources, - StopResources: stopResources, + StartResources: startResources, + StopResources: stopResources, }, }, }) @@ -416,7 +415,7 @@ func (p *provisionerDaemon) runProjectImportParse(ctx context.Context, provision slog.F("output", msgType.Log.Output), ) - err = p.updateStream.Send(&proto.JobUpdate{ + _, err = p.client.UpdateJob(ctx, &proto.UpdateJobRequest{ JobId: job.JobId, Logs: []*proto.Log{{ Source: proto.LogSource_PROVISIONER, @@ -466,7 +465,7 @@ func (p *provisionerDaemon) runProjectImportProvision(ctx context.Context, provi slog.F("output", msgType.Log.Output), ) - err = p.updateStream.Send(&proto.JobUpdate{ + _, err = p.client.UpdateJob(ctx, &proto.UpdateJobRequest{ JobId: job.JobId, Logs: []*proto.Log{{ Source: proto.LogSource_PROVISIONER, @@ -519,7 +518,7 @@ func (p *provisionerDaemon) runWorkspaceProvision(ctx context.Context, provision slog.F("workspace_history_id", job.GetWorkspaceProvision().WorkspaceHistoryId), ) - err = p.updateStream.Send(&proto.JobUpdate{ + _, err = p.client.UpdateJob(ctx, &proto.UpdateJobRequest{ JobId: job.JobId, Logs: []*proto.Log{{ Source: proto.LogSource_PROVISIONER, diff --git a/provisionerd/provisionerd_test.go b/provisionerd/provisionerd_test.go index 99f9f254086d5..7e707dd314ac4 100644 --- a/provisionerd/provisionerd_test.go +++ b/provisionerd/provisionerd_test.go @@ -34,9 +34,8 @@ func TestMain(m *testing.M) { func TestProvisionerd(t *testing.T) { t.Parallel() - noopUpdateJob := func(stream proto.DRPCProvisionerDaemon_UpdateJobStream) error { - <-stream.Context().Done() - return nil + noopUpdateJob := func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { + return &proto.UpdateJobResponse{}, nil } t.Run("InstantClose", func(t *testing.T) { @@ -170,14 +169,9 @@ func TestProvisionerd(t *testing.T) { }, }, nil }, - updateJob: func(stream proto.DRPCProvisionerDaemon_UpdateJobStream) error { - for { - _, err := stream.Recv() - if err != nil { - return err - } - close(completeChan) - } + updateJob: func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { + close(completeChan) + return &proto.UpdateJobResponse{}, nil }, cancelJob: func(ctx context.Context, job *proto.CancelledJob) (*proto.Empty, error) { return &proto.Empty{}, nil @@ -222,18 +216,11 @@ func TestProvisionerd(t *testing.T) { }, }, nil }, - updateJob: func(stream proto.DRPCProvisionerDaemon_UpdateJobStream) error { - for { - msg, err := stream.Recv() - if err != nil { - return err - } - if len(msg.Logs) == 0 { - continue - } - + updateJob: func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { + if len(update.Logs) != 0 { didLog.Store(true) } + return &proto.UpdateJobResponse{}, nil }, completeJob: func(ctx context.Context, job *proto.CompletedJob) (*proto.Empty, error) { didComplete.Store(true) @@ -323,18 +310,11 @@ func TestProvisionerd(t *testing.T) { }, }, nil }, - updateJob: func(stream proto.DRPCProvisionerDaemon_UpdateJobStream) error { - for { - msg, err := stream.Recv() - if err != nil { - return err - } - if len(msg.Logs) == 0 { - continue - } - + updateJob: func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { + if len(update.Logs) != 0 { didLog.Store(true) } + return &proto.UpdateJobResponse{}, nil }, completeJob: func(ctx context.Context, job *proto.CompletedJob) (*proto.Empty, error) { didComplete.Store(true) @@ -463,7 +443,7 @@ func (p *provisionerTestServer) Provision(request *sdkproto.Provision_Request, s // passable functions for dynamic functionality. type provisionerDaemonTestServer struct { acquireJob func(ctx context.Context, _ *proto.Empty) (*proto.AcquiredJob, error) - updateJob func(stream proto.DRPCProvisionerDaemon_UpdateJobStream) error + updateJob func(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) cancelJob func(ctx context.Context, job *proto.CancelledJob) (*proto.Empty, error) completeJob func(ctx context.Context, job *proto.CompletedJob) (*proto.Empty, error) } @@ -472,8 +452,8 @@ func (p *provisionerDaemonTestServer) AcquireJob(ctx context.Context, empty *pro return p.acquireJob(ctx, empty) } -func (p *provisionerDaemonTestServer) UpdateJob(stream proto.DRPCProvisionerDaemon_UpdateJobStream) error { - return p.updateJob(stream) +func (p *provisionerDaemonTestServer) UpdateJob(ctx context.Context, update *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { + return p.updateJob(ctx, update) } func (p *provisionerDaemonTestServer) CancelJob(ctx context.Context, job *proto.CancelledJob) (*proto.Empty, error) { From e53f0beb26c83638ce269e3662fdc82d8b736ab2 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 20:54:46 +0000 Subject: [PATCH 05/20] Refactor job update for provisionerd --- coderd/provisionerdaemons.go | 254 +++++++++++-------------- coderd/provisionerjobs.go | 12 -- coderd/provisionerjobs_test.go | 14 +- provisioner/terraform/parse.go | 1 - provisioner/terraform/parse_test.go | 1 - provisionersdk/proto/provisioner.pb.go | 249 ++++++++++++------------ provisionersdk/proto/provisioner.proto | 1 - 7 files changed, 244 insertions(+), 288 deletions(-) diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index f84da2ebcb8b8..4e4e81ea9ea7e 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -109,12 +109,6 @@ type workspaceProvisionJob struct { DryRun bool `json:"dry_run"` } -// The input for a "project_import" job. -type projectVersionImportJob struct { - OrganizationID string `json:"organization_id"` - ProjectID uuid.UUID `json:"project_id"` -} - // Implementation of the provisioner daemon protobuf server. type provisionerdServer struct { ID uuid.UUID @@ -242,39 +236,8 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty }, } case database.ProvisionerJobTypeProjectVersionImport: - var input projectVersionImportJob - err = json.Unmarshal(job.Input, &input) - if err != nil { - return nil, failJob(fmt.Sprintf("unmarshal job input %q: %s", job.Input, err)) - } - - // Compute parameters for the workspace to consume. - parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{ - ProjectImportJobID: job.ID, - OrganizationID: input.OrganizationID, - ProjectID: uuid.NullUUID{ - UUID: input.ProjectID, - Valid: input.ProjectID.String() != uuid.Nil.String(), - }, - UserID: user.ID, - }, nil) - if err != nil { - return nil, failJob(fmt.Sprintf("compute parameters: %s", err)) - } - // Convert parameters to the protobuf type. - protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) - for _, parameter := range parameters { - converted, err := convertComputedParameterValue(parameter) - if err != nil { - return nil, failJob(fmt.Sprintf("convert parameter: %s", err)) - } - protoParameters = append(protoParameters, converted) - } - protoJob.Type = &proto.AcquiredJob_ProjectImport_{ - ProjectImport: &proto.AcquiredJob_ProjectImport{ - ParameterValues: protoParameters, - }, + ProjectImport: &proto.AcquiredJob_ProjectImport{}, } } switch job.StorageMethod { @@ -291,119 +254,137 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty return protoJob, err } -func (server *provisionerdServer) UpdateJob(stream proto.DRPCProvisionerDaemon_UpdateJobStream) error { - for { - update, err := stream.Recv() - if err != nil { - return err +func (server *provisionerdServer) UpdateJob(ctx context.Context, request *proto.UpdateJobRequest) (*proto.UpdateJobResponse, error) { + parsedID, err := uuid.Parse(request.JobId) + if err != nil { + return nil, xerrors.Errorf("parse job id: %w", err) + } + job, err := server.Database.GetProvisionerJobByID(ctx, parsedID) + if err != nil { + return nil, xerrors.Errorf("get job: %w", err) + } + if !job.WorkerID.Valid { + return nil, xerrors.New("job isn't running yet") + } + if job.WorkerID.UUID.String() != server.ID.String() { + return nil, xerrors.New("you don't own this job") + } + err = server.Database.UpdateProvisionerJobByID(ctx, database.UpdateProvisionerJobByIDParams{ + ID: parsedID, + UpdatedAt: database.Now(), + }) + if err != nil { + return nil, xerrors.Errorf("update job: %w", err) + } + + if len(request.Logs) > 0 { + insertParams := database.InsertProvisionerJobLogsParams{ + JobID: parsedID, } - parsedID, err := uuid.Parse(update.JobId) - if err != nil { - return xerrors.Errorf("parse job id: %w", err) + for _, log := range request.Logs { + logLevel, err := convertLogLevel(log.Level) + if err != nil { + return nil, xerrors.Errorf("convert log level: %w", err) + } + logSource, err := convertLogSource(log.Source) + if err != nil { + return nil, xerrors.Errorf("convert log source: %w", err) + } + insertParams.ID = append(insertParams.ID, uuid.New()) + insertParams.CreatedAt = append(insertParams.CreatedAt, time.UnixMilli(log.CreatedAt)) + insertParams.Level = append(insertParams.Level, logLevel) + insertParams.Source = append(insertParams.Source, logSource) + insertParams.Output = append(insertParams.Output, log.Output) } - job, err := server.Database.GetProvisionerJobByID(stream.Context(), parsedID) + logs, err := server.Database.InsertProvisionerJobLogs(context.Background(), insertParams) if err != nil { - return xerrors.Errorf("get job: %w", err) + return nil, xerrors.Errorf("insert job logs: %w", err) } - if !job.WorkerID.Valid { - return xerrors.New("job isn't running yet") - } - if job.WorkerID.UUID.String() != server.ID.String() { - return xerrors.New("you don't own this job") + data, err := json.Marshal(logs) + if err != nil { + return nil, xerrors.Errorf("marshal job log: %w", err) } - - err = server.Database.UpdateProvisionerJobByID(stream.Context(), database.UpdateProvisionerJobByIDParams{ - ID: parsedID, - UpdatedAt: database.Now(), - }) + err = server.Pubsub.Publish(provisionerJobLogsChannel(parsedID), data) if err != nil { - return xerrors.Errorf("update job: %w", err) + return nil, xerrors.Errorf("publish job log: %w", err) } - if len(update.Logs) > 0 { - insertParams := database.InsertProvisionerJobLogsParams{ - JobID: parsedID, - } - for _, log := range update.Logs { - logLevel, err := convertLogLevel(log.Level) - if err != nil { - return xerrors.Errorf("convert log level: %w", err) - } - logSource, err := convertLogSource(log.Source) - if err != nil { - return xerrors.Errorf("convert log source: %w", err) - } - insertParams.ID = append(insertParams.ID, uuid.New()) - insertParams.CreatedAt = append(insertParams.CreatedAt, time.UnixMilli(log.CreatedAt)) - insertParams.Level = append(insertParams.Level, logLevel) - insertParams.Source = append(insertParams.Source, logSource) - insertParams.Output = append(insertParams.Output, log.Output) - } - logs, err := server.Database.InsertProvisionerJobLogs(context.Background(), insertParams) - if err != nil { - return xerrors.Errorf("insert job logs: %w", err) - } - data, err := json.Marshal(logs) + } + + if len(request.ParameterSchemas) > 0 { + for _, protoParameter := range request.ParameterSchemas { + validationTypeSystem, err := convertValidationTypeSystem(protoParameter.ValidationTypeSystem) if err != nil { - return xerrors.Errorf("marshal job log: %w", err) + return nil, xerrors.Errorf("convert validation type system for %q: %w", protoParameter.Name, err) } - err = server.Pubsub.Publish(provisionerJobLogsChannel(parsedID), data) - if err != nil { - return xerrors.Errorf("publish job log: %w", err) + + parameterSchema := database.InsertParameterSchemaParams{ + ID: uuid.New(), + CreatedAt: database.Now(), + JobID: job.ID, + Name: protoParameter.Name, + Description: protoParameter.Description, + RedisplayValue: protoParameter.RedisplayValue, + ValidationError: protoParameter.ValidationError, + ValidationCondition: protoParameter.ValidationCondition, + ValidationValueType: protoParameter.ValidationValueType, + ValidationTypeSystem: validationTypeSystem, + + DefaultSourceScheme: database.ParameterSourceSchemeNone, + DefaultDestinationScheme: database.ParameterDestinationSchemeNone, + + AllowOverrideDestination: protoParameter.AllowOverrideDestination, + AllowOverrideSource: protoParameter.AllowOverrideSource, } - } - if update.GetProjectImport() != nil { - // Validate that all parameters send from the provisioner daemon - // follow the protocol. - parameterSchemas := make([]database.InsertParameterSchemaParams, 0, len(update.GetProjectImport().ParameterSchemas)) - for _, protoParameter := range update.GetProjectImport().ParameterSchemas { - validationTypeSystem, err := convertValidationTypeSystem(protoParameter.ValidationTypeSystem) + // It's possible a parameter doesn't define a default source! + if protoParameter.DefaultSource != nil { + parameterSourceScheme, err := convertParameterSourceScheme(protoParameter.DefaultSource.Scheme) if err != nil { - return xerrors.Errorf("convert validation type system for %q: %w", protoParameter.Name, err) - } - - parameterSchema := database.InsertParameterSchemaParams{ - ID: uuid.New(), - CreatedAt: database.Now(), - JobID: job.ID, - Name: protoParameter.Name, - Description: protoParameter.Description, - RedisplayValue: protoParameter.RedisplayValue, - ValidationError: protoParameter.ValidationError, - ValidationCondition: protoParameter.ValidationCondition, - ValidationValueType: protoParameter.ValidationValueType, - ValidationTypeSystem: validationTypeSystem, - - DefaultSourceScheme: database.ParameterSourceSchemeNone, - DefaultDestinationScheme: database.ParameterDestinationSchemeNone, - - AllowOverrideDestination: protoParameter.AllowOverrideDestination, - AllowOverrideSource: protoParameter.AllowOverrideSource, + return nil, xerrors.Errorf("convert parameter source scheme: %w", err) } + parameterSchema.DefaultSourceScheme = parameterSourceScheme + parameterSchema.DefaultSourceValue = protoParameter.DefaultSource.Value + } - // It's possible a parameter doesn't define a default source! - if protoParameter.DefaultSource != nil { - parameterSourceScheme, err := convertParameterSourceScheme(protoParameter.DefaultSource.Scheme) - if err != nil { - return xerrors.Errorf("convert parameter source scheme: %w", err) - } - parameterSchema.DefaultSourceScheme = parameterSourceScheme - parameterSchema.DefaultSourceValue = protoParameter.DefaultSource.Value + // It's possible a parameter doesn't define a default destination! + if protoParameter.DefaultDestination != nil { + parameterDestinationScheme, err := convertParameterDestinationScheme(protoParameter.DefaultDestination.Scheme) + if err != nil { + return nil, xerrors.Errorf("convert parameter destination scheme: %w", err) } + parameterSchema.DefaultDestinationScheme = parameterDestinationScheme + } - // It's possible a parameter doesn't define a default destination! - if protoParameter.DefaultDestination != nil { - parameterDestinationScheme, err := convertParameterDestinationScheme(protoParameter.DefaultDestination.Scheme) - if err != nil { - return xerrors.Errorf("convert parameter destination scheme: %w", err) - } - parameterSchema.DefaultDestinationScheme = parameterDestinationScheme - } + _, err = server.Database.InsertParameterSchema(ctx, parameterSchema) + if err != nil { + return nil, xerrors.Errorf("insert parameter schema: %w", err) + } + } - parameterSchemas = append(parameterSchemas, parameterSchema) + parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{ + ProjectImportJobID: job.ID, + OrganizationID: job.OrganizationID, + UserID: job.InitiatorID, + }, nil) + if err != nil { + return nil, xerrors.Errorf("compute parameters: %w", err) + } + // Convert parameters to the protobuf type. + protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) + for _, parameter := range parameters { + converted, err := convertComputedParameterValue(parameter) + if err != nil { + return nil, xerrors.Errorf("convert parameter: %s", err) } + protoParameters = append(protoParameters, converted) } + + return &proto.UpdateJobResponse{ + ParameterValues: protoParameters, + }, nil } + + return &proto.UpdateJobResponse{}, nil } func (server *provisionerdServer) CancelJob(ctx context.Context, cancelJob *proto.CancelledJob) (*proto.Empty, error) { @@ -450,17 +431,12 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr if err != nil { return nil, xerrors.Errorf("get job by id: %w", err) } - // TODO: Check if the worker ID matches! - // If it doesn't, a provisioner daemon could be impersonating another job! + if job.WorkerID.UUID.String() != server.ID.String() { + return nil, xerrors.Errorf("you don't have permission to update this job") + } switch jobType := completed.Type.(type) { case *proto.CompletedJob_ProjectImport_: - var input projectVersionImportJob - err = json.Unmarshal(job.Input, &input) - if err != nil { - return nil, xerrors.Errorf("unmarshal job data: %w", err) - } - err = server.Database.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ ID: jobID, UpdatedAt: database.Now(), diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index d5a446e0d57ad..dce331d153e6a 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -2,7 +2,6 @@ package coderd import ( "database/sql" - "encoding/json" "errors" "fmt" "net/http" @@ -80,16 +79,6 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r return } - input, err := json.Marshal(projectVersionImportJob{ - OrganizationID: organization.ID, - }) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("marshal job: %s", err), - }) - return - } - jobID := uuid.New() for _, parameterValue := range req.ParameterValues { _, err = api.Database.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ @@ -121,7 +110,6 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r StorageMethod: database.ProvisionerStorageMethodFile, StorageSource: file.Hash, Type: database.ProvisionerJobTypeProjectVersionImport, - Input: input, }) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index 89f90dbf0c07e..f0bb167d5e9f5 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -2,7 +2,6 @@ package coderd_test import ( "context" - "fmt" "net/http" "testing" "time" @@ -89,7 +88,6 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { }) require.NoError(t, err) job = coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - fmt.Printf("Job %+v\n", job) values, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) require.NoError(t, err) require.Equal(t, "somevalue", values[0].SourceValue) @@ -120,16 +118,23 @@ func TestProvisionerJobParametersByID(t *testing.T) { Complete: &proto.Parse_Complete{ ParameterSchemas: []*proto.ParameterSchema{{ Name: "example", + DefaultSource: &proto.ParameterSource{ + Scheme: proto.ParameterSource_DATA, + Value: "hello", + }, + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + }, }}, }, }, }}, Provision: echo.ProvisionComplete, }) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + job = coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) params, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) require.NoError(t, err) - require.Len(t, params, 0) + require.Len(t, params, 1) }) t.Run("ListNoRedisplay", func(t *testing.T) { @@ -149,7 +154,6 @@ func TestProvisionerJobParametersByID(t *testing.T) { }, DefaultDestination: &proto.ParameterDestination{ Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, - Value: "example", }, RedisplayValue: false, }}, diff --git a/provisioner/terraform/parse.go b/provisioner/terraform/parse.go index 24f4943c5bff5..11efa03a3ec6b 100644 --- a/provisioner/terraform/parse.go +++ b/provisioner/terraform/parse.go @@ -59,7 +59,6 @@ func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSc } schema.DefaultDestination = &proto.ParameterDestination{ Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, - Value: variable.Name, } } diff --git a/provisioner/terraform/parse_test.go b/provisioner/terraform/parse_test.go index 2a4ce6a8b2c8e..faf3860a2844a 100644 --- a/provisioner/terraform/parse_test.go +++ b/provisioner/terraform/parse_test.go @@ -78,7 +78,6 @@ func TestParse(t *testing.T) { }, DefaultDestination: &proto.ParameterDestination{ Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, - Value: "A", }, }}, }, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 3dcb781e06499..18f034e0c7fd4 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -274,7 +274,6 @@ type ParameterDestination struct { unknownFields protoimpl.UnknownFields Scheme ParameterDestination_Scheme `protobuf:"varint,1,opt,name=scheme,proto3,enum=provisioner.ParameterDestination_Scheme" json:"scheme,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *ParameterDestination) Reset() { @@ -316,13 +315,6 @@ func (x *ParameterDestination) GetScheme() ParameterDestination_Scheme { return ParameterDestination_ENVIRONMENT_VARIABLE } -func (x *ParameterDestination) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - // ParameterValue represents the resolved source and destination of a parameter. type ParameterValue struct { state protoimpl.MessageState @@ -1099,133 +1091,132 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x63, 0x68, 0x65, 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, 0x12, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x41, 0x54, 0x41, 0x10, 0x00, 0x22, - 0xac, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x44, 0x65, 0x73, + 0x96, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x63, 0x68, 0x65, - 0x6d, 0x65, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 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, 0x3c, 0x0a, 0x06, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x45, 0x4e, - 0x56, 0x49, 0x52, 0x4f, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x41, 0x52, 0x49, 0x41, 0x42, - 0x4c, 0x45, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, - 0x4e, 0x45, 0x52, 0x5f, 0x56, 0x41, 0x52, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x22, 0x93, - 0x01, 0x0a, 0x0e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x57, 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8d, 0x05, 0x0a, 0x0f, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 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, 0x43, - 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6f, 0x76, 0x65, - 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, - 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x44, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x1a, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x44, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0e, 0x72, 0x65, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x5d, 0x0a, 0x16, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x2e, 0x54, 0x79, 0x70, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x14, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x12, 0x32, 0x0a, 0x15, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x13, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x31, 0x0a, 0x14, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, - 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x1f, 0x0a, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x48, - 0x43, 0x4c, 0x10, 0x01, 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, 0x32, 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, 0x22, 0xfc, 0x01, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, - 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, - 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, + 0x6d, 0x65, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x3c, 0x0a, 0x06, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x45, 0x4e, 0x56, 0x49, 0x52, 0x4f, 0x4e, 0x4d, + 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x41, 0x52, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x18, + 0x0a, 0x14, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x45, 0x52, 0x5f, 0x56, 0x41, + 0x52, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x22, 0x93, 0x01, 0x0a, 0x0e, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x57, 0x0a, 0x12, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, + 0x65, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x63, + 0x68, 0x65, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x8d, + 0x05, 0x0a, 0x0f, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, + 0x6d, 0x61, 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, 0x43, 0x0a, 0x0e, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x32, 0x0a, + 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, 0x6c, + 0x6c, 0x6f, 0x77, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x52, 0x0a, 0x13, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, - 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, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, - 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x22, 0xfc, 0x02, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x1a, 0x9e, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, - 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, - 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, - 0x75, 0x6e, 0x1a, 0x55, 0x0a, 0x08, 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, 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, 0x77, 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, 0x3d, 0x0a, 0x08, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, - 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 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, 0x32, 0xa1, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x1a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6f, + 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x72, 0x65, + 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x5d, 0x0a, 0x16, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x14, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x32, 0x0a, 0x15, 0x76, + 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x29, 0x0a, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x31, 0x0a, 0x14, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x1f, 0x0a, + 0x0a, 0x54, 0x79, 0x70, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x08, 0x0a, 0x04, 0x4e, + 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x48, 0x43, 0x4c, 0x10, 0x01, 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, 0x32, 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, 0x22, 0xfc, + 0x01, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x79, 0x1a, 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x49, 0x0a, + 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 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, 0x39, 0x0a, 0x08, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, - 0x72, 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, + 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xfc, 0x02, + 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x9e, 0x01, 0x0a, 0x07, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x55, 0x0a, 0x08, + 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, 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, 0x77, 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, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 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, 0x32, 0xa1, 0x01, + 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, + 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, + 0x01, 0x12, 0x4e, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, + 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 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 546c081cd6882..0b999dffbc4fe 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -20,7 +20,6 @@ message ParameterDestination { PROVISIONER_VARIABLE = 1; } Scheme scheme = 1; - string value = 2; } // ParameterValue represents the resolved source and destination of a parameter. From 4466836a2208f33906b5435a9bd0497a40f7a56f Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 21:18:05 +0000 Subject: [PATCH 06/20] Handle multiple states correctly when provisioning a project --- coderd/parameter/compute.go | 5 +++ provisionerd/provisionerd.go | 63 +++++++++++++++++++++++-------- provisionerd/provisionerd_test.go | 10 ++++- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/coderd/parameter/compute.go b/coderd/parameter/compute.go index 493d72edf9ee9..801c93732ee24 100644 --- a/coderd/parameter/compute.go +++ b/coderd/parameter/compute.go @@ -11,6 +11,11 @@ import ( "github.com/coder/coder/database" ) +const ( + CoderUsername = "coder_username" + CoderWorkspaceTransition = "coder_workspace_transition" +) + // ComputeScope targets identifiers to pull parameters from. type ComputeScope struct { ProjectImportJobID uuid.UUID diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index 4fd11b6c9906f..bf50a8cc3281d 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -19,11 +19,23 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/provisionerd/proto" sdkproto "github.com/coder/coder/provisionersdk/proto" "github.com/coder/retry" ) +const ( + missingParameterErrorText = "missing parameter" +) + +// IsMissingParameterError returns whether the error message provided +// is a missing parameter error. This can indicate to consumers that +// they should check parameters. +func IsMissingParameterError(err string) bool { + return strings.Contains(err, missingParameterErrorText) +} + // Dialer represents the function to create a daemon client connection. type Dialer func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) @@ -352,32 +364,53 @@ func (p *provisionerDaemon) runProjectImport(ctx context.Context, provisioner sd valueByName[parameterValue.Name] = parameterValue } for _, parameterSchema := range parameterSchemas { + if parameterSchema.Name == parameter.CoderWorkspaceTransition { + // Hardcode the workspace transition variable. We'll + // make it do stuff later! + continue + } _, ok := valueByName[parameterSchema.Name] if !ok { - p.cancelActiveJobf("missing parameter: %s", parameterSchema.Name) + p.cancelActiveJobf("%s: %s", missingParameterErrorText, parameterSchema.Name) return } } + // Checks if the schema has defined a workspace transition variable. + // If not, we don't need to check for resources provisioned in a stopped state. + hasWorkspaceTransition := false + for _, parameterSchema := range parameterSchemas { + if parameterSchema.Name != parameter.CoderWorkspaceTransition { + continue + } + hasWorkspaceTransition = true + break + } - startResources, err := p.runProjectImportProvision(ctx, provisioner, job, append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ - DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, - // TODO: Make this a constant higher-up in the stack. - Name: "coder_workspace_transition", - Value: "start", - })) - if err != nil { - p.cancelActiveJobf("project import provision for start: %s", err) - return + startParameters := updateResponse.ParameterValues + if hasWorkspaceTransition { + startParameters = append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ + DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, + Name: parameter.CoderWorkspaceTransition, + Value: "start", + }) } - stopResources, err := p.runProjectImportProvision(ctx, provisioner, job, append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ - DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, - Name: "coder_workspace_transition", - Value: "stop", - })) + startResources, err := p.runProjectImportProvision(ctx, provisioner, job, startParameters) if err != nil { p.cancelActiveJobf("project import provision for start: %s", err) return } + stopResources := startResources + if hasWorkspaceTransition { + stopResources, err = p.runProjectImportProvision(ctx, provisioner, job, append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ + DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, + Name: "coder_workspace_transition", + Value: "stop", + })) + if err != nil { + p.cancelActiveJobf("project import provision for start: %s", err) + return + } + } _, err = p.client.CompleteJob(ctx, &proto.CompletedJob{ JobId: job.JobId, diff --git a/provisionerd/provisionerd_test.go b/provisionerd/provisionerd_test.go index 7e707dd314ac4..db7019a05f5dc 100644 --- a/provisionerd/provisionerd_test.go +++ b/provisionerd/provisionerd_test.go @@ -21,6 +21,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/provisionerd" "github.com/coder/coder/provisionerd/proto" "github.com/coder/coder/provisionersdk" @@ -195,6 +196,7 @@ func TestProvisionerd(t *testing.T) { didComplete atomic.Bool didLog atomic.Bool didAcquireJob atomic.Bool + didDryRun atomic.Bool ) completeChan := make(chan struct{}) closer := createProvisionerd(t, func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) { @@ -247,7 +249,9 @@ func TestProvisionerd(t *testing.T) { err = stream.Send(&sdkproto.Parse_Response{ Type: &sdkproto.Parse_Response_Complete{ Complete: &sdkproto.Parse_Complete{ - ParameterSchemas: []*sdkproto.ParameterSchema{}, + ParameterSchemas: []*sdkproto.ParameterSchema{{ + Name: parameter.CoderWorkspaceTransition, + }}, }, }, }) @@ -255,6 +259,9 @@ func TestProvisionerd(t *testing.T) { return nil }, provision: func(request *sdkproto.Provision_Request, stream sdkproto.DRPCProvisioner_ProvisionStream) error { + if request.DryRun { + didDryRun.Store(true) + } err := stream.Send(&sdkproto.Provision_Response{ Type: &sdkproto.Provision_Response_Log{ Log: &sdkproto.Log{ @@ -280,6 +287,7 @@ func TestProvisionerd(t *testing.T) { <-completeChan require.True(t, didLog.Load()) require.True(t, didComplete.Load()) + require.True(t, didDryRun.Load()) require.NoError(t, closer.Close()) }) From 8fe05d6f470faab54085fd78c8f36bf0d09031af Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 21:24:46 +0000 Subject: [PATCH 07/20] Add project import job resource table --- database/databasefake/databasefake.go | 67 +++++++++++++++++--------- database/dump.sql | 15 ++++++ database/migrations/000004_jobs.up.sql | 10 ++++ database/models.go | 9 ++++ database/querier.go | 1 + database/query.sql | 6 +++ database/query.sql.go | 37 ++++++++++++++ 7 files changed, 121 insertions(+), 24 deletions(-) diff --git a/database/databasefake/databasefake.go b/database/databasefake/databasefake.go index 9d53254f7af0f..bd0ac62b92fb1 100644 --- a/database/databasefake/databasefake.go +++ b/database/databasefake/databasefake.go @@ -19,18 +19,19 @@ func New() database.Store { organizationMembers: make([]database.OrganizationMember, 0), users: make([]database.User, 0), - files: make([]database.File, 0), - parameterValue: make([]database.ParameterValue, 0), - parameterSchema: make([]database.ParameterSchema, 0), - project: make([]database.Project, 0), - projectVersion: make([]database.ProjectVersion, 0), - provisionerDaemons: make([]database.ProvisionerDaemon, 0), - provisionerJobs: make([]database.ProvisionerJob, 0), - provisionerJobLog: make([]database.ProvisionerJobLog, 0), - workspace: make([]database.Workspace, 0), - workspaceResource: make([]database.WorkspaceResource, 0), - workspaceHistory: make([]database.WorkspaceHistory, 0), - workspaceAgent: make([]database.WorkspaceAgent, 0), + files: make([]database.File, 0), + parameterValue: make([]database.ParameterValue, 0), + parameterSchema: make([]database.ParameterSchema, 0), + project: make([]database.Project, 0), + projectVersion: make([]database.ProjectVersion, 0), + projectImportJobResource: make([]database.ProjectImportJobResource, 0), + provisionerDaemons: make([]database.ProvisionerDaemon, 0), + provisionerJobs: make([]database.ProvisionerJob, 0), + provisionerJobLog: make([]database.ProvisionerJobLog, 0), + workspace: make([]database.Workspace, 0), + workspaceResource: make([]database.WorkspaceResource, 0), + workspaceHistory: make([]database.WorkspaceHistory, 0), + workspaceAgent: make([]database.WorkspaceAgent, 0), } } @@ -45,18 +46,19 @@ type fakeQuerier struct { users []database.User // New tables - files []database.File - parameterValue []database.ParameterValue - parameterSchema []database.ParameterSchema - project []database.Project - projectVersion []database.ProjectVersion - provisionerDaemons []database.ProvisionerDaemon - provisionerJobs []database.ProvisionerJob - provisionerJobLog []database.ProvisionerJobLog - workspace []database.Workspace - workspaceAgent []database.WorkspaceAgent - workspaceHistory []database.WorkspaceHistory - workspaceResource []database.WorkspaceResource + files []database.File + parameterValue []database.ParameterValue + parameterSchema []database.ParameterSchema + project []database.Project + projectVersion []database.ProjectVersion + projectImportJobResource []database.ProjectImportJobResource + provisionerDaemons []database.ProvisionerDaemon + provisionerJobs []database.ProvisionerJob + provisionerJobLog []database.ProvisionerJobLog + workspace []database.Workspace + workspaceAgent []database.WorkspaceAgent + workspaceHistory []database.WorkspaceHistory + workspaceResource []database.WorkspaceResource } // InTx doesn't rollback data properly for in-memory yet. @@ -666,6 +668,23 @@ func (q *fakeQuerier) InsertProject(_ context.Context, arg database.InsertProjec return project, nil } +func (q *fakeQuerier) InsertProjectImportJobResource(ctx context.Context, arg database.InsertProjectImportJobResourceParams) (database.ProjectImportJobResource, error) { + q.mutex.Lock() + defer q.mutex.Unlock() + + //nolint:gosimple + projectResource := database.ProjectImportJobResource{ + ID: arg.ID, + CreatedAt: arg.CreatedAt, + JobID: arg.JobID, + Transition: arg.Transition, + Type: arg.Type, + Name: arg.Name, + } + q.projectImportJobResource = append(q.projectImportJobResource, projectResource) + return projectResource, nil +} + func (q *fakeQuerier) InsertProjectVersion(_ context.Context, arg database.InsertProjectVersionParams) (database.ProjectVersion, error) { q.mutex.Lock() defer q.mutex.Unlock() diff --git a/database/dump.sql b/database/dump.sql index 0fde40950ad7e..d0b1d194b3099 100644 --- a/database/dump.sql +++ b/database/dump.sql @@ -163,6 +163,15 @@ CREATE TABLE project ( active_version_id uuid NOT NULL ); +CREATE TABLE project_import_job_resource ( + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + job_id uuid NOT NULL, + transition workspace_transition NOT NULL, + type character varying(256) NOT NULL, + name character varying(64) NOT NULL +); + CREATE TABLE project_version ( id uuid NOT NULL, project_id uuid NOT NULL, @@ -291,6 +300,9 @@ ALTER TABLE ONLY parameter_value ALTER TABLE ONLY project ADD CONSTRAINT project_id_key UNIQUE (id); +ALTER TABLE ONLY project_import_job_resource + ADD CONSTRAINT project_import_job_resource_id_key UNIQUE (id); + ALTER TABLE ONLY project ADD CONSTRAINT project_organization_id_name_key UNIQUE (organization_id, name); @@ -339,6 +351,9 @@ ALTER TABLE ONLY workspace_resource ALTER TABLE ONLY parameter_schema ADD CONSTRAINT parameter_schema_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_job(id) ON DELETE CASCADE; +ALTER TABLE ONLY project_import_job_resource + ADD CONSTRAINT project_import_job_resource_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_job(id) ON DELETE CASCADE; + ALTER TABLE ONLY project_version ADD CONSTRAINT project_version_project_id_fkey FOREIGN KEY (project_id) REFERENCES project(id); diff --git a/database/migrations/000004_jobs.up.sql b/database/migrations/000004_jobs.up.sql index 436847e55d1fa..4d100e7f6fa80 100644 --- a/database/migrations/000004_jobs.up.sql +++ b/database/migrations/000004_jobs.up.sql @@ -119,3 +119,13 @@ CREATE TABLE parameter_value ( -- Prevents duplicates for parameters in the same scope. UNIQUE(name, scope, scope_id) ); + +-- Resources from multiple workspace states are stored here post project-import job. +CREATE TABLE project_import_job_resource ( + id uuid NOT NULL UNIQUE, + created_at timestamptz NOT NULL, + job_id uuid NOT NULL REFERENCES provisioner_job(id) ON DELETE CASCADE, + transition workspace_transition NOT NULL, + type varchar(256) NOT NULL, + name varchar(64) NOT NULL +); diff --git a/database/models.go b/database/models.go index ba4b57bbf0ed9..a0e60915feb23 100644 --- a/database/models.go +++ b/database/models.go @@ -342,6 +342,15 @@ type Project struct { ActiveVersionID uuid.UUID `db:"active_version_id" json:"active_version_id"` } +type ProjectImportJobResource struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + JobID uuid.UUID `db:"job_id" json:"job_id"` + Transition WorkspaceTransition `db:"transition" json:"transition"` + Type string `db:"type" json:"type"` + Name string `db:"name" json:"name"` +} + type ProjectVersion struct { ID uuid.UUID `db:"id" json:"id"` ProjectID uuid.UUID `db:"project_id" json:"project_id"` diff --git a/database/querier.go b/database/querier.go index 05fc044eeb95b..d9586447b9f57 100644 --- a/database/querier.go +++ b/database/querier.go @@ -48,6 +48,7 @@ type querier interface { InsertParameterSchema(ctx context.Context, arg InsertParameterSchemaParams) (ParameterSchema, error) InsertParameterValue(ctx context.Context, arg InsertParameterValueParams) (ParameterValue, error) InsertProject(ctx context.Context, arg InsertProjectParams) (Project, error) + InsertProjectImportJobResource(ctx context.Context, arg InsertProjectImportJobResourceParams) (ProjectImportJobResource, error) InsertProjectVersion(ctx context.Context, arg InsertProjectVersionParams) (ProjectVersion, error) InsertProvisionerDaemon(ctx context.Context, arg InsertProvisionerDaemonParams) (ProvisionerDaemon, error) InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error) diff --git a/database/query.sql b/database/query.sql index 0330df32aba12..b1c443e934df9 100644 --- a/database/query.sql +++ b/database/query.sql @@ -427,6 +427,12 @@ INSERT INTO VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *; +-- name: InsertProjectImportJobResource :one +INSERT INTO + project_import_job_resource (id, created_at, job_id, transition, type, name) +VALUES + ($1, $2, $3, $4, $5, $6) RETURNING *; + -- name: InsertProjectVersion :one INSERT INTO project_version ( diff --git a/database/query.sql.go b/database/query.sql.go index ed3bd76e7649c..71ff91f091f79 100644 --- a/database/query.sql.go +++ b/database/query.sql.go @@ -1569,6 +1569,43 @@ func (q *sqlQuerier) InsertProject(ctx context.Context, arg InsertProjectParams) return i, err } +const insertProjectImportJobResource = `-- name: InsertProjectImportJobResource :one +INSERT INTO + project_import_job_resource (id, created_at, job_id, transition, type, name) +VALUES + ($1, $2, $3, $4, $5, $6) RETURNING id, created_at, job_id, transition, type, name +` + +type InsertProjectImportJobResourceParams struct { + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + JobID uuid.UUID `db:"job_id" json:"job_id"` + Transition WorkspaceTransition `db:"transition" json:"transition"` + Type string `db:"type" json:"type"` + Name string `db:"name" json:"name"` +} + +func (q *sqlQuerier) InsertProjectImportJobResource(ctx context.Context, arg InsertProjectImportJobResourceParams) (ProjectImportJobResource, error) { + row := q.db.QueryRowContext(ctx, insertProjectImportJobResource, + arg.ID, + arg.CreatedAt, + arg.JobID, + arg.Transition, + arg.Type, + arg.Name, + ) + var i ProjectImportJobResource + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.JobID, + &i.Transition, + &i.Type, + &i.Name, + ) + return i, err +} + const insertProjectVersion = `-- name: InsertProjectVersion :one INSERT INTO project_version ( From 79a56b6d293033d66488e1187c77aded894e5abb Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 22:03:04 +0000 Subject: [PATCH 08/20] Basic creation flow works! --- cli/projectcreate.go | 72 +++-- cli/root.go | 10 + coderd/coderd.go | 1 + coderd/provisionerdaemons.go | 24 ++ coderd/provisionerjobs.go | 33 ++- coderd/provisionerjobs_test.go | 37 +++ codersdk/provisioners.go | 13 + database/databasefake/databasefake.go | 17 ++ database/querier.go | 1 + database/query.sql | 8 + database/query.sql.go | 39 +++ provisionerd/proto/provisionerd.pb.go | 377 ++++++++++---------------- provisionerd/provisionerd.go | 5 +- 13 files changed, 377 insertions(+), 260 deletions(-) diff --git a/cli/projectcreate.go b/cli/projectcreate.go index 48b0ea3040548..00da8d0e39143 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -11,6 +11,7 @@ import ( "github.com/briandowns/spinner" "github.com/fatih/color" + "github.com/google/uuid" "github.com/manifoldco/promptui" "github.com/spf13/cobra" "golang.org/x/xerrors" @@ -18,6 +19,7 @@ import ( "github.com/coder/coder/coderd" "github.com/coder/coder/codersdk" "github.com/coder/coder/database" + "github.com/coder/coder/provisionerd" ) func projectCreate() *cobra.Command { @@ -49,8 +51,8 @@ func projectCreate() *cobra.Command { Default: filepath.Base(directory), Label: "What's your project's name?", Validate: func(s string) error { - _, err = client.Project(cmd.Context(), organization.Name, s) - if err == nil { + project, _ := client.Project(cmd.Context(), organization.Name, s) + if project.ID.String() != uuid.Nil.String() { return xerrors.New("A project already exists with that name!") } return nil @@ -63,6 +65,7 @@ func projectCreate() *cobra.Command { spin := spinner.New(spinner.CharSets[0], 25*time.Millisecond) spin.Suffix = " Uploading current directory..." spin.Start() + defer spin.Stop() bytes, err := tarDirectory(directory) @@ -79,14 +82,6 @@ func projectCreate() *cobra.Command { StorageMethod: database.ProvisionerStorageMethodFile, StorageSource: resp.Hash, Provisioner: database.ProvisionerTypeTerraform, - // SkipResources on first import to detect variables defined by the project. - SkipResources: true, - // ParameterValues: []coderd.CreateParameterValueRequest{{ - // Name: "aws_access_key", - // SourceValue: "tomato", - // SourceScheme: database.ParameterSourceSchemeData, - // DestinationScheme: database.ParameterDestinationSchemeProvisionerVariable, - // }}, }) if err != nil { return err @@ -102,33 +97,60 @@ func projectCreate() *cobra.Command { if !ok { break } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[parse]"), log.Output) + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[tf]"), log.Output) } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Parsed project source... displaying parameters:") - - schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) + job, err = client.ProvisionerJob(cmd.Context(), organization.Name, job.ID) if err != nil { return err } - values, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, job.ID) + if provisionerd.IsMissingParameterError(job.Error) { + fmt.Printf("Missing something!\n") + return nil + } + + resources, err := client.ProvisionerJobResources(cmd.Context(), organization.Name, job.ID) if err != nil { return err } - valueBySchemaID := map[string]coderd.ComputedParameterValue{} - for _, value := range values { - valueBySchemaID[value.SchemaID.String()] = value - } - for _, schema := range schemas { - if value, ok := valueBySchemaID[schema.ID.String()]; ok { - fmt.Printf("Value for: %s %s\n", value.Name, value.SourceValue) - continue - } - fmt.Printf("No value for: %s\n", schema.Name) + fmt.Printf("Resources: %+v\n", resources) + + project, err := client.CreateProject(cmd.Context(), organization.Name, coderd.CreateProjectRequest{ + Name: name, + VersionImportJobID: job.ID, + }) + if err != nil { + return err } + fmt.Printf("Project: %+v\n", project) + + // _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Parsed project source... displaying parameters:") + + // schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) + // if err != nil { + // return err + // } + + // values, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, job.ID) + // if err != nil { + // return err + // } + // valueBySchemaID := map[string]coderd.ComputedParameterValue{} + // for _, value := range values { + // valueBySchemaID[value.SchemaID.String()] = value + // } + + // for _, schema := range schemas { + // if value, ok := valueBySchemaID[schema.ID.String()]; ok { + // fmt.Printf("Value for: %s %s\n", value.Name, value.SourceValue) + // continue + // } + // fmt.Printf("No value for: %s\n", schema.Name) + // } + // schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) // if err != nil { // return err diff --git a/cli/root.go b/cli/root.go index 85db65385291a..29ef8e9609b65 100644 --- a/cli/root.go +++ b/cli/root.go @@ -161,6 +161,16 @@ func runPrompt(cmd *cobra.Command, prompt *promptui.Prompt) (string, error) { Invalid: invalid, Valid: valid, } + oldValidate := prompt.Validate + if oldValidate != nil { + // Override the validate function to pass our default! + prompt.Validate = func(s string) error { + if s == "" { + s = defaultValue + } + return oldValidate(s) + } + } value, err := prompt.Run() if value == "" && !prompt.IsConfirm { value = defaultValue diff --git a/coderd/coderd.go b/coderd/coderd.go index 3bd7ff54404de..0a2a9d87e51a8 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -123,6 +123,7 @@ func New(options *Options) http.Handler { r.Get("/", api.provisionerJobByOrganization) r.Get("/schemas", api.provisionerJobParameterSchemasByID) r.Get("/computed", api.provisionerJobComputedParametersByID) + r.Get("/resources", api.provisionerJobResourcesByID) r.Get("/logs", api.provisionerJobLogsByID) }) }) diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 4e4e81ea9ea7e..f98a73a962126 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -437,6 +437,30 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr switch jobType := completed.Type.(type) { case *proto.CompletedJob_ProjectImport_: + for transition, resources := range map[database.WorkspaceTransition][]*sdkproto.Resource{ + database.WorkspaceTransitionStart: jobType.ProjectImport.StartResources, + database.WorkspaceTransitionStop: jobType.ProjectImport.StopResources, + } { + for _, resource := range resources { + server.Logger.Info(ctx, "inserting project import job resource", + slog.F("job_id", job.ID.String()), + slog.F("resource_name", resource.Name), + slog.F("resource_type", resource.Type), + slog.F("transition", transition)) + _, err = server.Database.InsertProjectImportJobResource(ctx, database.InsertProjectImportJobResourceParams{ + ID: uuid.New(), + CreatedAt: database.Now(), + JobID: jobID, + Transition: transition, + Type: resource.Type, + Name: resource.Name, + }) + if err != nil { + return nil, xerrors.Errorf("insert resource: %w", err) + } + } + } + err = server.Database.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{ ID: jobID, UpdatedAt: database.Now(), diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index dce331d153e6a..9c000280f7228 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -16,6 +16,8 @@ import ( "github.com/coder/coder/httpmw" ) +type ProjectImportJobResource database.ProjectImportJobResource + type ProvisionerJobStatus string // Completed returns whether the job is still processing. @@ -125,9 +127,9 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r // Return parsed parameter schemas for a job. func (api *api) provisionerJobParameterSchemasByID(rw http.ResponseWriter, r *http.Request) { job := httpmw.ProvisionerJobParam(r) - if convertProvisionerJob(job).Status != ProvisionerJobStatusSucceeded { + if !convertProvisionerJob(job).Status.Completed() { httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ - Message: fmt.Sprintf("Job is in state %q! Must be %q.", convertProvisionerJob(job).Status, ProvisionerJobStatusSucceeded), + Message: "Job hasn't completed!", }) return } @@ -150,9 +152,9 @@ func (api *api) provisionerJobParameterSchemasByID(rw http.ResponseWriter, r *ht func (api *api) provisionerJobComputedParametersByID(rw http.ResponseWriter, r *http.Request) { apiKey := httpmw.APIKey(r) job := httpmw.ProvisionerJobParam(r) - if convertProvisionerJob(job).Status != ProvisionerJobStatusSucceeded { + if !convertProvisionerJob(job).Status.Completed() { httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ - Message: fmt.Sprintf("Job is in state %q! Must be %q.", convertProvisionerJob(job).Status, ProvisionerJobStatusSucceeded), + Message: "Job hasn't completed!", }) return } @@ -163,6 +165,29 @@ func (api *api) provisionerJobComputedParametersByID(rw http.ResponseWriter, r * }) } +func (api *api) provisionerJobResourcesByID(rw http.ResponseWriter, r *http.Request) { + job := httpmw.ProvisionerJobParam(r) + if !convertProvisionerJob(job).Status.Completed() { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: "Job hasn't completed!", + }) + return + } + resources, err := api.Database.GetProjectImportJobResourcesByJobID(r.Context(), job.ID) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get project import job resources: %s", err), + }) + return + } + + render.Status(r, http.StatusOK) + render.JSON(rw, r, resources) +} + func convertProvisionerJob(provisionerJob database.ProvisionerJob) ProvisionerJob { job := ProvisionerJob{ ID: provisionerJob.ID, diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index f0bb167d5e9f5..bb4831362a3bf 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -10,6 +10,7 @@ import ( "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/codersdk" "github.com/coder/coder/database" "github.com/coder/coder/provisioner/echo" @@ -170,3 +171,39 @@ func TestProvisionerJobParametersByID(t *testing.T) { require.Equal(t, params[0].SourceValue, "") }) } + +func TestProvisionerJobResourcesByID(t *testing.T) { + t.Parallel() + t.Run("Something", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: parameter.CoderWorkspaceTransition, + }}, + }, + }, + }}, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "hello", + Type: "ec2_instance", + }}, + }, + }, + }}, + }) + coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + resources, err := client.ProvisionerJobResources(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + // One for start, and one for stop! + require.Len(t, resources, 2) + }) +} diff --git a/codersdk/provisioners.go b/codersdk/provisioners.go index ddb84b9b34688..c77df205686b1 100644 --- a/codersdk/provisioners.go +++ b/codersdk/provisioners.go @@ -179,3 +179,16 @@ func (c *Client) ProvisionerJobParameterValues(ctx context.Context, organization var params []coderd.ComputedParameterValue return params, json.NewDecoder(res.Body).Decode(¶ms) } + +func (c *Client) ProvisionerJobResources(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ProjectImportJobResource, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/resources", organization, job), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, readBodyAsError(res) + } + var resources []coderd.ProjectImportJobResource + return resources, json.NewDecoder(res.Body).Decode(&resources) +} diff --git a/database/databasefake/databasefake.go b/database/databasefake/databasefake.go index bd0ac62b92fb1..a0a2f4359839e 100644 --- a/database/databasefake/databasefake.go +++ b/database/databasefake/databasefake.go @@ -401,6 +401,23 @@ func (q *fakeQuerier) GetProjectByOrganizationAndName(_ context.Context, arg dat return database.Project{}, sql.ErrNoRows } +func (q *fakeQuerier) GetProjectImportJobResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]database.ProjectImportJobResource, error) { + q.mutex.Lock() + defer q.mutex.Unlock() + + resources := make([]database.ProjectImportJobResource, 0) + for _, resource := range q.projectImportJobResource { + if resource.JobID.String() != jobID.String() { + continue + } + resources = append(resources, resource) + } + if len(resources) == 0 { + return nil, sql.ErrNoRows + } + return resources, nil +} + func (q *fakeQuerier) GetProjectVersionsByProjectID(_ context.Context, projectID uuid.UUID) ([]database.ProjectVersion, error) { q.mutex.Lock() defer q.mutex.Unlock() diff --git a/database/querier.go b/database/querier.go index d9586447b9f57..93ba57d931e93 100644 --- a/database/querier.go +++ b/database/querier.go @@ -20,6 +20,7 @@ type querier interface { GetParameterValuesByScope(ctx context.Context, arg GetParameterValuesByScopeParams) ([]ParameterValue, error) GetProjectByID(ctx context.Context, id uuid.UUID) (Project, error) GetProjectByOrganizationAndName(ctx context.Context, arg GetProjectByOrganizationAndNameParams) (Project, error) + GetProjectImportJobResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]ProjectImportJobResource, error) GetProjectVersionByID(ctx context.Context, id uuid.UUID) (ProjectVersion, error) GetProjectVersionByProjectIDAndName(ctx context.Context, arg GetProjectVersionByProjectIDAndNameParams) (ProjectVersion, error) GetProjectVersionsByProjectID(ctx context.Context, projectID uuid.UUID) ([]ProjectVersion, error) diff --git a/database/query.sql b/database/query.sql index b1c443e934df9..2bffab68704d2 100644 --- a/database/query.sql +++ b/database/query.sql @@ -173,6 +173,14 @@ FROM WHERE job_id = $1; +-- name: GetProjectImportJobResourcesByJobID :many +SELECT + * +FROM + project_import_job_resource +WHERE + job_id = $1; + -- name: GetProjectVersionsByProjectID :many SELECT * diff --git a/database/query.sql.go b/database/query.sql.go index 71ff91f091f79..5b2f3d8bae011 100644 --- a/database/query.sql.go +++ b/database/query.sql.go @@ -424,6 +424,45 @@ func (q *sqlQuerier) GetProjectByOrganizationAndName(ctx context.Context, arg Ge return i, err } +const getProjectImportJobResourcesByJobID = `-- name: GetProjectImportJobResourcesByJobID :many +SELECT + id, created_at, job_id, transition, type, name +FROM + project_import_job_resource +WHERE + job_id = $1 +` + +func (q *sqlQuerier) GetProjectImportJobResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]ProjectImportJobResource, error) { + rows, err := q.db.QueryContext(ctx, getProjectImportJobResourcesByJobID, jobID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ProjectImportJobResource + for rows.Next() { + var i ProjectImportJobResource + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.JobID, + &i.Transition, + &i.Type, + &i.Name, + ); 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 getProjectVersionByID = `-- name: GetProjectVersionByID :one SELECT id, project_id, created_at, updated_at, name, description, import_job_id diff --git a/provisionerd/proto/provisionerd.pb.go b/provisionerd/proto/provisionerd.pb.go index 0322a2f163b05..1c3db72fd341b 100644 --- a/provisionerd/proto/provisionerd.pb.go +++ b/provisionerd/proto/provisionerd.pb.go @@ -283,66 +283,6 @@ func (x *CancelledJob) GetError() string { return "" } -// TransitionedResource represents a resource that knows whether -// it's existence is dependent on stop or not. -// -// This is used on import to display start + stopped resources -// for the lifecycle of a workspace. -type TransitionedResource struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Resource *proto.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` - DestroyOnStop bool `protobuf:"varint,2,opt,name=destroy_on_stop,json=destroyOnStop,proto3" json:"destroy_on_stop,omitempty"` -} - -func (x *TransitionedResource) Reset() { - *x = TransitionedResource{} - if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *TransitionedResource) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TransitionedResource) ProtoMessage() {} - -func (x *TransitionedResource) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[3] - 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 TransitionedResource.ProtoReflect.Descriptor instead. -func (*TransitionedResource) Descriptor() ([]byte, []int) { - return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{3} -} - -func (x *TransitionedResource) GetResource() *proto.Resource { - if x != nil { - return x.Resource - } - return nil -} - -func (x *TransitionedResource) GetDestroyOnStop() bool { - if x != nil { - return x.DestroyOnStop - } - return false -} - // CompletedJob is sent when the provisioner daemon completes a job. type CompletedJob struct { state protoimpl.MessageState @@ -359,7 +299,7 @@ type CompletedJob struct { func (x *CompletedJob) Reset() { *x = CompletedJob{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[4] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -372,7 +312,7 @@ func (x *CompletedJob) String() string { func (*CompletedJob) ProtoMessage() {} func (x *CompletedJob) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[4] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -385,7 +325,7 @@ func (x *CompletedJob) ProtoReflect() protoreflect.Message { // Deprecated: Use CompletedJob.ProtoReflect.Descriptor instead. func (*CompletedJob) Descriptor() ([]byte, []int) { - return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{4} + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{3} } func (x *CompletedJob) GetJobId() string { @@ -447,7 +387,7 @@ type Log struct { func (x *Log) Reset() { *x = Log{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[5] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -460,7 +400,7 @@ func (x *Log) String() string { func (*Log) ProtoMessage() {} func (x *Log) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[5] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -473,7 +413,7 @@ func (x *Log) ProtoReflect() protoreflect.Message { // Deprecated: Use Log.ProtoReflect.Descriptor instead. func (*Log) Descriptor() ([]byte, []int) { - return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{5} + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{4} } func (x *Log) GetSource() LogSource { @@ -518,7 +458,7 @@ type UpdateJobRequest struct { func (x *UpdateJobRequest) Reset() { *x = UpdateJobRequest{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[6] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -531,7 +471,7 @@ func (x *UpdateJobRequest) String() string { func (*UpdateJobRequest) ProtoMessage() {} func (x *UpdateJobRequest) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[6] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -544,7 +484,7 @@ func (x *UpdateJobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateJobRequest.ProtoReflect.Descriptor instead. func (*UpdateJobRequest) Descriptor() ([]byte, []int) { - return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{6} + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{5} } func (x *UpdateJobRequest) GetJobId() string { @@ -581,7 +521,7 @@ type UpdateJobResponse struct { func (x *UpdateJobResponse) Reset() { *x = UpdateJobResponse{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -594,7 +534,7 @@ func (x *UpdateJobResponse) String() string { func (*UpdateJobResponse) ProtoMessage() {} func (x *UpdateJobResponse) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -607,7 +547,7 @@ func (x *UpdateJobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateJobResponse.ProtoReflect.Descriptor instead. func (*UpdateJobResponse) Descriptor() ([]byte, []int) { - return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{7} + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{6} } func (x *UpdateJobResponse) GetParameterValues() []*proto.ParameterValue { @@ -631,7 +571,7 @@ type AcquiredJob_WorkspaceProvision struct { func (x *AcquiredJob_WorkspaceProvision) Reset() { *x = AcquiredJob_WorkspaceProvision{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -644,7 +584,7 @@ func (x *AcquiredJob_WorkspaceProvision) String() string { func (*AcquiredJob_WorkspaceProvision) ProtoMessage() {} func (x *AcquiredJob_WorkspaceProvision) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -697,7 +637,7 @@ type AcquiredJob_ProjectImport struct { func (x *AcquiredJob_ProjectImport) Reset() { *x = AcquiredJob_ProjectImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -710,7 +650,7 @@ func (x *AcquiredJob_ProjectImport) String() string { func (*AcquiredJob_ProjectImport) ProtoMessage() {} func (x *AcquiredJob_ProjectImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -738,7 +678,7 @@ type CompletedJob_WorkspaceProvision struct { func (x *CompletedJob_WorkspaceProvision) Reset() { *x = CompletedJob_WorkspaceProvision{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -751,7 +691,7 @@ func (x *CompletedJob_WorkspaceProvision) String() string { func (*CompletedJob_WorkspaceProvision) ProtoMessage() {} func (x *CompletedJob_WorkspaceProvision) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -764,7 +704,7 @@ func (x *CompletedJob_WorkspaceProvision) ProtoReflect() protoreflect.Message { // Deprecated: Use CompletedJob_WorkspaceProvision.ProtoReflect.Descriptor instead. func (*CompletedJob_WorkspaceProvision) Descriptor() ([]byte, []int) { - return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{4, 0} + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{3, 0} } func (x *CompletedJob_WorkspaceProvision) GetState() []byte { @@ -793,7 +733,7 @@ type CompletedJob_ProjectImport struct { func (x *CompletedJob_ProjectImport) Reset() { *x = CompletedJob_ProjectImport{} if protoimpl.UnsafeEnabled { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -806,7 +746,7 @@ func (x *CompletedJob_ProjectImport) String() string { func (*CompletedJob_ProjectImport) ProtoMessage() {} func (x *CompletedJob_ProjectImport) ProtoReflect() protoreflect.Message { - mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[11] + mi := &file_provisionerd_proto_provisionerd_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -819,7 +759,7 @@ func (x *CompletedJob_ProjectImport) ProtoReflect() protoreflect.Message { // Deprecated: Use CompletedJob_ProjectImport.ProtoReflect.Descriptor instead. func (*CompletedJob_ProjectImport) Descriptor() ([]byte, []int) { - return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{4, 1} + return file_provisionerd_proto_provisionerd_proto_rawDescGZIP(), []int{3, 1} } func (x *CompletedJob_ProjectImport) GetStartResources() []*proto.Resource { @@ -886,94 +826,87 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{ 0x61, 0x6e, 0x63, 0x65, 0x6c, 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, 0x22, 0x71, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, - 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, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x5f, 0x6f, - 0x6e, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x65, - 0x73, 0x74, 0x72, 0x6f, 0x79, 0x4f, 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x22, 0xd3, 0x03, 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, 0x60, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2d, 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, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x48, - 0x00, 0x52, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, - 0x5f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xd3, 0x03, 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, 0x60, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x5f, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 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, 0x8d, 0x01, 0x0a, 0x0d, 0x50, 0x72, - 0x6f, 0x6a, 0x65, 0x63, 0x74, 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, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x12, + 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 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, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x1a, 0x5f, 0x0a, 0x12, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 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, 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, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x9a, 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, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9b, - 0x01, 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, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x22, 0x5b, 0x0a, 0x11, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, - 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 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, - 0x9d, 0x02, 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x3c, 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, 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, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x8d, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6a, 0x65, + 0x63, 0x74, 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, + 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, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x9a, + 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, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x10, 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, 0x3c, 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x1a, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 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, - 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, - 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 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, + 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, 0x49, + 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, + 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x22, 0x5b, 0x0a, 0x11, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, + 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 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, 0x9d, 0x02, 0x0a, + 0x11, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x44, 0x61, 0x65, 0x6d, + 0x6f, 0x6e, 0x12, 0x3c, 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, + 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, 0x3c, + 0x0a, 0x09, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4a, 0x6f, 0x62, 0x12, 0x1a, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 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, 0x2b, 0x5a, 0x29, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, + 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 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 ( @@ -989,54 +922,52 @@ 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, 12) +var file_provisionerd_proto_provisionerd_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{ (LogSource)(0), // 0: provisionerd.LogSource (*Empty)(nil), // 1: provisionerd.Empty (*AcquiredJob)(nil), // 2: provisionerd.AcquiredJob (*CancelledJob)(nil), // 3: provisionerd.CancelledJob - (*TransitionedResource)(nil), // 4: provisionerd.TransitionedResource - (*CompletedJob)(nil), // 5: provisionerd.CompletedJob - (*Log)(nil), // 6: provisionerd.Log - (*UpdateJobRequest)(nil), // 7: provisionerd.UpdateJobRequest - (*UpdateJobResponse)(nil), // 8: provisionerd.UpdateJobResponse - (*AcquiredJob_WorkspaceProvision)(nil), // 9: provisionerd.AcquiredJob.WorkspaceProvision - (*AcquiredJob_ProjectImport)(nil), // 10: provisionerd.AcquiredJob.ProjectImport - (*CompletedJob_WorkspaceProvision)(nil), // 11: provisionerd.CompletedJob.WorkspaceProvision - (*CompletedJob_ProjectImport)(nil), // 12: provisionerd.CompletedJob.ProjectImport - (*proto.Resource)(nil), // 13: provisioner.Resource - (proto.LogLevel)(0), // 14: provisioner.LogLevel - (*proto.ParameterSchema)(nil), // 15: provisioner.ParameterSchema - (*proto.ParameterValue)(nil), // 16: provisioner.ParameterValue + (*CompletedJob)(nil), // 4: provisionerd.CompletedJob + (*Log)(nil), // 5: provisionerd.Log + (*UpdateJobRequest)(nil), // 6: provisionerd.UpdateJobRequest + (*UpdateJobResponse)(nil), // 7: provisionerd.UpdateJobResponse + (*AcquiredJob_WorkspaceProvision)(nil), // 8: provisionerd.AcquiredJob.WorkspaceProvision + (*AcquiredJob_ProjectImport)(nil), // 9: provisionerd.AcquiredJob.ProjectImport + (*CompletedJob_WorkspaceProvision)(nil), // 10: provisionerd.CompletedJob.WorkspaceProvision + (*CompletedJob_ProjectImport)(nil), // 11: provisionerd.CompletedJob.ProjectImport + (proto.LogLevel)(0), // 12: provisioner.LogLevel + (*proto.ParameterSchema)(nil), // 13: provisioner.ParameterSchema + (*proto.ParameterValue)(nil), // 14: provisioner.ParameterValue + (*proto.Resource)(nil), // 15: provisioner.Resource } var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{ - 9, // 0: provisionerd.AcquiredJob.workspace_provision:type_name -> provisionerd.AcquiredJob.WorkspaceProvision - 10, // 1: provisionerd.AcquiredJob.project_import:type_name -> provisionerd.AcquiredJob.ProjectImport - 13, // 2: provisionerd.TransitionedResource.resource:type_name -> provisioner.Resource - 11, // 3: provisionerd.CompletedJob.workspace_provision:type_name -> provisionerd.CompletedJob.WorkspaceProvision - 12, // 4: provisionerd.CompletedJob.project_import:type_name -> provisionerd.CompletedJob.ProjectImport - 0, // 5: provisionerd.Log.source:type_name -> provisionerd.LogSource - 14, // 6: provisionerd.Log.level:type_name -> provisioner.LogLevel - 6, // 7: provisionerd.UpdateJobRequest.logs:type_name -> provisionerd.Log - 15, // 8: provisionerd.UpdateJobRequest.parameter_schemas:type_name -> provisioner.ParameterSchema - 16, // 9: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue - 16, // 10: provisionerd.AcquiredJob.WorkspaceProvision.parameter_values:type_name -> provisioner.ParameterValue - 13, // 11: provisionerd.CompletedJob.WorkspaceProvision.resources:type_name -> provisioner.Resource - 13, // 12: provisionerd.CompletedJob.ProjectImport.start_resources:type_name -> provisioner.Resource - 13, // 13: provisionerd.CompletedJob.ProjectImport.stop_resources:type_name -> provisioner.Resource - 1, // 14: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty - 7, // 15: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest - 3, // 16: provisionerd.ProvisionerDaemon.CancelJob:input_type -> provisionerd.CancelledJob - 5, // 17: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob - 2, // 18: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob - 8, // 19: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse - 1, // 20: provisionerd.ProvisionerDaemon.CancelJob:output_type -> provisionerd.Empty - 1, // 21: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty - 18, // [18:22] is the sub-list for method output_type - 14, // [14:18] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 8, // 0: provisionerd.AcquiredJob.workspace_provision:type_name -> provisionerd.AcquiredJob.WorkspaceProvision + 9, // 1: provisionerd.AcquiredJob.project_import:type_name -> provisionerd.AcquiredJob.ProjectImport + 10, // 2: provisionerd.CompletedJob.workspace_provision:type_name -> provisionerd.CompletedJob.WorkspaceProvision + 11, // 3: provisionerd.CompletedJob.project_import:type_name -> provisionerd.CompletedJob.ProjectImport + 0, // 4: provisionerd.Log.source:type_name -> provisionerd.LogSource + 12, // 5: provisionerd.Log.level:type_name -> provisioner.LogLevel + 5, // 6: provisionerd.UpdateJobRequest.logs:type_name -> provisionerd.Log + 13, // 7: provisionerd.UpdateJobRequest.parameter_schemas:type_name -> provisioner.ParameterSchema + 14, // 8: provisionerd.UpdateJobResponse.parameter_values:type_name -> provisioner.ParameterValue + 14, // 9: provisionerd.AcquiredJob.WorkspaceProvision.parameter_values:type_name -> provisioner.ParameterValue + 15, // 10: provisionerd.CompletedJob.WorkspaceProvision.resources:type_name -> provisioner.Resource + 15, // 11: provisionerd.CompletedJob.ProjectImport.start_resources:type_name -> provisioner.Resource + 15, // 12: provisionerd.CompletedJob.ProjectImport.stop_resources:type_name -> provisioner.Resource + 1, // 13: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty + 6, // 14: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest + 3, // 15: provisionerd.ProvisionerDaemon.CancelJob:input_type -> provisionerd.CancelledJob + 4, // 16: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob + 2, // 17: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob + 7, // 18: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse + 1, // 19: provisionerd.ProvisionerDaemon.CancelJob:output_type -> provisionerd.Empty + 1, // 20: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty + 17, // [17:21] is the sub-list for method output_type + 13, // [13:17] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_provisionerd_proto_provisionerd_proto_init() } @@ -1082,18 +1013,6 @@ func file_provisionerd_proto_provisionerd_proto_init() { } } file_provisionerd_proto_provisionerd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TransitionedResource); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_provisionerd_proto_provisionerd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob); i { case 0: return &v.state @@ -1105,7 +1024,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[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Log); i { case 0: return &v.state @@ -1117,7 +1036,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[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateJobRequest); i { case 0: return &v.state @@ -1129,7 +1048,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[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateJobResponse); i { case 0: return &v.state @@ -1141,7 +1060,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[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcquiredJob_WorkspaceProvision); i { case 0: return &v.state @@ -1153,7 +1072,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[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcquiredJob_ProjectImport); i { case 0: return &v.state @@ -1165,7 +1084,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[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_WorkspaceProvision); i { case 0: return &v.state @@ -1177,7 +1096,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[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CompletedJob_ProjectImport); i { case 0: return &v.state @@ -1194,7 +1113,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { (*AcquiredJob_WorkspaceProvision_)(nil), (*AcquiredJob_ProjectImport_)(nil), } - file_provisionerd_proto_provisionerd_proto_msgTypes[4].OneofWrappers = []interface{}{ + file_provisionerd_proto_provisionerd_proto_msgTypes[3].OneofWrappers = []interface{}{ (*CompletedJob_WorkspaceProvision_)(nil), (*CompletedJob_ProjectImport_)(nil), } @@ -1204,7 +1123,7 @@ func file_provisionerd_proto_provisionerd_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionerd_proto_provisionerd_proto_rawDesc, NumEnums: 1, - NumMessages: 12, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index bf50a8cc3281d..4c438d7360f66 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -20,6 +20,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/coderd/parameter" + "github.com/coder/coder/database" "github.com/coder/coder/provisionerd/proto" sdkproto "github.com/coder/coder/provisionersdk/proto" "github.com/coder/retry" @@ -391,7 +392,7 @@ func (p *provisionerDaemon) runProjectImport(ctx context.Context, provisioner sd startParameters = append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, Name: parameter.CoderWorkspaceTransition, - Value: "start", + Value: string(database.WorkspaceTransitionStart), }) } startResources, err := p.runProjectImportProvision(ctx, provisioner, job, startParameters) @@ -404,7 +405,7 @@ func (p *provisionerDaemon) runProjectImport(ctx context.Context, provisioner sd stopResources, err = p.runProjectImportProvision(ctx, provisioner, job, append(updateResponse.ParameterValues, &sdkproto.ParameterValue{ DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, Name: "coder_workspace_transition", - Value: "stop", + Value: string(database.WorkspaceTransitionStop), })) if err != nil { p.cancelActiveJobf("project import provision for start: %s", err) From dc86c0e8cf1d221a2f5c81a4b0701ab7a43d25fa Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 10 Feb 2022 23:06:07 +0000 Subject: [PATCH 09/20] Create project fully works!!! --- cli/projectcreate.go | 273 ++++++++++++++------------ provisioner/terraform/parse.go | 6 +- provisioner/terraform/parse_test.go | 9 +- provisionerd/proto/provisionerd.proto | 10 - 4 files changed, 162 insertions(+), 136 deletions(-) diff --git a/cli/projectcreate.go b/cli/projectcreate.go index 00da8d0e39143..fdfd8c43b489f 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "strings" "time" "github.com/briandowns/spinner" @@ -14,9 +15,11 @@ import ( "github.com/google/uuid" "github.com/manifoldco/promptui" "github.com/spf13/cobra" + "github.com/xlab/treeprint" "golang.org/x/xerrors" "github.com/coder/coder/coderd" + "github.com/coder/coder/coderd/parameter" "github.com/coder/coder/codersdk" "github.com/coder/coder/database" "github.com/coder/coder/provisionerd" @@ -62,150 +65,178 @@ func projectCreate() *cobra.Command { return err } - spin := spinner.New(spinner.CharSets[0], 25*time.Millisecond) - spin.Suffix = " Uploading current directory..." - spin.Start() - - defer spin.Stop() - - bytes, err := tarDirectory(directory) + job, err := doProjectLoop(cmd, client, organization, directory, []coderd.CreateParameterValueRequest{}) if err != nil { return err } - - resp, err := client.UploadFile(cmd.Context(), codersdk.ContentTypeTar, bytes) + project, err := client.CreateProject(cmd.Context(), organization.Name, coderd.CreateProjectRequest{ + Name: name, + VersionImportJobID: job.ID, + }) if err != nil { return err } - job, err := client.CreateProjectVersionImportProvisionerJob(cmd.Context(), organization.Name, coderd.CreateProjectImportJobRequest{ - StorageMethod: database.ProvisionerStorageMethodFile, - StorageSource: resp.Hash, - Provisioner: database.ProvisionerTypeTerraform, + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s The %s project has been created!\n", color.HiBlackString(">"), color.HiCyanString(project.Name)) + _, err = runPrompt(cmd, &promptui.Prompt{ + Label: "Create a new workspace?", + IsConfirm: true, + Default: "y", }) if err != nil { return err } - spin.Stop() - logs, err := client.FollowProvisionerJobLogsAfter(cmd.Context(), organization.Name, job.ID, time.Time{}) - if err != nil { - return err - } - for { - log, ok := <-logs - if !ok { - break - } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[tf]"), log.Output) - } + fmt.Printf("Create a new workspace now!\n") + return nil + }, + } + currentDirectory, _ := os.Getwd() + cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from") - job, err = client.ProvisionerJob(cmd.Context(), organization.Name, job.ID) - if err != nil { - return err - } + return cmd +} - if provisionerd.IsMissingParameterError(job.Error) { - fmt.Printf("Missing something!\n") - return nil - } +func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization coderd.Organization, directory string, params []coderd.CreateParameterValueRequest) (*coderd.ProvisionerJob, error) { + spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond) + spin.Writer = cmd.OutOrStdout() + spin.Suffix = " Uploading current directory..." + spin.Color("fgHiGreen") + spin.Start() + defer spin.Stop() - resources, err := client.ProvisionerJobResources(cmd.Context(), organization.Name, job.ID) - if err != nil { - return err - } + bytes, err := tarDirectory(directory) + if err != nil { + return nil, err + } + + resp, err := client.UploadFile(cmd.Context(), codersdk.ContentTypeTar, bytes) + if err != nil { + return nil, err + } - fmt.Printf("Resources: %+v\n", resources) + job, err := client.CreateProjectVersionImportProvisionerJob(cmd.Context(), organization.Name, coderd.CreateProjectImportJobRequest{ + StorageMethod: database.ProvisionerStorageMethodFile, + StorageSource: resp.Hash, + Provisioner: database.ProvisionerTypeTerraform, + ParameterValues: params, + }) + if err != nil { + return nil, err + } - project, err := client.CreateProject(cmd.Context(), organization.Name, coderd.CreateProjectRequest{ - Name: name, - VersionImportJobID: job.ID, + spin.Suffix = " Waiting for the import to complete..." + + logs, err := client.FollowProvisionerJobLogsAfter(cmd.Context(), organization.Name, job.ID, time.Time{}) + if err != nil { + return nil, err + } + for { + _, ok := <-logs + if !ok { + break + } + // _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[tf]"), log.Output) + } + + job, err = client.ProvisionerJob(cmd.Context(), organization.Name, job.ID) + if err != nil { + return nil, err + } + + parameterSchemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) + if err != nil { + return nil, err + } + parameterValues, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, job.ID) + if err != nil { + return nil, err + } + spin.Stop() + + if provisionerd.IsMissingParameterError(job.Error) { + valuesBySchemaID := map[string]coderd.ComputedParameterValue{} + for _, parameterValue := range parameterValues { + valuesBySchemaID[parameterValue.SchemaID.String()] = parameterValue + } + for _, parameterSchema := range parameterSchemas { + _, ok := valuesBySchemaID[parameterSchema.ID.String()] + if ok { + continue + } + if parameterSchema.Name == parameter.CoderWorkspaceTransition { + continue + } + value, err := runPrompt(cmd, &promptui.Prompt{ + Label: fmt.Sprintf("Enter value for %s:", color.HiCyanString(parameterSchema.Name)), }) if err != nil { - return err + return nil, err } + params = append(params, coderd.CreateParameterValueRequest{ + Name: parameterSchema.Name, + SourceValue: value, + SourceScheme: database.ParameterSourceSchemeData, + DestinationScheme: parameterSchema.DefaultDestinationScheme, + }) + } + return doProjectLoop(cmd, client, organization, directory, params) + } - fmt.Printf("Project: %+v\n", project) - - // _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Parsed project source... displaying parameters:") - - // schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) - // if err != nil { - // return err - // } - - // values, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, job.ID) - // if err != nil { - // return err - // } - // valueBySchemaID := map[string]coderd.ComputedParameterValue{} - // for _, value := range values { - // valueBySchemaID[value.SchemaID.String()] = value - // } - - // for _, schema := range schemas { - // if value, ok := valueBySchemaID[schema.ID.String()]; ok { - // fmt.Printf("Value for: %s %s\n", value.Name, value.SourceValue) - // continue - // } - // fmt.Printf("No value for: %s\n", schema.Name) - // } - - // schemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) - // if err != nil { - // return err - // } - // _, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n %s\n\n", color.HiBlackString("Parameters")) - - // for _, param := range params { - // if param.Value == nil { - // _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s = must be set\n", color.HiRedString(param.Schema.Name)) - // continue - // } - // value := param.Value.DestinationValue - // if !param.Schema.RedisplayValue { - // value = "" - // } - // output := fmt.Sprintf(" %s = %s", color.HiGreenString(param.Value.SourceValue), color.CyanString(value)) - // param.Value.DefaultSourceValue = false - // param.Value.Scope = database.ParameterScopeOrganization - // param.Value.ScopeID = organization.ID - // if param.Value.DefaultSourceValue { - // output += " (default value)" - // } else { - // output += fmt.Sprintf(" (inherited from %s)", param.Value.Scope) - // } - // root := treeprint.NewWithRoot(output) - // root.AddNode(color.HiBlackString("Description") + "\n" + param.Schema.Description) - // fmt.Fprintln(cmd.OutOrStdout(), strings.Join(strings.Split(root.String(), "\n"), "\n ")) - // } - - // for _, param := range params { - // if param.Value != nil { - // continue - // } - - // value, err := runPrompt(cmd, &promptui.Prompt{ - // Label: "Specify value for " + color.HiCyanString(param.Schema.Name), - // Validate: func(s string) error { - // // param.Schema.Vali - // return nil - // }, - // }) - // if err != nil { - // continue - // } - // fmt.Printf(": %s\n", value) - // } - - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Create project %q!\n", name) - return nil - }, + if job.Status != coderd.ProvisionerJobStatusSucceeded { + return nil, xerrors.New(job.Error) } - currentDirectory, _ := os.Getwd() - cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from") - return cmd + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Successfully imported project source!\n", color.HiGreenString("✓")) + + resources, err := client.ProvisionerJobResources(cmd.Context(), organization.Name, job.ID) + if err != nil { + return nil, err + } + return &job, outputProjectInformation(cmd, parameterSchemas, parameterValues, resources) +} + +func outputProjectInformation(cmd *cobra.Command, parameterSchemas []coderd.ParameterSchema, parameterValues []coderd.ComputedParameterValue, resources []coderd.ProjectImportJobResource) error { + schemaByID := map[string]coderd.ParameterSchema{} + for _, schema := range parameterSchemas { + schemaByID[schema.ID.String()] = schema + } + + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n %s\n\n", color.HiBlackString("Parameters")) + for _, value := range parameterValues { + schema, ok := schemaByID[value.SchemaID.String()] + if !ok { + return xerrors.Errorf("schema not found: %s", value.Name) + } + displayValue := value.SourceValue + if !schema.RedisplayValue { + displayValue = "" + } + output := fmt.Sprintf("%s %s %s", color.HiCyanString(value.Name), color.HiBlackString("="), displayValue) + if value.DefaultSourceValue { + output += " (default value)" + } else if value.Scope != database.ParameterScopeImportJob { + output += fmt.Sprintf(" (inherited from %s)", value.Scope) + } + + root := treeprint.NewWithRoot(output) + if schema.Description != "" { + root.AddBranch(fmt.Sprintf("%s\n%s\n", color.HiBlackString("Description"), schema.Description)) + } + if schema.AllowOverrideSource { + root.AddBranch(fmt.Sprintf("%s Users can customize this value!", color.HiYellowString("+"))) + } + _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.Join(strings.Split(root.String(), "\n"), "\n ")) + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s\n\n", color.HiBlackString("Resources")) + for _, resource := range resources { + transition := color.HiGreenString("start") + if resource.Transition == database.WorkspaceTransitionStop { + transition = color.HiRedString("stop") + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s %s on %s\n\n", color.HiCyanString(resource.Type), color.HiCyanString(resource.Name), transition) + } + return nil } func tarDirectory(directory string) ([]byte, error) { diff --git a/provisioner/terraform/parse.go b/provisioner/terraform/parse.go index 11efa03a3ec6b..dcdf77fa3a874 100644 --- a/provisioner/terraform/parse.go +++ b/provisioner/terraform/parse.go @@ -46,6 +46,9 @@ func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSc Description: variable.Description, RedisplayValue: !variable.Sensitive, ValidationValueType: variable.Type, + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + }, } if variable.Default != nil { @@ -57,9 +60,6 @@ func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSc Scheme: proto.ParameterSource_DATA, Value: string(defaultData), } - schema.DefaultDestination = &proto.ParameterDestination{ - Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, - } } if len(variable.Validations) > 0 && variable.Validations[0].Condition != nil { diff --git a/provisioner/terraform/parse_test.go b/provisioner/terraform/parse_test.go index faf3860a2844a..f77882db6b9bc 100644 --- a/provisioner/terraform/parse_test.go +++ b/provisioner/terraform/parse_test.go @@ -55,6 +55,9 @@ func TestParse(t *testing.T) { Name: "A", RedisplayValue: true, Description: "Testing!", + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + }, }}, }, }, @@ -100,8 +103,10 @@ func TestParse(t *testing.T) { RedisplayValue: true, ValidationCondition: `var.A == "value"`, ValidationTypeSystem: proto.ParameterSchema_HCL, - }, - }, + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + }, + }}, }, }, }, diff --git a/provisionerd/proto/provisionerd.proto b/provisionerd/proto/provisionerd.proto index 620d579910e2d..64a37bd3e05ef 100644 --- a/provisionerd/proto/provisionerd.proto +++ b/provisionerd/proto/provisionerd.proto @@ -35,16 +35,6 @@ message CancelledJob { string error = 2; } -// TransitionedResource represents a resource that knows whether -// it's existence is dependent on stop or not. -// -// This is used on import to display start + stopped resources -// for the lifecycle of a workspace. -message TransitionedResource { - provisioner.Resource resource = 1; - bool destroy_on_stop = 2; -} - // CompletedJob is sent when the provisioner daemon completes a job. message CompletedJob { message WorkspaceProvision { From bff96b692b8883ded1191c3ddeaa97d51d32b4ca Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 03:53:19 +0000 Subject: [PATCH 10/20] Only show job status if completed --- cli/projectcreate.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cli/projectcreate.go b/cli/projectcreate.go index fdfd8c43b489f..766f7fd181e53 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -131,12 +131,13 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod if err != nil { return nil, err } + logBuffer := make([]coderd.ProvisionerJobLog, 0, 64) for { - _, ok := <-logs + log, ok := <-logs if !ok { break } - // _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[tf]"), log.Output) + logBuffer = append(logBuffer, log) } job, err = client.ProvisionerJob(cmd.Context(), organization.Name, job.ID) @@ -184,6 +185,10 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod } if job.Status != coderd.ProvisionerJobStatusSucceeded { + for _, log := range logBuffer { + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", color.HiGreenString("[tf]"), log.Output) + } + return nil, xerrors.New(job.Error) } From 8766a33cc1a7fc6da593d1d920a5c09df04d2096 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 05:27:25 +0000 Subject: [PATCH 11/20] Add create workspace support --- .vscode/settings.json | 1 + cli/clitest/clitest.go | 76 +++++++++++++++++++++- cli/login.go | 10 +-- cli/projectcreate.go | 93 +++++++++++---------------- cli/projectcreate_test.go | 70 +++++++++++++++++++-- cli/projects.go | 50 +++++++++++++++ cli/root.go | 6 +- cli/workspacecreate.go | 128 ++++++++++++++++++++++++++++++++++++++ cli/workspaces.go | 1 + 9 files changed, 366 insertions(+), 69 deletions(-) create mode 100644 cli/workspacecreate.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 1c6d6a8f8c189..34ed9fbae2c42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -44,6 +44,7 @@ "nhooyr", "nolint", "nosec", + "ntqry", "oneof", "parameterscopeid", "promptui", diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index dc390485b0eff..afcda2c5c8e55 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -1,21 +1,36 @@ package clitest import ( + "archive/tar" "bufio" + "bytes" "context" + "errors" "io" + "os" + "path/filepath" + "regexp" "testing" "github.com/spf13/cobra" "github.com/stretchr/testify/require" + "golang.org/x/xerrors" "github.com/coder/coder/cli" "github.com/coder/coder/cli/config" "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" + "github.com/coder/coder/provisioner/echo" ) +var ( + // Used to ensure terminal output doesn't have anything crazy! + stripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))") +) + +// New creates a CLI instance with a configuration pointed to a +// temporary testing directory. func New(t *testing.T, args ...string) (*cobra.Command, config.Root) { cmd := cli.Root() dir := t.TempDir() @@ -24,6 +39,8 @@ func New(t *testing.T, args ...string) (*cobra.Command, config.Root) { return cmd, root } +// CreateInitialUser creates the initial user and write's the session +// token to the config root provided. func CreateInitialUser(t *testing.T, client *codersdk.Client, root config.Root) coderd.CreateInitialUserRequest { user := coderdtest.CreateInitialUser(t, client) resp, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{ @@ -38,6 +55,19 @@ func CreateInitialUser(t *testing.T, client *codersdk.Client, root config.Root) return user } +// CreateProjectVersionSource writes the echo provisioner responses into a +// new temporary testing directory. +func CreateProjectVersionSource(t *testing.T, responses *echo.Responses) string { + directory := t.TempDir() + data, err := echo.Tar(responses) + require.NoError(t, err) + err = extractTar(data, directory) + require.NoError(t, err) + return directory +} + +// StdoutLogs provides a writer to t.Log that strips +// all ANSI escape codes. func StdoutLogs(t *testing.T) io.Writer { reader, writer := io.Pipe() scanner := bufio.NewScanner(reader) @@ -50,8 +80,52 @@ func StdoutLogs(t *testing.T) io.Writer { if scanner.Err() != nil { return } - t.Log(scanner.Text()) + t.Log(stripAnsi.ReplaceAllString(scanner.Text(), "")) } }() return writer } + +func extractTar(data []byte, directory string) error { + reader := tar.NewReader(bytes.NewBuffer(data)) + for { + header, err := reader.Next() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return xerrors.Errorf("read project source archive: %w", err) + } + path := filepath.Join(directory, header.Name) + mode := header.FileInfo().Mode() + if mode == 0 { + mode = 0600 + } + switch header.Typeflag { + case tar.TypeDir: + err = os.MkdirAll(path, mode) + if err != nil { + return xerrors.Errorf("mkdir: %w", err) + } + case tar.TypeReg: + file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, mode) + if err != nil { + return xerrors.Errorf("create file %q: %w", path, err) + } + // Max file size of 10MB. + _, err = io.CopyN(file, reader, (1<<20)*10) + if errors.Is(err, io.EOF) { + err = nil + } + if err != nil { + _ = file.Close() + return err + } + err = file.Close() + if err != nil { + return err + } + } + } + return nil +} diff --git a/cli/login.go b/cli/login.go index 73758719d0128..a1df7e905d7dd 100644 --- a/cli/login.go +++ b/cli/login.go @@ -49,7 +49,7 @@ func login() *cobra.Command { } _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Your Coder deployment hasn't been set up!\n", color.HiBlackString(">")) - _, err := runPrompt(cmd, &promptui.Prompt{ + _, err := prompt(cmd, &promptui.Prompt{ Label: "Would you like to create the first user?", IsConfirm: true, Default: "y", @@ -61,7 +61,7 @@ func login() *cobra.Command { if err != nil { return xerrors.Errorf("get current user: %w", err) } - username, err := runPrompt(cmd, &promptui.Prompt{ + username, err := prompt(cmd, &promptui.Prompt{ Label: "What username would you like?", Default: currentUser.Username, }) @@ -69,7 +69,7 @@ func login() *cobra.Command { return xerrors.Errorf("pick username prompt: %w", err) } - organization, err := runPrompt(cmd, &promptui.Prompt{ + organization, err := prompt(cmd, &promptui.Prompt{ Label: "What is the name of your organization?", Default: "acme-corp", }) @@ -77,7 +77,7 @@ func login() *cobra.Command { return xerrors.Errorf("pick organization prompt: %w", err) } - email, err := runPrompt(cmd, &promptui.Prompt{ + email, err := prompt(cmd, &promptui.Prompt{ Label: "What's your email?", Validate: func(s string) error { err := validator.New().Var(s, "email") @@ -91,7 +91,7 @@ func login() *cobra.Command { return xerrors.Errorf("specify email prompt: %w", err) } - password, err := runPrompt(cmd, &promptui.Prompt{ + password, err := prompt(cmd, &promptui.Prompt{ Label: "Enter a password:", Mask: '*', }) diff --git a/cli/projectcreate.go b/cli/projectcreate.go index 766f7fd181e53..69ea595cb654e 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -3,11 +3,11 @@ package cli import ( "archive/tar" "bytes" + "errors" "fmt" "io" "os" "path/filepath" - "strings" "time" "github.com/briandowns/spinner" @@ -15,7 +15,6 @@ import ( "github.com/google/uuid" "github.com/manifoldco/promptui" "github.com/spf13/cobra" - "github.com/xlab/treeprint" "golang.org/x/xerrors" "github.com/coder/coder/coderd" @@ -27,7 +26,8 @@ import ( func projectCreate() *cobra.Command { var ( - directory string + directory string + provisioner string ) cmd := &cobra.Command{ Use: "create", @@ -41,16 +41,19 @@ func projectCreate() *cobra.Command { if err != nil { return err } - _, err = runPrompt(cmd, &promptui.Prompt{ + _, err = prompt(cmd, &promptui.Prompt{ Default: "y", IsConfirm: true, Label: fmt.Sprintf("Set up %s in your organization?", color.New(color.FgHiCyan).Sprintf("%q", directory)), }) if err != nil { + if errors.Is(err, promptui.ErrAbort) { + return nil + } return err } - name, err := runPrompt(cmd, &promptui.Prompt{ + name, err := prompt(cmd, &promptui.Prompt{ Default: filepath.Base(directory), Label: "What's your project's name?", Validate: func(s string) error { @@ -65,7 +68,7 @@ func projectCreate() *cobra.Command { return err } - job, err := doProjectLoop(cmd, client, organization, directory, []coderd.CreateParameterValueRequest{}) + job, err := validateProjectVersionSource(cmd, client, organization, database.ProvisionerType(provisioner), directory) if err != nil { return err } @@ -77,27 +80,44 @@ func projectCreate() *cobra.Command { return err } + _, err = prompt(cmd, &promptui.Prompt{ + Label: "Create project?", + IsConfirm: true, + Default: "y", + }) + if err != nil { + if errors.Is(err, promptui.ErrAbort) { + return nil + } + return err + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s The %s project has been created!\n", color.HiBlackString(">"), color.HiCyanString(project.Name)) - _, err = runPrompt(cmd, &promptui.Prompt{ + _, err = prompt(cmd, &promptui.Prompt{ Label: "Create a new workspace?", IsConfirm: true, Default: "y", }) if err != nil { + if errors.Is(err, promptui.ErrAbort) { + return nil + } return err } - fmt.Printf("Create a new workspace now!\n") return nil }, } currentDirectory, _ := os.Getwd() cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from") + cmd.Flags().StringVarP(&provisioner, "provisioner", "p", "terraform", "Customize the provisioner backend") + // This is for testing! There's only 1 provisioner type right now. + cmd.Flags().MarkHidden("provisioner") return cmd } -func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization coderd.Organization, directory string, params []coderd.CreateParameterValueRequest) (*coderd.ProvisionerJob, error) { +func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, organization coderd.Organization, provisioner database.ProvisionerType, directory string, parameters ...coderd.CreateParameterValueRequest) (*coderd.ProvisionerJob, error) { spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond) spin.Writer = cmd.OutOrStdout() spin.Suffix = " Uploading current directory..." @@ -118,8 +138,8 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod job, err := client.CreateProjectVersionImportProvisionerJob(cmd.Context(), organization.Name, coderd.CreateProjectImportJobRequest{ StorageMethod: database.ProvisionerStorageMethodFile, StorageSource: resp.Hash, - Provisioner: database.ProvisionerTypeTerraform, - ParameterValues: params, + Provisioner: provisioner, + ParameterValues: parameters, }) if err != nil { return nil, err @@ -168,20 +188,20 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod if parameterSchema.Name == parameter.CoderWorkspaceTransition { continue } - value, err := runPrompt(cmd, &promptui.Prompt{ + value, err := prompt(cmd, &promptui.Prompt{ Label: fmt.Sprintf("Enter value for %s:", color.HiCyanString(parameterSchema.Name)), }) if err != nil { return nil, err } - params = append(params, coderd.CreateParameterValueRequest{ + parameters = append(parameters, coderd.CreateParameterValueRequest{ Name: parameterSchema.Name, SourceValue: value, SourceScheme: database.ParameterSourceSchemeData, DestinationScheme: parameterSchema.DefaultDestinationScheme, }) } - return doProjectLoop(cmd, client, organization, directory, params) + return validateProjectVersionSource(cmd, client, organization, provisioner, directory, parameters...) } if job.Status != coderd.ProvisionerJobStatusSucceeded { @@ -198,50 +218,7 @@ func doProjectLoop(cmd *cobra.Command, client *codersdk.Client, organization cod if err != nil { return nil, err } - return &job, outputProjectInformation(cmd, parameterSchemas, parameterValues, resources) -} - -func outputProjectInformation(cmd *cobra.Command, parameterSchemas []coderd.ParameterSchema, parameterValues []coderd.ComputedParameterValue, resources []coderd.ProjectImportJobResource) error { - schemaByID := map[string]coderd.ParameterSchema{} - for _, schema := range parameterSchemas { - schemaByID[schema.ID.String()] = schema - } - - _, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n %s\n\n", color.HiBlackString("Parameters")) - for _, value := range parameterValues { - schema, ok := schemaByID[value.SchemaID.String()] - if !ok { - return xerrors.Errorf("schema not found: %s", value.Name) - } - displayValue := value.SourceValue - if !schema.RedisplayValue { - displayValue = "" - } - output := fmt.Sprintf("%s %s %s", color.HiCyanString(value.Name), color.HiBlackString("="), displayValue) - if value.DefaultSourceValue { - output += " (default value)" - } else if value.Scope != database.ParameterScopeImportJob { - output += fmt.Sprintf(" (inherited from %s)", value.Scope) - } - - root := treeprint.NewWithRoot(output) - if schema.Description != "" { - root.AddBranch(fmt.Sprintf("%s\n%s\n", color.HiBlackString("Description"), schema.Description)) - } - if schema.AllowOverrideSource { - root.AddBranch(fmt.Sprintf("%s Users can customize this value!", color.HiYellowString("+"))) - } - _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.Join(strings.Split(root.String(), "\n"), "\n ")) - } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s\n\n", color.HiBlackString("Resources")) - for _, resource := range resources { - transition := color.HiGreenString("start") - if resource.Transition == database.WorkspaceTransitionStop { - transition = color.HiRedString("stop") - } - _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s %s on %s\n\n", color.HiCyanString(resource.Type), color.HiCyanString(resource.Name), transition) - } - return nil + return &job, displayProjectImportInfo(cmd, parameterSchemas, parameterValues, resources) } func tarDirectory(directory string) ([]byte, error) { diff --git a/cli/projectcreate_test.go b/cli/projectcreate_test.go index 1d4a83288ddbf..aec401af98781 100644 --- a/cli/projectcreate_test.go +++ b/cli/projectcreate_test.go @@ -6,29 +6,40 @@ import ( "github.com/Netflix/go-expect" "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/database" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk/proto" "github.com/stretchr/testify/require" ) func TestProjectCreate(t *testing.T) { t.Parallel() - t.Run("InitialUserTTY", func(t *testing.T) { + t.Run("NoParameters", func(t *testing.T) { t.Parallel() console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) require.NoError(t, err) client := coderdtest.New(t) - directory := t.TempDir() - cmd, root := clitest.New(t, "projects", "create", "--directory", directory) + _ = coderdtest.NewProvisionerDaemon(t, client) + source := clitest.CreateProjectVersionSource(t, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: echo.ProvisionComplete, + }) + cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) _ = clitest.CreateInitialUser(t, client, root) cmd.SetIn(console.Tty()) cmd.SetOut(console.Tty()) + closeChan := make(chan struct{}) go func() { err := cmd.Execute() require.NoError(t, err) + close(closeChan) }() matches := []string{ "organization?", "y", - "name?", "", + "name?", "test-project", + "project?", "y", + "created!", "n", } for i := 0; i < len(matches); i += 2 { match := matches[i] @@ -38,5 +49,56 @@ func TestProjectCreate(t *testing.T) { _, err = console.SendLine(value) require.NoError(t, err) } + <-closeChan + }) + + t.Run("Parameter", func(t *testing.T) { + t.Parallel() + console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) + require.NoError(t, err) + client := coderdtest.New(t) + _ = coderdtest.NewProvisionerDaemon(t, client) + source := clitest.CreateProjectVersionSource(t, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "somevar", + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + }, + }}, + }, + }, + }}, + Provision: echo.ProvisionComplete, + }) + cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) + _ = clitest.CreateInitialUser(t, client, root) + cmd.SetIn(console.Tty()) + cmd.SetOut(console.Tty()) + closeChan := make(chan struct{}) + go func() { + err := cmd.Execute() + require.NoError(t, err) + close(closeChan) + }() + + matches := []string{ + "organization?", "y", + "name?", "test-project", + "somevar:", "value", + "project?", "y", + "created!", "n", + } + for i := 0; i < len(matches); i += 2 { + match := matches[i] + value := matches[i+1] + _, err = console.ExpectString(match) + require.NoError(t, err) + _, err = console.SendLine(value) + require.NoError(t, err) + } + <-closeChan }) } diff --git a/cli/projects.go b/cli/projects.go index e0963459a6ed6..219d1e8733c70 100644 --- a/cli/projects.go +++ b/cli/projects.go @@ -1,8 +1,15 @@ package cli import ( + "fmt" + "strings" + + "github.com/coder/coder/coderd" + "github.com/coder/coder/database" "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/xlab/treeprint" + "golang.org/x/xerrors" ) func projects() *cobra.Command { @@ -29,3 +36,46 @@ func projects() *cobra.Command { return cmd } + +func displayProjectImportInfo(cmd *cobra.Command, parameterSchemas []coderd.ParameterSchema, parameterValues []coderd.ComputedParameterValue, resources []coderd.ProjectImportJobResource) error { + schemaByID := map[string]coderd.ParameterSchema{} + for _, schema := range parameterSchemas { + schemaByID[schema.ID.String()] = schema + } + + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n %s\n\n", color.HiBlackString("Parameters")) + for _, value := range parameterValues { + schema, ok := schemaByID[value.SchemaID.String()] + if !ok { + return xerrors.Errorf("schema not found: %s", value.Name) + } + displayValue := value.SourceValue + if !schema.RedisplayValue { + displayValue = "" + } + output := fmt.Sprintf("%s %s %s", color.HiCyanString(value.Name), color.HiBlackString("="), displayValue) + if value.DefaultSourceValue { + output += " (default value)" + } else if value.Scope != database.ParameterScopeImportJob { + output += fmt.Sprintf(" (inherited from %s)", value.Scope) + } + + root := treeprint.NewWithRoot(output) + if schema.Description != "" { + root.AddBranch(fmt.Sprintf("%s\n%s", color.HiBlackString("Description"), schema.Description)) + } + if schema.AllowOverrideSource { + root.AddBranch(fmt.Sprintf("%s Users can customize this value!", color.HiYellowString("+"))) + } + _, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+strings.Join(strings.Split(root.String(), "\n"), "\n ")) + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s\n\n", color.HiBlackString("Resources")) + for _, resource := range resources { + transition := color.HiGreenString("start") + if resource.Transition == database.WorkspaceTransitionStop { + transition = color.HiRedString("stop") + } + _, _ = fmt.Fprintf(cmd.OutOrStdout(), " %s %s on %s\n\n", color.HiCyanString(resource.Type), color.HiCyanString(resource.Name), transition) + } + return nil +} diff --git a/cli/root.go b/cli/root.go index 29ef8e9609b65..22d9e791d4699 100644 --- a/cli/root.go +++ b/cli/root.go @@ -69,6 +69,8 @@ func Root() *cobra.Command { return cmd } +// createClient returns a new client from the command context. +// The configuration directory will be read from the global flag. func createClient(cmd *cobra.Command) (*codersdk.Client, error) { root := createConfig(cmd) rawURL, err := root.URL().Read() @@ -87,6 +89,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) { return client, client.SetSessionToken(token) } +// currentOrganization returns the currently active organization for the authenticated user. func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (coderd.Organization, error) { orgs, err := client.UserOrganizations(cmd.Context(), "me") if err != nil { @@ -97,6 +100,7 @@ func currentOrganization(cmd *cobra.Command, client *codersdk.Client) (coderd.Or return orgs[0], nil } +// createConfig consumes the global configuration flag to produce a config root. func createConfig(cmd *cobra.Command) config.Root { globalRoot, err := cmd.Flags().GetString(varGlobalConfig) if err != nil { @@ -116,7 +120,7 @@ func isTTY(reader io.Reader) bool { return isatty.IsTerminal(file.Fd()) } -func runPrompt(cmd *cobra.Command, prompt *promptui.Prompt) (string, error) { +func prompt(cmd *cobra.Command, prompt *promptui.Prompt) (string, error) { var ok bool prompt.Stdin, ok = cmd.InOrStdin().(io.ReadCloser) if !ok { diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go new file mode 100644 index 0000000000000..ede86c3d825a2 --- /dev/null +++ b/cli/workspacecreate.go @@ -0,0 +1,128 @@ +package cli + +import ( + "errors" + "fmt" + "time" + + "github.com/coder/coder/coderd" + "github.com/coder/coder/database" + "github.com/fatih/color" + "github.com/google/uuid" + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + "golang.org/x/xerrors" +) + +func workspaceCreate() *cobra.Command { + cmd := &cobra.Command{ + Use: "create [name]", + Short: "Create a workspace from a project", + RunE: func(cmd *cobra.Command, args []string) error { + client, err := createClient(cmd) + if err != nil { + return err + } + organization, err := currentOrganization(cmd, client) + if err != nil { + return err + } + + var name string + if len(args) >= 2 { + name = args[1] + } else { + name, err = prompt(cmd, &promptui.Prompt{ + Label: "What's your workspace's name?", + Validate: func(s string) error { + workspace, _ := client.Workspace(cmd.Context(), "", s) + if workspace.ID.String() != uuid.Nil.String() { + return xerrors.New("A workspace already exists with that name!") + } + return nil + }, + }) + if err != nil { + if errors.Is(err, promptui.ErrAbort) { + return nil + } + return err + } + } + + _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Previewing project create...\n", color.HiBlackString(">")) + + project, err := client.Project(cmd.Context(), organization.Name, args[0]) + if err != nil { + return err + } + projectVersion, err := client.ProjectVersion(cmd.Context(), organization.Name, project.Name, project.ActiveVersionID.String()) + if err != nil { + return err + } + parameterSchemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, projectVersion.ImportJobID) + if err != nil { + return err + } + parameterValues, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, projectVersion.ImportJobID) + if err != nil { + return err + } + resources, err := client.ProvisionerJobResources(cmd.Context(), organization.Name, projectVersion.ImportJobID) + if err != nil { + return err + } + err = displayProjectImportInfo(cmd, parameterSchemas, parameterValues, resources) + if err != nil { + return err + } + + _, err = prompt(cmd, &promptui.Prompt{ + Label: fmt.Sprintf("Create workspace %s?", color.HiCyanString(name)), + Default: "y", + IsConfirm: true, + }) + if err != nil { + if errors.Is(err, promptui.ErrAbort) { + return nil + } + return err + } + + workspace, err := client.CreateWorkspace(cmd.Context(), "", coderd.CreateWorkspaceRequest{ + ProjectID: project.ID, + Name: name, + }) + if err != nil { + return err + } + history, err := client.CreateWorkspaceHistory(cmd.Context(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ + ProjectVersionID: projectVersion.ID, + Transition: database.WorkspaceTransitionStart, + }) + if err != nil { + return err + } + + logs, err := client.FollowProvisionerJobLogsAfter(cmd.Context(), organization.Name, history.ProvisionJobID, time.Time{}) + if err != nil { + return err + } + logBuffer := make([]coderd.ProvisionerJobLog, 0, 64) + for { + log, ok := <-logs + if !ok { + break + } + fmt.Printf("Logging: %s\n", log.Output) + logBuffer = append(logBuffer, log) + } + + fmt.Printf("Create workspace! %s\n", name) + + return nil + }, + } + + return cmd +} diff --git a/cli/workspaces.go b/cli/workspaces.go index 4140d8c9ed7a2..d405f00cea88b 100644 --- a/cli/workspaces.go +++ b/cli/workspaces.go @@ -6,6 +6,7 @@ func workspaces() *cobra.Command { cmd := &cobra.Command{ Use: "workspaces", } + cmd.AddCommand(workspaceCreate()) return cmd } From aac220fe5f1a226f71513a70880f40488545bd30 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 05:29:18 +0000 Subject: [PATCH 12/20] Replace Netflix/go-expect with ActiveState --- cli/login_test.go | 4 +--- cli/projectcreate_test.go | 2 +- go.mod | 7 ++++++- go.sum | 24 ++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/cli/login_test.go b/cli/login_test.go index f2102177d6710..cb63ffb6b62a5 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -1,5 +1,3 @@ -//go:build !windows - package cli_test import ( @@ -9,7 +7,7 @@ import ( "github.com/coder/coder/coderd/coderdtest" "github.com/stretchr/testify/require" - "github.com/Netflix/go-expect" + "github.com/ActiveState/termtest/expect" ) func TestLogin(t *testing.T) { diff --git a/cli/projectcreate_test.go b/cli/projectcreate_test.go index aec401af98781..2b16abe50c9cd 100644 --- a/cli/projectcreate_test.go +++ b/cli/projectcreate_test.go @@ -3,7 +3,7 @@ package cli_test import ( "testing" - "github.com/Netflix/go-expect" + "github.com/ActiveState/termtest/expect" "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/database" diff --git a/go.mod b/go.mod index f84913c114df6..317337c0a0fed 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ replace github.com/hashicorp/terraform-config-inspect => github.com/kylecarbs/te require ( cdr.dev/slog v1.4.1 - github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 + github.com/ActiveState/termtest/expect v0.7.0 github.com/briandowns/spinner v1.18.1 github.com/coder/retry v1.3.0 github.com/fatih/color v1.13.0 @@ -55,8 +55,12 @@ require ( require ( cloud.google.com/go/compute v0.1.0 // indirect + github.com/ActiveState/termtest/conpty v0.5.0 // indirect + github.com/ActiveState/termtest/xpty v0.6.0 // indirect + github.com/ActiveState/vt10x v1.3.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/alecthomas/chroma v0.10.0 // indirect @@ -88,6 +92,7 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect github.com/klauspost/compress v1.13.6 // indirect + github.com/kr/pty v1.1.8 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect github.com/mattn/go-colorable v0.1.12 // indirect diff --git a/go.sum b/go.sum index f39b53bdea85f..b14a346104c3c 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,14 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +github.com/ActiveState/termtest/conpty v0.5.0 h1:JLUe6YDs4Jw4xNPCU+8VwTpniYOGeKzQg4SM2YHQNA8= +github.com/ActiveState/termtest/conpty v0.5.0/go.mod h1:LO4208FLsxw6DcNZ1UtuGUMW+ga9PFtX4ntv8Ymg9og= +github.com/ActiveState/termtest/expect v0.7.0 h1:VNrcfXTHXXoe7i+3WgF5QJhstQLGP4saj+XYM9ZzBvY= +github.com/ActiveState/termtest/expect v0.7.0/go.mod h1:64QuJvMtMu7+H5U+5TSMBxAs1FAaLvRIyN7WPOICido= +github.com/ActiveState/termtest/xpty v0.6.0 h1:L9c17TDfy+ed+tY5cMOErn0n2EYG4tj8StdxHmoPok8= +github.com/ActiveState/termtest/xpty v0.6.0/go.mod h1:MmTm/62Ajq+D92emHq8LOu9Q+2+pkBurDLahkUP6Odg= +github.com/ActiveState/vt10x v1.3.1 h1:7qi8BGXUEBghzBxfXSY0J77etO+L95PZQlwD7ay2mn0= +github.com/ActiveState/vt10x v1.3.1/go.mod h1:8wJKd36c9NmCfGyPyOJmkvyIMvbUPfHkfdS8zZlK19s= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= @@ -103,6 +111,8 @@ github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01 github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/Netflix/go-expect v0.0.0-20200312175327-da48e75238e2/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 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= @@ -150,6 +160,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/autarch/testify v1.2.2 h1:9Q9V6zqhP7R6dv+zRUddv6kXKLo6ecQhnFRFWM71i1c= +github.com/autarch/testify v1.2.2/go.mod h1:oDbHKfFv2/D5UtVrxkk90OKcb6P4/AqF1Pcf6ZbvDQo= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= @@ -438,6 +450,8 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ= +github.com/gdamore/tcell v1.0.1-0.20180608172421-b3cebc399d6f/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -715,6 +729,7 @@ github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniy github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -836,6 +851,7 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -862,6 +878,7 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v0.0.0-20180526135729-345fbb3dbcdb/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= @@ -1153,6 +1170,7 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/snowflakedb/gosnowflake v1.6.3/go.mod h1:6hLajn6yxuJ4xUHZegMekpq9rnQbGJ7TMwXjgTmA6lg= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1184,6 +1202,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1318,6 +1337,7 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1540,6 +1560,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1549,6 +1570,7 @@ golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1556,6 +1578,7 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1877,6 +1900,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From c493bf9da0a80112ca4a79a3991c97b9c95624aa Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 05:34:55 +0000 Subject: [PATCH 13/20] Fix linting errors --- cli/clitest/clitest.go | 1 + cli/login_test.go | 3 ++- cli/projectcreate.go | 16 ++++++++++------ cli/projectcreate_test.go | 7 ++++--- cli/projects.go | 5 +++-- cli/workspacecreate.go | 13 +++++++------ coderd/provisionerdaemons.go | 10 +++++----- codersdk/provisioners_test.go | 7 ------- database/databasefake/databasefake.go | 4 ++-- 9 files changed, 34 insertions(+), 32 deletions(-) diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index afcda2c5c8e55..a7dc55802ff66 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -96,6 +96,7 @@ func extractTar(data []byte, directory string) error { if err != nil { return xerrors.Errorf("read project source archive: %w", err) } + // #nosec path := filepath.Join(directory, header.Name) mode := header.FileInfo().Mode() if mode == 0 { diff --git a/cli/login_test.go b/cli/login_test.go index cb63ffb6b62a5..a37918a0ad61b 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -3,9 +3,10 @@ package cli_test import ( "testing" + "github.com/stretchr/testify/require" + "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" - "github.com/stretchr/testify/require" "github.com/ActiveState/termtest/expect" ) diff --git a/cli/projectcreate.go b/cli/projectcreate.go index 69ea595cb654e..40bc3a3b68128 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -112,8 +112,10 @@ func projectCreate() *cobra.Command { cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from") cmd.Flags().StringVarP(&provisioner, "provisioner", "p", "terraform", "Customize the provisioner backend") // This is for testing! There's only 1 provisioner type right now. - cmd.Flags().MarkHidden("provisioner") - + err := cmd.Flags().MarkHidden("provisioner") + if err != nil { + panic(err) + } return cmd } @@ -121,16 +123,18 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond) spin.Writer = cmd.OutOrStdout() spin.Suffix = " Uploading current directory..." - spin.Color("fgHiGreen") + err := spin.Color("fgHiGreen") + if err != nil { + return nil, err + } spin.Start() defer spin.Stop() - bytes, err := tarDirectory(directory) + tarData, err := tarDirectory(directory) if err != nil { return nil, err } - - resp, err := client.UploadFile(cmd.Context(), codersdk.ContentTypeTar, bytes) + resp, err := client.UploadFile(cmd.Context(), codersdk.ContentTypeTar, tarData) if err != nil { return nil, err } diff --git a/cli/projectcreate_test.go b/cli/projectcreate_test.go index 2b16abe50c9cd..53a0d83e1ce00 100644 --- a/cli/projectcreate_test.go +++ b/cli/projectcreate_test.go @@ -4,12 +4,13 @@ import ( "testing" "github.com/ActiveState/termtest/expect" + "github.com/stretchr/testify/require" + "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/database" "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionersdk/proto" - "github.com/stretchr/testify/require" ) func TestProjectCreate(t *testing.T) { @@ -19,13 +20,13 @@ func TestProjectCreate(t *testing.T) { console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) require.NoError(t, err) client := coderdtest.New(t) - _ = coderdtest.NewProvisionerDaemon(t, client) source := clitest.CreateProjectVersionSource(t, &echo.Responses{ Parse: echo.ParseComplete, Provision: echo.ProvisionComplete, }) cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) _ = clitest.CreateInitialUser(t, client, root) + _ = coderdtest.NewProvisionerDaemon(t, client) cmd.SetIn(console.Tty()) cmd.SetOut(console.Tty()) closeChan := make(chan struct{}) @@ -57,7 +58,6 @@ func TestProjectCreate(t *testing.T) { console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) require.NoError(t, err) client := coderdtest.New(t) - _ = coderdtest.NewProvisionerDaemon(t, client) source := clitest.CreateProjectVersionSource(t, &echo.Responses{ Parse: []*proto.Parse_Response{{ Type: &proto.Parse_Response_Complete{ @@ -75,6 +75,7 @@ func TestProjectCreate(t *testing.T) { }) cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) _ = clitest.CreateInitialUser(t, client, root) + _ = coderdtest.NewProvisionerDaemon(t, client) cmd.SetIn(console.Tty()) cmd.SetOut(console.Tty()) closeChan := make(chan struct{}) diff --git a/cli/projects.go b/cli/projects.go index 219d1e8733c70..b11317367aa8b 100644 --- a/cli/projects.go +++ b/cli/projects.go @@ -4,12 +4,13 @@ import ( "fmt" "strings" - "github.com/coder/coder/coderd" - "github.com/coder/coder/database" "github.com/fatih/color" "github.com/spf13/cobra" "github.com/xlab/treeprint" "golang.org/x/xerrors" + + "github.com/coder/coder/coderd" + "github.com/coder/coder/database" ) func projects() *cobra.Command { diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index ede86c3d825a2..b6be5b75af418 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -5,13 +5,14 @@ import ( "fmt" "time" - "github.com/coder/coder/coderd" - "github.com/coder/coder/database" "github.com/fatih/color" "github.com/google/uuid" "github.com/manifoldco/promptui" "github.com/spf13/cobra" "golang.org/x/xerrors" + + "github.com/coder/coder/coderd" + "github.com/coder/coder/database" ) func workspaceCreate() *cobra.Command { @@ -108,17 +109,17 @@ func workspaceCreate() *cobra.Command { if err != nil { return err } - logBuffer := make([]coderd.ProvisionerJobLog, 0, 64) + // logBuffer := make([]coderd.ProvisionerJobLog, 0, 64) for { log, ok := <-logs if !ok { break } - fmt.Printf("Logging: %s\n", log.Output) - logBuffer = append(logBuffer, log) + _, _ = fmt.Printf("Logging: %s\n", log.Output) + // logBuffer = append(logBuffer, log) } - fmt.Printf("Create workspace! %s\n", name) + _, _ = fmt.Printf("Create workspace! %s\n", name) return nil }, diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index f98a73a962126..28ba99b61e061 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -219,8 +219,8 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty } // Convert parameters to the protobuf type. protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) - for _, parameter := range parameters { - converted, err := convertComputedParameterValue(parameter) + for _, computedParameter := range parameters { + converted, err := convertComputedParameterValue(computedParameter) if err != nil { return nil, failJob(fmt.Sprintf("convert parameter: %s", err)) } @@ -371,8 +371,8 @@ func (server *provisionerdServer) UpdateJob(ctx context.Context, request *proto. } // Convert parameters to the protobuf type. protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters)) - for _, parameter := range parameters { - converted, err := convertComputedParameterValue(parameter) + for _, computedParameter := range parameters { + converted, err := convertComputedParameterValue(computedParameter) if err != nil { return nil, xerrors.Errorf("convert parameter: %s", err) } @@ -597,7 +597,7 @@ func convertLogSource(logSource proto.LogSource) (database.LogSource, error) { } func convertComputedParameterValue(param parameter.ComputedValue) (*sdkproto.ParameterValue, error) { - scheme := sdkproto.ParameterDestination_ENVIRONMENT_VARIABLE + var scheme sdkproto.ParameterDestination_Scheme switch param.DestinationScheme { case database.ParameterDestinationSchemeEnvironmentVariable: scheme = sdkproto.ParameterDestination_ENVIRONMENT_VARIABLE diff --git a/codersdk/provisioners_test.go b/codersdk/provisioners_test.go index 60c3ad49f8b9d..6808dd19a3b78 100644 --- a/codersdk/provisioners_test.go +++ b/codersdk/provisioners_test.go @@ -106,10 +106,3 @@ func TestFollowProvisionerJobLogsAfter(t *testing.T) { require.False(t, ok) }) } - -func TestProvisionerJobParameterSchemas(t *testing.T) { - t.Parallel() - t.Run("Error", func(t *testing.T) { - - }) -} diff --git a/database/databasefake/databasefake.go b/database/databasefake/databasefake.go index a0a2f4359839e..3af24ad8bb563 100644 --- a/database/databasefake/databasefake.go +++ b/database/databasefake/databasefake.go @@ -401,7 +401,7 @@ func (q *fakeQuerier) GetProjectByOrganizationAndName(_ context.Context, arg dat return database.Project{}, sql.ErrNoRows } -func (q *fakeQuerier) GetProjectImportJobResourcesByJobID(ctx context.Context, jobID uuid.UUID) ([]database.ProjectImportJobResource, error) { +func (q *fakeQuerier) GetProjectImportJobResourcesByJobID(_ context.Context, jobID uuid.UUID) ([]database.ProjectImportJobResource, error) { q.mutex.Lock() defer q.mutex.Unlock() @@ -685,7 +685,7 @@ func (q *fakeQuerier) InsertProject(_ context.Context, arg database.InsertProjec return project, nil } -func (q *fakeQuerier) InsertProjectImportJobResource(ctx context.Context, arg database.InsertProjectImportJobResourceParams) (database.ProjectImportJobResource, error) { +func (q *fakeQuerier) InsertProjectImportJobResource(_ context.Context, arg database.InsertProjectImportJobResourceParams) (database.ProjectImportJobResource, error) { q.mutex.Lock() defer q.mutex.Unlock() From 485c07b56a13b18e5b2dc4a69173d20bcd085b43 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 05:44:36 +0000 Subject: [PATCH 14/20] Use forked chzyer/readline --- go.mod | 3 +++ go.sum | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 317337c0a0fed..e7168de21e631 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,9 @@ replace github.com/hashicorp/terraform-exec => github.com/kylecarbs/terraform-ex // Required until https://github.com/hashicorp/terraform-config-inspect/pull/74 is merged. replace github.com/hashicorp/terraform-config-inspect => github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88 +// Required until https://github.com/chzyer/readline/pull/198 is merged. +replace github.com/chzyer/readline => github.com/kylecarbs/readline v0.0.0-20220211054233-0d62993714c8 + require ( cdr.dev/slog v1.4.1 github.com/ActiveState/termtest/expect v0.7.0 diff --git a/go.sum b/go.sum index b14a346104c3c..c8bd72397ed5a 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,6 @@ github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d8 github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= @@ -859,6 +857,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/kylecarbs/promptui v0.8.1-0.20201231190244-d8f2159af2b2 h1:MUREBTh4kybLY1KyuBfSx+QPfTB8XiUHs6ZxUhOPTnU= github.com/kylecarbs/promptui v0.8.1-0.20201231190244-d8f2159af2b2/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/kylecarbs/readline v0.0.0-20220211054233-0d62993714c8 h1:Y7O3Z3YeNRtw14QrtHpevU4dSjCkov0J40MtQ7Nc0n8= +github.com/kylecarbs/readline v0.0.0-20220211054233-0d62993714c8/go.mod h1:n/KX1BZoN1m9EwoXkn/xAV4fd3k8c++gGBsgLONaPOY= github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88 h1:tvG/qs5c4worwGyGnbbb4i/dYYLjpFwDMqcIT3awAf8= github.com/kylecarbs/terraform-config-inspect v0.0.0-20211215004401-bbc517866b88/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs= github.com/kylecarbs/terraform-exec v0.15.1-0.20220202050609-a1ce7181b180 h1:yafC0pmxjs18fnO5RdKFLSItJIjYwGfSHTfcUvlZb3E= From b5a774a78c6b577ee53d9e03622a5d3f08a2ccf1 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 17:38:09 +0000 Subject: [PATCH 15/20] Add create workspace CLI --- cli/clitest/clitest.go | 31 +++++++---------- cli/login_test.go | 14 +++----- cli/projectcreate_test.go | 23 +++++------- cli/root.go | 3 +- cli/workspacecreate.go | 3 ++ cli/workspacecreate_test.go | 62 +++++++++++++++++++++++++++++++++ coderd/coderdtest/coderdtest.go | 3 +- coderd/provisionerdaemons.go | 11 +++--- coderd/provisionerjobs.go | 1 + coderd/workspaces_test.go | 2 +- codersdk/client.go | 24 ++++--------- 11 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 cli/workspacecreate_test.go diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index a7dc55802ff66..ad21230c3b8fb 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -4,7 +4,6 @@ import ( "archive/tar" "bufio" "bytes" - "context" "errors" "io" "os" @@ -12,14 +11,13 @@ import ( "regexp" "testing" + "github.com/Netflix/go-expect" "github.com/spf13/cobra" "github.com/stretchr/testify/require" "golang.org/x/xerrors" "github.com/coder/coder/cli" "github.com/coder/coder/cli/config" - "github.com/coder/coder/coderd" - "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" ) @@ -39,20 +37,12 @@ func New(t *testing.T, args ...string) (*cobra.Command, config.Root) { return cmd, root } -// CreateInitialUser creates the initial user and write's the session -// token to the config root provided. -func CreateInitialUser(t *testing.T, client *codersdk.Client, root config.Root) coderd.CreateInitialUserRequest { - user := coderdtest.CreateInitialUser(t, client) - resp, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{ - Email: user.Email, - Password: user.Password, - }) - require.NoError(t, err) - err = root.Session().Write(resp.SessionToken) +// SetupConfig applies the URL and SessionToken of the client to the config. +func SetupConfig(t *testing.T, client *codersdk.Client, root config.Root) { + err := root.Session().Write(client.SessionToken) require.NoError(t, err) err = root.URL().Write(client.URL.String()) require.NoError(t, err) - return user } // CreateProjectVersionSource writes the echo provisioner responses into a @@ -66,9 +56,9 @@ func CreateProjectVersionSource(t *testing.T, responses *echo.Responses) string return directory } -// StdoutLogs provides a writer to t.Log that strips -// all ANSI escape codes. -func StdoutLogs(t *testing.T) io.Writer { +// NewConsole creates a new TTY bound to the command provided. +// All ANSI escape codes are stripped to provide clean output. +func NewConsole(t *testing.T, cmd *cobra.Command) *expect.Console { reader, writer := io.Pipe() scanner := bufio.NewScanner(reader) t.Cleanup(func() { @@ -83,7 +73,12 @@ func StdoutLogs(t *testing.T) io.Writer { t.Log(stripAnsi.ReplaceAllString(scanner.Text(), "")) } }() - return writer + + console, err := expect.NewConsole(expect.WithStdout(writer)) + require.NoError(t, err) + cmd.SetIn(console.Tty()) + cmd.SetOut(console.Tty()) + return console } func extractTar(data []byte, directory string) error { diff --git a/cli/login_test.go b/cli/login_test.go index a37918a0ad61b..faefdf4ccccb3 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -3,12 +3,9 @@ package cli_test import ( "testing" - "github.com/stretchr/testify/require" - "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" - - "github.com/ActiveState/termtest/expect" + "github.com/stretchr/testify/require" ) func TestLogin(t *testing.T) { @@ -23,12 +20,9 @@ func TestLogin(t *testing.T) { t.Run("InitialUserTTY", func(t *testing.T) { t.Parallel() - console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) - require.NoError(t, err) client := coderdtest.New(t) root, _ := clitest.New(t, "login", client.URL.String()) - root.SetIn(console.Tty()) - root.SetOut(console.Tty()) + console := clitest.NewConsole(t, root) go func() { err := root.Execute() require.NoError(t, err) @@ -44,12 +38,12 @@ func TestLogin(t *testing.T) { for i := 0; i < len(matches); i += 2 { match := matches[i] value := matches[i+1] - _, err = console.ExpectString(match) + _, err := console.ExpectString(match) require.NoError(t, err) _, err = console.SendLine(value) require.NoError(t, err) } - _, err = console.ExpectString("Welcome to Coder") + _, err := console.ExpectString("Welcome to Coder") require.NoError(t, err) }) } diff --git a/cli/projectcreate_test.go b/cli/projectcreate_test.go index 53a0d83e1ce00..547447d3b87bd 100644 --- a/cli/projectcreate_test.go +++ b/cli/projectcreate_test.go @@ -3,7 +3,6 @@ package cli_test import ( "testing" - "github.com/ActiveState/termtest/expect" "github.com/stretchr/testify/require" "github.com/coder/coder/cli/clitest" @@ -17,18 +16,16 @@ func TestProjectCreate(t *testing.T) { t.Parallel() t.Run("NoParameters", func(t *testing.T) { t.Parallel() - console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) - require.NoError(t, err) client := coderdtest.New(t) + coderdtest.CreateInitialUser(t, client) source := clitest.CreateProjectVersionSource(t, &echo.Responses{ Parse: echo.ParseComplete, Provision: echo.ProvisionComplete, }) cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) - _ = clitest.CreateInitialUser(t, client, root) + clitest.SetupConfig(t, client, root) _ = coderdtest.NewProvisionerDaemon(t, client) - cmd.SetIn(console.Tty()) - cmd.SetOut(console.Tty()) + console := clitest.NewConsole(t, cmd) closeChan := make(chan struct{}) go func() { err := cmd.Execute() @@ -45,7 +42,7 @@ func TestProjectCreate(t *testing.T) { for i := 0; i < len(matches); i += 2 { match := matches[i] value := matches[i+1] - _, err = console.ExpectString(match) + _, err := console.ExpectString(match) require.NoError(t, err) _, err = console.SendLine(value) require.NoError(t, err) @@ -55,9 +52,8 @@ func TestProjectCreate(t *testing.T) { t.Run("Parameter", func(t *testing.T) { t.Parallel() - console, err := expect.NewConsole(expect.WithStdout(clitest.StdoutLogs(t))) - require.NoError(t, err) client := coderdtest.New(t) + coderdtest.CreateInitialUser(t, client) source := clitest.CreateProjectVersionSource(t, &echo.Responses{ Parse: []*proto.Parse_Response{{ Type: &proto.Parse_Response_Complete{ @@ -74,10 +70,9 @@ func TestProjectCreate(t *testing.T) { Provision: echo.ProvisionComplete, }) cmd, root := clitest.New(t, "projects", "create", "--directory", source, "--provisioner", string(database.ProvisionerTypeEcho)) - _ = clitest.CreateInitialUser(t, client, root) - _ = coderdtest.NewProvisionerDaemon(t, client) - cmd.SetIn(console.Tty()) - cmd.SetOut(console.Tty()) + clitest.SetupConfig(t, client, root) + coderdtest.NewProvisionerDaemon(t, client) + console := clitest.NewConsole(t, cmd) closeChan := make(chan struct{}) go func() { err := cmd.Execute() @@ -95,7 +90,7 @@ func TestProjectCreate(t *testing.T) { for i := 0; i < len(matches); i += 2 { match := matches[i] value := matches[i+1] - _, err = console.ExpectString(match) + _, err := console.ExpectString(match) require.NoError(t, err) _, err = console.SendLine(value) require.NoError(t, err) diff --git a/cli/root.go b/cli/root.go index 22d9e791d4699..9133d7655d133 100644 --- a/cli/root.go +++ b/cli/root.go @@ -86,7 +86,8 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) { return nil, err } client := codersdk.New(serverURL) - return client, client.SetSessionToken(token) + client.SessionToken = token + return client, nil } // currentOrganization returns the currently active organization for the authenticated user. diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index b6be5b75af418..9eaa4941d26cc 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -36,6 +36,9 @@ func workspaceCreate() *cobra.Command { name, err = prompt(cmd, &promptui.Prompt{ Label: "What's your workspace's name?", Validate: func(s string) error { + if s == "" { + return xerrors.Errorf("You must provide a name!") + } workspace, _ := client.Workspace(cmd.Context(), "", s) if workspace.ID.String() != uuid.Nil.String() { return xerrors.New("A workspace already exists with that name!") diff --git a/cli/workspacecreate_test.go b/cli/workspacecreate_test.go new file mode 100644 index 0000000000000..6b24f315d582c --- /dev/null +++ b/cli/workspacecreate_test.go @@ -0,0 +1,62 @@ +package cli_test + +import ( + "testing" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk/proto" + "github.com/stretchr/testify/require" +) + +func TestWorkspaceCreate(t *testing.T) { + t.Parallel() + t.Run("Create", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _ = coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "example", + Type: "aws_instance", + }}, + }, + }, + }}, + }) + coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + project := coderdtest.CreateProject(t, client, user.Organization, job.ID) + cmd, root := clitest.New(t, "workspaces", "create", project.Name) + clitest.SetupConfig(t, client, root) + + console := clitest.NewConsole(t, cmd) + closeChan := make(chan struct{}) + go func() { + err := cmd.Execute() + require.NoError(t, err) + close(closeChan) + }() + + matches := []string{ + "name?", "workspace-name", + "Create workspace", "y", + } + for i := 0; i < len(matches); i += 2 { + match := matches[i] + value := matches[i+1] + _, err := console.ExpectString(match) + require.NoError(t, err) + _, err = console.SendLine(value) + require.NoError(t, err) + } + _, err := console.ExpectString("Create") + require.NoError(t, err) + <-closeChan + }) +} diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 82295ef35af9b..4dff2a7939716 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -117,8 +117,7 @@ func CreateInitialUser(t *testing.T, client *codersdk.Client) coderd.CreateIniti Password: req.Password, }) require.NoError(t, err) - err = client.SetSessionToken(login.SessionToken) - require.NoError(t, err) + client.SessionToken = login.SessionToken return req } diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 28ba99b61e061..a75730dc0a876 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -195,15 +195,11 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty if err != nil { return nil, failJob(fmt.Sprintf("get project: %s", err)) } - organization, err := server.Database.GetOrganizationByID(ctx, project.OrganizationID) - if err != nil { - return nil, failJob(fmt.Sprintf("get organization: %s", err)) - } // Compute parameters for the workspace to consume. parameters, err := parameter.Compute(ctx, server.Database, parameter.ComputeScope{ ProjectImportJobID: projectVersion.ImportJobID, - OrganizationID: organization.ID, + OrganizationID: job.OrganizationID, ProjectID: uuid.NullUUID{ UUID: project.ID, Valid: true, @@ -226,6 +222,11 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty } protoParameters = append(protoParameters, converted) } + protoParameters = append(protoParameters, &sdkproto.ParameterValue{ + DestinationScheme: sdkproto.ParameterDestination_PROVISIONER_VARIABLE, + Name: parameter.CoderWorkspaceTransition, + Value: string(workspaceHistory.Transition), + }) protoJob.Type = &proto.AcquiredJob_WorkspaceProvision_{ WorkspaceProvision: &proto.AcquiredJob_WorkspaceProvision{ diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 9c000280f7228..a7e75aa37b321 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -112,6 +112,7 @@ func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r StorageMethod: database.ProvisionerStorageMethodFile, StorageSource: file.Hash, Type: database.ProvisionerJobTypeProjectVersionImport, + Input: []byte{'{', '}'}, }) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 06f4023e87499..8d820b5964c31 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -73,7 +73,7 @@ func TestPostWorkspaceByUser(t *testing.T) { Password: anotherUser.Password, }) require.NoError(t, err) - err = client.SetSessionToken(token.SessionToken) + client.SessionToken = token.SessionToken require.NoError(t, err) _, err = client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ diff --git a/codersdk/client.go b/codersdk/client.go index de67ba1ae8058..976c31d06f3da 100644 --- a/codersdk/client.go +++ b/codersdk/client.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "net/http" - "net/http/cookiejar" "net/url" "strings" @@ -28,27 +27,12 @@ func New(serverURL *url.URL) *Client { // Client is an HTTP caller for methods to the Coder API. type Client struct { - URL *url.URL + URL *url.URL + SessionToken string httpClient *http.Client } -// SetSessionToken applies the provided token to the current client. -func (c *Client) SetSessionToken(token string) error { - if c.httpClient.Jar == nil { - var err error - c.httpClient.Jar, err = cookiejar.New(nil) - if err != nil { - return err - } - } - c.httpClient.Jar.SetCookies(c.URL, []*http.Cookie{{ - Name: httpmw.AuthCookie, - Value: token, - }}) - return nil -} - // request performs an HTTP request with the body provided. // The caller is responsible for closing the response body. func (c *Client) request(ctx context.Context, method, path string, body interface{}, opts ...func(r *http.Request)) (*http.Response, error) { @@ -76,6 +60,10 @@ func (c *Client) request(ctx context.Context, method, path string, body interfac if err != nil { return nil, xerrors.Errorf("create request: %w", err) } + req.AddCookie(&http.Cookie{ + Name: httpmw.AuthCookie, + Value: c.SessionToken, + }) if body != nil { req.Header.Set("Content-Type", "application/json") } From d2cbf362b487232516d827bafcad0e593e1d4bc1 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 17:49:26 +0000 Subject: [PATCH 16/20] Add CLI test --- cli/clitest/clitest_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 cli/clitest/clitest_test.go diff --git a/cli/clitest/clitest_test.go b/cli/clitest/clitest_test.go new file mode 100644 index 0000000000000..71f9aeefa1bce --- /dev/null +++ b/cli/clitest/clitest_test.go @@ -0,0 +1,29 @@ +package clitest_test + +import ( + "testing" + + "github.com/coder/coder/cli/clitest" + "github.com/coder/coder/coderd/coderdtest" + "github.com/stretchr/testify/require" + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestCli(t *testing.T) { + t.Parallel() + clitest.CreateProjectVersionSource(t, nil) + client := coderdtest.New(t) + cmd, config := clitest.New(t) + clitest.SetupConfig(t, client, config) + console := clitest.NewConsole(t, cmd) + go func() { + err := cmd.Execute() + require.NoError(t, err) + }() + _, err := console.ExpectString("coder") + require.NoError(t, err) +} From a8d00d82fe80e1e947ed290dc3ff846dd677b236 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 11 Feb 2022 19:12:17 +0000 Subject: [PATCH 17/20] Move jobs to their own APIs --- cli/projectcreate.go | 16 +- cli/workspacecreate.go | 8 +- cli/workspacecreate_test.go | 4 +- coderd/coderd.go | 47 +++-- coderd/coderdtest/coderdtest.go | 28 ++- coderd/coderdtest/coderdtest_test.go | 6 +- coderd/parameters.go | 109 ------------ coderd/projectimport.go | 185 ++++++++++++++++++++ coderd/projectimport_test.go | 162 ++++++++++++++++++ coderd/projects.go | 61 ++++++- coderd/projects_test.go | 16 +- coderd/projectversion_test.go | 6 +- coderd/provisionerdaemons_test.go | 7 +- coderd/provisionerjoblogs.go | 196 --------------------- coderd/provisionerjoblogs_test.go | 133 --------------- coderd/provisionerjobs.go | 245 ++++++++++++++++----------- coderd/provisionerjobs_test.go | 150 ++++++++++++++-- coderd/workspacehistory_test.go | 26 +-- coderd/workspaces_test.go | 14 +- codersdk/projectimport.go | 95 +++++++++++ codersdk/projectimport_test.go | 146 ++++++++++++++++ codersdk/projects_test.go | 14 +- codersdk/provisioners.go | 95 +---------- codersdk/provisioners_test.go | 62 ------- codersdk/workspaces.go | 26 +++ codersdk/workspaces_test.go | 16 +- 26 files changed, 1071 insertions(+), 802 deletions(-) delete mode 100644 coderd/parameters.go create mode 100644 coderd/projectimport.go create mode 100644 coderd/projectimport_test.go delete mode 100644 coderd/provisionerjoblogs.go delete mode 100644 coderd/provisionerjoblogs_test.go create mode 100644 codersdk/projectimport.go create mode 100644 codersdk/projectimport_test.go diff --git a/cli/projectcreate.go b/cli/projectcreate.go index 40bc3a3b68128..21e667b462996 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -139,7 +139,8 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o return nil, err } - job, err := client.CreateProjectVersionImportProvisionerJob(cmd.Context(), organization.Name, coderd.CreateProjectImportJobRequest{ + before := time.Now() + job, err := client.CreateProjectImportJob(cmd.Context(), organization.Name, coderd.CreateProjectImportJobRequest{ StorageMethod: database.ProvisionerStorageMethodFile, StorageSource: resp.Hash, Provisioner: provisioner, @@ -148,10 +149,8 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o if err != nil { return nil, err } - spin.Suffix = " Waiting for the import to complete..." - - logs, err := client.FollowProvisionerJobLogsAfter(cmd.Context(), organization.Name, job.ID, time.Time{}) + logs, err := client.ProjectImportJobLogsAfter(cmd.Context(), organization.Name, job.ID, before) if err != nil { return nil, err } @@ -164,16 +163,15 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o logBuffer = append(logBuffer, log) } - job, err = client.ProvisionerJob(cmd.Context(), organization.Name, job.ID) + job, err = client.ProjectImportJob(cmd.Context(), organization.Name, job.ID) if err != nil { return nil, err } - - parameterSchemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, job.ID) + parameterSchemas, err := client.ProjectImportJobSchemas(cmd.Context(), organization.Name, job.ID) if err != nil { return nil, err } - parameterValues, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, job.ID) + parameterValues, err := client.ProjectImportJobParameters(cmd.Context(), organization.Name, job.ID) if err != nil { return nil, err } @@ -218,7 +216,7 @@ func validateProjectVersionSource(cmd *cobra.Command, client *codersdk.Client, o _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s Successfully imported project source!\n", color.HiGreenString("✓")) - resources, err := client.ProvisionerJobResources(cmd.Context(), organization.Name, job.ID) + resources, err := client.ProjectImportJobResources(cmd.Context(), organization.Name, job.ID) if err != nil { return nil, err } diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index 9eaa4941d26cc..3b4392096f0ae 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -64,15 +64,15 @@ func workspaceCreate() *cobra.Command { if err != nil { return err } - parameterSchemas, err := client.ProvisionerJobParameterSchemas(cmd.Context(), organization.Name, projectVersion.ImportJobID) + parameterSchemas, err := client.ProjectImportJobSchemas(cmd.Context(), organization.Name, projectVersion.ImportJobID) if err != nil { return err } - parameterValues, err := client.ProvisionerJobParameterValues(cmd.Context(), organization.Name, projectVersion.ImportJobID) + parameterValues, err := client.ProjectImportJobParameters(cmd.Context(), organization.Name, projectVersion.ImportJobID) if err != nil { return err } - resources, err := client.ProvisionerJobResources(cmd.Context(), organization.Name, projectVersion.ImportJobID) + resources, err := client.ProjectImportJobResources(cmd.Context(), organization.Name, projectVersion.ImportJobID) if err != nil { return err } @@ -108,7 +108,7 @@ func workspaceCreate() *cobra.Command { return err } - logs, err := client.FollowProvisionerJobLogsAfter(cmd.Context(), organization.Name, history.ProvisionJobID, time.Time{}) + logs, err := client.WorkspaceProvisionJobLogsAfter(cmd.Context(), organization.Name, history.ProvisionJobID, time.Time{}) if err != nil { return err } diff --git a/cli/workspacecreate_test.go b/cli/workspacecreate_test.go index 6b24f315d582c..1b0f0dcf2d2ff 100644 --- a/cli/workspacecreate_test.go +++ b/cli/workspacecreate_test.go @@ -17,7 +17,7 @@ func TestWorkspaceCreate(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ Parse: echo.ParseComplete, Provision: []*proto.Provision_Response{{ Type: &proto.Provision_Response_Complete{ @@ -30,7 +30,7 @@ func TestWorkspaceCreate(t *testing.T) { }, }}, }) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) cmd, root := clitest.New(t, "workspaces", "create", project.Name) clitest.SetupConfig(t, client, root) diff --git a/coderd/coderd.go b/coderd/coderd.go index 0a2a9d87e51a8..9669dbf92c2e1 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -107,27 +107,38 @@ func New(options *Options) http.Handler { r.Post("/", api.postFiles) }) - r.Route("/provisioners", func(r chi.Router) { - r.Route("/daemons", func(r chi.Router) { - r.Get("/", api.provisionerDaemons) - r.Get("/serve", api.provisionerDaemonsServe) + r.Route("/projectimport/{organization}", func(r chi.Router) { + r.Use( + httpmw.ExtractAPIKey(options.Database, nil), + httpmw.ExtractOrganizationParam(options.Database), + ) + r.Post("/", api.postProjectImportByOrganization) + r.Route("/{provisionerjob}", func(r chi.Router) { + r.Use(httpmw.ExtractProvisionerJobParam(options.Database)) + r.Get("/", api.provisionerJobByID) + r.Get("/schemas", api.projectImportJobSchemasByID) + r.Get("/parameters", api.projectImportJobParametersByID) + r.Get("/resources", api.projectImportJobResourcesByID) + r.Get("/logs", api.provisionerJobLogsByID) }) - r.Route("/jobs/{organization}", func(r chi.Router) { - r.Use( - httpmw.ExtractAPIKey(options.Database, nil), - httpmw.ExtractOrganizationParam(options.Database), - ) - r.Post("/import", api.postProvisionerImportJobByOrganization) - r.Route("/{provisionerjob}", func(r chi.Router) { - r.Use(httpmw.ExtractProvisionerJobParam(options.Database)) - r.Get("/", api.provisionerJobByOrganization) - r.Get("/schemas", api.provisionerJobParameterSchemasByID) - r.Get("/computed", api.provisionerJobComputedParametersByID) - r.Get("/resources", api.provisionerJobResourcesByID) - r.Get("/logs", api.provisionerJobLogsByID) - }) + }) + + r.Route("/workspaceprovision/{organization}", func(r chi.Router) { + r.Use( + httpmw.ExtractAPIKey(options.Database, nil), + httpmw.ExtractOrganizationParam(options.Database), + ) + r.Route("/{provisionerjob}", func(r chi.Router) { + r.Use(httpmw.ExtractProvisionerJobParam(options.Database)) + r.Get("/", api.provisionerJobByID) + r.Get("/logs", api.provisionerJobLogsByID) }) }) + + r.Route("/provisioners/daemons", func(r chi.Router) { + r.Get("/", api.provisionerDaemons) + r.Get("/serve", api.provisionerDaemonsServe) + }) }) r.NotFound(site.Handler(options.Logger).ServeHTTP) return r diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 4dff2a7939716..dc7f782c83748 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -28,8 +28,8 @@ import ( "github.com/coder/coder/provisionersdk/proto" ) -// New constructs a new coderd test instance. This returned Server -// should contain no side-effects. +// New constructs an in-memory coderd instance and returns +// the connected client. func New(t *testing.T) *codersdk.Client { // This can be hotswapped for a live database instance. db := databasefake.New() @@ -121,15 +121,15 @@ func CreateInitialUser(t *testing.T, client *codersdk.Client) coderd.CreateIniti return req } -// CreateProjectImportProvisionerJob creates a project import provisioner job +// CreateProjectImportJob creates a project import provisioner job // with the responses provided. It uses the "echo" provisioner for compatibility // with testing. -func CreateProjectImportProvisionerJob(t *testing.T, client *codersdk.Client, organization string, res *echo.Responses) coderd.ProvisionerJob { +func CreateProjectImportJob(t *testing.T, client *codersdk.Client, organization string, res *echo.Responses) coderd.ProvisionerJob { data, err := echo.Tar(res) require.NoError(t, err) file, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data) require.NoError(t, err) - job, err := client.CreateProjectVersionImportProvisionerJob(context.Background(), organization, coderd.CreateProjectImportJobRequest{ + job, err := client.CreateProjectImportJob(context.Background(), organization, coderd.CreateProjectImportJobRequest{ StorageSource: file.Hash, StorageMethod: database.ProvisionerStorageMethodFile, Provisioner: database.ProvisionerTypeEcho, @@ -149,12 +149,24 @@ func CreateProject(t *testing.T, client *codersdk.Client, organization string, j return project } -// AwaitProvisionerJob awaits for a job to reach completed status. -func AwaitProvisionerJob(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.ProvisionerJob { +// AwaitProjectImportJob awaits for an import job to reach completed status. +func AwaitProjectImportJob(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.ProvisionerJob { var provisionerJob coderd.ProvisionerJob require.Eventually(t, func() bool { var err error - provisionerJob, err = client.ProvisionerJob(context.Background(), organization, job) + provisionerJob, err = client.ProjectImportJob(context.Background(), organization, job) + require.NoError(t, err) + return provisionerJob.Status.Completed() + }, 3*time.Second, 25*time.Millisecond) + return provisionerJob +} + +// AwaitWorkspaceProvisionJob awaits for a workspace provision job to reach completed status. +func AwaitWorkspaceProvisionJob(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.ProvisionerJob { + var provisionerJob coderd.ProvisionerJob + require.Eventually(t, func() bool { + var err error + provisionerJob, err = client.WorkspaceProvisionJob(context.Background(), organization, job) require.NoError(t, err) return provisionerJob.Status.Completed() }, 3*time.Second, 25*time.Millisecond) diff --git a/coderd/coderdtest/coderdtest_test.go b/coderd/coderdtest/coderdtest_test.go index b388ea6428f43..f351bb4a4d5a4 100644 --- a/coderd/coderdtest/coderdtest_test.go +++ b/coderd/coderdtest/coderdtest_test.go @@ -22,8 +22,8 @@ func TestNew(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) closer := coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) history, err := client.CreateWorkspaceHistory(context.Background(), "me", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ @@ -31,6 +31,6 @@ func TestNew(t *testing.T) { Transition: database.WorkspaceTransitionStart, }) require.NoError(t, err) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, history.ProvisionJobID) + coderdtest.AwaitWorkspaceProvisionJob(t, client, user.Organization, history.ProvisionJobID) closer.Close() } diff --git a/coderd/parameters.go b/coderd/parameters.go deleted file mode 100644 index 0df7f112aadda..0000000000000 --- a/coderd/parameters.go +++ /dev/null @@ -1,109 +0,0 @@ -package coderd - -import ( - "database/sql" - "errors" - "fmt" - "net/http" - - "github.com/go-chi/render" - "github.com/google/uuid" - - "github.com/coder/coder/coderd/parameter" - "github.com/coder/coder/database" - "github.com/coder/coder/httpapi" -) - -// ParameterSchema represents a parameter parsed from project version source. -type ParameterSchema database.ParameterSchema - -// ParameterValue represents a set value for the scope. -type ParameterValue database.ParameterValue - -// ComputedParameterValue represents a computed parameter value. -type ComputedParameterValue parameter.ComputedValue - -// CreateParameterValueRequest is used to create a new parameter value for a scope. -type CreateParameterValueRequest struct { - Name string `json:"name" validate:"required"` - SourceValue string `json:"source_value" validate:"required"` - SourceScheme database.ParameterSourceScheme `json:"source_scheme" validate:"oneof=data,required"` - DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme" validate:"oneof=environment_variable provisioner_variable,required"` -} - -// Abstracts creating parameters into a single request/response format. -// Callers are in charge of validating the requester has permissions to -// perform the creation. -func postParameterValueForScope(rw http.ResponseWriter, r *http.Request, db database.Store, scope database.ParameterScope, scopeID string) { - var createRequest CreateParameterValueRequest - if !httpapi.Read(rw, r, &createRequest) { - return - } - parameterValue, err := db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ - ID: uuid.New(), - Name: createRequest.Name, - CreatedAt: database.Now(), - UpdatedAt: database.Now(), - Scope: scope, - ScopeID: scopeID, - SourceScheme: createRequest.SourceScheme, - SourceValue: createRequest.SourceValue, - DestinationScheme: createRequest.DestinationScheme, - }) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("insert parameter value: %s", err), - }) - return - } - - render.Status(r, http.StatusCreated) - render.JSON(rw, r, parameterValue) -} - -// Abstracts returning parameters for a scope into a standardized -// request/response format. Callers are responsible for checking -// requester permissions. -func parametersForScope(rw http.ResponseWriter, r *http.Request, db database.Store, req database.GetParameterValuesByScopeParams) { - parameterValues, err := db.GetParameterValuesByScope(r.Context(), req) - if errors.Is(err, sql.ErrNoRows) { - err = nil - parameterValues = []database.ParameterValue{} - } - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("get parameter values: %s", err), - }) - return - } - - apiParameterValues := make([]ParameterValue, 0, len(parameterValues)) - for _, parameterValue := range parameterValues { - apiParameterValues = append(apiParameterValues, convertParameterValue(parameterValue)) - } - - render.Status(r, http.StatusOK) - render.JSON(rw, r, apiParameterValues) -} - -// Returns parameters for a specific scope. -func computedParametersForScope(rw http.ResponseWriter, r *http.Request, db database.Store, scope parameter.ComputeScope) { - values, err := parameter.Compute(r.Context(), db, scope, ¶meter.ComputeOptions{ - // We *never* want to send the client secret parameter values. - HideRedisplayValues: true, - }) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("compute values: %s", err), - }) - return - } - - render.Status(r, http.StatusOK) - render.JSON(rw, r, values) -} - -func convertParameterValue(parameterValue database.ParameterValue) ParameterValue { - parameterValue.SourceValue = "" - return ParameterValue(parameterValue) -} diff --git a/coderd/projectimport.go b/coderd/projectimport.go new file mode 100644 index 0000000000000..d3032f9b1eee5 --- /dev/null +++ b/coderd/projectimport.go @@ -0,0 +1,185 @@ +package coderd + +import ( + "database/sql" + "errors" + "fmt" + "net/http" + + "github.com/go-chi/render" + "github.com/google/uuid" + + "github.com/coder/coder/coderd/parameter" + "github.com/coder/coder/database" + "github.com/coder/coder/httpapi" + "github.com/coder/coder/httpmw" +) + +// ParameterSchema represents a parameter parsed from project version source. +type ParameterSchema database.ParameterSchema + +// ComputedParameterValue represents a computed parameter value. +type ComputedParameterValue parameter.ComputedValue + +// ProjectImportJobResource is a resource created by a project import job. +type ProjectImportJobResource database.ProjectImportJobResource + +// CreateProjectImportJobRequest provides options to create a project import job. +type CreateProjectImportJobRequest struct { + StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"` + StorageSource string `json:"storage_source" validate:"required"` + Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"` + // ParameterValues allows for additional parameters to be provided + // during the dry-run provision stage. + ParameterValues []CreateParameterValueRequest `json:"parameter_values"` +} + +// Create a new project import job! +func (api *api) postProjectImportByOrganization(rw http.ResponseWriter, r *http.Request) { + apiKey := httpmw.APIKey(r) + organization := httpmw.OrganizationParam(r) + var req CreateProjectImportJobRequest + if !httpapi.Read(rw, r, &req) { + return + } + file, err := api.Database.GetFileByHash(r.Context(), req.StorageSource) + if errors.Is(err, sql.ErrNoRows) { + httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ + Message: "file not found", + }) + return + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get file: %s", err), + }) + return + } + + jobID := uuid.New() + for _, parameterValue := range req.ParameterValues { + _, err = api.Database.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: parameterValue.Name, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + Scope: database.ParameterScopeImportJob, + ScopeID: jobID.String(), + SourceScheme: parameterValue.SourceScheme, + SourceValue: parameterValue.SourceValue, + DestinationScheme: parameterValue.DestinationScheme, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("insert parameter value: %s", err), + }) + return + } + } + + job, err := api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ + ID: jobID, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + OrganizationID: organization.ID, + InitiatorID: apiKey.UserID, + Provisioner: req.Provisioner, + StorageMethod: database.ProvisionerStorageMethodFile, + StorageSource: file.Hash, + Type: database.ProvisionerJobTypeProjectVersionImport, + Input: []byte{'{', '}'}, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("insert provisioner job: %s", err), + }) + return + } + + render.Status(r, http.StatusCreated) + render.JSON(rw, r, convertProvisionerJob(job)) +} + +// Returns imported parameter schemas from a completed job! +func (api *api) projectImportJobSchemasByID(rw http.ResponseWriter, r *http.Request) { + job := httpmw.ProvisionerJobParam(r) + if !convertProvisionerJob(job).Status.Completed() { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: "Job hasn't completed!", + }) + return + } + + schemas, err := api.Database.GetParameterSchemasByJobID(r.Context(), job.ID) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("list parameter schemas: %s", err), + }) + return + } + if schemas == nil { + schemas = []database.ParameterSchema{} + } + render.Status(r, http.StatusOK) + render.JSON(rw, r, schemas) +} + +// Returns computed parameters for an import job by ID. +func (api *api) projectImportJobParametersByID(rw http.ResponseWriter, r *http.Request) { + apiKey := httpmw.APIKey(r) + job := httpmw.ProvisionerJobParam(r) + if !convertProvisionerJob(job).Status.Completed() { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: "Job hasn't completed!", + }) + return + } + values, err := parameter.Compute(r.Context(), api.Database, parameter.ComputeScope{ + ProjectImportJobID: job.ID, + OrganizationID: job.OrganizationID, + UserID: apiKey.UserID, + }, ¶meter.ComputeOptions{ + // We *never* want to send the client secret parameter values. + HideRedisplayValues: true, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("compute values: %s", err), + }) + return + } + if values == nil { + values = []parameter.ComputedValue{} + } + render.Status(r, http.StatusOK) + render.JSON(rw, r, values) +} + +// Returns resources for an import job by ID. +func (api *api) projectImportJobResourcesByID(rw http.ResponseWriter, r *http.Request) { + job := httpmw.ProvisionerJobParam(r) + if !convertProvisionerJob(job).Status.Completed() { + httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ + Message: "Job hasn't completed!", + }) + return + } + resources, err := api.Database.GetProjectImportJobResourcesByJobID(r.Context(), job.ID) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get project import job resources: %s", err), + }) + return + } + if resources == nil { + resources = []database.ProjectImportJobResource{} + } + render.Status(r, http.StatusOK) + render.JSON(rw, r, resources) +} diff --git a/coderd/projectimport_test.go b/coderd/projectimport_test.go new file mode 100644 index 0000000000000..06140190f51d5 --- /dev/null +++ b/coderd/projectimport_test.go @@ -0,0 +1,162 @@ +package coderd_test + +import ( + "context" + "net/http" + "testing" + + "github.com/coder/coder/coderd" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/codersdk" + "github.com/coder/coder/database" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk/proto" + "github.com/stretchr/testify/require" +) + +func TestPostProjectImportByOrganization(t *testing.T) { + t.Parallel() + t.Run("FileNotFound", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _, err := client.CreateProjectImportJob(context.Background(), user.Organization, coderd.CreateProjectImportJobRequest{ + StorageMethod: database.ProvisionerStorageMethodFile, + StorageSource: "bananas", + Provisioner: database.ProvisionerTypeEcho, + }) + require.Error(t, err) + }) + t.Run("Create", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _ = coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + }) +} + +func TestProjectImportJobSchemasByID(t *testing.T) { + t.Parallel() + t.Run("ListRunning", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + _, err := client.ProjectImportJobSchemas(context.Background(), user.Organization, job.ID) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) + }) + t.Run("List", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "example", + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + }, + }}, + }, + }, + }}, + Provision: echo.ProvisionComplete, + }) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + schemas, err := client.ProjectImportJobSchemas(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + require.NotNil(t, schemas) + require.Len(t, schemas, 1) + }) +} + +func TestProjectImportJobParametersByID(t *testing.T) { + t.Parallel() + t.Run("ListRunning", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + _, err := client.ProjectImportJobSchemas(context.Background(), user.Organization, job.ID) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) + }) + t.Run("List", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{ + ParameterSchemas: []*proto.ParameterSchema{{ + Name: "example", + RedisplayValue: true, + DefaultSource: &proto.ParameterSource{ + Scheme: proto.ParameterSource_DATA, + Value: "hello", + }, + DefaultDestination: &proto.ParameterDestination{ + Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE, + }, + }}, + }, + }, + }}, + Provision: echo.ProvisionComplete, + }) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + params, err := client.ProjectImportJobParameters(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + require.NotNil(t, params) + require.Len(t, params, 1) + require.Equal(t, "hello", params[0].SourceValue) + }) +} + +func TestProjectImportJobResourcesByID(t *testing.T) { + t.Parallel() + t.Run("ListRunning", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + _, err := client.ProjectImportJobResources(context.Background(), user.Organization, job.ID) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) + }) + t.Run("List", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "some", + Type: "example", + }}, + }, + }, + }}, + }) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + resources, err := client.ProjectImportJobResources(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + require.NotNil(t, resources) + require.Len(t, resources, 2) + require.Equal(t, "some", resources[0].Name) + require.Equal(t, "example", resources[0].Type) + }) +} diff --git a/coderd/projects.go b/coderd/projects.go index f50c192e6a006..90c6ce243888b 100644 --- a/coderd/projects.go +++ b/coderd/projects.go @@ -16,6 +16,17 @@ import ( "github.com/coder/coder/httpmw" ) +// ParameterValue represents a set value for the scope. +type ParameterValue database.ParameterValue + +// CreateParameterValueRequest is used to create a new parameter value for a scope. +type CreateParameterValueRequest struct { + Name string `json:"name" validate:"required"` + SourceValue string `json:"source_value" validate:"required"` + SourceScheme database.ParameterSourceScheme `json:"source_scheme" validate:"oneof=data,required"` + DestinationScheme database.ParameterDestinationScheme `json:"destination_scheme" validate:"oneof=environment_variable provisioner_variable,required"` +} + // Project is the JSON representation of a Coder project. // This type matches the database object for now, but is // abstracted for ease of change later on. @@ -177,16 +188,60 @@ func (*api) projectByOrganization(rw http.ResponseWriter, r *http.Request) { // This should validate the calling user has permissions! func (api *api) postParametersByProject(rw http.ResponseWriter, r *http.Request) { project := httpmw.ProjectParam(r) + var createRequest CreateParameterValueRequest + if !httpapi.Read(rw, r, &createRequest) { + return + } + parameterValue, err := api.Database.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + ID: uuid.New(), + Name: createRequest.Name, + CreatedAt: database.Now(), + UpdatedAt: database.Now(), + Scope: database.ParameterScopeProject, + ScopeID: project.ID.String(), + SourceScheme: createRequest.SourceScheme, + SourceValue: createRequest.SourceValue, + DestinationScheme: createRequest.DestinationScheme, + }) + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("insert parameter value: %s", err), + }) + return + } - postParameterValueForScope(rw, r, api.Database, database.ParameterScopeProject, project.ID.String()) + render.Status(r, http.StatusCreated) + render.JSON(rw, r, parameterValue) } // Lists parameters for a project. func (api *api) parametersByProject(rw http.ResponseWriter, r *http.Request) { project := httpmw.ProjectParam(r) - - parametersForScope(rw, r, api.Database, database.GetParameterValuesByScopeParams{ + parameterValues, err := api.Database.GetParameterValuesByScope(r.Context(), database.GetParameterValuesByScopeParams{ Scope: database.ParameterScopeProject, ScopeID: project.ID.String(), }) + if errors.Is(err, sql.ErrNoRows) { + err = nil + parameterValues = []database.ParameterValue{} + } + if err != nil { + httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ + Message: fmt.Sprintf("get parameter values: %s", err), + }) + return + } + + apiParameterValues := make([]ParameterValue, 0, len(parameterValues)) + for _, parameterValue := range parameterValues { + apiParameterValues = append(apiParameterValues, convertParameterValue(parameterValue)) + } + + render.Status(r, http.StatusOK) + render.JSON(rw, r, apiParameterValues) +} + +func convertParameterValue(parameterValue database.ParameterValue) ParameterValue { + parameterValue.SourceValue = "" + return ParameterValue(parameterValue) } diff --git a/coderd/projects_test.go b/coderd/projects_test.go index 42efedb939f83..9ea0cbf87f443 100644 --- a/coderd/projects_test.go +++ b/coderd/projects_test.go @@ -30,7 +30,7 @@ func TestProjects(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) _ = coderdtest.CreateProject(t, client, user.Organization, job.ID) projects, err := client.Projects(context.Background(), "") require.NoError(t, err) @@ -54,7 +54,7 @@ func TestProjectsByOrganization(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) _ = coderdtest.CreateProject(t, client, user.Organization, job.ID) projects, err := client.Projects(context.Background(), "") require.NoError(t, err) @@ -68,7 +68,7 @@ func TestPostProjectsByOrganization(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) _ = coderdtest.CreateProject(t, client, user.Organization, job.ID) }) @@ -76,7 +76,7 @@ func TestPostProjectsByOrganization(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ Name: project.Name, @@ -94,7 +94,7 @@ func TestProjectByOrganization(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.Project(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -107,7 +107,7 @@ func TestPostParametersByProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{ Name: "somename", @@ -125,7 +125,7 @@ func TestParametersByProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) params, err := client.ProjectParameters(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -136,7 +136,7 @@ func TestParametersByProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{ Name: "example", diff --git a/coderd/projectversion_test.go b/coderd/projectversion_test.go index a842f3ec277cf..d288b53552086 100644 --- a/coderd/projectversion_test.go +++ b/coderd/projectversion_test.go @@ -16,7 +16,7 @@ func TestProjectVersionsByOrganization(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) versions, err := client.ProjectVersions(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -31,7 +31,7 @@ func TestProjectVersionByOrganizationAndName(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String()) require.NoError(t, err) @@ -44,7 +44,7 @@ func TestPostProjectVersionByOrganization(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{ ImportJobID: job.ID, diff --git a/coderd/provisionerdaemons_test.go b/coderd/provisionerdaemons_test.go index 4c62c7c96e355..b0f1ff54c9b65 100644 --- a/coderd/provisionerdaemons_test.go +++ b/coderd/provisionerdaemons_test.go @@ -11,11 +11,8 @@ import ( ) func TestProvisionerDaemons(t *testing.T) { - // Tests for properly processing specific job - // types should be placed in their respective - // resource location. - // - // eg. project import is a project-related job + // Tests for properly processing specific job types should be placed + // in their respective files. t.Parallel() client := coderdtest.New(t) diff --git a/coderd/provisionerjoblogs.go b/coderd/provisionerjoblogs.go deleted file mode 100644 index f47c7825a300f..0000000000000 --- a/coderd/provisionerjoblogs.go +++ /dev/null @@ -1,196 +0,0 @@ -package coderd - -import ( - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/go-chi/render" - "github.com/google/uuid" - - "cdr.dev/slog" - "github.com/coder/coder/database" - "github.com/coder/coder/httpapi" - "github.com/coder/coder/httpmw" -) - -// ProvisionerJobLog represents a single log from a provisioner job. -type ProvisionerJobLog struct { - ID uuid.UUID - CreatedAt time.Time `json:"created_at"` - Source database.LogSource `json:"log_source"` - Level database.LogLevel `json:"log_level"` - Output string `json:"output"` -} - -// Returns provisioner logs based on query parameters. -// The intended usage for a client to stream all logs (with JS API): -// const timestamp = new Date().getTime(); -// 1. GET /logs?before= -// 2. GET /logs?after=&follow -// The combination of these responses should provide all current logs -// to the consumer, and future logs are streamed in the follow request. -func (api *api) provisionerJobLogsByID(rw http.ResponseWriter, r *http.Request) { - follow := r.URL.Query().Has("follow") - afterRaw := r.URL.Query().Get("after") - beforeRaw := r.URL.Query().Get("before") - if beforeRaw != "" && follow { - httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: "before cannot be used with follow", - }) - return - } - - var after time.Time - // Only fetch logs created after the time provided. - if afterRaw != "" { - afterMS, err := strconv.ParseInt(afterRaw, 10, 64) - if err != nil { - httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: fmt.Sprintf("unable to parse after %q: %s", afterRaw, err), - }) - return - } - after = time.UnixMilli(afterMS) - } else { - if follow { - after = database.Now() - } - } - var before time.Time - // Only fetch logs created before the time provided. - if beforeRaw != "" { - beforeMS, err := strconv.ParseInt(beforeRaw, 10, 64) - if err != nil { - httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: fmt.Sprintf("unable to parse before %q: %s", beforeRaw, err), - }) - return - } - before = time.UnixMilli(beforeMS) - } else { - before = database.Now() - } - - job := httpmw.ProvisionerJobParam(r) - if !follow { - logs, err := api.Database.GetProvisionerLogsByIDBetween(r.Context(), database.GetProvisionerLogsByIDBetweenParams{ - JobID: job.ID, - CreatedAfter: after, - CreatedBefore: before, - }) - if errors.Is(err, sql.ErrNoRows) { - err = nil - } - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("get provisioner logs: %s", err), - }) - return - } - if logs == nil { - logs = []database.ProvisionerJobLog{} - } - render.Status(r, http.StatusOK) - render.JSON(rw, r, logs) - return - } - - bufferedLogs := make(chan database.ProvisionerJobLog, 128) - closeSubscribe, err := api.Pubsub.Subscribe(provisionerJobLogsChannel(job.ID), func(ctx context.Context, message []byte) { - var logs []database.ProvisionerJobLog - err := json.Unmarshal(message, &logs) - if err != nil { - api.Logger.Warn(r.Context(), fmt.Sprintf("invalid provisioner job log on channel %q: %s", provisionerJobLogsChannel(job.ID), err.Error())) - return - } - - for _, log := range logs { - select { - case bufferedLogs <- log: - default: - // If this overflows users could miss logs streaming. This can happen - // if a database request takes a long amount of time, and we get a lot of logs. - api.Logger.Warn(r.Context(), "provisioner job log overflowing channel") - } - } - }) - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("subscribe to provisioner job logs: %s", err), - }) - return - } - defer closeSubscribe() - - provisionerJobLogs, err := api.Database.GetProvisionerLogsByIDBetween(r.Context(), database.GetProvisionerLogsByIDBetweenParams{ - JobID: job.ID, - CreatedAfter: after, - CreatedBefore: before, - }) - if errors.Is(err, sql.ErrNoRows) { - err = nil - provisionerJobLogs = []database.ProvisionerJobLog{} - } - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprint("get provisioner job logs: %w", err), - }) - return - } - - // "follow" uses the ndjson format to stream data. - // See: https://canjs.com/doc/can-ndjson-stream.html - rw.Header().Set("Content-Type", "application/stream+json") - rw.WriteHeader(http.StatusOK) - rw.(http.Flusher).Flush() - - // The Go stdlib JSON encoder appends a newline character after message write. - encoder := json.NewEncoder(rw) - - for _, provisionerJobLog := range provisionerJobLogs { - err = encoder.Encode(convertProvisionerJobLog(provisionerJobLog)) - if err != nil { - return - } - } - - ticker := time.NewTicker(250 * time.Millisecond) - defer ticker.Stop() - for { - select { - case <-r.Context().Done(): - return - case log := <-bufferedLogs: - err = encoder.Encode(convertProvisionerJobLog(log)) - if err != nil { - return - } - rw.(http.Flusher).Flush() - case <-ticker.C: - job, err := api.Database.GetProvisionerJobByID(r.Context(), job.ID) - if err != nil { - api.Logger.Warn(r.Context(), "streaming job logs; checking if completed", slog.Error(err), slog.F("job_id", job.ID.String())) - continue - } - if convertProvisionerJob(job).Status.Completed() { - return - } - } - } -} - -func convertProvisionerJobLog(provisionerJobLog database.ProvisionerJobLog) ProvisionerJobLog { - return ProvisionerJobLog{ - ID: provisionerJobLog.ID, - CreatedAt: provisionerJobLog.CreatedAt, - Source: provisionerJobLog.Source, - Level: provisionerJobLog.Level, - Output: provisionerJobLog.Output, - } -} diff --git a/coderd/provisionerjoblogs_test.go b/coderd/provisionerjoblogs_test.go deleted file mode 100644 index 1667d6feb74d5..0000000000000 --- a/coderd/provisionerjoblogs_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package coderd_test - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/coder/coder/coderd" - "github.com/coder/coder/coderd/coderdtest" - "github.com/coder/coder/database" - "github.com/coder/coder/provisioner/echo" - "github.com/coder/coder/provisionersdk/proto" -) - -func TestProvisionerJobLogsByName(t *testing.T) { - t.Parallel() - t.Run("List", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ - Parse: echo.ParseComplete, - Provision: []*proto.Provision_Response{{ - Type: &proto.Provision_Response_Log{ - Log: &proto.Log{ - Level: proto.LogLevel_INFO, - Output: "log-output", - }, - }, - }, { - Type: &proto.Provision_Response_Complete{ - Complete: &proto.Provision_Complete{}, - }, - }}, - }) - project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) - history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ - ProjectVersionID: project.ActiveVersionID, - Transition: database.WorkspaceTransitionStart, - }) - require.NoError(t, err) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, history.ProvisionJobID) - // Return the log after completion! - logs, err := client.ProvisionerJobLogs(context.Background(), user.Organization, history.ProvisionJobID) - require.NoError(t, err) - require.NotNil(t, logs) - require.Len(t, logs, 1) - }) - - t.Run("StreamAfterComplete", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ - Parse: echo.ParseComplete, - Provision: []*proto.Provision_Response{{ - Type: &proto.Provision_Response_Log{ - Log: &proto.Log{ - Level: proto.LogLevel_INFO, - Output: "log-output", - }, - }, - }, { - Type: &proto.Provision_Response_Complete{ - Complete: &proto.Provision_Complete{}, - }, - }}, - }) - project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) - before := time.Now().UTC() - history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ - ProjectVersionID: project.ActiveVersionID, - Transition: database.WorkspaceTransitionStart, - }) - require.NoError(t, err) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, history.ProvisionJobID) - - logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), user.Organization, history.ProvisionJobID, before) - require.NoError(t, err) - log, ok := <-logs - require.True(t, ok) - require.Equal(t, "log-output", log.Output) - // Make sure the channel automatically closes! - _, ok = <-logs - require.False(t, ok) - }) - - t.Run("StreamWhileRunning", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ - Parse: echo.ParseComplete, - Provision: []*proto.Provision_Response{{ - Type: &proto.Provision_Response_Log{ - Log: &proto.Log{ - Level: proto.LogLevel_INFO, - Output: "log-output", - }, - }, - }, { - Type: &proto.Provision_Response_Complete{ - Complete: &proto.Provision_Complete{}, - }, - }}, - }) - project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) - before := database.Now() - history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ - ProjectVersionID: project.ActiveVersionID, - Transition: database.WorkspaceTransitionStart, - }) - require.NoError(t, err) - logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), user.Organization, history.ProvisionJobID, before) - require.NoError(t, err) - log := <-logs - require.Equal(t, "log-output", log.Output) - // Make sure the channel automatically closes! - _, ok := <-logs - require.False(t, ok) - }) -} diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index a7e75aa37b321..a817b39f24873 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -1,23 +1,25 @@ package coderd import ( + "context" "database/sql" + "encoding/json" "errors" "fmt" "net/http" + "strconv" "time" "github.com/go-chi/render" "github.com/google/uuid" - "github.com/coder/coder/coderd/parameter" + "cdr.dev/slog" + "github.com/coder/coder/database" "github.com/coder/coder/httpapi" "github.com/coder/coder/httpmw" ) -type ProjectImportJobResource database.ProjectImportJobResource - type ProvisionerJobStatus string // Completed returns whether the job is still processing. @@ -46,147 +48,186 @@ type ProvisionerJob struct { WorkerID *uuid.UUID `json:"worker_id,omitempty"` } -type CreateProjectImportJobRequest struct { - StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"` - StorageSource string `json:"storage_source" validate:"required"` - Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"` - ParameterValues []CreateParameterValueRequest `json:"parameter_values"` +// ProvisionerJobLog represents a single log from a provisioner job. +type ProvisionerJobLog struct { + ID uuid.UUID + CreatedAt time.Time `json:"created_at"` + Source database.LogSource `json:"log_source"` + Level database.LogLevel `json:"log_level"` + Output string `json:"output"` } -func (*api) provisionerJobByOrganization(rw http.ResponseWriter, r *http.Request) { +func (*api) provisionerJobByID(rw http.ResponseWriter, r *http.Request) { job := httpmw.ProvisionerJobParam(r) - render.Status(r, http.StatusOK) render.JSON(rw, r, convertProvisionerJob(job)) } -func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r *http.Request) { - apiKey := httpmw.APIKey(r) - organization := httpmw.OrganizationParam(r) - var req CreateProjectImportJobRequest - if !httpapi.Read(rw, r, &req) { - return - } - file, err := api.Database.GetFileByHash(r.Context(), req.StorageSource) - if errors.Is(err, sql.ErrNoRows) { +// Returns provisioner logs based on query parameters. +// The intended usage for a client to stream all logs (with JS API): +// const timestamp = new Date().getTime(); +// 1. GET /logs?before= +// 2. GET /logs?after=&follow +// The combination of these responses should provide all current logs +// to the consumer, and future logs are streamed in the follow request. +func (api *api) provisionerJobLogsByID(rw http.ResponseWriter, r *http.Request) { + follow := r.URL.Query().Has("follow") + afterRaw := r.URL.Query().Get("after") + beforeRaw := r.URL.Query().Get("before") + if beforeRaw != "" && follow { httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ - Message: "file not found", + Message: "before cannot be used with follow", }) return } - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("get file: %s", err), - }) - return + + var after time.Time + // Only fetch logs created after the time provided. + if afterRaw != "" { + afterMS, err := strconv.ParseInt(afterRaw, 10, 64) + if err != nil { + httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ + Message: fmt.Sprintf("unable to parse after %q: %s", afterRaw, err), + }) + return + } + after = time.UnixMilli(afterMS) + } else { + if follow { + after = database.Now() + } + } + var before time.Time + // Only fetch logs created before the time provided. + if beforeRaw != "" { + beforeMS, err := strconv.ParseInt(beforeRaw, 10, 64) + if err != nil { + httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ + Message: fmt.Sprintf("unable to parse before %q: %s", beforeRaw, err), + }) + return + } + before = time.UnixMilli(beforeMS) + } else { + before = database.Now() } - jobID := uuid.New() - for _, parameterValue := range req.ParameterValues { - _, err = api.Database.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ - ID: uuid.New(), - Name: parameterValue.Name, - CreatedAt: database.Now(), - UpdatedAt: database.Now(), - Scope: database.ParameterScopeImportJob, - ScopeID: jobID.String(), - SourceScheme: parameterValue.SourceScheme, - SourceValue: parameterValue.SourceValue, - DestinationScheme: parameterValue.DestinationScheme, + job := httpmw.ProvisionerJobParam(r) + if !follow { + logs, err := api.Database.GetProvisionerLogsByIDBetween(r.Context(), database.GetProvisionerLogsByIDBetweenParams{ + JobID: job.ID, + CreatedAfter: after, + CreatedBefore: before, }) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("insert parameter value: %s", err), + Message: fmt.Sprintf("get provisioner logs: %s", err), }) return } + if logs == nil { + logs = []database.ProvisionerJobLog{} + } + render.Status(r, http.StatusOK) + render.JSON(rw, r, logs) + return } - job, err := api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ - ID: jobID, - CreatedAt: database.Now(), - UpdatedAt: database.Now(), - OrganizationID: organization.ID, - InitiatorID: apiKey.UserID, - Provisioner: req.Provisioner, - StorageMethod: database.ProvisionerStorageMethodFile, - StorageSource: file.Hash, - Type: database.ProvisionerJobTypeProjectVersionImport, - Input: []byte{'{', '}'}, + bufferedLogs := make(chan database.ProvisionerJobLog, 128) + closeSubscribe, err := api.Pubsub.Subscribe(provisionerJobLogsChannel(job.ID), func(ctx context.Context, message []byte) { + var logs []database.ProvisionerJobLog + err := json.Unmarshal(message, &logs) + if err != nil { + api.Logger.Warn(r.Context(), fmt.Sprintf("invalid provisioner job log on channel %q: %s", provisionerJobLogsChannel(job.ID), err.Error())) + return + } + + for _, log := range logs { + select { + case bufferedLogs <- log: + default: + // If this overflows users could miss logs streaming. This can happen + // if a database request takes a long amount of time, and we get a lot of logs. + api.Logger.Warn(r.Context(), "provisioner job log overflowing channel") + } + } }) if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("insert provisioner job: %s", err), - }) - return - } - - render.Status(r, http.StatusCreated) - render.JSON(rw, r, convertProvisionerJob(job)) -} - -// Return parsed parameter schemas for a job. -func (api *api) provisionerJobParameterSchemasByID(rw http.ResponseWriter, r *http.Request) { - job := httpmw.ProvisionerJobParam(r) - if !convertProvisionerJob(job).Status.Completed() { - httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ - Message: "Job hasn't completed!", + Message: fmt.Sprintf("subscribe to provisioner job logs: %s", err), }) return } + defer closeSubscribe() - schemas, err := api.Database.GetParameterSchemasByJobID(r.Context(), job.ID) + provisionerJobLogs, err := api.Database.GetProvisionerLogsByIDBetween(r.Context(), database.GetProvisionerLogsByIDBetweenParams{ + JobID: job.ID, + CreatedAfter: after, + CreatedBefore: before, + }) if errors.Is(err, sql.ErrNoRows) { err = nil + provisionerJobLogs = []database.ProvisionerJobLog{} } if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("list parameter schemas: %s", err), + Message: fmt.Sprint("get provisioner job logs: %w", err), }) return } - render.Status(r, http.StatusOK) - render.JSON(rw, r, schemas) -} + // "follow" uses the ndjson format to stream data. + // See: https://canjs.com/doc/can-ndjson-stream.html + rw.Header().Set("Content-Type", "application/stream+json") + rw.WriteHeader(http.StatusOK) + rw.(http.Flusher).Flush() -func (api *api) provisionerJobComputedParametersByID(rw http.ResponseWriter, r *http.Request) { - apiKey := httpmw.APIKey(r) - job := httpmw.ProvisionerJobParam(r) - if !convertProvisionerJob(job).Status.Completed() { - httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ - Message: "Job hasn't completed!", - }) - return - } - computedParametersForScope(rw, r, api.Database, parameter.ComputeScope{ - ProjectImportJobID: job.ID, - OrganizationID: job.OrganizationID, - UserID: apiKey.UserID, - }) -} + // The Go stdlib JSON encoder appends a newline character after message write. + encoder := json.NewEncoder(rw) -func (api *api) provisionerJobResourcesByID(rw http.ResponseWriter, r *http.Request) { - job := httpmw.ProvisionerJobParam(r) - if !convertProvisionerJob(job).Status.Completed() { - httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{ - Message: "Job hasn't completed!", - }) - return - } - resources, err := api.Database.GetProjectImportJobResourcesByJobID(r.Context(), job.ID) - if errors.Is(err, sql.ErrNoRows) { - err = nil + for _, provisionerJobLog := range provisionerJobLogs { + err = encoder.Encode(convertProvisionerJobLog(provisionerJobLog)) + if err != nil { + return + } } - if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ - Message: fmt.Sprintf("get project import job resources: %s", err), - }) - return + + ticker := time.NewTicker(250 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-r.Context().Done(): + return + case log := <-bufferedLogs: + err = encoder.Encode(convertProvisionerJobLog(log)) + if err != nil { + return + } + rw.(http.Flusher).Flush() + case <-ticker.C: + job, err := api.Database.GetProvisionerJobByID(r.Context(), job.ID) + if err != nil { + api.Logger.Warn(r.Context(), "streaming job logs; checking if completed", slog.Error(err), slog.F("job_id", job.ID.String())) + continue + } + if convertProvisionerJob(job).Status.Completed() { + return + } + } } +} - render.Status(r, http.StatusOK) - render.JSON(rw, r, resources) +func convertProvisionerJobLog(provisionerJobLog database.ProvisionerJobLog) ProvisionerJobLog { + return ProvisionerJobLog{ + ID: provisionerJobLog.ID, + CreatedAt: provisionerJobLog.CreatedAt, + Source: provisionerJobLog.Source, + Level: provisionerJobLog.Level, + Output: provisionerJobLog.Output, + } } func convertProvisionerJob(provisionerJob database.ProvisionerJob) ProvisionerJob { diff --git a/coderd/provisionerjobs_test.go b/coderd/provisionerjobs_test.go index bb4831362a3bf..8a45d7b97f42e 100644 --- a/coderd/provisionerjobs_test.go +++ b/coderd/provisionerjobs_test.go @@ -25,7 +25,7 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { user := coderdtest.CreateInitialUser(t, client) _ = coderdtest.NewProvisionerDaemon(t, client) before := time.Now() - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ Parse: []*proto.Parse_Response{{ Type: &proto.Parse_Response_Complete{ Complete: &proto.Parse_Complete{ @@ -44,7 +44,7 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { }, }}, }) - logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), user.Organization, job.ID, before) + logs, err := client.ProjectImportJobLogsAfter(context.Background(), user.Organization, job.ID, before) require.NoError(t, err) for { log, ok := <-logs @@ -76,7 +76,7 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { require.NoError(t, err) file, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data) require.NoError(t, err) - job, err := client.CreateProjectVersionImportProvisionerJob(context.Background(), user.Organization, coderd.CreateProjectImportJobRequest{ + job, err := client.CreateProjectImportJob(context.Background(), user.Organization, coderd.CreateProjectImportJobRequest{ StorageSource: file.Hash, StorageMethod: database.ProvisionerStorageMethodFile, Provisioner: database.ProvisionerTypeEcho, @@ -88,8 +88,8 @@ func TestPostProvisionerImportJobByOrganization(t *testing.T) { }}, }) require.NoError(t, err) - job = coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - values, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) + job = coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + values, err := client.ProjectImportJobParameters(context.Background(), user.Organization, job.ID) require.NoError(t, err) require.Equal(t, "somevalue", values[0].SourceValue) }) @@ -101,8 +101,8 @@ func TestProvisionerJobParametersByID(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) - _, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + _, err := client.ProjectImportJobParameters(context.Background(), user.Organization, job.ID) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode()) @@ -113,7 +113,7 @@ func TestProvisionerJobParametersByID(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ Parse: []*proto.Parse_Response{{ Type: &proto.Parse_Response_Complete{ Complete: &proto.Parse_Complete{ @@ -132,8 +132,8 @@ func TestProvisionerJobParametersByID(t *testing.T) { }}, Provision: echo.ProvisionComplete, }) - job = coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - params, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) + job = coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + params, err := client.ProjectImportJobParameters(context.Background(), user.Organization, job.ID) require.NoError(t, err) require.Len(t, params, 1) }) @@ -143,7 +143,7 @@ func TestProvisionerJobParametersByID(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ Parse: []*proto.Parse_Response{{ Type: &proto.Parse_Response_Complete{ Complete: &proto.Parse_Complete{ @@ -163,8 +163,8 @@ func TestProvisionerJobParametersByID(t *testing.T) { }}, Provision: echo.ProvisionComplete, }) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - params, err := client.ProvisionerJobParameterValues(context.Background(), user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + params, err := client.ProjectImportJobParameters(context.Background(), user.Organization, job.ID) require.NoError(t, err) require.Len(t, params, 1) require.NotNil(t, params[0]) @@ -179,7 +179,7 @@ func TestProvisionerJobResourcesByID(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ Parse: []*proto.Parse_Response{{ Type: &proto.Parse_Response_Complete{ Complete: &proto.Parse_Complete{ @@ -200,10 +200,128 @@ func TestProvisionerJobResourcesByID(t *testing.T) { }, }}, }) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) - resources, err := client.ProvisionerJobResources(context.Background(), user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + resources, err := client.ProjectImportJobResources(context.Background(), user.Organization, job.ID) require.NoError(t, err) // One for start, and one for stop! require.Len(t, resources, 2) }) } + +func TestProvisionerJobLogsByName(t *testing.T) { + t.Parallel() + t.Run("List", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{ + Level: proto.LogLevel_INFO, + Output: "log-output", + }, + }, + }, { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{}, + }, + }}, + }) + project := coderdtest.CreateProject(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ + ProjectVersionID: project.ActiveVersionID, + Transition: database.WorkspaceTransitionStart, + }) + require.NoError(t, err) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, history.ProvisionJobID) + // Return the log after completion! + logs, err := client.WorkspaceProvisionJobLogsBefore(context.Background(), user.Organization, history.ProvisionJobID, time.Time{}) + require.NoError(t, err) + require.NotNil(t, logs) + require.Len(t, logs, 1) + }) + + t.Run("StreamAfterComplete", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{ + Level: proto.LogLevel_INFO, + Output: "log-output", + }, + }, + }, { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{}, + }, + }}, + }) + project := coderdtest.CreateProject(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + before := time.Now().UTC() + history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ + ProjectVersionID: project.ActiveVersionID, + Transition: database.WorkspaceTransitionStart, + }) + require.NoError(t, err) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, history.ProvisionJobID) + + logs, err := client.WorkspaceProvisionJobLogsAfter(context.Background(), user.Organization, history.ProvisionJobID, before) + require.NoError(t, err) + log, ok := <-logs + require.True(t, ok) + require.Equal(t, "log-output", log.Output) + // Make sure the channel automatically closes! + _, ok = <-logs + require.False(t, ok) + }) + + t.Run("StreamWhileRunning", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: echo.ParseComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Log{ + Log: &proto.Log{ + Level: proto.LogLevel_INFO, + Output: "log-output", + }, + }, + }, { + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{}, + }, + }}, + }) + project := coderdtest.CreateProject(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) + before := database.Now() + history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ + ProjectVersionID: project.ActiveVersionID, + Transition: database.WorkspaceTransitionStart, + }) + require.NoError(t, err) + logs, err := client.WorkspaceProvisionJobLogsAfter(context.Background(), user.Organization, history.ProvisionJobID, before) + require.NoError(t, err) + log := <-logs + require.Equal(t, "log-output", log.Output) + // Make sure the channel automatically closes! + _, ok := <-logs + require.False(t, ok) + }) +} diff --git a/coderd/workspacehistory_test.go b/coderd/workspacehistory_test.go index 7b6ba0e8cab74..987955b7a336f 100644 --- a/coderd/workspacehistory_test.go +++ b/coderd/workspacehistory_test.go @@ -22,7 +22,7 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) _, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ @@ -40,11 +40,11 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ Provision: []*proto.Provision_Response{{}}, }) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) _, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectVersionID: project.ActiveVersionID, @@ -61,9 +61,9 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) closeDaemon := coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) // Close here so workspace history doesn't process! closeDaemon.Close() workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) @@ -87,16 +87,16 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) firstHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectVersionID: project.ActiveVersionID, Transition: database.WorkspaceTransitionStart, }) require.NoError(t, err) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, firstHistory.ProvisionJobID) + coderdtest.AwaitWorkspaceProvisionJob(t, client, user.Organization, firstHistory.ProvisionJobID) secondHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectVersionID: project.ActiveVersionID, Transition: database.WorkspaceTransitionStart, @@ -117,7 +117,7 @@ func TestWorkspaceHistoryByUser(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) history, err := client.ListWorkspaceHistory(context.Background(), "me", workspace.Name) @@ -131,9 +131,9 @@ func TestWorkspaceHistoryByUser(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) _, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectVersionID: project.ActiveVersionID, @@ -152,8 +152,8 @@ func TestWorkspaceHistoryByName(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID) history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 8d820b5964c31..cdc6bc353cde7 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -29,7 +29,7 @@ func TestWorkspaces(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _ = coderdtest.CreateWorkspace(t, client, "", project.ID) workspaces, err := client.Workspaces(context.Background(), "") @@ -58,7 +58,7 @@ func TestPostWorkspaceByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) anotherUser := coderd.CreateUserRequest{ @@ -90,7 +90,7 @@ func TestPostWorkspaceByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) _, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{ @@ -107,7 +107,7 @@ func TestPostWorkspaceByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _ = coderdtest.CreateWorkspace(t, client, "", project.ID) }) @@ -117,7 +117,7 @@ func TestWorkspaceByUser(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) _, err := client.Workspace(context.Background(), "", workspace.Name) @@ -130,7 +130,7 @@ func TestWorkspacesByProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspaces, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -141,7 +141,7 @@ func TestWorkspacesByProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _ = coderdtest.CreateWorkspace(t, client, "", project.ID) workspaces, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name) diff --git a/codersdk/projectimport.go b/codersdk/projectimport.go new file mode 100644 index 0000000000000..95f7cb6812872 --- /dev/null +++ b/codersdk/projectimport.go @@ -0,0 +1,95 @@ +package codersdk + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/google/uuid" + + "github.com/coder/coder/coderd" +) + +// CreateProjectImportJob creates a new import job in the organization provided. +// ProjectImportJob is not associated with a project by default. Projects +// are created from import. +func (c *Client) CreateProjectImportJob(ctx context.Context, organization string, req coderd.CreateProjectImportJobRequest) (coderd.ProvisionerJob, error) { + res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/projectimport/%s", organization), req) + if err != nil { + return coderd.ProvisionerJob{}, err + } + if res.StatusCode != http.StatusCreated { + defer res.Body.Close() + return coderd.ProvisionerJob{}, readBodyAsError(res) + } + var job coderd.ProvisionerJob + return job, json.NewDecoder(res.Body).Decode(&job) +} + +// ProjectImportJob returns an import job by ID. +func (c *Client) ProjectImportJob(ctx context.Context, organization string, job uuid.UUID) (coderd.ProvisionerJob, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectimport/%s/%s", organization, job), nil) + if err != nil { + return coderd.ProvisionerJob{}, nil + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return coderd.ProvisionerJob{}, readBodyAsError(res) + } + var resp coderd.ProvisionerJob + return resp, json.NewDecoder(res.Body).Decode(&resp) +} + +// ProjectImportJobLogsBefore returns logs that occurred before a specific time. +func (c *Client) ProjectImportJobLogsBefore(ctx context.Context, organization string, job uuid.UUID, before time.Time) ([]coderd.ProvisionerJobLog, error) { + return c.provisionerJobLogsBefore(ctx, "projectimport", organization, job, before) +} + +// ProjectImportJobLogsAfter streams logs for a project import operation that occurred after a specific time. +func (c *Client) ProjectImportJobLogsAfter(ctx context.Context, organization string, job uuid.UUID, after time.Time) (<-chan coderd.ProvisionerJobLog, error) { + return c.provisionerJobLogsAfter(ctx, "projectimport", organization, job, after) +} + +// ProjectImportJobSchemas returns schemas for an import job by ID. +func (c *Client) ProjectImportJobSchemas(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ParameterSchema, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectimport/%s/%s/schemas", organization, job), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, readBodyAsError(res) + } + var params []coderd.ParameterSchema + return params, json.NewDecoder(res.Body).Decode(¶ms) +} + +// ProjectImportJobParameters returns computed parameters for a project import job. +func (c *Client) ProjectImportJobParameters(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ComputedParameterValue, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectimport/%s/%s/parameters", organization, job), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, readBodyAsError(res) + } + var params []coderd.ComputedParameterValue + return params, json.NewDecoder(res.Body).Decode(¶ms) +} + +// ProjectImportJobResources returns resources for a project import job. +func (c *Client) ProjectImportJobResources(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ProjectImportJobResource, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projectimport/%s/%s/resources", organization, job), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, readBodyAsError(res) + } + var resources []coderd.ProjectImportJobResource + return resources, json.NewDecoder(res.Body).Decode(&resources) +} diff --git a/codersdk/projectimport_test.go b/codersdk/projectimport_test.go new file mode 100644 index 0000000000000..8cc6b28a23f6c --- /dev/null +++ b/codersdk/projectimport_test.go @@ -0,0 +1,146 @@ +package codersdk_test + +import ( + "context" + "testing" + "time" + + "github.com/coder/coder/coderd" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/provisionersdk/proto" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestCreateProjectImportJob(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _, err := client.CreateProjectImportJob(context.Background(), "", coderd.CreateProjectImportJobRequest{}) + require.Error(t, err) + }) + + t.Run("Create", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + _ = coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + }) +} + +func TestProjectImportJob(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _, err := client.ProjectImportJob(context.Background(), "", uuid.New()) + require.Error(t, err) + }) + + t.Run("Get", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) + _, err := client.ProjectImportJob(context.Background(), user.Organization, job.ID) + require.NoError(t, err) + }) +} + +func TestProjectImportJobLogsBefore(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _, err := client.ProjectImportJobLogsBefore(context.Background(), "", uuid.New(), time.Time{}) + require.Error(t, err) + }) + + t.Run("Get", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + before := time.Now() + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Log{ + Log: &proto.Log{ + Output: "hello", + }, + }, + }}, + Provision: echo.ProvisionComplete, + }) + logs, err := client.ProjectImportJobLogsAfter(context.Background(), user.Organization, job.ID, before) + require.NoError(t, err) + <-logs + }) +} + +func TestProjectImportJobLogsAfter(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _, err := client.ProjectImportJobLogsAfter(context.Background(), "", uuid.New(), time.Time{}) + require.Error(t, err) + }) + + t.Run("Get", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + user := coderdtest.CreateInitialUser(t, client) + coderdtest.NewProvisionerDaemon(t, client) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{ + Parse: []*proto.Parse_Response{{ + Type: &proto.Parse_Response_Log{ + Log: &proto.Log{ + Output: "hello", + }, + }, + }, { + Type: &proto.Parse_Response_Complete{ + Complete: &proto.Parse_Complete{}, + }, + }}, + Provision: echo.ProvisionComplete, + }) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) + logs, err := client.ProjectImportJobLogsBefore(context.Background(), user.Organization, job.ID, time.Time{}) + require.NoError(t, err) + require.Len(t, logs, 1) + }) +} + +func TestProjectImportJobSchemas(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _, err := client.ProjectImportJobSchemas(context.Background(), "", uuid.New()) + require.Error(t, err) + }) +} + +func TestProjectImportJobParameters(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _, err := client.ProjectImportJobParameters(context.Background(), "", uuid.New()) + require.Error(t, err) + }) +} + +func TestProjectImportJobResources(t *testing.T) { + t.Parallel() + t.Run("Error", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _, err := client.ProjectImportJobResources(context.Background(), "", uuid.New()) + require.Error(t, err) + }) +} diff --git a/codersdk/projects_test.go b/codersdk/projects_test.go index b280887ec84ae..4b5459fbede15 100644 --- a/codersdk/projects_test.go +++ b/codersdk/projects_test.go @@ -43,7 +43,7 @@ func TestProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.Project(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -66,7 +66,7 @@ func TestCreateProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) _ = coderdtest.CreateProject(t, client, user.Organization, job.ID) }) } @@ -84,7 +84,7 @@ func TestProjectVersions(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.ProjectVersions(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -104,7 +104,7 @@ func TestProjectVersion(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String()) require.NoError(t, err) @@ -124,7 +124,7 @@ func TestCreateProjectVersion(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{ ImportJobID: job.ID, @@ -146,7 +146,7 @@ func TestProjectParameters(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.ProjectParameters(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -166,7 +166,7 @@ func TestCreateProjectParameter(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{ Name: "example", diff --git a/codersdk/provisioners.go b/codersdk/provisioners.go index c77df205686b1..afef953beabb9 100644 --- a/codersdk/provisioners.go +++ b/codersdk/provisioners.go @@ -20,6 +20,7 @@ import ( "github.com/coder/coder/provisionersdk" ) +// ProvisionerDaemons returns registered provisionerd instances. func (c *Client) ProvisionerDaemons(ctx context.Context) ([]coderd.ProvisionerDaemon, error) { res, err := c.request(ctx, http.MethodGet, "/api/v2/provisioners/daemons", nil) if err != nil { @@ -59,51 +60,15 @@ func (c *Client) ProvisionerDaemonClient(ctx context.Context) (proto.DRPCProvisi return proto.NewDRPCProvisionerDaemonClient(provisionersdk.Conn(session)), nil } -// CreateProjectVersionImportProvisionerJob creates a job for importing -// the provided project version. -func (c *Client) CreateProjectVersionImportProvisionerJob(ctx context.Context, organization string, req coderd.CreateProjectImportJobRequest) (coderd.ProvisionerJob, error) { - res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/provisioners/jobs/%s/import", organization), req) - if err != nil { - return coderd.ProvisionerJob{}, err - } - if res.StatusCode != http.StatusCreated { - defer res.Body.Close() - return coderd.ProvisionerJob{}, readBodyAsError(res) - } - var job coderd.ProvisionerJob - return job, json.NewDecoder(res.Body).Decode(&job) -} - -// ProvisionerJob returns a job by ID. -func (c *Client) ProvisionerJob(ctx context.Context, organization string, job uuid.UUID) (coderd.ProvisionerJob, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s", organization, job), nil) - if err != nil { - return coderd.ProvisionerJob{}, nil - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return coderd.ProvisionerJob{}, readBodyAsError(res) - } - var resp coderd.ProvisionerJob - return resp, json.NewDecoder(res.Body).Decode(&resp) -} - -// ProvisionerJobLogs returns all logs for workspace history. -// To stream logs, use the FollowProvisionerJobLogs function. -func (c *Client) ProvisionerJobLogs(ctx context.Context, organization string, jobID uuid.UUID) ([]coderd.ProvisionerJobLog, error) { - return c.ProvisionerJobLogsBetween(ctx, organization, jobID, time.Time{}, time.Time{}) -} - -// ProvisionerJobLogsBetween returns logs between a specific time. -func (c *Client) ProvisionerJobLogsBetween(ctx context.Context, organization string, jobID uuid.UUID, after, before time.Time) ([]coderd.ProvisionerJobLog, error) { +// provisionerJobLogsBefore provides log output that occurred before a time. +// This is abstracted from a specific job type to provide consistency between +// APIs. Logs is the only shared route between jobs. +func (c *Client) provisionerJobLogsBefore(ctx context.Context, jobType, organization string, job uuid.UUID, before time.Time) ([]coderd.ProvisionerJobLog, error) { values := url.Values{} - if !after.IsZero() { - values["after"] = []string{strconv.FormatInt(after.UTC().UnixMilli(), 10)} - } if !before.IsZero() { values["before"] = []string{strconv.FormatInt(before.UTC().UnixMilli(), 10)} } - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/logs?%s", organization, jobID, values.Encode()), nil) + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/%s/%s/%s/logs?%s", jobType, organization, job, values.Encode()), nil) if err != nil { return nil, err } @@ -116,14 +81,13 @@ func (c *Client) ProvisionerJobLogsBetween(ctx context.Context, organization str return logs, json.NewDecoder(res.Body).Decode(&logs) } -// FollowProvisionerJobLogsAfter returns a stream of workspace history logs. -// The channel will close when the workspace history job is no longer active. -func (c *Client) FollowProvisionerJobLogsAfter(ctx context.Context, organization string, jobID uuid.UUID, after time.Time) (<-chan coderd.ProvisionerJobLog, error) { +// provisionerJobLogsAfter streams logs that occurred after a specific time. +func (c *Client) provisionerJobLogsAfter(ctx context.Context, jobType, organization string, job uuid.UUID, after time.Time) (<-chan coderd.ProvisionerJobLog, error) { afterQuery := "" if !after.IsZero() { afterQuery = fmt.Sprintf("&after=%d", after.UTC().UnixMilli()) } - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/logs?follow%s", organization, jobID, afterQuery), nil) + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/%s/%s/%s/logs?follow%s", jobType, organization, job, afterQuery), nil) if err != nil { return nil, err } @@ -151,44 +115,3 @@ func (c *Client) FollowProvisionerJobLogsAfter(ctx context.Context, organization }() return logs, nil } - -// ProvisionerJobParameters returns computed project parameters for a job by ID. -func (c *Client) ProvisionerJobParameterSchemas(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ParameterSchema, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/schemas", organization, job), nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, readBodyAsError(res) - } - var params []coderd.ParameterSchema - return params, json.NewDecoder(res.Body).Decode(¶ms) -} - -// ProvisionerJobParameterValues returns computed parameters for a provisioner job. -func (c *Client) ProvisionerJobParameterValues(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ComputedParameterValue, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/computed", organization, job), nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, readBodyAsError(res) - } - var params []coderd.ComputedParameterValue - return params, json.NewDecoder(res.Body).Decode(¶ms) -} - -func (c *Client) ProvisionerJobResources(ctx context.Context, organization string, job uuid.UUID) ([]coderd.ProjectImportJobResource, error) { - res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/provisioners/jobs/%s/%s/resources", organization, job), nil) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, readBodyAsError(res) - } - var resources []coderd.ProjectImportJobResource - return resources, json.NewDecoder(res.Body).Decode(&resources) -} diff --git a/codersdk/provisioners_test.go b/codersdk/provisioners_test.go index 6808dd19a3b78..9bb4528ebec1e 100644 --- a/codersdk/provisioners_test.go +++ b/codersdk/provisioners_test.go @@ -3,16 +3,11 @@ package codersdk_test import ( "context" "testing" - "time" - "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/coder/coder/coderd/coderdtest" - "github.com/coder/coder/database" - "github.com/coder/coder/provisioner/echo" "github.com/coder/coder/provisionerd/proto" - sdkproto "github.com/coder/coder/provisionersdk/proto" ) func TestProvisionerDaemons(t *testing.T) { @@ -49,60 +44,3 @@ func TestProvisionerDaemonClient(t *testing.T) { require.NoError(t, err) }) } -func TestProvisionerJobLogs(t *testing.T) { - t.Parallel() - t.Run("Error", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - _, err := client.ProvisionerJobLogs(context.Background(), "nothing", uuid.New()) - require.Error(t, err) - }) - - t.Run("List", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) - _, err := client.ProvisionerJobLogs(context.Background(), user.Organization, job.ID) - require.NoError(t, err) - }) -} - -func TestFollowProvisionerJobLogsAfter(t *testing.T) { - t.Parallel() - t.Run("Error", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - _, err := client.FollowProvisionerJobLogsAfter(context.Background(), "nothing", uuid.New(), time.Time{}) - require.Error(t, err) - }) - - t.Run("Stream", func(t *testing.T) { - t.Parallel() - client := coderdtest.New(t) - user := coderdtest.CreateInitialUser(t, client) - _ = coderdtest.NewProvisionerDaemon(t, client) - before := database.Now() - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{ - Parse: []*sdkproto.Parse_Response{{ - Type: &sdkproto.Parse_Response_Log{ - Log: &sdkproto.Log{ - Output: "hello", - }, - }, - }, { - Type: &sdkproto.Parse_Response_Complete{ - Complete: &sdkproto.Parse_Complete{}, - }, - }}, - Provision: echo.ProvisionComplete, - }) - logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), user.Organization, job.ID, before) - require.NoError(t, err) - _, ok := <-logs - require.True(t, ok) - _, ok = <-logs - require.False(t, ok) - }) -} diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index b84efeff628d1..28f926c518049 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -5,6 +5,9 @@ import ( "encoding/json" "fmt" "net/http" + "time" + + "github.com/google/uuid" "github.com/coder/coder/coderd" ) @@ -131,3 +134,26 @@ func (c *Client) CreateWorkspaceHistory(ctx context.Context, owner, workspace st var workspaceHistory coderd.WorkspaceHistory return workspaceHistory, json.NewDecoder(res.Body).Decode(&workspaceHistory) } + +func (c *Client) WorkspaceProvisionJob(ctx context.Context, organization string, job uuid.UUID) (coderd.ProvisionerJob, error) { + res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceprovision/%s/%s", organization, job), nil) + if err != nil { + return coderd.ProvisionerJob{}, nil + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return coderd.ProvisionerJob{}, readBodyAsError(res) + } + var resp coderd.ProvisionerJob + return resp, json.NewDecoder(res.Body).Decode(&resp) +} + +// WorkspaceProvisionJobLogsBefore returns logs that occurred before a specific time. +func (c *Client) WorkspaceProvisionJobLogsBefore(ctx context.Context, organization string, job uuid.UUID, before time.Time) ([]coderd.ProvisionerJobLog, error) { + return c.provisionerJobLogsBefore(ctx, "workspaceprovision", organization, job, before) +} + +// WorkspaceProvisionJobLogsAfter streams logs for a workspace provision operation that occurred after a specific time. +func (c *Client) WorkspaceProvisionJobLogsAfter(ctx context.Context, organization string, job uuid.UUID, after time.Time) (<-chan coderd.ProvisionerJobLog, error) { + return c.provisionerJobLogsAfter(ctx, "workspaceprovision", organization, job, after) +} diff --git a/codersdk/workspaces_test.go b/codersdk/workspaces_test.go index 39ad8eddf4663..503895243dbcb 100644 --- a/codersdk/workspaces_test.go +++ b/codersdk/workspaces_test.go @@ -42,7 +42,7 @@ func TestWorkspacesByProject(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name) require.NoError(t, err) @@ -62,7 +62,7 @@ func TestWorkspace(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) _, err := client.Workspace(context.Background(), "", workspace.Name) @@ -83,7 +83,7 @@ func TestListWorkspaceHistory(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) _, err := client.ListWorkspaceHistory(context.Background(), "", workspace.Name) @@ -105,9 +105,9 @@ func TestWorkspaceHistory(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) _, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectVersionID: project.ActiveVersionID, @@ -130,7 +130,7 @@ func TestCreateWorkspace(t *testing.T) { t.Parallel() client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) _ = coderdtest.CreateWorkspace(t, client, "", project.ID) }) @@ -150,9 +150,9 @@ func TestCreateWorkspaceHistory(t *testing.T) { client := coderdtest.New(t) user := coderdtest.CreateInitialUser(t, client) _ = coderdtest.NewProvisionerDaemon(t, client) - job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil) + job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil) project := coderdtest.CreateProject(t, client, user.Organization, job.ID) - coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID) + coderdtest.AwaitProjectImportJob(t, client, user.Organization, job.ID) workspace := coderdtest.CreateWorkspace(t, client, "", project.ID) _, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{ ProjectVersionID: project.ActiveVersionID, From e4770bb8f2ace23849dd8904967e7cfc646ec157 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Sat, 12 Feb 2022 00:00:25 +0000 Subject: [PATCH 18/20] Remove go-expect --- cli/clitest/clitest.go | 28 +++++++--------------------- cli/clitest/clitest_test.go | 2 ++ cli/login_test.go | 2 ++ cli/projectcreate.go | 2 +- cli/projectcreate_test.go | 2 ++ cli/workspacecreate.go | 6 ++---- go.mod | 7 +------ go.sum | 24 ------------------------ 8 files changed, 17 insertions(+), 56 deletions(-) diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index ad21230c3b8fb..e4a7de024a5d6 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -14,7 +14,6 @@ import ( "github.com/Netflix/go-expect" "github.com/spf13/cobra" "github.com/stretchr/testify/require" - "golang.org/x/xerrors" "github.com/coder/coder/cli" "github.com/coder/coder/cli/config" @@ -51,8 +50,7 @@ func CreateProjectVersionSource(t *testing.T, responses *echo.Responses) string directory := t.TempDir() data, err := echo.Tar(responses) require.NoError(t, err) - err = extractTar(data, directory) - require.NoError(t, err) + extractTar(t, data, directory) return directory } @@ -81,16 +79,14 @@ func NewConsole(t *testing.T, cmd *cobra.Command) *expect.Console { return console } -func extractTar(data []byte, directory string) error { +func extractTar(t *testing.T, data []byte, directory string) { reader := tar.NewReader(bytes.NewBuffer(data)) for { header, err := reader.Next() if errors.Is(err, io.EOF) { break } - if err != nil { - return xerrors.Errorf("read project source archive: %w", err) - } + require.NoError(t, err) // #nosec path := filepath.Join(directory, header.Name) mode := header.FileInfo().Mode() @@ -100,28 +96,18 @@ func extractTar(data []byte, directory string) error { switch header.Typeflag { case tar.TypeDir: err = os.MkdirAll(path, mode) - if err != nil { - return xerrors.Errorf("mkdir: %w", err) - } + require.NoError(t, err) case tar.TypeReg: file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, mode) - if err != nil { - return xerrors.Errorf("create file %q: %w", path, err) - } + require.NoError(t, err) // Max file size of 10MB. _, err = io.CopyN(file, reader, (1<<20)*10) if errors.Is(err, io.EOF) { err = nil } - if err != nil { - _ = file.Close() - return err - } + require.NoError(t, err) err = file.Close() - if err != nil { - return err - } + require.NoError(t, err) } } - return nil } diff --git a/cli/clitest/clitest_test.go b/cli/clitest/clitest_test.go index 71f9aeefa1bce..806e04ecc2a4e 100644 --- a/cli/clitest/clitest_test.go +++ b/cli/clitest/clitest_test.go @@ -1,3 +1,5 @@ +//go:build !windows + package clitest_test import ( diff --git a/cli/login_test.go b/cli/login_test.go index faefdf4ccccb3..06f942ee95b9c 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -1,3 +1,5 @@ +//go:build !windows + package cli_test import ( diff --git a/cli/projectcreate.go b/cli/projectcreate.go index 21e667b462996..4c67201d5a894 100644 --- a/cli/projectcreate.go +++ b/cli/projectcreate.go @@ -111,7 +111,7 @@ func projectCreate() *cobra.Command { currentDirectory, _ := os.Getwd() cmd.Flags().StringVarP(&directory, "directory", "d", currentDirectory, "Specify the directory to create from") cmd.Flags().StringVarP(&provisioner, "provisioner", "p", "terraform", "Customize the provisioner backend") - // This is for testing! There's only 1 provisioner type right now. + // This is for testing! err := cmd.Flags().MarkHidden("provisioner") if err != nil { panic(err) diff --git a/cli/projectcreate_test.go b/cli/projectcreate_test.go index 547447d3b87bd..ed802475ffe94 100644 --- a/cli/projectcreate_test.go +++ b/cli/projectcreate_test.go @@ -1,3 +1,5 @@ +//go:build !windows + package cli_test import ( diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index 3b4392096f0ae..d95fab74e2008 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -112,17 +112,15 @@ func workspaceCreate() *cobra.Command { if err != nil { return err } - // logBuffer := make([]coderd.ProvisionerJobLog, 0, 64) for { log, ok := <-logs if !ok { break } - _, _ = fmt.Printf("Logging: %s\n", log.Output) - // logBuffer = append(logBuffer, log) + _, _ = fmt.Printf("Terraform: %s\n", log.Output) } - _, _ = fmt.Printf("Create workspace! %s\n", name) + _, _ = fmt.Printf("Created workspace! %s\n", name) return nil }, diff --git a/go.mod b/go.mod index e7168de21e631..7a692c6352a8e 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ replace github.com/chzyer/readline => github.com/kylecarbs/readline v0.0.0-20220 require ( cdr.dev/slog v1.4.1 - github.com/ActiveState/termtest/expect v0.7.0 + github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 github.com/briandowns/spinner v1.18.1 github.com/coder/retry v1.3.0 github.com/fatih/color v1.13.0 @@ -58,12 +58,8 @@ require ( require ( cloud.google.com/go/compute v0.1.0 // indirect - github.com/ActiveState/termtest/conpty v0.5.0 // indirect - github.com/ActiveState/termtest/xpty v0.6.0 // indirect - github.com/ActiveState/vt10x v1.3.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.5.1 // indirect - github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/alecthomas/chroma v0.10.0 // indirect @@ -95,7 +91,6 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect github.com/klauspost/compress v1.13.6 // indirect - github.com/kr/pty v1.1.8 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect github.com/mattn/go-colorable v0.1.12 // indirect diff --git a/go.sum b/go.sum index c8bd72397ed5a..231783ed456a6 100644 --- a/go.sum +++ b/go.sum @@ -57,14 +57,6 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -github.com/ActiveState/termtest/conpty v0.5.0 h1:JLUe6YDs4Jw4xNPCU+8VwTpniYOGeKzQg4SM2YHQNA8= -github.com/ActiveState/termtest/conpty v0.5.0/go.mod h1:LO4208FLsxw6DcNZ1UtuGUMW+ga9PFtX4ntv8Ymg9og= -github.com/ActiveState/termtest/expect v0.7.0 h1:VNrcfXTHXXoe7i+3WgF5QJhstQLGP4saj+XYM9ZzBvY= -github.com/ActiveState/termtest/expect v0.7.0/go.mod h1:64QuJvMtMu7+H5U+5TSMBxAs1FAaLvRIyN7WPOICido= -github.com/ActiveState/termtest/xpty v0.6.0 h1:L9c17TDfy+ed+tY5cMOErn0n2EYG4tj8StdxHmoPok8= -github.com/ActiveState/termtest/xpty v0.6.0/go.mod h1:MmTm/62Ajq+D92emHq8LOu9Q+2+pkBurDLahkUP6Odg= -github.com/ActiveState/vt10x v1.3.1 h1:7qi8BGXUEBghzBxfXSY0J77etO+L95PZQlwD7ay2mn0= -github.com/ActiveState/vt10x v1.3.1/go.mod h1:8wJKd36c9NmCfGyPyOJmkvyIMvbUPfHkfdS8zZlK19s= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= @@ -111,8 +103,6 @@ github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01 github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= -github.com/Netflix/go-expect v0.0.0-20200312175327-da48e75238e2/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 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= @@ -160,8 +150,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/autarch/testify v1.2.2 h1:9Q9V6zqhP7R6dv+zRUddv6kXKLo6ecQhnFRFWM71i1c= -github.com/autarch/testify v1.2.2/go.mod h1:oDbHKfFv2/D5UtVrxkk90OKcb6P4/AqF1Pcf6ZbvDQo= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= @@ -448,8 +436,6 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ= -github.com/gdamore/tcell v1.0.1-0.20180608172421-b3cebc399d6f/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -727,7 +713,6 @@ github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniy github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -849,7 +834,6 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -878,7 +862,6 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lucasb-eyer/go-colorful v0.0.0-20180526135729-345fbb3dbcdb/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= @@ -1170,7 +1153,6 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/snowflakedb/gosnowflake v1.6.3/go.mod h1:6hLajn6yxuJ4xUHZegMekpq9rnQbGJ7TMwXjgTmA6lg= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1202,7 +1184,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1337,7 +1318,6 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1560,7 +1540,6 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1570,7 +1549,6 @@ golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1578,7 +1556,6 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1900,7 +1877,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From c6cee947fd78f47ec82c74972ff48a4217cff2a4 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Sat, 12 Feb 2022 19:06:42 +0000 Subject: [PATCH 19/20] Fix requested changes --- cli/clitest/clitest.go | 1 + cli/projects.go | 1 - cli/workspacecreate.go | 3 ++- coderd/files.go | 14 ++++++++++++-- coderd/files_test.go | 11 +++++++++++ coderd/parameter/compute.go | 2 +- coderd/provisionerjobs.go | 1 - codersdk/files.go | 2 +- 8 files changed, 28 insertions(+), 7 deletions(-) diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index e4a7de024a5d6..e9fbbd4f23d1d 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -23,6 +23,7 @@ import ( var ( // Used to ensure terminal output doesn't have anything crazy! + // See: https://stackoverflow.com/a/29497680 stripAnsi = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))") ) diff --git a/cli/projects.go b/cli/projects.go index b11317367aa8b..bce9930bd21ca 100644 --- a/cli/projects.go +++ b/cli/projects.go @@ -17,7 +17,6 @@ func projects() *cobra.Command { cmd := &cobra.Command{ Use: "projects", Aliases: []string{"project"}, - Long: "Testing something", Example: ` - Create a project for developers to create workspaces diff --git a/cli/workspacecreate.go b/cli/workspacecreate.go index d95fab74e2008..be883ebaffe00 100644 --- a/cli/workspacecreate.go +++ b/cli/workspacecreate.go @@ -120,8 +120,9 @@ func workspaceCreate() *cobra.Command { _, _ = fmt.Printf("Terraform: %s\n", log.Output) } - _, _ = fmt.Printf("Created workspace! %s\n", name) + // This command is WIP, and output will change! + _, _ = fmt.Printf("Created workspace! %s\n", name) return nil }, } diff --git a/coderd/files.go b/coderd/files.go index 01337b9d2e143..069509c567ca6 100644 --- a/coderd/files.go +++ b/coderd/files.go @@ -40,8 +40,18 @@ func (api *api) postFiles(rw http.ResponseWriter, r *http.Request) { return } hashBytes := sha256.Sum256(data) - file, err := api.Database.InsertFile(r.Context(), database.InsertFileParams{ - Hash: hex.EncodeToString(hashBytes[:]), + hash := hex.EncodeToString(hashBytes[:]) + file, err := api.Database.GetFileByHash(r.Context(), hash) + if err == nil { + // The file already exists! + render.Status(r, http.StatusOK) + render.JSON(rw, r, UploadFileResponse{ + Hash: file.Hash, + }) + return + } + file, err = api.Database.InsertFile(r.Context(), database.InsertFileParams{ + Hash: hash, CreatedBy: apiKey.UserID, CreatedAt: database.Now(), Mimetype: contentType, diff --git a/coderd/files_test.go b/coderd/files_test.go index 2ffa455df7e81..d14c1aff99fed 100644 --- a/coderd/files_test.go +++ b/coderd/files_test.go @@ -27,4 +27,15 @@ func TestPostFiles(t *testing.T) { _, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, make([]byte, 1024)) require.NoError(t, err) }) + + t.Run("InsertAlreadyExists", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t) + _ = coderdtest.CreateInitialUser(t, client) + data := make([]byte, 1024) + _, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data) + require.NoError(t, err) + _, err = client.UploadFile(context.Background(), codersdk.ContentTypeTar, data) + require.NoError(t, err) + }) } diff --git a/coderd/parameter/compute.go b/coderd/parameter/compute.go index 801c93732ee24..d7c68a5ac30e1 100644 --- a/coderd/parameter/compute.go +++ b/coderd/parameter/compute.go @@ -88,7 +88,7 @@ func Compute(ctx context.Context, db database.Store, scope ComputeScope, options continue } if _, ok := compute.computedParameterByName[parameterSchema.Name]; ok { - // We already have a value! No need to use th default. + // We already have a value! No need to use the default. continue } diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index a817b39f24873..a99ca905c83c5 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -170,7 +170,6 @@ func (api *api) provisionerJobLogsByID(rw http.ResponseWriter, r *http.Request) }) if errors.Is(err, sql.ErrNoRows) { err = nil - provisionerJobLogs = []database.ProvisionerJobLog{} } if err != nil { httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ diff --git a/codersdk/files.go b/codersdk/files.go index 25c7fcb70cc2b..f4fe82f8cd146 100644 --- a/codersdk/files.go +++ b/codersdk/files.go @@ -20,7 +20,7 @@ func (c *Client) UploadFile(ctx context.Context, contentType string, content []b return coderd.UploadFileResponse{}, err } defer res.Body.Close() - if res.StatusCode != http.StatusCreated { + if res.StatusCode != http.StatusCreated && res.StatusCode != http.StatusOK { return coderd.UploadFileResponse{}, readBodyAsError(res) } var resp coderd.UploadFileResponse From f9814beb3777f40fb2d7f5288cc37c835b881c39 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Sat, 12 Feb 2022 19:22:02 +0000 Subject: [PATCH 20/20] Skip workspacecreate test on windows --- cli/workspacecreate_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/workspacecreate_test.go b/cli/workspacecreate_test.go index 1b0f0dcf2d2ff..138e0ee1e61d6 100644 --- a/cli/workspacecreate_test.go +++ b/cli/workspacecreate_test.go @@ -1,3 +1,5 @@ +//go:build !windows + package cli_test import (