Fixing issue #804 Improving delete command to list affected children (#1319)

* Attempting to delete application, now lists affected URLs

Signed-off-by: Mohammed Zeeshan Ahmed <mohammed.zee1000@gmail.com>

* Making component delete list affected objects before deletion.

* Applying missed go gmt

* Altering Application ListInProject to actually list in project.

 - Also includes changes to corresponding references to
use List instead

* Fixing imports in component that got missed

* Applying go fmt

* Improving messaging for application and project.

 - More detailed messages are now displayed about affected
   child components
 - Abortion of deletion in interactive, now logged as error

* Adding more details of affected components for project deletion

* Updating messaging to list linked services during component deletion

* Adding missed printProjectDeleteInfo after rebasae

* Updating component and project to use new format of URLs

* Fixing up changes messed up during rebase.

* Fixing up after rebase

* Applying go fmt

* Force deleting testversioncmp

* Adding missing message for application deletion

* Adding tests to ensure proper messaging during deletion of app and project

* Deletion of application now lists affected service instances.

* Fixing up application and project after rebase to use new structs

* Fixing up component after rebase to use new struct and removing linked services

  - New struct makes it harder to list these, so will figure out a way
    later

* Fixing up missing return of error

* Fixing unnessasary message.

* fixing after rebase

* Fixing up after rebase to use new format of URL

* Moving back to infof
This commit is contained in:
Mohammed Ahmed
2019-03-12 18:25:56 +05:30
committed by OpenShift Merge Robot
parent f816d9f41a
commit 54ff6263e3
9 changed files with 238 additions and 31 deletions

View File

@@ -7,7 +7,6 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/pkg/errors" "github.com/pkg/errors"
applabels "github.com/redhat-developer/odo/pkg/application/labels" applabels "github.com/redhat-developer/odo/pkg/application/labels"
"github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/occlient" "github.com/redhat-developer/odo/pkg/occlient"
@@ -82,14 +81,18 @@ func Create(client *occlient.Client, appName string) error {
// List all applications in current project // List all applications in current project
func List(client *occlient.Client) ([]preference.ApplicationInfo, error) { func List(client *occlient.Client) ([]preference.ApplicationInfo, error) {
return ListInProject(client) return ListInProject(client, client.Namespace)
} }
// ListInProject lists all applications in given project // ListInProject lists all applications in given project
// Queries cluster and config file. // Queries cluster and config file.
// Shows also empty applications (empty applications are those that are just // Shows also empty applications (empty applications are those that are just
// mentioned in config file but don't have any object associated with it on cluster). // mentioned in config file but don't have any object associated with it on cluster).
func ListInProject(client *occlient.Client) ([]preference.ApplicationInfo, error) { // ListInProject lists all applications in given project
// Queries cluster and config file.
// Shows also empty applications (empty applications are those that are just
// mentioned in config file but don't have any object associated with it on cluster).
func ListInProject(client *occlient.Client, projectName string) ([]preference.ApplicationInfo, error) {
var applications []preference.ApplicationInfo var applications []preference.ApplicationInfo
cfg, err := preference.New() cfg, err := preference.New()
@@ -99,7 +102,7 @@ func ListInProject(client *occlient.Client) ([]preference.ApplicationInfo, error
// All applications of the current project from config file // All applications of the current project from config file
for i := range cfg.ActiveApplications { for i := range cfg.ActiveApplications {
if cfg.ActiveApplications[i].Project == client.Namespace { if cfg.ActiveApplications[i].Project == projectName {
applications = append(applications, cfg.ActiveApplications[i]) applications = append(applications, cfg.ActiveApplications[i])
} }
} }
@@ -114,7 +117,7 @@ func ListInProject(client *occlient.Client) ([]preference.ApplicationInfo, error
// skip applications that are already in the list (they were mentioned in config file) // skip applications that are already in the list (they were mentioned in config file)
found := false found := false
for _, app := range applications { for _, app := range applications {
if app.Project == client.Namespace && app.Name == name { if app.Project == projectName && app.Name == name {
found = true found = true
} }
} }
@@ -123,7 +126,7 @@ func ListInProject(client *occlient.Client) ([]preference.ApplicationInfo, error
Name: name, Name: name,
// if this application is not in config file, it can't be active // if this application is not in config file, it can't be active
Active: false, Active: false,
Project: client.Namespace, Project: projectName,
}) })
} }
} }
@@ -252,7 +255,7 @@ func SetCurrent(client *occlient.Client, appName string) error {
// Exists returns true if given application (appName) exists // Exists returns true if given application (appName) exists
func Exists(client *occlient.Client, appName string) (bool, error) { func Exists(client *occlient.Client, appName string) (bool, error) {
apps, err := ListInProject(client) apps, err := List(client)
if err != nil { if err != nil {
return false, errors.Wrap(err, "unable to list applications") return false, errors.Wrap(err, "unable to list applications")
} }

View File

@@ -3,19 +3,22 @@ package application
import ( import (
"fmt" "fmt"
"github.com/golang/glog"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/application" "github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/occlient" "github.com/redhat-developer/odo/pkg/occlient"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions" "github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/storage" "github.com/redhat-developer/odo/pkg/storage"
"github.com/redhat-developer/odo/pkg/url"
"github.com/redhat-developer/odo/pkg/component" "github.com/redhat-developer/odo/pkg/component"
odoutil "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" "github.com/redhat-developer/odo/pkg/odo/util/completion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/redhat-developer/odo/pkg/service"
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// RecommendedCommandName is the recommended app command name // RecommendedCommandName is the recommended app command name
@@ -78,21 +81,47 @@ func printDeleteAppInfo(client *occlient.Client, appName string, projectName str
return errors.Wrap(err, "failed to get Component list") return errors.Wrap(err, "failed to get Component list")
} }
for _, currentComponent := range componentList.Items { if len(componentList.Items) != 0 {
componentDesc, err := component.GetComponent(client, currentComponent.Name, appName, projectName) log.Info("This application has following components that will be deleted")
if err != nil { for _, currentComponent := range componentList.Items {
return errors.Wrap(err, "unable to get component description") componentDesc, err := component.GetComponent(client, currentComponent.Name, appName, projectName)
} if err != nil {
log.Info("Component", currentComponent.Name, "will be deleted.") return errors.Wrap(err, "unable to get component description")
}
log.Info(" component named ", currentComponent.Name)
if len(componentDesc.Spec.URL) != 0 { if len(componentDesc.Spec.URL) != 0 {
fmt.Println(" Externally exposed URLs will be removed") ul, err := url.List(client, componentDesc.Name, appName)
if err != nil {
return errors.Wrap(err, "Could not get url list")
}
log.Info(" This component has following urls that will be deleted with component")
for _, u := range ul.Items {
log.Info(" URL named ", u.GetName(), " with host ", u.Spec.Host, " having protocol ", u.Spec.Protocol, " at port ", u.Spec.Port)
}
}
storages, err := storage.List(client, currentComponent.Name, appName)
odoutil.LogErrorAndExit(err, "")
if len(storages.Items) != 0 {
log.Info(" The component has following storages which will be deleted with the component")
for _, storageName := range componentDesc.Spec.Storage {
store := storages.Get(storageName)
log.Info(" Storage named ", store.GetName(), " of size ", store.Spec.Size)
}
}
} }
storages, err := storage.List(client, currentComponent.Name, appName) // List services that will be removed
odoutil.LogErrorAndExit(err, "") serviceList, err := service.List(client, appName)
for _, storageName := range componentDesc.Spec.Storage { if err != nil {
store := storages.Get(storageName) log.Info("No services / could not get services")
fmt.Println(" Storage", store.Name, "of size", store.Spec.Size, "will be removed") glog.V(4).Info(err.Error())
}
if len(serviceList) != 0 {
log.Info("This application has following service that will be deleted")
for _, ser := range serviceList {
log.Info(" service named ", ser.Name, " of type ", ser.Type)
}
} }
} }

View File

@@ -2,6 +2,7 @@ package application
import ( import (
"fmt" "fmt"
"github.com/redhat-developer/odo/pkg/application" "github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/odo/cli/project" "github.com/redhat-developer/odo/pkg/odo/cli/project"

View File

@@ -49,7 +49,7 @@ func (o *ListOptions) Validate() (err error) {
// Run contains the logic for the odo command // Run contains the logic for the odo command
func (o *ListOptions) Run() (err error) { func (o *ListOptions) Run() (err error) {
apps, err := application.ListInProject(o.Client) apps, err := application.List(o.Client)
if err != nil { if err != nil {
return fmt.Errorf("unable to get list of applications: %v", err) return fmt.Errorf("unable to get list of applications: %v", err)
} }

View File

@@ -3,7 +3,13 @@ package component
import ( import (
"fmt" "fmt"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/occlient"
"github.com/redhat-developer/odo/pkg/odo/util/completion" "github.com/redhat-developer/odo/pkg/odo/util/completion"
"github.com/redhat-developer/odo/pkg/storage"
"github.com/redhat-developer/odo/pkg/url"
"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"
@@ -86,3 +92,33 @@ func AddComponentFlag(cmd *cobra.Command) {
cmd.Flags().String(genericclioptions.ComponentFlagName, "", "Component, defaults to active component.") cmd.Flags().String(genericclioptions.ComponentFlagName, "", "Component, defaults to active component.")
completion.RegisterCommandFlagHandler(cmd, "component", completion.ComponentNameCompletionHandler) completion.RegisterCommandFlagHandler(cmd, "component", completion.ComponentNameCompletionHandler)
} }
// printDeleteComponentInfo will print things which will be deleted
func printDeleteComponentInfo(client *occlient.Client, componentName string, appName string, projectName string) error {
componentDesc, err := component.GetComponent(client, componentName, appName, projectName)
if err != nil {
return errors.Wrap(err, "unable to get component description")
}
if len(componentDesc.Spec.URL) != 0 {
log.Info("This component has following urls that will be deleted with component")
ul, err := url.List(client, componentDesc.Name, appName)
if err != nil {
return errors.Wrap(err, "Could not get url list")
}
for _, u := range ul.Items {
log.Info(" URL named ", u.GetName(), " with host ", u.Spec.Host, " having protocol ", u.Spec.Protocol, " at port ", u.Spec.Port)
}
}
storages, err := storage.List(client, componentDesc.Name, appName)
odoutil.LogErrorAndExit(err, "")
if len(storages.Items) != 0 {
log.Info("This component has following storages which will be deleted with the component")
for _, storageName := range componentDesc.Spec.Storage {
store := storages.Get(storageName)
log.Info(" Storage", store.GetName(), "of size", store.Spec.Size)
}
}
return nil
}

View File

@@ -2,15 +2,17 @@ package component
import ( import (
"fmt" "fmt"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/log"
appCmd "github.com/redhat-developer/odo/pkg/odo/cli/application" 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/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/util/completion" "github.com/redhat-developer/odo/pkg/odo/util/completion"
ktemplates "k8s.io/kubernetes/pkg/kubectl/cmd/templates" ktemplates "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
"github.com/redhat-developer/odo/pkg/log"
odoutil "github.com/redhat-developer/odo/pkg/odo/util" odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/golang/glog" "github.com/golang/glog"
@@ -59,6 +61,11 @@ func (do *DeleteOptions) Run() (err error) {
glog.V(4).Infof("component delete called") glog.V(4).Infof("component delete called")
glog.V(4).Infof("args: %#v", do) glog.V(4).Infof("args: %#v", do)
err = printDeleteComponentInfo(do.Client, do.componentName, do.Context.Application, do.Context.Project)
if err != nil {
return err
}
if do.componentForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete %v from %v?", do.componentName, do.Application)) { if do.componentForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete %v from %v?", do.componentName, do.Application)) {
err := component.Delete(do.Client, do.componentName, do.Application) err := component.Delete(do.Client, do.componentName, do.Application)
if err != nil { if err != nil {
@@ -78,7 +85,7 @@ func (do *DeleteOptions) Run() (err error) {
} }
} else { } else {
log.Infof("Aborting deletion of component: %v", do.componentName) return fmt.Errorf("Aborting deletion of component: %v", do.componentName)
} }
return return

View File

@@ -2,6 +2,7 @@ package project
import ( import (
"fmt" "fmt"
"github.com/redhat-developer/odo/pkg/log" "github.com/redhat-developer/odo/pkg/log"
"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/genericclioptions" "github.com/redhat-developer/odo/pkg/odo/genericclioptions"
@@ -60,6 +61,10 @@ func (pdo *ProjectDeleteOptions) Validate() (err error) {
// Run runs the project delete command // Run runs the project delete command
func (pdo *ProjectDeleteOptions) Run() (err error) { func (pdo *ProjectDeleteOptions) Run() (err error) {
err = printDeleteProjectInfo(pdo.Context.Client, pdo.projectName)
if err != nil {
return err
}
if pdo.projectForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete project %v", pdo.projectName)) { if pdo.projectForceDeleteFlag || ui.Proceed(fmt.Sprintf("Are you sure you want to delete project %v", pdo.projectName)) {
currentProject, err := project.Delete(pdo.Context.Client, pdo.projectName) currentProject, err := project.Delete(pdo.Context.Client, pdo.projectName)
if err != nil { if err != nil {

View File

@@ -3,9 +3,18 @@ package project
import ( import (
"fmt" "fmt"
"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/redhat-developer/odo/pkg/application"
"github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/log"
"github.com/redhat-developer/odo/pkg/occlient"
"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"
"github.com/redhat-developer/odo/pkg/odo/util/completion" "github.com/redhat-developer/odo/pkg/odo/util/completion"
"github.com/redhat-developer/odo/pkg/service"
"github.com/redhat-developer/odo/pkg/storage"
"github.com/redhat-developer/odo/pkg/url"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -66,3 +75,71 @@ func AddProjectFlag(cmd *cobra.Command) {
cmd.Flags().String(genericclioptions.ProjectFlagName, "", "Project, defaults to active project") cmd.Flags().String(genericclioptions.ProjectFlagName, "", "Project, defaults to active project")
completion.RegisterCommandFlagHandler(cmd, "project", completion.ProjectNameCompletionHandler) completion.RegisterCommandFlagHandler(cmd, "project", completion.ProjectNameCompletionHandler)
} }
// printDeleteProjectInfo prints objects affected by project deletion
func printDeleteProjectInfo(client *occlient.Client, projectName string) error {
// Fetch and List the applications
applicationList, err := application.ListInProject(client, projectName)
if err != nil {
return errors.Wrap(err, "failed to get application list")
}
if len(applicationList) != 0 {
log.Info("This project contains the following applications, which will be deleted")
for _, app := range applicationList {
log.Info(" Application ", app.Name)
// List the components
componentList, err := component.List(client, app.Name)
if err != nil {
return errors.Wrap(err, "failed to get Component list")
}
if len(componentList.Items) != 0 {
log.Info(" This application has following components that will be deleted")
for _, currentComponent := range componentList.Items {
componentDesc, err := component.GetComponent(client, currentComponent.Name, app.Name, app.Project)
if err != nil {
return errors.Wrap(err, "unable to get component description")
}
log.Info(" component named ", componentDesc.Name)
if len(componentDesc.Spec.URL) != 0 {
ul, err := url.List(client, componentDesc.Name, app.Name)
if err != nil {
return errors.Wrap(err, "Could not get url list")
}
log.Info(" This component has following urls that will be deleted with component")
for _, u := range ul.Items {
log.Info(" URL named ", u.GetName(), " with host ", u.Spec.Host, " having protocol ", u.Spec.Protocol, " at port ", u.Spec.Port)
}
}
storages, err := storage.List(client, currentComponent.Name, app.Name)
odoutil.LogErrorAndExit(err, "")
if len(storages.Items) != 0 {
log.Info(" This component has following storages which will be deleted with the component")
for _, storageName := range componentDesc.Spec.Storage {
store := storages.Get(storageName)
log.Info(" Storage named ", store.GetName(), " of size ", store.Spec.Size)
}
}
}
}
// List services that will be removed
serviceList, err := service.List(client, app.Name)
if err != nil {
log.Info("No services / could not get services")
glog.V(4).Info(err.Error())
}
if len(serviceList) != 0 {
log.Info(" This application has following service that will be deleted")
for _, ser := range serviceList {
log.Info(" service named ", ser.Name, " of type ", ser.Type)
}
}
}
}
return nil
}

View File

@@ -3,16 +3,17 @@
package e2e package e2e
import ( import (
"os"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
"time" "time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
) )
// TODO: A neater way to provide odo path. Currently we assume \ // TODO: A neater way to provide odo path. Currently we assume \
@@ -725,7 +726,7 @@ var _ = Describe("odoe2e", func() {
}) })
It("should delete the deployed image-specific component", func() { It("should delete the deployed image-specific component", func() {
runCmdShouldPass("odo delete testversioncmp") runCmdShouldPass("odo delete testversioncmp -f")
}) })
}) })
@@ -1152,6 +1153,54 @@ var _ = Describe("updateE2e", func() {
Expect(token).To(ContainSubstring(userToken)) Expect(token).To(ContainSubstring(userToken))
}) })
}) })
Context("Deleting application, with component, should list affected children", func() {
projectName := generateTimeBasedName("project")
appName := generateTimeBasedName("app")
componentName := generateTimeBasedName("component")
urlName := generateTimeBasedName("url")
It("Should setup for the tests ,by creating dummy projects to test against", func() {
odoCreateProject(projectName)
runCmdShouldPass("odo app create " + appName)
runCmdShouldPass("odo component create nodejs " + componentName)
runCmdShouldPass("odo url create " + urlName)
})
It("Should list affected child objects", func() {
session := runCmdShouldPass("odo app delete -f " + appName)
Expect(session).To(ContainSubstring("This application has following components that will be deleted"))
Expect(session).To(ContainSubstring(componentName))
Expect(session).To(ContainSubstring(urlName))
})
It("Should delete project", func() {
odoDeleteProject(projectName)
})
})
Context("Deleting project, with application should list affected children", func() {
projectName := generateTimeBasedName("project")
appName := generateTimeBasedName("app")
componentName := generateTimeBasedName("component")
urlName := generateTimeBasedName("url")
It("Should setup for the tests ,by creating dummy projects to test against", func() {
odoCreateProject(projectName)
runCmdShouldPass("odo app create " + appName)
runCmdShouldPass("odo component create nodejs " + componentName)
runCmdShouldPass("odo url create " + urlName)
})
It("Should list affected child objects", func() {
session := runCmdShouldPass("odo project delete -f " + projectName)
Expect(session).To(ContainSubstring("This project contains the following applications, which will be deleted"))
Expect(session).To(ContainSubstring(appName))
Expect(session).To(ContainSubstring(componentName))
Expect(session).To(ContainSubstring(urlName))
})
})
Context("logout of the cluster", func() { Context("logout of the cluster", func() {
// test for odo logout // test for odo logout
It("should logout the user from the cluster", func() { It("should logout the user from the cluster", func() {