Skip to content

Commit

Permalink
odo add binding - Bind as files UI update
Browse files Browse the repository at this point in the history
Signed-off-by: Parthvi Vala <[email protected]>
  • Loading branch information
valaparthvi committed Jun 13, 2022
1 parent e3b3b8e commit 271bd9d
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,50 @@
title: odo add binding
---

## Description
The `odo add binding` command can add a link between an operator-backed service and a component. odo uses the Service Binding Operator to create this link. Running this command will make the necessary changes to the Devfile, and once pushed to the cluster, it creates an instance of the `ServiceBinding` resource.

Currently, it only allows connecting to the operator-backed services which support binding via the Service Binding Operator.
To know about the operators supported by the Service Binding Operator, read their [README](https://github.com/redhat-developer/service-binding-operator#known-bindable-operators).

## Pre-requisites
## Running the Command

### Pre-requisites
* A directory containing a Devfile; if you don't have one, see [odo init](init.md) on obtaining a devfile.
* A cluster with the Service Binding operator installed, along with the operator whose service you need to bind to

## Interactive Mode
### Interactive Mode
In the interactive mode, you will be guided to choose:
* a service from the list of bindable service instances as supported by the Service Binding Operator,
* option to bind the service as a file,
* option to bind the service as a file; see [Understanding Bind as Files](#understanding-bind-as-files) for more information on this.
* a name for the binding.

```shell
# Add binding between service named 'myservice',
# and the component present in the working directory in the interactive mode
# Add binding between a service, and the component present in the working directory in the interactive mode
odo add binding
```

## Non-interactive mode
### Non-interactive mode
In the non-interactive mode, you will have to specify the following required information through the command-line:
* `--service` flag to specify the service you want to bind to,
* `--name` flag to specify a name for the binding,
* `--name` flag to specify a name for the binding; see [Understanding Bind as Files](#understanding-bind-as-files) for more information on this.
* `--bind-as-files` flag to specify if the service should be bound as a file; this flag is set to true by default.


```shell
# Add binding between a service named 'myservice',
# Add binding between a service named 'cluster-sample',
# and the component present in the working directory in the non-interactive mode
odo add binding --name mybinding --service myRedisService.Redis
```

### Formats supported by the `--service` flag
#### Understanding Bind as Files
To connect your component with a service, you need to store some data(e.g. username, password, host address) on your component's container.
If the service is bound as files, this data will be written to a file and stored on the container, else it will be exported to Environment Variables inside the container.

Note that every piece of data is stored in its own individual file or environment variable.
For example, if your data includes a username, and password, then 2 separate files, or 2 environment variables will be created to store them both.

#### Formats supported by the `--service` flag
The `--service` flag supports the following formats to specify the service name:
* `<name>`
* `<name>.<kind>`
Expand All @@ -46,19 +55,18 @@ The `--service` flag supports the following formats to specify the service name:

The above formats are helpful when multiple services with the same name exist on the cluster.

#### Examples -
### Using different formats
```shell
# Add binding between a service named 'myservice',
# and the component present in the working directory
odo add binding --service myservice --name myRedisService
# Add binding between a service named 'cluster-sample', and the component present in the working directory
odo add binding --service cluster-sample --name restapi-cluster-sample

# Add binding between service named 'myservice' of kind 'Redis', and APIGroup 'redis.redis.opstreelab.in',
# Add binding between service named 'cluster-sample' of kind 'Cluster', and APIGroup 'postgresql.k8s.enterprisedb.io',
# and the component present in the working directory
odo add binding --service myservice/Redis.redis.redis.opstreelab.in --name myRedisService
odo add binding --service myservice.Redis.redis.redis.opstreelab.in --name myRedisService
odo add binding --service cluster-sample/Cluster.postgresql.k8s.enterprisedb.io --name restapi-cluster-sample
odo add binding --service cluster-sample.Cluster.postgresql.k8s.enterprisedb.io --name restapi-cluster-sample

# Add binding between service named 'myservice' of kind 'Redis',
# Add binding between service named 'cluster-sample' of kind 'Cluster',
# and the component present in the working directory
odo add binding --service myservice/Redis --name myRedisService
odo add binding --service myservice.Redis --name myRedisService
```
odo add binding --service cluster-sample/Cluster --name restapi-cluster-sample
odo add binding --service cluster-sample.Cluster --name restapi-cluster-sample
```
17 changes: 11 additions & 6 deletions pkg/binding/asker/survey_asker.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,19 @@ func (s *Survey) AskServiceBindingName(defaultName string) (string, error) {
}

func (o *Survey) AskBindAsFiles() (bool, error) {
question := &survey.Confirm{
Message: "Bind as files?",
Default: true,
question := &survey.Select{
Message: "How do you want to bind the service?",
Options: []string{"Bind as Files", "Bind as Environment Variables"},
}
var answer bool
var answer string
err := survey.AskOne(question, &answer)
if err != nil {
return false, err
return true, err
}
return answer, nil

var bindAsFiles bool
if answer == "Bind as Files" {
bindAsFiles = true
}
return bindAsFiles, nil
}
4 changes: 2 additions & 2 deletions pkg/odo/cli/add/binding/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
const BindingRecommendedCommandName = "binding"

var addBindingExample = ktemplates.Examples(`
# Add binding between service named 'myservice' and the component present in the working directory in the interactive mode
# Add binding between a service, and the component present in the working directory in the interactive mode
%[1]s
# Add binding between service named 'myservice' and the component present in the working directory
Expand Down Expand Up @@ -117,7 +117,7 @@ func (o *AddBindingOptions) Run(_ context.Context) error {
kindGroup := strings.ReplaceAll(strings.ReplaceAll(splitService[1], "(", ""), ")", "")
exitMessage += fmt.Sprintf("\nYou can automate this command by executing:\n odo add binding --service %s.%s --name %s", serviceName, kindGroup, bindingName)
if !bindAsFiles {
exitMessage += " --bind-as-files false"
exitMessage += " --bind-as-files=false"
}
}
log.Infof(exitMessage)
Expand Down
1 change: 1 addition & 0 deletions tests/integration/cmd_namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var _ = Describe("odo create/delete/list/set namespace/project tests", func() {
Describe("create "+commandName, func() {
namespace := fmt.Sprintf("%s-%s", helper.RandString(4), commandName)

// TODO: Remove for loop; find a way to use DescribeTable
It(fmt.Sprintf("should successfully create the %s", commandName), func() {
helper.Cmd("odo", "create", commandName, namespace, "--wait").ShouldPass()
defer func(ns string) {
Expand Down
101 changes: 101 additions & 0 deletions tests/interactive/cmd_add_binding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
// +build linux darwin dragonfly solaris openbsd netbsd freebsd

package interactive

import (
"fmt"
"os"
"path/filepath"

"github.com/redhat-developer/odo/tests/helper"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("odo init interactive command tests", func() {

var commonVar helper.CommonVar
var serviceName string

// This is run before every Spec (It)
var _ = BeforeEach(func() {
commonVar = helper.CommonBeforeEach()
helper.Chdir(commonVar.Context)

// We make EXPLICITLY sure that we are outputting with NO COLOR
// this is because in some cases we are comparing the output with a colorized one
os.Setenv("NO_COLOR", "true")

// Ensure that the operators are installed
commonVar.CliRunner.EnsureOperatorIsInstalled("service-binding-operator")
commonVar.CliRunner.EnsureOperatorIsInstalled("cloud-native-postgresql")
Eventually(func() string {
out, _ := commonVar.CliRunner.GetBindableKinds()
return out
}, 120, 3).Should(ContainSubstring("Cluster"))
addBindableKind := commonVar.CliRunner.Run("apply", "-f", helper.GetExamplePath("manifests", "bindablekind-instance.yaml"))
Expect(addBindableKind.ExitCode()).To(BeEquivalentTo(0))
serviceName = "cluster-sample" // Hard coded from bindablekind-instance.yaml
})

// Clean up after the test
// This is run after every Spec (It)
var _ = AfterEach(func() {
helper.CommonAfterEach(commonVar)
})

When("the component is bootstrapped", func() {
var componentName = "mynode"
var bindingName string
BeforeEach(func() {
helper.Cmd("odo", "init", "--name", componentName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml"), "--starter", "nodejs-starter").ShouldPass()
bindingName = fmt.Sprintf("%s-%s", componentName, serviceName)
})

It("should successsfully add binding to the devfile (Bind as Environment Variables)", func() {
command := []string{"odo", "add", "binding"}

_, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select service instance you want to bind to:")
helper.SendLine(ctx, "cluster-sample (Cluster.postgresql.k8s.enterprisedb.io)")

helper.ExpectString(ctx, "Enter the Binding's name")
helper.SendLine(ctx, "\n")

helper.ExpectString(ctx, "How do you want to bind the service?")
helper.SendLine(ctx, "Bind as Environment Variables")

helper.ExpectString(ctx, "Successfully added the binding to the devfile.")

helper.ExpectString(ctx, fmt.Sprintf("odo add binding --service cluster-sample.Cluster.postgresql.k8s.enterprisedb.io --name %s --bind-as-files=false", bindingName))
})

Expect(err).To(BeNil())
components := helper.GetDevfileComponents(filepath.Join(commonVar.Context, "devfile.yaml"), bindingName)
Expect(components).ToNot(BeNil())
})

It("should successsfully add binding to the devfile (Bind as Files)", func() {
command := []string{"odo", "add", "binding"}

_, err := helper.RunInteractive(command, nil, func(ctx helper.InteractiveContext) {
helper.ExpectString(ctx, "Select service instance you want to bind to:")
helper.SendLine(ctx, "cluster-sample (Cluster.postgresql.k8s.enterprisedb.io)")

helper.ExpectString(ctx, "Enter the Binding's name")
helper.SendLine(ctx, "\n")

helper.ExpectString(ctx, "How do you want to bind the service?")
helper.SendLine(ctx, "Bind as Files")

helper.ExpectString(ctx, "Successfully added the binding to the devfile.")
})

Expect(err).To(BeNil())
components := helper.GetDevfileComponents(filepath.Join(commonVar.Context, "devfile.yaml"), bindingName)
Expect(components).ToNot(BeNil())
})
})
})

0 comments on commit 271bd9d

Please sign in to comment.