mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Dropping support for service catalog based services (#4906)
* Removing cli layer and integration tests related to service catalog Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Fixing missing error msg Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Fixing golint errors Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Fixing error for interactive mode Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Moving interactive mode error to top Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Fixing Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Fixing interactive mode error condition Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Removing some more service related code Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Fixing golint Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Removing service catalog backend part 1 Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Updating changelogs Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Removing some more of the service catalog related code Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Updating as per comments in review Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Update pkg/odo/cli/service/create.go Co-authored-by: Parthvi Vala <pvala@redhat.com> * Fixing gofmt Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Adding kube to cli docs Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Updating changelog as per comments by @dharmit Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Removing some unnessasary stuff Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Updating docs based changes as per review Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Fixing test Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com> * Update pkg/odo/cli/catalog/describe/service.go Co-authored-by: Philippe Martin <contact@elol.fr> * Update pkg/odo/cli/service/list.go Co-authored-by: Philippe Martin <contact@elol.fr> Co-authored-by: Parthvi Vala <pvala@redhat.com> Co-authored-by: Philippe Martin <contact@elol.fr>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
- Added a column in odo list output to indicate components created by odo ([#4962](https://github.com/openshift/odo/pull/4962))
|
||||
- Fix description for "odo link" command ([#4930](https://github.com/openshift/odo/pull/4930))
|
||||
- Add deprecation warnings for s2i components([#4967](https://github.com/openshift/odo/issues/4967))
|
||||
- Dropping support for service catalog based services ([#4906](https://github.com/openshift/odo/pull/4906))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
||||
9
Makefile
9
Makefile
@@ -166,10 +166,6 @@ test-cmd-login-logout: ## Run odo login and logout tests
|
||||
test-cmd-link-unlink-4-cluster: ## Run link and unlink commnad tests against 4.x cluster
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo link and unlink commnad tests" tests/integration/
|
||||
|
||||
.PHONY: test-cmd-link-unlink-311-cluster
|
||||
test-cmd-link-unlink-311-cluster: ## Run link and unlink command tests against 3.11 cluster
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo link and unlink command tests" tests/integration/servicecatalog/
|
||||
|
||||
.PHONY: test-cmd-service
|
||||
test-cmd-service: ## Run odo service command tests
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo service command tests" tests/integration/servicecatalog/
|
||||
@@ -299,11 +295,6 @@ test-integration-devfile: ## Run devfile integration tests
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS) tests/integration/devfile/
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS_SERIAL) tests/integration/devfile/debug/
|
||||
|
||||
# Only service and link command tests are the part of this test run
|
||||
.PHONY: test-integration-service-catalog
|
||||
test-integration-service-catalog: ## Run command's integration tests which are dependent on service catalog enabled cluster.
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS) tests/integration/servicecatalog/
|
||||
|
||||
.PHONY: test-e2e-beta
|
||||
test-e2e-beta: ## Run core beta flow e2e tests
|
||||
$(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo core beta flow" tests/e2escenarios/
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
applabels "github.com/openshift/odo/pkg/application/labels"
|
||||
"github.com/openshift/odo/pkg/component"
|
||||
"github.com/openshift/odo/pkg/occlient"
|
||||
"github.com/openshift/odo/pkg/service"
|
||||
"github.com/openshift/odo/pkg/util"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -86,21 +85,6 @@ func Delete(client *occlient.Client, name string) error {
|
||||
|
||||
labels := applabels.GetLabels(name, false)
|
||||
|
||||
// first delete the services (ServiceInstance in OpenShift terminology)
|
||||
// belonging to the app
|
||||
svcList, err := service.List(client, name)
|
||||
if err != nil {
|
||||
// error is returned when there's no Service Catalog enabled in the service
|
||||
klog.V(4).Infof("Service catalog is not enabled in the cluster, skipping service deletion")
|
||||
} else {
|
||||
for _, svc := range svcList.Items {
|
||||
err = service.DeleteServiceAndUnlinkComponents(client, svc.Name, name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to delete the application %s due to failure in deleting service(s) in the application", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
supported, err := client.IsDeploymentConfigSupported()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Package application provides functions to list, check existence of, delete and get machine readable description of applications.
|
||||
// An application is a set of components and services.
|
||||
// An application is materialized by the `app:` label in `deployments`, `deploymentconfigs`,
|
||||
// or service instances (service instances from Service Catalog or from Operator Backed Services).
|
||||
// or service instances (service instances from Operator Backed Services).
|
||||
package application
|
||||
|
||||
@@ -4,17 +4,14 @@ import (
|
||||
"fmt"
|
||||
|
||||
applabels "github.com/openshift/odo/pkg/application/labels"
|
||||
"github.com/openshift/odo/pkg/component"
|
||||
"github.com/openshift/odo/pkg/kclient"
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/occlient"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/openshift/odo/pkg/component"
|
||||
odoutil "github.com/openshift/odo/pkg/odo/util"
|
||||
"github.com/openshift/odo/pkg/odo/util/completion"
|
||||
"github.com/openshift/odo/pkg/service"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -87,18 +84,5 @@ func printAppInfo(client *occlient.Client, kClient *kclient.Client, appName stri
|
||||
}
|
||||
}
|
||||
}
|
||||
// List services that will be removed
|
||||
serviceList, err := service.List(client, appName)
|
||||
if err != nil {
|
||||
log.Info("No services / could not get services")
|
||||
klog.V(4).Info(err.Error())
|
||||
}
|
||||
if len(serviceList.Items) != 0 {
|
||||
log.Info("This application has following service(s) that will be deleted")
|
||||
for _, ser := range serviceList.Items {
|
||||
log.Info("service named", ser.ObjectMeta.Name, "of type", ser.Spec.Type)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
"github.com/openshift/odo/pkg/service"
|
||||
svc "github.com/openshift/odo/pkg/service"
|
||||
"github.com/spf13/cobra"
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
)
|
||||
@@ -13,29 +12,18 @@ import (
|
||||
const serviceRecommendedCommandName = "service"
|
||||
|
||||
var (
|
||||
serviceExample = ktemplates.Examples(` # Describe a service catalog service
|
||||
%[1]s mysql-persistent
|
||||
|
||||
# Describe a Operator backed service
|
||||
serviceExample = ktemplates.Examples(`# Describe a Operator backed service
|
||||
%[1]s
|
||||
`)
|
||||
|
||||
serviceLongDesc = ktemplates.LongDesc(`Describes a service type.
|
||||
This command supports both Service Catalog services and Operator backed services.
|
||||
This command supports Operator backed services.
|
||||
A user can describe an Operator backed service by providing the full identifier for an Operand i.e. <operator_type>/<cr_name> which they can find by running "odo catalog list services".
|
||||
|
||||
If the format doesn't match <operator_type>/<cr_name> then service catalog services would be searched.
|
||||
|
||||
`)
|
||||
)
|
||||
|
||||
// DescribeServiceOptions encapsulates the options for the odo catalog describe service command
|
||||
type DescribeServiceOptions struct {
|
||||
// name of the service to describe, from command arguments
|
||||
serviceName string
|
||||
// resolved service
|
||||
service svc.ServiceClass
|
||||
plans []svc.ServicePlan
|
||||
// generic context options common to all commands
|
||||
*genericclioptions.Context
|
||||
|
||||
@@ -59,7 +47,7 @@ func (o *DescribeServiceOptions) Complete(name string, cmd *cobra.Command, args
|
||||
if _, _, err := service.SplitServiceKindName(args[0]); err == nil {
|
||||
o.backend = NewOperatorBackend()
|
||||
} else {
|
||||
o.backend = NewServiceCatalogBackend()
|
||||
return fmt.Errorf("no deployable operators found")
|
||||
}
|
||||
|
||||
return o.backend.CompleteDescribeService(o, args)
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package describe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
svc "github.com/openshift/odo/pkg/service"
|
||||
)
|
||||
|
||||
type serviceCatalogBackend struct {
|
||||
}
|
||||
|
||||
func NewServiceCatalogBackend() *serviceCatalogBackend {
|
||||
return &serviceCatalogBackend{}
|
||||
}
|
||||
|
||||
func (ohb *serviceCatalogBackend) CompleteDescribeService(dso *DescribeServiceOptions, args []string) error {
|
||||
dso.serviceName = args[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ohb *serviceCatalogBackend) ValidateDescribeService(dso *DescribeServiceOptions) error {
|
||||
var err error
|
||||
dso.service, dso.plans, err = svc.GetServiceClassAndPlans(dso.Client, dso.serviceName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ohb *serviceCatalogBackend) RunDescribeService(dso *DescribeServiceOptions) error {
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetBorder(false)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
|
||||
serviceData := [][]string{
|
||||
{"Name", dso.service.Name},
|
||||
{"Bindable", fmt.Sprint(dso.service.Bindable)},
|
||||
{"Operated by the broker", dso.service.ServiceBrokerName},
|
||||
{"Short Description", dso.service.ShortDescription},
|
||||
{"Long Description", dso.service.LongDescription},
|
||||
{"Versions Available", strings.Join(dso.service.VersionsAvailable, ",")},
|
||||
{"Tags", strings.Join(dso.service.Tags, ",")},
|
||||
}
|
||||
|
||||
table.AppendBulk(serviceData)
|
||||
|
||||
table.Append([]string{""})
|
||||
|
||||
if len(dso.plans) > 0 {
|
||||
table.Append([]string{"PLANS"})
|
||||
|
||||
for _, plan := range dso.plans {
|
||||
|
||||
// create the display values for required and optional parameters
|
||||
requiredWithMandatoryUserInputParameterNames := []string{}
|
||||
requiredWithOptionalUserInputParameterNames := []string{}
|
||||
optionalParameterDisplay := []string{}
|
||||
for _, parameter := range plan.Parameters {
|
||||
if parameter.Required {
|
||||
// until we have a better solution for displaying the plan data (like a separate table perhaps)
|
||||
// this is simplest thing to do
|
||||
if len(parameter.Default) > 0 {
|
||||
requiredWithOptionalUserInputParameterNames = append(
|
||||
requiredWithOptionalUserInputParameterNames,
|
||||
fmt.Sprintf("%s (default: '%s')", parameter.Name, parameter.Default))
|
||||
} else {
|
||||
requiredWithMandatoryUserInputParameterNames = append(requiredWithMandatoryUserInputParameterNames, parameter.Name)
|
||||
}
|
||||
|
||||
} else {
|
||||
optionalParameterDisplay = append(optionalParameterDisplay, parameter.Name)
|
||||
}
|
||||
}
|
||||
|
||||
table.Append([]string{"***********************", "*****************************************************"})
|
||||
planLineSeparator := []string{"-----------------", "-----------------"}
|
||||
|
||||
planData := [][]string{
|
||||
{"Name", plan.Name},
|
||||
planLineSeparator,
|
||||
{"Display Name", plan.DisplayName},
|
||||
planLineSeparator,
|
||||
{"Short Description", plan.Description},
|
||||
planLineSeparator,
|
||||
{"Required Params without a default value", strings.Join(requiredWithMandatoryUserInputParameterNames, ", ")},
|
||||
planLineSeparator,
|
||||
{"Required Params with a default value", strings.Join(requiredWithOptionalUserInputParameterNames, ", ")},
|
||||
planLineSeparator,
|
||||
{"Optional Params", strings.Join(optionalParameterDisplay, ", ")},
|
||||
{"", ""},
|
||||
}
|
||||
table.AppendBulk(planData)
|
||||
}
|
||||
table.Render()
|
||||
} else {
|
||||
return fmt.Errorf("no plans found for service %s", dso.serviceName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -17,13 +17,11 @@ import (
|
||||
|
||||
const servicesRecommendedCommandName = "services"
|
||||
|
||||
var servicesExample = ` # Get the supported services from service catalog
|
||||
var servicesExample = ` # Get the supported services
|
||||
%[1]s`
|
||||
|
||||
// ServiceOptions encapsulates the options for the odo catalog list services command
|
||||
type ServiceOptions struct {
|
||||
// list of known services
|
||||
services catalog.ServiceTypeList
|
||||
// list of clusterserviceversions (installed by Operators)
|
||||
csvs *olm.ClusterServiceVersionList
|
||||
// generic context options common to all commands
|
||||
@@ -49,11 +47,6 @@ func (o *ServiceOptions) Complete(name string, cmd *cobra.Command, args []string
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
o.services, _ = catalog.ListSvcCatServices(o.Client)
|
||||
|
||||
o.services = util.FilterHiddenServices(o.services)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -65,20 +58,16 @@ func (o *ServiceOptions) Validate() (err error) {
|
||||
// Run contains the logic for the command associated with ListServicesOptions
|
||||
func (o *ServiceOptions) Run(cmd *cobra.Command) (err error) {
|
||||
if log.IsJSON() {
|
||||
machineoutput.OutputSuccess(newCatalogListOutput(&o.services, o.csvs))
|
||||
machineoutput.OutputSuccess(newCatalogListOutput(o.csvs))
|
||||
} else {
|
||||
if len(o.csvs.Items) == 0 && len(o.services.Items) == 0 {
|
||||
log.Info("no deployable services/operators found")
|
||||
if len(o.csvs.Items) == 0 {
|
||||
log.Info("no deployable operators found")
|
||||
return
|
||||
}
|
||||
|
||||
if len(o.csvs.Items) > 0 {
|
||||
util.DisplayClusterServiceVersions(o.csvs)
|
||||
}
|
||||
|
||||
if len(o.services.Items) > 0 {
|
||||
util.DisplayServices(o.services)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -102,18 +91,16 @@ func NewCmdCatalogListServices(name, fullName string) *cobra.Command {
|
||||
type catalogListOutput struct {
|
||||
v1.TypeMeta `json:",inline"`
|
||||
v1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Services *catalog.ServiceTypeList `json:"services,omitempty"`
|
||||
// list of clusterserviceversions (installed by Operators)
|
||||
Operators *olm.ClusterServiceVersionList `json:"operators,omitempty"`
|
||||
}
|
||||
|
||||
func newCatalogListOutput(services *catalog.ServiceTypeList, operators *olm.ClusterServiceVersionList) catalogListOutput {
|
||||
func newCatalogListOutput(operators *olm.ClusterServiceVersionList) catalogListOutput {
|
||||
return catalogListOutput{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "List",
|
||||
APIVersion: machineoutput.APIVersion,
|
||||
},
|
||||
Services: services,
|
||||
Operators: operators,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func NewCmdCatalogSearchService(name, fullName string) *cobra.Command {
|
||||
Long: `Search service type in catalog.
|
||||
|
||||
This searches for a partial match for the given search term in all the available
|
||||
services from service catalog.
|
||||
services from operator hub services.
|
||||
`,
|
||||
Example: fmt.Sprintf(serviceExample, fullName),
|
||||
Args: cobra.ExactArgs(1),
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/openshift/odo/pkg/catalog"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestFilterHiddenServices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input catalog.ServiceTypeList
|
||||
expected catalog.ServiceTypeList
|
||||
}{
|
||||
/*
|
||||
This test is not needed.. Also fails using DeepEqual anyways..
|
||||
--- FAIL: TestFilterHiddenServices/Case_1:_empty_input (0.00s)
|
||||
util_test.go:101: got: [], wanted: []
|
||||
{
|
||||
name: "Case 1: empty input",
|
||||
input: catalog.ServiceTypeList{},
|
||||
expected: catalog.ServiceTypeList{},
|
||||
},
|
||||
*/
|
||||
{
|
||||
name: "Case 2: non empty input",
|
||||
input: catalog.ServiceTypeList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "List",
|
||||
APIVersion: "odo.dev/v1alpha1",
|
||||
},
|
||||
ListMeta: metav1.ListMeta{},
|
||||
Items: []catalog.ServiceType{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n1",
|
||||
},
|
||||
Spec: catalog.ServiceSpec{
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n2",
|
||||
},
|
||||
Spec: catalog.ServiceSpec{
|
||||
Hidden: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n3",
|
||||
},
|
||||
Spec: catalog.ServiceSpec{
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n4",
|
||||
},
|
||||
Spec: catalog.ServiceSpec{
|
||||
Hidden: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: catalog.ServiceTypeList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "List",
|
||||
APIVersion: "odo.dev/v1alpha1",
|
||||
},
|
||||
ListMeta: metav1.ListMeta{},
|
||||
Items: []catalog.ServiceType{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n2",
|
||||
},
|
||||
Spec: catalog.ServiceSpec{
|
||||
Hidden: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n4",
|
||||
},
|
||||
Spec: catalog.ServiceSpec{
|
||||
Hidden: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output := FilterHiddenServices(tt.input)
|
||||
if !reflect.DeepEqual(tt.expected, output) {
|
||||
t.Errorf("got: %+v, wanted: %+v", output.Items, tt.expected.Items)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterHiddenComponents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []catalog.ComponentType
|
||||
expected []catalog.ComponentType
|
||||
}{
|
||||
{
|
||||
name: "Case 1: empty input",
|
||||
input: []catalog.ComponentType{},
|
||||
expected: []catalog.ComponentType{},
|
||||
},
|
||||
{
|
||||
name: "Case 2: non empty input",
|
||||
input: []catalog.ComponentType{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n1",
|
||||
},
|
||||
Spec: catalog.ComponentSpec{
|
||||
NonHiddenTags: []string{"1", "latest"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n2",
|
||||
},
|
||||
Spec: catalog.ComponentSpec{
|
||||
NonHiddenTags: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n3",
|
||||
},
|
||||
Spec: catalog.ComponentSpec{
|
||||
NonHiddenTags: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n4",
|
||||
},
|
||||
Spec: catalog.ComponentSpec{
|
||||
NonHiddenTags: []string{"10"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []catalog.ComponentType{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n1",
|
||||
},
|
||||
Spec: catalog.ComponentSpec{
|
||||
NonHiddenTags: []string{"1", "latest"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "n4",
|
||||
},
|
||||
Spec: catalog.ComponentSpec{
|
||||
NonHiddenTags: []string{"10"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output := FilterHiddenComponents(tt.input)
|
||||
if !reflect.DeepEqual(tt.expected, output) {
|
||||
t.Errorf("got: %+v, wanted: %+v", output, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
projectCmd "github.com/openshift/odo/pkg/odo/cli/project"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
odoutil "github.com/openshift/odo/pkg/odo/util"
|
||||
"github.com/openshift/odo/pkg/odo/util/completion"
|
||||
svc "github.com/openshift/odo/pkg/service"
|
||||
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
@@ -44,7 +43,7 @@ var (
|
||||
# and make the secrets accessible as files in the '/bindings/etcd/' directory
|
||||
%[1]s EtcdCluster/myetcd --bind-as-files --name etcd`)
|
||||
|
||||
linkLongDesc = `Link component to a service (backed by an Operator or Service Catalog) or component
|
||||
linkLongDesc = `Link component to a service (backed by an Operator) or component
|
||||
|
||||
If the source component is not provided, the current active component is assumed.
|
||||
In both use cases, link adds the appropriate secret to the environment of the source component.
|
||||
@@ -72,14 +71,7 @@ odo link backend --component frontend
|
||||
Now the frontend has 2 ENV variables it can use:
|
||||
COMPONENT_BACKEND_HOST=backend-app
|
||||
COMPONENT_BACKEND_PORT=8080
|
||||
|
||||
If you wish to use a database, we can use the Service Catalog and link it to our backend:
|
||||
odo service create dh-postgresql-apb --plan dev -p postgresql_user=luke -p postgresql_password=secret
|
||||
odo link dh-postgresql-apb
|
||||
|
||||
Now backend has 2 ENV variables it can use:
|
||||
DB_USER=luke
|
||||
DB_PASSWORD=secret`
|
||||
`
|
||||
)
|
||||
|
||||
// LinkOptions encapsulates the options for the odo link command
|
||||
@@ -182,7 +174,5 @@ func NewCmdLink(name, fullName string) *cobra.Command {
|
||||
//Adding context flag
|
||||
genericclioptions.AddContextFlag(linkCmd, &o.componentContext)
|
||||
|
||||
completion.RegisterCommandHandler(linkCmd, completion.LinkCompletionHandler)
|
||||
|
||||
return linkCmd
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
appCmd "github.com/openshift/odo/pkg/odo/cli/application"
|
||||
projectCmd "github.com/openshift/odo/pkg/odo/cli/project"
|
||||
"github.com/openshift/odo/pkg/odo/util/completion"
|
||||
svc "github.com/openshift/odo/pkg/service"
|
||||
|
||||
"github.com/openshift/odo/pkg/odo/util"
|
||||
@@ -112,7 +111,5 @@ func NewCmdUnlink(name, fullName string) *cobra.Command {
|
||||
// Adding context flag
|
||||
genericclioptions.AddContextFlag(unlinkCmd, &o.componentContext)
|
||||
|
||||
completion.RegisterCommandHandler(unlinkCmd, completion.UnlinkCompletionHandler)
|
||||
|
||||
return unlinkCmd
|
||||
}
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/odo/cli/component"
|
||||
"github.com/openshift/odo/pkg/odo/cli/service/ui"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
"github.com/openshift/odo/pkg/odo/util/completion"
|
||||
"github.com/spf13/cobra"
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
createRecommendedCommandName = "create"
|
||||
equivalentTemplate = "{{.CmdFullName}} {{.ServiceType}}" +
|
||||
"{{if .ServiceName}} {{.ServiceName}}{{end}}" +
|
||||
" --app {{.Application}}" +
|
||||
" --project {{.Project}}" +
|
||||
"{{if .Plan}} --plan {{.Plan}}{{end}}" +
|
||||
"{{range $key, $value := .ParametersMap}} -p {{$key}}={{$value}}{{end}}"
|
||||
)
|
||||
|
||||
var (
|
||||
createExample = ktemplates.Examples(`
|
||||
# Create new postgresql service from service catalog using dev plan and name my-postgresql-db.
|
||||
%[1]s dh-postgresql-apb my-postgresql-db --plan dev -p postgresql_user=luke -p postgresql_password=secret`)
|
||||
|
||||
createOperatorExample = ktemplates.Examples(`
|
||||
# Create new EtcdCluster service from etcdoperator.v0.9.4 operator.
|
||||
%[1]s etcdoperator.v0.9.4/EtcdCluster`)
|
||||
|
||||
createShortDesc = `Create a new service from Operator Hub or Service Catalog and deploy it on OpenShift.`
|
||||
createShortDesc = `Create a new service from Operator Hub and deploy it on Kubernetes or OpenShift.`
|
||||
|
||||
createLongDesc = ktemplates.LongDesc(`
|
||||
Create a new service from Operator Hub or Service Catalog and deploy it on OpenShift.
|
||||
Create a new service from Operator Hub and deploy it on Kubernetes or OpenShift.
|
||||
|
||||
Service creation can be performed from a valid component directory (one containing a devfile.yaml) only.
|
||||
|
||||
@@ -46,8 +31,6 @@ To create the service from outside a component directory, specify path to a vali
|
||||
|
||||
When creating a service using Operator Hub, provide a service name along with Operator name.
|
||||
|
||||
When creating a service using Service Catalog, a --plan must be passed along with the service type. Parameters to configure the service are passed as key=value pairs.
|
||||
|
||||
For a full list of service types, use: 'odo catalog list services'`)
|
||||
)
|
||||
|
||||
@@ -55,8 +38,6 @@ For a full list of service types, use: 'odo catalog list services'`)
|
||||
type CreateOptions struct {
|
||||
// parameters hold the user-provided values for service class parameters via flags (populated by cobra)
|
||||
parameters []string
|
||||
// Plan is the selected service plan
|
||||
Plan string
|
||||
// ServiceType corresponds to the service class name
|
||||
ServiceType string
|
||||
// ServiceName is how the service will be named and known by odo
|
||||
@@ -65,8 +46,6 @@ type CreateOptions struct {
|
||||
ParametersMap map[string]string
|
||||
// interactive specifies whether the command operates in interactive mode or not
|
||||
interactive bool
|
||||
// outputCLI specifies whether to output the non-interactive version of the command or not
|
||||
outputCLI bool
|
||||
// CmdFullName records the command's full name
|
||||
CmdFullName string
|
||||
// whether or not to wait for the service to be ready
|
||||
@@ -79,7 +58,7 @@ type CreateOptions struct {
|
||||
DryRun bool
|
||||
// Location of the file in which yaml specification of CR is stored.
|
||||
fromFile string
|
||||
// Backend is the service provider backend (Operator Hub or Service Catalog) providing the service requested by the user
|
||||
// Backend is the service provider backend providing the service requested by the user
|
||||
Backend ServiceProviderBackend
|
||||
}
|
||||
|
||||
@@ -114,36 +93,17 @@ func (o *CreateOptions) Complete(name string, cmd *cobra.Command, args []string)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// decide which service backend to use
|
||||
if o.fromFile != "" {
|
||||
// fromFile is supported only for Operator backend
|
||||
o.Backend = NewOperatorBackend()
|
||||
// since interactive mode is not supported for Operators yet, set it to false
|
||||
o.interactive = false
|
||||
|
||||
return o.Backend.CompleteServiceCreate(o, cmd, args)
|
||||
//if no args are provided and if request is not from file, user wants interactive mode
|
||||
if o.fromFile == "" && len(args) == 0 {
|
||||
return fmt.Errorf("odo doesn't support interactive mode for creating Operator backed service")
|
||||
}
|
||||
|
||||
// check if interactive mode is requested
|
||||
if len(args) == 0 {
|
||||
o.interactive = true
|
||||
// only Service Catalog backend supports interactive mode for service creation
|
||||
o.Backend = NewServiceCatalogBackend()
|
||||
} else {
|
||||
o.Backend = decideBackend(args[0])
|
||||
}
|
||||
|
||||
o.Backend = NewOperatorBackend()
|
||||
o.interactive = false
|
||||
return o.Backend.CompleteServiceCreate(o, cmd, args)
|
||||
}
|
||||
|
||||
// Validate validates the CreateOptions based on completed values
|
||||
func (o *CreateOptions) Validate() (err error) {
|
||||
// if we are in interactive mode, all values are already valid
|
||||
if o.interactive {
|
||||
return nil
|
||||
}
|
||||
|
||||
return o.Backend.ValidateServiceCreate(o)
|
||||
}
|
||||
|
||||
@@ -159,54 +119,30 @@ func (o *CreateOptions) Run(cmd *cobra.Command) (err error) {
|
||||
log.Info("Successfully added service to the configuration; do 'odo push' to create service on the cluster")
|
||||
}
|
||||
|
||||
equivalent := o.outputNonInteractiveEquivalent()
|
||||
if len(equivalent) > 0 {
|
||||
log.Info("Equivalent command:\n" + ui.StyledOutput(equivalent, "cyan"))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// outputNonInteractiveEquivalent outputs the populated options as the equivalent command that would be used in non-interactive mode
|
||||
func (o *CreateOptions) outputNonInteractiveEquivalent() string {
|
||||
if o.outputCLI {
|
||||
var tpl bytes.Buffer
|
||||
t := template.Must(template.New("service-create-cli").Parse(equivalentTemplate))
|
||||
e := t.Execute(&tpl, o)
|
||||
if e != nil {
|
||||
panic(e) // shouldn't happen
|
||||
}
|
||||
return strings.TrimSpace(tpl.String())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// NewCmdServiceCreate implements the odo service create command.
|
||||
func NewCmdServiceCreate(name, fullName string) *cobra.Command {
|
||||
o := NewCreateOptions()
|
||||
o.CmdFullName = fullName
|
||||
serviceCreateCmd := &cobra.Command{
|
||||
Use: name + " <service_type> --plan <plan_name> [service_name]",
|
||||
Use: name + " <operator_type>/<crd_name> [service_name] [flags]",
|
||||
Short: createShortDesc,
|
||||
Long: createLongDesc,
|
||||
Example: fmt.Sprintf(createExample, fullName),
|
||||
Example: fmt.Sprintf(createOperatorExample, fullName),
|
||||
Args: cobra.RangeArgs(0, 2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
genericclioptions.GenericRun(o, cmd, args)
|
||||
},
|
||||
}
|
||||
|
||||
serviceCreateCmd.Use += fmt.Sprintf(" [flags]\n %s <operator_type>/<crd_name> [service_name] [flags]", o.CmdFullName)
|
||||
serviceCreateCmd.Example += "\n\n" + fmt.Sprintf(createOperatorExample, fullName)
|
||||
serviceCreateCmd.Flags().BoolVar(&o.DryRun, "dry-run", false, "Print the yaml specificiation that will be used to create the operator backed service")
|
||||
// remove this feature after enabling service create interactive mode for operator backed services
|
||||
serviceCreateCmd.Flags().StringVar(&o.fromFile, "from-file", "", "Path to the file containing yaml specification to use to start operator backed service")
|
||||
|
||||
serviceCreateCmd.Flags().StringVar(&o.Plan, "plan", "", "The name of the plan of the service to be created")
|
||||
serviceCreateCmd.Flags().StringArrayVarP(&o.parameters, "parameters", "p", []string{}, "Parameters of the plan where a parameter is expressed as <key>=<value")
|
||||
serviceCreateCmd.Flags().BoolVarP(&o.wait, "wait", "w", false, "Wait until the service is ready")
|
||||
genericclioptions.AddContextFlag(serviceCreateCmd, &o.componentContext)
|
||||
completion.RegisterCommandHandler(serviceCreateCmd, completion.ServiceClassCompletionHandler)
|
||||
completion.RegisterCommandFlagHandler(serviceCreateCmd, "plan", completion.ServicePlanCompletionHandler)
|
||||
completion.RegisterCommandFlagHandler(serviceCreateCmd, "parameters", completion.ServiceParameterCompletionHandler)
|
||||
return serviceCreateCmd
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/openshift/odo/pkg/occlient"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
)
|
||||
|
||||
func TestOutputNonInteractiveEquivalent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, _ := occlient.FakeNew()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
options CreateOptions
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "when output is not requested, should return empty string",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: false,
|
||||
ServiceType: "foo",
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "just service class",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: true,
|
||||
ServiceType: "foo",
|
||||
},
|
||||
expected: RecommendedCommandName + " foo --app app --project testproject",
|
||||
},
|
||||
{
|
||||
name: "just service class and name",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: true,
|
||||
ServiceType: "foo",
|
||||
ServiceName: "myservice",
|
||||
},
|
||||
expected: RecommendedCommandName + " foo myservice --app app --project testproject",
|
||||
},
|
||||
{
|
||||
name: "service class, name and plan",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: true,
|
||||
ServiceType: "foo",
|
||||
ServiceName: "myservice",
|
||||
Plan: "dev",
|
||||
},
|
||||
expected: RecommendedCommandName + " foo myservice --app app --project testproject --plan dev",
|
||||
},
|
||||
{
|
||||
name: "service class and plan",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: true,
|
||||
ServiceType: "foo",
|
||||
Plan: "dev",
|
||||
},
|
||||
expected: RecommendedCommandName + " foo --app app --project testproject --plan dev",
|
||||
},
|
||||
{
|
||||
name: "service class and empty params",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: true,
|
||||
ServiceType: "foo",
|
||||
ParametersMap: map[string]string{},
|
||||
},
|
||||
expected: RecommendedCommandName + " foo --app app --project testproject",
|
||||
},
|
||||
{
|
||||
name: "service class and params",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: true,
|
||||
ServiceType: "foo",
|
||||
ParametersMap: map[string]string{"param1": "value1", "param2": "value2"},
|
||||
},
|
||||
expected: RecommendedCommandName + " foo --app app --project testproject -p param1=value1 -p param2=value2",
|
||||
},
|
||||
{
|
||||
name: "all",
|
||||
options: CreateOptions{
|
||||
Context: genericclioptions.NewFakeContext("testproject", "app", "", client, nil),
|
||||
CmdFullName: RecommendedCommandName,
|
||||
outputCLI: true,
|
||||
ServiceType: "foo",
|
||||
ServiceName: "name",
|
||||
Plan: "plan",
|
||||
ParametersMap: map[string]string{"param1": "value1", "param2": "value2"},
|
||||
},
|
||||
expected: RecommendedCommandName + " foo name --app app --project testproject --plan plan -p param1=value1 -p param2=value2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := tt.options.outputNonInteractiveEquivalent()
|
||||
if tt.expected != actual {
|
||||
t.Errorf("expected '%s', got '%s'", tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,13 +2,13 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/openshift/odo/pkg/service"
|
||||
"strings"
|
||||
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/odo/cli/component"
|
||||
"github.com/openshift/odo/pkg/odo/cli/ui"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
"github.com/openshift/odo/pkg/odo/util/completion"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog"
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
@@ -32,7 +32,7 @@ type DeleteOptions struct {
|
||||
*genericclioptions.Context
|
||||
// Context to use when listing service. This will use app and project values from the context
|
||||
componentContext string
|
||||
// Backend is the service provider backend (Operator Hub or Service Catalog) that was used to create the service
|
||||
// Backend is the service provider backend that was used to create the service
|
||||
Backend ServiceProviderBackend
|
||||
}
|
||||
|
||||
@@ -57,9 +57,12 @@ func (o *DeleteOptions) Complete(name string, cmd *cobra.Command, args []string)
|
||||
return err
|
||||
}
|
||||
|
||||
// decide which service backend to use
|
||||
o.Backend = decideBackend(args[0])
|
||||
o.serviceName = args[0]
|
||||
_, _, err = service.SplitServiceKindName(o.serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid service name")
|
||||
}
|
||||
o.Backend = NewOperatorBackend()
|
||||
|
||||
return
|
||||
}
|
||||
@@ -107,6 +110,5 @@ func NewCmdServiceDelete(name, fullName string) *cobra.Command {
|
||||
}
|
||||
serviceDeleteCmd.Flags().BoolVarP(&o.serviceForceDeleteFlag, "force", "f", false, "Delete service without prompting")
|
||||
genericclioptions.AddContextFlag(serviceDeleteCmd, &o.componentContext)
|
||||
completion.RegisterCommandHandler(serviceDeleteCmd, completion.ServiceCompletionHandler)
|
||||
return serviceDeleteCmd
|
||||
}
|
||||
|
||||
@@ -2,14 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/machineoutput"
|
||||
"github.com/openshift/odo/pkg/odo/cli/component"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
odoutil "github.com/openshift/odo/pkg/odo/util"
|
||||
svc "github.com/openshift/odo/pkg/service"
|
||||
"github.com/spf13/cobra"
|
||||
ktemplates "k8s.io/kubectl/pkg/util/templates"
|
||||
@@ -31,7 +25,7 @@ type ServiceListOptions struct {
|
||||
*genericclioptions.Context
|
||||
// Context to use when listing service. This will use app and project values from the context
|
||||
componentContext string
|
||||
// choose between Operator Hub and Service Catalog. If true, Operator Hub
|
||||
// If true, Operator Hub is installed on the cluster
|
||||
csvSupport bool
|
||||
}
|
||||
|
||||
@@ -54,56 +48,19 @@ func (o *ServiceListOptions) Complete(name string, cmd *cobra.Command, args []st
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
o.Context, err = genericclioptions.NewContext(cmd)
|
||||
return fmt.Errorf("failed to list operator backed services, have you installed operators on the cluseter?")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validate validates the ServiceListOptions based on completed values
|
||||
func (o *ServiceListOptions) Validate() (err error) {
|
||||
if !o.csvSupport {
|
||||
// Throw error if project and application values are not available.
|
||||
// This will most likely be the case when user does odo service list from outside a component directory and
|
||||
// doesn't provide --app and/or --project flags
|
||||
if o.Context.Project == "" || o.Context.Application == "" {
|
||||
return odoutil.ThrowContextError()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Run contains the logic for the odo service list command
|
||||
func (o *ServiceListOptions) Run(cmd *cobra.Command) (err error) {
|
||||
if o.csvSupport {
|
||||
// if cluster supports Operators, we list only operator backed services
|
||||
// and not service catalog ones
|
||||
return o.listOperatorServices()
|
||||
}
|
||||
|
||||
return o.listServiceCatalogServices()
|
||||
}
|
||||
|
||||
func (o *ServiceListOptions) listServiceCatalogServices() (err error) {
|
||||
services, err := svc.ListWithDetailedStatus(o.Client, o.Application)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Service catalog is not enabled within your cluster: %v", err)
|
||||
}
|
||||
|
||||
if len(services.Items) == 0 {
|
||||
return fmt.Errorf("There are no services deployed for this application")
|
||||
}
|
||||
|
||||
if log.IsJSON() {
|
||||
machineoutput.OutputSuccess(services)
|
||||
} else {
|
||||
w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent)
|
||||
fmt.Fprintln(w, "NAME", "\t", "TYPE", "\t", "PLAN", "\t", "STATUS")
|
||||
for _, comp := range services.Items {
|
||||
fmt.Fprintln(w, comp.ObjectMeta.Name, "\t", comp.Spec.Type, "\t", comp.Spec.Plan, "\t", comp.Status.Status)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
return
|
||||
return o.listOperatorServices()
|
||||
}
|
||||
|
||||
// NewCmdServiceList implements the odo service list command.
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// RecommendedCommandName is the recommended service command name
|
||||
const RecommendedCommandName = "service"
|
||||
|
||||
var serviceLongDesc = ktemplates.LongDesc(`Perform service catalog operations`)
|
||||
var serviceLongDesc = ktemplates.LongDesc(`Perform service related operations`)
|
||||
|
||||
// NewCmdService implements the odo service command
|
||||
func NewCmdService(name, fullName string) *cobra.Command {
|
||||
@@ -23,7 +23,7 @@ func NewCmdService(name, fullName string) *cobra.Command {
|
||||
serviceDeleteCmd := NewCmdServiceDelete(deleteRecommendedCommandName, util.GetFullName(fullName, deleteRecommendedCommandName))
|
||||
serviceCmd := &cobra.Command{
|
||||
Use: name,
|
||||
Short: "Perform service catalog operations",
|
||||
Short: "Perform service related operations",
|
||||
Long: serviceLongDesc,
|
||||
Example: fmt.Sprintf("%s\n\n%s\n\n%s",
|
||||
serviceCreateCmd.Example,
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/openshift/odo/pkg/odo/util/validation"
|
||||
|
||||
svc "github.com/openshift/odo/pkg/service"
|
||||
|
||||
scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/odo/cli/service/ui"
|
||||
commonui "github.com/openshift/odo/pkg/odo/cli/ui"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// This CompleteServiceCreate contains logic to complete the "odo service create" call for the case of Service Catalog backend
|
||||
func (b *ServiceCatalogBackend) CompleteServiceCreate(o *CreateOptions, cmd *cobra.Command, args []string) (err error) {
|
||||
var class scv1beta1.ClusterServiceClass
|
||||
|
||||
if o.interactive {
|
||||
classesByCategory, err := o.Client.GetKubeClient().ListServiceClassesByCategory()
|
||||
if err != nil {
|
||||
// this error indicates that Service Catalog is not properly setup
|
||||
// we inform the user that if they're trying interactive mode for Operators, it's not yet supported.
|
||||
// TODO: remove the warning when interactive mode for Operators is supported
|
||||
log.Warning("odo doesn't support interactive mode for creating Operator backed service yet; refer \"odo service create -h\"")
|
||||
return fmt.Errorf("unable to retrieve service classes: %v", err)
|
||||
}
|
||||
|
||||
if len(classesByCategory) == 0 {
|
||||
return fmt.Errorf("no available service classes")
|
||||
}
|
||||
|
||||
class, o.ServiceType = ui.SelectClassInteractively(classesByCategory)
|
||||
|
||||
plans, err := o.Client.GetKubeClient().ListMatchingPlans(class)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't retrieve plans for class %s: %v", class.GetExternalName(), err)
|
||||
}
|
||||
|
||||
var svcPlan scv1beta1.ClusterServicePlan
|
||||
// if there is only one available plan, we select it
|
||||
if len(plans) == 1 {
|
||||
for k, v := range plans {
|
||||
o.Plan = k
|
||||
svcPlan = v
|
||||
}
|
||||
klog.V(4).Infof("Plan %s was automatically selected since it's the only one available for service %s", o.Plan, o.ServiceType)
|
||||
} else {
|
||||
// otherwise select the plan interactively
|
||||
o.Plan = ui.SelectPlanNameInteractively(plans, "Which service plan should we use ")
|
||||
svcPlan = plans[o.Plan]
|
||||
}
|
||||
|
||||
o.ParametersMap = ui.EnterServicePropertiesInteractively(svcPlan)
|
||||
o.ServiceName = ui.EnterServiceNameInteractively(o.ServiceType, "How should we name your service ", o.validateServiceName)
|
||||
o.outputCLI = commonui.Proceed("Output the non-interactive version of the selected options")
|
||||
o.wait = commonui.Proceed("Wait for the service to be ready")
|
||||
} else {
|
||||
|
||||
o.ServiceType = args[0]
|
||||
|
||||
// if two args are given, first is service type and second one is service name
|
||||
if len(args) == 2 {
|
||||
o.ServiceName = args[1]
|
||||
} else {
|
||||
o.ServiceName = o.ServiceType
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *ServiceCatalogBackend) ValidateServiceCreate(o *CreateOptions) (err error) {
|
||||
// make sure the service type exists
|
||||
classPtr, err := o.Client.GetKubeClient().GetClusterServiceClass(o.ServiceType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create service because Service Catalog is not enabled in your cluster")
|
||||
}
|
||||
if classPtr == nil {
|
||||
return fmt.Errorf("service %v doesn't exist\nRun 'odo catalog list services' to see a list of supported services.\n", o.ServiceType)
|
||||
}
|
||||
|
||||
// check plan
|
||||
plans, err := o.Client.GetKubeClient().ListMatchingPlans(*classPtr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.Plan) == 0 {
|
||||
// when the plan has not been supplied, if there is only one available plan, we select it
|
||||
if len(plans) == 1 {
|
||||
for k := range plans {
|
||||
o.Plan = k
|
||||
}
|
||||
klog.V(4).Infof("Plan %s was automatically selected since it's the only one available for service %s", o.Plan, o.ServiceType)
|
||||
} else {
|
||||
return fmt.Errorf("no plan was supplied for service %v.\nPlease select one of: %v\n", o.ServiceType, strings.Join(ui.GetServicePlanNames(plans), ","))
|
||||
}
|
||||
} else {
|
||||
// when the plan has been supplied, we need to make sure it exists
|
||||
if _, ok := plans[o.Plan]; !ok {
|
||||
return fmt.Errorf("plan %s is invalid for service %v.\nPlease select one of: %v\n", o.Plan, o.ServiceType, strings.Join(ui.GetServicePlanNames(plans), ","))
|
||||
}
|
||||
}
|
||||
//validate service name
|
||||
return o.validateServiceName(o.ServiceName)
|
||||
}
|
||||
|
||||
// validateServiceName adopts the Validator interface and checks that the name of the service being created is valid
|
||||
func (o *CreateOptions) validateServiceName(i interface{}) (err error) {
|
||||
s := i.(string)
|
||||
err = validation.ValidateName(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exists, err := svc.SvcExists(o.Client, s, o.Application)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return fmt.Errorf("%s service already exists in the current application", o.ServiceName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *ServiceCatalogBackend) RunServiceCreate(o *CreateOptions) (err error) {
|
||||
s := &log.Status{}
|
||||
|
||||
log.Infof("Deploying service %q of type: %q", o.ServiceName, o.ServiceType)
|
||||
// create a ServiceInstance
|
||||
serviceInstance, err := svc.CreateService(o.Client, o.ServiceName, o.ServiceType, o.Plan, o.ParametersMap, o.Application)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.AddKubernetesComponentToDevfile(serviceInstance, o.ServiceName, o.EnvSpecificInfo.GetDevfileObj())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.End(true)
|
||||
|
||||
if o.wait {
|
||||
s = log.Spinner("Waiting for service to come up")
|
||||
_, err = o.Client.GetKubeClient().WaitAndGetSecret(o.ServiceName, o.Project)
|
||||
if err == nil {
|
||||
s.End(true)
|
||||
log.Successf(`Service %q is ready for use`, o.ServiceName)
|
||||
}
|
||||
} else {
|
||||
log.Successf(`Service %q was created`, o.ServiceName)
|
||||
log.Italic("\nProgress of the provisioning will not be reported and might take a long time\nYou can see the current status by executing 'odo service list'")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ServiceDefined returns true if the service is defined in the devfile
|
||||
func (b *ServiceCatalogBackend) ServiceDefined(o *DeleteOptions) (bool, error) {
|
||||
return svc.IsDefined(o.serviceName, o.EnvSpecificInfo.GetDevfileObj())
|
||||
}
|
||||
|
||||
func (b *ServiceCatalogBackend) ServiceExists(o *DeleteOptions) (bool, error) {
|
||||
return svc.SvcExists(o.Client, o.serviceName, o.Application)
|
||||
}
|
||||
|
||||
func (b *ServiceCatalogBackend) DeleteService(o *DeleteOptions, name string, application string) error {
|
||||
err := svc.DeleteServiceAndUnlinkComponents(o.Client, o.serviceName, o.Application)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.DeleteKubernetesComponentFromDevfile(o.serviceName, o.EnvSpecificInfo.GetDevfileObj())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/openshift/odo/pkg/testingutil"
|
||||
|
||||
"github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
|
||||
componentlabels "github.com/openshift/odo/pkg/component/labels"
|
||||
"github.com/openshift/odo/pkg/occlient"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
"github.com/openshift/odo/pkg/odo/util/completion"
|
||||
"github.com/posener/complete"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ktesting "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
func TestCompletions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
handler completion.ContextualizedPredictor
|
||||
cmd *cobra.Command
|
||||
last string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "Completing service create without input returns all available service class external names",
|
||||
handler: completion.ServiceClassCompletionHandler,
|
||||
cmd: NewCmdServiceCreate(createRecommendedCommandName, createRecommendedCommandName),
|
||||
want: []string{"foo", "bar", "boo"},
|
||||
},
|
||||
{
|
||||
name: "Completing service delete without input returns all available service instances",
|
||||
handler: completion.ServiceCompletionHandler,
|
||||
cmd: NewCmdServiceDelete(deleteRecommendedCommandName, deleteRecommendedCommandName),
|
||||
want: []string{"foo"},
|
||||
},
|
||||
}
|
||||
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &v1beta1.ClusterServiceClassList{
|
||||
Items: []v1beta1.ClusterServiceClass{
|
||||
testingutil.FakeClusterServiceClass("foo"),
|
||||
testingutil.FakeClusterServiceClass("bar"),
|
||||
testingutil.FakeClusterServiceClass("boo"),
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &v1beta1.ServiceInstanceList{
|
||||
Items: []v1beta1.ServiceInstance{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{"app.kubernetes.io/part-of": "foo", componentlabels.ComponentLabel: "foo", componentlabels.ComponentTypeLabel: "service"},
|
||||
},
|
||||
Status: v1beta1.ServiceInstanceStatus{
|
||||
Conditions: []v1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "some reason",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
context := genericclioptions.NewFakeContext("", "", "", client, nil)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := complete.Args{Last: tt.last}
|
||||
|
||||
got := tt.handler(tt.cmd, completion.NewParsedArgs(a, tt.cmd), context)
|
||||
|
||||
if !equal(got, tt.want) {
|
||||
t.Errorf("Failed %s: got: %q, want: %q", t.Name(), got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func equal(s1, s2 []string) bool {
|
||||
sort.Strings(s1)
|
||||
sort.Strings(s2)
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for i := range s1 {
|
||||
if s1[i] != s2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -17,11 +17,3 @@ type OperatorBackend struct {
|
||||
func NewOperatorBackend() *OperatorBackend {
|
||||
return &OperatorBackend{}
|
||||
}
|
||||
|
||||
// ServiceCatalogBackend implements the interface ServiceProviderBackend and contains methods that help create a service from Service Catalog
|
||||
type ServiceCatalogBackend struct {
|
||||
}
|
||||
|
||||
func NewServiceCatalogBackend() *ServiceCatalogBackend {
|
||||
return &ServiceCatalogBackend{}
|
||||
}
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/openshift/odo/pkg/odo/cli/ui"
|
||||
"github.com/openshift/odo/pkg/odo/util/validation"
|
||||
"github.com/openshift/odo/pkg/service"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/mgutz/ansi"
|
||||
terminal2 "golang.org/x/term"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
|
||||
scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
|
||||
)
|
||||
|
||||
// Retrieve the list of existing service class categories
|
||||
func getServiceClassesCategories(categories map[string][]scv1beta1.ClusterServiceClass) (keys []string) {
|
||||
keys = make([]string, len(categories))
|
||||
|
||||
i := 0
|
||||
for k := range categories {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// GetServicePlanNames returns the service plan names included in the specified map
|
||||
func GetServicePlanNames(stringMap map[string]scv1beta1.ClusterServicePlan) (keys []string) {
|
||||
keys = make([]string, len(stringMap))
|
||||
|
||||
i := 0
|
||||
for k := range stringMap {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// getServiceClassMap converts the specified array of service classes to a name-service class map
|
||||
func getServiceClassMap(classes []scv1beta1.ClusterServiceClass) (classMap map[string]scv1beta1.ClusterServiceClass) {
|
||||
classMap = make(map[string]scv1beta1.ClusterServiceClass, len(classes))
|
||||
for _, v := range classes {
|
||||
classMap[v.Spec.ExternalName] = v
|
||||
}
|
||||
|
||||
return classMap
|
||||
}
|
||||
|
||||
// getServiceClassNames retrieves the keys (service class names) of the specified name-service class mappings
|
||||
func getServiceClassNames(stringMap map[string]scv1beta1.ClusterServiceClass) (keys []string) {
|
||||
keys = make([]string, len(stringMap))
|
||||
|
||||
i := 0
|
||||
for k := range stringMap {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// SelectPlanNameInteractively lets the user to select the plan name from possible options, specifying which text should appear
|
||||
// in the prompt
|
||||
func SelectPlanNameInteractively(plans map[string]scv1beta1.ClusterServicePlan, promptText string) (plan string) {
|
||||
prompt := &survey.Select{
|
||||
Message: promptText,
|
||||
Options: GetServicePlanNames(plans),
|
||||
}
|
||||
err := survey.AskOne(prompt, &plan, nil)
|
||||
ui.HandleError(err)
|
||||
return plan
|
||||
}
|
||||
|
||||
// EnterServiceNameInteractively lets the user enter the name of the service instance to create, defaulting to the provided
|
||||
// default value and specifying both the prompt text and validation function for the name
|
||||
func EnterServiceNameInteractively(defaultValue, promptText string, validateName validation.Validator) (serviceName string) {
|
||||
// if only one arg is given, ask to Name the service providing the class Name as default
|
||||
instancePrompt := &survey.Input{
|
||||
Message: promptText,
|
||||
Default: defaultValue,
|
||||
}
|
||||
err := survey.AskOne(instancePrompt, &serviceName, survey.Validator(validateName))
|
||||
ui.HandleError(err)
|
||||
return serviceName
|
||||
}
|
||||
|
||||
// SelectClassInteractively lets the user select target service class from possible options, first filtering by categories then
|
||||
// by class name
|
||||
func SelectClassInteractively(classesByCategory map[string][]scv1beta1.ClusterServiceClass) (class scv1beta1.ClusterServiceClass, serviceType string) {
|
||||
var category string
|
||||
prompt := &survey.Select{
|
||||
Message: "Which kind of service do you wish to create",
|
||||
Options: getServiceClassesCategories(classesByCategory),
|
||||
}
|
||||
err := survey.AskOne(prompt, &category, survey.Required)
|
||||
ui.HandleError(err)
|
||||
|
||||
classes := getServiceClassMap(classesByCategory[category])
|
||||
|
||||
// make a new displayClassInfo function available to survey templates to be able to add class information to the display
|
||||
displayClassInfo := "displayClassInfo"
|
||||
core.TemplateFuncs[displayClassInfo] = func(index int, pageEntries []string) string {
|
||||
if index >= 0 && len(pageEntries) > index {
|
||||
selected := pageEntries[index]
|
||||
class := classes[selected]
|
||||
return ansi.ColorCode("default+bu") + "Service class details" + ansi.ColorCode("reset") + ":\n" +
|
||||
classInfoItem("Name", class.GetExternalName()) +
|
||||
classInfoItem("Description", class.GetDescription()) +
|
||||
classInfoItem("Long", getLongDescription(class))
|
||||
}
|
||||
return "No matching entry"
|
||||
}
|
||||
defer delete(core.TemplateFuncs, displayClassInfo)
|
||||
|
||||
// record original template and defer restoring it once done
|
||||
original := survey.SelectQuestionTemplate
|
||||
defer restoreOriginalTemplate(original)
|
||||
|
||||
// add more information about the currently selected class
|
||||
survey.SelectQuestionTemplate = original + `
|
||||
{{- if not .ShowAnswer}}
|
||||
{{$classInfo:=(displayClassInfo .SelectedIndex .PageEntries)}}
|
||||
{{- if $classInfo}}
|
||||
{{$classInfo}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
prompt = &survey.Select{
|
||||
Message: "Which " + category + " service class should we use",
|
||||
Options: getServiceClassNames(classes),
|
||||
}
|
||||
|
||||
err = survey.AskOne(prompt, &serviceType, survey.Required)
|
||||
ui.HandleError(err)
|
||||
|
||||
return classes[serviceType], serviceType
|
||||
}
|
||||
|
||||
// classInfoItem computes how a given service class information item should be displayed
|
||||
func classInfoItem(name, value string) string {
|
||||
// wrap value if needed accounting for size of value "header" (its name)
|
||||
value = wrapIfNeeded(value, len(name)+3)
|
||||
|
||||
if len(value) > 0 {
|
||||
// display the name using the default color, in bold and then reset style right after
|
||||
return StyledOutput(name, "default+b") + ": " + value + "\n"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// StyledOutput returns an ANSI color code to style the specified text accordingly, issuing a reset code when done using the
|
||||
// https://github.com/mgutz/ansi#style-format format
|
||||
func StyledOutput(text, style string) string {
|
||||
return ansi.ColorCode(style) + text + ansi.ColorCode("reset")
|
||||
}
|
||||
|
||||
const defaultColumnNumberBeforeWrap = 80
|
||||
|
||||
// wrapIfNeeded wraps the given string taking the given prefix size into account based on the width of the terminal (or
|
||||
// defaultColumnNumberBeforeWrap if terminal size cannot be determined).
|
||||
func wrapIfNeeded(value string, prefixSize int) string {
|
||||
// get the width of the terminal
|
||||
width, _, err := terminal2.GetSize(0)
|
||||
if width == 0 || err != nil {
|
||||
// if for some reason we couldn't get the size use default value
|
||||
width = defaultColumnNumberBeforeWrap
|
||||
}
|
||||
|
||||
// if the value length is greater than the width, wrap it
|
||||
// note that we need to account for the size of the name of the value being displayed before the value (i.e. its name)
|
||||
valueSize := len(value)
|
||||
if valueSize+prefixSize >= width {
|
||||
// look at each line of the value
|
||||
split := strings.Split(value, "\n")
|
||||
for index, line := range split {
|
||||
// for each line, trim it and split it in space-separated clusters ("words")
|
||||
line = strings.TrimSpace(line)
|
||||
words := strings.Split(line, " ")
|
||||
newLine := ""
|
||||
lineSize := 0
|
||||
|
||||
for _, word := range words {
|
||||
if lineSize+len(word)+1+prefixSize < width {
|
||||
// concatenate word to the new computed line only if adding it to the line won't make it larger than acceptable
|
||||
newLine = newLine + " " + word
|
||||
lineSize = lineSize + 1 + len(word) // accumulate the line size
|
||||
} else {
|
||||
// otherwise, break the line and add the word on a new "line"
|
||||
newLine = newLine + "\n" + word
|
||||
lineSize = len(word) // reset the line size
|
||||
}
|
||||
}
|
||||
// replace the initial line with the new computed version
|
||||
split[index] = strings.TrimSpace(newLine)
|
||||
}
|
||||
// compute the new value by joining all the modified lines
|
||||
value = strings.Join(split, "\n")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// restoreOriginalTemplate restores the original survey template once we're done with the display
|
||||
func restoreOriginalTemplate(original string) {
|
||||
survey.SelectQuestionTemplate = original
|
||||
}
|
||||
|
||||
// Convert the provided ClusterServiceClass to its UI representation
|
||||
func getLongDescription(class scv1beta1.ClusterServiceClass) (longDescription string) {
|
||||
extension := class.Spec.ExternalMetadata
|
||||
if extension != nil {
|
||||
var meta map[string]interface{}
|
||||
err := json.Unmarshal(extension.Raw, &meta)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("Unable unmarshal Extension metadata for ClusterServiceClass '%v'", class.Spec.ExternalName)
|
||||
}
|
||||
if val, ok := meta["longDescription"]; ok {
|
||||
longDescription = val.(string)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EnterServicePropertiesInteractively lets the user enter the properties specified by the provided plan if not already
|
||||
// specified by the passed values
|
||||
func EnterServicePropertiesInteractively(svcPlan scv1beta1.ClusterServicePlan) (values map[string]string) {
|
||||
return enterServicePropertiesInteractively(svcPlan)
|
||||
}
|
||||
|
||||
// enterServicePropertiesInteractively lets user enter the properties interactively using the specified Stdio instance (useful
|
||||
// for testing purposes)
|
||||
func enterServicePropertiesInteractively(svcPlan scv1beta1.ClusterServicePlan, stdio ...terminal.Stdio) (values map[string]string) {
|
||||
planDetails, _ := service.NewServicePlan(svcPlan)
|
||||
|
||||
properties := make(map[string]service.ServicePlanParameter, len(planDetails.Parameters))
|
||||
for _, v := range planDetails.Parameters {
|
||||
properties[v.Name] = v
|
||||
}
|
||||
|
||||
values = make(map[string]string, len(properties))
|
||||
|
||||
sort.Sort(planDetails.Parameters)
|
||||
|
||||
// first deal with required properties
|
||||
for _, prop := range planDetails.Parameters {
|
||||
if prop.Required {
|
||||
addValueFor(prop, values, stdio...)
|
||||
// remove property from list of properties to consider
|
||||
delete(properties, prop.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// finally check if we still have plan properties that have not been considered
|
||||
if len(properties) > 0 && ui.Proceed("Provide values for non-required properties", stdio...) {
|
||||
for _, prop := range properties {
|
||||
addValueFor(prop, values, stdio...)
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func addValueFor(prop service.ServicePlanParameter, values map[string]string, stdio ...terminal.Stdio) {
|
||||
var result string
|
||||
prompt := &survey.Input{
|
||||
Message: fmt.Sprintf("Enter a value for %s property %s:", prop.Type, propDesc(prop)),
|
||||
}
|
||||
|
||||
if len(stdio) == 1 {
|
||||
prompt.WithStdio(stdio[0])
|
||||
}
|
||||
|
||||
if len(prop.Default) > 0 {
|
||||
prompt.Default = prop.Default
|
||||
}
|
||||
|
||||
err := survey.AskOne(prompt, &result, ui.GetValidatorFor(prop.AsValidatable()))
|
||||
ui.HandleError(err)
|
||||
values[prop.Name] = result
|
||||
}
|
||||
|
||||
// propDesc computes a human-readable description of the specified property
|
||||
func propDesc(prop service.ServicePlanParameter) string {
|
||||
msg := ""
|
||||
if len(prop.Title) > 0 {
|
||||
msg = prop.Title
|
||||
} else if len(prop.Description) > 0 {
|
||||
msg = prop.Description
|
||||
}
|
||||
|
||||
if len(msg) > 0 {
|
||||
msg = " (" + strings.TrimSpace(msg) + ")"
|
||||
}
|
||||
|
||||
return prop.Name + msg
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/Netflix/go-expect"
|
||||
beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
|
||||
"github.com/openshift/odo/pkg/service"
|
||||
"github.com/openshift/odo/pkg/testingutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestGetCategories(t *testing.T) {
|
||||
t.Run("getServiceClassesCategories should work", func(t *testing.T) {
|
||||
foo := testingutil.FakeClusterServiceClass("foo", "footag", "footag2")
|
||||
bar := testingutil.FakeClusterServiceClass("bar", "")
|
||||
boo := testingutil.FakeClusterServiceClass("boo")
|
||||
classes := map[string][]beta1.ClusterServiceClass{"footag": {foo}, "other": {bar, boo}}
|
||||
|
||||
categories := getServiceClassesCategories(classes)
|
||||
expected := []string{"footag", "other"}
|
||||
if !reflect.DeepEqual(expected, categories) {
|
||||
t.Errorf("test failed, expected %v, got %v", expected, categories)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetServicePlanNames(t *testing.T) {
|
||||
t.Run("GetServicePlanNames should work", func(t *testing.T) {
|
||||
foo := testingutil.FakeClusterServicePlan("foo", 1)
|
||||
bar := testingutil.FakeClusterServicePlan("bar", 2)
|
||||
boo := testingutil.FakeClusterServicePlan("boo", 3)
|
||||
|
||||
plans := GetServicePlanNames(map[string]beta1.ClusterServicePlan{"foo": foo, "bar": bar, "boo": boo})
|
||||
expected := []string{"bar", "boo", "foo"}
|
||||
if !reflect.DeepEqual(expected, plans) {
|
||||
t.Errorf("test failed, expected %v, got %v", expected, plans)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWrapIfNeeded(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
prefixSize int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty string, empty prefix",
|
||||
input: "",
|
||||
prefixSize: 0,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "short string, empty prefix should not be wrapped",
|
||||
input: "foo bar baz",
|
||||
prefixSize: 0,
|
||||
expected: "foo bar baz",
|
||||
},
|
||||
{
|
||||
name: "short string, empty prefix should not be wrapped with default width",
|
||||
input: "foo bar baz",
|
||||
prefixSize: 2,
|
||||
expected: "foo bar baz",
|
||||
},
|
||||
{
|
||||
name: "short string, long prefix should wrap",
|
||||
input: "foo bar baz",
|
||||
prefixSize: 78,
|
||||
expected: "foo\nbar\nbaz",
|
||||
},
|
||||
{
|
||||
name: "long string, empty prefix should wrap",
|
||||
input: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789",
|
||||
prefixSize: 0,
|
||||
expected: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n0123456789",
|
||||
},
|
||||
{
|
||||
name: "long string, short prefix should wrap",
|
||||
input: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789",
|
||||
prefixSize: 2,
|
||||
expected: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n0123456789",
|
||||
},
|
||||
{
|
||||
name: "long string, longer prefix should wrap more",
|
||||
input: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789",
|
||||
prefixSize: 10,
|
||||
expected: "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789\n0123456789 0123456789",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output := wrapIfNeeded(tt.input, tt.prefixSize)
|
||||
if tt.expected != output {
|
||||
t.Errorf("test failed, expected %s, got %s", tt.expected, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestEnterServicePropertiesInteractively(t *testing.T) {
|
||||
// TODO: this test is currently skipped because it is not currently working properly. :(
|
||||
t.Skip("TODO: Skip this test until we can figure out what is wrong with it")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
servicePlan beta1.ClusterServicePlan
|
||||
expectedValues map[string]string
|
||||
}{
|
||||
{
|
||||
name: "test 1 : with correct values",
|
||||
servicePlan: testingutil.FakeClusterServicePlan("dev", 1),
|
||||
expectedValues: map[string]string{
|
||||
"PLAN_DATABASE_URI": "someuri",
|
||||
"PLAN_DATABASE_USERNAME": "default",
|
||||
"PLAN_DATABASE_PASSWORD": "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
plan := tt.servicePlan
|
||||
|
||||
valuesPtr := new(map[string]string)
|
||||
testingutil.RunTest(t, func(c *expect.Console) {
|
||||
_, _ = c.ExpectString("Enter a value for string property PLAN_DATABASE_PASSWORD:")
|
||||
_, _ = c.SendLine("foo")
|
||||
_, _ = c.ExpectString("Enter a value for string property PLAN_DATABASE_URI:")
|
||||
_, _ = c.SendLine("")
|
||||
_, _ = c.ExpectString("Enter a value for string property PLAN_DATABASE_USERNAME:")
|
||||
_, _ = c.SendLine("")
|
||||
_, _ = c.ExpectString("Provide values for non-required properties")
|
||||
_, _ = c.SendLine("")
|
||||
_, _ = c.ExpectEOF()
|
||||
}, func(stdio terminal.Stdio) error {
|
||||
values := enterServicePropertiesInteractively(plan, stdio)
|
||||
valuesPtr = &values
|
||||
return nil
|
||||
})
|
||||
|
||||
require.Equal(t, tt.expectedValues, *valuesPtr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLongDescription(t *testing.T) {
|
||||
desc := testingutil.FakeClusterServiceClass("foo")
|
||||
desc.Spec.ExternalMetadata = testingutil.SingleValuedRawExtension("longDescription", "description")
|
||||
empty := testingutil.FakeClusterServiceClass("foo")
|
||||
empty.Spec.ExternalMetadata = testingutil.SingleValuedRawExtension("longDescription", "")
|
||||
tests := []struct {
|
||||
name string
|
||||
input beta1.ClusterServiceClass
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no metadata",
|
||||
input: testingutil.FakeClusterServiceClass("foo"),
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
input: desc,
|
||||
expected: "description",
|
||||
},
|
||||
{
|
||||
name: "empty description",
|
||||
input: empty,
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output := getLongDescription(tt.input)
|
||||
if tt.expected != output {
|
||||
t.Errorf("test failed, expected %s, got %s", tt.expected, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPropDesc(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
prop service.ServicePlanParameter
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
prop: service.ServicePlanParameter{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "name only",
|
||||
prop: service.ServicePlanParameter{Name: "foo"},
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "with title",
|
||||
prop: service.ServicePlanParameter{Name: "foo", Title: "title"},
|
||||
expected: "foo (title)",
|
||||
},
|
||||
{
|
||||
name: "with description",
|
||||
prop: service.ServicePlanParameter{Name: "foo", Description: "desc"},
|
||||
expected: "foo (desc)",
|
||||
},
|
||||
{
|
||||
name: "with title and description",
|
||||
prop: service.ServicePlanParameter{Name: "foo", Description: "desc", Title: "title"},
|
||||
expected: "foo (title)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output := propDesc(tt.prop)
|
||||
if tt.expected != output {
|
||||
t.Errorf("test failed, expected %v, got %v", tt.expected, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
svc "github.com/openshift/odo/pkg/service"
|
||||
|
||||
"github.com/openshift/odo/pkg/odo/cli/component"
|
||||
"github.com/openshift/odo/pkg/util"
|
||||
)
|
||||
@@ -22,15 +20,3 @@ func validDevfileDirectory(componentContext string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decideBackend returns the type of service provider backend to be used
|
||||
func decideBackend(arg string) ServiceProviderBackend {
|
||||
_, _, err := svc.SplitServiceKindName(arg)
|
||||
if err != nil {
|
||||
// failure to split provided name into two; hence ServiceCatalogBackend
|
||||
return NewServiceCatalogBackend()
|
||||
} else {
|
||||
// provided name adheres to the format <operator-type>/<crd-name>; hence OperatorBackend
|
||||
return NewOperatorBackend()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
applabels "github.com/openshift/odo/pkg/application/labels"
|
||||
|
||||
"github.com/openshift/odo/pkg/application"
|
||||
@@ -11,7 +8,6 @@ import (
|
||||
"github.com/openshift/odo/pkg/component"
|
||||
"github.com/openshift/odo/pkg/config"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
"github.com/openshift/odo/pkg/service"
|
||||
"github.com/openshift/odo/pkg/storage"
|
||||
"github.com/openshift/odo/pkg/url"
|
||||
"github.com/openshift/odo/pkg/util"
|
||||
@@ -19,128 +15,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ServiceCompletionHandler provides service name completion for the current project and application
|
||||
var ServiceCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
completions = make([]string, 0)
|
||||
|
||||
services, err := service.List(context.Client, context.Application)
|
||||
if err != nil {
|
||||
return completions
|
||||
}
|
||||
|
||||
for _, class := range services.Items {
|
||||
if args.commands[class.ObjectMeta.Name] {
|
||||
return nil
|
||||
}
|
||||
completions = append(completions, class.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ServiceClassCompletionHandler provides catalog service class name completion
|
||||
var ServiceClassCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
completions = make([]string, 0)
|
||||
services, err := context.Client.GetKubeClient().ListClusterServiceClasses()
|
||||
if err != nil {
|
||||
complete.Log("error retrieving services")
|
||||
return completions
|
||||
}
|
||||
|
||||
complete.Log(fmt.Sprintf("found %d services", len(services)))
|
||||
for _, class := range services {
|
||||
if args.commands[class.Spec.ExternalName] {
|
||||
return nil
|
||||
}
|
||||
completions = append(completions, class.Spec.ExternalName)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ServicePlanCompletionHandler provides completion for the the plan of a selected service
|
||||
var ServicePlanCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
completions = make([]string, 0)
|
||||
// if we have less than two arguments, it means the user didn't specify the name of the service
|
||||
// meaning that there is no point in providing suggestions
|
||||
if len(args.original.Completed) < 2 {
|
||||
complete.Log("Couldn't extract the service name")
|
||||
return completions
|
||||
}
|
||||
|
||||
inputServiceName := args.original.Completed[1]
|
||||
|
||||
complete.Log(fmt.Sprintf("Using input: serviceName = %s", inputServiceName))
|
||||
|
||||
clusterServiceClass, err := context.Client.GetKubeClient().GetClusterServiceClass(inputServiceName)
|
||||
if err != nil {
|
||||
complete.Log("Error retrieving details of service")
|
||||
return completions
|
||||
}
|
||||
|
||||
servicePlans, err := context.Client.GetKubeClient().ListClusterServicePlansByServiceName(clusterServiceClass.Name)
|
||||
if err != nil {
|
||||
complete.Log("Error retrieving details of plans of service")
|
||||
return completions
|
||||
}
|
||||
|
||||
for _, servicePlan := range servicePlans {
|
||||
completions = append(completions, servicePlan.Spec.ExternalName)
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
// ServiceParameterCompletionHandler provides completion for the parameter names of a selected service and plan
|
||||
var ServiceParameterCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
completions = make([]string, 0)
|
||||
if len(args.original.Completed) < 2 {
|
||||
complete.Log("Couldn't extract the service name")
|
||||
return completions
|
||||
}
|
||||
|
||||
inputServiceName := args.original.Completed[1]
|
||||
inputPlanName := args.flagValues["plan"]
|
||||
|
||||
complete.Log(fmt.Sprintf("Using input: serviceName = %s, servicePlan = %s", inputServiceName, inputPlanName))
|
||||
|
||||
_, servicePlans, err := service.GetServiceClassAndPlans(context.Client, inputServiceName)
|
||||
if err != nil {
|
||||
complete.Log("Error retrieving details of service")
|
||||
return completions
|
||||
}
|
||||
|
||||
var matchingServicePlan *service.ServicePlan = nil
|
||||
if len(servicePlans) == 0 {
|
||||
complete.Log("Service has no plans so no parameters can be found")
|
||||
return completions
|
||||
} else if len(servicePlans) == 1 && inputPlanName == "" {
|
||||
matchingServicePlan = &servicePlans[0]
|
||||
} else {
|
||||
for _, sp := range servicePlans {
|
||||
servicePlan := sp
|
||||
if servicePlan.Name == inputPlanName {
|
||||
matchingServicePlan = &servicePlan
|
||||
break
|
||||
}
|
||||
}
|
||||
if matchingServicePlan == nil {
|
||||
complete.Log("No service plan for the service matched the supplied plan name")
|
||||
return completions
|
||||
}
|
||||
}
|
||||
|
||||
alreadyAddedParameters := args.flagValues["parameters"]
|
||||
for _, servicePlanParameter := range matchingServicePlan.Parameters {
|
||||
// don't add the parameter if it's already on the command line
|
||||
if !strings.Contains(alreadyAddedParameters, servicePlanParameter.Name) {
|
||||
completions = append(completions, servicePlanParameter.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
// AppCompletionHandler provides completion for the app commands
|
||||
var AppCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
completions = make([]string, 0)
|
||||
@@ -304,99 +178,6 @@ var CreateCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context
|
||||
return completions
|
||||
}
|
||||
|
||||
// LinkCompletionHandler provides completion for the odo link command
|
||||
// The function returns both components and services
|
||||
var LinkCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
components, err := component.GetComponentNames(context.Client, context.Application)
|
||||
if err != nil {
|
||||
return completions
|
||||
}
|
||||
|
||||
services, err := service.List(context.Client, context.Application)
|
||||
if err != nil {
|
||||
return completions
|
||||
}
|
||||
|
||||
completions = make([]string, 0, len(components)+len(services.Items))
|
||||
for _, component := range components {
|
||||
// we found the name in the list which means
|
||||
// that the name has been already selected by the user so no need to suggest more
|
||||
if val, ok := args.commands[component]; ok && val {
|
||||
return nil
|
||||
}
|
||||
// we don't want to show the selected component as a target for linking, so we remove it from the suggestions
|
||||
if component != context.Component() {
|
||||
completions = append(completions, component)
|
||||
}
|
||||
}
|
||||
|
||||
for _, service := range services.Items {
|
||||
// we found the name in the list which means
|
||||
// that the name has been already selected by the user so no need to suggest more
|
||||
if val, ok := args.commands[service.ObjectMeta.Name]; ok && val {
|
||||
return nil
|
||||
}
|
||||
completions = append(completions, service.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
// LinkCompletionHandler provides completion for the odo unlink command
|
||||
// The function returns both components and services
|
||||
var UnlinkCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
// first we need to retrieve the current component
|
||||
comp, err := component.GetPushedComponent(context.Client, context.Component(), context.Application)
|
||||
if err != nil {
|
||||
return completions
|
||||
}
|
||||
|
||||
components, err := component.GetComponentNames(context.Client, context.Application)
|
||||
if err != nil {
|
||||
return completions
|
||||
}
|
||||
|
||||
services, err := service.List(context.Client, context.Application)
|
||||
if err != nil {
|
||||
return completions
|
||||
}
|
||||
|
||||
completions = make([]string, 0, len(components)+len(services.Items))
|
||||
secretMounts := comp.GetLinkedSecrets()
|
||||
for _, component := range components {
|
||||
// we found the name in the list which means
|
||||
// that the name has been already selected by the user so no need to suggest more
|
||||
if val, ok := args.commands[component]; ok && val {
|
||||
return nil
|
||||
}
|
||||
// we don't want to show the selected component as a target for linking, so we remove it from the suggestions
|
||||
if component != context.Component() {
|
||||
// we also need to make sure that this component has been linked to the current component
|
||||
for _, secret := range secretMounts {
|
||||
if strings.Contains(secret.SecretName, component) {
|
||||
completions = append(completions, component)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, service := range services.Items {
|
||||
// we found the name in the list which means
|
||||
// that the name has been already selected by the user so no need to suggest more
|
||||
if val, ok := args.commands[service.Name]; ok && val {
|
||||
return nil
|
||||
}
|
||||
// we also need to make sure that this component has been linked to the current component
|
||||
for _, secret := range secretMounts {
|
||||
if strings.Contains(secret.SecretName, service.Name) {
|
||||
completions = append(completions, service.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
// ComponentNameCompletionHandler provides component name completion
|
||||
var ComponentNameCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
|
||||
completions = make([]string, 0)
|
||||
|
||||
@@ -1,748 +0,0 @@
|
||||
package completion
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/openshift/odo/pkg/component"
|
||||
"github.com/openshift/odo/pkg/testingutil"
|
||||
"github.com/posener/complete"
|
||||
|
||||
scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
|
||||
appsv1 "github.com/openshift/api/apps/v1"
|
||||
applabels "github.com/openshift/odo/pkg/application/labels"
|
||||
componentlabels "github.com/openshift/odo/pkg/component/labels"
|
||||
"github.com/openshift/odo/pkg/occlient"
|
||||
"github.com/openshift/odo/pkg/odo/genericclioptions"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ktesting "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
func TestServicePlanCompletionHandler(t *testing.T) {
|
||||
serviceClassList := &scv1beta1.ClusterServiceClassList{
|
||||
Items: []scv1beta1.ClusterServiceClass{testingutil.FakeClusterServiceClass("class name", "dummy")},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
returnedServiceClass *scv1beta1.ClusterServiceClassList
|
||||
returnedServicePlan []scv1beta1.ClusterServicePlan
|
||||
output []string
|
||||
parsedArgs parsedArgs
|
||||
}{
|
||||
{
|
||||
name: "Case 0: no service name supplied",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create"},
|
||||
},
|
||||
},
|
||||
output: []string{},
|
||||
},
|
||||
{
|
||||
name: "Case 1: single plan exists",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
},
|
||||
output: []string{"default"},
|
||||
},
|
||||
{
|
||||
name: "Case 2: multiple plans exist",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{
|
||||
testingutil.FakeClusterServicePlan("plan1", 1),
|
||||
testingutil.FakeClusterServicePlan("plan2", 2),
|
||||
},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
},
|
||||
output: []string{"plan1", "plan2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
context := genericclioptions.NewFakeContext("project", "app", "component", client, nil)
|
||||
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, tt.returnedServiceClass, nil
|
||||
})
|
||||
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceplans", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &scv1beta1.ClusterServicePlanList{Items: tt.returnedServicePlan}, nil
|
||||
})
|
||||
|
||||
completions := ServicePlanCompletionHandler(nil, tt.parsedArgs, context)
|
||||
|
||||
// Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important)
|
||||
sort.Strings(completions)
|
||||
sort.Strings(tt.output)
|
||||
|
||||
if !reflect.DeepEqual(tt.output, completions) {
|
||||
t.Errorf("expected output: %#v,got: %#v", tt.output, completions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceParameterCompletionHandler(t *testing.T) {
|
||||
serviceClassList := &scv1beta1.ClusterServiceClassList{
|
||||
Items: []scv1beta1.ClusterServiceClass{testingutil.FakeClusterServiceClass("class name", "dummy")},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
returnedServiceClass *scv1beta1.ClusterServiceClassList
|
||||
returnedServicePlan []scv1beta1.ClusterServicePlan
|
||||
output []string
|
||||
parsedArgs parsedArgs
|
||||
}{
|
||||
{
|
||||
name: "Case 0: no service name supplied",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create"},
|
||||
},
|
||||
},
|
||||
output: []string{},
|
||||
},
|
||||
{
|
||||
name: "Case 1: no plan supplied and single plan exists",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
},
|
||||
output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_USERNAME", "PLAN_DATABASE_PASSWORD", "SOME_OTHER"},
|
||||
},
|
||||
{
|
||||
name: "Case 2: no plan supplied and multiple plans exists",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{
|
||||
testingutil.FakeClusterServicePlan("plan1", 1),
|
||||
testingutil.FakeClusterServicePlan("plan2", 2),
|
||||
},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
},
|
||||
output: []string{},
|
||||
},
|
||||
{
|
||||
name: "Case 3: plan supplied but doesn't match",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
flagValues: map[string]string{"plan": "other"},
|
||||
},
|
||||
output: []string{},
|
||||
},
|
||||
{
|
||||
name: "Case 4: matching plan supplied and no other parameters supplied",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{
|
||||
testingutil.FakeClusterServicePlan("plan2", 2),
|
||||
testingutil.FakeClusterServicePlan("plan1", 1),
|
||||
},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
flagValues: map[string]string{"plan": "plan1"},
|
||||
},
|
||||
output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_USERNAME", "PLAN_DATABASE_PASSWORD", "SOME_OTHER"},
|
||||
},
|
||||
{
|
||||
name: "Case 5: no plan supplied but some other parameters supplied",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
flagValues: map[string]string{"parameters": "[PLAN_DATABASE_USERNAME, SOME_OTHER]"},
|
||||
},
|
||||
output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_PASSWORD"},
|
||||
},
|
||||
{
|
||||
name: "Case 6: matching plan supplied but some other parameters supplied",
|
||||
returnedServiceClass: serviceClassList,
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{testingutil.FakeClusterServicePlan("default", 1)},
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create", "class name"},
|
||||
},
|
||||
flagValues: map[string]string{"plan": "default", "parameters": "[PLAN_DATABASE_USERNAME]"},
|
||||
},
|
||||
output: []string{"PLAN_DATABASE_URI", "PLAN_DATABASE_PASSWORD", "SOME_OTHER"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
context := genericclioptions.NewFakeContext("project", "app", "component", client, nil)
|
||||
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, tt.returnedServiceClass, nil
|
||||
})
|
||||
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceplans", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &scv1beta1.ClusterServicePlanList{Items: tt.returnedServicePlan}, nil
|
||||
})
|
||||
|
||||
completions := ServiceParameterCompletionHandler(nil, tt.parsedArgs, context)
|
||||
|
||||
// Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important)
|
||||
sort.Strings(completions)
|
||||
sort.Strings(tt.output)
|
||||
|
||||
if !reflect.DeepEqual(tt.output, completions) {
|
||||
t.Errorf("expected output: %#v,got: %#v", tt.output, completions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkCompletionHandler(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
component string
|
||||
dcList appsv1.DeploymentConfigList
|
||||
serviceList scv1beta1.ServiceInstanceList
|
||||
output []string
|
||||
}{
|
||||
{
|
||||
name: "Case 1: both components and services are present",
|
||||
component: "frontend",
|
||||
serviceList: scv1beta1.ServiceInstanceList{
|
||||
Items: []scv1beta1.ServiceInstance{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mysql-persistent",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "mysql-persistent",
|
||||
componentlabels.ComponentTypeLabel: "mysql-persistent",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
component.ComponentSourceTypeAnnotation: "local",
|
||||
},
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "mysql-persistent",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "ProvisionedSuccessfully",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-ephemeral",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "postgresql-ephemeral",
|
||||
componentlabels.ComponentTypeLabel: "postgresql-ephemeral",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
component.ComponentSourceTypeAnnotation: "local",
|
||||
},
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "postgresql-ephemeral",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "Provisioning",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dcList: appsv1.DeploymentConfigList{
|
||||
Items: []appsv1.DeploymentConfig{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "backend",
|
||||
componentlabels.ComponentTypeLabel: "java",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
component.ComponentSourceTypeAnnotation: "local",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dummyContainer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "frontend",
|
||||
componentlabels.ComponentTypeLabel: "nodejs",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
component.ComponentSourceTypeAnnotation: "local",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dummyContainer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// make sure that the 'component' is not part of the suggestions
|
||||
output: []string{"backend", "mysql-persistent", "postgresql-ephemeral"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
parsedArgs := parsedArgs{
|
||||
commands: make(map[string]bool),
|
||||
}
|
||||
context := genericclioptions.NewFakeContext("project", "app", tt.component, client, nil)
|
||||
|
||||
fakeClientSet.ProjClientset.PrependReactor("get", "projects", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &testingutil.FakeOnlyOneExistingProjects().Items[0], nil
|
||||
})
|
||||
|
||||
//fake the services
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.serviceList, nil
|
||||
})
|
||||
|
||||
//fake the dcs
|
||||
fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.dcList, nil
|
||||
})
|
||||
|
||||
for i := range tt.dcList.Items {
|
||||
fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.dcList.Items[i], nil
|
||||
})
|
||||
}
|
||||
|
||||
completions := LinkCompletionHandler(nil, parsedArgs, context)
|
||||
sort.Strings(completions)
|
||||
|
||||
if !reflect.DeepEqual(tt.output, completions) {
|
||||
t.Errorf("expected output: %#v,got: %#v", tt.output, completions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnlinkCompletionHandler(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
component string
|
||||
dcList appsv1.DeploymentConfigList
|
||||
serviceList scv1beta1.ServiceInstanceList
|
||||
output []string
|
||||
}{
|
||||
{
|
||||
name: "Case 1: both components and services are present",
|
||||
component: "frontend",
|
||||
serviceList: scv1beta1.ServiceInstanceList{
|
||||
Items: []scv1beta1.ServiceInstance{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mysql-persistent",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "mysql-persistent",
|
||||
componentlabels.ComponentTypeLabel: "mysql-persistent",
|
||||
},
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "mysql-persistent",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "ProvisionedSuccessfully",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-ephemeral",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "postgresql-ephemeral",
|
||||
componentlabels.ComponentTypeLabel: "postgresql-ephemeral",
|
||||
},
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "postgresql-ephemeral",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "ProvisionedSuccessfully",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dcList: appsv1.DeploymentConfigList{
|
||||
Items: []appsv1.DeploymentConfig{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "backend-app",
|
||||
Namespace: "project",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "backend",
|
||||
componentlabels.ComponentTypeLabel: "java",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
component.ComponentSourceTypeAnnotation: "local",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dummyContainer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "backend2-app",
|
||||
Namespace: "project",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "backend2",
|
||||
componentlabels.ComponentTypeLabel: "java",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
component.ComponentSourceTypeAnnotation: "local",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dummyContainer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "frontend-app",
|
||||
Namespace: "project",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "frontend",
|
||||
componentlabels.ComponentTypeLabel: "nodejs",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
component.ComponentSourceTypeAnnotation: "local",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dummyContainer",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "postgresql-ephemeral"},
|
||||
},
|
||||
},
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "backend-8080"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// make sure that the 'component' is not part of the suggestions and that only actually linked components/services show up
|
||||
output: []string{"backend", "postgresql-ephemeral"},
|
||||
},
|
||||
}
|
||||
|
||||
p := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-ephemeral",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
parsedArgs := parsedArgs{
|
||||
commands: make(map[string]bool),
|
||||
}
|
||||
context := genericclioptions.NewFakeContext("project", "app", tt.component, client, nil)
|
||||
|
||||
//fake the services
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.serviceList, nil
|
||||
})
|
||||
|
||||
fakeClientSet.ProjClientset.PrependReactor("get", "projects", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &testingutil.FakeOnlyOneExistingProjects().Items[0], nil
|
||||
})
|
||||
|
||||
//fake the dcs
|
||||
fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.dcList, nil
|
||||
})
|
||||
|
||||
for i := range tt.dcList.Items {
|
||||
fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.dcList.Items[i], nil
|
||||
})
|
||||
}
|
||||
|
||||
fakeClientSet.Kubernetes.PrependReactor("get", "secrets", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &p, nil
|
||||
})
|
||||
|
||||
completions := UnlinkCompletionHandler(nil, parsedArgs, context)
|
||||
sort.Strings(completions)
|
||||
|
||||
if !reflect.DeepEqual(tt.output, completions) {
|
||||
t.Errorf("expected output: %#v,got: %#v", tt.output, completions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceCompletionHandler(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
returnedServiceClassInstances *scv1beta1.ServiceInstanceList
|
||||
output []string
|
||||
parsedArgs parsedArgs
|
||||
}{
|
||||
{
|
||||
name: "test case 1: no service instance exists and name not typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"delete"},
|
||||
},
|
||||
},
|
||||
returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{},
|
||||
output: []string{},
|
||||
},
|
||||
{
|
||||
name: "test case 2: one service class instance exists and name not typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"delete"},
|
||||
},
|
||||
},
|
||||
returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{
|
||||
Items: []scv1beta1.ServiceInstance{
|
||||
testingutil.FakeServiceClassInstance("service-1", "mariadb-apb", "default", "ProvisionedSuccessfully"),
|
||||
},
|
||||
},
|
||||
output: []string{"service-1"},
|
||||
},
|
||||
{
|
||||
name: "test case 3: multiple service class instance exists and name not typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"delete"},
|
||||
},
|
||||
},
|
||||
returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{
|
||||
Items: []scv1beta1.ServiceInstance{
|
||||
testingutil.FakeServiceClassInstance("service-1", "mariadb-apb", "default", "ProvisionedSuccessfully"),
|
||||
testingutil.FakeServiceClassInstance("service-2", "mariadb-apb", "prod", "ProvisionedSuccessfully"),
|
||||
},
|
||||
},
|
||||
output: []string{"service-1", "service-2"},
|
||||
},
|
||||
{
|
||||
name: "test case 4: multiple service class instance exists and name fully typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"delete"},
|
||||
},
|
||||
commands: map[string]bool{"service-1": true},
|
||||
},
|
||||
returnedServiceClassInstances: &scv1beta1.ServiceInstanceList{
|
||||
Items: []scv1beta1.ServiceInstance{
|
||||
testingutil.FakeServiceClassInstance("service-1", "mariadb-apb", "default", "ProvisionedSuccessfully"),
|
||||
testingutil.FakeServiceClassInstance("service-2", "mariadb-apb", "prod", "ProvisionedSuccessfully"),
|
||||
},
|
||||
},
|
||||
output: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
context := genericclioptions.NewFakeContext("project", "app", "component", client, nil)
|
||||
|
||||
//fake the services
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, tt.returnedServiceClassInstances, nil
|
||||
})
|
||||
|
||||
completions := ServiceCompletionHandler(nil, tt.parsedArgs, context)
|
||||
|
||||
// Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important)
|
||||
sort.Strings(completions)
|
||||
sort.Strings(tt.output)
|
||||
|
||||
if !reflect.DeepEqual(tt.output, completions) {
|
||||
t.Errorf("expected output: %#v,got: %#v", tt.output, completions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceClassCompletionHandler(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
returnedServiceClasses *scv1beta1.ClusterServiceClassList
|
||||
output []string
|
||||
parsedArgs parsedArgs
|
||||
}{
|
||||
{
|
||||
name: "test case 1: no service class exists and name not typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create"},
|
||||
},
|
||||
},
|
||||
returnedServiceClasses: &scv1beta1.ClusterServiceClassList{},
|
||||
output: []string{},
|
||||
},
|
||||
{
|
||||
name: "test case 2: one service class exists and name not typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create"},
|
||||
},
|
||||
},
|
||||
returnedServiceClasses: &scv1beta1.ClusterServiceClassList{
|
||||
Items: []scv1beta1.ClusterServiceClass{
|
||||
testingutil.FakeClusterServiceClass("foo"),
|
||||
},
|
||||
},
|
||||
output: []string{"foo"},
|
||||
},
|
||||
{
|
||||
name: "test case 3: multiple service classes exist and name not typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"create"},
|
||||
},
|
||||
},
|
||||
returnedServiceClasses: &scv1beta1.ClusterServiceClassList{
|
||||
Items: []scv1beta1.ClusterServiceClass{
|
||||
testingutil.FakeClusterServiceClass("foo"),
|
||||
testingutil.FakeClusterServiceClass("bar"),
|
||||
},
|
||||
},
|
||||
output: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "test case 4: multiple service classes exist and name fully typed",
|
||||
parsedArgs: parsedArgs{
|
||||
original: complete.Args{
|
||||
Completed: []string{"delete"},
|
||||
},
|
||||
commands: map[string]bool{"foo": true},
|
||||
},
|
||||
returnedServiceClasses: &scv1beta1.ClusterServiceClassList{
|
||||
Items: []scv1beta1.ClusterServiceClass{
|
||||
testingutil.FakeClusterServiceClass("foo"),
|
||||
testingutil.FakeClusterServiceClass("bar"),
|
||||
},
|
||||
},
|
||||
output: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
context := genericclioptions.NewFakeContext("project", "app", "component", client, nil)
|
||||
|
||||
//fake the services
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, tt.returnedServiceClasses, nil
|
||||
})
|
||||
|
||||
completions := ServiceClassCompletionHandler(nil, tt.parsedArgs, context)
|
||||
|
||||
// Sort the output and expected output in order to avoid false negatives (since ordering of the results is not important)
|
||||
sort.Strings(completions)
|
||||
sort.Strings(tt.output)
|
||||
|
||||
if !reflect.DeepEqual(tt.output, completions) {
|
||||
t.Errorf("expected output: %#v,got: %#v", tt.output, completions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,10 @@ import (
|
||||
parsercommon "github.com/devfile/library/pkg/devfile/parser/data/v2/common"
|
||||
"github.com/openshift/odo/pkg/kclient"
|
||||
"github.com/openshift/odo/pkg/log"
|
||||
"github.com/openshift/odo/pkg/odo/util/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/klog"
|
||||
|
||||
scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
|
||||
appsv1 "github.com/openshift/api/apps/v1"
|
||||
olm "github.com/operator-framework/api/pkg/operators/v1alpha1"
|
||||
|
||||
@@ -44,45 +42,8 @@ const ServiceLabel = "app.kubernetes.io/service-name"
|
||||
// ServiceKind is the kind of the service in the service binding object
|
||||
const ServiceKind = "app.kubernetes.io/service-kind"
|
||||
|
||||
// NewServicePlanParameter creates a new ServicePlanParameter instance with the specified state
|
||||
func NewServicePlanParameter(name, typeName, defaultValue string, required bool) ServicePlanParameter {
|
||||
return ServicePlanParameter{
|
||||
Name: name,
|
||||
Default: defaultValue,
|
||||
Validatable: validation.Validatable{
|
||||
Type: typeName,
|
||||
Required: required,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type servicePlanParameters []ServicePlanParameter
|
||||
|
||||
func (params servicePlanParameters) Len() int {
|
||||
return len(params)
|
||||
}
|
||||
|
||||
func (params servicePlanParameters) Less(i, j int) bool {
|
||||
return params[i].Name < params[j].Name
|
||||
}
|
||||
|
||||
func (params servicePlanParameters) Swap(i, j int) {
|
||||
params[i], params[j] = params[j], params[i]
|
||||
}
|
||||
|
||||
// CreateService creates new service from serviceCatalog
|
||||
// It returns string representation of service instance created on the cluster and error (if any).
|
||||
func CreateService(client *occlient.Client, serviceName, serviceType, servicePlan string, parameters map[string]string, applicationName string) (string, error) {
|
||||
labels := componentlabels.GetLabels(serviceName, applicationName, true)
|
||||
// save service type as label
|
||||
labels[componentlabels.ComponentTypeLabel] = serviceType
|
||||
serviceInstance, err := client.GetKubeClient().CreateServiceInstance(serviceName, serviceType, servicePlan, parameters, labels)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "unable to create service instance")
|
||||
}
|
||||
return serviceInstance, nil
|
||||
}
|
||||
|
||||
// GetCSV checks if the CR provided by the user in the YAML file exists in the namesapce
|
||||
// It returns a CR (string representation) and CSV (Operator) upon successfully
|
||||
// able to find them, an error otherwise.
|
||||
@@ -583,115 +544,11 @@ func SplitServiceKindName(serviceName string) (string, string, error) {
|
||||
return kind, name, nil
|
||||
}
|
||||
|
||||
// GetServiceClassAndPlans returns the service class details with the associated plans
|
||||
// serviceName is the name of the service class
|
||||
// the first parameter returned is the ServiceClass object
|
||||
// the second parameter returned is the array of ServicePlan associated with the service class
|
||||
func GetServiceClassAndPlans(client *occlient.Client, serviceName string) (ServiceClass, []ServicePlan, error) {
|
||||
result, err := client.GetKubeClient().GetClusterServiceClass(serviceName)
|
||||
if err != nil {
|
||||
return ServiceClass{}, nil, errors.Wrap(err, "unable to get the given service")
|
||||
}
|
||||
|
||||
var meta map[string]interface{}
|
||||
err = json.Unmarshal(result.Spec.ExternalMetadata.Raw, &meta)
|
||||
if err != nil {
|
||||
return ServiceClass{}, nil, errors.Wrap(err, "unable to unmarshal data the given service")
|
||||
}
|
||||
|
||||
service := ServiceClass{
|
||||
Name: result.Spec.ExternalName,
|
||||
Bindable: result.Spec.Bindable,
|
||||
ShortDescription: result.Spec.Description,
|
||||
Tags: result.Spec.Tags,
|
||||
ServiceBrokerName: result.Spec.ClusterServiceBrokerName,
|
||||
}
|
||||
|
||||
if val, ok := meta["longDescription"]; ok {
|
||||
service.LongDescription = val.(string)
|
||||
}
|
||||
|
||||
if val, ok := meta["dependencies"]; ok {
|
||||
versions := fmt.Sprint(val)
|
||||
versions = strings.Replace(versions, "[", "", -1)
|
||||
versions = strings.Replace(versions, "]", "", -1)
|
||||
service.VersionsAvailable = strings.Split(versions, " ")
|
||||
}
|
||||
|
||||
// get the plans according to the service name
|
||||
planResults, err := client.GetKubeClient().ListClusterServicePlansByServiceName(result.Name)
|
||||
if err != nil {
|
||||
return ServiceClass{}, nil, errors.Wrap(err, "unable to get plans for the given service")
|
||||
}
|
||||
|
||||
var plans []ServicePlan
|
||||
for _, result := range planResults {
|
||||
plan, err := NewServicePlan(result)
|
||||
if err != nil {
|
||||
return ServiceClass{}, nil, err
|
||||
}
|
||||
|
||||
plans = append(plans, plan)
|
||||
}
|
||||
|
||||
return service, plans, nil
|
||||
}
|
||||
|
||||
type InstanceCreateParameterSchema struct {
|
||||
Required []string
|
||||
Properties map[string]ServicePlanParameter
|
||||
}
|
||||
|
||||
// NewServicePlan creates a new ServicePlan based on the specified ClusterServicePlan
|
||||
func NewServicePlan(result scv1beta1.ClusterServicePlan) (plan ServicePlan, err error) {
|
||||
plan = ServicePlan{
|
||||
Name: result.Spec.ExternalName,
|
||||
Description: result.Spec.Description,
|
||||
}
|
||||
|
||||
// get the display name from the external meta data
|
||||
var externalMetaData map[string]interface{}
|
||||
err = json.Unmarshal(result.Spec.ExternalMetadata.Raw, &externalMetaData)
|
||||
if err != nil {
|
||||
return plan, errors.Wrap(err, "unable to unmarshal data the given service")
|
||||
}
|
||||
|
||||
if val, ok := externalMetaData["displayName"]; ok {
|
||||
plan.DisplayName = val.(string)
|
||||
}
|
||||
|
||||
// get the create parameters
|
||||
schema := InstanceCreateParameterSchema{}
|
||||
paramBytes := result.Spec.InstanceCreateParameterSchema.Raw
|
||||
err = json.Unmarshal(paramBytes, &schema)
|
||||
if err != nil {
|
||||
return plan, errors.Wrapf(err, "unable to unmarshal data the given service: %s", string(paramBytes[:]))
|
||||
}
|
||||
|
||||
plan.Parameters = make([]ServicePlanParameter, 0, len(schema.Properties))
|
||||
for k, v := range schema.Properties {
|
||||
v.Name = k
|
||||
// we set the Required flag if the name of parameter
|
||||
// is one of the parameters indicated as required
|
||||
// these parameters are not strictly required since they might have default values
|
||||
v.Required = isRequired(schema.Required, k)
|
||||
|
||||
plan.Parameters = append(plan.Parameters, v)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// isRequired checks whether the parameter with the specified name is among the given list of required ones
|
||||
func isRequired(required []string, name string) bool {
|
||||
for _, n := range required {
|
||||
if n == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCSVSupported checks if the cluster supports resources of type ClusterServiceVersion
|
||||
func IsCSVSupported() (bool, error) {
|
||||
client, err := occlient.New()
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/pkg/devfile/parser/data/v2/common"
|
||||
@@ -12,767 +9,10 @@ import (
|
||||
devfileCtx "github.com/devfile/library/pkg/devfile/parser/context"
|
||||
"github.com/devfile/library/pkg/devfile/parser/data"
|
||||
devfileFileSystem "github.com/devfile/library/pkg/testingutil/filesystem"
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
"github.com/onsi/gomega/matchers"
|
||||
"github.com/openshift/odo/pkg/testingutil"
|
||||
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
scv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1"
|
||||
appsv1 "github.com/openshift/api/apps/v1"
|
||||
applabels "github.com/openshift/odo/pkg/application/labels"
|
||||
componentlabels "github.com/openshift/odo/pkg/component/labels"
|
||||
"github.com/openshift/odo/pkg/occlient"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ktesting "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
func TestGetServiceClassAndPlans(t *testing.T) {
|
||||
|
||||
classExternalMetaData := make(map[string]interface{})
|
||||
classExternalMetaData["longDescription"] = "example long description"
|
||||
classExternalMetaData["dependencies"] = []string{"docker.io/centos/7", "docker.io/centos/8"}
|
||||
|
||||
classExternalMetaDataRaw, err := json.Marshal(classExternalMetaData)
|
||||
if err != nil {
|
||||
fmt.Printf("error occured %v during marshalling", err)
|
||||
return
|
||||
}
|
||||
|
||||
type args struct {
|
||||
ServiceName string
|
||||
}
|
||||
plan1 := testingutil.FakeClusterServicePlan("dev", 1)
|
||||
plan2 := testingutil.FakeClusterServicePlan("prod", 2)
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
returnedClassID string
|
||||
returnedServiceClass *scv1beta1.ClusterServiceClassList
|
||||
returnedServicePlan []scv1beta1.ClusterServicePlan
|
||||
wantedServiceClass ServiceClass
|
||||
wantedServicePlans []ServicePlan
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "test 1 : with correct values",
|
||||
args: args{
|
||||
ServiceName: "class name",
|
||||
},
|
||||
returnedClassID: "1dda1477cace09730bd8ed7a6505607e",
|
||||
returnedServiceClass: &scv1beta1.ClusterServiceClassList{
|
||||
Items: []scv1beta1.ClusterServiceClass{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "1dda1477cace09730bd8ed7a6505607e"},
|
||||
Spec: scv1beta1.ClusterServiceClassSpec{
|
||||
CommonServiceClassSpec: scv1beta1.CommonServiceClassSpec{
|
||||
ExternalName: "class name",
|
||||
Bindable: false,
|
||||
Description: "example description",
|
||||
Tags: []string{"php", "java"},
|
||||
ExternalMetadata: &runtime.RawExtension{Raw: classExternalMetaDataRaw},
|
||||
},
|
||||
ClusterServiceBrokerName: "broker name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
returnedServicePlan: []scv1beta1.ClusterServicePlan{plan1, plan2},
|
||||
wantedServiceClass: ServiceClass{
|
||||
Name: "class name",
|
||||
ShortDescription: "example description",
|
||||
LongDescription: "example long description",
|
||||
Tags: []string{"php", "java"},
|
||||
Bindable: false,
|
||||
ServiceBrokerName: "broker name",
|
||||
VersionsAvailable: []string{"docker.io/centos/7", "docker.io/centos/8"},
|
||||
},
|
||||
wantedServicePlans: []ServicePlan{
|
||||
{
|
||||
Name: "dev",
|
||||
Description: "this is a example description 1",
|
||||
DisplayName: "plan-name-1",
|
||||
Parameters: []ServicePlanParameter{
|
||||
NewServicePlanParameter("PLAN_DATABASE_URI", "string", "someuri", true),
|
||||
NewServicePlanParameter("PLAN_DATABASE_USERNAME", "string", "name", true),
|
||||
NewServicePlanParameter("PLAN_DATABASE_PASSWORD", "string", "", true),
|
||||
NewServicePlanParameter("SOME_OTHER", "string", "other", false),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "prod",
|
||||
Description: "this is a example description 2",
|
||||
DisplayName: "plan-name-2",
|
||||
Parameters: []ServicePlanParameter{
|
||||
NewServicePlanParameter("PLAN_DATABASE_USERNAME_2", "string", "user2", true),
|
||||
NewServicePlanParameter("PLAN_DATABASE_PASSWORD", "string", "", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceclasses", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if action.(ktesting.ListAction).GetListRestrictions().Fields.String() != fmt.Sprintf("spec.externalName=%v", tt.args.ServiceName) {
|
||||
t.Errorf("got a different service name got: %v , expected: %v", action.(ktesting.ListAction).GetListRestrictions().Fields.String(), fmt.Sprintf("spec.externalName=%v", tt.args.ServiceName))
|
||||
}
|
||||
return true, tt.returnedServiceClass, nil
|
||||
})
|
||||
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "clusterserviceplans", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if action.(ktesting.ListAction).GetListRestrictions().Fields.String() != fmt.Sprintf("spec.clusterServiceClassRef.name=%v", tt.returnedClassID) {
|
||||
t.Errorf("got a different service name got: %v , expected: %v", action.(ktesting.ListAction).GetListRestrictions().Fields.String(), fmt.Sprintf("spec.clusterServiceClassRef.name=%v", tt.returnedClassID))
|
||||
}
|
||||
return true, &scv1beta1.ClusterServicePlanList{Items: tt.returnedServicePlan}, nil
|
||||
})
|
||||
|
||||
serviceClass, servicePlans, err := GetServiceClassAndPlans(client, tt.args.ServiceName)
|
||||
|
||||
if err == nil && !tt.wantErr {
|
||||
if len(fakeClientSet.ServiceCatalogClientSet.Actions()) != 2 {
|
||||
t.Errorf("expected 2 actions in GetServiceClassAndPlans got: %v", fakeClientSet.ServiceCatalogClientSet.Actions())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantedServiceClass.Name, serviceClass.Name) {
|
||||
t.Errorf("different service class name expected got: %v , expected: %v", serviceClass.Name, tt.wantedServiceClass.Name)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantedServiceClass.Bindable, serviceClass.Bindable) {
|
||||
t.Errorf("different service class bindable value expected got: %v , expected: %v", serviceClass.Bindable, tt.wantedServiceClass.Bindable)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantedServiceClass.ShortDescription, serviceClass.ShortDescription) {
|
||||
t.Errorf("different short description value expected got: %v , expected: %v", serviceClass.ShortDescription, tt.wantedServiceClass.ShortDescription)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantedServiceClass.LongDescription, serviceClass.LongDescription) {
|
||||
t.Errorf("different long description value expected got: %v , expected: %v", serviceClass.LongDescription, tt.wantedServiceClass.LongDescription)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantedServiceClass.ServiceBrokerName, serviceClass.ServiceBrokerName) {
|
||||
t.Errorf("different service broker name value expected got: %v , expected: %v", serviceClass.ServiceBrokerName, tt.wantedServiceClass.ServiceBrokerName)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantedServiceClass.Tags, serviceClass.Tags) {
|
||||
t.Errorf("different service class tags value expected got: %v , expected: %v", serviceClass.Tags, tt.wantedServiceClass.Tags)
|
||||
}
|
||||
|
||||
for _, wantedServicePlan := range tt.wantedServicePlans {
|
||||
|
||||
// make sure that the plans are sorted so we can compare them later
|
||||
sort.Slice(wantedServicePlan.Parameters, func(i, j int) bool {
|
||||
return wantedServicePlan.Parameters[i].Name < wantedServicePlan.Parameters[j].Name
|
||||
})
|
||||
|
||||
found := false
|
||||
for _, gotServicePlan := range servicePlans {
|
||||
if reflect.DeepEqual(wantedServicePlan.Name, gotServicePlan.Name) {
|
||||
found = true
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure that the plans are sorted so we can compare them
|
||||
sort.Slice(gotServicePlan.Parameters, func(i, j int) bool {
|
||||
return gotServicePlan.Parameters[i].Name < gotServicePlan.Parameters[j].Name
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(wantedServicePlan.Parameters, gotServicePlan.Parameters) {
|
||||
t.Errorf("Different plan parameters value. Expected: %v , got: %v", wantedServicePlan.Parameters, gotServicePlan.Parameters)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(wantedServicePlan.DisplayName, gotServicePlan.DisplayName) {
|
||||
t.Errorf("Different plan display name value. Expected: %v , got: %v", wantedServicePlan.DisplayName, gotServicePlan.DisplayName)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(wantedServicePlan.Description, gotServicePlan.Description) {
|
||||
t.Errorf("Different plan description value. Expected: %v , got: %v", wantedServicePlan.Description, gotServicePlan.Description)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("service plan %v not found", wantedServicePlan.Name)
|
||||
}
|
||||
}
|
||||
} else if err == nil && tt.wantErr {
|
||||
t.Error("test failed, expected: false, got true")
|
||||
} else if err != nil && !tt.wantErr {
|
||||
t.Errorf("test failed, expected: no error, got error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListWithDetailedStatus(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
Project string
|
||||
Selector string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
serviceList scv1beta1.ServiceInstanceList
|
||||
secretList corev1.SecretList
|
||||
dcList appsv1.DeploymentConfigList
|
||||
output []Service
|
||||
}{
|
||||
{
|
||||
name: "Case 1: services with various statuses, some bound and some linked",
|
||||
args: args{
|
||||
Project: "myproject",
|
||||
Selector: "app.kubernetes.io/instance=mysql-persistent,app.kubernetes.io/part-of=app",
|
||||
},
|
||||
serviceList: scv1beta1.ServiceInstanceList{
|
||||
Items: []scv1beta1.ServiceInstance{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mysql-persistent",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "mysql-persistent",
|
||||
componentlabels.ComponentTypeLabel: "mysql-persistent",
|
||||
},
|
||||
Namespace: "myproject",
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "mysql-persistent",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "ProvisionedSuccessfully",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-ephemeral",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "postgresql-ephemeral",
|
||||
componentlabels.ComponentTypeLabel: "postgresql-ephemeral",
|
||||
},
|
||||
Namespace: "myproject",
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "postgresql-ephemeral",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "ProvisionedSuccessfully",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mongodb",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "mongodb",
|
||||
componentlabels.ComponentTypeLabel: "mongodb",
|
||||
},
|
||||
Namespace: "myproject",
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "mongodb",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "ProvisionedSuccessfully",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "jenkins-persistent",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
componentlabels.ComponentLabel: "jenkins-persistent",
|
||||
componentlabels.ComponentTypeLabel: "jenkins-persistent",
|
||||
},
|
||||
Namespace: "myproject",
|
||||
},
|
||||
Spec: scv1beta1.ServiceInstanceSpec{
|
||||
PlanReference: scv1beta1.PlanReference{
|
||||
ClusterServiceClassExternalName: "jenkins-persistent",
|
||||
ClusterServicePlanExternalName: "default",
|
||||
},
|
||||
},
|
||||
Status: scv1beta1.ServiceInstanceStatus{
|
||||
Conditions: []scv1beta1.ServiceInstanceCondition{
|
||||
{
|
||||
Reason: "Provisioning",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
secretList: corev1.SecretList{
|
||||
Items: []corev1.Secret{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "dummySecret",
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-ephemeral",
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mysql-persistent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dcList: appsv1.DeploymentConfigList{
|
||||
Items: []appsv1.DeploymentConfig{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dummyContainer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: "app",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "mysql-persistent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: []Service{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
APIVersion: "odo.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mysql-persistent",
|
||||
},
|
||||
Spec: ServiceSpec{
|
||||
Type: "mysql-persistent",
|
||||
Plan: "default",
|
||||
},
|
||||
Status: ServiceStatus{
|
||||
Status: "ProvisionedAndLinked",
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
APIVersion: "odo.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "postgresql-ephemeral",
|
||||
},
|
||||
Spec: ServiceSpec{
|
||||
Type: "postgresql-ephemeral",
|
||||
Plan: "default",
|
||||
},
|
||||
Status: ServiceStatus{
|
||||
Status: "ProvisionedAndBound",
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
APIVersion: "odo.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mongodb",
|
||||
},
|
||||
Spec: ServiceSpec{
|
||||
Type: "mongodb",
|
||||
Plan: "default",
|
||||
},
|
||||
Status: ServiceStatus{
|
||||
Status: "ProvisionedSuccessfully",
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Service",
|
||||
APIVersion: "odo.dev/v1alpha1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "jenkins-persistent",
|
||||
},
|
||||
Spec: ServiceSpec{
|
||||
Type: "jenkins-persistent",
|
||||
Plan: "default",
|
||||
},
|
||||
Status: ServiceStatus{
|
||||
Status: "Provisioning",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
|
||||
//fake the services
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.serviceList, nil
|
||||
})
|
||||
|
||||
//fake the secrets
|
||||
fakeClientSet.Kubernetes.PrependReactor("list", "secrets", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.secretList, nil
|
||||
})
|
||||
|
||||
//fake the dcs
|
||||
fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.dcList, nil
|
||||
})
|
||||
|
||||
svcInstanceList, _ := ListWithDetailedStatus(client, "app")
|
||||
|
||||
if !reflect.DeepEqual(tt.output, svcInstanceList.Items) {
|
||||
t.Error(fmt.Sprintf("Expected output: %v", pretty.Compare(tt.serviceList, svcInstanceList.Items)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteServiceAndUnlinkComponents(t *testing.T) {
|
||||
const appName = "app"
|
||||
type args struct {
|
||||
ServiceName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
serviceList scv1beta1.ServiceInstanceList
|
||||
dcList appsv1.DeploymentConfigList
|
||||
expectedDCNamesToBeUpdated []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Case 1: Delete service that has linked component",
|
||||
args: args{
|
||||
ServiceName: "mysql",
|
||||
},
|
||||
wantErr: false,
|
||||
expectedDCNamesToBeUpdated: []string{"component-with-matching-link"},
|
||||
serviceList: scv1beta1.ServiceInstanceList{
|
||||
Items: []scv1beta1.ServiceInstance{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mysql",
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: appName,
|
||||
componentlabels.ComponentLabel: "mysql",
|
||||
componentlabels.ComponentTypeLabel: "mysql-persistent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dcList: appsv1.DeploymentConfigList{
|
||||
Items: []appsv1.DeploymentConfig{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "component-with-no-links" + "-" + appName,
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: appName,
|
||||
componentlabels.ComponentLabel: "component-with-no-links",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "dummyContainer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "component-with-matching-link" + "-" + appName,
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: appName,
|
||||
componentlabels.ComponentLabel: "component-with-matching-link",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "mysql",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "component-with-non-matching-link" + "-" + appName,
|
||||
Labels: map[string]string{
|
||||
applabels.ApplicationLabel: appName,
|
||||
componentlabels.ComponentLabel: "component-with-non-matching-link",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentConfigSpec{
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "other",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
client, fakeClientSet := occlient.FakeNew()
|
||||
|
||||
//fake the services listing
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("list", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.serviceList, nil
|
||||
})
|
||||
|
||||
// Fake the servicebinding delete
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("delete", "servicebindings", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, nil
|
||||
})
|
||||
|
||||
// Fake the serviceinstance delete
|
||||
fakeClientSet.ServiceCatalogClientSet.PrependReactor("delete", "serviceinstances", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, nil
|
||||
})
|
||||
|
||||
//fake the dc listing
|
||||
fakeClientSet.AppsClientset.PrependReactor("list", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
return true, &tt.dcList, nil
|
||||
})
|
||||
|
||||
//fake the dc get
|
||||
fakeClientSet.AppsClientset.PrependReactor("get", "deploymentconfigs", func(action ktesting.Action) (bool, runtime.Object, error) {
|
||||
dcNameToFind := action.(ktesting.GetAction).GetName()
|
||||
var matchingDC appsv1.DeploymentConfig
|
||||
found := false
|
||||
for _, dc := range tt.dcList.Items {
|
||||
if dc.Name == dcNameToFind {
|
||||
matchingDC = dc
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("Expected to find DeploymentConfig named %s in the dcList", dcNameToFind)
|
||||
}
|
||||
|
||||
return true, &matchingDC, nil
|
||||
})
|
||||
|
||||
err := DeleteServiceAndUnlinkComponents(client, tt.args.ServiceName, "app")
|
||||
|
||||
if !tt.wantErr == (err != nil) {
|
||||
t.Errorf("service.DeleteServiceAndUnlinkComponents(...) unexpected error %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
// ensure we deleted the service
|
||||
if len(fakeClientSet.ServiceCatalogClientSet.Actions()) != 3 && !tt.wantErr {
|
||||
t.Errorf("service was deleted properly, got actions: %v", fakeClientSet.ServiceCatalogClientSet.Actions())
|
||||
}
|
||||
|
||||
// ensure we updated the correct number of deployments
|
||||
// there should always be a list action
|
||||
// then each update to a dc is 2 actions, a get and an update
|
||||
expectedNumberOfDCActions := 1 + (2 * len(tt.expectedDCNamesToBeUpdated))
|
||||
if len(fakeClientSet.AppsClientset.Actions()) != 3 && !tt.wantErr {
|
||||
t.Errorf("expected to see %d actions, got: %v", expectedNumberOfDCActions, fakeClientSet.AppsClientset.Actions())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicePlanParameterUnmarshalling(t *testing.T) {
|
||||
parameter := NewServicePlanParameter("name", "string", "default", true)
|
||||
parameter.Title = "title"
|
||||
parameter.Description = "description"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
expected ServicePlanParameter
|
||||
}{
|
||||
{
|
||||
name: "full",
|
||||
json: `{
|
||||
"name": "name",
|
||||
"title": "title",
|
||||
"description": "description",
|
||||
"default": "default",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}`,
|
||||
expected: parameter,
|
||||
},
|
||||
{
|
||||
name: "not required",
|
||||
json: `{
|
||||
"name": "name",
|
||||
"default": "default",
|
||||
"type": "integer"
|
||||
}`,
|
||||
expected: NewServicePlanParameter("name", "integer", "default", false),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
spp := &ServicePlanParameter{}
|
||||
err := json.Unmarshal([]byte(tt.json), &spp)
|
||||
if err != nil {
|
||||
t.Errorf("unmarshalling failed: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.expected, *spp) {
|
||||
t.Errorf("param: %v, got: %v", tt.expected, *spp)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServicePlanParameterMarshalling(t *testing.T) {
|
||||
parameter := NewServicePlanParameter("name", "string", "default", true)
|
||||
parameter.Title = "title"
|
||||
parameter.Description = "description"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
param ServicePlanParameter
|
||||
}{
|
||||
{
|
||||
name: "full",
|
||||
json: `{
|
||||
"name": "name",
|
||||
"title": "title",
|
||||
"description": "description",
|
||||
"default": "default",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}`,
|
||||
param: parameter,
|
||||
},
|
||||
{
|
||||
name: "not required",
|
||||
json: `{
|
||||
"name": "name",
|
||||
"default": "default",
|
||||
"type": "integer"
|
||||
}`,
|
||||
param: NewServicePlanParameter("name", "integer", "default", false),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, err := json.Marshal(tt.param)
|
||||
if err != nil {
|
||||
t.Errorf("marshalling failed: %v", err)
|
||||
}
|
||||
s := string(actual)
|
||||
matcher := matchers.MatchJSONMatcher{JSONToMatch: tt.json}
|
||||
success, err := matcher.Match(s)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't match json: %v", err)
|
||||
}
|
||||
if !success {
|
||||
t.Errorf("param: %v, got: %v", tt.json, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddKubernetesComponentToDevfile(t *testing.T) {
|
||||
fs := devfileFileSystem.NewFakeFs()
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ var _ = Describe("odo link and unlink command tests", func() {
|
||||
It("should display the help", func() {
|
||||
By("for the link command", func() {
|
||||
appHelp := helper.Cmd("odo", "link", "-h").ShouldPass().Out()
|
||||
helper.MatchAllInOutput(appHelp, []string{"Link component to a service ", "backed by an Operator or Service Catalog", "or component"})
|
||||
helper.MatchAllInOutput(appHelp, []string{"Link component to a service ", "backed by an Operator", "or component"})
|
||||
})
|
||||
By("for the unlink command", func() {
|
||||
appHelp := helper.Cmd("odo", "unlink", "-h").ShouldPass().Out()
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/openshift/odo/tests/helper"
|
||||
)
|
||||
|
||||
var _ = Describe("odo link and unlink command tests", func() {
|
||||
//new clean context for each test
|
||||
/*
|
||||
Uncomment when we uncomment the test specs
|
||||
var context1, context2 string
|
||||
var oc helper.OcRunner
|
||||
*/
|
||||
var commonVar helper.CommonVar
|
||||
|
||||
// This is run before every Spec (It)
|
||||
var _ = BeforeEach(func() {
|
||||
// oc = helper.NewOcRunner("oc")
|
||||
commonVar = helper.CommonBeforeEach()
|
||||
//context1 = helper.CreateNewContext()
|
||||
//context2 = helper.CreateNewContext()
|
||||
})
|
||||
|
||||
// Clean up after the test
|
||||
// This is run after every Spec (It)
|
||||
var _ = AfterEach(func() {
|
||||
//helper.DeleteDir(context1)
|
||||
//helper.DeleteDir(context2)
|
||||
helper.CommonAfterEach(commonVar)
|
||||
})
|
||||
|
||||
Context("when running help for link and unlink command", func() {
|
||||
It("should display the help", func() {
|
||||
appHelp := helper.Cmd("odo", "link", "-h").ShouldPass().Out()
|
||||
Expect(appHelp).To(ContainSubstring("Link component to a service"))
|
||||
appHelp = helper.Cmd("odo", "unlink", "-h").ShouldPass().Out()
|
||||
Expect(appHelp).To(ContainSubstring("Unlink component or service from a component"))
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
Context("When link between components using wrong port", func() {
|
||||
It("should fail", func() {
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
helper.CopyExample(filepath.Join("source", "python"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
stdErr := helper.CmdShouldFail("odo", "link", "backend", "--context", context1, "--port", "1234")
|
||||
Expect(stdErr).To(ContainSubstring("Unable to properly link to component backend using port 1234"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("When handling link/unlink between components", func() {
|
||||
It("should link the frontend application to the backend and then unlink successfully", func() {
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "url", "create", "--port", "8080", "--context", context1)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
frontendURL := helper.DetermineRouteURL(context1)
|
||||
helper.CopyExample(filepath.Join("source", "python"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "url", "create", "--context", context2)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
|
||||
helper.CmdShouldPass("odo", "link", "backend", "--context", context1)
|
||||
|
||||
// ensure that the proper envFrom entry was created
|
||||
envFromOutput := oc.GetEnvFromEntry("frontend", "app", commonVar.Project)
|
||||
Expect(envFromOutput).To(ContainSubstring("backend"))
|
||||
|
||||
dcName := oc.GetDcName("frontend", commonVar.Project)
|
||||
// wait for DeploymentConfig rollout to finish, so we can check if application is successfully running
|
||||
oc.WaitForDCRollout(dcName, commonVar.Project, 20*time.Second)
|
||||
helper.HttpWaitFor(frontendURL, "Hello world from node.js!", 20, 1)
|
||||
|
||||
outputErr := helper.CmdShouldFail("odo", "link", "backend", "--context", context1)
|
||||
Expect(outputErr).To(ContainSubstring("been linked"))
|
||||
helper.CmdShouldPass("odo", "unlink", "backend", "--context", context1)
|
||||
})
|
||||
})
|
||||
|
||||
Context("When link backend between component and service", func() {
|
||||
It("should link backend to service successfully", func() {
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
helper.CopyExample(filepath.Join("source", "python"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
helper.CmdShouldPass("odo", "link", "backend", "--context", context1) // context1 is the frontend
|
||||
// Switching to context2 dir because --context flag is not supported with service command
|
||||
helper.Chdir(context2)
|
||||
helper.CmdShouldPass("odo", "service", "create", "mysql-persistent")
|
||||
|
||||
ocArgs := []string{"get", "serviceinstance", "-n", commonVar.Project, "-o", "name"}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "mysql-persistent")
|
||||
})
|
||||
helper.CmdShouldPass("odo", "link", "mysql-persistent", "--wait-for-target", "--component", "backend", "--project", commonVar.Project)
|
||||
// ensure that the proper envFrom entry was created
|
||||
envFromOutput := oc.GetEnvFromEntry("backend", "app", commonVar.Project)
|
||||
Expect(envFromOutput).To(ContainSubstring("mysql-persistent"))
|
||||
outputErr := helper.CmdShouldFail("odo", "link", "mysql-persistent", "--context", context2)
|
||||
Expect(outputErr).To(ContainSubstring("been linked"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("When deleting service and unlink the backend from the frontend", func() {
|
||||
It("should pass", func() {
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "nodejs", "frontend", "--context", context1, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
helper.CopyExample(filepath.Join("source", "python"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "python", "backend", "--context", context2, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
helper.CmdShouldPass("odo", "link", "backend", "--context", context1)
|
||||
helper.Chdir(context2)
|
||||
helper.CmdShouldPass("odo", "service", "create", "mysql-persistent")
|
||||
|
||||
ocArgs := []string{"get", "serviceinstance", "-n", commonVar.Project, "-o", "name"}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "mysql-persistent")
|
||||
})
|
||||
helper.CmdShouldPass("odo", "service", "delete", "mysql-persistent", "-f")
|
||||
// ensure that the backend no longer has an envFrom value
|
||||
backendEnvFromOutput := oc.GetEnvFromEntry("backend", "app", commonVar.Project)
|
||||
Expect(backendEnvFromOutput).To(Equal("''"))
|
||||
// ensure that the frontend envFrom was not changed
|
||||
frontEndEnvFromOutput := oc.GetEnvFromEntry("frontend", "app", commonVar.Project)
|
||||
Expect(frontEndEnvFromOutput).To(ContainSubstring("backend"))
|
||||
helper.CmdShouldPass("odo", "unlink", "backend", "--component", "frontend", "--project", commonVar.Project)
|
||||
// ensure that the proper envFrom entry was created
|
||||
envFromOutput := oc.GetEnvFromEntry("frontend", "app", commonVar.Project)
|
||||
Expect(envFromOutput).To(Equal("''"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("When linking or unlinking a service or component", func() {
|
||||
It("should print the environment variables being linked/unlinked", func() {
|
||||
helper.CopyExample(filepath.Join("source", "python"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "python", "component1", "--context", context1, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "nodejs", "component2", "--context", context2, "--project", commonVar.Project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
|
||||
// tests for linking a component to a component
|
||||
stdOut := helper.CmdShouldPass("odo", "link", "component2", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"})
|
||||
|
||||
// tests for unlinking a component from a component
|
||||
stdOut = helper.CmdShouldPass("odo", "unlink", "component2", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"})
|
||||
|
||||
// first create a service
|
||||
helper.CmdShouldPass("odo", "service", "create", "-w", "dh-postgresql-apb", "--project", commonVar.Project, "--plan", "dev",
|
||||
"-p", "postgresql_user=luke", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6")
|
||||
ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "dh-postgresql-apb")
|
||||
})
|
||||
|
||||
// tests for linking a service to a component
|
||||
stdOut = helper.CmdShouldPass("odo", "link", "dh-postgresql-apb", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "DB_PORT", "DB_HOST"})
|
||||
|
||||
// tests for unlinking a service to a component
|
||||
stdOut = helper.CmdShouldPass("odo", "unlink", "dh-postgresql-apb", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "DB_PORT", "DB_HOST"})
|
||||
})
|
||||
})
|
||||
|
||||
*/
|
||||
})
|
||||
@@ -1,421 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/openshift/odo/tests/helper"
|
||||
)
|
||||
|
||||
var _ = Describe("odo service command tests", func() {
|
||||
var app, serviceName string
|
||||
/*
|
||||
Uncomment when we uncomment the test specs
|
||||
var context1, context2 string
|
||||
var oc helper.OcRunner
|
||||
*/
|
||||
var commonVar helper.CommonVar
|
||||
|
||||
// This is run before every Spec (It)
|
||||
var _ = BeforeEach(func() {
|
||||
//oc = helper.NewOcRunner("oc")
|
||||
commonVar = helper.CommonBeforeEach()
|
||||
// context1 = helper.CreateNewContext()
|
||||
// context2 = helper.CreateNewContext()
|
||||
})
|
||||
|
||||
// Clean up after the test
|
||||
// This is run after every Spec (It)
|
||||
var _ = AfterEach(func() {
|
||||
helper.CommonAfterEach(commonVar)
|
||||
// helper.DeleteDir(context1)
|
||||
// helper.DeleteDir(context2)
|
||||
})
|
||||
|
||||
Context("when running help for service command", func() {
|
||||
It("should display the help", func() {
|
||||
appHelp := helper.Cmd("odo", "service", "-h").ShouldPass().Out()
|
||||
Expect(appHelp).To(ContainSubstring("Perform service catalog operations"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("check catalog service search functionality", func() {
|
||||
It("check that a service does not exist", func() {
|
||||
serviceRandomName := helper.RandString(7)
|
||||
output := helper.Cmd("odo", "catalog", "search", "service", serviceRandomName).ShouldFail().Err()
|
||||
Expect(output).To(ContainSubstring("no service matched the query: " + serviceRandomName))
|
||||
})
|
||||
})
|
||||
|
||||
Context("checking machine readable output for service catalog", func() {
|
||||
It("should succeed listing catalog components", func() {
|
||||
// Since service catalog is constantly changing, we simply check to see if this command passes.. rather than checking the JSON each time.
|
||||
output := helper.Cmd("odo", "catalog", "list", "services", "-o", "json").ShouldPass().Out()
|
||||
Expect(output).To(ContainSubstring("List"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("checking machine readable output for service catalog", func() {
|
||||
It("should succeed listing catalog components", func() {
|
||||
// Since service catalog is constantly changing, we simply check to see if this command passes.. rather than checking the JSON each time.
|
||||
helper.Cmd("odo", "catalog", "list", "services", "-o", "json").ShouldPass()
|
||||
})
|
||||
})
|
||||
|
||||
Context("check search functionality", func() {
|
||||
|
||||
It("should pass with searching for part of a service name", func() {
|
||||
|
||||
// We just use "sql" as some catalogs only have postgresql-persistent and
|
||||
// others dh-postgresql-db. So let's just see if there's "any" postgresql to begin
|
||||
// with
|
||||
output := helper.Cmd("odo", "catalog", "search", "service", "sql").ShouldPass().Out()
|
||||
Expect(output).To(ContainSubstring("postgresql"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Context("create service with Env non-interactively", func() {
|
||||
JustBeforeEach(func() {
|
||||
app = helper.RandString(7)
|
||||
})
|
||||
|
||||
It("should be able to create postgresql with env", func() {
|
||||
helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--project", commonVar.Project, "--app", app,
|
||||
"--plan", "dev", "-p", "postgresql_user=lukecage", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", "-w").ShouldPass()
|
||||
// there is only a single pod in the project
|
||||
ocArgs := []string{"describe", "pod", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "lukecage")
|
||||
})
|
||||
|
||||
// Delete the service
|
||||
helper.Cmd("odo", "service", "delete", "dh-postgresql-apb", "-f", "--app", app, "--project", commonVar.Project).ShouldPass()
|
||||
})
|
||||
|
||||
It("should be able to create postgresql with env multiple times", func() {
|
||||
helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--project", commonVar.Project, "--app", app,
|
||||
"--plan", "dev", "-p", "postgresql_user=lukecage", "-p", "postgresql_user=testworker", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_password=universe", "-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", "-w").ShouldPass()
|
||||
// there is only a single pod in the project
|
||||
ocArgs := []string{"describe", "pod", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "testworker")
|
||||
})
|
||||
|
||||
// Delete the service
|
||||
helper.Cmd("odo", "service", "delete", "dh-postgresql-apb", "-f", "--app", app, "--project", commonVar.Project).ShouldPass()
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
Context("When creating with a spring boot application", func() {
|
||||
JustBeforeEach(func() {
|
||||
context = helper.CreateNewContext()
|
||||
os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml"))
|
||||
project = helper.CreateRandProject()
|
||||
originalDir = helper.Getwd()
|
||||
helper.Chdir(context)
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
helper.DeleteProject(project)
|
||||
helper.Chdir(originalDir)
|
||||
helper.DeleteDir(context)
|
||||
os.Unsetenv("GLOBALODOCONFIG")
|
||||
})
|
||||
It("should be able to create postgresql and link it with springboot", func() {
|
||||
oc.ImportJavaIS(project)
|
||||
helper.CopyExample(filepath.Join("source", "openjdk-sb-postgresql"), context)
|
||||
|
||||
// Local config needs to be present in order to create service https://github.com/openshift/odo/issues/1602
|
||||
helper.CmdShouldPass("odo", "create", "--s2i", "java:8", "sb-app", "--project", project)
|
||||
|
||||
// Create a URL
|
||||
helper.CmdShouldPass("odo", "url", "create", "--port", "8080")
|
||||
|
||||
// push
|
||||
helper.CmdShouldPass("odo", "push")
|
||||
|
||||
// create the postgres service
|
||||
helper.CmdShouldPass("odo", "service", "create", "dh-postgresql-apb", "--project", project, "--plan", "dev",
|
||||
"-p", "postgresql_user=luke", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", "-w")
|
||||
|
||||
// link the service
|
||||
helper.CmdShouldPass("odo", "link", "dh-postgresql-apb", "--project", project, "-w", "--wait-for-target")
|
||||
odoArgs := []string{"service", "list"}
|
||||
helper.WaitForCmdOut("odo", odoArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "dh-postgresql-apb") &&
|
||||
strings.Contains(output, "ProvisionedAndLinked")
|
||||
})
|
||||
|
||||
routeURL := helper.DetermineRouteURL("")
|
||||
|
||||
// Ping said URL
|
||||
helper.HttpWaitFor(routeURL, "Spring Boot", 90, 1)
|
||||
|
||||
// Delete the service
|
||||
helper.CmdShouldPass("odo", "service", "delete", "dh-postgresql-apb", "-f")
|
||||
|
||||
// Delete the component and the config
|
||||
helper.CmdShouldPass("odo", "delete", "sb-app", "-f", "--all")
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
// TODO: auth issue, we need to find a proper way how to test it without requiring cluster admin privileges
|
||||
|
||||
// Context("odo hides a hidden service in service catalog", func() {
|
||||
// It("not show a hidden service in the catalog", func() {
|
||||
// runCmdShouldPass("oc apply -f https://github.com/openshift/library/raw/master/official/sso/templates/sso72-https.json -n openshift")
|
||||
// outputErr := runCmdShouldFail("odo catalog search service sso72-https")
|
||||
// Expect(outputErr).To(ContainSubstring("No service matched the query: sso72-https"))
|
||||
// })
|
||||
// })
|
||||
|
||||
Context("When working from outside a component dir", func() {
|
||||
JustBeforeEach(func() {
|
||||
app = helper.RandString(7)
|
||||
serviceName = "odo-postgres-service"
|
||||
helper.Chdir(commonVar.Context)
|
||||
})
|
||||
|
||||
It("should be able to create, list and delete a service using a given value for --context", func() {
|
||||
// create a component by copying the example
|
||||
helper.CopyExample(filepath.Join("source", "python"), commonVar.Context)
|
||||
helper.Cmd("odo", "create", "--s2i", "python", "--app", app, "--project", commonVar.Project).ShouldPass()
|
||||
|
||||
// cd to the originalDir to create service using --context
|
||||
helper.Chdir(commonVar.OriginalWorkingDirectory)
|
||||
helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--plan", "dev",
|
||||
"-p", "postgresql_user=luke", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6", serviceName,
|
||||
"--context", commonVar.Context,
|
||||
).ShouldPass()
|
||||
|
||||
// now check if listing the service using --context works
|
||||
ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, serviceName)
|
||||
})
|
||||
stdOut := helper.Cmd("odo", "service", "list", "--context", commonVar.Context).ShouldPass().Out()
|
||||
Expect(stdOut).To(ContainSubstring(serviceName))
|
||||
|
||||
// now check if deleting the service using --context works
|
||||
stdOut = helper.Cmd("odo", "service", "delete", "-f", serviceName, "--context", commonVar.Context).ShouldPass().Out()
|
||||
Expect(stdOut).To(ContainSubstring(serviceName))
|
||||
})
|
||||
|
||||
It("should be able to list services, as well as json list in a given app and project combination", func() {
|
||||
// create a component by copying the example
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
|
||||
helper.Cmd("odo", "create", "--s2i", "nodejs", "--app", app, "--project", commonVar.Project).ShouldPass()
|
||||
|
||||
// create a service from within a component directory
|
||||
helper.Cmd("odo", "service", "create", "dh-prometheus-apb", "--plan", "ephemeral",
|
||||
"--app", app, "--project", commonVar.Project,
|
||||
).ShouldPass()
|
||||
ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "dh-prometheus-apb")
|
||||
})
|
||||
|
||||
// Listing the services should work as expected from within the component directory.
|
||||
// This means, it should not require --app or --project flags
|
||||
stdOut := helper.Cmd("odo", "service", "list").ShouldPass().Out()
|
||||
Expect(stdOut).To(ContainSubstring("dh-prometheus-apb"))
|
||||
|
||||
// Check json output
|
||||
stdOut = helper.Cmd("odo", "service", "list", "-o", "json").ShouldPass().Out()
|
||||
helper.MatchAllInOutput(stdOut, []string{"dh-prometheus-apb", "List"})
|
||||
|
||||
// cd to a non-component directory and list services
|
||||
helper.Chdir(commonVar.OriginalWorkingDirectory)
|
||||
stdOut = helper.Cmd("odo", "service", "list", "--app", app, "--project", commonVar.Project).ShouldPass().Out()
|
||||
Expect(stdOut).To(ContainSubstring("dh-prometheus-apb"))
|
||||
|
||||
// Check json output
|
||||
helper.Chdir(commonVar.OriginalWorkingDirectory)
|
||||
stdOut = helper.Cmd("odo", "service", "list", "--app", app, "--project", commonVar.Project, "-o", "json").ShouldPass().Out()
|
||||
helper.MatchAllInOutput(stdOut, []string{"dh-prometheus-apb", "List"})
|
||||
})
|
||||
})
|
||||
|
||||
Context("When working from outside a component dir", func() {
|
||||
It("should be able to create, list and delete services without a context and using --app and --project flags instaed", func() {
|
||||
app = helper.RandString(7)
|
||||
// create the service
|
||||
helper.Cmd("odo", "service", "create", "dh-postgresql-apb", "--plan", "dev",
|
||||
"-p", "postgresql_user=luke", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6",
|
||||
"--app", app, "--project", commonVar.Project).ShouldPass()
|
||||
|
||||
ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "dh-postgresql-apb")
|
||||
})
|
||||
|
||||
// list the service using app and project flags
|
||||
stdOut := helper.Cmd("odo", "service", "list", "--app", app, "--project", commonVar.Project).ShouldPass().Out()
|
||||
Expect(stdOut).To(ContainSubstring("dh-postgresql-apb"))
|
||||
|
||||
// delete the service using app and project flags
|
||||
helper.Cmd("odo", "service", "delete", "-f", "dh-postgresql-apb", "--app", app, "--project", commonVar.Project).ShouldPass()
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
Context("When link backend between component and service", func() {
|
||||
JustBeforeEach(func() {
|
||||
preSetup()
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
cleanPreSetup()
|
||||
})
|
||||
It("should link backend to service successfully", func() {
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "--s2i", "nodejs", "frontend", "--context", context1, "--project", project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
helper.CopyExample(filepath.Join("source", "python"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "--s2i", "python", "backend", "--context", context2, "--project", project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
helper.CmdShouldPass("odo", "link", "backend", "--context", context1)
|
||||
// Switching to context2 dir because --context flag is not supported with service command
|
||||
helper.Chdir(context2)
|
||||
helper.CmdShouldPass("odo", "service", "create", "mysql-persistent")
|
||||
|
||||
ocArgs := []string{"get", "serviceinstance", "-n", project, "-o", "name"}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "mysql-persistent")
|
||||
})
|
||||
helper.CmdShouldPass("odo", "link", "mysql-persistent", "--wait-for-target", "--component", "backend", "--project", project)
|
||||
// ensure that the proper envFrom entry was created
|
||||
envFromOutput := oc.GetEnvFromEntry("backend", "app", project)
|
||||
Expect(envFromOutput).To(ContainSubstring("mysql-persistent"))
|
||||
outputErr := helper.CmdShouldFail("odo", "link", "mysql-persistent", "--context", context2)
|
||||
Expect(outputErr).To(ContainSubstring("been linked"))
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
/*
|
||||
Context("When deleting service and unlink the backend from the frontend", func() {
|
||||
JustBeforeEach(func() {
|
||||
preSetup()
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
cleanPreSetup()
|
||||
})
|
||||
It("should pass", func() {
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "--s2i", "nodejs", "frontend", "--context", context1, "--project", project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
helper.CopyExample(filepath.Join("source", "python"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "--s2i", "python", "backend", "--context", context2, "--project", project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
helper.CmdShouldPass("odo", "link", "backend", "--context", context1)
|
||||
helper.Chdir(context2)
|
||||
helper.CmdShouldPass("odo", "service", "create", "mysql-persistent")
|
||||
|
||||
ocArgs := []string{"get", "serviceinstance", "-n", project, "-o", "name"}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "mysql-persistent")
|
||||
})
|
||||
helper.CmdShouldPass("odo", "service", "delete", "mysql-persistent", "-f")
|
||||
// ensure that the backend no longer has an envFrom value
|
||||
backendEnvFromOutput := oc.GetEnvFromEntry("backend", "app", project)
|
||||
Expect(backendEnvFromOutput).To(Equal("''"))
|
||||
// ensure that the frontend envFrom was not changed
|
||||
frontEndEnvFromOutput := oc.GetEnvFromEntry("frontend", "app", project)
|
||||
Expect(frontEndEnvFromOutput).To(ContainSubstring("backend"))
|
||||
helper.CmdShouldPass("odo", "unlink", "backend", "--component", "frontend", "--project", project)
|
||||
// ensure that the proper envFrom entry was created
|
||||
envFromOutput := oc.GetEnvFromEntry("frontend", "app", project)
|
||||
Expect(envFromOutput).To(Equal("''"))
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
/*
|
||||
Context("When linking or unlinking a service or component", func() {
|
||||
JustBeforeEach(func() {
|
||||
preSetup()
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
cleanPreSetup()
|
||||
})
|
||||
|
||||
It("should print the environment variables being linked/unlinked", func() {
|
||||
helper.CopyExample(filepath.Join("source", "python"), context1)
|
||||
helper.CmdShouldPass("odo", "create", "--s2i", "python", "component1", "--context", context1, "--project", project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context1)
|
||||
helper.CopyExample(filepath.Join("source", "nodejs"), context2)
|
||||
helper.CmdShouldPass("odo", "create", "--s2i", "nodejs", "component2", "--context", context2, "--project", project)
|
||||
helper.CmdShouldPass("odo", "push", "--context", context2)
|
||||
|
||||
// tests for linking a component to a component
|
||||
stdOut := helper.CmdShouldPass("odo", "link", "component2", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"})
|
||||
|
||||
// tests for unlinking a component from a component
|
||||
stdOut = helper.CmdShouldPass("odo", "unlink", "component2", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "COMPONENT_COMPONENT2_HOST", "COMPONENT_COMPONENT2_PORT"})
|
||||
|
||||
// first create a service
|
||||
helper.CmdShouldPass("odo", "service", "create", "-w", "dh-postgresql-apb", "--project", project, "--plan", "dev",
|
||||
"-p", "postgresql_user=luke", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6")
|
||||
ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "dh-postgresql-apb")
|
||||
})
|
||||
|
||||
// tests for linking a service to a component
|
||||
stdOut = helper.CmdShouldPass("odo", "link", "dh-postgresql-apb", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were added", "DB_PORT", "DB_HOST"})
|
||||
|
||||
// tests for unlinking a service to a component
|
||||
stdOut = helper.CmdShouldPass("odo", "unlink", "dh-postgresql-apb", "--context", context1)
|
||||
helper.MatchAllInOutput(stdOut, []string{"The below secret environment variables were removed", "DB_PORT", "DB_HOST"})
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
Context("When describing services", func() {
|
||||
It("should succeed when we're describing service that could have integer value for default field", func() {
|
||||
// https://github.com/openshift/odo/issues/2488
|
||||
helper.CopyExample(filepath.Join("source", "python"), commonVar.Context)
|
||||
helper.Cmd("odo", "create", "--s2i", "python", "component1", "--context", commonVar.Context, "--project", commonVar.Project).ShouldPass()
|
||||
helper.Chdir(commonVar.Context)
|
||||
|
||||
helper.Cmd("odo", "catalog", "describe", "service", "dh-es-apb").ShouldPass()
|
||||
helper.Cmd("odo", "catalog", "describe", "service", "dh-import-vm-apb").ShouldPass()
|
||||
})
|
||||
})
|
||||
|
||||
Context("When the application is deleted", func() {
|
||||
JustBeforeEach(func() {
|
||||
app = helper.RandString(6)
|
||||
})
|
||||
It("should delete the service(s) in the application as well", func() {
|
||||
helper.Cmd("odo", "service", "create", "--app", app, "-w", "dh-postgresql-apb", "--project", commonVar.Project, "--plan", "dev",
|
||||
"-p", "postgresql_user=luke", "-p", "postgresql_password=secret",
|
||||
"-p", "postgresql_database=my_data", "-p", "postgresql_version=9.6").ShouldPass()
|
||||
ocArgs := []string{"get", "serviceinstance", "-o", "name", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "dh-postgresql-apb")
|
||||
})
|
||||
|
||||
helper.Cmd("odo", "app", "delete", app, "--project", commonVar.Project, "-f").ShouldPass()
|
||||
|
||||
ocArgs = []string{"get", "serviceinstances", "-n", commonVar.Project}
|
||||
helper.WaitForCmdOut("oc", ocArgs, 1, true, func(output string) bool {
|
||||
return strings.Contains(output, "No resources found")
|
||||
}, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/openshift/odo/tests/helper"
|
||||
)
|
||||
|
||||
func TestServicecatalog(t *testing.T) {
|
||||
helper.RunTestSpecs(t, "Servicecatalog Suite")
|
||||
}
|
||||
Reference in New Issue
Block a user