Skip to content

Commit b63643e

Browse files
committed
Add prompt on change when delete or update action
As more and more production system uses openwhisk, Users will need some feature to protect their action to be deleted or updated by mistake.
1 parent e01606d commit b63643e

File tree

6 files changed

+177
-29
lines changed

6 files changed

+177
-29
lines changed

commands/action.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ var actionUpdateCmd = &cobra.Command{
139139
return actionParseError(cmd, args, err)
140140
}
141141

142+
if Properties.PromptOnChange {
143+
if !Flags.action.force {
144+
errMsg := wski18n.T("please update action using --force if you really want to update it")
145+
whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
146+
whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
147+
return whiskErr
148+
}
149+
}
150+
142151
if _, _, err = Client.Actions.Insert(action, true); err != nil {
143152
return actionInsertError(action, err)
144153
}
@@ -335,6 +344,15 @@ var actionDeleteCmd = &cobra.Command{
335344

336345
Client.Namespace = qualifiedName.GetNamespace()
337346

347+
if Properties.PromptOnChange {
348+
if !Flags.action.force {
349+
errMsg := wski18n.T("please delete action using --force if you really want to delete it")
350+
whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
351+
whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
352+
return whiskErr
353+
}
354+
}
355+
338356
if _, err = Client.Actions.Delete(qualifiedName.GetEntityName()); err != nil {
339357
return actionDeleteError(qualifiedName.GetEntityName(), err)
340358
}
@@ -1269,6 +1287,8 @@ func init() {
12691287
actionCreateCmd.Flags().StringVar(&Flags.action.web, WEB_FLAG, "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
12701288
actionCreateCmd.Flags().StringVar(&Flags.action.websecure, WEB_SECURE_FLAG, "", wski18n.T("secure the web action. where `SECRET` is true, false, or any string. Only valid when the ACTION is a web action"))
12711289

1290+
actionDeleteCmd.Flags().BoolVar(&Flags.action.force, "force", false, wski18n.T("force to do this operation when property promptOnChange is true"))
1291+
12721292
actionUpdateCmd.Flags().BoolVar(&Flags.action.native, "native", false, wski18n.T("treat ACTION as native action (zip file provides a compatible executable to run)"))
12731293
actionUpdateCmd.Flags().StringVar(&Flags.action.docker, "docker", "", wski18n.T("use provided docker image (a path on DockerHub) to run the action"))
12741294
actionUpdateCmd.Flags().BoolVar(&Flags.action.copy, "copy", false, wski18n.T("treat ACTION as the name of an existing action"))
@@ -1284,6 +1304,7 @@ func init() {
12841304
actionUpdateCmd.Flags().StringVarP(&Flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))
12851305
actionUpdateCmd.Flags().StringVar(&Flags.action.web, WEB_FLAG, "", wski18n.T("treat ACTION as a web action, a raw HTTP web action, or as a standard action; yes | true = web action, raw = raw HTTP web action, no | false = standard action"))
12861306
actionUpdateCmd.Flags().StringVar(&Flags.action.websecure, WEB_SECURE_FLAG, "", wski18n.T("secure the web action. where `SECRET` is true, false, or any string. Only valid when the ACTION is a web action"))
1307+
actionUpdateCmd.Flags().BoolVar(&Flags.action.force, "force", false, wski18n.T("force to do this operation when property promptOnChange is true"))
12871308

12881309
actionInvokeCmd.Flags().StringSliceVarP(&Flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format"))
12891310
actionInvokeCmd.Flags().StringVarP(&Flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format"))

commands/flags.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,21 @@ type FlagsStruct struct {
6969
}
7070

7171
property struct {
72-
cert bool
73-
key bool
74-
auth bool
75-
apihost bool
76-
apiversion bool
77-
namespace bool
78-
cliversion bool
79-
apibuild bool
80-
apibuildno bool
81-
insecure bool
82-
all bool
83-
apihostSet string
84-
apiversionSet string
85-
namespaceSet string
72+
cert bool
73+
key bool
74+
auth bool
75+
apihost bool
76+
apiversion bool
77+
namespace bool
78+
promptOnChange bool
79+
cliversion bool
80+
apibuild bool
81+
apibuildno bool
82+
insecure bool
83+
all bool
84+
apihostSet string
85+
apiversionSet string
86+
namespaceSet string
8687
}
8788

8889
action ActionFlags
@@ -145,6 +146,7 @@ type ActionFlags struct {
145146
url bool
146147
save bool
147148
saveAs string
149+
force bool
148150
}
149151

150152
func IsVerbose() bool {

commands/property.go

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,17 @@ import (
3131
)
3232

3333
var Properties struct {
34-
Cert string
35-
Key string
36-
Auth string
37-
APIHost string
38-
APIVersion string
39-
APIBuild string
40-
APIBuildNo string
41-
CLIVersion string
42-
Namespace string
43-
PropsFile string
34+
Cert string
35+
Key string
36+
Auth string
37+
APIHost string
38+
APIVersion string
39+
APIBuild string
40+
APIBuildNo string
41+
CLIVersion string
42+
Namespace string
43+
PromptOnChange bool
44+
PropsFile string
4445
}
4546

4647
const DefaultCert string = ""
@@ -51,6 +52,7 @@ const DefaultAPIVersion string = "v1"
5152
const DefaultAPIBuild string = ""
5253
const DefaultAPIBuildNo string = ""
5354
const DefaultNamespace string = "_"
55+
const DefaultPromptOnChange bool = false
5456
const DefaultPropsFile string = "~/.wskprops"
5557

5658
var propertyCmd = &cobra.Command{
@@ -165,6 +167,13 @@ var propertySetCmd = &cobra.Command{
165167
}
166168
}
167169

170+
if promptOnChange := Flags.property.promptOnChange; promptOnChange {
171+
props["PROMPTONCHANGE"] = "true"
172+
okMsg += fmt.Sprintf(
173+
wski18n.T("{{.ok}} whisk promptOnChange set to {{.name}}\n",
174+
map[string]interface{}{"ok": color.GreenString("ok:"), "name": boldString(promptOnChange)}))
175+
}
176+
168177
err = WriteProps(Properties.PropsFile, props)
169178
if err != nil {
170179
whisk.Debug(whisk.DbgError, "writeProps(%s, %#v) failed: %s\n", Properties.PropsFile, props, err)
@@ -261,6 +270,13 @@ var propertyUnsetCmd = &cobra.Command{
261270
}
262271
}
263272

273+
if Flags.property.promptOnChange {
274+
delete(props, "PROMPTONCHANGE")
275+
okMsg += fmt.Sprintf(
276+
wski18n.T("{{.ok}} whisk promptOnChange unset.\n",
277+
map[string]interface{}{"ok": color.GreenString("ok:")}))
278+
}
279+
264280
err = WriteProps(Properties.PropsFile, props)
265281
if err != nil {
266282
whisk.Debug(whisk.DbgError, "writeProps(%s, %#v) failed: %s\n", Properties.PropsFile, props, err)
@@ -291,8 +307,9 @@ var propertyGetCmd = &cobra.Command{
291307
if !(Flags.property.all || Flags.property.cert ||
292308
Flags.property.key || Flags.property.auth ||
293309
Flags.property.apiversion || Flags.property.cliversion ||
294-
Flags.property.namespace || Flags.property.apibuild ||
295-
Flags.property.apihost || Flags.property.apibuildno) {
310+
Flags.property.namespace || Flags.property.promptOnChange ||
311+
Flags.property.apibuild || Flags.property.apihost ||
312+
Flags.property.apibuildno) {
296313
Flags.property.all = true
297314
}
298315

@@ -320,6 +337,10 @@ var propertyGetCmd = &cobra.Command{
320337
fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk namespace"), boldString(Properties.Namespace))
321338
}
322339

340+
if Flags.property.all || Flags.property.promptOnChange {
341+
fmt.Fprintf(color.Output, "%s\t\t%s\n", wski18n.T("whisk promptOnChange"), boldString(Properties.PromptOnChange))
342+
}
343+
323344
if Flags.property.all || Flags.property.cliversion {
324345
fmt.Fprintf(color.Output, "%s\t%s\n", wski18n.T("whisk CLI version"), boldString(Properties.CLIVersion))
325346
}
@@ -368,6 +389,7 @@ func init() {
368389
propertyGetCmd.Flags().BoolVar(&Flags.property.apibuildno, "apibuildno", false, wski18n.T("whisk API build number"))
369390
propertyGetCmd.Flags().BoolVar(&Flags.property.cliversion, "cliversion", false, wski18n.T("whisk CLI version"))
370391
propertyGetCmd.Flags().BoolVar(&Flags.property.namespace, "namespace", false, wski18n.T("whisk namespace"))
392+
propertyGetCmd.Flags().BoolVar(&Flags.property.promptOnChange, "promptOnChange", false, wski18n.T("whisk promptOnChange"))
371393
propertyGetCmd.Flags().BoolVar(&Flags.property.all, "all", false, wski18n.T("all properties"))
372394

373395
propertySetCmd.Flags().StringVarP(&Flags.Global.Auth, "auth", "u", "", wski18n.T("authorization `KEY`"))
@@ -376,13 +398,15 @@ func init() {
376398
propertySetCmd.Flags().StringVar(&Flags.property.apihostSet, "apihost", "", wski18n.T("whisk API `HOST`"))
377399
propertySetCmd.Flags().StringVar(&Flags.property.apiversionSet, "apiversion", "", wski18n.T("whisk API `VERSION`"))
378400
propertySetCmd.Flags().StringVar(&Flags.property.namespaceSet, "namespace", "", wski18n.T("whisk `NAMESPACE`"))
401+
propertySetCmd.Flags().BoolVar(&Flags.property.promptOnChange, "promptOnChange", false, wski18n.T("whisk promptOnChange"))
379402

380403
propertyUnsetCmd.Flags().BoolVar(&Flags.property.cert, "cert", false, wski18n.T("client cert"))
381404
propertyUnsetCmd.Flags().BoolVar(&Flags.property.key, "key", false, wski18n.T("client key"))
382405
propertyUnsetCmd.Flags().BoolVar(&Flags.property.auth, "auth", false, wski18n.T("authorization key"))
383406
propertyUnsetCmd.Flags().BoolVar(&Flags.property.apihost, "apihost", false, wski18n.T("whisk API host"))
384407
propertyUnsetCmd.Flags().BoolVar(&Flags.property.apiversion, "apiversion", false, wski18n.T("whisk API version"))
385408
propertyUnsetCmd.Flags().BoolVar(&Flags.property.namespace, "namespace", false, wski18n.T("whisk namespace"))
409+
propertyUnsetCmd.Flags().BoolVar(&Flags.property.promptOnChange, "promptOnChange", false, wski18n.T("whisk promptOnChange"))
386410

387411
}
388412

@@ -391,6 +415,7 @@ func SetDefaultProperties() {
391415
Properties.Cert = DefaultKey
392416
Properties.Auth = DefaultAuth
393417
Properties.Namespace = DefaultNamespace
418+
Properties.PromptOnChange = DefaultPromptOnChange
394419
Properties.APIHost = DefaultAPIHost
395420
Properties.APIBuild = DefaultAPIBuild
396421
Properties.APIBuildNo = DefaultAPIBuildNo
@@ -492,6 +517,10 @@ func loadProperties() error {
492517
Properties.Namespace = namespace
493518
}
494519

520+
if promptOnChange, hasProp := props["PROMPTONCHANGE"]; hasProp && len(promptOnChange) > 0 && promptOnChange == "true" {
521+
Properties.PromptOnChange = true
522+
}
523+
495524
return nil
496525
}
497526

tests/src/integration/command_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func TestValidateDefaultProperties(t *testing.T) {
100100
os.Setenv("WSK_CONFIG_FILE", tmpProp)
101101
assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
102102

103-
stdout, err := wsk.RunCommand("property", "unset", "--auth", "--apihost", "--apiversion", "--namespace")
103+
stdout, err := wsk.RunCommand("property", "unset", "--auth", "--apihost", "--apiversion", "--namespace", "--promptOnChange")
104104
assert.Equal(t, nil, err, "The command property unset failed to run.")
105105
outputString := string(stdout)
106106
assert.Contains(t, outputString, "ok: whisk auth unset",
@@ -111,6 +111,8 @@ func TestValidateDefaultProperties(t *testing.T) {
111111
"The output of the command does not contain \"ok: whisk API version unset\".")
112112
assert.Contains(t, outputString, "ok: whisk namespace unset",
113113
"The output of the command does not contain \"ok: whisk namespace unset\".")
114+
assert.Contains(t, outputString, "ok: whisk promptOnChange unset",
115+
"The output of the command does not contain \"ok: whisk promptOnChange unset\".")
114116

115117
stdout, err = wsk.RunCommand("property", "get", "--auth")
116118
assert.Equal(t, nil, err, "The command property get --auth failed to run.")
@@ -127,6 +129,11 @@ func TestValidateDefaultProperties(t *testing.T) {
127129
assert.Equal(t, "whisk namespace _", common.RemoveRedundentSpaces(string(stdout)),
128130
"The output of the command does not equal to \"whisk namespace _\".")
129131

132+
stdout, err = wsk.RunCommand("property", "get", "--promptOnChange")
133+
assert.Equal(t, nil, err, "The command property get --promptOnChange failed to run.")
134+
assert.Equal(t, "whisk promptOnChange", common.RemoveRedundentSpaces(string(stdout)),
135+
"The output of the command does not equal to \"whisk promptOnChange\".")
136+
130137
common.DeleteFile(tmpProp)
131138
}
132139

@@ -145,6 +152,21 @@ func TestSetAuth(t *testing.T) {
145152
common.DeleteFile(tmpProp)
146153
}
147154

155+
// Test case to set promptOnChange in property file.
156+
func TestSetPromptOnChange(t *testing.T) {
157+
common.CreateFile(tmpProp)
158+
159+
os.Setenv("WSK_CONFIG_FILE", tmpProp)
160+
assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
161+
162+
_, err := wsk.RunCommand("property", "set", "--promptOnChange")
163+
assert.Equal(t, nil, err, "The command property set --promptOnChange failed to run.")
164+
output := common.ReadFile(tmpProp)
165+
assert.Contains(t, output, "PROMPTONCHANGE=true",
166+
"The wsk property file does not contain \"PROMPTONCHANGE=true\".")
167+
common.DeleteFile(tmpProp)
168+
}
169+
148170
// Test case to set multiple property values with single command.
149171
func TestSetMultipleValues(t *testing.T) {
150172
common.CreateFile(tmpProp)
@@ -153,13 +175,14 @@ func TestSetMultipleValues(t *testing.T) {
153175
assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
154176

155177
_, err := wsk.RunCommand("property", "set", "--auth", "testKey", "--apihost", "openwhisk.ng.bluemix.net",
156-
"--apiversion", "v1")
178+
"--apiversion", "v1", "--promptOnChange")
157179
assert.Equal(t, nil, err, "The command property set --auth --apihost --apiversion failed to run.")
158180
output := common.ReadFile(tmpProp)
159181
assert.Contains(t, output, "AUTH=testKey", "The wsk property file does not contain \"AUTH=testKey\".")
160182
assert.Contains(t, output, "APIHOST=openwhisk.ng.bluemix.net",
161183
"The wsk property file does not contain \"APIHOST=openwhisk.ng.bluemix.net\".")
162184
assert.Contains(t, output, "APIVERSION=v1", "The wsk property file does not contain \"APIVERSION=v1\".")
185+
assert.Contains(t, output, "PROMPTONCHANGE=true", "The wsk property file does not contain \"PROMPTONCHANGE=true\".")
163186
common.DeleteFile(tmpProp)
164187
}
165188

tests/src/integration/integration_test.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ func TestSetAPIHostAuthNamespace(t *testing.T) {
342342
namespaces := strings.Split(strings.TrimSpace(string(namespace)), "\n")
343343
expectedNamespace := string(namespaces[len(namespaces)-1])
344344
fmt.Println(wsk.Wskprops.APIHost)
345-
if wsk.Wskprops.APIHost != "" && wsk.Wskprops.APIHost != "" {
345+
if wsk.Wskprops.APIHost != "" && wsk.Wskprops.AuthKey != "" {
346346
stdout, err := wsk.RunCommand("property", "set", "--apihost", wsk.Wskprops.APIHost,
347347
"--auth", wsk.Wskprops.AuthKey, "--namespace", expectedNamespace)
348348
ouputString := string(stdout)
@@ -357,6 +357,55 @@ func TestSetAPIHostAuthNamespace(t *testing.T) {
357357
common.DeleteFile(tmpProp)
358358
}
359359

360+
// Test delete action when property promptOnChange is true
361+
func TestDeleteActionWhenPromptOnChangeIsTrue(t *testing.T) {
362+
common.CreateFile(tmpProp)
363+
common.WriteFile(tmpProp, []string{})
364+
365+
os.Setenv("WSK_CONFIG_FILE", tmpProp)
366+
assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
367+
368+
namespace, _ := wsk.ListNamespaces()
369+
namespaces := strings.Split(strings.TrimSpace(string(namespace)), "\n")
370+
expectedNamespace := string(namespaces[len(namespaces)-1])
371+
fmt.Println(wsk.Wskprops.APIHost)
372+
if wsk.Wskprops.APIHost != "" && wsk.Wskprops.AuthKey != "" {
373+
stdout, err := wsk.RunCommand("property", "set", "--apihost", wsk.Wskprops.APIHost,
374+
"--auth", wsk.Wskprops.AuthKey, "--namespace", expectedNamespace, "--promptOnChange")
375+
assert.Equal(t, nil, err, "The command property set --apihost --auth --namespace --promptOnChange failed to run.")
376+
stdout, err := wsk.RunCommand("action", "delete", "actionName")
377+
assert.Equal(t, nil, err, "The command action delete actionName failed to run.")
378+
assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "please delete action using --force if you really want to delete it",
379+
"The output of the command does not contain \"please delete action using --force if you really want to delete it\".")
380+
}
381+
common.DeleteFile(tmpProp)
382+
}
383+
384+
// Test update action when property promptOnChange is true
385+
func TestUpdateActionWhenPromptOnChangeIsTrue(t *testing.T) {
386+
common.CreateFile(tmpProp)
387+
common.WriteFile(tmpProp, []string{})
388+
389+
os.Setenv("WSK_CONFIG_FILE", tmpProp)
390+
assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
391+
392+
namespace, _ := wsk.ListNamespaces()
393+
namespaces := strings.Split(strings.TrimSpace(string(namespace)), "\n")
394+
expectedNamespace := string(namespaces[len(namespaces)-1])
395+
fmt.Println(wsk.Wskprops.APIHost)
396+
if wsk.Wskprops.APIHost != "" && wsk.Wskprops.AuthKey != "" {
397+
stdout, err := wsk.RunCommand("property", "set", "--apihost", wsk.Wskprops.APIHost,
398+
"--auth", wsk.Wskprops.AuthKey, "--namespace", expectedNamespace, "--promptOnChange")
399+
assert.Equal(t, nil, err, "The command property set --apihost --auth --namespace --promptOnChange failed to run.")
400+
helloFile := common.GetTestActionFilename("hello.js")
401+
stdout, err := wsk.RunCommand("action", "update", "hello", helloFile)
402+
assert.Equal(t, nil, err, "The command action update hello failed to run.")
403+
assert.Contains(t, common.RemoveRedundentSpaces(string(stdout)), "please update action using --force if you really want to update it",
404+
"The output of the command does not contain \"please update action using --force if you really want to update it\".")
405+
}
406+
common.DeleteFile(tmpProp)
407+
}
408+
360409
// Test case to show api build version using property file.
361410
func TestShowAPIBuildVersion(t *testing.T) {
362411
common.CreateFile(tmpProp)

wski18n/resources/en_US.all.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,26 @@
288288
"id": "{{.ok}} whisk namespace set to {{.name}}\n",
289289
"translation": "{{.ok}} whisk namespace set to {{.name}}\n"
290290
},
291+
{
292+
"id": "{{.ok}} whisk promptOnChange set to {{.name}}\n",
293+
"translation": "{{.ok}} whisk promptOnChange set to {{.name}}\n"
294+
},
295+
{
296+
"id": "{{.ok}} whisk promptOnChange unset.\n",
297+
"translation": "{{.ok}} whisk promptOnChange unset.\n"
298+
},
299+
{
300+
"id": "force to do this operation when property promptOnChange is true",
301+
"translation": "force to do this operation when property promptOnChange is true"
302+
},
303+
{
304+
"id": "please update action using --force if you really want to update it",
305+
"translation": "please update action using --force if you really want to update it"
306+
},
307+
{
308+
"id": "please delete action using --force if you really want to delete it",
309+
"translation": "please delete action using --force if you really want to delete it"
310+
},
291311
{
292312
"id": "Unable to set the property value(s): {{.err}}",
293313
"translation": "Unable to set the property value(s): {{.err}}"
@@ -360,6 +380,10 @@
360380
"id": "whisk namespace",
361381
"translation": "whisk namespace"
362382
},
383+
{
384+
"id": "whisk promptOnChange",
385+
"translation": "whisk promptOnChange"
386+
},
363387
{
364388
"id": "whisk CLI version",
365389
"translation": "whisk CLI version"

0 commit comments

Comments
 (0)