|
7 | 7 | "errors"
|
8 | 8 | "fmt"
|
9 | 9 | "net/http"
|
| 10 | + "reflect" |
10 | 11 |
|
11 | 12 | "golang.org/x/xerrors"
|
12 | 13 | "storj.io/drpc/drpcmux"
|
@@ -267,6 +268,194 @@ func (s *provisionerdServer) CancelJob(ctx context.Context, cancelJob *proto.Can
|
267 | 268 | return &proto.Empty{}, nil
|
268 | 269 | }
|
269 | 270 |
|
| 271 | +// CompleteJob is triggered by a provision daemon to mark a provisioner job as completed. |
270 | 272 | func (s *provisionerdServer) CompleteJob(ctx context.Context, completed *proto.CompletedJob) (*proto.Empty, error) {
|
271 |
| - return nil, nil |
| 273 | + jobID, err := uuid.Parse(completed.JobId) |
| 274 | + if err != nil { |
| 275 | + return nil, xerrors.Errorf("parse job id: %w", err) |
| 276 | + } |
| 277 | + job, err := s.Database.GetProvisionerJobByID(ctx, jobID) |
| 278 | + if err != nil { |
| 279 | + return nil, xerrors.Errorf("get job by id: %w", err) |
| 280 | + } |
| 281 | + // TODO: Check if the worker ID matches! |
| 282 | + // If it doesn't, a provisioner daemon could be impersonating another job! |
| 283 | + |
| 284 | + switch jobType := completed.Type.(type) { |
| 285 | + case *proto.CompletedJob_ProjectImport_: |
| 286 | + var input projectImportJob |
| 287 | + err = json.Unmarshal(job.Input, &input) |
| 288 | + if err != nil { |
| 289 | + return nil, xerrors.Errorf("unmarshal job data: %w", err) |
| 290 | + } |
| 291 | + |
| 292 | + // Validate that all parameters send from the provisioner daemon |
| 293 | + // follow the protocol. |
| 294 | + projectParameters := make([]database.InsertProjectParameterParams, 0, len(jobType.ProjectImport.ParameterSchemas)) |
| 295 | + for _, protoParameter := range jobType.ProjectImport.ParameterSchemas { |
| 296 | + validationTypeSystem, err := convertValidationTypeSystem(protoParameter.ValidationTypeSystem) |
| 297 | + if err != nil { |
| 298 | + return nil, xerrors.Errorf("convert validation type system for %q: %w", protoParameter.Name, err) |
| 299 | + } |
| 300 | + |
| 301 | + projectParameter := database.InsertProjectParameterParams{ |
| 302 | + ID: uuid.New(), |
| 303 | + CreatedAt: database.Now(), |
| 304 | + ProjectHistoryID: input.ProjectHistoryID, |
| 305 | + Name: protoParameter.Name, |
| 306 | + Description: protoParameter.Description, |
| 307 | + RedisplayValue: protoParameter.RedisplayValue, |
| 308 | + ValidationError: protoParameter.ValidationError, |
| 309 | + ValidationCondition: protoParameter.ValidationCondition, |
| 310 | + ValidationValueType: protoParameter.ValidationValueType, |
| 311 | + ValidationTypeSystem: validationTypeSystem, |
| 312 | + |
| 313 | + AllowOverrideDestination: protoParameter.AllowOverrideDestination, |
| 314 | + AllowOverrideSource: protoParameter.AllowOverrideSource, |
| 315 | + } |
| 316 | + |
| 317 | + // It's possible a parameter doesn't define a default source! |
| 318 | + if protoParameter.DefaultSource != nil { |
| 319 | + parameterSourceScheme, err := convertParameterSourceScheme(protoParameter.DefaultSource.Scheme) |
| 320 | + if err != nil { |
| 321 | + return nil, xerrors.Errorf("convert parameter source scheme: %w", err) |
| 322 | + } |
| 323 | + projectParameter.DefaultSourceScheme = parameterSourceScheme |
| 324 | + projectParameter.DefaultSourceValue = sql.NullString{ |
| 325 | + String: protoParameter.DefaultSource.Value, |
| 326 | + Valid: protoParameter.DefaultSource.Value != "", |
| 327 | + } |
| 328 | + } |
| 329 | + |
| 330 | + // It's possible a parameter doesn't define a default destination! |
| 331 | + if protoParameter.DefaultDestination != nil { |
| 332 | + parameterDestinationScheme, err := convertParameterDestinationScheme(protoParameter.DefaultDestination.Scheme) |
| 333 | + if err != nil { |
| 334 | + return nil, xerrors.Errorf("convert parameter destination scheme: %w", err) |
| 335 | + } |
| 336 | + projectParameter.DefaultDestinationScheme = parameterDestinationScheme |
| 337 | + projectParameter.DefaultDestinationValue = sql.NullString{ |
| 338 | + String: protoParameter.DefaultDestination.Value, |
| 339 | + Valid: protoParameter.DefaultDestination.Value != "", |
| 340 | + } |
| 341 | + } |
| 342 | + |
| 343 | + projectParameters = append(projectParameters, projectParameter) |
| 344 | + } |
| 345 | + |
| 346 | + // This must occur in a transaction in case of failure. |
| 347 | + err = s.Database.InTx(func(db database.Store) error { |
| 348 | + err = db.UpdateProvisionerJobByID(ctx, database.UpdateProvisionerJobByIDParams{ |
| 349 | + ID: jobID, |
| 350 | + UpdatedAt: database.Now(), |
| 351 | + CompletedAt: sql.NullTime{ |
| 352 | + Time: database.Now(), |
| 353 | + Valid: true, |
| 354 | + }, |
| 355 | + }) |
| 356 | + if err != nil { |
| 357 | + return xerrors.Errorf("update provisioner job: %w", err) |
| 358 | + } |
| 359 | + for _, projectParameter := range projectParameters { |
| 360 | + _, err = db.InsertProjectParameter(ctx, projectParameter) |
| 361 | + if err != nil { |
| 362 | + return xerrors.Errorf("insert project parameter %q: %w", projectParameter.Name, err) |
| 363 | + } |
| 364 | + } |
| 365 | + return nil |
| 366 | + }) |
| 367 | + if err != nil { |
| 368 | + return nil, xerrors.Errorf("complete job: %w", err) |
| 369 | + } |
| 370 | + case *proto.CompletedJob_WorkspaceProvision_: |
| 371 | + var input workspaceProvisionJob |
| 372 | + err = json.Unmarshal(job.Input, &input) |
| 373 | + if err != nil { |
| 374 | + return nil, xerrors.Errorf("unmarshal job data: %w", err) |
| 375 | + } |
| 376 | + |
| 377 | + workspaceHistory, err := s.Database.GetWorkspaceHistoryByID(ctx, input.WorkspaceHistoryID) |
| 378 | + if err != nil { |
| 379 | + return nil, xerrors.Errorf("get workspace history: %w", err) |
| 380 | + } |
| 381 | + |
| 382 | + err = s.Database.InTx(func(db database.Store) error { |
| 383 | + err = db.UpdateProvisionerJobByID(ctx, database.UpdateProvisionerJobByIDParams{ |
| 384 | + ID: jobID, |
| 385 | + UpdatedAt: database.Now(), |
| 386 | + CompletedAt: sql.NullTime{ |
| 387 | + Time: database.Now(), |
| 388 | + Valid: true, |
| 389 | + }, |
| 390 | + }) |
| 391 | + if err != nil { |
| 392 | + return xerrors.Errorf("update provisioner job: %w", err) |
| 393 | + } |
| 394 | + err = db.UpdateWorkspaceHistoryByID(ctx, database.UpdateWorkspaceHistoryByIDParams{ |
| 395 | + ID: workspaceHistory.ID, |
| 396 | + UpdatedAt: database.Now(), |
| 397 | + ProvisionerState: jobType.WorkspaceProvision.State, |
| 398 | + CompletedAt: sql.NullTime{ |
| 399 | + Time: database.Now(), |
| 400 | + Valid: true, |
| 401 | + }, |
| 402 | + }) |
| 403 | + if err != nil { |
| 404 | + return xerrors.Errorf("update workspace history: %w", err) |
| 405 | + } |
| 406 | + for _, protoResource := range jobType.WorkspaceProvision.Resources { |
| 407 | + _, err = db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{ |
| 408 | + ID: uuid.New(), |
| 409 | + CreatedAt: database.Now(), |
| 410 | + WorkspaceHistoryID: input.WorkspaceHistoryID, |
| 411 | + Type: protoResource.Type, |
| 412 | + Name: protoResource.Name, |
| 413 | + // TODO: Generate this at the variable validation phase. |
| 414 | + // Set the value in `default_source`, and disallow overwrite. |
| 415 | + WorkspaceAgentToken: uuid.NewString(), |
| 416 | + }) |
| 417 | + if err != nil { |
| 418 | + return xerrors.Errorf("insert workspace resource %q: %w", protoResource.Name, err) |
| 419 | + } |
| 420 | + } |
| 421 | + return nil |
| 422 | + }) |
| 423 | + if err != nil { |
| 424 | + return nil, xerrors.Errorf("complete job: %w", err) |
| 425 | + } |
| 426 | + default: |
| 427 | + return nil, xerrors.Errorf("unknown job type %q; ensure coderd and provisionerd versions match", |
| 428 | + reflect.TypeOf(completed.Type).String()) |
| 429 | + } |
| 430 | + |
| 431 | + return &proto.Empty{}, nil |
| 432 | +} |
| 433 | + |
| 434 | +func convertValidationTypeSystem(typeSystem sdkproto.ParameterSchema_TypeSystem) (database.ParameterTypeSystem, error) { |
| 435 | + switch typeSystem { |
| 436 | + case sdkproto.ParameterSchema_HCL: |
| 437 | + return database.ParameterTypeSystemHCL, nil |
| 438 | + default: |
| 439 | + return database.ParameterTypeSystem(""), xerrors.Errorf("unknown type system: %d", typeSystem) |
| 440 | + } |
| 441 | +} |
| 442 | + |
| 443 | +func convertParameterSourceScheme(sourceScheme sdkproto.ParameterSource_Scheme) (database.ParameterSourceScheme, error) { |
| 444 | + switch sourceScheme { |
| 445 | + case sdkproto.ParameterSource_DATA: |
| 446 | + return database.ParameterSourceSchemeData, nil |
| 447 | + default: |
| 448 | + return database.ParameterSourceScheme(""), xerrors.Errorf("unknown parameter source scheme: %d", sourceScheme) |
| 449 | + } |
| 450 | +} |
| 451 | + |
| 452 | +func convertParameterDestinationScheme(destinationScheme sdkproto.ParameterDestination_Scheme) (database.ParameterDestinationScheme, error) { |
| 453 | + switch destinationScheme { |
| 454 | + case sdkproto.ParameterDestination_ENVIRONMENT_VARIABLE: |
| 455 | + return database.ParameterDestinationSchemeEnvironmentVariable, nil |
| 456 | + case sdkproto.ParameterDestination_PROVISIONER_VARIABLE: |
| 457 | + return database.ParameterDestinationSchemeProvisionerVariable, nil |
| 458 | + default: |
| 459 | + return database.ParameterDestinationScheme(""), xerrors.Errorf("unknown parameter destination scheme: %d", destinationScheme) |
| 460 | + } |
272 | 461 | }
|
0 commit comments