Skip to content

Commit 3b7a7ed

Browse files
committed
Add protect feature to avoid delete actions by mistake
As more and more production system uses openwhisk, Users will need some features to protect their service safe from mistake. If we have this feature, user can protect their actions from deletion by mistake.
1 parent 5da62ed commit 3b7a7ed

File tree

2 files changed

+63
-16
lines changed

2 files changed

+63
-16
lines changed

core/controller/src/main/scala/whisk/core/controller/Actions.scala

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
184184
* - 500 Internal Server Error
185185
*/
186186
override def create(user: Identity, entityName: FullyQualifiedEntityName)(implicit transid: TransactionId) = {
187-
parameter('overwrite ? false) { overwrite =>
187+
parameter('overwrite ? false, 'protection ? false) { (overwrite, protection) =>
188188
entity(as[WhiskActionPut]) { content =>
189189
val request = content.resolve(user.namespace)
190190
val checkAdditionalPrivileges = entitleReferencedEntities(user, Privilege.READ, request.exec).flatMap {
@@ -193,9 +193,15 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
193193

194194
onComplete(checkAdditionalPrivileges) {
195195
case Success(_) =>
196-
putEntity(WhiskAction, entityStore, entityName.toDocId, overwrite, update(user, request) _, () => {
197-
make(user, entityName, request)
198-
})
196+
putEntity(
197+
WhiskAction,
198+
entityStore,
199+
entityName.toDocId,
200+
overwrite,
201+
update(user, request, protection) _,
202+
() => {
203+
make(user, entityName, request, protection)
204+
})
199205
case Failure(f) =>
200206
super.handleEntitlementFailure(f)
201207
}
@@ -307,7 +313,21 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
307313
* - 500 Internal Server Error
308314
*/
309315
override def remove(user: Identity, entityName: FullyQualifiedEntityName)(implicit transid: TransactionId) = {
310-
deleteEntity(WhiskAction, entityStore, entityName.toDocId, (a: WhiskAction) => Future.successful({}))
316+
getEntity(WhiskAction.get(entityStore, entityName.toDocId), Some {
317+
action: WhiskAction =>
318+
action.annotations.get("protection") match {
319+
case Some(value) => {
320+
if (value.convertTo[String] == "false") {
321+
deleteEntity(WhiskAction, entityStore, entityName.toDocId, (a: WhiskAction) => Future.successful({}))
322+
} else {
323+
complete(MethodNotAllowed, "this action can't be deleted until protection annotation is updated to false")
324+
}
325+
}
326+
case None => {
327+
deleteEntity(WhiskAction, entityStore, entityName.toDocId, (a: WhiskAction) => Future.successful({}))
328+
}
329+
}
330+
})
311331
}
312332

313333
/**
@@ -387,7 +407,7 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
387407
/**
388408
* Creates a WhiskAction instance from the PUT request.
389409
*/
390-
private def makeWhiskAction(content: WhiskActionPut, entityName: FullyQualifiedEntityName)(
410+
private def makeWhiskAction(content: WhiskActionPut, entityName: FullyQualifiedEntityName, protection: Boolean)(
391411
implicit transid: TransactionId) = {
392412
val exec = content.exec.get
393413
val limits = content.limits map { l =>
@@ -412,7 +432,9 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
412432
limits,
413433
content.version getOrElse SemVer(),
414434
content.publish getOrElse false,
415-
(content.annotations getOrElse Parameters()) ++ execAnnotation(exec))
435+
(content.annotations getOrElse Parameters()) ++ execAnnotation(exec) ++ Parameters(
436+
"protection",
437+
if (protection) "true" else "false"))
416438
}
417439

418440
/** For a sequence action, gather referenced entities and authorize access. */
@@ -437,37 +459,38 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
437459
}
438460

439461
/** Creates a WhiskAction from PUT content, generating default values where necessary. */
440-
private def make(user: Identity, entityName: FullyQualifiedEntityName, content: WhiskActionPut)(
462+
private def make(user: Identity, entityName: FullyQualifiedEntityName, content: WhiskActionPut, protection: Boolean)(
441463
implicit transid: TransactionId) = {
442464
content.exec map {
443465
case seq: SequenceExec =>
444466
// check that the sequence conforms to max length and no recursion rules
445467
checkSequenceActionLimits(entityName, seq.components) map { _ =>
446-
makeWhiskAction(content.replace(seq), entityName)
468+
makeWhiskAction(content.replace(seq), entityName, protection)
447469
}
448470
case supportedExec if !supportedExec.deprecated =>
449-
Future successful makeWhiskAction(content, entityName)
471+
Future successful makeWhiskAction(content, entityName, protection)
450472
case deprecatedExec =>
451473
Future failed RejectRequest(BadRequest, runtimeDeprecated(deprecatedExec))
452474

453475
} getOrElse Future.failed(RejectRequest(BadRequest, "exec undefined"))
454476
}
455477

456478
/** Updates a WhiskAction from PUT content, merging old action where necessary. */
457-
private def update(user: Identity, content: WhiskActionPut)(action: WhiskAction)(implicit transid: TransactionId) = {
479+
private def update(user: Identity, content: WhiskActionPut, protection: Boolean)(action: WhiskAction)(
480+
implicit transid: TransactionId) = {
458481
content.exec map {
459482
case seq: SequenceExec =>
460483
// check that the sequence conforms to max length and no recursion rules
461484
checkSequenceActionLimits(FullyQualifiedEntityName(action.namespace, action.name), seq.components) map { _ =>
462-
updateWhiskAction(content.replace(seq), action)
485+
updateWhiskAction(content.replace(seq), action, protection)
463486
}
464487
case supportedExec if !supportedExec.deprecated =>
465-
Future successful updateWhiskAction(content, action)
488+
Future successful updateWhiskAction(content, action, protection)
466489
case deprecatedExec =>
467490
Future failed RejectRequest(BadRequest, runtimeDeprecated(deprecatedExec))
468491
} getOrElse {
469492
if (!action.exec.deprecated) {
470-
Future successful updateWhiskAction(content, action)
493+
Future successful updateWhiskAction(content, action, protection)
471494
} else {
472495
Future failed RejectRequest(BadRequest, runtimeDeprecated(action.exec))
473496
}
@@ -477,7 +500,8 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
477500
/**
478501
* Updates a WhiskAction instance from the PUT request.
479502
*/
480-
private def updateWhiskAction(content: WhiskActionPut, action: WhiskAction)(implicit transid: TransactionId) = {
503+
private def updateWhiskAction(content: WhiskActionPut, action: WhiskAction, protection: Boolean)(
504+
implicit transid: TransactionId) = {
481505
val limits = content.limits map { l =>
482506
ActionLimits(
483507
l.timeout getOrElse action.limits.timeout,
@@ -526,7 +550,9 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
526550
limits,
527551
content.version getOrElse action.version.upPatch,
528552
content.publish getOrElse action.publish,
529-
(content.annotations getOrElse action.annotations) ++ execAnnotation(exec))
553+
(content.annotations getOrElse action.annotations) ++ execAnnotation(exec) ++ Parameters(
554+
"protection",
555+
if (protection) "true" else "false"))
530556
.revision[WhiskAction](action.docinfo.rev)
531557
}
532558

tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,27 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi {
401401
}
402402
}
403403

404+
it should "delete action not allowed when protection annotation is true" in {
405+
implicit val tid = transid()
406+
val action = WhiskAction(namespace, aname(), jsDefault(""))
407+
val content = JsObject("exec" -> JsObject("code" -> "".toJson, "kind" -> action.exec.kind.toJson))
408+
Put(s"$collectionPath/${action.name}?protection=true", content) ~> Route.seal(routes(creds)) ~> check {
409+
status should be(OK)
410+
}
411+
412+
Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check {
413+
status should be(MethodNotAllowed)
414+
}
415+
416+
Put(s"$collectionPath/${action.name}?overwrite=true&protection=false", content) ~> Route.seal(routes(creds)) ~> check {
417+
status should be(OK)
418+
}
419+
420+
Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check {
421+
status should be(OK)
422+
}
423+
}
424+
404425
it should "report NotFound for delete non existent action" in {
405426
implicit val tid = transid()
406427
Delete(s"$collectionPath/xyz") ~> Route.seal(routes(creds)) ~> check {

0 commit comments

Comments
 (0)