Remove commands that wont be implemented for v3-alpha1 (#5433)

* Remove odo staorage commands

* Remove "odo service" + "odo catalog * service" commands

* Remove odo link/unlink commands

* Remove related integration tests

* Remove application concept

* fix rebase

* fix test

* Remove config command

* Remove env command

* Remove application package

* Remove config package

* Move odogenerator and unions packages into kclient

* Move notify package to cli/version

* Fix script mockgen

* Remove odo debug command oand debug package

* Remove odo component describe/exec/log/status/test

* Remove operator-hub tests from IBM tests

* Remove operator hub tests from CI

* Fix e2e tests
This commit is contained in:
Philippe Martin
2022-02-07 05:42:15 +01:00
committed by GitHub
parent 97e211b715
commit 850c7cd723
124 changed files with 51 additions and 11652 deletions

View File

@@ -13,7 +13,6 @@ cleanup_namespaces
set -e set -e
make install make install
make test-integration-devfile make test-integration-devfile
make test-operator-hub
make test-e2e-devfile make test-e2e-devfile
make test-cmd-project make test-cmd-project
) |& tee "/tmp/${LOGFILE}" ) |& tee "/tmp/${LOGFILE}"

View File

@@ -13,7 +13,6 @@ cleanup_namespaces
make install make install
make test-integration make test-integration
make test-integration-devfile make test-integration-devfile
make test-operator-hub
make test-cmd-login-logout make test-cmd-login-logout
make test-cmd-project make test-cmd-project
make test-e2e-devfile make test-e2e-devfile

View File

@@ -66,8 +66,6 @@ function Run-Test {
Check-ExitCode $LASTEXITCODE Check-ExitCode $LASTEXITCODE
make test-integration | tee -a C:\Users\Administrator.ANSIBLE-TEST-VS\AppData\Local\Temp\$LOGFILE make test-integration | tee -a C:\Users\Administrator.ANSIBLE-TEST-VS\AppData\Local\Temp\$LOGFILE
Check-ExitCode $LASTEXITCODE Check-ExitCode $LASTEXITCODE
make test-operator-hub | tee -a C:\Users\Administrator.ANSIBLE-TEST-VS\AppData\Local\Temp\$LOGFILE
Check-ExitCode $LASTEXITCODE
make test-cmd-login-logout | tee -a C:\Users\Administrator.ANSIBLE-TEST-VS\AppData\Local\Temp\$LOGFILE make test-cmd-login-logout | tee -a C:\Users\Administrator.ANSIBLE-TEST-VS\AppData\Local\Temp\$LOGFILE
Check-ExitCode $LASTEXITCODE Check-ExitCode $LASTEXITCODE
make test-cmd-project | tee -a C:\Users\Administrator.ANSIBLE-TEST-VS\AppData\Local\Temp\$LOGFILE make test-cmd-project | tee -a C:\Users\Administrator.ANSIBLE-TEST-VS\AppData\Local\Temp\$LOGFILE

View File

@@ -297,10 +297,6 @@ vendor-update: ## Update vendoring
openshiftci-presubmit-unittests: openshiftci-presubmit-unittests:
./scripts/openshiftci-presubmit-unittests.sh ./scripts/openshiftci-presubmit-unittests.sh
.PHONY: test-operator-hub
test-operator-hub: ## Run OperatorHub tests
$(RUN_GINKGO) $(GINKGO_FLAGS) tests/integration/operatorhub/
.PHONY: test-cmd-devfile-describe .PHONY: test-cmd-devfile-describe
test-cmd-devfile-describe: test-cmd-devfile-describe:
$(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo devfile describe command tests" tests/integration/devfile/ $(RUN_GINKGO) $(GINKGO_FLAGS) -focus="odo devfile describe command tests" tests/integration/devfile/

View File

@@ -1,12 +0,0 @@
package application
import "github.com/redhat-developer/odo/pkg/component"
type Client interface {
List() ([]string, error)
Exists(app string) (bool, error)
Delete(name string) error
ComponentList(name string) ([]component.Component, error)
GetMachineReadableFormat(appName string, projectName string) App
GetMachineReadableFormatForList(apps []App) AppList
}

View File

@@ -1,5 +0,0 @@
// 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 Operator Backed Services).
package application

View File

@@ -1,112 +0,0 @@
package application
import (
"github.com/pkg/errors"
applabels "github.com/redhat-developer/odo/pkg/application/labels"
"github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
)
type kubernetesClient struct {
client kclient.ClientInterface
}
func NewClient(client kclient.ClientInterface) Client {
return kubernetesClient{
client: client,
}
}
// List all applications names in current project by looking at `app` labels in deployments
func (o kubernetesClient) List() ([]string, error) {
if o.client == nil {
return nil, nil
}
// Get all Deployments with the "app" label
deploymentAppNames, err := o.client.GetDeploymentLabelValues(applabels.ApplicationLabel, applabels.ApplicationLabel)
if err != nil {
return nil, errors.Wrap(err, "unable to list applications from deployments")
}
// Filter out any names, as there could be multiple components but within the same application
return util.RemoveDuplicates(deploymentAppNames), nil
}
// Exists checks whether the given app exist or not in the list of applications
func (o kubernetesClient) Exists(app string) (bool, error) {
appList, err := o.List()
if err != nil {
return false, err
}
for _, appName := range appList {
if appName == app {
return true, nil
}
}
return false, nil
}
// Delete the given application by deleting deployments and services instances belonging to this application
func (o kubernetesClient) Delete(name string) error {
klog.V(4).Infof("Deleting application %q", name)
labels := applabels.GetLabels(name, false)
// delete application from cluster
err := o.client.Delete(labels, false)
if err != nil {
return errors.Wrapf(err, "unable to delete application %s", name)
}
return nil
}
// ComponentList returns the list of components for an application
func (o kubernetesClient) ComponentList(name string) ([]component.Component, error) {
selector := applabels.GetSelector(name)
componentList, err := component.List(o.client, selector)
if err != nil {
return nil, errors.Wrap(err, "failed to get Component list")
}
return componentList.Items, nil
}
// GetMachineReadableFormat returns resource information in machine readable format
func (o kubernetesClient) GetMachineReadableFormat(appName string, projectName string) App {
componentList, _ := component.GetComponentNames(o.client, appName)
appDef := App{
TypeMeta: metav1.TypeMeta{
Kind: appKind,
APIVersion: machineoutput.APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: projectName,
},
Spec: AppSpec{
Components: componentList,
},
}
return appDef
}
// GetMachineReadableFormatForList returns application list in machine readable format
func (o kubernetesClient) GetMachineReadableFormatForList(apps []App) AppList {
return AppList{
TypeMeta: metav1.TypeMeta{
Kind: appList,
APIVersion: machineoutput.APIVersion,
},
ListMeta: metav1.ListMeta{},
Items: apps,
}
}

View File

@@ -1,348 +0,0 @@
package application
import (
"errors"
"reflect"
"strings"
"testing"
gomock "github.com/golang/mock/gomock"
applabels "github.com/redhat-developer/odo/pkg/application/labels"
componentlabels "github.com/redhat-developer/odo/pkg/component/labels"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/testingutil"
"github.com/redhat-developer/odo/pkg/unions"
"github.com/redhat-developer/odo/pkg/version"
appsv1 "k8s.io/api/apps/v1"
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 TestList(t *testing.T) {
ctrl := gomock.NewController(t)
kubclient := kclient.NewMockClientInterface(ctrl)
kubclient.EXPECT().GetDeploymentLabelValues("app.kubernetes.io/part-of", "app.kubernetes.io/part-of").Return([]string{"app1", "app3", "app1", "app2"}, nil).AnyTimes()
appClient := NewClient(kubclient)
result, err := appClient.List()
expected := []string{"app1", "app2", "app3"}
if err != nil {
t.Errorf("Expected nil error, got %s", err)
}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Got %v, expected %v", result, expected)
}
}
func TestExists(t *testing.T) {
tests := []struct {
name string
search string
result bool
err bool
}{
{
name: "not exists",
search: "an-app",
result: false,
err: false,
},
{
name: "exists",
search: "app1",
result: true,
err: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
kubclient := kclient.NewMockClientInterface(ctrl)
kubclient.EXPECT().GetDeploymentLabelValues("app.kubernetes.io/part-of", "app.kubernetes.io/part-of").Return([]string{"app1", "app3", "app1", "app2"}, nil).AnyTimes()
appClient := NewClient(kubclient)
result, err := appClient.Exists(tt.search)
if err != nil != tt.err {
t.Errorf("Expected %v error, got %v", tt.err, err)
}
if result != tt.result {
t.Errorf("Expected %v, got %v", tt.result, result)
}
})
}
}
func TestDelete(t *testing.T) {
tests := []struct {
name string
deleteReturn error
expectedErr string
}{
{
name: "kubernetes delete works",
deleteReturn: nil,
expectedErr: "",
},
{
name: "kubernetes delete fails",
deleteReturn: errors.New("an error"),
expectedErr: "unable to delete application",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
kubclient := kclient.NewMockClientInterface(ctrl)
appClient := NewClient(kubclient)
labels := map[string]string{
"app.kubernetes.io/part-of": "an-app",
}
kubclient.EXPECT().Delete(labels, false).Return(tt.deleteReturn).Times(1)
// kube Delete works
err := appClient.Delete("an-app")
if err == nil && tt.expectedErr != "" {
t.Errorf("Expected %v, got no error", tt.expectedErr)
return
}
if err != nil && tt.expectedErr == "" {
t.Errorf("Expected no error, got %v", err.Error())
return
}
if err != nil && tt.expectedErr != "" && !strings.Contains(err.Error(), tt.expectedErr) {
t.Errorf("Expected error %v, got %v", tt.expectedErr, err.Error())
return
}
if err != nil {
return
}
})
}
}
func TestComponentList(t *testing.T) {
ctrl := gomock.NewController(t)
kubclient := kclient.NewMockClientInterface(ctrl)
depList := []appsv1.Deployment{
{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app.kubernetes.io/instance": "a-component",
"app.kubernetes.io/part-of": "an-app-name",
},
Annotations: map[string]string{
"odo.dev/project-type": "nodejs",
},
},
},
}
kubclient.EXPECT().GetDeploymentFromSelector("app=an-app-name,app.kubernetes.io/managed-by=odo,app.kubernetes.io/part-of=an-app-name").Return(depList, nil).AnyTimes()
kubclient.EXPECT().GetCurrentNamespace().Return("my-namespace").AnyTimes()
kubclient.EXPECT().GetOneDeployment("a-component", "an-app-name").Return(&depList[0], nil).AnyTimes()
ingresses := &unions.KubernetesIngressList{
Items: nil,
}
kubclient.EXPECT().ListIngresses("app.kubernetes.io/instance=a-component,app.kubernetes.io/part-of=an-app-name").Return(ingresses, nil).AnyTimes()
kubclient.EXPECT().IsServiceBindingSupported().Return(false, nil).AnyTimes()
kubclient.EXPECT().ListSecrets("app.kubernetes.io/instance=a-component,app.kubernetes.io/part-of=an-app-name").Return(nil, nil).AnyTimes()
kubclient.EXPECT().ListServices("").Return(nil, nil).AnyTimes()
appClient := NewClient(kubclient)
result, err := appClient.ComponentList("an-app-name")
if len(result) != 1 {
t.Errorf("expected 1 component in list, got %d", len(result))
}
component := result[0]
if component.Name != "a-component" {
t.Errorf("Expected component name %q, got %q", "a-component", component.Name)
}
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
}
func TestGetMachineReadableFormat(t *testing.T) {
type args struct {
appName string
projectName string
active bool
}
tests := []struct {
name string
args args
want App
}{
{
name: "Test Case: machine readable output for application",
args: args{
appName: "myapp",
projectName: "myproject",
active: true,
},
want: App{
TypeMeta: metav1.TypeMeta{
Kind: appKind,
APIVersion: machineoutput.APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
Namespace: "myproject",
},
Spec: AppSpec{
Components: []string{"frontend"},
},
},
},
}
deploymentList := appsv1.DeploymentList{
Items: []appsv1.Deployment{
{
ObjectMeta: metav1.ObjectMeta{
Name: "frontend-myapp",
Namespace: "myproject",
Labels: map[string]string{
applabels.ApplicationLabel: "myapp",
componentlabels.ComponentLabel: "frontend",
componentlabels.ComponentTypeLabel: "nodejs",
applabels.ManagedBy: "odo",
applabels.ManagerVersion: version.VERSION,
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "dummyContainer",
},
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "backend-app",
Namespace: "myproject",
Labels: map[string]string{
applabels.ApplicationLabel: "app",
componentlabels.ComponentLabel: "backend",
componentlabels.ComponentTypeLabel: "java",
applabels.ManagedBy: "odo",
applabels.ManagerVersion: version.VERSION,
},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "dummyContainer",
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Fake the client with the appropriate arguments
client, fakeClientSet := kclient.FakeNew()
// fake the project
fakeClientSet.Kubernetes.PrependReactor("get", "projects", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &testingutil.FakeOnlyOneExistingProjects().Items[0], nil
})
//fake the deployments
fakeClientSet.Kubernetes.PrependReactor("list", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &deploymentList, nil
})
for i := range deploymentList.Items {
fakeClientSet.Kubernetes.PrependReactor("get", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) {
return true, &deploymentList.Items[i], nil
})
}
kclient := NewClient(client)
if got := kclient.GetMachineReadableFormat(tt.args.appName, tt.args.projectName); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetMachineReadableFormat() = %v,\n want %v", got, tt.want)
}
})
}
}
func TestGetMachineReadableFormatForList(t *testing.T) {
type args struct {
apps []App
}
tests := []struct {
name string
args args
want AppList
}{
{
name: "Test Case: Machine Readable for Application List",
args: args{
apps: []App{
{
TypeMeta: metav1.TypeMeta{
Kind: appKind,
APIVersion: machineoutput.APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
},
Spec: AppSpec{
Components: []string{"frontend"},
},
},
},
},
want: AppList{
TypeMeta: metav1.TypeMeta{
Kind: appList,
APIVersion: machineoutput.APIVersion,
},
ListMeta: metav1.ListMeta{},
Items: []App{
{
TypeMeta: metav1.TypeMeta{
Kind: appKind,
APIVersion: machineoutput.APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: "myapp",
},
Spec: AppSpec{
Components: []string{"frontend"},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, _ := kclient.FakeNew()
kclient := NewClient(client)
if got := kclient.GetMachineReadableFormatForList(tt.args.apps); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetMachineReadableFormatForList() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,122 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: pkg/application/application.go
// Package application is a generated GoMock package.
package application
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
component "github.com/redhat-developer/odo/pkg/component"
)
// MockClient is a mock of Client interface.
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
}
// MockClientMockRecorder is the mock recorder for MockClient.
type MockClientMockRecorder struct {
mock *MockClient
}
// NewMockClient creates a new mock instance.
func NewMockClient(ctrl *gomock.Controller) *MockClient {
mock := &MockClient{ctrl: ctrl}
mock.recorder = &MockClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClient) EXPECT() *MockClientMockRecorder {
return m.recorder
}
// ComponentList mocks base method.
func (m *MockClient) ComponentList(name string) ([]component.Component, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ComponentList", name)
ret0, _ := ret[0].([]component.Component)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ComponentList indicates an expected call of ComponentList.
func (mr *MockClientMockRecorder) ComponentList(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ComponentList", reflect.TypeOf((*MockClient)(nil).ComponentList), name)
}
// Delete mocks base method.
func (m *MockClient) Delete(name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", name)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockClientMockRecorder) Delete(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), name)
}
// Exists mocks base method.
func (m *MockClient) Exists(app string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Exists", app)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Exists indicates an expected call of Exists.
func (mr *MockClientMockRecorder) Exists(app interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockClient)(nil).Exists), app)
}
// GetMachineReadableFormat mocks base method.
func (m *MockClient) GetMachineReadableFormat(appName, projectName string) App {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMachineReadableFormat", appName, projectName)
ret0, _ := ret[0].(App)
return ret0
}
// GetMachineReadableFormat indicates an expected call of GetMachineReadableFormat.
func (mr *MockClientMockRecorder) GetMachineReadableFormat(appName, projectName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachineReadableFormat", reflect.TypeOf((*MockClient)(nil).GetMachineReadableFormat), appName, projectName)
}
// GetMachineReadableFormatForList mocks base method.
func (m *MockClient) GetMachineReadableFormatForList(apps []App) AppList {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMachineReadableFormatForList", apps)
ret0, _ := ret[0].(AppList)
return ret0
}
// GetMachineReadableFormatForList indicates an expected call of GetMachineReadableFormatForList.
func (mr *MockClientMockRecorder) GetMachineReadableFormatForList(apps interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachineReadableFormatForList", reflect.TypeOf((*MockClient)(nil).GetMachineReadableFormatForList), apps)
}
// List mocks base method.
func (m *MockClient) List() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List")
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockClientMockRecorder) List() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List))
}

View File

@@ -1,29 +0,0 @@
package application
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
appKind = "Application"
appList = "List"
)
// Application
type App struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec AppSpec `json:"spec,omitempty"`
}
// AppSpec is list of components present in given application
type AppSpec struct {
Components []string `json:"components,omitempty"`
}
// AppList is a list of applications
type AppList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []App `json:"items"`
}

View File

@@ -1,106 +0,0 @@
package config
import (
"strings"
"github.com/devfile/library/pkg/devfile/parser"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/util"
)
const (
// Name is the name of the setting controlling the component name
Name = "Name"
// NameDescription is human-readable description of the Name setting
NameDescription = "The name of the component"
// Memory is the name of the setting controlling the memory a component consumes
Memory = "Memory"
// MemoryDescription is the description of the setting controlling the min and max memory to same value
MemoryDescription = "The minimum and maximum memory a component can consume"
// Ports is the space separated list of user specified ports to be opened in the component
Ports = "Ports"
// PortsDescription is the description of the ports component setting
PortsDescription = "Ports to be opened in the component"
)
var (
supportedDevfileParameterDescriptions = map[string]string{
Name: NameDescription,
Ports: PortsDescription,
Memory: MemoryDescription,
}
lowerCaseDevfileParameters = util.GetLowerCaseParameters(GetDevfileSupportedParameters())
)
// FormatDevfileSupportedParameters outputs supported parameters and their description
func FormatDevfileSupportedParameters() (result string) {
for _, v := range GetDevfileSupportedParameters() {
result = result + " " + v + " - " + supportedDevfileParameterDescriptions[v] + "\n"
}
return "\nAvailable Parameters for Devfile Components:\n" + result
}
func GetDevfileSupportedParameters() []string {
return util.GetSortedKeys(supportedDevfileParameterDescriptions)
}
// AsDevfileSupportedParameter returns the parameter in lower case and a boolean indicating if it is a supported parameter
func AsDevfileSupportedParameter(param string) (string, bool) {
lower := strings.ToLower(param)
return lower, lowerCaseDevfileParameters[lower]
}
// SetDevfileConfiguration allows setting all the parameters that are configurable in a devfile
func SetDevfileConfiguration(d parser.DevfileObj, parameter string, value interface{}) error {
// we are ignoring this error becase a developer is usually aware of the type of value that is
// being passed. So consider this a shortcut, if you know its a string value use this strValue
// else parse it inside the switch case.
strValue, _ := value.(string)
if param, ok := AsDevfileSupportedParameter(parameter); ok {
switch param {
case "name":
return d.SetMetadataName(strValue)
case "ports":
arrValue := strings.Split(strValue, ",")
return d.SetPorts(arrValue...)
case "memory":
return d.SetMemory(strValue)
}
}
return errors.Errorf("unknown parameter :'%s' is not a configurable parameter in the devfile", parameter)
}
// DeleteConfiguration allows deleting the parameters that are configurable in a devfile
func DeleteDevfileConfiguration(d parser.DevfileObj, parameter string) error {
if param, ok := AsDevfileSupportedParameter(parameter); ok {
switch param {
case "name":
return d.SetMetadataName("")
case "ports":
return d.RemovePorts()
case "memory":
return d.SetMemory("")
}
}
return errors.Errorf("unknown parameter :'%s' is not a configurable parameter in the devfile", parameter)
}
// IsSet checks if a parameter is set in the devfile
func IsSetInDevfile(d parser.DevfileObj, parameter string) bool {
if p, ok := AsDevfileSupportedParameter(parameter); ok {
switch p {
case "name":
return d.GetMetadataName() != ""
case "ports":
return d.HasPorts()
case "memory":
return d.GetMemory() != ""
}
}
return false
}

View File

@@ -1,209 +0,0 @@
package config
import (
"reflect"
"testing"
"github.com/devfile/library/pkg/devfile/parser/data"
devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/parser"
devfileCtx "github.com/devfile/library/pkg/devfile/parser/context"
devfilefs "github.com/devfile/library/pkg/testingutil/filesystem"
"github.com/kylelemons/godebug/pretty"
odoTestingUtil "github.com/redhat-developer/odo/pkg/testingutil"
)
func TestSetDevfileConfiguration(t *testing.T) {
// Use fakeFs
fs := devfilefs.NewFakeFs()
tests := []struct {
name string
args map[string]string
currentDevfile parser.DevfileObj
wantDevFile parser.DevfileObj
wantErr bool
}{
{
name: "case 1: set memory to 500Mi",
args: map[string]string{
"memory": "500Mi",
},
currentDevfile: odoTestingUtil.GetTestDevfileObj(fs),
wantDevFile: parser.DevfileObj{
Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath),
Data: func() data.DevfileData {
devfileData, err := data.NewDevfileData(string(data.APISchemaVersion200))
if err != nil {
t.Error(err)
}
err = devfileData.AddComponents([]devfilev1.Component{
{
Name: "runtime",
ComponentUnion: devfilev1.ComponentUnion{
Container: &devfilev1.ContainerComponent{
Container: devfilev1.Container{
Image: "quay.io/nodejs-12",
MemoryLimit: "500Mi",
},
Endpoints: []devfilev1.Endpoint{
{
Name: "port-3030",
TargetPort: 3000,
},
},
},
},
},
{
Name: "loadbalancer",
ComponentUnion: devfilev1.ComponentUnion{
Container: &devfilev1.ContainerComponent{
Container: devfilev1.Container{
Image: "quay.io/nginx",
MemoryLimit: "500Mi",
},
},
},
},
})
if err != nil {
t.Error(err)
}
err = devfileData.AddCommands([]devfilev1.Command{
{
Id: "devbuild",
CommandUnion: devfilev1.CommandUnion{
Exec: &devfilev1.ExecCommand{
WorkingDir: "/projects/nodejs-starter",
},
},
},
})
if err != nil {
t.Error(err)
}
return devfileData
}(),
},
},
{
name: "case 2: set ports array",
args: map[string]string{
"ports": "8080,8081/UDP,8080/TCP",
},
currentDevfile: odoTestingUtil.GetTestDevfileObj(fs),
wantDevFile: parser.DevfileObj{
Ctx: devfileCtx.FakeContext(fs, parser.OutputDevfileYamlPath),
Data: func() data.DevfileData {
devfileData, err := data.NewDevfileData(string(data.APISchemaVersion200))
if err != nil {
t.Error(err)
}
err = devfileData.AddCommands([]devfilev1.Command{
{
Id: "devbuild",
CommandUnion: devfilev1.CommandUnion{
Exec: &devfilev1.ExecCommand{
WorkingDir: "/projects/nodejs-starter",
},
},
},
})
if err != nil {
t.Error(err)
}
err = devfileData.AddComponents([]devfilev1.Component{
{
Name: "runtime",
ComponentUnion: devfilev1.ComponentUnion{
Container: &devfilev1.ContainerComponent{
Container: devfilev1.Container{
Image: "quay.io/nodejs-12",
},
Endpoints: []devfilev1.Endpoint{
{
Name: "port-3030",
TargetPort: 3000,
},
{
Name: "port-8080-tcp",
TargetPort: 8080,
Protocol: "tcp",
}, {
Name: "port-8081-udp",
TargetPort: 8081,
Protocol: "udp",
},
},
},
},
},
{
Name: "loadbalancer",
ComponentUnion: devfilev1.ComponentUnion{
Container: &devfilev1.ContainerComponent{
Container: devfilev1.Container{
Image: "quay.io/nginx",
},
Endpoints: []devfilev1.Endpoint{
{
Name: "port-8080-tcp",
TargetPort: 8080,
Protocol: "tcp",
}, {
Name: "port-8081-udp",
TargetPort: 8081,
Protocol: "udp",
},
},
},
},
},
})
if err != nil {
t.Error(err)
}
return devfileData
}(),
},
},
{
name: "case 3: set ports array fails due to validation",
args: map[string]string{
"ports": "8080,8081/UDP,8083/",
},
currentDevfile: odoTestingUtil.GetTestDevfileObj(fs),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for key, value := range tt.args {
err := SetDevfileConfiguration(tt.currentDevfile, key, value)
if tt.wantErr {
if err == nil {
t.Errorf("expected error but got nil")
}
// we dont expect an error here
} else {
if err != nil {
t.Errorf("error while setting configuration %+v", err.Error())
}
}
}
if !tt.wantErr {
if !reflect.DeepEqual(tt.currentDevfile.Data, tt.wantDevFile.Data) {
t.Errorf("wanted: %v, got: %v, difference at %v", tt.wantDevFile, tt.currentDevfile, pretty.Compare(tt.currentDevfile.Data, tt.wantDevFile.Data))
}
}
})
}
}

View File

@@ -1,5 +0,0 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
approvers:
- mik-dass

View File

@@ -1,160 +0,0 @@
package debug
import (
"encoding/json"
"errors"
"net"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
)
// Info contains the information about the current Debug session
type Info struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec InfoSpec `json:"spec"`
}
type InfoSpec struct {
App string `json:"app,omitempty"`
DebugProcessID int `json:"debugProcessID"`
RemotePort int `json:"remotePort"`
LocalPort int `json:"localPort"`
}
// GetDebugInfoFilePath gets the file path of the debug info file
func GetDebugInfoFilePath(componentName, appName string, projectName string) string {
tempDir := os.TempDir()
debugFileSuffix := "odo-debug.json"
var arr []string
if appName == "" {
arr = []string{projectName, componentName, debugFileSuffix}
} else {
arr = []string{projectName, appName, componentName, debugFileSuffix}
}
debugFileName := strings.Join(arr, "-")
return filepath.Join(tempDir, debugFileName)
}
func CreateDebugInfoFile(f *DefaultPortForwarder, portPair string) error {
return createDebugInfoFile(f, portPair, filesystem.DefaultFs{})
}
// createDebugInfoFile creates a file in the temp directory with information regarding the debugging session of a component
func createDebugInfoFile(f *DefaultPortForwarder, portPair string, fs filesystem.Filesystem) error {
portPairs := strings.Split(portPair, ":")
if len(portPairs) != 2 {
return errors.New("port pair should be of the format localPort:RemotePort")
}
localPort, err := strconv.Atoi(portPairs[0])
if err != nil {
return errors.New("local port should be a int")
}
remotePort, err := strconv.Atoi(portPairs[1])
if err != nil {
return errors.New("remote port should be a int")
}
debugFile := Info{
TypeMeta: metav1.TypeMeta{
Kind: "OdoDebugInfo",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: f.componentName,
Namespace: f.projectName,
},
Spec: InfoSpec{
App: f.appName,
DebugProcessID: os.Getpid(),
RemotePort: remotePort,
LocalPort: localPort,
},
}
odoDebugPathData, err := json.Marshal(debugFile)
if err != nil {
return errors.New("error marshalling json data")
}
// writes the data to the debug info file
file, err := fs.OpenFile(GetDebugInfoFilePath(f.componentName, f.appName, f.projectName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer file.Close() // #nosec G307
_, err = file.Write(odoDebugPathData)
if err != nil {
return err
}
return nil
}
// GetInfo gathers the information with regards to debugging information
func GetInfo(f *DefaultPortForwarder) (Info, bool) {
return getInfo(f, filesystem.DefaultFs{})
}
// getInfo gets information regarding the debugging session of the component
// returns the OdoDebugFile from the debug info file
// returns true if debugging is running else false
func getInfo(f *DefaultPortForwarder, fs filesystem.Filesystem) (Info, bool) {
// gets the debug info file path and reads/unmarshalls it
debugInfoFilePath := GetDebugInfoFilePath(f.componentName, f.appName, f.projectName)
readFile, err := fs.ReadFile(debugInfoFilePath)
if err != nil {
klog.V(4).Infof("the debug %v is not present", debugInfoFilePath)
return Info{}, false
}
var info Info
err = json.Unmarshal(readFile, &info)
if err != nil {
klog.V(4).Infof("couldn't unmarshal the debug file %v", debugInfoFilePath)
return Info{}, false
}
// get the debug process id and send a signal 0 to check if it's alive or not
// according to https://golang.org/pkg/os/#FindProcess
// On Unix systems, FindProcess always succeeds and returns a Process for the given pid, regardless of whether the process exists.
// thus this step will pass on Unix systems and so for those systems and some others supporting signals
// we check if the process is alive or not by sending a signal 0 to the process
processInfo, err := os.FindProcess(info.Spec.DebugProcessID)
if err != nil || processInfo == nil {
klog.V(4).Infof("error getting the process info for pid %v", info.Spec.DebugProcessID)
return Info{}, false
}
// signal is not available on windows so we skip this step for windows
if runtime.GOOS != "windows" {
err = processInfo.Signal(syscall.Signal(0))
if err != nil {
klog.V(4).Infof("error sending signal 0 to pid %v, cause: %v", info.Spec.DebugProcessID, err)
return Info{}, false
}
}
// gets the debug local port and tries to listen on it
// if error doesn't occur the debug port was free and thus no debug process was using the port
addressLook := "localhost:" + strconv.Itoa(info.Spec.LocalPort)
listener, err := net.Listen("tcp", addressLook)
if err == nil {
klog.V(4).Infof("the debug port %v is free, thus debug is not running", info.Spec.LocalPort)
err = listener.Close()
if err != nil {
klog.V(4).Infof("error occurred while closing the listener, cause :%v", err)
}
return Info{}, false
}
return info, true
}

View File

@@ -1,369 +0,0 @@
package debug
import (
"encoding/json"
"os"
"reflect"
"testing"
"github.com/redhat-developer/odo/pkg/util"
"github.com/redhat-developer/odo/pkg/testingutil"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// fakeOdoDebugFileString creates a json string of a fake OdoDebugFile
func fakeOdoDebugFileString(typeMeta metav1.TypeMeta, processID int, projectName, appName, componentName string, remotePort, localPort int) (string, error) {
file := Info{
TypeMeta: typeMeta,
ObjectMeta: metav1.ObjectMeta{
Namespace: projectName,
Name: componentName,
},
Spec: InfoSpec{
App: appName,
DebugProcessID: processID,
RemotePort: remotePort,
LocalPort: localPort,
},
}
data, err := json.Marshal(file)
if err != nil {
return "", err
}
return string(data), nil
}
func Test_createDebugInfoFile(t *testing.T) {
// create a fake fs in memory
fs := filesystem.NewFakeFs()
type args struct {
defaultPortForwarder *DefaultPortForwarder
portPair string
fs filesystem.Filesystem
}
tests := []struct {
name string
args args
alreadyExistFile bool
wantDebugInfo Info
wantErr bool
}{
{
name: "case 1: normal json write to the debug file",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
componentName: "nodejs-ex",
appName: "app",
projectName: "testing-1",
},
portPair: "5858:9001",
fs: fs,
},
wantDebugInfo: Info{
TypeMeta: metav1.TypeMeta{
Kind: "OdoDebugInfo",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nodejs-ex",
Namespace: "testing-1",
},
Spec: InfoSpec{
DebugProcessID: os.Getpid(),
App: "app",
RemotePort: 9001,
LocalPort: 5858,
},
},
alreadyExistFile: false,
wantErr: false,
},
{
name: "case 2: overwrite the debug file",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
componentName: "nodejs-ex",
appName: "app",
projectName: "testing-1",
},
portPair: "5758:9004",
fs: fs,
},
wantDebugInfo: Info{
TypeMeta: metav1.TypeMeta{
Kind: "OdoDebugInfo",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nodejs-ex",
Namespace: "testing-1",
},
Spec: InfoSpec{
DebugProcessID: os.Getpid(),
App: "app",
RemotePort: 9004,
LocalPort: 5758,
},
},
alreadyExistFile: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
debugFilePath := GetDebugInfoFilePath(tt.args.defaultPortForwarder.componentName, tt.args.defaultPortForwarder.appName, tt.args.defaultPortForwarder.projectName)
// create a already existing file
if tt.alreadyExistFile {
_, err := testingutil.MkFileWithContent(debugFilePath, "blah", fs)
if err != nil {
t.Errorf("error happened while writing, cause: %v", err)
}
}
if err := createDebugInfoFile(tt.args.defaultPortForwarder, tt.args.portPair, tt.args.fs); (err != nil) != tt.wantErr {
t.Errorf("createDebugInfoFile() error = %v, wantErr %v", err, tt.wantErr)
}
readBytes, err := fs.ReadFile(debugFilePath)
if err != nil {
t.Errorf("error while reading file, cause: %v", err)
}
var odoDebugFileData Info
err = json.Unmarshal(readBytes, &odoDebugFileData)
if err != nil {
t.Errorf("error occured while unmarshalling json, cause: %v", err)
}
if !reflect.DeepEqual(tt.wantDebugInfo, odoDebugFileData) {
t.Errorf("odo debug info on file doesn't match, got: %v, want: %v", odoDebugFileData, tt.wantDebugInfo)
}
// clear the odo debug info file
_ = fs.RemoveAll(debugFilePath)
})
}
}
func Test_getDebugInfo(t *testing.T) {
// create a fake fs in memory
fs := filesystem.NewFakeFs()
type args struct {
defaultPortForwarder *DefaultPortForwarder
fs filesystem.Filesystem
}
tests := []struct {
name string
args args
fileExists bool
debugPortListening bool
readDebugFile Info
wantDebugFile Info
debugRunning bool
}{
{
name: "case 1: the debug file exists",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
appName: "app",
componentName: "nodejs-ex",
projectName: "testing-1",
},
fs: fs,
},
wantDebugFile: Info{
TypeMeta: metav1.TypeMeta{
Kind: "OdoDebugInfo",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nodejs-ex",
Namespace: "testing-1",
},
Spec: InfoSpec{
DebugProcessID: os.Getpid(),
App: "app",
RemotePort: 5858,
LocalPort: 9001,
},
},
readDebugFile: Info{
TypeMeta: metav1.TypeMeta{
Kind: "OdoDebugInfo",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nodejs-ex",
Namespace: "testing-1",
},
Spec: InfoSpec{
DebugProcessID: os.Getpid(),
App: "app",
RemotePort: 5858,
LocalPort: 9001,
},
},
debugPortListening: true,
fileExists: true,
debugRunning: true,
},
{
name: "case 2: the debug file doesn't exists",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
appName: "app",
componentName: "nodejs-ex",
projectName: "testing-1",
},
fs: fs,
},
debugPortListening: true,
wantDebugFile: Info{},
readDebugFile: Info{},
fileExists: false,
debugRunning: false,
},
{
name: "case 3: debug port not listening",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
appName: "app",
componentName: "nodejs-ex",
projectName: "testing-1",
},
fs: fs,
},
debugPortListening: false,
wantDebugFile: Info{},
readDebugFile: Info{
TypeMeta: metav1.TypeMeta{
Kind: "OdoDebugInfo",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nodejs-ex",
Namespace: "testing-1",
},
Spec: InfoSpec{
DebugProcessID: os.Getpid(),
App: "app",
RemotePort: 5858,
LocalPort: 9001,
},
},
fileExists: true,
debugRunning: false,
},
{
name: "case 4: the process is not running",
args: args{
defaultPortForwarder: &DefaultPortForwarder{
appName: "app",
componentName: "nodejs-ex",
projectName: "testing-1",
},
fs: fs,
},
debugPortListening: true,
wantDebugFile: Info{},
readDebugFile: Info{
TypeMeta: metav1.TypeMeta{
Kind: "OdoDebugInfo",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nodejs-ex",
Namespace: "testing-1",
},
Spec: InfoSpec{
DebugProcessID: os.Getpid() + 818177979,
App: "app",
RemotePort: 5858,
LocalPort: 9001,
},
},
fileExists: true,
debugRunning: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
freePort, err := util.HTTPGetFreePort()
if err != nil {
t.Errorf("error occured while getting a free port, cause: %v", err)
}
if tt.readDebugFile.Spec.LocalPort != 0 {
tt.readDebugFile.Spec.LocalPort = freePort
}
if tt.wantDebugFile.Spec.LocalPort != 0 {
tt.wantDebugFile.Spec.LocalPort = freePort
}
odoDebugFilePath := GetDebugInfoFilePath(tt.args.defaultPortForwarder.componentName, tt.args.defaultPortForwarder.appName, tt.args.defaultPortForwarder.projectName)
if tt.fileExists {
fakeString, err := fakeOdoDebugFileString(tt.readDebugFile.TypeMeta,
tt.readDebugFile.Spec.DebugProcessID,
tt.readDebugFile.ObjectMeta.Namespace,
tt.readDebugFile.Spec.App,
tt.readDebugFile.ObjectMeta.Name,
tt.readDebugFile.Spec.RemotePort,
tt.readDebugFile.Spec.LocalPort)
if err != nil {
t.Errorf("error occured while getting odo debug file string, cause: %v", err)
}
_, err = testingutil.MkFileWithContent(odoDebugFilePath, fakeString, fs)
if err != nil {
t.Errorf("error occured while writing to file, cause: %v", err)
}
}
stopListenerChan := make(chan bool)
listenerStarted := false
if tt.debugPortListening {
startListenerChan := make(chan bool)
go func() {
err := testingutil.FakePortListener(startListenerChan, stopListenerChan, tt.readDebugFile.Spec.LocalPort)
if err != nil {
// the fake listener failed, show error and close the channel
t.Errorf("error while starting fake port listerner, cause: %v", err)
close(startListenerChan)
}
}()
// wait for the test server to start listening
if <-startListenerChan {
listenerStarted = true
}
}
got, resultRunning := getInfo(tt.args.defaultPortForwarder, tt.args.fs)
if !reflect.DeepEqual(got, tt.wantDebugFile) {
t.Errorf("getDebugInfo() got = %v, want %v", got, tt.wantDebugFile)
}
if resultRunning != tt.debugRunning {
t.Errorf("getDebugInfo() got1 = %v, want %v", resultRunning, tt.debugRunning)
}
// clear the odo debug info file
_ = fs.RemoveAll(odoDebugFilePath)
// close the listener
if listenerStarted == true {
stopListenerChan <- true
}
close(stopListenerChan)
})
}
}

View File

@@ -1,78 +0,0 @@
package debug
import (
"github.com/redhat-developer/odo/pkg/kclient"
"k8s.io/client-go/rest"
"fmt"
"net/http"
"github.com/redhat-developer/odo/pkg/log"
corev1 "k8s.io/api/core/v1"
k8sgenclioptions "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)
// DefaultPortForwarder implements the SPDY based port forwarder
type DefaultPortForwarder struct {
kClient kclient.ClientInterface
k8sgenclioptions.IOStreams
componentName string
appName string
projectName string
}
func NewDefaultPortForwarder(componentName, appName string, projectName string, kClient kclient.ClientInterface, streams k8sgenclioptions.IOStreams) *DefaultPortForwarder {
return &DefaultPortForwarder{
kClient: kClient,
IOStreams: streams,
componentName: componentName,
appName: appName,
projectName: projectName,
}
}
// ForwardPorts forwards the port using the url for the remote pod.
// portPair is a pair of port in format "localPort:RemotePort" that is to be forwarded
// stop Chan is used to stop port forwarding
// ready Chan is used to signal failure to the channel receiver
func (f *DefaultPortForwarder) ForwardPorts(portPair string, stopChan, readyChan chan struct{}, isDevfile bool) error {
var pod *corev1.Pod
var conf *rest.Config
var err error
if f.kClient != nil && isDevfile {
conf, err = f.kClient.GetConfig().ClientConfig()
if err != nil {
return err
}
pod, err = f.kClient.GetOnePod(f.componentName, f.appName)
if err != nil {
return err
}
} else {
conf = f.kClient.GetClientConfig()
}
if pod.Status.Phase != corev1.PodRunning {
return fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase)
}
transport, upgrader, err := spdy.RoundTripperFor(conf)
if err != nil {
return err
}
req := f.kClient.GeneratePortForwardReq(pod.Name)
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL())
fw, err := portforward.New(dialer, []string{portPair}, stopChan, readyChan, f.Out, f.ErrOut)
if err != nil {
return err
}
log.Info("Started port forwarding at ports -", portPair)
return fw.ForwardPorts()
}

View File

@@ -1,3 +1,4 @@
// envinfo package is DEPRECATED and will be removed during v3 implementation
package envinfo package envinfo
import ( import (

View File

@@ -6,7 +6,7 @@ import (
"github.com/devfile/library/pkg/devfile/generator" "github.com/devfile/library/pkg/devfile/generator"
applabels "github.com/redhat-developer/odo/pkg/application/labels" applabels "github.com/redhat-developer/odo/pkg/application/labels"
componentlabels "github.com/redhat-developer/odo/pkg/component/labels" componentlabels "github.com/redhat-developer/odo/pkg/component/labels"
"github.com/redhat-developer/odo/pkg/unions" "github.com/redhat-developer/odo/pkg/kclient/unions"
"github.com/redhat-developer/odo/pkg/url/labels" "github.com/redhat-developer/odo/pkg/url/labels"
"github.com/redhat-developer/odo/pkg/util" "github.com/redhat-developer/odo/pkg/util"
"github.com/redhat-developer/odo/pkg/version" "github.com/redhat-developer/odo/pkg/version"

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/redhat-developer/odo/pkg/unions" "github.com/redhat-developer/odo/pkg/kclient/unions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/redhat-developer/odo/pkg/unions" "github.com/redhat-developer/odo/pkg/kclient/unions"
"github.com/devfile/library/pkg/devfile/generator" "github.com/devfile/library/pkg/devfile/generator"
"github.com/pkg/errors" "github.com/pkg/errors"

View File

@@ -11,8 +11,8 @@ import (
projectv1 "github.com/openshift/api/project/v1" projectv1 "github.com/openshift/api/project/v1"
routev1 "github.com/openshift/api/route/v1" routev1 "github.com/openshift/api/route/v1"
olm "github.com/operator-framework/api/pkg/operators/v1alpha1" olm "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/redhat-developer/odo/pkg/kclient/unions"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/unions"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

View File

@@ -14,8 +14,8 @@ import (
v1 "github.com/openshift/api/project/v1" v1 "github.com/openshift/api/project/v1"
v10 "github.com/openshift/api/route/v1" v10 "github.com/openshift/api/route/v1"
v1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" v1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
unions "github.com/redhat-developer/odo/pkg/kclient/unions"
log "github.com/redhat-developer/odo/pkg/log" log "github.com/redhat-developer/odo/pkg/log"
unions "github.com/redhat-developer/odo/pkg/unions"
v11 "k8s.io/api/apps/v1" v11 "k8s.io/api/apps/v1"
v12 "k8s.io/api/core/v1" v12 "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/api/meta" meta "k8s.io/apimachinery/pkg/api/meta"

View File

@@ -1,4 +1,4 @@
package odogenerator package unions
import ( import (
"github.com/devfile/library/pkg/devfile/generator" "github.com/devfile/library/pkg/devfile/generator"
@@ -54,7 +54,7 @@ func getNetworkingV1IngressSpec(ingressSpecParams generator.IngressSpecParams) *
return ingressSpec return ingressSpec
} }
func GetNetworkingV1Ingress(ingressParams generator.IngressParams) *v1.Ingress { func getNetworkingV1Ingress(ingressParams generator.IngressParams) *v1.Ingress {
var ip *v1.Ingress var ip *v1.Ingress
ingressSpec := getNetworkingV1IngressSpec(ingressParams.IngressSpecParams) ingressSpec := getNetworkingV1IngressSpec(ingressParams.IngressSpecParams)
ip = &v1.Ingress{ ip = &v1.Ingress{

View File

@@ -5,7 +5,6 @@ import (
v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/pkg/devfile/generator" "github.com/devfile/library/pkg/devfile/generator"
"github.com/redhat-developer/odo/pkg/odogenerator"
"k8s.io/api/extensions/v1beta1" "k8s.io/api/extensions/v1beta1"
v1 "k8s.io/api/networking/v1" v1 "k8s.io/api/networking/v1"
) )
@@ -37,7 +36,7 @@ func NewGeneratedKubernetesIngress() *KubernetesIngress {
//NewKubernetesIngressFromParams generates a new KubernetesIngress from the ingress params //NewKubernetesIngressFromParams generates a new KubernetesIngress from the ingress params
func NewKubernetesIngressFromParams(ingressParams generator.IngressParams) *KubernetesIngress { func NewKubernetesIngressFromParams(ingressParams generator.IngressParams) *KubernetesIngress {
ki := NewGeneratedKubernetesIngress() ki := NewGeneratedKubernetesIngress()
ki.NetworkingV1Ingress = odogenerator.GetNetworkingV1Ingress(ingressParams) ki.NetworkingV1Ingress = getNetworkingV1Ingress(ingressParams)
ki.ExtensionV1Beta1Ingress = generator.GetIngress(v1alpha2.Endpoint{}, ingressParams) ki.ExtensionV1Beta1Ingress = generator.GetIngress(v1alpha2.Endpoint{}, ingressParams)
return ki return ki
} }

View File

@@ -1,48 +0,0 @@
package application
import (
"fmt"
"github.com/spf13/cobra"
"github.com/redhat-developer/odo/pkg/odo/util"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
)
// RecommendedCommandName is the recommended app command name
const RecommendedCommandName = "app"
// NewCmdApplication implements the odo application command
func NewCmdApplication(name, fullName string) *cobra.Command {
delete := NewCmdDelete(deleteRecommendedCommandName, odoutil.GetFullName(fullName, deleteRecommendedCommandName))
describe := NewCmdDescribe(describeRecommendedCommandName, odoutil.GetFullName(fullName, describeRecommendedCommandName))
list := NewCmdList(listRecommendedCommandName, odoutil.GetFullName(fullName, listRecommendedCommandName))
applicationCmd := &cobra.Command{
Use: name,
Short: "Perform application operations",
Long: `Performs application operations related to your project.`,
Example: fmt.Sprintf("%s\n\n%s\n\n%s",
delete.Example,
describe.Example,
list.Example),
Aliases: []string{"application"},
Run: func(cmd *cobra.Command, args []string) {
},
}
applicationCmd.AddCommand(delete, describe, list)
// Add a defined annotation in order to appear in the help menu
applicationCmd.Annotations = map[string]string{"command": "main"}
applicationCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
return applicationCmd
}
// AddApplicationFlag adds a `app` flag to the given cobra command
// Also adds a completion handler to the flag
func AddApplicationFlag(cmd *cobra.Command) {
cmd.Flags().String(util.ApplicationFlagName, "", "Application, defaults to active application")
completion.RegisterCommandFlagHandler(cmd, "app", completion.AppCompletionHandler)
}

View File

@@ -1,152 +0,0 @@
package application
import (
"fmt"
"github.com/spf13/cobra"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoUtil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const deleteRecommendedCommandName = "delete"
var (
deleteExample = ktemplates.Examples(` # Delete the application
%[1]s myapp`)
)
// DeleteOptions encapsulates the options for the odo command
type DeleteOptions struct {
// Context
*genericclioptions.Context
// Clients
appClient application.Client
// Parameters
appName string
// Flags
forceFlag bool
}
// NewDeleteOptions creates a new DeleteOptions instance
func NewDeleteOptions(appClient application.Client) *DeleteOptions {
return &DeleteOptions{
appClient: appClient,
}
}
// Complete completes DeleteOptions after they've been created
func (o *DeleteOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
o.appName = o.GetApplication()
if len(args) == 1 {
// If app name passed, consider it for deletion
o.appName = args[0]
}
return
}
// Validate validates the DeleteOptions based on completed values
func (o *DeleteOptions) Validate() (err error) {
if o.Context.GetProject() == "" || o.appName == "" {
return odoUtil.ThrowContextError()
}
exist, err := o.appClient.Exists(o.appName)
if !exist {
return fmt.Errorf("%s app does not exists", o.appName)
}
return err
}
// Run contains the logic for the odo command
func (o *DeleteOptions) Run() (err error) {
if o.IsJSON() {
return o.appClient.Delete(o.appName)
}
// Print App Information which will be deleted
err = printAppInfo(o.appClient, o.appName, o.GetProject())
if err != nil {
return err
}
if o.forceFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete the application: %v from project: %v", o.appName, o.GetProject())) {
err = o.appClient.Delete(o.appName)
if err != nil {
return err
}
log.Infof("Deleted application: %s from project: %v", o.appName, o.GetProject())
} else {
log.Infof("Aborting deletion of application: %v", o.appName)
}
return nil
}
// printAppInfo will print information about the app requested for deletion
func printAppInfo(appClient application.Client, appName string, projectName string) error {
components, err := appClient.ComponentList(appName)
if err != nil {
return err
}
if len(components) != 0 {
log.Info("This application has following components that will be deleted")
for _, currentComponent := range components {
log.Info("component named", currentComponent.Name)
if len(currentComponent.Spec.URL) != 0 {
log.Info("This component has following urls that will be deleted with component")
for _, u := range currentComponent.Spec.URLSpec {
log.Info("URL named", u.GetName(), "with host", u.Spec.Host, "having protocol", u.Spec.Protocol, "at port", u.Spec.Port)
}
}
if len(currentComponent.Spec.Storage) != 0 {
log.Info("The component has following storages which will be deleted with the component")
for _, storage := range currentComponent.Spec.StorageSpec {
store := storage
log.Info("Storage named", store.GetName(), "of size", store.Spec.Size)
}
}
}
}
return nil
}
// NewCmdDelete implements the odo command.
func NewCmdDelete(name, fullName string) *cobra.Command {
// The error is not handled at this point, it will be handled during Context creation
kubclient, _ := kclient.New()
o := NewDeleteOptions(application.NewClient(kubclient))
command := &cobra.Command{
Use: name,
Short: "Delete the given application",
Long: "Delete the given application",
Example: fmt.Sprintf(deleteExample, fullName),
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
command.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Delete application without prompting")
project.AddProjectFlag(command)
completion.RegisterCommandHandler(command, completion.AppCompletionHandler)
return command
}

View File

@@ -1,203 +0,0 @@
package application
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/golang/mock/gomock"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDelete(t *testing.T) {
prefixDir, err := os.MkdirTemp(os.TempDir(), "unittests-")
if err != nil {
t.Errorf("Error creating temp directory for tests")
return
}
workingDir := filepath.Join(prefixDir, "myapp")
tests := []struct {
name string
populateWorkingDir func(fs filesystem.Filesystem)
args []string
existingApps []string
wantAppName string
wantErrValidate string
}{
{
name: "default app",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "an-app-name",
})
},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "an-app-name",
},
{
name: "app from args",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "an-app-name",
})
},
args: []string{"another-app-name"},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "another-app-name",
},
{
name: "empty app name",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "",
})
},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "",
wantErrValidate: "Please specify the application name and project name",
},
{
name: "non existing app name",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "an-app-name",
})
},
args: []string{"an-unknown-app-name"},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "an-unknown-app-name",
wantErrValidate: " app does not exists",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// the first one is to cleanup the directory before execution (in case there are remaining files from a previous execution)
os.RemoveAll(prefixDir)
// the second one to cleanup after execution
defer os.RemoveAll(prefixDir)
// Fake Cobra
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cmdline := cmdline.NewMockCmdline(ctrl)
// Fake odo Kube client
kclient := kclient.NewMockClientInterface(ctrl)
/* Mocks for Complete */
cmdline.EXPECT().GetWorkingDirectory().Return(workingDir, nil).AnyTimes()
cmdline.EXPECT().CheckIfConfigurationNeeded().Return(false, nil).AnyTimes()
cmdline.EXPECT().FlagValueIfSet("project").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("app").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("component").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("o").Return("").AnyTimes()
cmdline.EXPECT().GetKubeClient().Return(kclient, nil).AnyTimes()
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "a-project",
},
}
kclient.EXPECT().GetNamespaceNormal("a-project").Return(ns, nil).AnyTimes()
kclient.EXPECT().SetNamespace("a-project").AnyTimes()
tt.populateWorkingDir(filesystem.DefaultFs{})
/* Mocks for Complete */
appClient := application.NewMockClient(ctrl)
appClient.EXPECT().Exists(tt.wantAppName).Return(func() bool {
for _, app := range tt.existingApps {
if tt.wantAppName == app {
return true
}
}
return false
}(), nil).AnyTimes()
appClient.EXPECT().ComponentList(tt.wantAppName).AnyTimes()
opts := NewDeleteOptions(appClient)
// Force to disable interactive confirmation
opts.forceFlag = true
/* COMPLETE */
err := opts.Complete(cmdline, tt.args)
if err != nil {
t.Errorf("Expected nil error, got %s", err)
return
}
if opts.appName != tt.wantAppName {
t.Errorf("Got appName %q, expected %q", opts.appName, tt.wantAppName)
}
/* VALIDATE */
err = opts.Validate()
if err == nil && tt.wantErrValidate != "" {
t.Errorf("Expected %v, got no error", tt.wantErrValidate)
return
}
if err != nil && tt.wantErrValidate == "" {
t.Errorf("Expected no error, got %v", err.Error())
return
}
if err != nil && tt.wantErrValidate != "" && !strings.Contains(err.Error(), tt.wantErrValidate) {
t.Errorf("Expected error %v, got %v", tt.wantErrValidate, err.Error())
return
}
if err != nil {
return
}
/* Mocks for Run */
kclient.EXPECT().GetDeploymentFromSelector(fmt.Sprintf("app=%s,app.kubernetes.io/managed-by=odo,app.kubernetes.io/part-of=%s", tt.wantAppName, tt.wantAppName)).AnyTimes()
appClient.EXPECT().Delete(tt.wantAppName).Times(1)
/* RUN */
err = opts.Run()
if err != nil {
t.Errorf("Expected nil err, got %s", err)
}
})
}
}

View File

@@ -1,124 +0,0 @@
package application
import (
"fmt"
"github.com/spf13/cobra"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const describeRecommendedCommandName = "describe"
var (
describeExample = ktemplates.Examples(` # Describe 'webapp' application
%[1]s webapp`)
)
// DescribeOptions encapsulates the options for the odo command
type DescribeOptions struct {
// Context
*genericclioptions.Context
// Clients
appClient application.Client
// Parameters
appName string
}
// NewDescribeOptions creates a new DescribeOptions instance
func NewDescribeOptions(appClient application.Client) *DescribeOptions {
return &DescribeOptions{
appClient: appClient,
}
}
// Complete completes DescribeOptions after they've been created
func (o *DescribeOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
o.appName = o.GetApplication()
if len(args) == 1 {
o.appName = args[0]
}
return
}
// Validate validates the DescribeOptions based on completed values
func (o *DescribeOptions) Validate() (err error) {
if o.Context.GetProject() == "" || o.appName == "" {
return util.ThrowContextError()
}
exist, err := o.appClient.Exists(o.appName)
if !exist {
return fmt.Errorf("%s app does not exists", o.appName)
}
return err
}
// Run contains the logic for the odo command
func (o *DescribeOptions) Run() (err error) {
if o.IsJSON() {
appDef := o.appClient.GetMachineReadableFormat(o.appName, o.GetProject())
machineoutput.OutputSuccess(appDef)
return nil
}
componentList, err := o.appClient.ComponentList(o.appName)
if err != nil {
return err
}
if len(componentList) == 0 {
fmt.Printf("Application %s has no components or services deployed.", o.appName)
return
}
fmt.Printf("Application Name: %s has %v component(s):\n--------------------------------------\n",
o.appName, len(componentList))
for _, currentComponent := range componentList {
err := util.PrintComponentInfo(o.KClient, currentComponent.Name, currentComponent, o.appName, o.GetProject())
if err != nil {
return err
}
fmt.Println("--------------------------------------")
}
return nil
}
// NewCmdDescribe implements the odo command.
func NewCmdDescribe(name, fullName string) *cobra.Command {
// The error is not handled at this point, it will be handled during Context creation
kubclient, _ := kclient.New()
o := NewDescribeOptions(application.NewClient(kubclient))
command := &cobra.Command{
Use: fmt.Sprintf("%s [application_name]", name),
Short: "Describe the given application",
Long: "Describe the given application",
Example: fmt.Sprintf(describeExample, fullName),
Args: cobra.MaximumNArgs(1),
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
completion.RegisterCommandHandler(command, completion.AppCompletionHandler)
project.AddProjectFlag(command)
return command
}

View File

@@ -1,197 +0,0 @@
package application
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/golang/mock/gomock"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDescribe(t *testing.T) {
prefixDir, err := os.MkdirTemp(os.TempDir(), "unittests-")
if err != nil {
t.Errorf("Error creating temp directory for tests")
return
}
workingDir := filepath.Join(prefixDir, "myapp")
tests := []struct {
name string
populateWorkingDir func(fs filesystem.Filesystem)
args []string
existingApps []string
wantAppName string
wantErrValidate string
}{
{
name: "default app",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "an-app-name",
})
},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "an-app-name",
},
{
name: "app from args",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "an-app-name",
})
},
args: []string{"another-app-name"},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "another-app-name",
},
{
name: "empty app name",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "",
})
},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "",
wantErrValidate: "Please specify the application name and project name",
},
{
name: "non existing app name",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "an-app-name",
})
},
args: []string{"an-unknown-app-name"},
existingApps: []string{"an-app-name", "another-app-name"},
wantAppName: "an-unknown-app-name",
wantErrValidate: " app does not exists",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// the first one is to cleanup the directory before execution (in case there are remaining files from a previous execution)
os.RemoveAll(prefixDir)
// the second one to cleanup after execution
defer os.RemoveAll(prefixDir)
// Fake Cobra
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cmdline := cmdline.NewMockCmdline(ctrl)
// Fake odo Kube client
kclient := kclient.NewMockClientInterface(ctrl)
/* Mocks for Complete */
cmdline.EXPECT().GetWorkingDirectory().Return(workingDir, nil).AnyTimes()
cmdline.EXPECT().CheckIfConfigurationNeeded().Return(false, nil).AnyTimes()
cmdline.EXPECT().FlagValueIfSet("project").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("app").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("component").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("o").Return("").AnyTimes()
cmdline.EXPECT().GetKubeClient().Return(kclient, nil).AnyTimes()
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "a-project",
},
}
kclient.EXPECT().GetNamespaceNormal("a-project").Return(ns, nil).AnyTimes()
kclient.EXPECT().SetNamespace("a-project").AnyTimes()
tt.populateWorkingDir(filesystem.DefaultFs{})
/* Mocks for Complete */
appClient := application.NewMockClient(ctrl)
appClient.EXPECT().Exists(tt.wantAppName).Return(func() bool {
for _, app := range tt.existingApps {
if tt.wantAppName == app {
return true
}
}
return false
}(), nil).AnyTimes()
opts := NewDescribeOptions(appClient)
/* COMPLETE */
err := opts.Complete(cmdline, tt.args)
if err != nil {
return
}
if opts.appName != tt.wantAppName {
t.Errorf("Got appName %q, expected %q", opts.appName, tt.wantAppName)
}
/* VALIDATE */
err = opts.Validate()
if err == nil && tt.wantErrValidate != "" {
t.Errorf("Expected %v, got no error", tt.wantErrValidate)
return
}
if err != nil && tt.wantErrValidate == "" {
t.Errorf("Expected no error, got %v", err.Error())
return
}
if err != nil && tt.wantErrValidate != "" && !strings.Contains(err.Error(), tt.wantErrValidate) {
t.Errorf("Expected error %v, got %v", tt.wantErrValidate, err.Error())
return
}
if err != nil {
return
}
/* Mocks for Run */
appClient.EXPECT().ComponentList(tt.wantAppName)
/* RUN */
err = opts.Run()
if err != nil {
t.Errorf("Expected nil err, got %s", err)
}
})
}
}

View File

@@ -1,128 +0,0 @@
package application
import (
"fmt"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/util"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const listRecommendedCommandName = "list"
var (
listExample = ktemplates.Examples(` # List all applications in the current project
%[1]s
# List all applications in the specified project
%[1]s --project myproject`)
)
// ListOptions encapsulates the options for the odo command
type ListOptions struct {
// Context
*genericclioptions.Context
// Clients
appClient application.Client
}
// NewListOptions creates a new ListOptions instance
func NewListOptions(appClient application.Client) *ListOptions {
return &ListOptions{
appClient: appClient,
}
}
// Complete completes ListOptions after they've been created
func (o *ListOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
return err
}
// Validate validates the ListOptions based on completed values
func (o *ListOptions) Validate() (err error) {
// list doesn't need the app name
if o.Context.GetProject() == "" {
return util.ThrowContextError()
}
return nil
}
// Run contains the logic for the odo command
func (o *ListOptions) Run() (err error) {
apps, err := o.appClient.List()
if err != nil {
return fmt.Errorf("unable to get list of applications: %v", err)
}
if len(apps) == 0 {
if o.IsJSON() {
apps := o.appClient.GetMachineReadableFormatForList([]application.App{})
machineoutput.OutputSuccess(apps)
return nil
}
log.Infof("There are no applications deployed in the project '%v'", o.GetProject())
return nil
}
if o.IsJSON() {
var appList []application.App
for _, app := range apps {
appDef := o.appClient.GetMachineReadableFormat(app, o.GetProject())
appList = append(appList, appDef)
}
appListDef := o.appClient.GetMachineReadableFormatForList(appList)
machineoutput.OutputSuccess(appListDef)
return nil
}
log.Infof("The project '%v' has the following applications:", o.GetProject())
tabWriter := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent)
_, err = fmt.Fprintln(tabWriter, "NAME")
if err != nil {
return err
}
for _, app := range apps {
_, err := fmt.Fprintln(tabWriter, app)
if err != nil {
return err
}
}
return tabWriter.Flush()
}
// NewCmdList implements the odo command.
func NewCmdList(name, fullName string) *cobra.Command {
// The error is not handled at this point, it will be handled during Context creation
kubclient, _ := kclient.New()
o := NewListOptions(application.NewClient(kubclient))
command := &cobra.Command{
Use: name,
Short: "List all applications in the current project",
Long: "List all applications in the current project",
Example: fmt.Sprintf(listExample, fullName),
Args: cobra.NoArgs,
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
project.AddProjectFlag(command)
return command
}

View File

@@ -1,149 +0,0 @@
package application
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/golang/mock/gomock"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestList(t *testing.T) {
prefixDir, err := os.MkdirTemp(os.TempDir(), "unittests-")
if err != nil {
t.Errorf("Error creating temp directory for tests")
return
}
workingDir := filepath.Join(prefixDir, "myapp")
tests := []struct {
name string
populateWorkingDir func(fs filesystem.Filesystem)
currentNamespace string
wantErrValidate string
}{
{
name: "default app",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "a-project",
AppName: "an-app-name",
})
},
},
{
name: "empty project name",
populateWorkingDir: func(fs filesystem.Filesystem) {
_ = fs.MkdirAll(filepath.Join(prefixDir, "myapp", ".odo", "env"), 0755)
env, err := envinfo.NewEnvSpecificInfo(filepath.Join(prefixDir, "myapp"))
if err != nil {
return
}
_ = env.SetComponentSettings(envinfo.ComponentSettings{
Name: "a-name",
Project: "",
AppName: "an-app-name",
})
},
currentNamespace: "",
wantErrValidate: "Please specify the application name and project name",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// the first one is to cleanup the directory before execution (in case there are remaining files from a previous execution)
os.RemoveAll(prefixDir)
// the second one to cleanup after execution
defer os.RemoveAll(prefixDir)
// Fake Cobra
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cmdline := cmdline.NewMockCmdline(ctrl)
// Fake odo Kube client
kclient := kclient.NewMockClientInterface(ctrl)
/* Mocks for Complete */
cmdline.EXPECT().GetWorkingDirectory().Return(workingDir, nil).AnyTimes()
cmdline.EXPECT().CheckIfConfigurationNeeded().Return(false, nil).AnyTimes()
cmdline.EXPECT().FlagValueIfSet("project").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("app").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("component").Return("").AnyTimes()
cmdline.EXPECT().FlagValueIfSet("o").Return("").AnyTimes()
cmdline.EXPECT().GetName().Return("list").AnyTimes()
cmdline.EXPECT().GetParentName().Return("application").AnyTimes()
cmdline.EXPECT().GetKubeClient().Return(kclient, nil).AnyTimes()
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "a-project",
},
}
kclient.EXPECT().GetNamespaceNormal("a-project").Return(ns, nil).AnyTimes()
kclient.EXPECT().GetNamespaceNormal("").Return(nil, nil).AnyTimes()
kclient.EXPECT().SetNamespace("a-project").AnyTimes()
kclient.EXPECT().SetNamespace("").AnyTimes()
kclient.EXPECT().GetCurrentNamespace().Return(tt.currentNamespace).AnyTimes()
tt.populateWorkingDir(filesystem.DefaultFs{})
/* Mocks for Complete */
appClient := application.NewMockClient(ctrl)
opts := NewListOptions(appClient)
/* COMPLETE */
err := opts.Complete(cmdline, []string{})
if err != nil {
return
}
/* VALIDATE */
err = opts.Validate()
if err == nil && tt.wantErrValidate != "" {
t.Errorf("Expected %v, got no error", tt.wantErrValidate)
return
}
if err != nil && tt.wantErrValidate == "" {
t.Errorf("Expected no error, got %v", err.Error())
return
}
if err != nil && tt.wantErrValidate != "" && !strings.Contains(err.Error(), tt.wantErrValidate) {
t.Errorf("Expected error %v, got %v", tt.wantErrValidate, err.Error())
return
}
if err != nil {
return
}
/* Mocks for Run */
appClient.EXPECT().List().Times(1)
/* RUN */
err = opts.Run()
if err != nil {
t.Errorf("Expected nil err, got %s", err)
}
})
}
}

View File

@@ -13,17 +13,15 @@ const RecommendedCommandName = "describe"
// NewCmdCatalogDescribe implements the odo catalog describe command // NewCmdCatalogDescribe implements the odo catalog describe command
func NewCmdCatalogDescribe(name, fullName string) *cobra.Command { func NewCmdCatalogDescribe(name, fullName string) *cobra.Command {
component := NewCmdCatalogDescribeComponent(componentRecommendedCommandName, util.GetFullName(fullName, componentRecommendedCommandName)) component := NewCmdCatalogDescribeComponent(componentRecommendedCommandName, util.GetFullName(fullName, componentRecommendedCommandName))
service := NewCmdCatalogDescribeService(serviceRecommendedCommandName, util.GetFullName(fullName, serviceRecommendedCommandName))
catalogDescribeCmd := &cobra.Command{ catalogDescribeCmd := &cobra.Command{
Use: name, Use: name,
Short: "Describe catalog item", Short: "Describe catalog item",
Long: "Describe the given catalog item from OpenShift", Long: "Describe the given catalog item from OpenShift",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Example: fmt.Sprintf("%s\n\n%s\n", component.Example, service.Example), Example: fmt.Sprintf("%s\n", component.Example),
} }
catalogDescribeCmd.AddCommand( catalogDescribeCmd.AddCommand(
component, component,
service,
) )
return catalogDescribeCmd return catalogDescribeCmd

View File

@@ -1,11 +0,0 @@
package describe
// CatalogProviderBackend is implemented by the catalog backends supported by odo
// It is used in "odo catalog describe service".
type CatalogProviderBackend interface {
// the second argument can be a list of anything that needs to be sent to populate internal
// structs
CompleteDescribeService(*DescribeServiceOptions, []string) error
ValidateDescribeService(*DescribeServiceOptions) error
RunDescribeService(*DescribeServiceOptions) error
}

View File

@@ -1,345 +0,0 @@
package describe
import (
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"sort"
"strings"
"text/tabwriter"
"github.com/go-openapi/spec"
olm "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/service"
"gopkg.in/yaml.v2"
"k8s.io/klog"
)
type operatorBackend struct {
Name string
OperatorType string
CustomResource string
CSV olm.ClusterServiceVersion
CR *olm.CRDDescription
CRDSpec *spec.Schema
CRDList *service.OperatorBackedServiceCRList
}
func NewOperatorBackend() *operatorBackend {
return &operatorBackend{}
}
func (ohb *operatorBackend) CompleteDescribeService(dso *DescribeServiceOptions, args []string) error {
ohb.Name = args[0]
oprType, CR, err := service.SplitServiceKindName(ohb.Name)
if err != nil {
klog.V(2).Infof("could not determine csv, falling back to describing all of them")
oprType = args[0]
CR = ""
}
// we check if the cluster supports ClusterServiceVersion or not.
isCSVSupported, err := dso.KClient.IsCSVSupported()
if err != nil {
// if there is an error checking it, we return the error.
return err
}
// if its not supported then we return an error
if !isCSVSupported {
return errors.New("it seems the cluster doesn't support Operators. Please install OLM and try again")
}
ohb.OperatorType = oprType
ohb.CustomResource = CR
return nil
}
func (ohb *operatorBackend) ValidateDescribeService(dso *DescribeServiceOptions) error {
var err error
if ohb.OperatorType == "" {
return errors.New("invalid service name provided. should either be <operator-type> or <operator-type>/<crd-name>")
}
// make sure that CSV of the specified OperatorType exists
ohb.CSV, err = dso.KClient.GetClusterServiceVersion(ohb.OperatorType)
if err != nil {
// error only occurs when OperatorHub is not installed.
// k8s does't have it installed by default but OCP does
return err
}
//if both operator type and cr are known, validate that it exists
if ohb.OperatorType != "" && ohb.CustomResource != "" {
var hasCR bool
hasCR, ohb.CR = dso.KClient.CheckCustomResourceInCSV(ohb.CustomResource, &ohb.CSV)
if !hasCR {
return fmt.Errorf("the %q resource doesn't exist in specified %q operator", ohb.CustomResource, ohb.OperatorType)
}
ohb.CRDSpec, err = dso.KClient.GetCRDSpec(ohb.CR, ohb.OperatorType, ohb.CustomResource)
if err != nil {
return err
}
}
return nil
}
func (ohb *operatorBackend) RunDescribeService(dso *DescribeServiceOptions) error {
if ohb.OperatorType != "" && ohb.CustomResource == "" {
//we don't have cr so list all possible crds
ohb.CRDList = service.NewOperatorBackedCRList(ohb.OperatorType, ohb.CSV.Spec.DisplayName, ohb.CSV.Spec.Description)
crds := *dso.KClient.GetCustomResourcesFromCSV(&ohb.CSV)
for _, custRes := range crds {
ohb.CRDList.Spec.CRDS = append(ohb.CRDList.Spec.CRDS, service.OperatorServiceCRItem{
Kind: custRes.Kind,
Description: custRes.Description,
})
}
if log.IsJSON() {
machineoutput.OutputSuccess(ohb.CRDList)
} else {
HumanReadableCRListOutput(os.Stdout, ohb.CRDList)
}
return nil
}
if dso.exampleFlag {
almExample, err := service.GetAlmExample(ohb.CSV, ohb.CustomResource, ohb.OperatorType)
if err != nil {
return err
}
if log.IsJSON() {
jsonExample := service.NewOperatorExample(almExample)
jsonCR, err := json.MarshalIndent(jsonExample, "", " ")
if err != nil {
return err
}
fmt.Println(string(jsonCR))
} else {
yamlCR, err := yaml.Marshal(almExample)
if err != nil {
return err
}
log.Info(string(yamlCR))
}
}
svc := service.NewOperatorBackedService(ohb.Name, ohb.CR.Kind, ohb.CR.Version, ohb.CR.Description, ohb.CR.DisplayName, ohb.CRDSpec)
if log.IsJSON() {
machineoutput.OutputSuccess(svc)
} else {
HumanReadableOutput(os.Stdout, svc)
}
return nil
}
func HumanReadableOutput(w io.Writer, service service.OperatorBackedService) {
fmt.Fprintf(w, "KIND: %s\n", service.Spec.Kind)
fmt.Fprintf(w, "VERSION: %s\n", service.Spec.Version)
fmt.Fprintf(w, "\nDESCRIPTION:\n%s", indentText(service.Spec.Description, 5))
if service.Spec.Schema == nil {
log.Warningf("Unable to get parameters from CRD or CSV; Operator %q doesn't have the required information", service.Name)
return
}
fmt.Fprintln(w, "\nFIELDS:")
displayProperties(w, service.Spec.Schema, "")
}
func HumanReadableCRListOutput(w io.Writer, crsList *service.OperatorBackedServiceCRList) {
fmt.Fprintf(w, "NAME:\t%s\n", crsList.Name)
descriptionLines := strings.ReplaceAll(crsList.Spec.Description, "\n", "\n\t")
fmt.Fprintf(w, "DESCRIPTION:\n\n\t%s\n\n", descriptionLines)
fmt.Fprintf(w, "CRDs:\n")
tw := tabwriter.NewWriter(w, 4, 4, 3, ' ', tabwriter.TabIndent)
defer tw.Flush()
fmt.Fprintf(tw, "\tNAME\tDESCRIPTION\n")
for _, it := range crsList.Spec.CRDS {
fmt.Fprintf(tw, "\t%s\t%s\n", it.Kind, it.Description)
}
}
// displayProperties displays the properties of an OpenAPI schema in a human readable form
// required fields are displayed first
func displayProperties(w io.Writer, schema *spec.Schema, prefix string) {
required := schema.Required
requiredMap := map[string]bool{}
for _, req := range required {
requiredMap[req] = true
}
reqKeys := []string{}
for key := range schema.Properties {
if requiredMap[key] {
reqKeys = append(reqKeys, key)
}
}
sort.Strings(reqKeys)
nonReqKeys := []string{}
for key := range schema.Properties {
if !requiredMap[key] {
nonReqKeys = append(nonReqKeys, key)
}
}
sort.Strings(nonReqKeys)
keys := append(reqKeys, nonReqKeys...)
for _, key := range keys {
property := schema.Properties[key]
requiredInfo := ""
if requiredMap[key] {
requiredInfo = "-required-"
}
fmt.Fprintf(w, "%s%s (%s) %s\n", strings.Repeat(" ", 3+2*strings.Count(prefix, ".")), prefix+key, getTypeString(property), requiredInfo)
nl := false
if len(property.Title) > 0 {
fmt.Fprintf(w, "%s\n", indentText(property.Title, 5+2*strings.Count(prefix, ".")))
nl = true
}
if len(property.Description) > 0 {
fmt.Fprintf(w, "%s\n", indentText(property.Description, 5+2*strings.Count(prefix, ".")))
nl = true
}
if !nl {
fmt.Fprintln(w)
}
if property.Type.Contains("object") {
displayProperties(w, &property, prefix+key+".")
} else if property.Type.Contains("array") && property.Items.Schema.Type.Contains("object") {
displayProperties(w, property.Items.Schema, prefix+key+".*.")
}
}
}
func getTypeString(property spec.Schema) string {
if len(property.Type) != 1 {
// should not happen
return strings.Join(property.Type, ", ")
}
tpe := property.Type[0]
if tpe == "array" {
tpe = "[]" + getTypeString(*property.Items.Schema)
}
return tpe
}
func indentText(t string, indent int) string {
lines := wrapString(t, 80-indent)
res := ""
for _, line := range lines {
res += strings.Repeat(" ", indent) + line + "\n"
}
return res
}
// Following code from https://github.com/kubernetes/kubectl/blob/159a770147fb28337c6807abb1b2b9db843d0aff/pkg/explain/formatter.go
type line struct {
wrap int
words []string
}
func (l *line) String() string {
return strings.Join(l.words, " ")
}
func (l *line) Empty() bool {
return len(l.words) == 0
}
func (l *line) Len() int {
return len(l.String())
}
// Add adds the word to the line, returns true if we could, false if we
// didn't have enough room. It's always possible to add to an empty line.
func (l *line) Add(word string) bool {
newLine := line{
wrap: l.wrap,
words: append(l.words, word),
}
if newLine.Len() <= l.wrap || len(l.words) == 0 {
l.words = newLine.words
return true
}
return false
}
func wrapString(str string, wrap int) []string {
wrapped := []string{}
l := line{wrap: wrap}
// track the last word added to the current line
lastWord := ""
flush := func() {
if !l.Empty() {
lastWord = ""
wrapped = append(wrapped, l.String())
l = line{wrap: wrap}
}
}
// iterate over the lines in the original description
for _, str := range strings.Split(str, "\n") {
// preserve code blocks and blockquotes as-is
if strings.HasPrefix(str, " ") {
flush()
wrapped = append(wrapped, str)
continue
}
// preserve empty lines after the first line, since they can separate logical sections
if len(wrapped) > 0 && len(strings.TrimSpace(str)) == 0 {
flush()
wrapped = append(wrapped, "")
continue
}
// flush if we should start a new line
if shouldStartNewLine(lastWord, str) {
flush()
}
words := strings.Fields(str)
for _, word := range words {
lastWord = word
if !l.Add(word) {
flush()
if !l.Add(word) {
panic("Couldn't add to empty line.")
}
}
}
}
flush()
return wrapped
}
var bullet = regexp.MustCompile(`^(\d+\.?|-|\*)\s`)
func shouldStartNewLine(lastWord, str string) bool {
// preserve line breaks ending in :
if strings.HasSuffix(lastWord, ":") {
return true
}
// preserve code blocks
if strings.HasPrefix(str, " ") {
return true
}
str = strings.TrimSpace(str)
// preserve empty lines
if len(str) == 0 {
return true
}
// preserve lines that look like they're starting lists
if bullet.MatchString(str) {
return true
}
// otherwise combine
return false
}

View File

@@ -1,50 +0,0 @@
package describe
import (
"testing"
"github.com/go-openapi/spec"
)
func TestGetTypeString(t *testing.T) {
tests := []struct {
name string
property spec.Schema
want string
}{
{
name: "string type",
property: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
want: "string",
},
{
name: "array of strings type",
property: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
want: "[]string",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getTypeString(tt.property)
if result != tt.want {
t.Errorf("Failed %s: got: %q, want: %q", t.Name(), result, tt.want)
}
})
}
}

View File

@@ -1,84 +0,0 @@
package describe
import (
"fmt"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const serviceRecommendedCommandName = "service"
var (
serviceExample = ktemplates.Examples(`# Describe a Operator backed service
%[1]s
`)
serviceLongDesc = ktemplates.LongDesc(`Describes a service type.
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".
`)
)
// DescribeServiceOptions encapsulates the options for the odo catalog describe service command
type DescribeServiceOptions struct {
// Context
*genericclioptions.Context
// Flags
exampleFlag bool
// Service backend
backend CatalogProviderBackend
}
// NewDescribeServiceOptions creates a new DescribeServiceOptions instance
func NewDescribeServiceOptions() *DescribeServiceOptions {
return &DescribeServiceOptions{}
}
// Complete completes DescribeServiceOptions after they've been created
func (o *DescribeServiceOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
//we initialize operator backend regardless of if we can split name or not. Decision
//to describe crs or not will be taken later
o.backend = NewOperatorBackend()
return o.backend.CompleteDescribeService(o, args)
}
// Validate validates the DescribeServiceOptions based on completed values
func (o *DescribeServiceOptions) Validate() (err error) {
return o.backend.ValidateDescribeService(o)
}
// Run contains the logic for the command associated with DescribeServiceOptions
func (o *DescribeServiceOptions) Run() (err error) {
return o.backend.RunDescribeService(o)
}
// NewCmdCatalogDescribeService implements the odo catalog describe service command
func NewCmdCatalogDescribeService(name, fullName string) *cobra.Command {
o := NewDescribeServiceOptions()
command := &cobra.Command{
Use: name,
Short: "Describe a service",
Long: serviceLongDesc,
Example: fmt.Sprintf(serviceExample, fullName),
Annotations: map[string]string{"machineoutput": "json"},
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
command.Flags().BoolVarP(&o.exampleFlag, "example", "e", false, "Show an example of the service")
return command
}

View File

@@ -13,18 +13,16 @@ const RecommendedCommandName = "list"
// NewCmdCatalogList implements the odo catalog list command // NewCmdCatalogList implements the odo catalog list command
func NewCmdCatalogList(name, fullName string) *cobra.Command { func NewCmdCatalogList(name, fullName string) *cobra.Command {
components := NewCmdCatalogListComponents(componentsRecommendedCommandName, util.GetFullName(fullName, componentsRecommendedCommandName)) components := NewCmdCatalogListComponents(componentsRecommendedCommandName, util.GetFullName(fullName, componentsRecommendedCommandName))
services := NewCmdCatalogListServices(servicesRecommendedCommandName, util.GetFullName(fullName, servicesRecommendedCommandName))
catalogListCmd := &cobra.Command{ catalogListCmd := &cobra.Command{
Use: name, Use: name,
Short: "List all available component & service types.", Short: "List all available component types.",
Long: "List all available component and service types from OpenShift", Long: "List all available component types from OpenShift",
Example: fmt.Sprintf("%s\n\n%s\n", components.Example, services.Example), Example: fmt.Sprintf("%s\n", components.Example),
} }
catalogListCmd.AddCommand( catalogListCmd.AddCommand(
components, components,
services,
) )
return catalogListCmd return catalogListCmd

View File

@@ -1,105 +0,0 @@
package list
import (
"fmt"
"strings"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
olm "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cli/catalog/util"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/service"
"github.com/spf13/cobra"
)
const servicesRecommendedCommandName = "services"
var servicesExample = ` # Get the supported services
%[1]s`
// ServiceOptions encapsulates the options for the odo catalog list services command
type ServiceOptions struct {
// Context
*genericclioptions.Context
// list of clusterserviceversions (installed by Operators)
csvs *olm.ClusterServiceVersionList
}
// NewServiceOptions creates a new ListServicesOptions instance
func NewServiceOptions() *ServiceOptions {
return &ServiceOptions{}
}
// Complete completes ListServicesOptions after they've been created
func (o *ServiceOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
o.csvs, err = service.ListSucceededClusterServiceVersions(o.KClient)
if err != nil && !strings.Contains(err.Error(), "could not find specified operator") {
return err
}
return nil
}
// Validate validates the ListServicesOptions based on completed values
func (o *ServiceOptions) Validate() error {
return nil
}
// Run contains the logic for the command associated with ListServicesOptions
func (o *ServiceOptions) Run() error {
if log.IsJSON() {
machineoutput.OutputSuccess(newCatalogListOutput(o.csvs))
} else {
if len(o.csvs.Items) == 0 {
log.Info("no deployable operators found")
return nil
}
if len(o.csvs.Items) > 0 {
util.DisplayClusterServiceVersions(o.csvs)
}
}
return nil
}
// NewCmdCatalogListServices implements the odo catalog list services command
func NewCmdCatalogListServices(name, fullName string) *cobra.Command {
o := NewServiceOptions()
return &cobra.Command{
Use: name,
Short: "Lists all available services",
Long: "Lists all available services",
Example: fmt.Sprintf(servicesExample, fullName),
Args: cobra.ExactArgs(0),
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
}
type catalogListOutput struct {
v1.TypeMeta `json:",inline"`
v1.ObjectMeta `json:"metadata,omitempty"`
// list of clusterserviceversions (installed by Operators)
Operators *olm.ClusterServiceVersionList `json:"operators,omitempty"`
}
func newCatalogListOutput(operators *olm.ClusterServiceVersionList) catalogListOutput {
return catalogListOutput{
TypeMeta: v1.TypeMeta{
Kind: "List",
APIVersion: machineoutput.APIVersion,
},
Operators: operators,
}
}

View File

@@ -13,7 +13,6 @@ const RecommendedCommandName = "search"
// NewCmdCatalogSearch implements the odo catalog search command // NewCmdCatalogSearch implements the odo catalog search command
func NewCmdCatalogSearch(name, fullName string) *cobra.Command { func NewCmdCatalogSearch(name, fullName string) *cobra.Command {
component := NewCmdCatalogSearchComponent(componentRecommendedCommandName, util.GetFullName(fullName, componentRecommendedCommandName)) component := NewCmdCatalogSearchComponent(componentRecommendedCommandName, util.GetFullName(fullName, componentRecommendedCommandName))
service := NewCmdCatalogSearchService(serviceRecommendedCommandName, util.GetFullName(fullName, serviceRecommendedCommandName))
catalogSearchCmd := &cobra.Command{ catalogSearchCmd := &cobra.Command{
Use: name, Use: name,
Short: "Search available component & service types.", Short: "Search available component & service types.",
@@ -22,9 +21,9 @@ func NewCmdCatalogSearch(name, fullName string) *cobra.Command {
This searches for a partial match for the given search term in all the available This searches for a partial match for the given search term in all the available
components & services. components & services.
`, `,
Example: fmt.Sprintf("%s\n\n%s\n", component.Example, service.Example), Example: fmt.Sprintf("%s\n", component.Example),
} }
catalogSearchCmd.AddCommand(component, service) catalogSearchCmd.AddCommand(component)
return catalogSearchCmd return catalogSearchCmd
} }

View File

@@ -1,86 +0,0 @@
package search
import (
"fmt"
olm "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/redhat-developer/odo/pkg/odo/cli/catalog/util"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/spf13/cobra"
)
const serviceRecommendedCommandName = "service"
var serviceExample = ` # Search for a service
%[1]s mysql`
// SearchServiceOptions encapsulates the options for the odo catalog describe service command
type SearchServiceOptions struct {
// Context
*genericclioptions.Context
// Parameters
searchTerm string
// list of clusterserviceversions (installed by Operators)
csvs *olm.ClusterServiceVersionList
}
// NewSearchServiceOptions creates a new SearchServiceOptions instance
func NewSearchServiceOptions() *SearchServiceOptions {
return &SearchServiceOptions{}
}
// Complete completes SearchServiceOptions after they've been created
func (o *SearchServiceOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
o.searchTerm = args[0]
o.csvs, err = o.KClient.SearchClusterServiceVersionList(o.searchTerm)
if err != nil {
return fmt.Errorf("unable to list services because Operator Hub is not enabled in your cluster: %v", err)
}
return nil
}
// Validate validates the SearchServiceOptions based on completed values
func (o *SearchServiceOptions) Validate() error {
if len(o.csvs.Items) == 0 {
return fmt.Errorf("no service matched the query: %s", o.searchTerm)
}
return nil
}
// Run contains the logic for the command associated with SearchServiceOptions
func (o *SearchServiceOptions) Run() error {
if len(o.csvs.Items) > 0 {
util.DisplayClusterServiceVersions(o.csvs)
}
return nil
}
// NewCmdCatalogSearchService implements the odo catalog search service command
func NewCmdCatalogSearchService(name, fullName string) *cobra.Command {
o := NewSearchServiceOptions()
return &cobra.Command{
Use: name,
Short: "Search service type in catalog",
Long: `Search service type in catalog.
This searches for a partial match for the given search term in all the available
services from operator hub services.
`,
Example: fmt.Sprintf(serviceExample, fullName),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
}

View File

@@ -6,14 +6,10 @@ import (
"os" "os"
"strings" "strings"
"github.com/redhat-developer/odo/pkg/odo/cli/application"
"github.com/redhat-developer/odo/pkg/odo/cli/build_images" "github.com/redhat-developer/odo/pkg/odo/cli/build_images"
"github.com/redhat-developer/odo/pkg/odo/cli/catalog" "github.com/redhat-developer/odo/pkg/odo/cli/catalog"
"github.com/redhat-developer/odo/pkg/odo/cli/component" "github.com/redhat-developer/odo/pkg/odo/cli/component"
"github.com/redhat-developer/odo/pkg/odo/cli/config"
"github.com/redhat-developer/odo/pkg/odo/cli/debug"
"github.com/redhat-developer/odo/pkg/odo/cli/deploy" "github.com/redhat-developer/odo/pkg/odo/cli/deploy"
"github.com/redhat-developer/odo/pkg/odo/cli/env"
_init "github.com/redhat-developer/odo/pkg/odo/cli/init" _init "github.com/redhat-developer/odo/pkg/odo/cli/init"
"github.com/redhat-developer/odo/pkg/odo/cli/login" "github.com/redhat-developer/odo/pkg/odo/cli/login"
"github.com/redhat-developer/odo/pkg/odo/cli/logout" "github.com/redhat-developer/odo/pkg/odo/cli/logout"
@@ -21,8 +17,6 @@ import (
"github.com/redhat-developer/odo/pkg/odo/cli/preference" "github.com/redhat-developer/odo/pkg/odo/cli/preference"
"github.com/redhat-developer/odo/pkg/odo/cli/project" "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cli/registry" "github.com/redhat-developer/odo/pkg/odo/cli/registry"
"github.com/redhat-developer/odo/pkg/odo/cli/service"
"github.com/redhat-developer/odo/pkg/odo/cli/storage"
"github.com/redhat-developer/odo/pkg/odo/cli/telemetry" "github.com/redhat-developer/odo/pkg/odo/cli/telemetry"
"github.com/redhat-developer/odo/pkg/odo/cli/url" "github.com/redhat-developer/odo/pkg/odo/cli/url"
"github.com/redhat-developer/odo/pkg/odo/cli/utils" "github.com/redhat-developer/odo/pkg/odo/cli/utils"
@@ -181,34 +175,22 @@ func odoRootCmd(name, fullName string) *cobra.Command {
cobra.AddTemplateFunc("CapitalizeFlagDescriptions", util.CapitalizeFlagDescriptions) cobra.AddTemplateFunc("CapitalizeFlagDescriptions", util.CapitalizeFlagDescriptions)
cobra.AddTemplateFunc("ModifyAdditionalFlags", util.ModifyAdditionalFlags) cobra.AddTemplateFunc("ModifyAdditionalFlags", util.ModifyAdditionalFlags)
rootCmdList := append([]*cobra.Command{}, application.NewCmdApplication(application.RecommendedCommandName, util.GetFullName(fullName, application.RecommendedCommandName)), rootCmdList := append([]*cobra.Command{},
catalog.NewCmdCatalog(catalog.RecommendedCommandName, util.GetFullName(fullName, catalog.RecommendedCommandName)), catalog.NewCmdCatalog(catalog.RecommendedCommandName, util.GetFullName(fullName, catalog.RecommendedCommandName)),
component.NewCmdComponent(component.RecommendedCommandName, util.GetFullName(fullName, component.RecommendedCommandName)), component.NewCmdComponent(component.RecommendedCommandName, util.GetFullName(fullName, component.RecommendedCommandName)),
component.NewCmdCreate(component.CreateRecommendedCommandName, util.GetFullName(fullName, component.CreateRecommendedCommandName)), component.NewCmdCreate(component.CreateRecommendedCommandName, util.GetFullName(fullName, component.CreateRecommendedCommandName)),
component.NewCmdDelete(component.DeleteRecommendedCommandName, util.GetFullName(fullName, component.DeleteRecommendedCommandName)), component.NewCmdDelete(component.DeleteRecommendedCommandName, util.GetFullName(fullName, component.DeleteRecommendedCommandName)),
component.NewCmdDescribe(component.DescribeRecommendedCommandName, util.GetFullName(fullName, component.DescribeRecommendedCommandName)),
component.NewCmdLink(component.LinkRecommendedCommandName, util.GetFullName(fullName, component.LinkRecommendedCommandName)),
component.NewCmdUnlink(component.UnlinkRecommendedCommandName, util.GetFullName(fullName, component.UnlinkRecommendedCommandName)),
component.NewCmdList(component.ListRecommendedCommandName, util.GetFullName(fullName, component.ListRecommendedCommandName)), component.NewCmdList(component.ListRecommendedCommandName, util.GetFullName(fullName, component.ListRecommendedCommandName)),
component.NewCmdLog(component.LogRecommendedCommandName, util.GetFullName(fullName, component.LogRecommendedCommandName)),
component.NewCmdPush(component.PushRecommendedCommandName, util.GetFullName(fullName, component.PushRecommendedCommandName)), component.NewCmdPush(component.PushRecommendedCommandName, util.GetFullName(fullName, component.PushRecommendedCommandName)),
component.NewCmdWatch(component.WatchRecommendedCommandName, util.GetFullName(fullName, component.WatchRecommendedCommandName)), component.NewCmdWatch(component.WatchRecommendedCommandName, util.GetFullName(fullName, component.WatchRecommendedCommandName)),
component.NewCmdStatus(component.StatusRecommendedCommandName, util.GetFullName(fullName, component.StatusRecommendedCommandName)),
component.NewCmdExec(component.ExecRecommendedCommandName, util.GetFullName(fullName, component.ExecRecommendedCommandName)),
login.NewCmdLogin(login.RecommendedCommandName, util.GetFullName(fullName, login.RecommendedCommandName)), login.NewCmdLogin(login.RecommendedCommandName, util.GetFullName(fullName, login.RecommendedCommandName)),
logout.NewCmdLogout(logout.RecommendedCommandName, util.GetFullName(fullName, logout.RecommendedCommandName)), logout.NewCmdLogout(logout.RecommendedCommandName, util.GetFullName(fullName, logout.RecommendedCommandName)),
project.NewCmdProject(project.RecommendedCommandName, util.GetFullName(fullName, project.RecommendedCommandName)), project.NewCmdProject(project.RecommendedCommandName, util.GetFullName(fullName, project.RecommendedCommandName)),
service.NewCmdService(service.RecommendedCommandName, util.GetFullName(fullName, service.RecommendedCommandName)),
storage.NewCmdStorage(storage.RecommendedCommandName, util.GetFullName(fullName, storage.RecommendedCommandName)),
url.NewCmdURL(url.RecommendedCommandName, util.GetFullName(fullName, url.RecommendedCommandName)), url.NewCmdURL(url.RecommendedCommandName, util.GetFullName(fullName, url.RecommendedCommandName)),
utils.NewCmdUtils(utils.RecommendedCommandName, util.GetFullName(fullName, utils.RecommendedCommandName)), utils.NewCmdUtils(utils.RecommendedCommandName, util.GetFullName(fullName, utils.RecommendedCommandName)),
version.NewCmdVersion(version.RecommendedCommandName, util.GetFullName(fullName, version.RecommendedCommandName)), version.NewCmdVersion(version.RecommendedCommandName, util.GetFullName(fullName, version.RecommendedCommandName)),
config.NewCmdConfiguration(config.RecommendedCommandName, util.GetFullName(fullName, config.RecommendedCommandName)),
preference.NewCmdPreference(preference.RecommendedCommandName, util.GetFullName(fullName, preference.RecommendedCommandName)), preference.NewCmdPreference(preference.RecommendedCommandName, util.GetFullName(fullName, preference.RecommendedCommandName)),
debug.NewCmdDebug(debug.RecommendedCommandName, util.GetFullName(fullName, debug.RecommendedCommandName)),
registry.NewCmdRegistry(registry.RecommendedCommandName, util.GetFullName(fullName, registry.RecommendedCommandName)), registry.NewCmdRegistry(registry.RecommendedCommandName, util.GetFullName(fullName, registry.RecommendedCommandName)),
component.NewCmdTest(component.TestRecommendedCommandName, util.GetFullName(fullName, component.TestRecommendedCommandName)),
env.NewCmdEnv(env.RecommendedCommandName, util.GetFullName(fullName, env.RecommendedCommandName)),
telemetry.NewCmdTelemetry(telemetry.RecommendedCommandName), telemetry.NewCmdTelemetry(telemetry.RecommendedCommandName),
build_images.NewCmdBuildImages(build_images.RecommendedCommandName, util.GetFullName(fullName, build_images.RecommendedCommandName)), build_images.NewCmdBuildImages(build_images.RecommendedCommandName, util.GetFullName(fullName, build_images.RecommendedCommandName)),
deploy.NewCmdDeploy(deploy.RecommendedCommandName, util.GetFullName(fullName, deploy.RecommendedCommandName)), deploy.NewCmdDeploy(deploy.RecommendedCommandName, util.GetFullName(fullName, deploy.RecommendedCommandName)),

View File

@@ -1,390 +0,0 @@
package component
import (
"encoding/json"
"fmt"
"strings"
"github.com/redhat-developer/odo/pkg/devfile"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/util"
svc "github.com/redhat-developer/odo/pkg/service"
v1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
"gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const unlink = "unlink"
type commonLinkOptions struct {
secretName string
isTargetAService bool
name string
bindAsFiles bool
devfilePath string
suppliedName string
operation func(secretName, componentName, applicationName string) error
operationName string
// Service Binding Operator options
serviceBinding *servicebinding.ServiceBinding
serviceType string
serviceName string
*genericclioptions.Context
// choose between Operator Hub and Service Catalog. If true, Operator Hub
csvSupport bool
inlined bool
// mappings is an array of strings representing the custom binding data that user wants to inject into the component
mappings []string
}
func newCommonLinkOptions() *commonLinkOptions {
return &commonLinkOptions{}
}
func (o *commonLinkOptions) getLinkType() string {
linkType := "component"
if o.isTargetAService {
linkType = "service"
}
return linkType
}
// Complete completes LinkOptions after they've been created
func (o *commonLinkOptions) complete(cmdline cmdline.Cmdline, args []string, context string) (err error) {
o.operationName = cmdline.GetName()
o.suppliedName = args[0]
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(context))
if err != nil {
return err
}
o.csvSupport, _ = o.KClient.IsCSVSupported()
o.serviceType, o.serviceName, err = svc.IsOperatorServiceNameValid(o.suppliedName)
if err != nil {
// error indicates the service name provided by user doesn't adhere to <crd-name>/<instance-name>
// so it's another odo component that they want to link/unlink to/from
o.serviceName = o.suppliedName
o.isTargetAService = false
o.serviceType = "Service" // Kubernetes Service
// TODO find the service using an app name to link components in other apps
// requires modification of the app flag or finding some other way
var s *v1.Service
s, err = o.Context.KClient.GetOneService(o.suppliedName, o.EnvSpecificInfo.GetApplication())
if kerrors.IsNotFound(err) {
return fmt.Errorf("couldn't find component named %q. Refer %q to see list of running components", o.suppliedName, "odo list")
}
if err != nil {
return err
}
o.serviceName = s.Name
} else {
o.isTargetAService = true
}
if o.operationName == unlink {
// rest of the code is specific to link operation
return nil
}
componentName := o.EnvSpecificInfo.GetName()
deployment, err := o.KClient.GetOneDeployment(componentName, o.EnvSpecificInfo.GetApplication())
if err != nil {
return err
}
deploymentGVR, err := o.KClient.GetDeploymentAPIVersion()
if err != nil {
return err
}
paramsMap, err := util.MapFromParameters(o.mappings)
if err != nil {
return err
}
// MappingsMap is a map of mappings to be used in the ServiceBinding we create for an "odo link"
var mappingsMap []servicebinding.Mapping
for kv := range paramsMap {
mapping := servicebinding.Mapping{
Name: kv,
Value: paramsMap[kv],
}
mappingsMap = append(mappingsMap, mapping)
}
var service servicebinding.Service
if o.isTargetAService {
// since the service exists, let's get more info to populate service binding request
// first get the CR itself
cr, err := o.KClient.GetCustomResource(o.serviceType)
if err != nil {
return err
}
// now get the group, version, kind information from CR
group, version, kind, err := svc.GetGVKFromCR(cr)
if err != nil {
return err
}
service = servicebinding.Service{
Id: &o.serviceName, // Id field is helpful if user wants to inject mappings (custom binding data)
NamespacedRef: servicebinding.NamespacedRef{
Ref: servicebinding.Ref{
Group: group,
Version: version,
Kind: kind,
Name: o.serviceName,
},
},
}
} else {
service = servicebinding.Service{
Id: &o.serviceName, // Id field is helpful if user wants to inject mappings (custom binding data)
NamespacedRef: servicebinding.NamespacedRef{
Ref: servicebinding.Ref{
Version: "v1",
Kind: "Service",
Name: o.serviceName,
},
},
}
}
o.serviceBinding = &servicebinding.ServiceBinding{
TypeMeta: metav1.TypeMeta{
APIVersion: strings.Join([]string{kclient.ServiceBindingGroup, kclient.ServiceBindingVersion}, "/"),
Kind: kclient.ServiceBindingKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: o.getServiceBindingName(componentName),
},
Spec: servicebinding.ServiceBindingSpec{
DetectBindingResources: true,
BindAsFiles: o.bindAsFiles,
Application: servicebinding.Application{
Ref: servicebinding.Ref{
Name: deployment.Name,
Group: deploymentGVR.Group,
Version: deploymentGVR.Version,
Resource: deploymentGVR.Resource,
},
},
Mappings: mappingsMap,
Services: []servicebinding.Service{service},
},
}
return nil
}
func (o *commonLinkOptions) validate() (err error) {
if o.EnvSpecificInfo == nil {
return fmt.Errorf("failed to find environment info to validate")
}
var svcFullName string
if o.isTargetAService {
// let's validate if the service exists
svcFullName = strings.Join([]string{o.serviceType, o.serviceName}, "/")
svcExists, err := svc.OperatorSvcExists(o.KClient, svcFullName)
if err != nil {
return err
}
if !svcExists {
return fmt.Errorf("couldn't find service named %q. Refer %q to see list of running services", svcFullName, "odo service list")
}
} else {
svcFullName = o.serviceName
if o.suppliedName == o.EnvSpecificInfo.GetName() {
if o.operationName == unlink {
return fmt.Errorf("the component %q cannot be unlinked from itself", o.suppliedName)
} else {
return fmt.Errorf("the component %q cannot be linked with itself", o.suppliedName)
}
}
}
if o.operationName == unlink {
_, found, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName, o.GetComponentContext())
if err != nil {
return err
}
if !found {
if o.getLinkType() == "service" {
return fmt.Errorf("failed to unlink the %s %q since no link was found in the configuration referring this %s", o.getLinkType(), svcFullName, o.getLinkType())
}
return fmt.Errorf("failed to unlink the %s %q since no link was found in the configuration referring this %s", o.getLinkType(), o.suppliedName, o.getLinkType())
}
return nil
}
return nil
}
func (o *commonLinkOptions) run() (err error) {
if o.Context.EnvSpecificInfo != nil {
if o.operationName == unlink {
return o.unlinkOperator()
}
return o.linkOperator()
}
var component string
if o.Context.EnvSpecificInfo != nil {
component = o.EnvSpecificInfo.GetName()
err = o.operation(o.secretName, component, o.GetApplication())
} else {
component, err = o.Component()
if err != nil {
return err
}
err = o.operation(o.secretName, component, o.GetApplication())
}
if err != nil {
return err
}
switch o.operationName {
case "link":
log.Successf("The %s %s has been successfully linked to the component %s\n", o.getLinkType(), o.suppliedName, component)
case "unlink":
log.Successf("The %s %s has been successfully unlinked from the component %s\n", o.getLinkType(), o.suppliedName, component)
default:
return fmt.Errorf("unknown operation %s", o.operationName)
}
secret, err := o.KClient.GetSecret(o.secretName, o.GetProject())
if err != nil {
return err
}
if len(secret.Data) == 0 {
log.Infof("There are no secret environment variables to expose within the %s service", o.suppliedName)
} else {
if o.operationName == "link" {
log.Infof("The below secret environment variables were added to the '%s' component:\n", component)
} else {
log.Infof("The below secret environment variables were removed from the '%s' component:\n", component)
}
// Output the environment variables
for i := range secret.Data {
fmt.Printf("· %v\n", i)
}
// Retrieve the first variable to use as an example.
// Have to use a range to access the map
var exampleEnv string
for i := range secret.Data {
exampleEnv = i
break
}
// Output what to do next if first linking...
if o.operationName == "link" {
log.Italicf(`
You can now access the environment variables from within the component pod, for example:
$%s is now available as a variable within component %s`, exampleEnv, component)
}
}
return
}
// getServiceBindingName creates a name to be used for creation/deletion of SBR during link/unlink operations
func (o *commonLinkOptions) getServiceBindingName(componentName string) string {
if len(o.name) > 0 {
return o.name
}
if !o.isTargetAService {
return strings.Join([]string{componentName, o.serviceName}, "-")
}
return strings.Join([]string{componentName, strings.ToLower(o.serviceType), o.serviceName}, "-")
}
// linkOperator creates a service binding resource and links
// the current component with the given odo service or
// the current component with the given component's service
// and stores the link info in the env
func (o *commonLinkOptions) linkOperator() (err error) {
// Convert ServiceBinding -> JSON -> Map -> YAML
// JSON conversion step is necessary to inline TypeMeta
intermediate, err := json.Marshal(o.serviceBinding)
if err != nil {
return err
}
serviceBindingMap := make(map[string]interface{})
err = json.Unmarshal(intermediate, &serviceBindingMap)
if err != nil {
return err
}
yamlDesc, err := yaml.Marshal(serviceBindingMap)
if err != nil {
return err
}
// check if the component is already linked to the requested component/service
_, found, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName, o.GetComponentContext())
if err != nil {
return err
}
if found {
return fmt.Errorf("component %q is already linked with the %s %q", o.Context.EnvSpecificInfo.GetName(), o.getLinkType(), o.suppliedName)
}
if o.inlined {
err = devfile.AddKubernetesComponentToDevfile(string(yamlDesc), o.serviceBinding.Name, o.EnvSpecificInfo.GetDevfileObj())
if err != nil {
return err
}
} else {
err = devfile.AddKubernetesComponent(string(yamlDesc), o.serviceBinding.Name, o.GetComponentContext(), o.EnvSpecificInfo.GetDevfileObj())
if err != nil {
return err
}
}
log.Successf("Successfully created link between component %q and %s %q\n", o.Context.EnvSpecificInfo.GetName(), o.getLinkType(), o.suppliedName)
log.Italic("To apply the link, please use `odo push`")
return err
}
// unlinkOperator deletes the service binding resource from the devfile
func (o *commonLinkOptions) unlinkOperator() (err error) {
// We already tested `found` in `validateForOperator`
name, _, err := svc.FindDevfileServiceBinding(o.EnvSpecificInfo.GetDevfileObj(), o.serviceType, o.serviceName, o.GetComponentContext())
if err != nil {
return err
}
err = devfile.DeleteKubernetesComponentFromDevfile(name, o.EnvSpecificInfo.GetDevfileObj(), o.GetComponentContext())
if err != nil {
return err
}
log.Successf("Successfully unlinked component %q from %s %q\n", o.Context.EnvSpecificInfo.GetName(), o.getLinkType(), o.suppliedName)
log.Italic("To apply the changes, please use `odo push`")
return nil
}

View File

@@ -43,16 +43,9 @@ func NewCmdComponent(name, fullName string) *cobra.Command {
componentGetCmd := NewCmdGet(GetRecommendedCommandName, odoutil.GetFullName(fullName, GetRecommendedCommandName)) componentGetCmd := NewCmdGet(GetRecommendedCommandName, odoutil.GetFullName(fullName, GetRecommendedCommandName))
createCmd := NewCmdCreate(CreateRecommendedCommandName, odoutil.GetFullName(fullName, CreateRecommendedCommandName)) createCmd := NewCmdCreate(CreateRecommendedCommandName, odoutil.GetFullName(fullName, CreateRecommendedCommandName))
deleteCmd := NewCmdDelete(DeleteRecommendedCommandName, odoutil.GetFullName(fullName, DeleteRecommendedCommandName)) deleteCmd := NewCmdDelete(DeleteRecommendedCommandName, odoutil.GetFullName(fullName, DeleteRecommendedCommandName))
describeCmd := NewCmdDescribe(DescribeRecommendedCommandName, odoutil.GetFullName(fullName, DescribeRecommendedCommandName))
linkCmd := NewCmdLink(LinkRecommendedCommandName, odoutil.GetFullName(fullName, LinkRecommendedCommandName))
unlinkCmd := NewCmdUnlink(UnlinkRecommendedCommandName, odoutil.GetFullName(fullName, UnlinkRecommendedCommandName))
listCmd := NewCmdList(ListRecommendedCommandName, odoutil.GetFullName(fullName, ListRecommendedCommandName)) listCmd := NewCmdList(ListRecommendedCommandName, odoutil.GetFullName(fullName, ListRecommendedCommandName))
logCmd := NewCmdLog(LogRecommendedCommandName, odoutil.GetFullName(fullName, LogRecommendedCommandName))
pushCmd := NewCmdPush(PushRecommendedCommandName, odoutil.GetFullName(fullName, PushRecommendedCommandName)) pushCmd := NewCmdPush(PushRecommendedCommandName, odoutil.GetFullName(fullName, PushRecommendedCommandName))
watchCmd := NewCmdWatch(WatchRecommendedCommandName, odoutil.GetFullName(fullName, WatchRecommendedCommandName)) watchCmd := NewCmdWatch(WatchRecommendedCommandName, odoutil.GetFullName(fullName, WatchRecommendedCommandName))
testCmd := NewCmdTest(TestRecommendedCommandName, odoutil.GetFullName(fullName, TestRecommendedCommandName))
execCmd := NewCmdExec(ExecRecommendedCommandName, odoutil.GetFullName(fullName, ExecRecommendedCommandName))
statusCmd := NewCmdStatus(StatusRecommendedCommandName, odoutil.GetFullName(fullName, StatusRecommendedCommandName))
// componentCmd represents the component command // componentCmd represents the component command
var componentCmd = &cobra.Command{ var componentCmd = &cobra.Command{
@@ -68,8 +61,7 @@ func NewCmdComponent(name, fullName string) *cobra.Command {
// add flags from 'get' to component command // add flags from 'get' to component command
componentCmd.Flags().AddFlagSet(componentGetCmd.Flags()) componentCmd.Flags().AddFlagSet(componentGetCmd.Flags())
componentCmd.AddCommand(componentGetCmd, createCmd, deleteCmd, describeCmd, linkCmd, unlinkCmd, listCmd, logCmd, pushCmd, watchCmd, execCmd) componentCmd.AddCommand(componentGetCmd, createCmd, deleteCmd, listCmd, pushCmd, watchCmd)
componentCmd.AddCommand(testCmd, statusCmd)
// Add a defined annotation in order to appear in the help menu // Add a defined annotation in order to appear in the help menu
componentCmd.Annotations = map[string]string{"command": "main"} componentCmd.Annotations = map[string]string{"command": "main"}

View File

@@ -21,7 +21,6 @@ import (
"github.com/redhat-developer/odo/pkg/devfile/location" "github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/envinfo" "github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project" projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions" "github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util" odoutil "github.com/redhat-developer/odo/pkg/odo/util"
@@ -394,8 +393,6 @@ func NewCmdCreate(name, fullName string) *cobra.Command {
odoutil.AddNowFlag(componentCreateCmd, &co.nowFlag) odoutil.AddNowFlag(componentCreateCmd, &co.nowFlag)
//Adding `--project` flag //Adding `--project` flag
projectCmd.AddProjectFlag(componentCreateCmd) projectCmd.AddProjectFlag(componentCreateCmd)
//Adding `--application` flag
appCmd.AddApplicationFlag(componentCreateCmd)
completion.RegisterCommandHandler(componentCreateCmd, completion.CreateCompletionHandler) completion.RegisterCommandHandler(componentCreateCmd, completion.CreateCompletionHandler)
completion.RegisterCommandFlagHandler(componentCreateCmd, "context", completion.FileCompletionHandler) completion.RegisterCommandFlagHandler(componentCreateCmd, "context", completion.FileCompletionHandler)

View File

@@ -3,16 +3,16 @@ package component
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/component"
"github.com/spf13/cobra"
"os" "os"
"path/filepath" "path/filepath"
"github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes/component"
"github.com/spf13/cobra"
"github.com/redhat-developer/odo/pkg/devfile" "github.com/redhat-developer/odo/pkg/devfile"
"github.com/redhat-developer/odo/pkg/devfile/adapters/common" "github.com/redhat-developer/odo/pkg/devfile/adapters/common"
"github.com/redhat-developer/odo/pkg/devfile/consts" "github.com/redhat-developer/odo/pkg/devfile/consts"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project" projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cli/ui" "github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/redhat-developer/odo/pkg/odo/cmdline" "github.com/redhat-developer/odo/pkg/odo/cmdline"
@@ -267,8 +267,6 @@ func NewCmdDelete(name, fullName string) *cobra.Command {
// Adding `--project` flag // Adding `--project` flag
projectCmd.AddProjectFlag(componentDeleteCmd) projectCmd.AddProjectFlag(componentDeleteCmd)
// Adding `--application` flag
appCmd.AddApplicationFlag(componentDeleteCmd)
return componentDeleteCmd return componentDeleteCmd
} }

View File

@@ -1,117 +0,0 @@
package component
import (
"fmt"
"os"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/component"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
ktemplates "k8s.io/kubectl/pkg/util/templates"
"github.com/spf13/cobra"
)
// DescribeRecommendedCommandName is the recommended describe command name
const DescribeRecommendedCommandName = "describe"
var describeExample = ktemplates.Examples(` # Describe nodejs component
%[1]s nodejs
`)
// DescribeOptions is a dummy container to attach complete, validate and run pattern
type DescribeOptions struct {
// Component context
*ComponentOptions
// Flags
contextFlag string
}
// NewDescribeOptions returns new instance of ListOptions
func NewDescribeOptions() *DescribeOptions {
return &DescribeOptions{
ComponentOptions: &ComponentOptions{},
}
}
// Complete completes describe args
func (do *DescribeOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
if do.contextFlag == "" {
do.contextFlag, err = os.Getwd()
if err != nil {
return err
}
}
err = do.ComponentOptions.Complete(cmdline, args)
if err != nil {
return err
}
return nil
}
// Validate validates the describe parameters
func (do *DescribeOptions) Validate() (err error) {
if !((do.GetApplication() != "" && do.GetProject() != "") || do.EnvSpecificInfo.Exists()) {
return fmt.Errorf("component %v does not exist", do.componentName)
}
return nil
}
// Run has the logic to perform the required actions as part of command
func (do *DescribeOptions) Run() (err error) {
cfd, err := component.NewComponentFullDescriptionFromClientAndLocalConfigProvider(do.Context.KClient, do.EnvSpecificInfo, do.componentName, do.Context.GetApplication(), do.Context.GetProject(), do.contextFlag)
if err != nil {
return err
}
if log.IsJSON() {
machineoutput.OutputSuccess(cfd)
} else {
err = cfd.Print(do.Context.KClient)
if err != nil {
return err
}
}
return
}
// NewCmdDescribe implements the describe odo command
func NewCmdDescribe(name, fullName string) *cobra.Command {
do := NewDescribeOptions()
var describeCmd = &cobra.Command{
Use: fmt.Sprintf("%s [component_name]", name),
Short: "Describe component",
Long: `Describe component.`,
Example: fmt.Sprintf(describeExample, fullName),
Args: cobra.RangeArgs(0, 1),
Annotations: map[string]string{"machineoutput": "json", "command": "component"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(do, cmd, args)
},
}
describeCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
completion.RegisterCommandHandler(describeCmd, completion.ComponentNameCompletionHandler)
// Adding --context flag
odoutil.AddContextFlag(describeCmd, &do.contextFlag)
//Adding `--project` flag
projectCmd.AddProjectFlag(describeCmd)
//Adding `--application` flag
appCmd.AddApplicationFlag(describeCmd)
return describeCmd
}

View File

@@ -2,12 +2,10 @@ package component
import ( import (
"os" "os"
"reflect"
"strings" "strings"
"github.com/redhat-developer/odo/pkg/devfile" "github.com/redhat-developer/odo/pkg/devfile"
devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/envinfo" "github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/machineoutput" "github.com/redhat-developer/odo/pkg/machineoutput"
@@ -114,58 +112,6 @@ func (po *PushOptions) devfilePushInner() (err error) {
return return
} }
// DevfileComponentLog fetch and display log from devfile components
func (lo LogOptions) DevfileComponentLog() error {
devObj, err := devfile.ParseAndValidateFromFile(lo.GetDevfilePath())
if err != nil {
return err
}
componentName := lo.Context.EnvSpecificInfo.GetName()
var platformContext interface{}
kc := kubernetes.KubernetesContext{
Namespace: lo.KClient.GetCurrentNamespace(),
}
platformContext = kc
devfileHandler, err := adapters.NewComponentAdapter(componentName, lo.contextFlag, lo.GetApplication(), devObj, platformContext)
if err != nil {
return err
}
var command devfilev1.Command
if lo.debugFlag {
command, err = common.GetDebugCommand(devObj.Data, "")
if err != nil {
return err
}
if reflect.DeepEqual(devfilev1.Command{}, command) {
return errors.Errorf("no debug command found in devfile, please run \"odo log\" for run command logs")
}
} else {
command, err = common.GetRunCommand(devObj.Data, "")
if err != nil {
return err
}
}
// Start or update the component
rd, err := devfileHandler.Log(lo.followFlag, command)
if err != nil {
log.Errorf(
"Failed to log component with name %s.\nError: %v",
componentName,
err,
)
return err
}
return util.DisplayLog(lo.followFlag, rd, os.Stdout, componentName, -1)
}
// DevfileUnDeploy undeploys the devfile kubernetes components // DevfileUnDeploy undeploys the devfile kubernetes components
func (do *DeleteOptions) DevfileUnDeploy() error { func (do *DeleteOptions) DevfileUnDeploy() error {
devObj, err := devfile.ParseAndValidateFromFile(do.GetDevfilePath()) devObj, err := devfile.ParseAndValidateFromFile(do.GetDevfilePath())
@@ -208,41 +154,3 @@ func (do *DeleteOptions) DevfileComponentDelete() error {
return devfileHandler.Delete(labels, do.showLogFlag, do.waitFlag) return devfileHandler.Delete(labels, do.showLogFlag, do.waitFlag)
} }
// RunTestCommand runs the specific test command in devfile
func (to *TestOptions) RunTestCommand() error {
componentName := to.Context.EnvSpecificInfo.GetName()
var platformContext interface{}
kc := kubernetes.KubernetesContext{
Namespace: to.KClient.GetCurrentNamespace(),
}
platformContext = kc
devfileHandler, err := adapters.NewComponentAdapter(componentName, to.contextFlag, to.GetApplication(), to.devObj, platformContext)
if err != nil {
return err
}
return devfileHandler.Test(to.testCommandFlag, to.showLogFlag)
}
// DevfileComponentExec executes the given user command inside the component
func (eo *ExecOptions) DevfileComponentExec(command []string) error {
devObj, err := devfile.ParseAndValidateFromFile(eo.componentOptions.GetDevfilePath())
if err != nil {
return err
}
componentName := eo.componentOptions.EnvSpecificInfo.GetName()
kc := kubernetes.KubernetesContext{
Namespace: eo.componentOptions.KClient.GetCurrentNamespace(),
}
devfileHandler, err := adapters.NewComponentAdapter(componentName, eo.contextFlag, eo.componentOptions.GetApplication(), devObj, kc)
if err != nil {
return err
}
return devfileHandler.Exec(command)
}

View File

@@ -1,97 +0,0 @@
package component
import (
"fmt"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
// ExecRecommendedCommandName is the recommended exec command name
const ExecRecommendedCommandName = "exec"
var execExample = ktemplates.Examples(` # Executes a command inside the component
%[1]s -- ls -a
`)
// ExecOptions contains exec options
type ExecOptions struct {
// Component context
componentOptions *ComponentOptions
// Parameters
command []string
// Flags
contextFlag string
}
// NewExecOptions returns new instance of ExecOptions
func NewExecOptions() *ExecOptions {
return &ExecOptions{
componentOptions: &ComponentOptions{},
}
}
// Complete completes exec args
func (eo *ExecOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
// gets the command args passed after the dash i.e `--`
eo.command, err = cmdline.GetArgsAfterDashes(args)
if err != nil || len(eo.command) <= 0 {
return fmt.Errorf(`no command was given for the exec command
Please provide a command to execute, odo exec -- <command to be execute>`)
}
// checks if something is passed between `odo exec` and the dash `--`
if len(eo.command) != len(args) {
return fmt.Errorf("no parameter is expected for the command")
}
eo.componentOptions.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(eo.contextFlag))
return err
}
// Validate validates the exec parameters
func (eo *ExecOptions) Validate() (err error) {
return
}
// Run has the logic to perform the required actions as part of command
func (eo *ExecOptions) Run() (err error) {
return eo.DevfileComponentExec(eo.command)
}
// NewCmdExec implements the exec odo command
func NewCmdExec(name, fullName string) *cobra.Command {
o := NewExecOptions()
var execCmd = &cobra.Command{
Use: name,
Short: "Executes a command inside the component",
Long: `Executes a command inside the component`,
Example: fmt.Sprintf(execExample, fullName),
Annotations: map[string]string{"command": "component"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
execCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
completion.RegisterCommandHandler(execCmd, completion.ComponentNameCompletionHandler)
odoutil.AddContextFlag(execCmd, &o.contextFlag)
//Adding `--project` flag
projectCmd.AddProjectFlag(execCmd)
// Adding `--app` flag
appCmd.AddApplicationFlag(execCmd)
return execCmd
}

View File

@@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
"github.com/redhat-developer/odo/pkg/odo/cli/project" "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline" "github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions" "github.com/redhat-developer/odo/pkg/odo/genericclioptions"
@@ -97,8 +96,6 @@ func NewCmdGet(name, fullName string) *cobra.Command {
//Adding `--project` flag //Adding `--project` flag
project.AddProjectFlag(componentGetCmd) project.AddProjectFlag(componentGetCmd)
//Adding `--application` flag
appCmd.AddApplicationFlag(componentGetCmd)
return componentGetCmd return componentGetCmd
} }

View File

@@ -1,131 +0,0 @@
package component
import (
"fmt"
servicebinding "github.com/redhat-developer/service-binding-operator/apis/binding/v1alpha1"
"github.com/spf13/cobra"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
// LinkRecommendedCommandName is the recommended link command name
const LinkRecommendedCommandName = "link"
var (
linkExample = ktemplates.Examples(`# Link the current component to the 'EtcdCluster' named 'myetcd'
%[1]s EtcdCluster/myetcd
# Link current component to the 'backend' component (backend must have a single exposed port)
%[1]s backend
# Link current component to the 'backend' component and puts the link definition in the devfile instead of a separate file
%[1]s backend --inlined
# Link component 'nodejs' to the 'backend' component
%[1]s backend --component nodejs
# Link current component to port 8080 of the 'backend' component (backend must have port 8080 exposed)
%[1]s backend --port 8080
# Link the current component to the 'EtcdCluster' named 'myetcd'
# and make the secrets accessible as files in the '/bindings/etcd/' directory
%[1]s EtcdCluster/myetcd --bind-as-files --name etcd`)
linkLongDesc = `Link current or provided component to a service (backed by an Operator) or another component
The appropriate secret will be added to the environment of the source component as environment variables by
default.
For example:
Let us say we have created a nodejs application called 'frontend' which we link to an another component called
'backend' which exposes port 8080, then linking the 2 using:
odo link backend --component frontend
The frontend has 2 ENV variables it can use:
SERVICE_BACKEND_IP=10.217.4.194
SERVICE_BACKEND_PORT_PORT-8080=8080
Using the '--bind-as-files' flag, secrets will be accessible as files instead of environment variables.
The value of the '--name' flag indicates the name of the directory under '/bindings/' containing the secrets files.
`
)
// LinkOptions encapsulates the options for the odo link command
type LinkOptions struct {
// Common link/unlink context
*commonLinkOptions
// Flags
contextFlag string
}
// NewLinkOptions creates a new LinkOptions instance
func NewLinkOptions() *LinkOptions {
options := LinkOptions{}
options.commonLinkOptions = newCommonLinkOptions()
options.commonLinkOptions.serviceBinding = &servicebinding.ServiceBinding{}
return &options
}
// Complete completes LinkOptions after they've been created
func (o *LinkOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.commonLinkOptions.devfilePath = location.DevfileLocation(o.contextFlag)
err = o.complete(cmdline, args, o.contextFlag)
if err != nil {
return err
}
if o.csvSupport {
o.operation = o.KClient.LinkSecret
}
return err
}
// Validate validates the LinkOptions based on completed values
func (o *LinkOptions) Validate() (err error) {
return o.validate()
}
// Run contains the logic for the odo link command
func (o *LinkOptions) Run() (err error) {
return o.run()
}
// NewCmdLink implements the link odo command
func NewCmdLink(name, fullName string) *cobra.Command {
o := NewLinkOptions()
linkCmd := &cobra.Command{
Use: fmt.Sprintf("%s <operator-service-type>/<service-name> OR %s <operator-service-type>/<service-name> --component [component] OR %s <component> --component [component]", name, name, name),
Short: "Link component to a service or component",
Long: linkLongDesc,
Example: fmt.Sprintf(linkExample, fullName),
Args: cobra.ExactArgs(1),
Annotations: map[string]string{"command": "component"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
linkCmd.PersistentFlags().BoolVarP(&o.inlined, "inlined", "", false, "Puts the link definition in the devfile instead of a separate file")
linkCmd.PersistentFlags().StringVar(&o.name, "name", "", "Name of the created ServiceBinding resource")
linkCmd.PersistentFlags().BoolVar(&o.bindAsFiles, "bind-as-files", false, "If enabled, configuration values will be mounted as files, instead of declared as environment variables")
linkCmd.PersistentFlags().StringArrayVarP(&o.mappings, "map", "", []string{}, "Mappings (custom binding data) to be added to the component; each map should be specified as <key>=<value>")
linkCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
//Adding `--component` flag
AddComponentFlag(linkCmd)
//Adding context flag
odoutil.AddContextFlag(linkCmd, &o.contextFlag)
return linkCmd
}

View File

@@ -19,7 +19,6 @@ import (
"github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project" projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline" "github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions" "github.com/redhat-developer/odo/pkg/odo/genericclioptions"
@@ -237,8 +236,6 @@ func NewCmdList(name, fullName string) *cobra.Command {
//Adding `--project` flag //Adding `--project` flag
projectCmd.AddProjectFlag(componentListCmd) projectCmd.AddProjectFlag(componentListCmd)
//Adding `--application` flag
appCmd.AddApplicationFlag(componentListCmd)
completion.RegisterCommandFlagHandler(componentListCmd, "path", completion.FileCompletionHandler) completion.RegisterCommandFlagHandler(componentListCmd, "path", completion.FileCompletionHandler)

View File

@@ -1,90 +0,0 @@
package component
import (
"fmt"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
ktemplates "k8s.io/kubectl/pkg/util/templates"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
)
// LogRecommendedCommandName is the recommended watch command name
const LogRecommendedCommandName = "log"
var logExample = ktemplates.Examples(` # Get the logs for the nodejs component
%[1]s nodejs
`)
// LogOptions contains log options
type LogOptions struct {
// Component context
*ComponentOptions
// Flags
followFlag bool
debugFlag bool
contextFlag string
}
// NewLogOptions returns new instance of LogOptions
func NewLogOptions() *LogOptions {
return &LogOptions{
ComponentOptions: &ComponentOptions{},
}
}
// Complete completes log args
func (lo *LogOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
lo.ComponentOptions.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(lo.contextFlag))
return err
}
// Validate validates the log parameters
func (lo *LogOptions) Validate() (err error) {
return
}
// Run has the logic to perform the required actions as part of command
func (lo *LogOptions) Run() (err error) {
err = lo.DevfileComponentLog()
return
}
// NewCmdLog implements the log odo command
func NewCmdLog(name, fullName string) *cobra.Command {
o := NewLogOptions()
var logCmd = &cobra.Command{
Use: fmt.Sprintf("%s [component_name]", name),
Short: "Retrieve the log for the given component",
Long: `Retrieve the log for the given component`,
Example: fmt.Sprintf(logExample, fullName),
Args: cobra.RangeArgs(0, 1),
Annotations: map[string]string{"command": "component"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
logCmd.Flags().BoolVarP(&o.followFlag, "follow", "f", false, "Follow logs")
logCmd.Flags().BoolVar(&o.debugFlag, "debug", false, "Show logs for debug command")
logCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
completion.RegisterCommandHandler(logCmd, completion.ComponentNameCompletionHandler)
// Adding `--context` flag
odoutil.AddContextFlag(logCmd, &o.contextFlag)
//Adding `--project` flag
projectCmd.AddProjectFlag(logCmd)
//Adding `--application` flag
appCmd.AddApplicationFlag(logCmd)
return logCmd
}

View File

@@ -1,134 +0,0 @@
package component
import (
"fmt"
"time"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/devfile/adapters"
"github.com/redhat-developer/odo/pkg/devfile/adapters/common"
"github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/url"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
ktemplates "k8s.io/kubectl/pkg/util/templates"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
)
// StatusRecommendedCommandName is the recommended watch command name
const StatusRecommendedCommandName = "status"
var statusExample = ktemplates.Examples(` # Get the status for the nodejs component
%[1]s nodejs -o json --follow
`)
// StatusOptions contains status options
type StatusOptions struct {
// Context
*genericclioptions.Context
// Flags
contextFlag string
followFlag bool
componentName string
devfileHandler common.ComponentAdapter
}
// NewStatusOptions returns new instance of StatusOptions
func NewStatusOptions() *StatusOptions {
return &StatusOptions{}
}
// Complete completes status args
func (so *StatusOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
so.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(so.contextFlag))
if err != nil {
return err
}
// Get the component name
so.componentName = so.EnvSpecificInfo.GetName()
platformContext := kubernetes.KubernetesContext{
Namespace: so.KClient.GetCurrentNamespace(),
}
so.devfileHandler, err = adapters.NewComponentAdapter(so.componentName, so.contextFlag, so.GetApplication(), so.EnvSpecificInfo.GetDevfileObj(), platformContext)
return err
}
// Validate validates the status parameters
func (so *StatusOptions) Validate() (err error) {
if !so.followFlag {
return fmt.Errorf("this command must be called with --follow")
}
return
}
// Run has the logic to perform the required actions as part of command
func (so *StatusOptions) Run() (err error) {
if !log.IsJSON() {
return errors.New("this command only supports the '-o json' output format")
}
so.devfileHandler.StartSupervisordCtlStatusWatch()
so.devfileHandler.StartContainerStatusWatch()
loggingClient := machineoutput.NewConsoleMachineEventLoggingClient()
url.StartURLHttpRequestStatusWatchForK8S(so.KClient, &so.LocalConfigProvider, loggingClient)
// You can call Run() any time you like, but you can never leave.
for {
time.Sleep(60 * time.Second)
}
}
// NewCmdStatus implements the status odo command
func NewCmdStatus(name, fullName string) *cobra.Command {
o := NewStatusOptions()
annotations := map[string]string{"command": "component", "machineoutput": "json"}
var statusCmd = &cobra.Command{
Use: fmt.Sprintf("%s [component_name]", name),
Short: "Watches the given component and outputs machine-readable JSON events representing component status changes",
Long: `Watches the given component and outputs machine-readable JSON events representing component status changes`,
Example: fmt.Sprintf(statusExample, fullName),
Args: cobra.MaximumNArgs(1),
Annotations: annotations,
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
statusCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
// Adding context flag
odoutil.AddContextFlag(statusCmd, &o.contextFlag)
statusCmd.Flags().BoolVarP(&o.followFlag, "follow", "f", false, "Follow the component and report all changes")
//Adding `--application` flag
appCmd.AddApplicationFlag(statusCmd)
//Adding `--project` flag
projectCmd.AddProjectFlag(statusCmd)
completion.RegisterCommandHandler(statusCmd, completion.ComponentNameCompletionHandler)
return statusCmd
}

View File

@@ -1,104 +0,0 @@
package component
import (
"fmt"
"github.com/redhat-developer/odo/pkg/devfile"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/util"
devfileParser "github.com/devfile/library/pkg/devfile/parser"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/templates"
)
// TestRecommendedCommandName is the recommended test command name
const TestRecommendedCommandName = "test"
// TestOptions encapsulates the options for the odo command
type TestOptions struct {
// Context
*genericclioptions.Context
// Flags
testCommandFlag string
contextFlag string
showLogFlag bool
// devfile content
devObj devfileParser.DevfileObj
}
var testExample = templates.Examples(`
# Run default test command
%[1]s
# Run a specific test command
%[1]s --test-command <command name>
`)
// NewTestOptions creates a new TestOptions instance
func NewTestOptions() *TestOptions {
return &TestOptions{}
}
// Complete completes TestOptions after they've been created
func (to *TestOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
to.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(to.contextFlag))
return
}
// Validate validates the TestOptions based on completed values
func (to *TestOptions) Validate() (err error) {
if !util.CheckPathExists(to.Context.GetDevfilePath()) {
return fmt.Errorf("unable to find devfile, odo test command is only supported by devfile components")
}
devObj, err := devfile.ParseAndValidateFromFile(to.Context.GetDevfilePath())
if err != nil {
return err
}
to.devObj = devObj
return
}
// Run contains the logic for the odo command
func (to *TestOptions) Run() (err error) {
return to.RunTestCommand()
}
// NewCmdTest implements the odo test command
func NewCmdTest(name, fullName string) *cobra.Command {
to := NewTestOptions()
testCmd := &cobra.Command{
Use: name,
Short: "Run the test command defined in the devfile",
Long: "Run the test command defined in the devfile",
Example: fmt.Sprintf(testExample, fullName),
Args: cobra.MaximumNArgs(0),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(to, cmd, args)
},
}
// Add a defined annotation in order to appear in the help menu
testCmd.Annotations = map[string]string{"command": "main"}
testCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
testCmd.Flags().StringVar(&to.testCommandFlag, "test-command", "", "Devfile Test Command to execute")
testCmd.Flags().BoolVar(&to.showLogFlag, "show-log", false, "If enabled, logs will be shown when running the test command")
//Adding `--context` flag
odoutil.AddContextFlag(testCmd, &to.contextFlag)
//Adding `--project` flag
projectCmd.AddProjectFlag(testCmd)
// Adding `--app` flag
appCmd.AddApplicationFlag(testCmd)
completion.RegisterCommandHandler(testCmd, completion.ComponentNameCompletionHandler)
return testCmd
}

View File

@@ -1,101 +0,0 @@
package component
import (
"fmt"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util"
ktemplates "k8s.io/kubectl/pkg/util/templates"
"github.com/spf13/cobra"
)
// UnlinkRecommendedCommandName is the recommended unlink command name
const UnlinkRecommendedCommandName = "unlink"
var (
unlinkExample = ktemplates.Examples(`# Unlink the 'my-postgresql' service from the current component
%[1]s my-postgresql
# Unlink the 'my-postgresql' service from the 'nodejs' component
%[1]s my-postgresql --component nodejs
# Unlink the 'backend' component from the current component (backend must have a single exposed port)
%[1]s backend
# Unlink the 'backend' service from the 'nodejs' component
%[1]s backend --component nodejs
# Unlink the backend's 8080 port from the current component
%[1]s backend --port 8080`)
unlinkLongDesc = `Unlink component or service from a component.
For this command to be successful, the service or component needs to have been linked prior to the invocation using 'odo link'`
)
// UnlinkOptions encapsulates the options for the odo link command
type UnlinkOptions struct {
// Common link/unlink context
*commonLinkOptions
// Flags
contextFlag string
}
// NewUnlinkOptions creates a new UnlinkOptions instance
func NewUnlinkOptions() *UnlinkOptions {
options := UnlinkOptions{}
options.commonLinkOptions = newCommonLinkOptions()
return &options
}
// Complete completes UnlinkOptions after they've been created
func (o *UnlinkOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
err = o.complete(cmdline, args, o.contextFlag)
if err != nil {
return err
}
if o.csvSupport {
o.operation = o.KClient.UnlinkSecret
}
return err
}
// Validate validates the UnlinkOptions based on completed values
func (o *UnlinkOptions) Validate() (err error) {
return o.validate()
}
// Run contains the logic for the odo link command
func (o *UnlinkOptions) Run() (err error) {
return o.run()
}
// NewCmdUnlink implements the link odo command
func NewCmdUnlink(name, fullName string) *cobra.Command {
o := NewUnlinkOptions()
unlinkCmd := &cobra.Command{
Use: fmt.Sprintf("%s <service> --component [component] OR %s <component> --component [component]", name, name),
Short: "Unlink component to a service or component",
Long: unlinkLongDesc,
Example: fmt.Sprintf(unlinkExample, fullName),
Args: cobra.ExactArgs(1),
Annotations: map[string]string{"command": "component"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
unlinkCmd.SetUsageTemplate(util.CmdUsageTemplate)
//Adding `--component` flag
AddComponentFlag(unlinkCmd)
// Adding context flag
odoutil.AddContextFlag(unlinkCmd, &o.contextFlag)
return unlinkCmd
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/redhat-developer/odo/pkg/devfile/adapters/common" "github.com/redhat-developer/odo/pkg/devfile/adapters/common"
"github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes" "github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes"
"github.com/redhat-developer/odo/pkg/envinfo" "github.com/redhat-developer/odo/pkg/envinfo"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project" projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/cmdline" "github.com/redhat-developer/odo/pkg/odo/cmdline"
ktemplates "k8s.io/kubectl/pkg/util/templates" ktemplates "k8s.io/kubectl/pkg/util/templates"
@@ -177,9 +176,6 @@ func NewCmdWatch(name, fullName string) *cobra.Command {
// Adding context flag // Adding context flag
odoutil.AddContextFlag(watchCmd, &wo.contextFlag) odoutil.AddContextFlag(watchCmd, &wo.contextFlag)
//Adding `--application` flag
appCmd.AddApplicationFlag(watchCmd)
//Adding `--project` flag //Adding `--project` flag
projectCmd.AddProjectFlag(watchCmd) projectCmd.AddProjectFlag(watchCmd)

View File

@@ -1,44 +0,0 @@
package config
import (
"fmt"
"github.com/redhat-developer/odo/pkg/config"
"github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
// RecommendedCommandName is the recommended config command name
const RecommendedCommandName = "config"
var configLongDesc = ktemplates.LongDesc(`Modifies odo specific configuration settings within the devfile or config file.
%[1]s
`)
// NewCmdConfiguration implements the utils config odo command
func NewCmdConfiguration(name, fullName string) *cobra.Command {
configurationViewCmd := NewCmdView(viewCommandName, util.GetFullName(fullName, viewCommandName))
configurationSetCmd := NewCmdSet(setCommandName, util.GetFullName(fullName, setCommandName))
configurationUnsetCmd := NewCmdUnset(unsetCommandName, util.GetFullName(fullName, unsetCommandName))
configurationCmd := &cobra.Command{
Use: name,
Short: "Change or view configuration",
Long: fmt.Sprintf(configLongDesc, config.FormatDevfileSupportedParameters()),
Example: fmt.Sprintf("%s\n%s\n%s",
configurationViewCmd.Example,
configurationSetCmd.Example,
configurationUnsetCmd.Example,
),
Aliases: []string{"configuration"},
}
configurationCmd.AddCommand(configurationViewCmd, configurationSetCmd)
configurationCmd.AddCommand(configurationUnsetCmd)
configurationCmd.SetUsageTemplate(util.CmdUsageTemplate)
configurationCmd.Annotations = map[string]string{"command": "main"}
return configurationCmd
}

View File

@@ -1,214 +0,0 @@
package config
import (
"fmt"
"strings"
"github.com/redhat-developer/odo/pkg/envvar"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/preference"
"github.com/redhat-developer/odo/pkg/project"
"github.com/redhat-developer/odo/pkg/util"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/config"
"github.com/redhat-developer/odo/pkg/log"
clicomponent "github.com/redhat-developer/odo/pkg/odo/cli/component"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/validation"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const setCommandName = "set"
var (
setLongDesc = ktemplates.LongDesc(`Set an individual value in the devfile or odo configuration file.
%[1]s
`)
devfileSetExample = ktemplates.Examples(`
# Set a configuration value in the devfile
%[1]s %[2]s testapp
%[1]s %[3]s 8080/TCP,8443/TCP
%[1]s %[4]s 500M
# Set a env variable in the devfiles
%[1]s --env KAFKA_HOST=kafka --env KAFKA_PORT=6639
`)
)
// SetOptions encapsulates the options for the command
type SetOptions struct {
// Push context
*clicomponent.PushOptions
// Parameters
paramName string
paramValue string
// Flags
forceFlag bool
envArrayFlag []string
nowFlag bool
}
// NewSetOptions creates a new SetOptions instance
func NewSetOptions(prjClient project.Client, prefClient preference.Client) *SetOptions {
return &SetOptions{
PushOptions: clicomponent.NewPushOptions(prjClient, prefClient),
}
}
// Complete completes SetOptions after they've been created
func (o *SetOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
params := genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.GetComponentContext())
if o.nowFlag {
params.CreateAppIfNeeded().RequireRouteAvailability()
}
o.Context, err = genericclioptions.New(params)
if err != nil {
if err1 := util.IsInvalidKubeConfigError(err); err1 != nil {
return err1
}
return err
}
o.DevfilePath = o.Context.EnvSpecificInfo.GetDevfilePath()
o.EnvSpecificInfo = o.Context.EnvSpecificInfo
if o.envArrayFlag == nil {
o.paramName = args[0]
o.paramValue = args[1]
}
if o.nowFlag {
prjName := o.Context.LocalConfigProvider.GetNamespace()
o.ResolveSrcAndConfigFlags()
err = o.ResolveProject(prjName)
if err != nil {
return err
}
}
return nil
}
// Validate validates the SetOptions based on completed values
func (o *SetOptions) Validate() error {
if !o.Context.LocalConfigProvider.Exists() {
return fmt.Errorf("the directory doesn't contain a component. Use 'odo create' to create a component")
}
return nil
}
// Run contains the logic for the command
func (o *SetOptions) Run() error {
if o.envArrayFlag != nil {
newEnvVarList, err := envvar.NewListFromSlice(o.envArrayFlag)
if err != nil {
return err
}
err = o.EnvSpecificInfo.GetDevfileObj().AddEnvVars(newEnvVarList.ToDevfileEnvVar())
if err != nil {
return err
}
log.Success("Environment variables were successfully updated")
if o.nowFlag {
return o.DevfilePush()
}
log.Italic("\nRun `odo push` command to apply changes to the cluster")
return err
}
if !o.forceFlag {
if config.IsSetInDevfile(o.EnvSpecificInfo.GetDevfileObj(), o.paramName) {
if !ui.Proceed(fmt.Sprintf("%v is already set. Do you want to override it in the devfile", o.paramName)) {
fmt.Println("Aborted by the user.")
return nil
}
}
}
err := config.SetDevfileConfiguration(o.EnvSpecificInfo.GetDevfileObj(), strings.ToLower(o.paramName), o.paramValue)
if err != nil {
return err
}
log.Success("Devfile successfully updated")
if o.nowFlag {
return o.DevfilePush()
}
log.Italic("\nRun `odo push` command to apply changes to the cluster")
return err
}
func isValidArgumentList(args []string) error {
if len(args) < 2 {
return fmt.Errorf("please provide a parameter name and value")
} else if len(args) > 2 {
return fmt.Errorf("only one value per parameter is allowed")
}
var err error
param, ok := config.AsDevfileSupportedParameter(args[0])
if !ok {
err = errors.Errorf("the provided parameter is not supported, %v", args[0])
}
switch param {
case "memory", "minmemory", "maxmemory", "cpu", "mincpu", "maxcpu":
err = validation.NonNegativeValidator(args[1])
if err != nil {
err = errors.Errorf("%s is invalid %v", param, err)
}
case "ports", "debugport":
err = validation.PortsValidator(args[1])
}
if err != nil {
err = errors.Errorf("validation failed for the provided arguments, %v", err)
}
return err
}
// NewCmdSet implements the config set odo command
func NewCmdSet(name, fullName string) *cobra.Command {
// The error is not handled at this point, it will be handled during Context creation
kubclient, _ := kclient.New()
prefClient, err := preference.NewClient()
if err != nil {
odoutil.LogErrorAndExit(err, "unable to set preference, something is wrong with odo, kindly raise an issue at https://github.com/redhat-developer/odo/issues/new?template=Bug.md")
}
o := NewSetOptions(project.NewClient(kubclient), prefClient)
configurationSetCmd := &cobra.Command{
Use: name,
Short: "Set a value in odo config file",
Long: fmt.Sprintf(setLongDesc, config.FormatDevfileSupportedParameters()),
Example: fmt.Sprintf("\n"+devfileSetExample, fullName, config.Name, config.Ports, config.Memory),
Args: func(cmd *cobra.Command, args []string) error {
if o.envArrayFlag != nil {
// no args are needed
if len(args) > 0 {
return fmt.Errorf("expected 0 args")
}
return nil
}
return isValidArgumentList(args)
}, Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
configurationSetCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Don't ask for confirmation, set the config directly")
configurationSetCmd.Flags().StringArrayVarP(&o.envArrayFlag, "env", "e", nil, "Set the environment variables in config")
o.AddContextFlag(configurationSetCmd)
odoutil.AddNowFlag(configurationSetCmd, &o.nowFlag)
return configurationSetCmd
}

View File

@@ -1,171 +0,0 @@
package config
import (
"fmt"
"strings"
"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/preference"
"github.com/redhat-developer/odo/pkg/project"
"github.com/redhat-developer/odo/pkg/util"
"github.com/redhat-developer/odo/pkg/config"
"github.com/redhat-developer/odo/pkg/log"
clicomponent "github.com/redhat-developer/odo/pkg/odo/cli/component"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const unsetCommandName = "unset"
var (
unsetLongDesc = ktemplates.LongDesc(`Unset an individual value in the devfile or odo configuration file.
%[1]s
`)
devfileUnsetExample = ktemplates.Examples(`
# Unset a configuration value in the devfile
%[1]s %[2]s
%[1]s %[3]s
%[1]s %[4]s
# Unset a env variable in the devfiles
%[1]s --env KAFKA_HOST --env KAFKA_PORT
`)
)
// UnsetOptions encapsulates the options for the command
type UnsetOptions struct {
// Push context
*clicomponent.PushOptions
// Parameters
paramName string
// Flags
forceFlag bool
envArrayFlag []string
nowFlag bool
}
// NewUnsetOptions creates a new UnsetOptions instance
func NewUnsetOptions(prjClient project.Client, prefClient preference.Client) *UnsetOptions {
return &UnsetOptions{
PushOptions: clicomponent.NewPushOptions(prjClient, prefClient),
}
}
// Complete completes UnsetOptions after they've been created
func (o *UnsetOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
params := genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.GetComponentContext())
if o.nowFlag {
params.CreateAppIfNeeded().RequireRouteAvailability()
}
o.Context, err = genericclioptions.New(params)
if err != nil {
if err1 := util.IsInvalidKubeConfigError(err); err1 != nil {
return err1
}
return err
}
o.DevfilePath = o.Context.EnvSpecificInfo.GetDevfilePath()
o.EnvSpecificInfo = o.Context.EnvSpecificInfo
if o.envArrayFlag == nil {
o.paramName = args[0]
}
if o.nowFlag {
prjName := o.Context.LocalConfigProvider.GetNamespace()
o.ResolveSrcAndConfigFlags()
err = o.ResolveProject(prjName)
if err != nil {
return err
}
}
return nil
}
// Validate validates the UnsetOptions based on completed values
func (o *UnsetOptions) Validate() error {
if !o.Context.LocalConfigProvider.Exists() {
return fmt.Errorf("the directory doesn't contain a component. Use 'odo create' to create a component")
}
return nil
}
// Run contains the logic for the command
func (o *UnsetOptions) Run() error {
if o.envArrayFlag != nil {
if err := o.EnvSpecificInfo.GetDevfileObj().RemoveEnvVars(o.envArrayFlag); err != nil {
return err
}
log.Success("Environment variables were successfully updated")
if o.nowFlag {
return o.DevfilePush()
}
log.Italic("\nRun `odo push` command to apply changes to the cluster")
return nil
}
if isSet := config.IsSetInDevfile(o.EnvSpecificInfo.GetDevfileObj(), o.paramName); isSet {
if !o.forceFlag && !ui.Proceed(fmt.Sprintf("Do you want to unset %s in the devfile", o.paramName)) {
fmt.Println("Aborted by the user.")
return nil
}
err := config.DeleteDevfileConfiguration(o.EnvSpecificInfo.GetDevfileObj(), strings.ToLower(o.paramName))
log.Success("Devfile was successfully updated.")
if o.nowFlag {
return o.DevfilePush()
}
return err
}
return fmt.Errorf("config already unset, cannot unset a configuration which is not set")
}
// NewCmdUnset implements the config unset odo command
func NewCmdUnset(name, fullName string) *cobra.Command {
// The error is not handled at this point, it will be handled during Context creation
kubclient, _ := kclient.New()
prefClient, err := preference.NewClient()
if err != nil {
odoutil.LogErrorAndExit(err, "unable to set preference, something is wrong with odo, kindly raise an issue at https://github.com/redhat-developer/odo/issues/new?template=Bug.md")
}
o := NewUnsetOptions(project.NewClient(kubclient), prefClient)
configurationUnsetCmd := &cobra.Command{
Use: name,
Short: "Unset a value in odo config file",
Long: fmt.Sprintf(unsetLongDesc, config.FormatDevfileSupportedParameters()),
Example: fmt.Sprintf("\n"+devfileUnsetExample, fullName, config.Name, config.Ports, config.Memory),
Args: func(cmd *cobra.Command, args []string) error {
if o.envArrayFlag != nil {
// no args are needed
if len(args) > 0 {
return fmt.Errorf("expected 0 args")
}
return nil
}
if len(args) < 1 {
return fmt.Errorf("please provide a parameter name")
} else if len(args) > 1 {
return fmt.Errorf("only one parameter is allowed")
} else {
return nil
}
}, Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
configurationUnsetCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Don't ask for confirmation, unsetting the config directly")
configurationUnsetCmd.Flags().StringSliceVarP(&o.envArrayFlag, "env", "e", nil, "Unset the environment variables in config")
o.AddContextFlag(configurationUnsetCmd)
odoutil.AddNowFlag(configurationUnsetCmd, &o.nowFlag)
return configurationUnsetCmd
}

View File

@@ -1,90 +0,0 @@
package config
import (
"fmt"
"os"
"text/tabwriter"
"github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
"sigs.k8s.io/yaml"
)
const viewCommandName = "view"
var viewExample = ktemplates.Examples(`# For viewing the current configuration from devfile or local config file
%[1]s
`)
// ViewOptions encapsulates the options for the command
type ViewOptions struct {
// Context
*genericclioptions.Context
// Flags
contextFlag string
}
// NewViewOptions creates a new ViewOptions instance
func NewViewOptions() *ViewOptions {
return &ViewOptions{}
}
// Complete completes ViewOptions after they've been created
func (o *ViewOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
params := genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag)
o.Context, err = genericclioptions.New(params)
return err
}
// Validate validates the ViewOptions based on completed values
func (o *ViewOptions) Validate() error {
return nil
}
// Run contains the logic for the command
func (o *ViewOptions) Run() (err error) {
w := tabwriter.NewWriter(os.Stdout, 5, 2, 2, ' ', tabwriter.TabIndent)
repr, err := component.ToDevfileRepresentation(o.Context.EnvSpecificInfo.GetDevfileObj())
if err != nil {
return err
}
if log.IsJSON() {
machineoutput.OutputSuccess(component.WrapFromJSONOutput(repr))
return nil
}
representation, err := yaml.Marshal(repr)
if err != nil {
return err
}
fmt.Fprintln(w, string(representation))
return nil
}
// NewCmdView implements the config view odo command
func NewCmdView(name, fullName string) *cobra.Command {
o := NewViewOptions()
configurationViewCmd := &cobra.Command{
Use: name,
Short: "View current configuration values",
Long: "View current configuration values",
Annotations: map[string]string{"machineoutput": "json"},
Example: fmt.Sprintf(fmt.Sprint("\n", viewExample), fullName),
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
odoutil.AddContextFlag(configurationViewCmd, &o.contextFlag)
return configurationViewCmd
}

View File

@@ -1,39 +0,0 @@
package debug
import (
"fmt"
"github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const (
// RecommendedCommandName is the recommended debug command name
RecommendedCommandName = "debug"
)
var debugLongDesc = ktemplates.LongDesc(`Debug allows you to remotely debug your application.`)
func NewCmdDebug(name, fullName string) *cobra.Command {
portforwardCmd := NewCmdPortForward(portforwardCommandName, util.GetFullName(fullName, portforwardCommandName))
infoCmd := NewCmdInfo(infoCommandName, util.GetFullName(fullName, infoCommandName))
debugCmd := &cobra.Command{
Use: name,
Short: "Debug commands",
Example: fmt.Sprintf("%s\n\n%s",
portforwardCmd.Example,
infoCmd.Example),
Long: debugLongDesc,
Aliases: []string{"d"},
}
debugCmd.SetUsageTemplate(util.CmdUsageTemplate)
debugCmd.AddCommand(portforwardCmd)
debugCmd.AddCommand(infoCmd)
debugCmd.Annotations = map[string]string{"command": "main"}
return debugCmd
}

View File

@@ -1,98 +0,0 @@
package debug
import (
"fmt"
"github.com/redhat-developer/odo/pkg/debug"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
k8sgenclioptions "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubectl/pkg/util/templates"
)
// InfoOptions contains all the options for running the info cli command.
type InfoOptions struct {
// Context
*genericclioptions.Context
// Flags
contextFlag string
// Port forwarder backend
PortForwarder *debug.DefaultPortForwarder
}
var (
infoLong = templates.LongDesc(`
Gets information regarding any debug session of the component.
`)
infoExample = templates.Examples(`
# Get information regarding any debug session of the component
odo debug info
`)
)
const (
infoCommandName = "info"
)
func NewInfoOptions() *InfoOptions {
return &InfoOptions{}
}
// Complete completes all the required options for port-forward cmd.
func (o *InfoOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
// Using Discard streams because nothing important is logged
o.PortForwarder = debug.NewDefaultPortForwarder(o.Context.EnvSpecificInfo.GetName(), o.Context.GetApplication(), o.Context.EnvSpecificInfo.GetNamespace(), o.KClient, k8sgenclioptions.NewTestIOStreamsDiscard())
return err
}
// Validate validates all the required options for port-forward cmd.
func (o InfoOptions) Validate() error {
return nil
}
// Run implements all the necessary functionality for port-forward cmd.
func (o InfoOptions) Run() error {
if debugInfo, debugging := debug.GetInfo(o.PortForwarder); debugging {
if log.IsJSON() {
machineoutput.OutputSuccess(debugInfo)
} else {
log.Infof("Debug is running for the component on the local port : %v", debugInfo.Spec.LocalPort)
}
} else {
return fmt.Errorf("debug is not running for the component %v", o.Context.EnvSpecificInfo.GetName())
}
return nil
}
// NewCmdInfo implements the debug info odo command
func NewCmdInfo(name, fullName string) *cobra.Command {
opts := NewInfoOptions()
cmd := &cobra.Command{
Use: name,
Short: "Displays debug info of a component",
Long: infoLong,
Example: infoExample,
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(opts, cmd, args)
},
}
odoutil.AddContextFlag(cmd, &opts.contextFlag)
return cmd
}

View File

@@ -1,170 +0,0 @@
package debug
import (
"fmt"
"net"
"os"
"os/signal"
"strconv"
"syscall"
"github.com/redhat-developer/odo/pkg/debug"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/util"
"github.com/spf13/cobra"
k8sgenclioptions "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubectl/pkg/util/templates"
)
const (
// DefaultDebugPort is the default port used for debugging on remote pod
DefaultDebugPort = 5858
)
// PortForwardOptions contains all the options for running the port-forward cli command.
type PortForwardOptions struct {
// Context
*genericclioptions.Context
// Flags
contextFlag string
localPortFlag int
// PortPair is the combination of local and remote port in the format "local:remote"
PortPair string
// Port forwarder backend
PortForwarder *debug.DefaultPortForwarder
// StopChannel is used to stop port forwarding
StopChannel chan struct{}
// ReadChannel is used to receive status of port forwarding ( ready or not ready )
ReadyChannel chan struct{}
}
var (
portforwardLong = templates.LongDesc(`Forward a local port to a remote port on the pod where the application is listening for a debugger. By default the local port and the remote port will be same. To change the local port you can use --local-port argument and to change the remote port use "odo env set DebugPort <port>"
`)
portforwardExample = templates.Examples(`
# Listen on default port and forwarding to the default port in the pod
odo debug port-forward
# Listen on the 5000 port locally, forwarding to default port in the pod
odo debug port-forward --local-port 5000
`)
)
const (
portforwardCommandName = "port-forward"
)
// NewPortForwardOptions returns the PortForwardOptions struct
func NewPortForwardOptions() *PortForwardOptions {
return &PortForwardOptions{}
}
// Complete completes all the required options for port-forward cmd.
func (o *PortForwardOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline))
if err != nil {
return err
}
remotePort := o.Context.EnvSpecificInfo.GetDebugPort()
// try to listen on the given local port and check if the port is free or not
addressLook := "localhost:" + strconv.Itoa(o.localPortFlag)
listener, err := net.Listen("tcp", addressLook)
if err != nil {
// if the local-port flag is set by the user, return the error and stop execution
if cmdline.IsFlagSet("local-port") {
return err
}
// else display a error message and auto select a new free port
log.Errorf("the local debug port %v is not free, cause: %v", o.localPortFlag, err)
o.localPortFlag, err = util.HTTPGetFreePort()
if err != nil {
return err
}
log.Infof("The local port %v is auto selected", o.localPortFlag)
} else {
err = listener.Close()
if err != nil {
return err
}
}
o.PortPair = fmt.Sprintf("%d:%d", o.localPortFlag, remotePort)
// Using Discard streams because nothing important is logged
o.PortForwarder = debug.NewDefaultPortForwarder(o.Context.EnvSpecificInfo.GetName(), o.Context.GetApplication(), o.Context.EnvSpecificInfo.GetNamespace(), o.KClient, k8sgenclioptions.NewTestIOStreamsDiscard())
o.StopChannel = make(chan struct{}, 1)
o.ReadyChannel = make(chan struct{})
return nil
}
// Validate validates all the required options for port-forward cmd.
func (o PortForwardOptions) Validate() error {
if len(o.PortPair) < 1 {
return fmt.Errorf("ports cannot be empty")
}
return nil
}
// Run implements all the necessary functionality for port-forward cmd.
func (o PortForwardOptions) Run() error {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
defer signal.Stop(signals)
defer os.RemoveAll(debug.GetDebugInfoFilePath(o.Context.EnvSpecificInfo.GetName(), o.Context.GetApplication(), o.Context.EnvSpecificInfo.GetNamespace()))
go func() {
<-signals
if o.StopChannel != nil {
close(o.StopChannel)
}
}()
err := debug.CreateDebugInfoFile(o.PortForwarder, o.PortPair)
if err != nil {
return err
}
devfilePath := location.DevfileLocation(o.contextFlag)
return o.PortForwarder.ForwardPorts(o.PortPair, o.StopChannel, o.ReadyChannel, util.CheckPathExists(devfilePath))
}
// NewCmdPortForward implements the port-forward odo command
func NewCmdPortForward(name, fullName string) *cobra.Command {
opts := NewPortForwardOptions()
cmd := &cobra.Command{
Use: name,
Short: "Forward one or more local ports to a pod",
Long: portforwardLong,
Example: portforwardExample,
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(opts, cmd, args)
},
}
odoutil.AddContextFlag(cmd, &opts.contextFlag)
cmd.Flags().IntVarP(&opts.localPortFlag, "local-port", "l", DefaultDebugPort, "Set the local port")
return cmd
}

View File

@@ -1,68 +0,0 @@
package env
import (
"fmt"
"strings"
"github.com/redhat-developer/odo/pkg/odo/util"
genericUtil "github.com/redhat-developer/odo/pkg/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
// RecommendedCommandName is the recommended env command name
const RecommendedCommandName = "env"
const (
nameParameter = "Name"
nameParameterDescription = "Use this value to set component name"
projectParameter = "Project"
projectParameterDescription = "Use this value to set component project"
debugportParameter = "DebugPort"
debugportParameterDescription = "Use this value to set component debug port"
)
var envLongDesc = ktemplates.LongDesc(`Modifies odo specific configuration settings within environment file`)
// NewCmdEnv implements the environment configuration command
func NewCmdEnv(name, fullName string) *cobra.Command {
envViewCmd := NewCmdView(viewCommandName, util.GetFullName(fullName, viewCommandName))
envSetCmd := NewCmdSet(setCommandName, util.GetFullName(fullName, setCommandName))
envUnsetCmd := NewCmdUnset(unsetCommandName, util.GetFullName(fullName, unsetCommandName))
envCmd := &cobra.Command{
Use: name,
Short: "Change or view environment configuration",
Long: envLongDesc,
Example: fmt.Sprintf("%s\n\n%s\n\n%s",
envViewCmd.Example,
envSetCmd.Example,
envUnsetCmd.Example,
),
}
envCmd.AddCommand(envViewCmd, envSetCmd, envUnsetCmd)
envCmd.SetUsageTemplate(util.CmdUsageTemplate)
envCmd.Annotations = map[string]string{"command": "main"}
return envCmd
}
func printSupportedParameters(supportedParameters map[string]string) string {
output := "\n\nAvailable parameters:\n"
for _, parameter := range genericUtil.GetSortedKeys(supportedParameters) {
output = fmt.Sprintf("%s %s: %s\n", output, parameter, supportedParameters[parameter])
}
return output
}
func isSupportedParameter(parameter string, supportedParameters map[string]string) bool {
for supportedParameter := range supportedParameters {
if strings.EqualFold(supportedParameter, parameter) {
return true
}
}
return false
}

View File

@@ -1,61 +0,0 @@
package env
import (
"reflect"
"sort"
"strings"
"testing"
)
func TestPrintSupportedParameters(t *testing.T) {
supportedSetParameters := map[string]string{
nameParameter: nameParameterDescription,
projectParameter: projectParameterDescription,
debugportParameter: debugportParameterDescription,
}
wantSetParameters := `Available parameters:
DebugPort: Use this value to set component debug port
Name: Use this value to set component name
Project: Use this value to set component project`
supportedUnsetParameters := map[string]string{
debugportParameter: debugportParameterDescription,
}
wantUnsetParameters := `Available parameters:
DebugPort: Use this value to set component debug port`
tests := []struct {
name string
supportedParameters map[string]string
want string
}{
{
name: "Case 1: Test print supported set parameters",
supportedParameters: supportedSetParameters,
want: wantSetParameters,
},
{
name: "Case 2: Test print supported unset parameters",
supportedParameters: supportedUnsetParameters,
want: wantUnsetParameters,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := strings.TrimSpace(printSupportedParameters(tt.supportedParameters))
gotStrings := strings.Split(got, "\n")
wantStrings := strings.Split(tt.want, "\n")
sort.Strings(gotStrings)
sort.Strings(wantStrings)
if !reflect.DeepEqual(wantStrings, gotStrings) {
t.Errorf("\nGot: %s\nWant: %s", got, tt.want)
}
})
}
}

138
pkg/odo/cli/env/set.go vendored
View File

@@ -1,138 +0,0 @@
package env
import (
"fmt"
"strings"
"github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const setCommandName = "set"
var (
setLongDesc = ktemplates.LongDesc(`
Set an individual value in the odo environment file
`)
setExample = ktemplates.Examples(`
# Set an individual value in the odo environment file
%[1]s %[2]s myNodejs
%[1]s %[3]s myProject
%[1]s %[4]s 8888
`)
)
var (
supportedSetParameters = map[string]string{
nameParameter: nameParameterDescription,
projectParameter: projectParameterDescription,
debugportParameter: debugportParameterDescription,
}
)
// SetOptions encapsulates the options for the command
type SetOptions struct {
// Env context
cfg *envinfo.EnvSpecificInfo
// Parameters
paramName string
paramValue string
// Flags
contextFlag string
forceFlag bool
}
// NewSetOptions creates a new SetOptions instance
func NewSetOptions() *SetOptions {
return &SetOptions{}
}
// Complete completes SetOptions after they've been created
func (o *SetOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.cfg, err = envinfo.NewEnvSpecificInfo(o.contextFlag)
if err != nil {
return errors.Wrap(err, "failed to load environment file")
}
o.paramName = args[0]
o.paramValue = args[1]
return nil
}
// Validate validates the SetOptions based on completed values
func (o *SetOptions) Validate() (err error) {
if !o.cfg.Exists() {
return errors.Errorf("the context directory doesn't contain a component, please refer `odo create --help` to create a component")
}
if !isSupportedParameter(o.paramName, supportedSetParameters) {
return errors.Errorf("%q is not a valid parameter to set, please refer `odo env set --help` to set a valid parameter", o.paramName)
}
return nil
}
// Run contains the logic for the command
func (o *SetOptions) Run() (err error) {
if !o.forceFlag {
if isSet := o.cfg.IsSet(o.paramName); isSet {
if !ui.Proceed(fmt.Sprintf("%v is already set. Do you want to override it in the environment", o.paramName)) {
log.Info("Aborted by the user")
return nil
}
}
}
err = o.cfg.SetConfiguration(strings.ToLower(o.paramName), o.paramValue)
if err != nil {
return err
}
log.Info("Environment was successfully updated")
if strings.ToLower(o.paramName) == "name" || strings.ToLower(o.paramName) == "project" {
log.Warningf("Updated %q would create a new component", o.paramName)
}
return nil
}
// NewCmdSet implements the env set odo command
func NewCmdSet(name, fullName string) *cobra.Command {
o := NewSetOptions()
envSetCmd := &cobra.Command{
Use: name,
Short: "Set a value in odo environment file",
Long: setLongDesc + printSupportedParameters(supportedSetParameters),
Example: fmt.Sprintf(fmt.Sprint(setExample), fullName,
envinfo.Name, envinfo.Project, envinfo.DebugPort),
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return fmt.Errorf("please provide a parameter name and value")
} else if len(args) > 2 {
return fmt.Errorf("only one value per parameter is allowed")
} else {
return nil
}
}, Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
envSetCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Don't ask for confirmation, set the environment directly")
envSetCmd.Flags().StringVar(&o.contextFlag, "context", "", "Use given context directory as a source for component settings")
return envSetCmd
}

View File

@@ -1,129 +0,0 @@
package env
import (
"fmt"
"strings"
"github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/pkg/errors"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const unsetCommandName = "unset"
var (
unsetLongDesc = ktemplates.LongDesc(`
Unset an individual value in the odo environment file
`)
unsetExample = ktemplates.Examples(`
# Unset an individual value in the environment file
%[1]s %[2]s
`)
)
var (
supportedUnsetParameters = map[string]string{
debugportParameter: debugportParameterDescription,
}
)
// UnsetOptions encapsulates the options for the command
type UnsetOptions struct {
// Env context
cfg *envinfo.EnvSpecificInfo
// Parameters
paramName string
// Flags
contextFlag string
forceFlag bool
}
// NewUnsetOptions creates a new UnsetOptions instance
func NewUnsetOptions() *UnsetOptions {
return &UnsetOptions{}
}
// Complete completes UnsetOptions after they've been created
func (o *UnsetOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.cfg, err = envinfo.NewEnvSpecificInfo(o.contextFlag)
if err != nil {
return errors.Wrap(err, "failed to load environment file")
}
o.paramName = args[0]
return nil
}
// Validate validates the UnsetOptions based on completed values
func (o *UnsetOptions) Validate() (err error) {
if !o.cfg.Exists() {
return errors.Errorf("the context directory doesn't contain a component, please refer `odo create --help` to create a component")
}
if !isSupportedParameter(o.paramName, supportedUnsetParameters) {
return errors.Errorf("%q is not a valid parameter to unset, please refer `odo env unset --help` to unset a valid parameter", o.paramName)
}
return nil
}
// Run contains the logic for the command
func (o *UnsetOptions) Run() (err error) {
if !o.forceFlag {
if isSet := o.cfg.IsSet(o.paramName); isSet {
if !ui.Proceed(fmt.Sprintf("Do you want to unset %s in the environment", o.paramName)) {
log.Infof("Aborted by the user")
return nil
}
} else {
return errors.New("environment already unset, cannot unset a environment which is not set")
}
}
err = o.cfg.DeleteConfiguration(strings.ToLower(o.paramName))
if err != nil {
return err
}
log.Info("Environment was successfully updated")
return nil
}
// NewCmdUnset implements the environment unset odo command
func NewCmdUnset(name, fullName string) *cobra.Command {
o := NewUnsetOptions()
envUnsetCmd := &cobra.Command{
Use: name,
Short: "Unset a value in odo environment file",
Long: unsetLongDesc + printSupportedParameters(supportedUnsetParameters),
Example: fmt.Sprintf(fmt.Sprint(unsetExample), fullName, envinfo.DebugPort),
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("please provide a parameter name")
} else if len(args) > 1 {
return fmt.Errorf("only one parameter is allowed")
} else {
return nil
}
}, Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
envUnsetCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Don't ask for confirmation, unsetting the environment directly")
envUnsetCmd.Flags().StringVar(&o.contextFlag, "context", "", "Use given context directory as a source for component settings")
return envUnsetCmd
}

View File

@@ -1,93 +0,0 @@
package env
import (
"fmt"
"os"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/envinfo"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const viewCommandName = "view"
var (
viewLongDesc = ktemplates.LongDesc(`
View current values in odo environment file
`)
viewExample = ktemplates.Examples(`
# For viewing the current environment configuration settings
%[1]s
`)
)
// ViewOptions encapsulates the options for the command
type ViewOptions struct {
// Env context
cfg *envinfo.EnvSpecificInfo
// Flags
contextFlag string
}
// NewViewOptions creates a new ViewOptions instance
func NewViewOptions() *ViewOptions {
return &ViewOptions{}
}
// Complete completes ViewOptions after they've been created
func (o *ViewOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.cfg, err = envinfo.NewEnvSpecificInfo(o.contextFlag)
if err != nil {
return errors.Wrap(err, "failed to load environment file")
}
return nil
}
// Validate validates the ViewOptions based on completed values
func (o *ViewOptions) Validate() (err error) {
if !o.cfg.Exists() {
return errors.Errorf("the context directory doesn't contain a component, please refer `odo create --help` on how to create a component")
}
return nil
}
// Run contains the logic for the command
func (o *ViewOptions) Run() (err error) {
info := envinfo.NewInfo(o.cfg.GetComponentSettings())
if log.IsJSON() {
machineoutput.OutputSuccess(info)
return
}
info.Output(os.Stdout)
return nil
}
// NewCmdView implements the env view odo command
func NewCmdView(name, fullName string) *cobra.Command {
o := NewViewOptions()
envViewCmd := &cobra.Command{
Use: name,
Short: "View current values in odo environment file",
Long: viewLongDesc,
Example: fmt.Sprintf(fmt.Sprint(viewExample), fullName),
Annotations: map[string]string{"machineoutput": "json"},
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
envViewCmd.Flags().StringVar(&o.contextFlag, "context", "", "Use given context directory as a source for component settings")
return envViewCmd
}

View File

@@ -1,9 +0,0 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
approvers:
- dharmit
- mik-dass
reviewers:
- dharmit
- mik-dass

View File

@@ -1,144 +0,0 @@
package service
import (
"fmt"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/util"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const (
createRecommendedCommandName = "create"
)
var (
createOperatorExample = ktemplates.Examples(`
# Create new EtcdCluster service from etcdoperator.v0.9.4 operator.
%[1]s etcdoperator.v0.9.4/EtcdCluster
# Create new EtcdCluster service from etcdoperator.v0.9.4 operator and puts the service definition in the devfile instead of a separate file.
%[1]s etcdoperator.v0.9.4/EtcdCluster --inlined`)
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 and deploy it on Kubernetes or OpenShift.
Service creation can be performed from a valid component directory (one containing a devfile.yaml) only.
To create the service from outside a component directory, specify path to a valid component directory using "--context" flag.
When creating a service using Operator Hub, provide a service name along with Operator name.
For a full list of service types, use: 'odo catalog list services'`)
)
// CreateOptions encapsulates the options for the odo service create command
type CreateOptions struct {
// Context
*genericclioptions.Context
// Flags
parametersFlag []string
waitFlag bool
contextFlag string
DryRunFlag bool
fromFileFlag string
inlinedFlag bool
// ServiceType corresponds to the service class name
ServiceType string
// ServiceName is how the service will be named and known by odo
ServiceName string
// ParametersMap is populated from the flag-provided values (parameters) and/or the interactive mode and is the expected format by the business logic
ParametersMap map[string]string
// interactive specifies whether the command operates in interactive mode or not
interactive bool
// CmdFullName records the command's full name
CmdFullName string
// Backend is the service provider backend providing the service requested by the user
Backend ServiceProviderBackend
}
// NewCreateOptions creates a new CreateOptions instance
func NewCreateOptions() *CreateOptions {
return &CreateOptions{}
}
// Complete completes CreateOptions after they've been created
func (o *CreateOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
}
// we convert the param list provided in the format of key=value list
// to a map
o.ParametersMap, err = util.MapFromParameters(o.parametersFlag)
if err != nil {
return err
}
err = validDevfileDirectory(o.contextFlag)
if err != nil {
return err
}
// if no args are provided and if request is not from file, user wants interactive mode
if o.fromFileFlag == "" && len(args) == 0 {
return fmt.Errorf("odo doesn't support interactive mode for creating Operator backed service")
}
o.Backend = NewOperatorBackend()
o.interactive = false
return o.Backend.CompleteServiceCreate(o, args)
}
// Validate validates the CreateOptions based on completed values
func (o *CreateOptions) Validate() (err error) {
return o.Backend.ValidateServiceCreate(o)
}
// Run contains the logic for the odo service create command
func (o *CreateOptions) Run() (err error) {
err = o.Backend.RunServiceCreate(o)
if err != nil {
return fmt.Errorf("service %q already exists in configuration", o.ServiceName)
}
// Information on what to do next; don't do this if "--dry-run" was requested as it gets appended to the file
if !o.DryRunFlag {
log.Info("Successfully added service to the configuration; do 'odo push' to create service on the cluster")
}
return nil
}
// NewCmdServiceCreate implements the odo service create command.
func NewCmdServiceCreate(name, fullName string) *cobra.Command {
o := NewCreateOptions()
o.CmdFullName = fullName
serviceCreateCmd := &cobra.Command{
Use: name + " <operator_type>/<crd_name> [service_name] [flags]",
Short: createShortDesc,
Long: createLongDesc,
Example: fmt.Sprintf(createOperatorExample, fullName),
Args: cobra.RangeArgs(0, 2),
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
serviceCreateCmd.Flags().BoolVar(&o.inlinedFlag, "inlined", false, "Puts the service definition in the devfile instead of a separate file")
serviceCreateCmd.Flags().BoolVar(&o.DryRunFlag, "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.fromFileFlag, "from-file", "", "Path to the file containing yaml specification to use to start operator backed service")
serviceCreateCmd.Flags().StringArrayVarP(&o.parametersFlag, "parameters", "p", []string{}, "Parameters to be used to create Operator backed service where a parameter is expressed as <key>=<value")
serviceCreateCmd.Flags().BoolVarP(&o.waitFlag, "wait", "w", false, "Wait until the service is ready")
odoutil.AddContextFlag(serviceCreateCmd, &o.contextFlag)
return serviceCreateCmd
}

View File

@@ -1,117 +0,0 @@
package service
import (
"fmt"
"strings"
"github.com/redhat-developer/odo/pkg/service"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
"k8s.io/klog"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const deleteRecommendedCommandName = "delete"
var (
deleteExample = ktemplates.Examples(`
# Delete the service named 'mysql-persistent'
%[1]s mysql-persistent`)
deleteLongDesc = ktemplates.LongDesc(`
Delete an existing service`)
)
// DeleteOptions encapsulates the options for the odo service delete command
type DeleteOptions struct {
// Context
*genericclioptions.Context
// Parameters
serviceName string
// Flags
forceFlag bool
contextFlag string
// Backend is the service provider backend that was used to create the service
Backend ServiceProviderBackend
}
// NewDeleteOptions creates a new DeleteOptions instance
func NewDeleteOptions() *DeleteOptions {
return &DeleteOptions{}
}
// Complete completes DeleteOptions after they've been created
func (o *DeleteOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
}
err = validDevfileDirectory(o.contextFlag)
if err != nil {
return err
}
o.serviceName = args[0]
_, _, err = service.SplitServiceKindName(o.serviceName)
if err != nil {
return fmt.Errorf("invalid service name")
}
o.Backend = NewOperatorBackend()
return nil
}
// Validate validates the DeleteOptions based on completed values
func (o *DeleteOptions) Validate() (err error) {
svcDefined, err := o.Backend.ServiceDefined(o.Context, o.serviceName)
if err != nil {
return err
}
if !svcDefined {
return fmt.Errorf("couldn't find service named %q. Refer %q to see list of defined services", o.serviceName, "odo service list")
}
return nil
}
// Run contains the logic for the odo service delete command
func (o *DeleteOptions) Run() (err error) {
if o.forceFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete %v", o.serviceName)) {
err = o.Backend.DeleteService(o, o.serviceName, o.GetApplication())
if err != nil {
return err
}
log.Infof("Service %q has been successfully deleted; do 'odo push' to delete service from the cluster", o.serviceName)
} else {
log.Errorf("Aborting deletion of service: %v", o.serviceName)
}
return nil
}
// NewCmdServiceDelete implements the odo service delete command.
func NewCmdServiceDelete(name, fullName string) *cobra.Command {
o := NewDeleteOptions()
serviceDeleteCmd := &cobra.Command{
Use: name + " <service_name>",
Short: "Delete an existing service",
Long: deleteLongDesc,
Example: fmt.Sprintf(deleteExample, fullName),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
klog.V(4).Infof("service delete called\n args: %#v", strings.Join(args, " "))
genericclioptions.GenericRun(o, cmd, args)
},
}
serviceDeleteCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Delete service without prompting")
odoutil.AddContextFlag(serviceDeleteCmd, &o.contextFlag)
return serviceDeleteCmd
}

View File

@@ -1,108 +0,0 @@
package service
import (
"fmt"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/service"
svc "github.com/redhat-developer/odo/pkg/service"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const describeRecommendedCommandName = "describe"
var (
describeExample = ktemplates.Examples(`
# Describe the service named 'mysql-persistent'
%[1]s mysql-persistent`)
describeLongDesc = ktemplates.LongDesc(`
Describe an existing service, either defined locally or deployed to the cluster`)
)
// DescribeOptions encapsulates the options for the odo service describe command
type DescribeOptions struct {
// Context
*genericclioptions.Context
// Parameters
serviceName string
// Flags
contextFlag string
// Backend is the service provider backend that was used to create the service
Backend ServiceProviderBackend
}
// NewDescribeOptions creates a new DescribeOptions instance
func NewDescribeOptions() *DescribeOptions {
return &DescribeOptions{}
}
func (o *DescribeOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
}
err = validDevfileDirectory(o.contextFlag)
if err != nil {
return err
}
o.serviceName = args[0]
_, _, err = service.SplitServiceKindName(o.serviceName)
if err != nil {
return fmt.Errorf("invalid service name")
}
o.Backend = NewOperatorBackend()
return nil
}
// Validate validates the DescribeOptions based on completed values
func (o *DescribeOptions) Validate() error {
svcDefined, err := o.Backend.ServiceDefined(o.Context, o.serviceName)
if err != nil {
return err
}
svcDeployed, err := svc.OperatorSvcExists(o.KClient, o.serviceName)
if err != nil {
return err
}
if !svcDefined && !svcDeployed {
return fmt.Errorf("couldn't find service named %q. Refer %q to see list of defined services", o.serviceName, "odo service list")
}
return nil
}
// Run contains the logic for the odo service describe command
func (o *DescribeOptions) Run() error {
return o.Backend.DescribeService(o, o.serviceName, o.GetApplication())
}
// NewCmdDescribe implements the describe odo command
func NewCmdServiceDescribe(name, fullName string) *cobra.Command {
do := NewDescribeOptions()
var describeCmd = &cobra.Command{
Use: fmt.Sprintf("%s [service_name]", name),
Short: "Describe an existing service",
Long: describeLongDesc,
Example: fmt.Sprintf(describeExample, fullName),
Args: cobra.ExactArgs(1),
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(do, cmd, args)
},
}
odoutil.AddContextFlag(describeCmd, &do.contextFlag)
return describeCmd
}

View File

@@ -1,17 +0,0 @@
package service
import (
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
)
// ServiceProviderBackend is implemented by the backends supported by odo
// It is used in "odo service create" and "odo service delete"
type ServiceProviderBackend interface {
CompleteServiceCreate(options *CreateOptions, args []string) error
ValidateServiceCreate(options *CreateOptions) error
RunServiceCreate(options *CreateOptions) error
ServiceDefined(context *genericclioptions.Context, name string) (bool, error)
DeleteService(options *DeleteOptions, serviceName, app string) error
DescribeService(options *DescribeOptions, serviceName, app string) error
}

View File

@@ -1,86 +0,0 @@
package service
import (
"fmt"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const listRecommendedCommandName = "list"
var (
listExample = ktemplates.Examples(`
# List all services in the application
%[1]s`)
listLongDesc = ktemplates.LongDesc(`
List all services in the current application
`)
)
// ServiceListOptions encapsulates the options for the odo service list command
type ServiceListOptions struct {
// Context
*genericclioptions.Context
// Flags
contextFlag string
// If true, Operator Hub is installed on the cluster
csvSupport bool
}
// NewServiceListOptions creates a new ServiceListOptions instance
func NewServiceListOptions() *ServiceListOptions {
return &ServiceListOptions{}
}
// Complete completes ServiceListOptions after they've been created
func (o *ServiceListOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
}
o.csvSupport, err = o.KClient.IsCSVSupported()
if err != nil {
return err
}
if !o.csvSupport {
return fmt.Errorf("failed to list Operator backed services, make sure you have installed the Operators on the cluster")
}
return nil
}
// Validate validates the ServiceListOptions based on completed values
func (o *ServiceListOptions) Validate() (err error) {
return nil
}
// Run contains the logic for the odo service list command
func (o *ServiceListOptions) Run() (err error) {
return o.listOperatorServices()
}
// NewCmdServiceList implements the odo service list command.
func NewCmdServiceList(name, fullName string) *cobra.Command {
o := NewServiceListOptions()
serviceListCmd := &cobra.Command{
Use: name,
Short: "List all services in the current application",
Long: listLongDesc,
Example: fmt.Sprintf(listExample, fullName),
Args: cobra.NoArgs,
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
odoutil.AddContextFlag(serviceListCmd, &o.contextFlag)
return serviceListCmd
}

View File

@@ -1,218 +0,0 @@
package service
import (
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"time"
applabels "github.com/redhat-developer/odo/pkg/application/labels"
cmplabels "github.com/redhat-developer/odo/pkg/component/labels"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
svc "github.com/redhat-developer/odo/pkg/service"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const ServiceItemKind = "Service"
type clusterInfo struct {
Labels map[string]string `json:"labels"`
CreationTimestamp time.Time `json:"creationTimestamp"`
}
type serviceItem struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
ClusterInfo *clusterInfo `json:"clusterInfo,omitempty"`
InDevfile bool `json:"inDevfile"`
Deployed bool `json:"deployed"`
Manifest map[string]interface{} `json:"manifest"`
}
type serviceItemList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []serviceItem `json:"items"`
}
func NewServiceItem(name string) *serviceItem {
return &serviceItem{
TypeMeta: metav1.TypeMeta{
Kind: ServiceItemKind,
APIVersion: machineoutput.APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
}
// listOperatorServices lists the operator backed services
// - deployed in the cluster
// - defined in the current devfile
func (o *ServiceListOptions) listOperatorServices() (err error) {
// get the services deployed
var clusterList []unstructured.Unstructured
clusterList, failedListingCR, err := svc.ListOperatorServices(o.KClient)
if err != nil {
return err
}
// get the services defined in the devfile
// and the name of the component of the devfile
var devfileList map[string]unstructured.Unstructured
var devfileComponent string
if o.EnvSpecificInfo != nil {
devfileList, err = svc.ListDevfileServices(o.KClient, o.EnvSpecificInfo.GetDevfileObj(), o.contextFlag)
if err != nil {
return fmt.Errorf("error reading devfile")
}
devfileComponent = o.EnvSpecificInfo.GetComponentSettings().Name
}
servicesItems := mixServices(clusterList, devfileList)
if len(servicesItems.Items) == 0 {
if len(failedListingCR) > 0 {
fmt.Printf("Failed to fetch services for operator(s): %q\n\n", strings.Join(failedListingCR, ", "))
}
return fmt.Errorf("no operator backed services found in namespace: %s", o.KClient.GetCurrentNamespace())
}
if log.IsJSON() {
machineoutput.OutputSuccess(servicesItems)
return nil
}
// output result
w := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent)
fmt.Fprintln(w, "NAME", "\t", "MANAGED BY ODO", "\t", "STATE", "\t", "AGE")
for i := range servicesItems.Items {
item := servicesItems.Items[i]
managedByOdo, state, duration := getTabularInfo(&item, devfileComponent)
fmt.Fprintln(w, item.Name, "\t", managedByOdo, "\t", state, "\t", duration)
}
w.Flush()
if len(failedListingCR) > 0 {
fmt.Printf("\nFailed to fetch services for operator(s): %q\n", strings.Join(failedListingCR, ", "))
}
return nil
}
// mixServices returns a structure containing both the services in cluster and defined in devfile
func mixServices(clusterList []unstructured.Unstructured, devfileList map[string]unstructured.Unstructured) serviceItemList {
servicesItems := map[string]*serviceItem{}
for _, item := range clusterList {
if item.GetKind() == "ServiceBinding" {
continue
}
name := strings.Join([]string{item.GetKind(), item.GetName()}, "/")
if _, ok := servicesItems[name]; !ok {
servicesItems[name] = NewServiceItem(name)
}
servicesItems[name].Manifest = item.Object
servicesItems[name].Deployed = true
servicesItems[name].ClusterInfo = &clusterInfo{
Labels: item.GetLabels(),
CreationTimestamp: item.GetCreationTimestamp().Time,
}
}
for name, manifest := range devfileList {
if manifest.GetKind() == "ServiceBinding" {
continue
}
if _, ok := servicesItems[name]; !ok {
servicesItems[name] = NewServiceItem(name)
}
servicesItems[name].InDevfile = true
if !servicesItems[name].Deployed {
servicesItems[name].Manifest = manifest.Object
}
}
return serviceItemList{
TypeMeta: metav1.TypeMeta{
Kind: "List",
APIVersion: machineoutput.APIVersion,
},
Items: getOrderedServices(servicesItems),
}
}
// getOrderedServices returns the services as a slice, ordered by name
func getOrderedServices(items map[string]*serviceItem) []serviceItem {
orderedNames := getOrderedServicesNames(items)
result := make([]serviceItem, len(items))
i := 0
for _, name := range orderedNames {
result[i] = *items[name]
i++
}
return result
}
// getOrderedServicesNames returns the names of the services ordered in alphabetic order
func getOrderedServicesNames(items map[string]*serviceItem) []string {
orderedNames := make([]string, len(items))
i := 0
for name := range items {
orderedNames[i] = name
i++
}
sort.Strings(orderedNames)
return orderedNames
}
// getTabularInfo returns information to be displayed in the output for a specific service and a specific current devfile component
func getTabularInfo(serviceItem *serviceItem, devfileComponent string) (managedByOdo, state, duration string) {
clusterItem := serviceItem.ClusterInfo
inDevfile := serviceItem.InDevfile
if clusterItem != nil {
// service deployed into cluster
var component string
labels := clusterItem.Labels
isManagedByOdo := labels[applabels.ManagedBy] == "odo"
if isManagedByOdo {
component = labels[cmplabels.ComponentLabel]
managedByOdo = fmt.Sprintf("Yes (%s)", component)
} else {
managedByOdo = "No"
}
duration = time.Since(clusterItem.CreationTimestamp).Truncate(time.Second).String()
if inDevfile {
// service deployed into cluster and defined in devfile
state = "Pushed"
} else {
// service deployed into cluster and not defined in devfile
if isManagedByOdo {
if devfileComponent == component {
state = "Deleted locally"
} else {
state = ""
}
} else {
state = ""
}
}
} else {
if inDevfile {
// service not deployed into cluster and defined in devfile
state = "Not pushed"
managedByOdo = fmt.Sprintf("Yes (%s)", devfileComponent)
} else {
// service not deployed into cluster and not defined in devfile
// should not happen
state = "Err!"
managedByOdo = "Err!"
}
}
return
}

View File

@@ -1,277 +0,0 @@
package service
import (
"reflect"
"testing"
"time"
"github.com/ghodss/yaml"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestGetOrderedServicesNames(t *testing.T) {
tests := []struct {
name string
services map[string]*serviceItem
want []string
}{
{
name: "Unordered names",
services: map[string]*serviceItem{
"name3": nil,
"name1": nil,
"name2": nil,
},
want: []string{"name1", "name2", "name3"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getOrderedServicesNames(tt.services)
if !reflect.DeepEqual(result, tt.want) {
t.Errorf("Failed %s: got: %q, want: %q", t.Name(), result, tt.want)
}
})
}
}
type getTabularInfoResult struct {
managedByOdo string
state string
durationContent bool
}
func TestGetTabularInfo(t *testing.T) {
tests := []struct {
name string
service *serviceItem
devfileComponent string
want getTabularInfoResult
}{
{
name: "case 1: service in cluster managed by current devfile",
service: &serviceItem{
ClusterInfo: &clusterInfo{
Labels: map[string]string{
"app.kubernetes.io/managed-by": "odo",
"app.kubernetes.io/instance": "component1",
},
},
InDevfile: true,
},
devfileComponent: "component1",
want: getTabularInfoResult{
managedByOdo: "Yes (component1)",
state: "Pushed",
durationContent: true,
},
},
{
name: "case 2: service in cluster not managed by Odo",
service: &serviceItem{
ClusterInfo: &clusterInfo{
Labels: map[string]string{},
},
InDevfile: false,
},
devfileComponent: "component1",
want: getTabularInfoResult{
managedByOdo: "No",
state: "",
durationContent: true,
},
},
{
name: "case 3: service in cluster absent from current devfile",
service: &serviceItem{
ClusterInfo: &clusterInfo{
Labels: map[string]string{
"app.kubernetes.io/managed-by": "odo",
"app.kubernetes.io/instance": "component1",
},
},
InDevfile: false,
},
devfileComponent: "component1",
want: getTabularInfoResult{
managedByOdo: "Yes (component1)",
state: "Deleted locally",
durationContent: true,
},
},
{
name: "case 4: service in cluster maaged by another devfile",
service: &serviceItem{
ClusterInfo: &clusterInfo{
Labels: map[string]string{
"app.kubernetes.io/managed-by": "odo",
"app.kubernetes.io/instance": "component2",
},
},
InDevfile: false,
},
devfileComponent: "component1",
want: getTabularInfoResult{
managedByOdo: "Yes (component2)",
state: "",
durationContent: true,
},
},
{
name: "case 5: service defined in devfile, not in cluster",
service: &serviceItem{
ClusterInfo: nil,
InDevfile: true,
},
devfileComponent: "component1",
want: getTabularInfoResult{
managedByOdo: "Yes (component1)",
state: "Not pushed",
durationContent: false,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
managedByOdo, state, duration := getTabularInfo(tt.service, tt.devfileComponent)
if managedByOdo != tt.want.managedByOdo {
t.Errorf("Failed %s: managedByOdo got: %q, want: %q", t.Name(), managedByOdo, tt.want.managedByOdo)
}
if state != tt.want.state {
t.Errorf("Failed %s: state got: %q, want: %q", t.Name(), state, tt.want.state)
}
if len(duration) > 0 != tt.want.durationContent {
t.Errorf("Failed %s: duration content got: %v, want: %v", t.Name(), len(duration) > 0, tt.want.durationContent)
}
})
}
}
func TestMixServices(t *testing.T) {
atime, _ := time.Parse(time.RFC3339, "2021-06-02T08:39:20Z00:00")
tests := []struct {
name string
clusterListInlined []string
devfileList []string
want []serviceItem
}{
{
name: "two in cluster and two in devfile, including one in common",
clusterListInlined: []string{`
kind: kind1
metadata:
name: name1
labels:
app.kubernetes.io/managed-by: odo
app.kubernetes.io/instance: component1
creationTimestamp: 2021-06-02T08:39:20Z00:00
spec:
field1: value1`,
`
kind: kind2
metadata:
name: name2
labels:
app.kubernetes.io/managed-by: odo
app.kubernetes.io/instance: component2
creationTimestamp: 2021-06-02T08:39:20Z00:00
spec:
field2: value2`},
devfileList: []string{`
kind: kind1
metadata:
name: name1
spec:
field1: value1`, `
kind: kind3
metadata:
name: name3
spec:
field3: value3`},
want: []serviceItem{
{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "odo.dev/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "kind1/name1",
},
ClusterInfo: &clusterInfo{
Labels: map[string]string{
"app.kubernetes.io/managed-by": "odo",
"app.kubernetes.io/instance": "component1",
},
CreationTimestamp: atime,
},
InDevfile: true,
Deployed: true,
},
{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "odo.dev/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "kind2/name2",
},
ClusterInfo: &clusterInfo{
Labels: map[string]string{
"app.kubernetes.io/managed-by": "odo",
"app.kubernetes.io/instance": "component2",
},
CreationTimestamp: atime,
},
InDevfile: false,
Deployed: true,
},
{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "odo.dev/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "kind3/name3",
},
ClusterInfo: nil,
InDevfile: true,
Deployed: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
usCluster := make([]unstructured.Unstructured, len(tt.clusterListInlined))
for i, clusterInlined := range tt.clusterListInlined {
err := yaml.Unmarshal([]byte(clusterInlined), &usCluster[i])
if err != nil {
t.Errorf("Failed to unmarshal spec manifest %q: %u", clusterInlined, err)
}
}
usDevfiles := make(map[string]unstructured.Unstructured)
for _, devfile := range tt.devfileList {
usDevfile := unstructured.Unstructured{}
err := yaml.Unmarshal([]byte(devfile), &usDevfile)
if err != nil {
t.Errorf("Failed to unmarshal spec manifest %q, %u", devfile, err)
}
usDevfiles[usDevfile.GetKind()+"/"+usDevfile.GetName()] = usDevfile
}
result := mixServices(usCluster, usDevfiles)
for i := range result.Items {
if reflect.DeepEqual(result.Items[i].Manifest, unstructured.Unstructured{}) {
t.Errorf("Manifest is empty")
}
// do not check manifest content
result.Items[i].Manifest = unstructured.Unstructured{}.Object
}
if !reflect.DeepEqual(result.Items, tt.want) {
t.Errorf("Failed %s\n\ngot: %+v\n\nwant: %+v\n", t.Name(), result.Items, tt.want)
}
})
}
}

View File

@@ -1,385 +0,0 @@
/*
This file contains code for various service backends supported by odo. Different backends have different logics for
Complete, Validate and Run functions. These are covered in this file.
*/
package service
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"text/tabwriter"
"github.com/ghodss/yaml"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/devfile"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
svc "github.com/redhat-developer/odo/pkg/service"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var (
ErrNoMetadataName = errors.New("invalid \"metadata\" in the yaml; provide a valid \"metadata.name\"")
)
// CompleteServiceCreate contains logic to complete the "odo service create" call for the case of Operator backend
func (b *OperatorBackend) CompleteServiceCreate(o *CreateOptions, args []string) (err error) {
// since interactive mode is not supported for Operators yet, set it to false
o.interactive = false
// if user has just used "odo service create", simply return
if o.fromFileFlag == "" && len(args) == 0 {
return
}
// if user wants to create service from file and use a name given on CLI
if o.fromFileFlag != "" {
if len(args) == 1 {
o.ServiceName = args[0]
}
return
}
// split the name provided on CLI and populate servicetype & customresource
o.ServiceType, b.CustomResource, err = svc.SplitServiceKindName(args[0])
if err != nil {
return fmt.Errorf("invalid service name, use the format <operator-type>/<crd-name>")
}
// if two args are given, first is service type and second one is service name
if len(args) == 2 {
o.ServiceName = args[1]
}
return nil
}
func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) error {
u := unstructured.Unstructured{}
// if the user wants to create service from a file, we check for
// existence of file and validate if the requested operator and CR
// exist on the cluster
if o.fromFileFlag != "" {
if _, err := os.Stat(o.fromFileFlag); err != nil {
return errors.Wrap(err, "unable to find specified file")
}
// Parse the file to find Operator and CR info
fileContents, err := ioutil.ReadFile(o.fromFileFlag)
if err != nil {
return err
}
err = yaml.Unmarshal(fileContents, &u.Object)
if err != nil {
return err
}
gvk := u.GroupVersionKind()
b.group, b.version, b.kind = gvk.Group, gvk.Version, gvk.Kind
if u.GetName() == "" {
return ErrNoMetadataName
}
if o.ServiceName != "" && !o.DryRunFlag {
// First check if service with provided name already exists
svcFullName := strings.Join([]string{b.kind, o.ServiceName}, "/")
exists, e := svc.OperatorSvcExists(o.KClient, svcFullName)
if e != nil {
return e
}
if exists {
return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first", svcFullName)
}
u.SetName(o.ServiceName)
} else {
o.ServiceName = u.GetName()
}
csvPtr, err := o.KClient.GetCSVWithCR(u.GetKind())
if err != nil {
// error only occurs when OperatorHub is not installed.
// k8s doesn't have it installed by default but OCP does
return err
}
csv := *csvPtr
// CRD is valid. We can use it further to create a service from it.
b.CustomResourceDefinition = u.Object
// Validate spec
hasCR, cr := o.KClient.CheckCustomResourceInCSV(b.kind, &csv)
if !hasCR {
return fmt.Errorf("the %q resource doesn't exist in specified %q operator", b.CustomResource, b.group)
}
crd, err := o.KClient.GetCRDSpec(cr, b.group, b.kind)
if err != nil {
return err
}
err = validate.AgainstSchema(crd, u.Object["spec"], strfmt.Default)
if err != nil {
return err
}
} else if b.CustomResource != "" {
// make sure that CSV of the specified ServiceType exists
csv, err := o.KClient.GetClusterServiceVersion(o.ServiceType)
if err != nil {
// error only occurs when OperatorHub is not installed.
// k8s doesn't have it installed by default but OCP does
return err
}
b.group, b.version, b.resource, err = svc.GetGVRFromOperator(csv, b.CustomResource)
if err != nil {
return err
}
// if the service name is blank then we set it to custom resource name
if o.ServiceName == "" {
o.ServiceName = strings.ToLower(b.CustomResource)
}
hasCR, cr := o.KClient.CheckCustomResourceInCSV(b.CustomResource, &csv)
if !hasCR {
return fmt.Errorf("the %q resource doesn't exist in specified %q operator", b.CustomResource, b.group)
}
crd, err := o.KClient.GetCRDSpec(cr, b.group, b.CustomResource)
if err != nil {
return err
}
if len(o.parametersFlag) != 0 {
builtCRD, e := svc.BuildCRDFromParams(o.ParametersMap, crd, b.group, b.version, b.CustomResource)
if e != nil {
return e
}
u.Object = builtCRD
} else {
almExample, e := svc.GetAlmExample(csv, b.CustomResource, o.ServiceType)
if e != nil {
return e
}
u.Object = almExample
}
if o.ServiceName != "" && !o.DryRunFlag {
// First check if service with provided name already exists
svcFullName := strings.Join([]string{b.CustomResource, o.ServiceName}, "/")
exists, e := svc.OperatorSvcExists(o.KClient, svcFullName)
if e != nil {
return e
}
if exists {
return fmt.Errorf("service %q already exists; please provide a different name or delete the existing service first", svcFullName)
}
}
u.SetName(o.ServiceName)
if u.GetName() == "" {
return ErrNoMetadataName
}
// CRD is valid. We can use it further to create a service from it.
b.CustomResourceDefinition = u.Object
if o.ServiceName == "" {
o.ServiceName = u.GetName()
}
// Validate spec
err = validate.AgainstSchema(crd, u.Object["spec"], strfmt.Default)
if err != nil {
return err
}
} else {
// This block is executed only when user has neither provided a
// file nor a valid `odo service create <operator-name>` to start
// the service from an Operator. So we raise an error because the
// correct way is to execute:
// `odo service create <operator-name>/<crd-name>`
return fmt.Errorf("please use a valid command to start an Operator backed service; desired format: %q", "odo service create <operator-name>/<crd-name>")
}
return nil
}
func (b *OperatorBackend) RunServiceCreate(o *CreateOptions) (err error) {
s := &log.Status{}
// if cluster has resources of type CSV and o.CustomResource is not
// empty, we're expected to create an Operator backed service
if o.DryRunFlag {
// if it's dry run, only print the alm-example (o.CustomResourceDefinition) and exit
jsonCR, err := json.MarshalIndent(b.CustomResourceDefinition, "", " ")
if err != nil {
return err
}
// convert json to yaml
yamlCR, err := yaml.JSONToYAML(jsonCR)
if err != nil {
return err
}
log.Info(string(yamlCR))
return nil
} else {
if o.inlinedFlag {
crdYaml, err := yaml.Marshal(b.CustomResourceDefinition)
if err != nil {
return err
}
err = devfile.AddKubernetesComponentToDevfile(string(crdYaml), o.ServiceName, o.EnvSpecificInfo.GetDevfileObj())
if err != nil {
return err
}
} else {
crdYaml, err := yaml.Marshal(b.CustomResourceDefinition)
if err != nil {
return err
}
err = devfile.AddKubernetesComponent(string(crdYaml), o.ServiceName, o.contextFlag, o.EnvSpecificInfo.GetDevfileObj())
if err != nil {
return err
}
}
if log.IsJSON() {
svcFullName := strings.Join([]string{b.CustomResource, o.ServiceName}, "/")
svc := NewServiceItem(svcFullName)
svc.Manifest = b.CustomResourceDefinition
svc.InDevfile = true
machineoutput.OutputSuccess(svc)
}
}
s.End(true)
return
}
// ServiceDefined returns true if the service is defined in the devfile
func (b *OperatorBackend) ServiceDefined(ctx *genericclioptions.Context, name string) (bool, error) {
_, instanceName, err := svc.SplitServiceKindName(name)
if err != nil {
return false, err
}
return devfile.IsComponentDefined(instanceName, ctx.EnvSpecificInfo.GetDevfileObj())
}
func (b *OperatorBackend) DeleteService(o *DeleteOptions, name string, application string) error {
// "name" is of the form CR-Name/Instance-Name so we split it
_, instanceName, err := svc.SplitServiceKindName(name)
if err != nil {
return err
}
err = devfile.DeleteKubernetesComponentFromDevfile(instanceName, o.EnvSpecificInfo.GetDevfileObj(), o.contextFlag)
if err != nil {
return errors.Wrap(err, "failed to delete service from the devfile")
}
return nil
}
func (b *OperatorBackend) DescribeService(o *DescribeOptions, serviceName, app string) error {
clusterList, _, err := svc.ListOperatorServices(o.KClient)
if err != nil {
return err
}
var clusterFound *unstructured.Unstructured
for i, clusterInstance := range clusterList {
fullName := strings.Join([]string{clusterInstance.GetKind(), clusterInstance.GetName()}, "/")
if fullName == serviceName {
clusterFound = &clusterList[i]
break
}
}
devfileList, err := svc.ListDevfileServices(o.KClient, o.EnvSpecificInfo.GetDevfileObj(), o.contextFlag)
if err != nil {
return err
}
devfileService, inDevfile := devfileList[serviceName]
item := NewServiceItem(serviceName)
item.InDevfile = inDevfile
item.Deployed = clusterFound != nil
if item.Deployed {
item.Manifest = clusterFound.Object
} else if item.InDevfile {
item.Manifest = devfileService.Object
}
if log.IsJSON() {
machineoutput.OutputSuccess(item)
return nil
}
return PrintHumanReadableOutput(item)
}
// PrintHumanReadableOutput outputs the description of a service in a human readable format
func PrintHumanReadableOutput(item *serviceItem) error {
log.Describef("Version: ", "%s", item.Manifest["apiVersion"])
log.Describef("Kind: ", "%s", item.Manifest["kind"])
metadata, ok := item.Manifest["metadata"].(map[string]interface{})
if !ok {
return errors.New("unable to get name from manifest")
}
log.Describef("Name: ", "%s", metadata["name"])
spec, ok := item.Manifest["spec"].(map[string]interface{})
if !ok {
return errors.New("unable to get specifications from manifest")
}
var tab bytes.Buffer
wr := tabwriter.NewWriter(&tab, 5, 2, 3, ' ', tabwriter.TabIndent)
fmt.Fprint(wr, "NAME", "\t", "VALUE", "\n")
displayParameters(wr, spec, "")
wr.Flush()
log.Describef("Parameters:\n", tab.String())
return nil
}
// displayParameters adds lines describing fields of a given map
func displayParameters(wr *tabwriter.Writer, spec map[string]interface{}, prefix string) {
keys := make([]string, len(spec))
i := 0
for key := range spec {
keys[i] = key
i++
}
for _, k := range keys {
v := spec[k]
switch val := v.(type) {
case map[string]interface{}:
displayParameters(wr, val, prefix+k+".")
default:
fmt.Fprintf(wr, "%s%s\t%v\n", prefix, k, val)
}
}
}

View File

@@ -1,54 +0,0 @@
package service
import (
"fmt"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application"
projectCmd "github.com/redhat-developer/odo/pkg/odo/cli/project"
"github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
// RecommendedCommandName is the recommended service command name
const RecommendedCommandName = "service"
var serviceLongDesc = ktemplates.LongDesc(`Perform service related operations`)
// NewCmdService implements the odo service command
func NewCmdService(name, fullName string) *cobra.Command {
serviceCreateCmd := NewCmdServiceCreate(createRecommendedCommandName, util.GetFullName(fullName, createRecommendedCommandName))
serviceListCmd := NewCmdServiceList(listRecommendedCommandName, util.GetFullName(fullName, listRecommendedCommandName))
serviceDeleteCmd := NewCmdServiceDelete(deleteRecommendedCommandName, util.GetFullName(fullName, deleteRecommendedCommandName))
serviceDescribeCmd := NewCmdServiceDescribe(describeRecommendedCommandName, util.GetFullName(fullName, describeRecommendedCommandName))
serviceCmd := &cobra.Command{
Use: name,
Short: "Perform service related operations",
Long: serviceLongDesc,
Example: fmt.Sprintf("%s\n\n%s\n\n%s\n\n%s",
serviceCreateCmd.Example,
serviceDeleteCmd.Example,
serviceDescribeCmd.Example,
serviceListCmd.Example),
Args: cobra.RangeArgs(1, 3),
}
// Add a defined annotation in order to appear in the help menu
serviceCmd.Annotations = map[string]string{"command": "main"}
serviceCmd.SetUsageTemplate(util.CmdUsageTemplate)
serviceCmd.AddCommand(serviceCreateCmd, serviceDeleteCmd, serviceDescribeCmd, serviceListCmd)
//Adding `--project` flag
projectCmd.AddProjectFlag(serviceCreateCmd)
projectCmd.AddProjectFlag(serviceDeleteCmd)
projectCmd.AddProjectFlag(serviceDescribeCmd)
projectCmd.AddProjectFlag(serviceListCmd)
//Adding `--application` flag
appCmd.AddApplicationFlag(serviceCreateCmd)
appCmd.AddApplicationFlag(serviceDeleteCmd)
appCmd.AddApplicationFlag(serviceDescribeCmd)
appCmd.AddApplicationFlag(serviceListCmd)
return serviceCmd
}

View File

@@ -1,21 +0,0 @@
package service
//OperatorBackend implements the interface ServiceProviderBackend and contains methods that help create a service from Operators
type OperatorBackend struct {
// Custom Resrouce to create service from
CustomResource string
// Custom Resrouce's Definition fetched from alm-examples
CustomResourceDefinition map[string]interface{}
// Group of the GVR
group string
// Version of the GVR
version string
// Resource of the GVR
resource string
// Kind of GVK
kind string
}
func NewOperatorBackend() *OperatorBackend {
return &OperatorBackend{}
}

View File

@@ -1,22 +0,0 @@
package service
import (
"fmt"
"github.com/redhat-developer/odo/pkg/devfile/location"
"github.com/redhat-developer/odo/pkg/odo/cli/component"
"github.com/redhat-developer/odo/pkg/util"
)
// validDevfileDirectory returns an error if the "odo service" command is executed from a directory not containing devfile.yaml
func validDevfileDirectory(componentContext string) error {
if componentContext == "" {
componentContext = component.LocalDirectoryDefaultLocation
}
devfilePath := location.DevfileLocation(componentContext)
if !util.CheckPathExists(devfilePath) {
return fmt.Errorf("service can be created/deleted from a valid component directory only\n"+
"refer %q for more information", "odo service create -h")
}
return nil
}

View File

@@ -1,133 +0,0 @@
package storage
import (
"fmt"
"github.com/redhat-developer/odo/pkg/localConfigProvider"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
"github.com/redhat-developer/odo/pkg/storage"
"github.com/redhat-developer/odo/pkg/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const createRecommendedCommandName = "create"
var (
storageCreateShortDesc = `Create storage and mount to a component`
storageCreateLongDesc = ktemplates.LongDesc(`Create storage and mount to a component`)
storageCreateExample = ktemplates.Examples(`
# Create storage of size 1Gb to a component
%[1]s mystorage --path=/opt/app-root/src/storage/ --size=1Gi
# Create storage with ephemeral volume of size 2Gi to a component
%[1]s mystorage --path=/opt/app-root/src/storage/ --size=2Gi --ephemeral
`)
)
type CreateOptions struct {
// Context
*genericclioptions.Context
// Parameters
storageName string
// Flags
sizeFlag string
pathFlag string
contextFlag string
containerFlag string
ephemeralFlag bool
storage localConfigProvider.LocalStorage
}
// NewStorageCreateOptions creates a new CreateOptions instance
func NewStorageCreateOptions() *CreateOptions {
return &CreateOptions{}
}
// Complete completes CreateOptions after they've been created
func (o *CreateOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
}
if len(args) != 0 {
o.storageName = args[0]
} else {
o.storageName = fmt.Sprintf("%s-%s", o.Context.LocalConfigProvider.GetName(), util.GenerateRandomString(4))
}
var eph *bool
if o.ephemeralFlag {
eph = &o.ephemeralFlag
}
o.storage = localConfigProvider.LocalStorage{
Name: o.storageName,
Size: o.sizeFlag,
Ephemeral: eph,
Path: o.pathFlag,
Container: o.containerFlag,
}
o.Context.LocalConfigProvider.CompleteStorage(&o.storage)
return nil
}
// Validate validates the CreateOptions based on completed values
func (o *CreateOptions) Validate() (err error) {
// validate the storage
return o.LocalConfigProvider.ValidateStorage(o.storage)
}
// Run contains the logic for the odo storage create command
func (o *CreateOptions) Run() (err error) {
err = o.Context.LocalConfigProvider.CreateStorage(o.storage)
if err != nil {
return err
}
if log.IsJSON() {
storageResultMachineReadable := storage.NewStorage(o.storage.Name, o.storage.Size, o.storage.Path, nil)
machineoutput.OutputSuccess(storageResultMachineReadable)
} else {
log.Successf("Added storage %v to %v", o.storageName, o.Context.LocalConfigProvider.GetName())
log.Italic("\nPlease use `odo push` command to make the storage accessible to the component")
}
return nil
}
// NewCmdStorageCreate implements the odo storage create command.
func NewCmdStorageCreate(name, fullName string) *cobra.Command {
o := NewStorageCreateOptions()
storageCreateCmd := &cobra.Command{
Use: name,
Short: storageCreateShortDesc,
Long: storageCreateLongDesc,
Example: fmt.Sprintf(storageCreateExample, fullName),
Args: cobra.MaximumNArgs(1),
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
storageCreateCmd.Flags().StringVar(&o.sizeFlag, "size", "", "Size of storage to add")
storageCreateCmd.Flags().StringVar(&o.pathFlag, "path", "", "Path to mount the storage on")
storageCreateCmd.Flags().StringVar(&o.containerFlag, "container", "", "Name of container to attach the storage to in devfile")
storageCreateCmd.Flags().BoolVar(&o.ephemeralFlag, "ephemeral", false, "Set volume as ephemeral")
odoutil.AddContextFlag(storageCreateCmd, &o.contextFlag)
completion.RegisterCommandFlagHandler(storageCreateCmd, "context", completion.FileCompletionHandler)
return storageCreateCmd
}

View File

@@ -1,122 +0,0 @@
package storage
import (
"fmt"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
"github.com/redhat-developer/odo/pkg/storage"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
const deleteRecommendedCommandName = "delete"
var (
storageDeleteShortDesc = `Delete storage from component`
storageDeleteLongDesc = ktemplates.LongDesc(`Delete storage from component`)
storageDeleteExample = ktemplates.Examples(`
# Delete storage mystorage from the currently active component
%[1]s mystorage
`)
)
type DeleteOptions struct {
// Context
*genericclioptions.Context
// Parameters
storageName string
// Flags
forceFlag bool
contextFlag string
}
// NewStorageDeleteOptions creates a new DeleteOptions instance
func NewStorageDeleteOptions() *DeleteOptions {
return &DeleteOptions{}
}
// Complete completes DeleteOptions after they've been created
func (o *DeleteOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
}
o.storageName = args[0]
return nil
}
// Validate validates the DeleteOptions based on completed values
func (o *DeleteOptions) Validate() (err error) {
gotStorage, err := o.LocalConfigProvider.GetStorage(o.storageName)
if err != nil {
return err
}
if gotStorage == nil {
return fmt.Errorf("the storage %v does not exists in the application %v, cause %v", o.storageName, o.GetApplication(), err)
}
return nil
}
// Run contains the logic for the odo storage delete command
func (o *DeleteOptions) Run() (err error) {
mPath, err := o.Context.LocalConfigProvider.GetStorageMountPath(o.storageName)
if err != nil {
return err
}
deleteMsg := fmt.Sprintf("Are you sure you want to delete the storage %v mounted to %v in %v component", o.storageName, mPath, o.Context.LocalConfigProvider.GetName())
if log.IsJSON() || o.forceFlag || ui.Proceed(deleteMsg) {
err := o.Context.LocalConfigProvider.DeleteStorage(o.storageName)
if err != nil {
return fmt.Errorf("failed to delete storage, cause %v", err)
}
successMessage := fmt.Sprintf("Deleted storage %v from %v", o.storageName, o.Context.LocalConfigProvider.GetName())
log.Infof(successMessage)
log.Italic("\nPlease use `odo push` command to delete the storage from the cluster")
if log.IsJSON() {
machineoutput.SuccessStatus(storage.StorageKind, o.storageName, successMessage)
}
} else {
return fmt.Errorf("aborting deletion of storage: %v", o.storageName)
}
return nil
}
// NewCmdStorageDelete implements the odo storage delete command.
func NewCmdStorageDelete(name, fullName string) *cobra.Command {
o := NewStorageDeleteOptions()
storageDeleteCmd := &cobra.Command{
Use: name,
Short: storageDeleteShortDesc,
Long: storageDeleteLongDesc,
Example: fmt.Sprintf(storageDeleteExample, fullName),
Args: cobra.ExactArgs(1),
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
storageDeleteCmd.Flags().BoolVarP(&o.forceFlag, "force", "f", false, "Delete storage without prompting")
completion.RegisterCommandHandler(storageDeleteCmd, completion.StorageDeleteCompletionHandler)
odoutil.AddContextFlag(storageDeleteCmd, &o.contextFlag)
completion.RegisterCommandFlagHandler(storageDeleteCmd, "context", completion.FileCompletionHandler)
return storageDeleteCmd
}

View File

@@ -1,216 +0,0 @@
package storage
import (
"fmt"
"os"
"text/tabwriter"
"github.com/redhat-developer/odo/pkg/localConfigProvider"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/odo/util/completion"
"github.com/redhat-developer/odo/pkg/storage"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
ktemplates "k8s.io/kubectl/pkg/util/templates"
"github.com/spf13/cobra"
)
const listRecommendedCommandName = "list"
var (
storageListShortDesc = `List storage attached to a component`
storageListLongDesc = ktemplates.LongDesc(`List storage attached to a component`)
storageListExample = ktemplates.Examples(`
# List all storage attached to the current component
%[1]s
`)
)
type ListOptions struct {
// Context
*genericclioptions.Context
// Flags
contextFlag string
// Backend
client storage.Client
}
// NewStorageListOptions creates a new ListOptions instance
func NewStorageListOptions() *ListOptions {
return &ListOptions{}
}
// Complete completes ListOptions after they've been created
func (o *ListOptions) Complete(cmdline cmdline.Cmdline, args []string) (err error) {
o.Context, err = genericclioptions.New(genericclioptions.NewCreateParameters(cmdline).NeedDevfile(o.contextFlag))
if err != nil {
return err
}
o.client = storage.NewClient(storage.ClientOptions{
LocalConfigProvider: o.Context.LocalConfigProvider,
Client: o.Context.KClient,
})
return nil
}
// Validate validates the ListOptions based on completed values
func (o *ListOptions) Validate() (err error) {
return nil
}
func (o *ListOptions) Run() (err error) {
storageList, err := o.client.List()
if err != nil {
return err
}
if log.IsJSON() {
machineoutput.OutputSuccess(storageList)
} else {
localContainers, err := o.Context.LocalConfigProvider.GetContainers()
if err != nil {
return err
}
if isContainerDisplay(storageList, localContainers) {
printStorageWithContainer(storageList, o.Context.LocalConfigProvider.GetName())
} else {
printStorage(storageList, o.Context.LocalConfigProvider.GetName())
}
}
return nil
}
// printStorage prints the given storageList
func printStorage(storageList storage.StorageList, compName string) {
if len(storageList.Items) > 0 {
tabWriterMounted := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent)
storageMap := make(map[string]bool)
// create headers of mounted storage table
fmt.Fprintln(tabWriterMounted, "NAME", "\t", "SIZE", "\t", "PATH", "\t", "STATE")
// iterating over all mounted storage and put in the mount storage table
for _, mStorage := range storageList.Items {
_, ok := storageMap[mStorage.Name]
if !ok {
storageMap[mStorage.Name] = true
fmt.Fprintln(tabWriterMounted, mStorage.Name, "\t", mStorage.Spec.Size, "\t", mStorage.Spec.Path, "\t", mStorage.Status)
}
}
// print all mounted storage of the given component
log.Infof("The component '%v' has the following storage attached:", compName)
tabWriterMounted.Flush()
} else {
log.Infof("The component '%v' has no storage attached", compName)
}
fmt.Println("")
}
// printStorageWithContainer prints the given storageList with the corresponding container name
func printStorageWithContainer(storageList storage.StorageList, compName string) {
if len(storageList.Items) > 0 {
tabWriterMounted := tabwriter.NewWriter(os.Stdout, 5, 2, 3, ' ', tabwriter.TabIndent)
// create headers of mounted storage table
fmt.Fprintln(tabWriterMounted, "NAME", "\t", "SIZE", "\t", "PATH", "\t", "CONTAINER", "\t", "STATE")
// iterating over all mounted storage and put in the mount storage table
for _, mStorage := range storageList.Items {
fmt.Fprintln(tabWriterMounted, mStorage.Name, "\t", mStorage.Spec.Size, "\t", mStorage.Spec.Path, "\t", mStorage.Spec.ContainerName, "\t", mStorage.Status)
}
// print all mounted storage of the given component
log.Infof("The component '%v' has the following storage attached:", compName)
tabWriterMounted.Flush()
} else {
log.Infof("The component '%v' has no storage attached", compName)
}
fmt.Println("")
}
// isContainerDisplay checks whether the container name should be included in the output
func isContainerDisplay(storageList storage.StorageList, components []localConfigProvider.LocalContainer) bool {
// get all the container names
componentsMap := make(map[string]bool)
for _, comp := range components {
componentsMap[comp.Name] = true
}
storageCompMap := make(map[string][]string)
pathMap := make(map[string]string)
storageMap := make(map[string]storage.StorageStatus)
for _, storageItem := range storageList.Items {
if pathMap[storageItem.Name] == "" {
pathMap[storageItem.Name] = storageItem.Spec.Path
}
if storageMap[storageItem.Name] == "" {
storageMap[storageItem.Name] = storageItem.Status
}
// check if the storage is mounted on the same path in all the containers
if pathMap[storageItem.Name] != storageItem.Spec.Path {
return true
}
// check if the storage is in the same state for all the containers
if storageMap[storageItem.Name] != storageItem.Status {
return true
}
// check if the storage is mounted on a valid devfile container
// this situation can arrive when a container is removed from the devfile
// but the state is not pushed thus it exists on the cluster
_, ok := componentsMap[storageItem.Spec.ContainerName]
if !ok {
return true
}
storageCompMap[storageItem.Name] = append(storageCompMap[storageItem.Name], storageItem.Spec.ContainerName)
}
for _, containerNames := range storageCompMap {
// check if the storage is mounted on all the devfile containers
if len(containerNames) != len(componentsMap) {
return true
}
}
return false
}
// NewCmdStorageList implements the odo storage list command.
func NewCmdStorageList(name, fullName string) *cobra.Command {
o := NewStorageListOptions()
storageListCmd := &cobra.Command{
Use: name,
Short: storageListShortDesc,
Long: storageListLongDesc,
Example: fmt.Sprintf(storageListExample, fullName),
Args: cobra.MaximumNArgs(1),
Annotations: map[string]string{"machineoutput": "json"},
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}
odoutil.AddContextFlag(storageListCmd, &o.contextFlag)
completion.RegisterCommandFlagHandler(storageListCmd, "context", completion.FileCompletionHandler)
return storageListCmd
}

View File

@@ -1,130 +0,0 @@
package storage
import (
"testing"
"github.com/redhat-developer/odo/pkg/localConfigProvider"
"github.com/redhat-developer/odo/pkg/storage"
)
func Test_isContainerDisplay(t *testing.T) {
generateStorage := func(storage storage.Storage, status storage.StorageStatus, containerName string) storage.Storage {
storage.Status = status
storage.Spec.ContainerName = containerName
return storage
}
type args struct {
storageList storage.StorageList
obj []localConfigProvider.LocalContainer
}
tests := []struct {
name string
args args
want bool
}{
{
name: "case 1: storage is mounted on all the containers on the same path",
args: args{
storageList: storage.StorageList{
Items: []storage.Storage{
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypePushed, "container-0"),
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypePushed, "container-1"),
},
},
obj: []localConfigProvider.LocalContainer{
{
Name: "container-0",
},
{
Name: "container-1",
},
},
},
want: false,
},
{
name: "case 2: storage is mounted on different paths",
args: args{
storageList: storage.StorageList{
Items: []storage.Storage{
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypePushed, "container-0"),
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/path", nil), storage.StateTypePushed, "container-1"),
},
},
obj: []localConfigProvider.LocalContainer{
{
Name: "container-0",
},
{
Name: "container-1",
},
},
},
want: true,
},
{
name: "case 3: storage is mounted to the same path on all the containers but states are different",
args: args{
storageList: storage.StorageList{
Items: []storage.Storage{
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypePushed, "container-0"),
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypeNotPushed, "container-1"),
},
},
obj: []localConfigProvider.LocalContainer{
{
Name: "container-0",
},
{
Name: "container-1",
},
},
},
want: true,
},
{
name: "case 4: storage is not mounted on all the containers",
args: args{
storageList: storage.StorageList{
Items: []storage.Storage{
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypePushed, "container-0"),
},
},
obj: []localConfigProvider.LocalContainer{
{
Name: "container-0",
},
{
Name: "container-1",
},
},
},
want: true,
},
{
name: "case 5: storage is mounted on a container deleted locally from the devfile",
args: args{
storageList: storage.StorageList{
Items: []storage.Storage{
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypePushed, "container-0"),
generateStorage(storage.NewStorage("pvc-1", "1Gi", "/data", nil), storage.StateTypePushed, "container-1"),
},
},
obj: []localConfigProvider.LocalContainer{
{
Name: "container-0",
},
},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isContainerDisplay(tt.args.storageList, tt.args.obj); got != tt.want {
t.Errorf("isContainerDisplay() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,44 +0,0 @@
package storage
import (
"fmt"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra"
ktemplates "k8s.io/kubectl/pkg/util/templates"
)
// RecommendedCommandName is the recommended command name
const RecommendedCommandName = "storage"
var (
storageShortDesc = `Perform storage operations`
storageLongDesc = ktemplates.LongDesc(`Perform storage operations`)
)
// NewCmdStorage implements the odo storage command
func NewCmdStorage(name, fullName string) *cobra.Command {
storageCreateCmd := NewCmdStorageCreate(createRecommendedCommandName, odoutil.GetFullName(fullName, createRecommendedCommandName))
storageDeleteCmd := NewCmdStorageDelete(deleteRecommendedCommandName, odoutil.GetFullName(fullName, deleteRecommendedCommandName))
storageListCmd := NewCmdStorageList(listRecommendedCommandName, odoutil.GetFullName(fullName, listRecommendedCommandName))
var storageCmd = &cobra.Command{
Use: name,
Short: storageShortDesc,
Long: storageLongDesc,
Example: fmt.Sprintf("%s\n\n%s\n\n%s",
storageCreateCmd.Example,
storageDeleteCmd.Example,
storageListCmd.Example),
}
storageCmd.AddCommand(storageCreateCmd)
storageCmd.AddCommand(storageDeleteCmd)
storageCmd.AddCommand(storageListCmd)
// Add a defined annotation in order to appear in the help menu
storageCmd.Annotations = map[string]string{"command": "main"}
storageCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
return storageCmd
}

View File

@@ -1,4 +1,4 @@
package notify package version
import ( import (
"strings" "strings"
@@ -30,9 +30,9 @@ func getLatestReleaseTag() (string, error) {
return strings.TrimSuffix(string(body), "\n"), nil return strings.TrimSuffix(string(body), "\n"), nil
} }
// CheckLatestReleaseTag returns the latest release tag if a newer latest // checkLatestReleaseTag returns the latest release tag if a newer latest
// release is available, else returns an empty string // release is available, else returns an empty string
func CheckLatestReleaseTag(currentVersion string) (string, error) { func checkLatestReleaseTag(currentVersion string) (string, error) {
currentSemver, err := semver.Make(strings.TrimPrefix(currentVersion, "v")) currentSemver, err := semver.Make(strings.TrimPrefix(currentVersion, "v"))
if err != nil { if err != nil {
return "", errors.Wrapf(err, "unable to make semver from the current version: %v", currentVersion) return "", errors.Wrapf(err, "unable to make semver from the current version: %v", currentVersion)

View File

@@ -1,4 +1,4 @@
package notify package version
/* /*
import ( import (

View File

@@ -13,7 +13,6 @@ import (
"github.com/redhat-developer/odo/pkg/preference" "github.com/redhat-developer/odo/pkg/preference"
odoversion "github.com/redhat-developer/odo/pkg/version" odoversion "github.com/redhat-developer/odo/pkg/version"
"github.com/redhat-developer/odo/pkg/notify"
"github.com/redhat-developer/odo/pkg/odo/util" "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/klog" "k8s.io/klog"
@@ -136,7 +135,7 @@ func NewCmdVersion(name, fullName string) *cobra.Command {
// GetLatestReleaseInfo Gets information about the latest release // GetLatestReleaseInfo Gets information about the latest release
func GetLatestReleaseInfo(info chan<- string) { func GetLatestReleaseInfo(info chan<- string) {
newTag, err := notify.CheckLatestReleaseTag(odoversion.VERSION) newTag, err := checkLatestReleaseTag(odoversion.VERSION)
if err != nil { if err != nil {
// The error is intentionally not being handled because we don't want // The error is intentionally not being handled because we don't want
// to stop the execution of the program because of this failure // to stop the execution of the program because of this failure

View File

@@ -8,7 +8,6 @@ import (
"github.com/redhat-developer/odo/pkg/testingutil/filesystem" "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
"github.com/posener/complete" "github.com/posener/complete"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/catalog" "github.com/redhat-developer/odo/pkg/catalog"
"github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions" "github.com/redhat-developer/odo/pkg/odo/genericclioptions"
@@ -16,25 +15,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// AppCompletionHandler provides completion for the app commands
var AppCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
completions = make([]string, 0)
appClient := application.NewClient(context.KClient)
applications, err := appClient.List()
if err != nil {
return completions
}
for _, app := range applications {
if args.commands[app] {
return nil
}
completions = append(completions, app)
}
return
}
// FileCompletionHandler provides suggestions for files and directories // FileCompletionHandler provides suggestions for files and directories
var FileCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) { var FileCompletionHandler = func(cmd *cobra.Command, args parsedArgs, context *genericclioptions.Context) (completions []string) {
completions = append(completions, complete.PredictFiles("*").Predict(args.original)...) completions = append(completions, complete.PredictFiles("*").Predict(args.original)...)

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"sort" "sort"
"github.com/redhat-developer/odo/pkg/unions" "github.com/redhat-developer/odo/pkg/kclient/unions"
"github.com/devfile/library/pkg/devfile/generator" "github.com/devfile/library/pkg/devfile/generator"
routev1 "github.com/openshift/api/route/v1" routev1 "github.com/openshift/api/route/v1"

View File

@@ -5,7 +5,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/redhat-developer/odo/pkg/unions" "github.com/redhat-developer/odo/pkg/kclient/unions"
networkingv1 "k8s.io/api/networking/v1" networkingv1 "k8s.io/api/networking/v1"
"github.com/devfile/library/pkg/devfile/generator" "github.com/devfile/library/pkg/devfile/generator"

View File

@@ -4,7 +4,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/redhat-developer/odo/pkg/unions" "github.com/redhat-developer/odo/pkg/kclient/unions"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
routev1 "github.com/openshift/api/route/v1" routev1 "github.com/openshift/api/route/v1"

View File

@@ -5,9 +5,9 @@ import (
"reflect" "reflect"
routev1 "github.com/openshift/api/route/v1" routev1 "github.com/openshift/api/route/v1"
"github.com/redhat-developer/odo/pkg/kclient/unions"
"github.com/redhat-developer/odo/pkg/localConfigProvider" "github.com/redhat-developer/odo/pkg/localConfigProvider"
"github.com/redhat-developer/odo/pkg/machineoutput" "github.com/redhat-developer/odo/pkg/machineoutput"
"github.com/redhat-developer/odo/pkg/unions"
urlLabels "github.com/redhat-developer/odo/pkg/url/labels" urlLabels "github.com/redhat-developer/odo/pkg/url/labels"
iextensionsv1 "k8s.io/api/extensions/v1beta1" iextensionsv1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

View File

@@ -13,7 +13,6 @@ SCRIPT_IDENTITY=${SCRIPT_IDENTITY:-"def-id"}
# Integration tests # Integration tests
shout "| Running integration Tests on MiniKube" shout "| Running integration Tests on MiniKube"
make test-operator-hub
make test-cmd-project make test-cmd-project
make test-integration-devfile make test-integration-devfile

View File

@@ -27,10 +27,6 @@ mockgen -source=pkg/odo/cmdline/cmdline.go \
-package cmdline \ -package cmdline \
-destination pkg/odo/cmdline/mock.go -destination pkg/odo/cmdline/mock.go
mockgen -source=pkg/application/application.go \
-package application \
-destination pkg/application/mock.go
mockgen -source=pkg/project/project.go \ mockgen -source=pkg/project/project.go \
-package project \ -package project \
-destination pkg/project/mock.go -destination pkg/project/mock.go

View File

@@ -31,7 +31,6 @@ make test-integration || error=true
make test-integration-devfile || error=true make test-integration-devfile || error=true
make test-cmd-login-logout || error=true make test-cmd-login-logout || error=true
make test-cmd-project || error=true make test-cmd-project || error=true
make test-operator-hub || error=true
# E2e tests # E2e tests
make test-e2e-all || error=true make test-e2e-all || error=true

View File

@@ -53,7 +53,6 @@ else
make test-integration-devfile || error=true make test-integration-devfile || error=true
make test-cmd-login-logout || error=true make test-cmd-login-logout || error=true
make test-cmd-project || error=true make test-cmd-project || error=true
make test-operator-hub || error=true
# E2e tests # E2e tests
make test-e2e-all || error=true make test-e2e-all || error=true

View File

@@ -39,7 +39,6 @@ if [ "${ARCH}" == "s390x" ]; then
make test-integration-devfile make test-integration-devfile
make test-cmd-login-logout make test-cmd-login-logout
make test-cmd-project make test-cmd-project
make test-operator-hub
# E2e tests # E2e tests
make test-e2e-all make test-e2e-all
elif [ "${ARCH}" == "ppc64le" ]; then elif [ "${ARCH}" == "ppc64le" ]; then
@@ -50,14 +49,12 @@ elif [ "${ARCH}" == "ppc64le" ]; then
make test-cmd-project make test-cmd-project
# E2e tests # E2e tests
make test-e2e-all make test-e2e-all
make test-operator-hub
else else
# Integration tests # Integration tests
make test-integration || error=true make test-integration || error=true
make test-integration-devfile || error=true make test-integration-devfile || error=true
make test-cmd-login-logout || error=true make test-cmd-login-logout || error=true
make test-cmd-project || error=true make test-cmd-project || error=true
make test-operator-hub || error=true
# E2e tests # E2e tests
make test-e2e-all || error=true make test-e2e-all || error=true

View File

@@ -69,7 +69,6 @@ git clone $REPO_URL odo && cd $WORKING_DIR/odo && git checkout "v$VERSION"
#Run tests #Run tests
make test-integration-devfile make test-integration-devfile
make test-integration make test-integration
make test-operator-hub
make test-e2e-all make test-e2e-all
make test-cmd-project make test-cmd-project

View File

@@ -73,7 +73,6 @@ set -x
# # Integration tests # # Integration tests
shout "Running integration Tests" shout "Running integration Tests"
make test-operator-hub || error=true
make test-integration || error=true make test-integration || error=true
make test-integration-devfile || error=true make test-integration-devfile || error=true
make test-cmd-login-logout || error=true make test-cmd-login-logout || error=true

Some files were not shown because too many files have changed in this diff Show More