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  }