github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/cf/actors/push_test.go (about)

     1  package actors_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  
    11  	"code.cloudfoundry.org/cli/cf/actors"
    12  	"code.cloudfoundry.org/cli/cf/actors/actorsfakes"
    13  	"code.cloudfoundry.org/cli/cf/api/applicationbits/applicationbitsfakes"
    14  	"code.cloudfoundry.org/cli/cf/api/resources"
    15  	"code.cloudfoundry.org/cli/cf/appfiles"
    16  	"code.cloudfoundry.org/cli/cf/appfiles/appfilesfakes"
    17  	"code.cloudfoundry.org/cli/cf/models"
    18  	. "github.com/onsi/ginkgo"
    19  	. "github.com/onsi/gomega"
    20  )
    21  
    22  var _ = Describe("Push Actor", func() {
    23  	var (
    24  		appBitsRepo  *applicationbitsfakes.FakeApplicationBitsRepository
    25  		appFiles     *appfilesfakes.FakeAppFiles
    26  		fakezipper   *appfilesfakes.FakeZipper
    27  		routeActor   *actorsfakes.FakeRouteActor
    28  		actor        actors.PushActor
    29  		fixturesDir  string
    30  		appDir       string
    31  		allFiles     []models.AppFileFields
    32  		presentFiles []resources.AppFileResource
    33  	)
    34  
    35  	BeforeEach(func() {
    36  		appBitsRepo = new(applicationbitsfakes.FakeApplicationBitsRepository)
    37  		appFiles = new(appfilesfakes.FakeAppFiles)
    38  		fakezipper = new(appfilesfakes.FakeZipper)
    39  		routeActor = new(actorsfakes.FakeRouteActor)
    40  		actor = actors.NewPushActor(appBitsRepo, fakezipper, appFiles, routeActor)
    41  		fixturesDir = filepath.Join("..", "..", "fixtures", "applications")
    42  		allFiles = []models.AppFileFields{
    43  			{Path: "example-app/.cfignore"},
    44  			{Path: "example-app/app.rb"},
    45  			{Path: "example-app/config.ru"},
    46  			{Path: "example-app/Gemfile"},
    47  			{Path: "example-app/Gemfile.lock"},
    48  			{Path: "example-app/ignore-me"},
    49  			{Path: "example-app/manifest.yml"},
    50  		}
    51  	})
    52  
    53  	Describe("GatherFiles", func() {
    54  		var tmpDir string
    55  
    56  		BeforeEach(func() {
    57  			presentFiles = []resources.AppFileResource{
    58  				{Path: "example-app/ignore-me"},
    59  			}
    60  
    61  			appDir = filepath.Join(fixturesDir, "example-app.zip")
    62  			appBitsRepo.GetApplicationFilesReturns(presentFiles, nil)
    63  			var err error
    64  			tmpDir, err = ioutil.TempDir("", "gather-files")
    65  			Expect(err).NotTo(HaveOccurred())
    66  		})
    67  
    68  		AfterEach(func() {
    69  			os.RemoveAll(tmpDir)
    70  		})
    71  
    72  		Context("when we cannot reach CC", func() {
    73  			var expectedErr error
    74  
    75  			BeforeEach(func() {
    76  				expectedErr = errors.New("error")
    77  				appBitsRepo.GetApplicationFilesReturns(nil, expectedErr)
    78  			})
    79  
    80  			It("returns an error if we cannot reach the cc", func() {
    81  				_, _, err := actor.GatherFiles(allFiles, appDir, tmpDir, true)
    82  				Expect(err).To(HaveOccurred())
    83  				Expect(err).To(Equal(expectedErr))
    84  			})
    85  		})
    86  
    87  		Context("when we cannot copy the app files", func() {
    88  			var expectedErr error
    89  
    90  			BeforeEach(func() {
    91  				expectedErr = errors.New("error")
    92  				appFiles.CopyFilesReturns(expectedErr)
    93  			})
    94  
    95  			It("returns an error", func() {
    96  				_, _, err := actor.GatherFiles(allFiles, appDir, tmpDir, true)
    97  				Expect(err).To(HaveOccurred())
    98  				Expect(err).To(Equal(expectedErr))
    99  			})
   100  		})
   101  
   102  		Context("when using .cfignore", func() {
   103  			BeforeEach(func() {
   104  				appDir = filepath.Join(fixturesDir, "exclude-a-default-cfignore")
   105  				// Ignore app files for this test as .cfignore is not one of them
   106  				appBitsRepo.GetApplicationFilesReturns(nil, nil)
   107  			})
   108  
   109  			It("copies the .cfignore file to the upload directory", func() {
   110  				_, _, err := actor.GatherFiles(allFiles, appDir, tmpDir, true)
   111  				Expect(err).NotTo(HaveOccurred())
   112  
   113  				_, err = os.Stat(filepath.Join(tmpDir, ".cfignore"))
   114  				Expect(os.IsNotExist(err)).To(BeFalse())
   115  			})
   116  		})
   117  
   118  		It("returns files to upload with file mode unchanged on non-Windows platforms", func() {
   119  			if runtime.GOOS == "windows" {
   120  				Skip("This does not run on windows")
   121  			}
   122  
   123  			info, err := os.Lstat(filepath.Join(fixturesDir, "example-app/ignore-me"))
   124  			Expect(err).NotTo(HaveOccurred())
   125  
   126  			expectedFileMode := fmt.Sprintf("%#o", info.Mode())
   127  
   128  			actualFiles, _, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   129  			Expect(err).NotTo(HaveOccurred())
   130  
   131  			expectedFiles := []resources.AppFileResource{
   132  				{
   133  					Path: "example-app/ignore-me",
   134  					Mode: expectedFileMode,
   135  				},
   136  			}
   137  
   138  			Expect(actualFiles).To(Equal(expectedFiles))
   139  		})
   140  
   141  		It("returns files to upload with file mode always being executable on Windows platforms", func() {
   142  			if runtime.GOOS != "windows" {
   143  				Skip("This runs only on windows")
   144  			}
   145  
   146  			info, err := os.Lstat(filepath.Join(fixturesDir, "example-app/ignore-me"))
   147  			Expect(err).NotTo(HaveOccurred())
   148  
   149  			expectedFileMode := fmt.Sprintf("%#o", info.Mode()|0700)
   150  
   151  			actualFiles, _, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   152  			Expect(err).NotTo(HaveOccurred())
   153  
   154  			expectedFiles := []resources.AppFileResource{
   155  				{
   156  					Path: "example-app/ignore-me",
   157  					Mode: expectedFileMode,
   158  				},
   159  			}
   160  
   161  			Expect(actualFiles).To(Equal(expectedFiles))
   162  		})
   163  
   164  		Context("when there are no remote files", func() {
   165  			BeforeEach(func() {
   166  				appBitsRepo.GetApplicationFilesReturns([]resources.AppFileResource{}, nil)
   167  			})
   168  
   169  			It("returns true for hasFileToUpload", func() {
   170  				_, hasFileToUpload, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   171  				Expect(err).NotTo(HaveOccurred())
   172  				Expect(hasFileToUpload).To(BeTrue())
   173  			})
   174  
   175  			It("copies all local files to the upload dir", func() {
   176  				expectedFiles := []models.AppFileFields{
   177  					{Path: "example-app/.cfignore"},
   178  					{Path: "example-app/app.rb"},
   179  					{Path: "example-app/config.ru"},
   180  					{Path: "example-app/Gemfile"},
   181  					{Path: "example-app/Gemfile.lock"},
   182  					{Path: "example-app/ignore-me"},
   183  					{Path: "example-app/manifest.yml"},
   184  				}
   185  				_, _, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   186  				Expect(err).NotTo(HaveOccurred())
   187  
   188  				Expect(appFiles.CopyFilesCallCount()).To(Equal(1))
   189  				filesToUpload, fromDir, toDir := appFiles.CopyFilesArgsForCall(0)
   190  				Expect(filesToUpload).To(Equal(expectedFiles))
   191  				Expect(fromDir).To(Equal(fixturesDir))
   192  				Expect(toDir).To(Equal(tmpDir))
   193  			})
   194  		})
   195  
   196  		Context("when there are local files that aren't matched", func() {
   197  			var remoteFiles []resources.AppFileResource
   198  
   199  			BeforeEach(func() {
   200  				remoteFiles = []resources.AppFileResource{
   201  					{Path: "example-app/manifest.yml"},
   202  				}
   203  
   204  				appBitsRepo.GetApplicationFilesReturns(remoteFiles, nil)
   205  			})
   206  
   207  			It("returns true for hasFileToUpload", func() {
   208  				_, hasFileToUpload, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   209  				Expect(err).NotTo(HaveOccurred())
   210  				Expect(hasFileToUpload).To(BeTrue())
   211  			})
   212  
   213  			It("copies unmatched local files to the upload dir", func() {
   214  				expectedFiles := []models.AppFileFields{
   215  					{Path: "example-app/.cfignore"},
   216  					{Path: "example-app/app.rb"},
   217  					{Path: "example-app/config.ru"},
   218  					{Path: "example-app/Gemfile"},
   219  					{Path: "example-app/Gemfile.lock"},
   220  					{Path: "example-app/ignore-me"},
   221  				}
   222  				_, _, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   223  				Expect(err).NotTo(HaveOccurred())
   224  
   225  				Expect(appFiles.CopyFilesCallCount()).To(Equal(1))
   226  				filesToUpload, fromDir, toDir := appFiles.CopyFilesArgsForCall(0)
   227  				Expect(filesToUpload).To(Equal(expectedFiles))
   228  				Expect(fromDir).To(Equal(fixturesDir))
   229  				Expect(toDir).To(Equal(tmpDir))
   230  			})
   231  		})
   232  
   233  		Context("when local and remote files are equivalent", func() {
   234  			BeforeEach(func() {
   235  				remoteFiles := []resources.AppFileResource{
   236  					{Path: "example-app/.cfignore", Mode: "0644"},
   237  					{Path: "example-app/app.rb", Mode: "0755"},
   238  					{Path: "example-app/config.ru", Mode: "0644"},
   239  					{Path: "example-app/Gemfile", Mode: "0644"},
   240  					{Path: "example-app/Gemfile.lock", Mode: "0644"},
   241  					{Path: "example-app/ignore-me", Mode: "0666"},
   242  					{Path: "example-app/manifest.yml", Mode: "0644"},
   243  				}
   244  
   245  				appBitsRepo.GetApplicationFilesReturns(remoteFiles, nil)
   246  			})
   247  
   248  			It("returns false for hasFileToUpload", func() {
   249  				_, hasFileToUpload, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   250  				Expect(err).NotTo(HaveOccurred())
   251  				Expect(hasFileToUpload).To(BeFalse())
   252  			})
   253  
   254  			It("copies nothing to the upload dir", func() {
   255  				_, _, err := actor.GatherFiles(allFiles, fixturesDir, tmpDir, true)
   256  				Expect(err).NotTo(HaveOccurred())
   257  
   258  				Expect(appFiles.CopyFilesCallCount()).To(Equal(1))
   259  				filesToUpload, fromDir, toDir := appFiles.CopyFilesArgsForCall(0)
   260  				Expect(filesToUpload).To(BeEmpty())
   261  				Expect(fromDir).To(Equal(fixturesDir))
   262  				Expect(toDir).To(Equal(tmpDir))
   263  			})
   264  		})
   265  
   266  		Context("when told not to use the remote cache", func() {
   267  			It("does not use the remote cache", func() {
   268  				actor.GatherFiles(allFiles, fixturesDir, tmpDir, false)
   269  				Expect(appBitsRepo.GetApplicationFilesCallCount()).To(Equal(0))
   270  			})
   271  		})
   272  	})
   273  
   274  	Describe("UploadApp", func() {
   275  		It("Simply delegates to the UploadApp function on the app bits repo, which is not worth testing", func() {})
   276  	})
   277  
   278  	Describe("ProcessPath", func() {
   279  		var (
   280  			wasCalled     bool
   281  			wasCalledWith string
   282  		)
   283  
   284  		BeforeEach(func() {
   285  			zipper := &appfiles.ApplicationZipper{}
   286  			actor = actors.NewPushActor(appBitsRepo, zipper, appFiles, routeActor)
   287  		})
   288  
   289  		Context("when given a zip file", func() {
   290  			var zipFile string
   291  
   292  			BeforeEach(func() {
   293  				zipFile = filepath.Join(fixturesDir, "example-app.zip")
   294  			})
   295  
   296  			It("extracts the zip when given a zip file", func() {
   297  				f := func(tempDir string) error {
   298  					for _, file := range allFiles {
   299  						actualFilePath := filepath.Join(tempDir, file.Path)
   300  						_, err := os.Stat(actualFilePath)
   301  						Expect(err).NotTo(HaveOccurred())
   302  					}
   303  					return nil
   304  				}
   305  				err := actor.ProcessPath(zipFile, f)
   306  				Expect(err).NotTo(HaveOccurred())
   307  			})
   308  
   309  			It("calls the provided function with the directory that it extracted to", func() {
   310  				f := func(tempDir string) error {
   311  					wasCalled = true
   312  					wasCalledWith = tempDir
   313  					return nil
   314  				}
   315  				err := actor.ProcessPath(zipFile, f)
   316  				Expect(err).NotTo(HaveOccurred())
   317  				Expect(wasCalled).To(BeTrue())
   318  				Expect(wasCalledWith).NotTo(Equal(zipFile))
   319  			})
   320  
   321  			It("cleans up the directory that it extracted to", func() {
   322  				var tempDirWas string
   323  				f := func(tempDir string) error {
   324  					tempDirWas = tempDir
   325  					return nil
   326  				}
   327  				err := actor.ProcessPath(zipFile, f)
   328  				Expect(err).NotTo(HaveOccurred())
   329  				_, err = os.Stat(tempDirWas)
   330  				Expect(err).To(HaveOccurred())
   331  			})
   332  
   333  			It("returns an error if the unzipping fails", func() {
   334  				e := errors.New("some-error")
   335  				fakezipper.UnzipReturns(e)
   336  				fakezipper.IsZipFileReturns(true)
   337  				actor = actors.NewPushActor(appBitsRepo, fakezipper, appFiles, routeActor)
   338  
   339  				f := func(_ string) error {
   340  					return nil
   341  				}
   342  				err := actor.ProcessPath(zipFile, f)
   343  				Expect(err).To(HaveOccurred())
   344  			})
   345  		})
   346  
   347  		It("calls the provided function with the provided directory", func() {
   348  			appDir = filepath.Join(fixturesDir, "example-app")
   349  			f := func(tempDir string) error {
   350  				wasCalled = true
   351  				wasCalledWith = tempDir
   352  				return nil
   353  			}
   354  			err := actor.ProcessPath(appDir, f)
   355  			Expect(err).NotTo(HaveOccurred())
   356  			Expect(wasCalled).To(BeTrue())
   357  
   358  			path, err := filepath.Abs(appDir)
   359  			Expect(err).NotTo(HaveOccurred())
   360  
   361  			path, err = filepath.EvalSymlinks(path)
   362  			Expect(err).NotTo(HaveOccurred())
   363  
   364  			Expect(wasCalledWith).To(Equal(path))
   365  		})
   366  
   367  		It("dereferences the symlink when given a symlink to an app dir", func() {
   368  			if runtime.GOOS == "windows" {
   369  				Skip("This should not run on Windows")
   370  			}
   371  
   372  			symlink := filepath.Join(fixturesDir, "example-app-symlink")
   373  			expectedDir := filepath.Join(fixturesDir, "example-app") // example-app-symlink -> example-app
   374  			f := func(dir string) error {
   375  				wasCalled = true
   376  				wasCalledWith = dir
   377  				return nil
   378  			}
   379  
   380  			err := actor.ProcessPath(symlink, f)
   381  			Expect(err).NotTo(HaveOccurred())
   382  			Expect(wasCalled).To(BeTrue())
   383  			path, err := filepath.Abs(expectedDir)
   384  			Expect(err).NotTo(HaveOccurred())
   385  			Expect(wasCalledWith).To(Equal(path))
   386  		})
   387  
   388  		It("calls the provided function with the provided absolute directory", func() {
   389  			appDir = filepath.Join(fixturesDir, "example-app")
   390  			absolutePath, err := filepath.Abs(appDir)
   391  			Expect(err).NotTo(HaveOccurred())
   392  			f := func(tempDir string) error {
   393  				wasCalled = true
   394  				wasCalledWith = tempDir
   395  				return nil
   396  			}
   397  			err = actor.ProcessPath(absolutePath, f)
   398  			Expect(err).NotTo(HaveOccurred())
   399  
   400  			absolutePath, err = filepath.EvalSymlinks(absolutePath)
   401  			Expect(err).NotTo(HaveOccurred())
   402  
   403  			Expect(wasCalled).To(BeTrue())
   404  			Expect(wasCalledWith).To(Equal(absolutePath))
   405  		})
   406  	})
   407  
   408  	Describe("ValidateAppParams", func() {
   409  		var apps []models.AppParams
   410  
   411  		Context("when HealthCheckType is not http", func() {
   412  			Context("when HealthCheckHTTPEndpoint is provided", func() {
   413  				BeforeEach(func() {
   414  					healthCheckType := "port"
   415  					endpoint := "/some-endpoint"
   416  					apps = []models.AppParams{
   417  						models.AppParams{
   418  							HealthCheckType:         &healthCheckType,
   419  							HealthCheckHTTPEndpoint: &endpoint,
   420  						},
   421  					}
   422  				})
   423  				It("displays error", func() {
   424  					errs := actor.ValidateAppParams(apps)
   425  					Expect(errs).To(HaveLen(1))
   426  					Expect(errs[0].Error()).To(Equal("Health check type must be 'http' to set a health check HTTP endpoint."))
   427  				})
   428  			})
   429  		})
   430  
   431  		Context("when docker and buildpack is provided", func() {
   432  			BeforeEach(func() {
   433  				appName := "my-app"
   434  				dockerImage := "some-image"
   435  				buildpackURL := "some-build-pack.url"
   436  				apps = []models.AppParams{
   437  					models.AppParams{
   438  						Name:         &appName,
   439  						BuildpackURL: &buildpackURL,
   440  						DockerImage:  &dockerImage,
   441  					},
   442  				}
   443  			})
   444  			It("displays error", func() {
   445  				errs := actor.ValidateAppParams(apps)
   446  				Expect(errs).To(HaveLen(1))
   447  				Expect(errs[0].Error()).To(Equal("Application my-app must not be configured with both 'buildpack' and 'docker'"))
   448  			})
   449  		})
   450  
   451  		Context("when docker and path is provided", func() {
   452  			BeforeEach(func() {
   453  				appName := "my-app"
   454  				dockerImage := "some-image"
   455  				path := "some-path"
   456  				apps = []models.AppParams{
   457  					models.AppParams{
   458  						Name:        &appName,
   459  						DockerImage: &dockerImage,
   460  						Path:        &path,
   461  					},
   462  				}
   463  			})
   464  			It("displays error", func() {
   465  				errs := actor.ValidateAppParams(apps)
   466  				Expect(errs).To(HaveLen(1))
   467  				Expect(errs[0].Error()).To(Equal("Application my-app must not be configured with both 'docker' and 'path'"))
   468  			})
   469  		})
   470  
   471  		Context("when 'routes' is provided", func() {
   472  			BeforeEach(func() {
   473  				appName := "my-app"
   474  				apps = []models.AppParams{
   475  					models.AppParams{
   476  						Name: &appName,
   477  						Routes: []models.ManifestRoute{
   478  							models.ManifestRoute{
   479  								Route: "route-name.example.com",
   480  							},
   481  							models.ManifestRoute{
   482  								Route: "other-route-name.example.com",
   483  							},
   484  						},
   485  					},
   486  				}
   487  			})
   488  
   489  			Context("and 'hosts' is provided", func() {
   490  				BeforeEach(func() {
   491  					apps[0].Hosts = []string{"host-name"}
   492  				})
   493  
   494  				It("returns an error", func() {
   495  					errs := actor.ValidateAppParams(apps)
   496  					Expect(errs).To(HaveLen(1))
   497  					Expect(errs[0].Error()).To(Equal("Application my-app must not be configured with both 'routes' and 'host'/'hosts'"))
   498  				})
   499  			})
   500  
   501  			Context("and 'domains' is provided", func() {
   502  				BeforeEach(func() {
   503  					apps[0].Domains = []string{"domain-name"}
   504  				})
   505  
   506  				It("returns an error", func() {
   507  					errs := actor.ValidateAppParams(apps)
   508  					Expect(errs).To(HaveLen(1))
   509  					Expect(errs[0].Error()).To(Equal("Application my-app must not be configured with both 'routes' and 'domain'/'domains'"))
   510  				})
   511  			})
   512  
   513  			Context("and 'no-hostname' is provided", func() {
   514  				BeforeEach(func() {
   515  					noHostBool := true
   516  					apps[0].NoHostname = &noHostBool
   517  				})
   518  
   519  				It("returns an error", func() {
   520  					errs := actor.ValidateAppParams(apps)
   521  					Expect(errs).To(HaveLen(1))
   522  					Expect(errs[0].Error()).To(Equal("Application my-app must not be configured with both 'routes' and 'no-hostname'"))
   523  				})
   524  			})
   525  
   526  			Context("and 'no-hostname' is not provided", func() {
   527  				BeforeEach(func() {
   528  					apps[0].NoHostname = nil
   529  				})
   530  
   531  				It("returns an error", func() {
   532  					errs := actor.ValidateAppParams(apps)
   533  					Expect(errs).To(HaveLen(0))
   534  				})
   535  			})
   536  		})
   537  	})
   538  
   539  	Describe("MapManifestRoute", func() {
   540  		It("passes arguments to route actor", func() {
   541  			appName := "app-name"
   542  			app := models.Application{
   543  				ApplicationFields: models.ApplicationFields{
   544  					Name: appName,
   545  					GUID: "app-guid",
   546  				},
   547  			}
   548  			appParamsFromContext := models.AppParams{
   549  				Name: &appName,
   550  			}
   551  
   552  			_ = actor.MapManifestRoute("route-name.example.com/testPath", app, appParamsFromContext)
   553  			actualRoute, actualApp, actualAppParams := routeActor.FindAndBindRouteArgsForCall(0)
   554  			Expect(actualRoute).To(Equal("route-name.example.com/testPath"))
   555  			Expect(actualApp).To(Equal(app))
   556  			Expect(actualAppParams).To(Equal(appParamsFromContext))
   557  		})
   558  	})
   559  })