github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/integration/v6/experimental/v3_zdt_push_command_test.go (about)

     1  package experimental
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"os"
     8  	"regexp"
     9  
    10  	"code.cloudfoundry.org/cli/integration/helpers"
    11  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/gomega"
    13  	. "github.com/onsi/gomega/gbytes"
    14  	. "github.com/onsi/gomega/gexec"
    15  	. "github.com/onsi/gomega/ghttp"
    16  )
    17  
    18  var _ = Describe("v3-zdt-push command", func() {
    19  	var (
    20  		orgName           string
    21  		spaceName         string
    22  		appName           string
    23  		userName          string
    24  		PublicDockerImage = "cloudfoundry/diego-docker-app-custom"
    25  	)
    26  
    27  	BeforeEach(func() {
    28  		orgName = helpers.NewOrgName()
    29  		spaceName = helpers.NewSpaceName()
    30  		appName = helpers.PrefixedRandomName("app")
    31  		userName, _ = helpers.GetCredentials()
    32  		helpers.TurnOffExperimental()
    33  	})
    34  
    35  	AfterEach(func() {
    36  		helpers.TurnOnExperimental()
    37  	})
    38  
    39  	Describe("help", func() {
    40  		Context("when --help flag is set", func() {
    41  			It("Displays command usage to output", func() {
    42  				session := helpers.CF("v3-zdt-push", "--help")
    43  				Eventually(session).Should(Say("NAME:"))
    44  				Eventually(session).Should(Say("v3-zdt-push - Update an app with zero down time"))
    45  				Eventually(session).Should(Say("USAGE:"))
    46  				Eventually(session).Should(Say(`cf v3-zdt-push APP_NAME \[-b BUILDPACK\]\.\.\. \[-p APP_PATH\] \[--no-route\]`))
    47  				Eventually(session).Should(Say(`cf v3-zdt-push APP_NAME --docker-image \[REGISTRY_HOST:PORT/\]IMAGE\[:TAG\] \[--docker-username USERNAME\] \[--no-route\]`))
    48  				Eventually(session).Should(Say("OPTIONS:"))
    49  				Eventually(session).Should(Say(`-b\s+Custom buildpack by name \(e\.g\. my-buildpack\) or Git URL \(e\.g\. 'https://github.com/cloudfoundry/java-buildpack.git'\) or Git URL with a branch or tag \(e\.g\. 'https://github.com/cloudfoundry/java-buildpack\.git#v3.3.0' for 'v3.3.0' tag\)\. To use built-in buildpacks only, specify 'default' or 'null'`))
    50  				Eventually(session).Should(Say(`-s\s+Stack to use \(a stack is a pre-built file system, including an operating system, that can run apps\)`))
    51  				Eventually(session).Should(Say(`--docker-image, -o\s+Docker image to use \(e\.g\. user/docker-image-name\)`))
    52  				Eventually(session).Should(Say(`--docker-username\s+Repository username; used with password from environment variable CF_DOCKER_PASSWORD`))
    53  				Eventually(session).Should(Say(`--no-route\s+Do not map a route to this app`))
    54  				Eventually(session).Should(Say(`-p\s+Path to app directory or to a zip file of the contents of the app directory`))
    55  				Eventually(session).Should(Say("ENVIRONMENT:"))
    56  				Eventually(session).Should(Say(`CF_DOCKER_PASSWORD=\s+Password used for private docker repository`))
    57  				Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for buildpack staging, in minutes`))
    58  				Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`))
    59  
    60  				Eventually(session).Should(Exit(0))
    61  			})
    62  		})
    63  	})
    64  
    65  	Context("when the app name is not provided", func() {
    66  		It("tells the user that the app name is required, prints help text, and exits 1", func() {
    67  			session := helpers.CF("v3-zdt-push")
    68  
    69  			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `APP_NAME` was not provided"))
    70  			Eventually(session).Should(Say("NAME:"))
    71  			Eventually(session).Should(Exit(1))
    72  		})
    73  	})
    74  
    75  	It("displays the experimental warning", func() {
    76  		session := helpers.CF("v3-zdt-push", appName)
    77  		Eventually(session.Err).Should(Say("This command is in EXPERIMENTAL stage and may change without notice"))
    78  		Eventually(session).Should(Exit())
    79  	})
    80  
    81  	Context("when the -b flag is not given an arg", func() {
    82  		It("tells the user that the flag requires an arg, prints help text, and exits 1", func() {
    83  			session := helpers.CF("v3-zdt-push", appName, "-b")
    84  
    85  			Eventually(session.Err).Should(Say("Incorrect Usage: expected argument for flag `-b'"))
    86  			Eventually(session).Should(Say("NAME:"))
    87  			Eventually(session).Should(Exit(1))
    88  		})
    89  	})
    90  
    91  	Context("when the -p flag is not given an arg", func() {
    92  		It("tells the user that the flag requires an arg, prints help text, and exits 1", func() {
    93  			session := helpers.CF("v3-zdt-push", appName, "-p")
    94  
    95  			Eventually(session.Err).Should(Say("Incorrect Usage: expected argument for flag `-p'"))
    96  			Eventually(session).Should(Say("NAME:"))
    97  			Eventually(session).Should(Exit(1))
    98  		})
    99  	})
   100  
   101  	Context("when the -p flag path does not exist", func() {
   102  		It("tells the user that the flag requires an arg, prints help text, and exits 1", func() {
   103  			session := helpers.CF("v3-zdt-push", appName, "-p", "path/that/does/not/exist")
   104  
   105  			Eventually(session.Err).Should(Say("Incorrect Usage: The specified path 'path/that/does/not/exist' does not exist."))
   106  			Eventually(session).Should(Say("NAME:"))
   107  			Eventually(session).Should(Exit(1))
   108  		})
   109  	})
   110  
   111  	Context("when the environment is not setup correctly", func() {
   112  		Context("when the v3 api version is lower than the minimum version", func() {
   113  			var server *Server
   114  
   115  			BeforeEach(func() {
   116  				server = helpers.StartAndTargetServerWithAPIVersions(helpers.DefaultV2Version, "3.0.0")
   117  			})
   118  
   119  			AfterEach(func() {
   120  				server.Close()
   121  			})
   122  
   123  			It("fails with error message that the minimum version is not met", func() {
   124  				session := helpers.CF("v3-zdt-push", appName)
   125  				Eventually(session).Should(Say("FAILED"))
   126  				Eventually(session.Err).Should(Say(`This command requires CF API version 3\.57\.0 or higher\.`))
   127  				Eventually(session).Should(Exit(1))
   128  			})
   129  		})
   130  
   131  		It("fails with the appropriate errors", func() {
   132  			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "v3-zdt-push", appName)
   133  		})
   134  	})
   135  
   136  	Context("when the environment is set up correctly", func() {
   137  		var domainName string
   138  
   139  		BeforeEach(func() {
   140  			helpers.SetupCF(orgName, spaceName)
   141  			domainName = helpers.DefaultSharedDomain()
   142  		})
   143  
   144  		AfterEach(func() {
   145  			helpers.QuickDeleteOrg(orgName)
   146  		})
   147  
   148  		Context("when the app exists", func() {
   149  			var session *Session
   150  			BeforeEach(func() {
   151  				helpers.WithHelloWorldApp(func(appDir string) {
   152  					Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName)).Should(Exit(0))
   153  				})
   154  
   155  				helpers.WithHelloWorldApp(func(appDir string) {
   156  					session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "https://github.com/cloudfoundry/staticfile-buildpack")
   157  					Eventually(session).Should(Exit(0))
   158  				})
   159  			})
   160  
   161  			It("pushes the app", func() {
   162  				Eventually(session).Should(Say(`Updating app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   163  				Eventually(session).Should(Say("OK"))
   164  				Eventually(session).Should(Say(""))
   165  				Eventually(session).Should(Say(`Uploading and creating bits package for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   166  				Eventually(session).Should(Say("OK"))
   167  				Eventually(session).Should(Say(""))
   168  				Eventually(session).Should(Say(`Staging package for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   169  				Eventually(session).Should(Say("OK"))
   170  				Eventually(session).Should(Say(""))
   171  				Eventually(session).Should(Say(`Mapping routes\.\.\.`))
   172  				Eventually(session).Should(Say("OK"))
   173  				Eventually(session).Should(Say(""))
   174  				Eventually(session).Should(Say(`Starting deployment for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   175  				Eventually(session).Should(Say("OK"))
   176  				Eventually(session).Should(Say(""))
   177  				Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
   178  				Eventually(session).Should(Say(`Showing health and status for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   179  				Eventually(session).Should(Say(""))
   180  				Eventually(session).Should(Say(`name:\s+%s`, appName))
   181  				Eventually(session).Should(Say(`requested state:\s+started`))
   182  				Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   183  				Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   184  
   185  				// TODO: Uncomment when capi sorts out droplet buildpack name/detectoutput
   186  				// Eventually(session).Should(Say(`buildpacks:\s+https://github.com/cloudfoundry/staticfile-buildpack`))
   187  				Eventually(session).Should(Say(""))
   188  				Eventually(session).Should(Say(`type:\s+web`))
   189  				Eventually(session).Should(Say(`instances:\s+1/1`))
   190  				Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   191  				Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   192  				Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   193  			})
   194  		})
   195  
   196  		Context("when the app does not already exist", func() {
   197  			var session *Session
   198  
   199  			BeforeEach(func() {
   200  				helpers.WithHelloWorldApp(func(appDir string) {
   201  					session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName)
   202  					Eventually(session).Should(Exit(0))
   203  				})
   204  			})
   205  
   206  			It("pushes the app", func() {
   207  				Eventually(session).Should(Say(`Creating app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   208  				Eventually(session).Should(Say("OK"))
   209  				Eventually(session).Should(Say(""))
   210  				Eventually(session).Should(Say(`Uploading and creating bits package for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   211  				Eventually(session).Should(Say("OK"))
   212  				Eventually(session).Should(Say(""))
   213  				Consistently(session).ShouldNot(Say("Stopping app %s", appName))
   214  				Eventually(session).Should(Say(`Staging package for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   215  				Eventually(session).Should(Say("OK"))
   216  				Eventually(session).Should(Say(""))
   217  				Eventually(session).Should(Say(`Mapping routes\.\.\.`))
   218  				Eventually(session).Should(Say("OK"))
   219  				Eventually(session).Should(Say(""))
   220  				Eventually(session).Should(Say(`Setting app %s to droplet .+ in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   221  				Eventually(session).Should(Say("OK"))
   222  				Eventually(session).Should(Say(""))
   223  				Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   224  				Eventually(session).Should(Say("OK"))
   225  				Eventually(session).Should(Say(""))
   226  				Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
   227  				Eventually(session).Should(Say(`Showing health and status for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   228  				Eventually(session).Should(Say(""))
   229  				Eventually(session).Should(Say(`name:\s+%s`, appName))
   230  				Eventually(session).Should(Say(`requested state:\s+started`))
   231  				Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   232  				Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   233  				Eventually(session).Should(Say(`buildpacks:\s+staticfile`))
   234  				Eventually(session).Should(Say(""))
   235  				Eventually(session).Should(Say(`type:\s+web`))
   236  				Eventually(session).Should(Say(`instances:\s+1/1`))
   237  				Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   238  				Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   239  				Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   240  			})
   241  		})
   242  
   243  		Context("when the app crashes", func() {
   244  			var session *Session
   245  
   246  			BeforeEach(func() {
   247  				helpers.WithCrashingApp(func(appDir string) {
   248  					session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName)
   249  					Eventually(session).Should(Exit(0))
   250  				})
   251  			})
   252  
   253  			It("pushes the app", func() {
   254  				Eventually(session).Should(Say(`Creating app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   255  				Eventually(session).Should(Say("OK"))
   256  				Eventually(session).Should(Say(""))
   257  				Eventually(session).Should(Say(`Uploading and creating bits package for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   258  				Eventually(session).Should(Say("OK"))
   259  				Eventually(session).Should(Say(""))
   260  				Consistently(session).ShouldNot(Say("Stopping app %s", appName))
   261  				Eventually(session).Should(Say(`Staging package for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   262  				Eventually(session).Should(Say("OK"))
   263  				Eventually(session).Should(Say(""))
   264  				Eventually(session).Should(Say(`Mapping routes\.\.\.`))
   265  				Eventually(session).Should(Say("OK"))
   266  				Eventually(session).Should(Say(""))
   267  				Eventually(session).Should(Say(`Setting app %s to droplet .+ in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   268  				Eventually(session).Should(Say("OK"))
   269  				Eventually(session).Should(Say(""))
   270  				Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   271  				Eventually(session).Should(Say("OK"))
   272  				Eventually(session).Should(Say(""))
   273  				Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
   274  				Eventually(session).Should(Say(`Showing health and status for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   275  				Eventually(session).Should(Say(""))
   276  				Eventually(session).Should(Say(`name:\s+%s`, appName))
   277  				Eventually(session).Should(Say(`requested state:\s+started`))
   278  				Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   279  				Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   280  				Eventually(session).Should(Say(`buildpacks:\s+ruby`))
   281  				Eventually(session).Should(Say(""))
   282  				Eventually(session).Should(Say(`type:\s+web`))
   283  				Eventually(session).Should(Say(`instances:\s+0/1`))
   284  				Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   285  				Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   286  				Eventually(session).Should(Say(`#0\s+crashed\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   287  			})
   288  		})
   289  
   290  		Context("when the -p flag is provided", func() {
   291  			Context("when the path is a directory", func() {
   292  				Context("when the directory contains files", func() {
   293  					It("pushes the app from the directory", func() {
   294  						helpers.WithHelloWorldApp(func(appDir string) {
   295  							session := helpers.CF("v3-zdt-push", appName, "-p", appDir)
   296  
   297  							Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   298  							Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
   299  							Eventually(session).Should(Say(`Showing health and status for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   300  							Eventually(session).Should(Say(""))
   301  							Eventually(session).Should(Say(`name:\s+%s`, appName))
   302  							Eventually(session).Should(Say(`requested state:\s+started`))
   303  							Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   304  							Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   305  							Eventually(session).Should(Say(`buildpacks:\s+staticfile`))
   306  							Eventually(session).Should(Say(""))
   307  							Eventually(session).Should(Say(`type:\s+web`))
   308  							Eventually(session).Should(Say(`instances:\s+1/1`))
   309  							Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   310  							Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   311  							Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   312  
   313  							Eventually(session).Should(Exit(0))
   314  						})
   315  					})
   316  				})
   317  
   318  				Context("when the directory is empty", func() {
   319  					var emptyDir string
   320  
   321  					BeforeEach(func() {
   322  						var err error
   323  						emptyDir, err = ioutil.TempDir("", "integration-push-path-empty")
   324  						Expect(err).ToNot(HaveOccurred())
   325  					})
   326  
   327  					AfterEach(func() {
   328  						Expect(os.RemoveAll(emptyDir)).ToNot(HaveOccurred())
   329  					})
   330  
   331  					It("returns an error", func() {
   332  						session := helpers.CF("v3-zdt-push", appName, "-p", emptyDir)
   333  						Eventually(session.Err).Should(Say("No app files found in '%s'", regexp.QuoteMeta(emptyDir)))
   334  						Eventually(session).Should(Exit(1))
   335  					})
   336  				})
   337  			})
   338  
   339  			Context("when the path is a zip file", func() {
   340  				Context("pushing a zip file", func() {
   341  					var archive string
   342  
   343  					BeforeEach(func() {
   344  						helpers.WithHelloWorldApp(func(appDir string) {
   345  							tmpfile, err := ioutil.TempFile("", "push-archive-integration")
   346  							Expect(err).ToNot(HaveOccurred())
   347  							archive = tmpfile.Name()
   348  							Expect(tmpfile.Close())
   349  
   350  							err = helpers.Zipit(appDir, archive, "")
   351  							Expect(err).ToNot(HaveOccurred())
   352  						})
   353  					})
   354  
   355  					AfterEach(func() {
   356  						Expect(os.RemoveAll(archive)).ToNot(HaveOccurred())
   357  					})
   358  
   359  					It("pushes the app from the zip file", func() {
   360  						session := helpers.CF("v3-zdt-push", appName, "-p", archive)
   361  
   362  						Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   363  						Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
   364  						Eventually(session).Should(Say(`Showing health and status for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   365  						Eventually(session).Should(Say(""))
   366  						Eventually(session).Should(Say(`name:\s+%s`, appName))
   367  						Eventually(session).Should(Say(`requested state:\s+started`))
   368  						Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   369  						Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   370  						Eventually(session).Should(Say(`buildpacks:\s+staticfile`))
   371  						Eventually(session).Should(Say(""))
   372  						Eventually(session).Should(Say(`type:\s+web`))
   373  						Eventually(session).Should(Say(`instances:\s+1/1`))
   374  						Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   375  						Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   376  						Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   377  
   378  						Eventually(session).Should(Exit(0))
   379  					})
   380  				})
   381  			})
   382  
   383  			Context("when the path is a symlink to a directory", func() {
   384  				var symlinkPath string
   385  
   386  				BeforeEach(func() {
   387  					tempFile, err := ioutil.TempFile("", "symlink-")
   388  					Expect(err).ToNot(HaveOccurred())
   389  					Expect(tempFile.Close()).To(Succeed())
   390  
   391  					symlinkPath = tempFile.Name()
   392  					Expect(os.Remove(symlinkPath)).To(Succeed())
   393  				})
   394  
   395  				AfterEach(func() {
   396  					Expect(os.Remove(symlinkPath)).To(Succeed())
   397  				})
   398  
   399  				It("creates and uploads the package from the directory", func() {
   400  					helpers.WithHelloWorldApp(func(appDir string) {
   401  						Expect(os.Symlink(appDir, symlinkPath)).To(Succeed())
   402  
   403  						session := helpers.CF("v3-zdt-push", appName, "-p", symlinkPath)
   404  
   405  						Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   406  						Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
   407  						Eventually(session).Should(Say(`Showing health and status for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   408  						Eventually(session).Should(Say(""))
   409  						Eventually(session).Should(Say(`name:\s+%s`, appName))
   410  						Eventually(session).Should(Say(`requested state:\s+started`))
   411  						Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   412  						Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   413  						Eventually(session).Should(Say(`buildpacks:\s+staticfile`))
   414  						Eventually(session).Should(Say(""))
   415  						Eventually(session).Should(Say(`type:\s+web`))
   416  						Eventually(session).Should(Say(`instances:\s+1/1`))
   417  						Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   418  						Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   419  						Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   420  
   421  						Eventually(session).Should(Exit(0))
   422  					})
   423  				})
   424  			})
   425  
   426  			Context("when the path is a symlink to a zip file", func() {
   427  				var (
   428  					archive     string
   429  					symlinkPath string
   430  				)
   431  
   432  				BeforeEach(func() {
   433  					helpers.WithHelloWorldApp(func(appDir string) {
   434  						tmpfile, err := ioutil.TempFile("", "package-archive-integration")
   435  						Expect(err).ToNot(HaveOccurred())
   436  						archive = tmpfile.Name()
   437  						Expect(tmpfile.Close())
   438  
   439  						err = helpers.Zipit(appDir, archive, "")
   440  						Expect(err).ToNot(HaveOccurred())
   441  					})
   442  
   443  					tempFile, err := ioutil.TempFile("", "symlink-to-archive-")
   444  					Expect(err).ToNot(HaveOccurred())
   445  					Expect(tempFile.Close()).To(Succeed())
   446  
   447  					symlinkPath = tempFile.Name()
   448  					Expect(os.Remove(symlinkPath)).To(Succeed())
   449  					Expect(os.Symlink(archive, symlinkPath)).To(Succeed())
   450  				})
   451  
   452  				AfterEach(func() {
   453  					Expect(os.Remove(archive)).To(Succeed())
   454  					Expect(os.Remove(symlinkPath)).To(Succeed())
   455  				})
   456  
   457  				It("creates and uploads the package from the zip file", func() {
   458  					session := helpers.CF("v3-zdt-push", appName, "-p", symlinkPath)
   459  
   460  					Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   461  					Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
   462  					Eventually(session).Should(Say(`Showing health and status for app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
   463  					Eventually(session).Should(Say(""))
   464  					Eventually(session).Should(Say(`name:\s+%s`, appName))
   465  					Eventually(session).Should(Say(`requested state:\s+started`))
   466  					Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   467  					Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   468  					Eventually(session).Should(Say(`buildpacks:\s+staticfile`))
   469  					Eventually(session).Should(Say(""))
   470  					Eventually(session).Should(Say(`type:\s+web`))
   471  					Eventually(session).Should(Say(`instances:\s+1/1`))
   472  					Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   473  					Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   474  					Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   475  
   476  					Eventually(session).Should(Exit(0))
   477  				})
   478  			})
   479  		})
   480  
   481  		Context("when the --no-route flag is set", func() {
   482  			var session *Session
   483  
   484  			BeforeEach(func() {
   485  				helpers.WithHelloWorldApp(func(appDir string) {
   486  					session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "--no-route")
   487  					Eventually(session).Should(Exit(0))
   488  				})
   489  			})
   490  
   491  			It("does not map any routes to the app", func() {
   492  				Consistently(session).ShouldNot(Say(`Mapping routes\.\.\.`))
   493  				Eventually(session).Should(Say(`name:\s+%s`, appName))
   494  				Eventually(session).Should(Say(`requested state:\s+started`))
   495  				Eventually(session).Should(Say(`routes:\s+\n`))
   496  				Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   497  				Eventually(session).Should(Say(`buildpacks:\s+staticfile`))
   498  				Eventually(session).Should(Say(""))
   499  				Eventually(session).Should(Say(`type:\s+web`))
   500  				Eventually(session).Should(Say(`instances:\s+1/1`))
   501  				Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   502  				Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   503  				Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   504  			})
   505  		})
   506  
   507  		Context("when the -s flag is set", func() {
   508  			When("the default stack is specified", func() {
   509  				It("uses the specified stack", func() {
   510  					var session *Session
   511  					helpers.WithHelloWorldApp(func(appDir string) {
   512  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-s", "cflinuxfs2")
   513  						Eventually(session).Should(Exit(0))
   514  					})
   515  					Eventually(session).Should(Say(`name:\s+%s`, appName))
   516  					Eventually(session).Should(Say(`requested state:\s+started`))
   517  					Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   518  					Eventually(session).Should(Say(`stack:\s+cflinuxfs2`))
   519  				})
   520  			})
   521  
   522  			When("a non-default stack is specified", func() {
   523  				It("uses the specified stack", func() {
   524  					var session *Session
   525  					helpers.WithHelloWorldApp(func(appDir string) {
   526  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-s", "cflinuxfs2")
   527  						Eventually(session).Should(Exit(0))
   528  					})
   529  					Eventually(session).Should(Say(`name:\s+%s`, appName))
   530  					Eventually(session).Should(Say(`requested state:\s+started`))
   531  					Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   532  					Eventually(session).Should(Say(`stack:\s+cflinuxfs2`))
   533  				})
   534  			})
   535  
   536  			When("the both -s and -b are specified", func() {
   537  				When("the buildpack and stack both exist", func() {
   538  					When("the buildpack specified exists for the stack specified", func() {
   539  						It("creates the app with the specified stack and buildpack", func() {
   540  							var session *Session
   541  							helpers.WithHelloWorldApp(func(appDir string) {
   542  								session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "https://github.com/cloudfoundry/staticfile-buildpack", "-s", "cflinuxfs2")
   543  								Eventually(session).Should(Exit(0))
   544  							})
   545  							// TODO: assert specific error text when it is written
   546  						})
   547  					})
   548  
   549  					When("the buildpack specified does not exist for the stack specified", func() {
   550  						It("prints the appropriate error", func() {
   551  							var session *Session
   552  							helpers.WithHelloWorldApp(func(appDir string) {
   553  								session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "ruby_buildpack", "-s", "windows2012R2")
   554  								Eventually(session).Should(Exit(1))
   555  							})
   556  							// TODO: assert specific error text when it is written
   557  						})
   558  					})
   559  				})
   560  			})
   561  
   562  			When("the specified stack does not exist", func() {
   563  				It("errors", func() {
   564  					var session *Session
   565  					helpers.WithHelloWorldApp(func(appDir string) {
   566  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-s", "invalid_stack")
   567  						Eventually(session).Should(Exit(1))
   568  					})
   569  					Eventually(session).Should(Say("FAILED"))
   570  					// TODO: confirm the text of the error message
   571  					Eventually(session.Err).Should(Say(`Stack must be an existing stack`))
   572  				})
   573  			})
   574  		})
   575  
   576  		Context("when the -b flag is set", func() {
   577  			var session *Session
   578  
   579  			Context("when pushing a multi-buildpack app", func() {
   580  				BeforeEach(func() {
   581  					helpers.WithMultiBuildpackApp(func(appDir string) {
   582  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "ruby_buildpack", "-b", "go_buildpack")
   583  
   584  						// TODO: uncomment this expectation once capi-release displays all buildpacks on droplet
   585  						// Story: https://www.pivotaltracker.com/story/show/150425459
   586  						// Eventually(session).Should(Say("buildpacks:.*ruby_buildpack, go_buildpack"))
   587  
   588  						Eventually(session).Should(Exit(0))
   589  					})
   590  				})
   591  
   592  				It("successfully compiles and runs the app", func() {
   593  					resp, err := http.Get(fmt.Sprintf("http://%s.%s", appName, helpers.DefaultSharedDomain()))
   594  					Expect(err).ToNot(HaveOccurred())
   595  					Expect(resp.StatusCode).To(Equal(http.StatusOK))
   596  				})
   597  			})
   598  
   599  			Context("when resetting the buildpack to default", func() {
   600  				BeforeEach(func() {
   601  					helpers.WithHelloWorldApp(func(appDir string) {
   602  						Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "java_buildpack")).Should(Exit(1))
   603  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "default")
   604  						Eventually(session).Should(Exit(0))
   605  					})
   606  				})
   607  
   608  				It("successfully pushes the app", func() {
   609  					Eventually(session).Should(Say(`name:\s+%s`, appName))
   610  					Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   611  					Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   612  				})
   613  			})
   614  
   615  			Context("when omitting the buildpack", func() {
   616  				BeforeEach(func() {
   617  					helpers.WithHelloWorldApp(func(appDir string) {
   618  						Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "java_buildpack")).Should(Exit(1))
   619  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName)
   620  						Eventually(session).Should(Exit(1))
   621  					})
   622  				})
   623  
   624  				It("continues using previously set buildpack", func() {
   625  					Eventually(session).Should(Say("FAILED"))
   626  				})
   627  			})
   628  
   629  			Context("when the buildpack is invalid", func() {
   630  				BeforeEach(func() {
   631  					helpers.WithHelloWorldApp(func(appDir string) {
   632  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "wut")
   633  						Eventually(session).Should(Exit(1))
   634  					})
   635  				})
   636  
   637  				It("errors and does not push the app", func() {
   638  					Consistently(session).ShouldNot(Say("Creating app"))
   639  					Eventually(session).Should(Say("FAILED"))
   640  					Eventually(session.Err).Should(Say(`Buildpack "wut" must be an existing admin buildpack or a valid git URI`))
   641  				})
   642  			})
   643  
   644  			Context("when the buildpack is valid", func() {
   645  				BeforeEach(func() {
   646  					helpers.WithHelloWorldApp(func(appDir string) {
   647  						session = helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-zdt-push", appName, "-b", "https://github.com/cloudfoundry/staticfile-buildpack")
   648  						Eventually(session).Should(Exit(0))
   649  					})
   650  				})
   651  
   652  				It("uses the specified buildpack", func() {
   653  					Eventually(session).Should(Say(`name:\s+%s`, appName))
   654  					Eventually(session).Should(Say(`requested state:\s+started`))
   655  					Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   656  					Eventually(session).Should(Say(`stack:\s+cflinuxfs`))
   657  
   658  					// TODO: Uncomment when capi sorts out droplet buildpack name/detectoutput
   659  					// Eventually(session).Should(Say(`buildpacks:\s+https://github.com/cloudfoundry/staticfile-buildpack`))
   660  					Eventually(session).Should(Say(""))
   661  					Eventually(session).Should(Say(`type:\s+web`))
   662  					Eventually(session).Should(Say(`instances:\s+1/1`))
   663  					Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   664  					Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   665  					Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   666  				})
   667  			})
   668  		})
   669  
   670  		Context("when the -o flag is set", func() {
   671  			Context("when the docker image is valid", func() {
   672  				It("uses the specified docker image", func() {
   673  					session := helpers.CF("v3-zdt-push", appName, "-o", PublicDockerImage)
   674  
   675  					Eventually(session).Should(Say(`name:\s+%s`, appName))
   676  					Eventually(session).Should(Say(`requested state:\s+started`))
   677  					Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   678  					Eventually(session).Should(Say("stack:"))
   679  					Eventually(session).ShouldNot(Say("buildpacks:"))
   680  					Eventually(session).Should(Say(`docker image:\s+%s`, PublicDockerImage))
   681  					Eventually(session).Should(Say(""))
   682  					Eventually(session).Should(Say(`type:\s+web`))
   683  					Eventually(session).Should(Say(`instances:\s+1/1`))
   684  					Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   685  					Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   686  					Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   687  					Eventually(session).Should(Exit(0))
   688  				})
   689  			})
   690  
   691  			Context("when the docker image is invalid", func() {
   692  				It("displays an error and exits 1", func() {
   693  					session := helpers.CF("v3-zdt-push", appName, "-o", "some-invalid-docker-image")
   694  					Eventually(session).Should(Say("FAILED"))
   695  					Eventually(session.Err).Should(Say("StagingError - Staging error: staging failed"))
   696  					Eventually(session).Should(Exit(1))
   697  				})
   698  			})
   699  
   700  			Context("when a docker username and password are provided with a private image", func() {
   701  				var (
   702  					privateDockerImage    string
   703  					privateDockerUsername string
   704  					privateDockerPassword string
   705  				)
   706  
   707  				BeforeEach(func() {
   708  					privateDockerImage = os.Getenv("CF_INT_DOCKER_IMAGE")
   709  					privateDockerUsername = os.Getenv("CF_INT_DOCKER_USERNAME")
   710  					privateDockerPassword = os.Getenv("CF_INT_DOCKER_PASSWORD")
   711  
   712  					if privateDockerImage == "" || privateDockerUsername == "" || privateDockerPassword == "" {
   713  						Skip("CF_INT_DOCKER_IMAGE, CF_INT_DOCKER_USERNAME, or CF_INT_DOCKER_PASSWORD is not set")
   714  					}
   715  				})
   716  
   717  				It("uses the specified private docker image", func() {
   718  					session := helpers.CustomCF(
   719  						helpers.CFEnv{
   720  							EnvVars: map[string]string{"CF_DOCKER_PASSWORD": privateDockerPassword},
   721  						},
   722  						"v3-zdt-push", "--docker-username", privateDockerUsername, "--docker-image", privateDockerImage, appName,
   723  					)
   724  
   725  					Eventually(session).Should(Say(`name:\s+%s`, appName))
   726  					Eventually(session).Should(Say(`requested state:\s+started`))
   727  					Eventually(session).Should(Say(`routes:\s+%s\.%s`, appName, domainName))
   728  					Eventually(session).Should(Say("stack:"))
   729  					Eventually(session).ShouldNot(Say("buildpacks:"))
   730  					Eventually(session).Should(Say(`docker image:\s+%s`, privateDockerImage))
   731  					Eventually(session).Should(Say(""))
   732  					Eventually(session).Should(Say(`type:\s+web`))
   733  					Eventually(session).Should(Say(`instances:\s+1/1`))
   734  					Eventually(session).Should(Say(`memory usage:\s+\d+(M|G)`))
   735  					Eventually(session).Should(Say(`state\s+since\s+cpu\s+memory\s+disk`))
   736  					Eventually(session).Should(Say(`#0\s+running\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [AP]M`))
   737  					Eventually(session).Should(Exit(0))
   738  				})
   739  			})
   740  		})
   741  
   742  		Describe("argument combination errors", func() {
   743  			Context("when the --docker-username is provided without the -o flag", func() {
   744  				It("displays an error and exits 1", func() {
   745  					helpers.WithHelloWorldApp(func(appDir string) {
   746  						session := helpers.CF("v3-zdt-push", appName, "--docker-username", "some-username")
   747  						Eventually(session).Should(Say("FAILED"))
   748  						Eventually(session.Err).Should(Say("Incorrect Usage: '--docker-image, -o' and '--docker-username' must be used together."))
   749  						Eventually(session).Should(Say("NAME:"))
   750  						Eventually(session).Should(Exit(1))
   751  					})
   752  				})
   753  			})
   754  
   755  			Context("when the --docker-username and -p flags are provided together", func() {
   756  				It("displays an error and exits 1", func() {
   757  					helpers.WithHelloWorldApp(func(appDir string) {
   758  						session := helpers.CF("v3-zdt-push", appName, "--docker-username", "some-username", "-p", appDir)
   759  						Eventually(session).Should(Say("FAILED"))
   760  						Eventually(session.Err).Should(Say("Incorrect Usage: '--docker-image, -o' and '--docker-username' must be used together."))
   761  						Eventually(session).Should(Say("NAME:"))
   762  						Eventually(session).Should(Exit(1))
   763  					})
   764  				})
   765  			})
   766  
   767  			Context("when the --docker-username is provided without a password", func() {
   768  				var oldPassword string
   769  
   770  				BeforeEach(func() {
   771  					oldPassword = os.Getenv("CF_DOCKER_PASSWORD")
   772  					err := os.Unsetenv("CF_DOCKER_PASSWORD")
   773  					Expect(err).ToNot(HaveOccurred())
   774  				})
   775  
   776  				AfterEach(func() {
   777  					err := os.Setenv("CF_DOCKER_PASSWORD", oldPassword)
   778  					Expect(err).ToNot(HaveOccurred())
   779  				})
   780  
   781  				It("displays an error and exits 1", func() {
   782  					helpers.WithHelloWorldApp(func(appDir string) {
   783  						session := helpers.CF("v3-zdt-push", appName, "--docker-username", "some-username", "--docker-image", "some-image")
   784  						Eventually(session).Should(Say("FAILED"))
   785  						Eventually(session.Err).Should(Say(`Environment variable CF_DOCKER_PASSWORD not set\.`))
   786  						Eventually(session).Should(Exit(1))
   787  					})
   788  				})
   789  			})
   790  
   791  			Context("when the -o and -p flags are provided together", func() {
   792  				It("displays an error and exits 1", func() {
   793  					helpers.WithHelloWorldApp(func(appDir string) {
   794  						session := helpers.CF("v3-zdt-push", appName, "-o", PublicDockerImage, "-p", appDir)
   795  						Eventually(session).Should(Say("FAILED"))
   796  						Eventually(session.Err).Should(Say("Incorrect Usage: The following arguments cannot be used together: --docker-image, -o, -p"))
   797  						Eventually(session).Should(Say("NAME:"))
   798  						Eventually(session).Should(Exit(1))
   799  					})
   800  				})
   801  			})
   802  
   803  			Context("when the -o and -b flags are provided together", func() {
   804  				It("displays an error and exits 1", func() {
   805  					helpers.WithHelloWorldApp(func(appDir string) {
   806  						session := helpers.CF("v3-zdt-push", appName, "-o", PublicDockerImage, "-b", "some-buildpack")
   807  						Eventually(session).Should(Say("FAILED"))
   808  						Eventually(session.Err).Should(Say("Incorrect Usage: The following arguments cannot be used together: -b, --docker-image, -o"))
   809  						Eventually(session).Should(Say("NAME:"))
   810  						Eventually(session).Should(Exit(1))
   811  					})
   812  				})
   813  			})
   814  		})
   815  	})
   816  })