Skip to content

Commit 7a1a4c5

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 7a1a4c5

File tree

7 files changed

+170
-29
lines changed

7 files changed

+170
-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/commands.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func SetupClientConfig(cmd *cobra.Command, args []string) error {
4444
Flags.property.cert || Flags.property.key || Flags.property.apihost || Flags.property.namespace ||
4545
Flags.property.apiversion || Flags.property.cliversion)) ||
4646
(cmd.Parent().Name() == "property" && cmd.Name() == "set" && (len(Flags.property.apihostSet) > 0 ||
47-
len(Flags.property.apiversionSet) > 0 || len(Flags.Global.Auth) > 0)) ||
47+
len(Flags.property.apiversionSet) > 0 || len(Flags.Global.Auth) > 0 || Flags.property.promptOnChange)) ||
4848
(cmd.Parent().Name() == "sdk" && cmd.Name() == "install" && len(args) > 0 && args[0] == "bashauto")
4949

5050
// Display an error if the parent command requires an API host to be set, and the current API host is not valid

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: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,21 @@ func TestSetAuth(t *testing.T) {
145145
common.DeleteFile(tmpProp)
146146
}
147147

148+
// Test case to set promptOnChange in property file.
149+
func TestSetPromptOnChange(t *testing.T) {
150+
common.CreateFile(tmpProp)
151+
152+
os.Setenv("WSK_CONFIG_FILE", tmpProp)
153+
assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
154+
155+
_, err := wsk.RunCommand("property", "set", "--promptOnChange")
156+
assert.Equal(t, nil, err, "The command property set --promptOnChange failed to run.")
157+
output := common.ReadFile(tmpProp)
158+
assert.Contains(t, output, "PROMPTONCHANGE=true",
159+
"The wsk property file does not contain \"PROMPTONCHANGE=true\".")
160+
common.DeleteFile(tmpProp)
161+
}
162+
148163
// Test case to set multiple property values with single command.
149164
func TestSetMultipleValues(t *testing.T) {
150165
common.CreateFile(tmpProp)
@@ -153,13 +168,14 @@ func TestSetMultipleValues(t *testing.T) {
153168
assert.Equal(t, os.Getenv("WSK_CONFIG_FILE"), tmpProp, "The environment variable WSK_CONFIG_FILE has not been set.")
154169

155170
_, err := wsk.RunCommand("property", "set", "--auth", "testKey", "--apihost", "openwhisk.ng.bluemix.net",
156-
"--apiversion", "v1")
171+
"--apiversion", "v1", "--promptOnChange")
157172
assert.Equal(t, nil, err, "The command property set --auth --apihost --apiversion failed to run.")
158173
output := common.ReadFile(tmpProp)
159174
assert.Contains(t, output, "AUTH=testKey", "The wsk property file does not contain \"AUTH=testKey\".")
160175
assert.Contains(t, output, "APIHOST=openwhisk.ng.bluemix.net",
161176
"The wsk property file does not contain \"APIHOST=openwhisk.ng.bluemix.net\".")
162177
assert.Contains(t, output, "APIVERSION=v1", "The wsk property file does not contain \"APIVERSION=v1\".")
178+
assert.Contains(t, output, "PROMPTONCHANGE=true", "The wsk property file does not contain \"PROMPTONCHANGE=true\".")
163179
common.DeleteFile(tmpProp)
164180
}
165181

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)