Skip to content

Commit 14f6cef

Browse files
cf delete wrapper plugin
initial commit
1 parent 9e5efc5 commit 14f6cef

File tree

5 files changed

+365
-1
lines changed

5 files changed

+365
-1
lines changed

README.md

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,92 @@
1-
# cf-delete-wrapper-plugin
1+
# cf-cli-delete-wrapper-plugin
2+
3+
A cf cli plugin that is a wrapper on top of "cf delete" command.
4+
This plugin wrapper provides two commands that helps to easy cleanup of apps.
5+
6+
1. You can now delete multiple apps via single command
7+
2. Command that auto detects the app name from manifest and deletes them
8+
9+
# Installation
10+
11+
You can use either of the below method to install the plugin.
12+
13+
**Option 1:**
14+
15+
1. Download the latest plugin from the [release](https://github.com/faisaltheparttimecoder/cf-cli-delete-wrapper-plugin/releases) section of this repository
16+
2. Install the plugin with `cf install-plugin <path_to_binary>`. Use -f flag to uninstall existing plugin if any and install the new one.
17+
18+
**Option 2:**
19+
20+
If you are using MacOS, you could run
21+
22+
cf install-plugin -f https://github.com/faisaltheparttimecoder/cf-cli-delete-wrapper-plugin/releases/cf-delete-wrapper_0.1.0.osx
23+
24+
# Usage
25+
26+
1. **Command:** delete-multi-apps, **Alias:** dma
27+
```
28+
$ cf delete-multi-apps --help
29+
NAME:
30+
delete-multi-apps - Delete multiple apps via a single command
31+
32+
ALIAS:
33+
dma
34+
35+
USAGE:
36+
cf delete-multi-apps <APP1>,<APP2>,....,<APPn>
37+
38+
OPTIONS:
39+
-force -f, no need to prompt for confirmation
40+
-app -a, list of apps to be deleted
41+
```
42+
43+
2. **Command:** delete-app-using-manifest, **Alias:** daum
44+
```
45+
$ cf delete-app-using-manifest --help
46+
NAME:
47+
delete-app-using-manifest - Detect the apps name from manifest and delete it
48+
49+
ALIAS:
50+
daum
51+
52+
USAGE:
53+
cf delete-app-using-manifest
54+
55+
OPTIONS:
56+
-force -f, no need to prompt for confirmation
57+
```
58+
59+
# Example
60+
61+
+ To delete multiple at once
62+
```
63+
$ cf dma -a test1,test2,test3
64+
Are you sure you want to delete these apps (test1,test2,test3), do you wish to continue (Yy/Nn)?: y
65+
66+
Successfully deleted the app "test1"
67+
68+
Successfully deleted the app "test2"
69+
70+
Successfully deleted the app "test3"
71+
```
72+
73+
+ To delete an app that is on the manifest
74+
```
75+
$ cat manifest.yml
76+
---
77+
applications:
78+
- name: customer-test
79+
80+
$ cf daum
81+
Are you sure you want to delete these apps (customer-test), do you wish to continue (Yy/Nn)?: y
82+
83+
Successfully deleted the app "customer-test"
84+
```
85+
86+
# Build
87+
88+
```
89+
go get github.com/faisaltheparttimecoder/cf-cli-delete-wrapper-plugin/
90+
-- Modify the code
91+
run "/bin/sh run.sh" to build package
92+
```

build.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
3+
PLUGIN_NAME=$1
4+
PLUGIN_VERSION=$2
5+
6+
# Linux
7+
GOOS=linux GOARCH=amd64 go build -o ${PLUGIN_NAME}_${PLUGIN_VERSION}.linux64
8+
GOOS=linux GOARCH=386 go build -o ${PLUGIN_NAME}_${PLUGIN_VERSION}.linux32
9+
10+
# Windows
11+
GOOS=windows GOARCH=amd64 go build -o ${PLUGIN_NAME}_${PLUGIN_VERSION}.win64
12+
GOOS=windows GOARCH=386 go build -o ${PLUGIN_NAME}_${PLUGIN_VERSION}.win32
13+
14+
# Mac OS X
15+
GOOS=darwin GOARCH=amd64 go build -o ${PLUGIN_NAME}_${PLUGIN_VERSION}.osx

plugin.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package main
2+
3+
import (
4+
"code.cloudfoundry.org/cli/cf/flags"
5+
"code.cloudfoundry.org/cli/plugin"
6+
"fmt"
7+
"gopkg.in/yaml.v2"
8+
)
9+
10+
type cfDeleteWrapper struct{}
11+
12+
// Flags that is used by the cf delete wrapper
13+
type WrapperFlags struct {
14+
AppName string
15+
Force bool
16+
}
17+
18+
// Manifest struct, we only care about the app name
19+
type Manifest struct {
20+
Applications []struct {
21+
Name string `yaml:"name"`
22+
} `yaml:"applications"`
23+
}
24+
25+
// Subcommand names
26+
const (
27+
multiAppDeleteCmd = "delete-multi-apps"
28+
deleteAppUsingManifestcmd = "delete-app-using-manifest"
29+
)
30+
31+
// Initialize a new flags wrapper
32+
var cmdOptions = new(WrapperFlags)
33+
34+
// Run is what is executed by the Cloud Foundry CLI
35+
func (c *cfDeleteWrapper) Run(cliConnection plugin.CliConnection, args []string) {
36+
switch args[0] {
37+
case multiAppDeleteCmd:
38+
c.buildMultiAppDeleteArguments(args, true)
39+
c.MultiAppDelete(cliConnection)
40+
case deleteAppUsingManifestcmd:
41+
c.buildMultiAppDeleteArguments(args, false)
42+
c.DeleteAppUsingManifest(cliConnection)
43+
}
44+
}
45+
46+
// GetMetadata provides the Cloud Foundry CLI with metadata on how to use this plugin
47+
func (c *cfDeleteWrapper) GetMetadata() plugin.PluginMetadata {
48+
return plugin.PluginMetadata{
49+
Name: "CfDeleteWrapper",
50+
Version: plugin.VersionType{
51+
Major: 0,
52+
Minor: 1,
53+
Build: 0,
54+
},
55+
MinCliVersion: plugin.VersionType{
56+
Major: 6,
57+
Minor: 7,
58+
Build: 0,
59+
},
60+
Commands: []plugin.Command{
61+
{
62+
Name: multiAppDeleteCmd,
63+
Alias: "dma",
64+
HelpText: "Delete multiple apps via a single command",
65+
UsageDetails: plugin.Usage{
66+
Usage: fmt.Sprintf("cf %s <APP1>,<APP2>,....,<APPn>", multiAppDeleteCmd),
67+
Options: map[string]string{
68+
"app": "-a, list of apps to be deleted",
69+
"force": "-f, no need to prompt for confirmation",
70+
},
71+
},
72+
},
73+
{
74+
Name: deleteAppUsingManifestcmd,
75+
Alias: "daum",
76+
HelpText: "Detect the apps name from manifest and delete it",
77+
UsageDetails: plugin.Usage{
78+
Usage: fmt.Sprintf("cf %s", deleteAppUsingManifestcmd),
79+
Options: map[string]string{
80+
"force": "-f, no need to prompt for confirmation",
81+
},
82+
},
83+
},
84+
},
85+
}
86+
}
87+
88+
// Build a list of argument flags to be used by this wrapper
89+
func (c *cfDeleteWrapper) buildMultiAppDeleteArguments(args []string, mandatory bool) {
90+
fc := flags.New()
91+
fc.NewBoolFlag("force", "f", "don't prompt for confirmation")
92+
fc.NewStringFlag("app", "a", "list of apps to delete")
93+
err := fc.Parse(args[1:]...)
94+
if err != nil {
95+
handleError(fmt.Sprintf("%v", err), true)
96+
}
97+
if fc.IsSet("f") {
98+
cmdOptions.Force = true
99+
}
100+
if fc.IsSet("app") {
101+
cmdOptions.AppName = fc.String("app")
102+
}
103+
if cmdOptions.AppName == "" && mandatory {
104+
handleError("Mandatory argument app name is missing", true)
105+
}
106+
}
107+
108+
// Program that helps to delete multiple apps via single command
109+
func (c *cfDeleteWrapper) MultiAppDelete(cli plugin.CliConnection) {
110+
111+
// Get the list of apps to be delete
112+
apps := spiltString(cmdOptions.AppName)
113+
114+
// Check with user if they want to continue
115+
if !cmdOptions.Force {
116+
yesOrNoConfirmation(cmdOptions.AppName)
117+
}
118+
119+
// Get the app and delete the app
120+
for _, app := range apps {
121+
checkDeleteApp(cli, app, false)
122+
}
123+
124+
}
125+
126+
// Reads the manifest and delete the app
127+
func (c *cfDeleteWrapper) DeleteAppUsingManifest(cli plugin.CliConnection) {
128+
129+
// Manifest
130+
m := new(Manifest)
131+
132+
// Check if the manifest file exists and get the content
133+
output := readManifest()
134+
135+
// Get the app name from the manifest
136+
err := yaml.Unmarshal(output, &m)
137+
if err != nil {
138+
handleError(fmt.Sprintf("Encounteed error when unmarshaling the manifest ym, err: %v", err), true)
139+
}
140+
141+
// Check & Delete the app name
142+
if len(m.Applications) > 0 && m.Applications[0].Name != "" {
143+
appName := m.Applications[0].Name
144+
if !cmdOptions.Force {
145+
yesOrNoConfirmation(appName)
146+
}
147+
checkDeleteApp(cli, appName, true)
148+
} else {
149+
handleError("Unable to find any information from manifest", true)
150+
}
151+
}
152+
153+
// Check & Delete the app
154+
func checkDeleteApp(cli plugin.CliConnection, app string, exit bool) {
155+
appInfo, _ := cli.GetApp(app)
156+
157+
// App not found
158+
if appInfo.Guid == "" {
159+
handleError(fmt.Sprintf("ERROR: App \"%s\" not found on the current org & space, continuing with remaining apps...", app), exit)
160+
} else {
161+
// Run the curl command to delete the app
162+
output, err := cli.CliCommandWithoutTerminalOutput("curl", "-X", "DELETE", fmt.Sprintf("/v2/apps/%s", appInfo.Guid))
163+
if err != nil {
164+
handleError(fmt.Sprintf("ERROR: Received error when deleting the app \"%s\", err: %v", appInfo.Name, err), exit)
165+
fmt.Println("continuing with other apps, if there is any...")
166+
}
167+
168+
// If we found any message, that means the CLI wants to tell us something is wrong
169+
if len(output) > 1 {
170+
handleError(fmt.Sprintf("ERROR: Something went wrong when deleting the app \"%s\", message: \n%v", appInfo.Name, output), exit)
171+
fmt.Println("continuing with other apps, if there is any...")
172+
} else {
173+
fmt.Println("\nSuccessfully deleted the app \"" + appInfo.Name + "\"")
174+
}
175+
}
176+
}
177+
178+
// Initialize and start the plugin
179+
func main() {
180+
plugin.Start(new(cfDeleteWrapper))
181+
}

run.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
3+
PLUGIN_VERSION=v0.1.0
4+
PLUGIN_NAME=cf-delete-wrapper
5+
6+
# Create the build
7+
/bin/sh build.sh ${PLUGIN_NAME} ${PLUGIN_VERSION}
8+
9+
# Install the plugin
10+
cf install-plugin ${PLUGIN_NAME}_${PLUGIN_VERSION}.osx -f

worker.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"strings"
9+
)
10+
11+
// Split the string
12+
func spiltString(str string) []string {
13+
return strings.Split(str, ",")
14+
}
15+
16+
// Handle error
17+
func handleError(err string, exitOnError bool) {
18+
fmt.Println("\n" + err)
19+
if exitOnError {
20+
os.Exit(1)
21+
}
22+
}
23+
24+
// Prompt for confirmation
25+
func yesOrNoConfirmation(appName string) string {
26+
var YesOrNo = map[string]string{"y": "y", "ye": "y", "yes": "y", "n": "n", "no": "n"}
27+
28+
// Start the new scanner to get the user input
29+
fmt.Printf("Are you sure you want to delete these apps (%s), do you wish to continue (Yy/Nn)?: ", appName)
30+
input := bufio.NewScanner(os.Stdin)
31+
for input.Scan() {
32+
33+
// The choice entered
34+
choiceEntered := input.Text()
35+
36+
// If its a valid value move on
37+
if YesOrNo[strings.ToLower(choiceEntered)] == "y" { // Is it Yes
38+
return choiceEntered
39+
} else if YesOrNo[strings.ToLower(choiceEntered)] == "n" { // Is it No
40+
handleError("Canceling as per user request", true)
41+
} else { // Invalid choice, ask to re-enter
42+
fmt.Println("Invalid Choice: Please enter Yy/Nn, try again.")
43+
return yesOrNoConfirmation(appName)
44+
}
45+
}
46+
return ""
47+
}
48+
49+
// Check if manifest file exists in the current working directory
50+
func readManifest() []byte {
51+
manifestFileName := "manifest.yml"
52+
currentDirectory, err := os.Getwd()
53+
fullPath := fmt.Sprintf("%s/%s", currentDirectory, manifestFileName)
54+
if err != nil {
55+
handleError(fmt.Sprintf("Unable to get the current working directory, err: %v", err), true)
56+
}
57+
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
58+
handleError(fmt.Sprintf("Unable to file the manifest file \"%s\"", fullPath), true)
59+
} else {
60+
b, err := ioutil.ReadFile(fullPath) // b has type []byte
61+
if err != nil {
62+
handleError(fmt.Sprintf("Error when reading the manifest \"%s\", err: %v",fullPath, err), true)
63+
}
64+
return b
65+
}
66+
return []byte{}
67+
}

0 commit comments

Comments
 (0)