github.com/databricks/cli@v0.203.0/bundle/config/mutator/translate_paths_test.go (about) 1 package mutator_test 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/databricks/cli/bundle" 10 "github.com/databricks/cli/bundle/config" 11 "github.com/databricks/cli/bundle/config/mutator" 12 "github.com/databricks/cli/bundle/config/resources" 13 "github.com/databricks/databricks-sdk-go/service/jobs" 14 "github.com/databricks/databricks-sdk-go/service/pipelines" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func touchNotebookFile(t *testing.T, path string) { 20 f, err := os.Create(path) 21 require.NoError(t, err) 22 f.WriteString("# Databricks notebook source\n") 23 f.Close() 24 } 25 26 func touchEmptyFile(t *testing.T, path string) { 27 err := os.MkdirAll(filepath.Dir(path), 0700) 28 require.NoError(t, err) 29 f, err := os.Create(path) 30 require.NoError(t, err) 31 f.Close() 32 } 33 34 func TestTranslatePathsSkippedWithGitSource(t *testing.T) { 35 dir := t.TempDir() 36 bundle := &bundle.Bundle{ 37 Config: config.Root{ 38 Path: dir, 39 Workspace: config.Workspace{ 40 FilesPath: "/bundle", 41 }, 42 Resources: config.Resources{ 43 Jobs: map[string]*resources.Job{ 44 "job": { 45 46 Paths: resources.Paths{ 47 ConfigFilePath: filepath.Join(dir, "resource.yml"), 48 }, 49 JobSettings: &jobs.JobSettings{ 50 GitSource: &jobs.GitSource{ 51 GitBranch: "somebranch", 52 GitCommit: "somecommit", 53 GitProvider: "github", 54 GitTag: "sometag", 55 GitUrl: "https://github.com/someuser/somerepo", 56 }, 57 Tasks: []jobs.Task{ 58 { 59 NotebookTask: &jobs.NotebookTask{ 60 NotebookPath: "my_job_notebook.py", 61 }, 62 }, 63 { 64 PythonWheelTask: &jobs.PythonWheelTask{ 65 PackageName: "foo", 66 }, 67 }, 68 { 69 SparkPythonTask: &jobs.SparkPythonTask{ 70 PythonFile: "my_python_file.py", 71 }, 72 }, 73 }, 74 }, 75 }, 76 }, 77 }, 78 }, 79 } 80 81 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 82 require.NoError(t, err) 83 84 assert.Equal( 85 t, 86 "my_job_notebook.py", 87 bundle.Config.Resources.Jobs["job"].Tasks[0].NotebookTask.NotebookPath, 88 ) 89 assert.Equal( 90 t, 91 "foo", 92 bundle.Config.Resources.Jobs["job"].Tasks[1].PythonWheelTask.PackageName, 93 ) 94 assert.Equal( 95 t, 96 "my_python_file.py", 97 bundle.Config.Resources.Jobs["job"].Tasks[2].SparkPythonTask.PythonFile, 98 ) 99 } 100 101 func TestTranslatePaths(t *testing.T) { 102 dir := t.TempDir() 103 touchNotebookFile(t, filepath.Join(dir, "my_job_notebook.py")) 104 touchNotebookFile(t, filepath.Join(dir, "my_pipeline_notebook.py")) 105 touchEmptyFile(t, filepath.Join(dir, "my_python_file.py")) 106 107 bundle := &bundle.Bundle{ 108 Config: config.Root{ 109 Path: dir, 110 Workspace: config.Workspace{ 111 FilesPath: "/bundle", 112 }, 113 Resources: config.Resources{ 114 Jobs: map[string]*resources.Job{ 115 "job": { 116 Paths: resources.Paths{ 117 ConfigFilePath: filepath.Join(dir, "resource.yml"), 118 }, 119 JobSettings: &jobs.JobSettings{ 120 Tasks: []jobs.Task{ 121 { 122 NotebookTask: &jobs.NotebookTask{ 123 NotebookPath: "./my_job_notebook.py", 124 }, 125 }, 126 { 127 NotebookTask: &jobs.NotebookTask{ 128 NotebookPath: "/Users/jane.doe@databricks.com/doesnt_exist.py", 129 }, 130 }, 131 { 132 NotebookTask: &jobs.NotebookTask{ 133 NotebookPath: "./my_job_notebook.py", 134 }, 135 }, 136 { 137 PythonWheelTask: &jobs.PythonWheelTask{ 138 PackageName: "foo", 139 }, 140 }, 141 { 142 SparkPythonTask: &jobs.SparkPythonTask{ 143 PythonFile: "./my_python_file.py", 144 }, 145 }, 146 }, 147 }, 148 }, 149 }, 150 Pipelines: map[string]*resources.Pipeline{ 151 "pipeline": { 152 Paths: resources.Paths{ 153 ConfigFilePath: filepath.Join(dir, "resource.yml"), 154 }, 155 PipelineSpec: &pipelines.PipelineSpec{ 156 Libraries: []pipelines.PipelineLibrary{ 157 { 158 Notebook: &pipelines.NotebookLibrary{ 159 Path: "./my_pipeline_notebook.py", 160 }, 161 }, 162 { 163 Notebook: &pipelines.NotebookLibrary{ 164 Path: "/Users/jane.doe@databricks.com/doesnt_exist.py", 165 }, 166 }, 167 { 168 Notebook: &pipelines.NotebookLibrary{ 169 Path: "./my_pipeline_notebook.py", 170 }, 171 }, 172 { 173 Jar: "foo", 174 }, 175 { 176 File: &pipelines.FileLibrary{ 177 Path: "./my_python_file.py", 178 }, 179 }, 180 }, 181 }, 182 }, 183 }, 184 }, 185 }, 186 } 187 188 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 189 require.NoError(t, err) 190 191 // Assert that the path in the tasks now refer to the artifact. 192 assert.Equal( 193 t, 194 "/bundle/my_job_notebook", 195 bundle.Config.Resources.Jobs["job"].Tasks[0].NotebookTask.NotebookPath, 196 ) 197 assert.Equal( 198 t, 199 "/Users/jane.doe@databricks.com/doesnt_exist.py", 200 bundle.Config.Resources.Jobs["job"].Tasks[1].NotebookTask.NotebookPath, 201 ) 202 assert.Equal( 203 t, 204 "/bundle/my_job_notebook", 205 bundle.Config.Resources.Jobs["job"].Tasks[2].NotebookTask.NotebookPath, 206 ) 207 assert.Equal( 208 t, 209 "/bundle/my_python_file.py", 210 bundle.Config.Resources.Jobs["job"].Tasks[4].SparkPythonTask.PythonFile, 211 ) 212 213 // Assert that the path in the libraries now refer to the artifact. 214 assert.Equal( 215 t, 216 "/bundle/my_pipeline_notebook", 217 bundle.Config.Resources.Pipelines["pipeline"].Libraries[0].Notebook.Path, 218 ) 219 assert.Equal( 220 t, 221 "/Users/jane.doe@databricks.com/doesnt_exist.py", 222 bundle.Config.Resources.Pipelines["pipeline"].Libraries[1].Notebook.Path, 223 ) 224 assert.Equal( 225 t, 226 "/bundle/my_pipeline_notebook", 227 bundle.Config.Resources.Pipelines["pipeline"].Libraries[2].Notebook.Path, 228 ) 229 assert.Equal( 230 t, 231 "/bundle/my_python_file.py", 232 bundle.Config.Resources.Pipelines["pipeline"].Libraries[4].File.Path, 233 ) 234 } 235 236 func TestTranslatePathsInSubdirectories(t *testing.T) { 237 dir := t.TempDir() 238 touchEmptyFile(t, filepath.Join(dir, "job", "my_python_file.py")) 239 touchEmptyFile(t, filepath.Join(dir, "pipeline", "my_python_file.py")) 240 241 bundle := &bundle.Bundle{ 242 Config: config.Root{ 243 Path: dir, 244 Workspace: config.Workspace{ 245 FilesPath: "/bundle", 246 }, 247 Resources: config.Resources{ 248 Jobs: map[string]*resources.Job{ 249 "job": { 250 Paths: resources.Paths{ 251 ConfigFilePath: filepath.Join(dir, "job/resource.yml"), 252 }, 253 JobSettings: &jobs.JobSettings{ 254 Tasks: []jobs.Task{ 255 { 256 SparkPythonTask: &jobs.SparkPythonTask{ 257 PythonFile: "./my_python_file.py", 258 }, 259 }, 260 }, 261 }, 262 }, 263 }, 264 Pipelines: map[string]*resources.Pipeline{ 265 "pipeline": { 266 Paths: resources.Paths{ 267 ConfigFilePath: filepath.Join(dir, "pipeline/resource.yml"), 268 }, 269 270 PipelineSpec: &pipelines.PipelineSpec{ 271 Libraries: []pipelines.PipelineLibrary{ 272 { 273 File: &pipelines.FileLibrary{ 274 Path: "./my_python_file.py", 275 }, 276 }, 277 }, 278 }, 279 }, 280 }, 281 }, 282 }, 283 } 284 285 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 286 require.NoError(t, err) 287 288 assert.Equal( 289 t, 290 "/bundle/job/my_python_file.py", 291 bundle.Config.Resources.Jobs["job"].Tasks[0].SparkPythonTask.PythonFile, 292 ) 293 294 assert.Equal( 295 t, 296 "/bundle/pipeline/my_python_file.py", 297 bundle.Config.Resources.Pipelines["pipeline"].Libraries[0].File.Path, 298 ) 299 } 300 301 func TestTranslatePathsOutsideBundleRoot(t *testing.T) { 302 dir := t.TempDir() 303 304 bundle := &bundle.Bundle{ 305 Config: config.Root{ 306 Path: dir, 307 Workspace: config.Workspace{ 308 FilesPath: "/bundle", 309 }, 310 Resources: config.Resources{ 311 Jobs: map[string]*resources.Job{ 312 "job": { 313 Paths: resources.Paths{ 314 ConfigFilePath: filepath.Join(dir, "../resource.yml"), 315 }, 316 JobSettings: &jobs.JobSettings{ 317 Tasks: []jobs.Task{ 318 { 319 SparkPythonTask: &jobs.SparkPythonTask{ 320 PythonFile: "./my_python_file.py", 321 }, 322 }, 323 }, 324 }, 325 }, 326 }, 327 }, 328 }, 329 } 330 331 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 332 assert.ErrorContains(t, err, "is not contained in bundle root") 333 } 334 335 func TestJobNotebookDoesNotExistError(t *testing.T) { 336 dir := t.TempDir() 337 338 bundle := &bundle.Bundle{ 339 Config: config.Root{ 340 Path: dir, 341 Resources: config.Resources{ 342 Jobs: map[string]*resources.Job{ 343 "job": { 344 Paths: resources.Paths{ 345 ConfigFilePath: filepath.Join(dir, "fake.yml"), 346 }, 347 JobSettings: &jobs.JobSettings{ 348 Tasks: []jobs.Task{ 349 { 350 NotebookTask: &jobs.NotebookTask{ 351 NotebookPath: "./doesnt_exist.py", 352 }, 353 }, 354 }, 355 }, 356 }, 357 }, 358 }, 359 }, 360 } 361 362 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 363 assert.EqualError(t, err, "notebook ./doesnt_exist.py not found") 364 } 365 366 func TestJobFileDoesNotExistError(t *testing.T) { 367 dir := t.TempDir() 368 369 bundle := &bundle.Bundle{ 370 Config: config.Root{ 371 Path: dir, 372 Resources: config.Resources{ 373 Jobs: map[string]*resources.Job{ 374 "job": { 375 Paths: resources.Paths{ 376 ConfigFilePath: filepath.Join(dir, "fake.yml"), 377 }, 378 JobSettings: &jobs.JobSettings{ 379 Tasks: []jobs.Task{ 380 { 381 SparkPythonTask: &jobs.SparkPythonTask{ 382 PythonFile: "./doesnt_exist.py", 383 }, 384 }, 385 }, 386 }, 387 }, 388 }, 389 }, 390 }, 391 } 392 393 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 394 assert.EqualError(t, err, "file ./doesnt_exist.py not found") 395 } 396 397 func TestPipelineNotebookDoesNotExistError(t *testing.T) { 398 dir := t.TempDir() 399 400 bundle := &bundle.Bundle{ 401 Config: config.Root{ 402 Path: dir, 403 Resources: config.Resources{ 404 Pipelines: map[string]*resources.Pipeline{ 405 "pipeline": { 406 Paths: resources.Paths{ 407 ConfigFilePath: filepath.Join(dir, "fake.yml"), 408 }, 409 PipelineSpec: &pipelines.PipelineSpec{ 410 Libraries: []pipelines.PipelineLibrary{ 411 { 412 Notebook: &pipelines.NotebookLibrary{ 413 Path: "./doesnt_exist.py", 414 }, 415 }, 416 }, 417 }, 418 }, 419 }, 420 }, 421 }, 422 } 423 424 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 425 assert.EqualError(t, err, "notebook ./doesnt_exist.py not found") 426 } 427 428 func TestPipelineFileDoesNotExistError(t *testing.T) { 429 dir := t.TempDir() 430 431 bundle := &bundle.Bundle{ 432 Config: config.Root{ 433 Path: dir, 434 Resources: config.Resources{ 435 Pipelines: map[string]*resources.Pipeline{ 436 "pipeline": { 437 Paths: resources.Paths{ 438 ConfigFilePath: filepath.Join(dir, "fake.yml"), 439 }, 440 PipelineSpec: &pipelines.PipelineSpec{ 441 Libraries: []pipelines.PipelineLibrary{ 442 { 443 File: &pipelines.FileLibrary{ 444 Path: "./doesnt_exist.py", 445 }, 446 }, 447 }, 448 }, 449 }, 450 }, 451 }, 452 }, 453 } 454 455 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 456 assert.EqualError(t, err, "file ./doesnt_exist.py not found") 457 } 458 459 func TestJobSparkPythonTaskWithNotebookSourceError(t *testing.T) { 460 dir := t.TempDir() 461 touchNotebookFile(t, filepath.Join(dir, "my_notebook.py")) 462 463 bundle := &bundle.Bundle{ 464 Config: config.Root{ 465 Path: dir, 466 Workspace: config.Workspace{ 467 FilesPath: "/bundle", 468 }, 469 Resources: config.Resources{ 470 Jobs: map[string]*resources.Job{ 471 "job": { 472 Paths: resources.Paths{ 473 ConfigFilePath: filepath.Join(dir, "resource.yml"), 474 }, 475 JobSettings: &jobs.JobSettings{ 476 Tasks: []jobs.Task{ 477 { 478 SparkPythonTask: &jobs.SparkPythonTask{ 479 PythonFile: "./my_notebook.py", 480 }, 481 }, 482 }, 483 }, 484 }, 485 }, 486 }, 487 }, 488 } 489 490 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 491 assert.ErrorContains(t, err, `expected a file for "tasks.spark_python_task.python_file" but got a notebook`) 492 } 493 494 func TestJobNotebookTaskWithFileSourceError(t *testing.T) { 495 dir := t.TempDir() 496 touchEmptyFile(t, filepath.Join(dir, "my_file.py")) 497 498 bundle := &bundle.Bundle{ 499 Config: config.Root{ 500 Path: dir, 501 Workspace: config.Workspace{ 502 FilesPath: "/bundle", 503 }, 504 Resources: config.Resources{ 505 Jobs: map[string]*resources.Job{ 506 "job": { 507 Paths: resources.Paths{ 508 ConfigFilePath: filepath.Join(dir, "resource.yml"), 509 }, 510 JobSettings: &jobs.JobSettings{ 511 Tasks: []jobs.Task{ 512 { 513 NotebookTask: &jobs.NotebookTask{ 514 NotebookPath: "./my_file.py", 515 }, 516 }, 517 }, 518 }, 519 }, 520 }, 521 }, 522 }, 523 } 524 525 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 526 assert.ErrorContains(t, err, `expected a notebook for "tasks.notebook_task.notebook_path" but got a file`) 527 } 528 529 func TestPipelineNotebookLibraryWithFileSourceError(t *testing.T) { 530 dir := t.TempDir() 531 touchEmptyFile(t, filepath.Join(dir, "my_file.py")) 532 533 bundle := &bundle.Bundle{ 534 Config: config.Root{ 535 Path: dir, 536 Workspace: config.Workspace{ 537 FilesPath: "/bundle", 538 }, 539 Resources: config.Resources{ 540 Pipelines: map[string]*resources.Pipeline{ 541 "pipeline": { 542 Paths: resources.Paths{ 543 ConfigFilePath: filepath.Join(dir, "resource.yml"), 544 }, 545 PipelineSpec: &pipelines.PipelineSpec{ 546 Libraries: []pipelines.PipelineLibrary{ 547 { 548 Notebook: &pipelines.NotebookLibrary{ 549 Path: "./my_file.py", 550 }, 551 }, 552 }, 553 }, 554 }, 555 }, 556 }, 557 }, 558 } 559 560 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 561 assert.ErrorContains(t, err, `expected a notebook for "libraries.notebook.path" but got a file`) 562 } 563 564 func TestPipelineFileLibraryWithNotebookSourceError(t *testing.T) { 565 dir := t.TempDir() 566 touchNotebookFile(t, filepath.Join(dir, "my_notebook.py")) 567 568 bundle := &bundle.Bundle{ 569 Config: config.Root{ 570 Path: dir, 571 Workspace: config.Workspace{ 572 FilesPath: "/bundle", 573 }, 574 Resources: config.Resources{ 575 Pipelines: map[string]*resources.Pipeline{ 576 "pipeline": { 577 Paths: resources.Paths{ 578 ConfigFilePath: filepath.Join(dir, "resource.yml"), 579 }, 580 PipelineSpec: &pipelines.PipelineSpec{ 581 Libraries: []pipelines.PipelineLibrary{ 582 { 583 File: &pipelines.FileLibrary{ 584 Path: "./my_notebook.py", 585 }, 586 }, 587 }, 588 }, 589 }, 590 }, 591 }, 592 }, 593 } 594 595 err := mutator.TranslatePaths().Apply(context.Background(), bundle) 596 assert.ErrorContains(t, err, `expected a file for "libraries.file.path" but got a notebook`) 597 }