github.com/LukasHeimann/cloudfoundrycli/v8@v8.4.4/integration/v7/isolated/bind_service_command_test.go (about)

     1  package isolated
     2  
     3  import (
     4  	"os"
     5  	"time"
     6  
     7  	"github.com/LukasHeimann/cloudfoundrycli/v8/integration/helpers"
     8  	"github.com/LukasHeimann/cloudfoundrycli/v8/integration/helpers/servicebrokerstub"
     9  	"github.com/LukasHeimann/cloudfoundrycli/v8/resources"
    10  	. "github.com/onsi/ginkgo"
    11  	. "github.com/onsi/gomega"
    12  	. "github.com/onsi/gomega/gbytes"
    13  	. "github.com/onsi/gomega/gexec"
    14  )
    15  
    16  var _ = Describe("bind-service command", func() {
    17  	const command = "bind-service"
    18  
    19  	Describe("help", func() {
    20  		matchHelpMessage := SatisfyAll(
    21  			Say(`NAME:\n`),
    22  			Say(`\s+bind-service - Bind a service instance to an app\n`),
    23  			Say(`\n`),
    24  			Say(`USAGE:\n`),
    25  			Say(`\s+cf bind-service APP_NAME SERVICE_INSTANCE \[-c PARAMETERS_AS_JSON\] \[--binding-name BINDING_NAME\]\n`),
    26  			Say(`\n`),
    27  			Say(`\s+Optionally provide service-specific configuration parameters in a valid JSON object in-line:\n`),
    28  			Say(`\n`),
    29  			Say(`\s+cf bind-service APP_NAME SERVICE_INSTANCE -c '\{"name":"value","name":"value"\}'\n`),
    30  			Say(`\n`),
    31  			Say(`\s+Optionally provide a file containing service-specific configuration parameters in a valid JSON object.\n`),
    32  			Say(`\s+The path to the parameters file can be an absolute or relative path to a file.\n`),
    33  			Say(`\s+cf bind-service APP_NAME SERVICE_INSTANCE -c PATH_TO_FILE\n`),
    34  			Say(`\n`),
    35  			Say(`\s+Example of valid JSON object:\n`),
    36  			Say(`\s+{\n`),
    37  			Say(`\s+"permissions": "read-only"\n`),
    38  			Say(`\s+}\n`),
    39  			Say(`\n`),
    40  			Say(`\s+Optionally provide a binding name for the association between an app and a service instance:\n`),
    41  			Say(`\n`),
    42  			Say(`\s+cf bind-service APP_NAME SERVICE_INSTANCE --binding-name BINDING_NAME\n`),
    43  			Say(`\n`),
    44  			Say(`EXAMPLES:\n`),
    45  			Say(`\s+Linux/Mac:\n`),
    46  			Say(`\s+cf bind-service myapp mydb -c '\{"permissions":"read-only"\}'\n`),
    47  			Say(`\n`),
    48  			Say(`\s+Windows Command Line:\n`),
    49  			Say(`\s+cf bind-service myapp mydb -c "\{\\"permissions\\":\\"read-only\\"\}"\n`),
    50  			Say(`\n`),
    51  			Say(`\s+Windows PowerShell:\n`),
    52  			Say(`\s+cf bind-service myapp mydb -c '\{\\"permissions\\":\\"read-only\\"\}'\n`),
    53  			Say(`\n`),
    54  			Say(`\s+cf bind-service myapp mydb -c ~/workspace/tmp/instance_config.json --binding-name BINDING_NAME\n`),
    55  			Say(`\n`),
    56  			Say(`ALIAS:\n`),
    57  			Say(`\s+bs\n`),
    58  			Say(`\n`),
    59  			Say(`OPTIONS:\n`),
    60  			Say(`\s+--binding-name\s+Name to expose service instance to app process with \(Default: service instance name\)\n`),
    61  			Say(`\s+-c\s+Valid JSON object containing service-specific configuration parameters, provided either in-line or in a file. For a list of supported configuration parameters, see documentation for the particular service offering.\n`),
    62  			Say(`\s+--wait, -w\s+Wait for the operation to complete\n`),
    63  			Say(`\n`),
    64  			Say(`SEE ALSO:\n`),
    65  			Say(`\s+services\n`),
    66  		)
    67  
    68  		When("the -h flag is specified", func() {
    69  			It("succeeds and prints help", func() {
    70  				session := helpers.CF(command, "-h")
    71  				Eventually(session).Should(Exit(0))
    72  				Expect(session.Out).To(matchHelpMessage)
    73  			})
    74  		})
    75  
    76  		When("the --help flag is specified", func() {
    77  			It("succeeds and prints help", func() {
    78  				session := helpers.CF(command, "--help")
    79  				Eventually(session).Should(Exit(0))
    80  				Expect(session.Out).To(matchHelpMessage)
    81  			})
    82  		})
    83  
    84  		When("no arguments are provided", func() {
    85  			It("displays a warning, the help text, and exits 1", func() {
    86  				session := helpers.CF(command)
    87  				Eventually(session).Should(Exit(1))
    88  				Expect(session.Err).To(Say("Incorrect Usage: the required arguments `APP_NAME` and `SERVICE_INSTANCE` were not provided"))
    89  				Expect(session.Out).To(matchHelpMessage)
    90  			})
    91  		})
    92  
    93  		When("unknown flag is passed", func() {
    94  			It("displays a warning, the help text, and exits 1", func() {
    95  				session := helpers.CF(command, "-u")
    96  				Eventually(session).Should(Exit(1))
    97  				Expect(session.Err).To(Say("Incorrect Usage: unknown flag `u"))
    98  				Expect(session.Out).To(matchHelpMessage)
    99  			})
   100  		})
   101  
   102  		When("-c is provided with invalid JSON", func() {
   103  			It("displays a warning, the help text, and exits 1", func() {
   104  				session := helpers.CF(command, "-c", `{"not":json"}`)
   105  				Eventually(session).Should(Exit(1))
   106  				Expect(session.Err).To(Say("Incorrect Usage: Invalid configuration provided for -c flag. Please provide a valid JSON object or path to a file containing a valid JSON object."))
   107  				Expect(session.Out).To(matchHelpMessage)
   108  			})
   109  		})
   110  
   111  		When("-c is provided with invalid JSON file", func() {
   112  			It("displays a warning, the help text, and exits 1", func() {
   113  				filename := helpers.TempFileWithContent(`{"not":json"}`)
   114  				defer os.Remove(filename)
   115  
   116  				session := helpers.CF(command, "-c", filename)
   117  				Eventually(session).Should(Exit(1))
   118  				Expect(session.Err).To(Say("Incorrect Usage: Invalid configuration provided for -c flag. Please provide a valid JSON object or path to a file containing a valid JSON object."))
   119  				Expect(session.Out).To(matchHelpMessage)
   120  			})
   121  		})
   122  
   123  		When("--binding-name is provided with empty value", func() {
   124  			It("displays a warning, the help text, and exits 1", func() {
   125  				session := helpers.CF(command, "appName", "serviceInstanceName", "--binding-name", "")
   126  				Eventually(session).Should(Exit(1))
   127  				Expect(session.Err).To(Say("Incorrect Usage: --binding-name must be at least 1 character in length"))
   128  				Expect(session.Out).To(matchHelpMessage)
   129  			})
   130  		})
   131  	})
   132  
   133  	When("the environment is not setup correctly", func() {
   134  		It("fails with the appropriate errors", func() {
   135  			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "bind-service", "app-name", "service-name")
   136  		})
   137  	})
   138  
   139  	When("targeting a space", func() {
   140  		var (
   141  			orgName   string
   142  			spaceName string
   143  			username  string
   144  		)
   145  
   146  		getBinding := func(serviceInstanceName string) resources.ServiceCredentialBinding {
   147  			var receiver struct {
   148  				Resources []resources.ServiceCredentialBinding `json:"resources"`
   149  			}
   150  			helpers.Curl(&receiver, "/v3/service_credential_bindings?service_instance_names=%s", serviceInstanceName)
   151  			Expect(receiver.Resources).To(HaveLen(1))
   152  			return receiver.Resources[0]
   153  		}
   154  
   155  		getParameters := func(serviceInstanceName string) (receiver map[string]interface{}) {
   156  			binding := getBinding(serviceInstanceName)
   157  			helpers.Curl(&receiver, "/v3/service_credential_bindings/%s/parameters", binding.GUID)
   158  			return
   159  		}
   160  
   161  		BeforeEach(func() {
   162  			orgName = helpers.NewOrgName()
   163  			spaceName = helpers.NewSpaceName()
   164  			helpers.SetupCF(orgName, spaceName)
   165  
   166  			username, _ = helpers.GetCredentials()
   167  		})
   168  
   169  		AfterEach(func() {
   170  			helpers.QuickDeleteOrg(orgName)
   171  		})
   172  
   173  		Context("user-provided route service", func() {
   174  			var (
   175  				serviceInstanceName string
   176  				appName             string
   177  				bindingName         string
   178  			)
   179  
   180  			BeforeEach(func() {
   181  				serviceInstanceName = helpers.NewServiceInstanceName()
   182  				Eventually(helpers.CF("cups", serviceInstanceName)).Should(Exit(0))
   183  
   184  				appName = helpers.NewAppName()
   185  				helpers.WithHelloWorldApp(func(appDir string) {
   186  					Eventually(helpers.CF("push", appName, "--no-start", "-p", appDir, "-b", "staticfile_buildpack", "--no-route")).Should(Exit(0))
   187  				})
   188  
   189  				bindingName = helpers.RandomName()
   190  			})
   191  
   192  			It("creates a binding", func() {
   193  				session := helpers.CF(command, appName, serviceInstanceName, "--binding-name", bindingName)
   194  				Eventually(session).Should(Exit(0))
   195  
   196  				Expect(session.Out).To(SatisfyAll(
   197  					Say(`Binding service instance %s to app %s in org %s / space %s as %s\.\.\.\n`, serviceInstanceName, appName, orgName, spaceName, username),
   198  					Say(`OK\n`),
   199  					Say(`\n`),
   200  					Say(`TIP: Use 'cf restage %s' to ensure your env variable changes take effect`, appName),
   201  				))
   202  
   203  				Expect(string(session.Err.Contents())).To(BeEmpty())
   204  
   205  				binding := getBinding(serviceInstanceName)
   206  				Expect(binding.Name).To(Equal(bindingName))
   207  				Expect(binding.LastOperation.State).To(BeEquivalentTo("succeeded"))
   208  			})
   209  
   210  			When("parameters are specified", func() {
   211  				It("fails with an error returned by the CC", func() {
   212  					session := helpers.CF(command, appName, serviceInstanceName, "-c", `{"foo":"bar"}`)
   213  					Eventually(session).Should(Exit(1))
   214  
   215  					Expect(session.Out).To(SatisfyAll(
   216  						Say(`Binding service instance %s to app %s in org %s / space %s as %s\.\.\.\n`, serviceInstanceName, appName, orgName, spaceName, username),
   217  						Say(`FAILED\n`),
   218  					))
   219  
   220  					Expect(session.Err).To(Say(`Binding parameters are not supported for user-provided service instances\n`))
   221  				})
   222  			})
   223  		})
   224  
   225  		Context("managed service instance with synchronous broker response", func() {
   226  			var (
   227  				broker              *servicebrokerstub.ServiceBrokerStub
   228  				serviceInstanceName string
   229  				appName             string
   230  				bindingName         string
   231  			)
   232  
   233  			BeforeEach(func() {
   234  				broker = servicebrokerstub.EnableServiceAccess()
   235  				serviceInstanceName = helpers.NewServiceInstanceName()
   236  				helpers.CreateManagedServiceInstance(broker.FirstServiceOfferingName(), broker.FirstServicePlanName(), serviceInstanceName)
   237  
   238  				appName = helpers.NewAppName()
   239  				helpers.WithHelloWorldApp(func(appDir string) {
   240  					Eventually(helpers.CF("push", appName, "--no-start", "-p", appDir, "-b", "staticfile_buildpack", "--no-route")).Should(Exit(0))
   241  				})
   242  
   243  				bindingName = helpers.RandomName()
   244  			})
   245  
   246  			AfterEach(func() {
   247  				broker.Forget()
   248  			})
   249  
   250  			It("creates a binding", func() {
   251  				session := helpers.CF(command, appName, serviceInstanceName, "--binding-name", bindingName)
   252  				Eventually(session).Should(Exit(0))
   253  
   254  				Expect(session.Out).To(SatisfyAll(
   255  					Say(`Binding service instance %s to app %s in org %s / space %s as %s\.\.\.\n`, serviceInstanceName, appName, orgName, spaceName, username),
   256  					Say(`OK\n`),
   257  					Say(`\n`),
   258  					Say(`TIP: Use 'cf restage %s' to ensure your env variable changes take effect`, appName),
   259  				))
   260  
   261  				Expect(string(session.Err.Contents())).To(BeEmpty())
   262  
   263  				binding := getBinding(serviceInstanceName)
   264  				Expect(binding.Name).To(Equal(bindingName))
   265  				Expect(binding.LastOperation.State).To(BeEquivalentTo("succeeded"))
   266  			})
   267  
   268  			When("parameters are specified", func() {
   269  				It("sends the parameters to the broker", func() {
   270  					session := helpers.CF(command, appName, serviceInstanceName, "-c", `{"foo":"bar"}`)
   271  					Eventually(session).Should(Exit(0))
   272  
   273  					Expect(getParameters(serviceInstanceName)).To(Equal(map[string]interface{}{"foo": "bar"}))
   274  				})
   275  			})
   276  		})
   277  
   278  		Context("managed service instance with asynchronous broker response", func() {
   279  			var (
   280  				broker              *servicebrokerstub.ServiceBrokerStub
   281  				serviceInstanceName string
   282  				appName             string
   283  				bindingName         string
   284  			)
   285  
   286  			BeforeEach(func() {
   287  				broker = servicebrokerstub.New().WithAsyncDelay(time.Second).EnableServiceAccess()
   288  				serviceInstanceName = helpers.NewServiceInstanceName()
   289  				helpers.CreateManagedServiceInstance(broker.FirstServiceOfferingName(), broker.FirstServicePlanName(), serviceInstanceName)
   290  
   291  				appName = helpers.NewAppName()
   292  				helpers.WithHelloWorldApp(func(appDir string) {
   293  					Eventually(helpers.CF("push", appName, "--no-start", "-p", appDir, "-b", "staticfile_buildpack", "--no-route")).Should(Exit(0))
   294  				})
   295  
   296  				bindingName = helpers.RandomName()
   297  			})
   298  
   299  			AfterEach(func() {
   300  				broker.Forget()
   301  			})
   302  
   303  			It("start to create a binding", func() {
   304  				session := helpers.CF(command, appName, serviceInstanceName, "--binding-name", bindingName)
   305  				Eventually(session).Should(Exit(0))
   306  
   307  				Expect(session.Out).To(SatisfyAll(
   308  					Say(`Binding service instance %s to app %s in org %s / space %s as %s\.\.\.\n`, serviceInstanceName, appName, orgName, spaceName, username),
   309  					Say(`OK\n`),
   310  					Say(`\n`),
   311  					Say(`Binding in progress. Use 'cf service %s' to check operation status\.\n`, serviceInstanceName),
   312  					Say(`\n`),
   313  					Say(`TIP: Once this operation succeeds, use 'cf restage %s' to ensure your env variable changes take effect`, appName),
   314  				))
   315  
   316  				Expect(string(session.Err.Contents())).To(BeEmpty())
   317  
   318  				binding := getBinding(serviceInstanceName)
   319  				Expect(binding.Name).To(Equal(bindingName))
   320  				Expect(binding.LastOperation.State).To(BeEquivalentTo("in progress"))
   321  			})
   322  
   323  			When("--wait flag specified", func() {
   324  				It("waits for completion", func() {
   325  					session := helpers.CF(command, appName, serviceInstanceName, "--binding-name", bindingName, "--wait")
   326  					Eventually(session).Should(Exit(0))
   327  
   328  					Expect(session.Out).To(SatisfyAll(
   329  						Say(`Binding service instance %s to app %s in org %s / space %s as %s\.\.\.\n`, serviceInstanceName, appName, orgName, spaceName, username),
   330  						Say(`Waiting for the operation to complete\.+\n`),
   331  						Say(`\n`),
   332  						Say(`OK\n`),
   333  					))
   334  
   335  					Expect(string(session.Err.Contents())).To(BeEmpty())
   336  
   337  					Expect(getBinding(serviceInstanceName).LastOperation.State).To(BeEquivalentTo("succeeded"))
   338  				})
   339  			})
   340  		})
   341  
   342  		Context("binding already exists", func() {
   343  			var (
   344  				serviceInstanceName string
   345  				appName             string
   346  			)
   347  
   348  			BeforeEach(func() {
   349  				serviceInstanceName = helpers.NewServiceInstanceName()
   350  				Eventually(helpers.CF("cups", serviceInstanceName)).Should(Exit(0))
   351  
   352  				appName = helpers.NewAppName()
   353  				helpers.WithHelloWorldApp(func(appDir string) {
   354  					Eventually(helpers.CF("push", appName, "--no-start", "-p", appDir, "-b", "staticfile_buildpack", "--no-route")).Should(Exit(0))
   355  				})
   356  
   357  				Eventually(helpers.CF(command, appName, serviceInstanceName)).Should(Exit(0))
   358  			})
   359  
   360  			It("says OK", func() {
   361  				session := helpers.CF(command, appName, serviceInstanceName)
   362  				Eventually(session).Should(Exit(0))
   363  
   364  				Expect(session.Out).To(SatisfyAll(
   365  					Say(`Binding service instance %s to app %s in org %s / space %s as %s\.\.\.\n`, serviceInstanceName, appName, orgName, spaceName, username),
   366  					Say(`App %s is already bound to service instance %s.\n`, appName, serviceInstanceName),
   367  					Say(`OK\n`),
   368  				))
   369  
   370  				Expect(string(session.Err.Contents())).To(BeEmpty())
   371  			})
   372  		})
   373  
   374  		Context("app does not exist", func() {
   375  			var serviceInstanceName string
   376  
   377  			BeforeEach(func() {
   378  				serviceInstanceName = helpers.NewServiceInstanceName()
   379  				Eventually(helpers.CF("cups", serviceInstanceName)).Should(Exit(0))
   380  			})
   381  
   382  			It("displays FAILED and app not found", func() {
   383  				session := helpers.CF(command, "does-not-exist", serviceInstanceName)
   384  				Eventually(session).Should(Exit(1))
   385  				Expect(session.Out).To(Say("FAILED"))
   386  				Expect(session.Err).To(Say("App 'does-not-exist' not found"))
   387  			})
   388  		})
   389  
   390  		Context("service instance does not exist", func() {
   391  			var appName string
   392  
   393  			BeforeEach(func() {
   394  				appName = helpers.NewAppName()
   395  				helpers.WithHelloWorldApp(func(appDir string) {
   396  					Eventually(helpers.CF("push", appName, "--no-start", "-p", appDir, "-b", "staticfile_buildpack", "--no-route")).Should(Exit(0))
   397  				})
   398  			})
   399  
   400  			It("displays FAILED and service not found", func() {
   401  				session := helpers.CF(command, appName, "does-not-exist")
   402  				Eventually(session).Should(Exit(1))
   403  				Expect(session.Out).To(Say("FAILED"))
   404  				Expect(session.Err).To(Say("Service instance 'does-not-exist' not found"))
   405  			})
   406  		})
   407  	})
   408  })