Lifecycle
P6 applications can be split into two categories, packaged
applications and updates
.
Updates are application having upgradeScript
and rollbackScript
. They are listed in a dedicated section inside the Application service.
P6 applications are meant to be installed, upgraded, rollback and uninstalled. These steps are listed and explained bellow.
Versions¶
The version of an application is very important and is used to determine when/if upgrade
and rollback
is possible.
You may only upgrade an application to a version greater than the version currently installed.
You may only rollback an application whose version is equal to the version of the upgrade app
If an application is uninstalled once it has been upgraded, it’s status field will be modified to contain the original version of the application and the version it was upgraded to
It will no longer be possible to rollback an upgrade application once it has been uninstalled.
Note
This is important if the application is later downloaded and installed on other platform6 instances
Application lifecycle¶
Installing an Application¶
To install an application, click on the Import button, select your .p6app
file (packaged P6 application) from the file system and upload it.
The upload process will verify the signature of the Application Publisher, and once the bundle is verified, install the application.
The application installation process:
- run the
preInstall
script if one was packaged in the app - unpack all service configuration items and saves them on the instance.
- run the
postInstall
script if one was packaged in the app - restart the concerned services
Note
Services that are known to require a restart to force them to reload their configuration will be automatically restarted after the post-installation script execution completes. Auto restarting services include: Routes, Workflow Steps and Transactions. (This behavior can be override inside the application definition) Auto restarting can be disabled inside the Installation popup.
Warning
Expired applications cannot be installed.
Pipeline and DSL¶
See Applications DSL for more details
The following pipeline variables are available during install script execution to help the application develop make decisions about the transformations required:
Variable | Note |
---|---|
p6.application.step | Equals to preinstall or postinstall |
p6.application.install.appKey | |
p6.application.install.id | Application UUID |
p6.application.install.status | |
p6.application.install.version | |
p6.application.install.instance.version.min | |
p6.application.install.instance.version.max |
Examples¶
Before the installation, preInstall
script if one was packaged in the app is executed.
It can be used to prepare the system by creating folders, warning users, etc…
def step = p6.pipeline.get("p6.application.step")
def appKey = p6.pipeline.get("p6.application.install.appKey")
def version = p6.pipeline.get("p6.application.install.version")
log.debug "${P6_CURRENT_SCRIPT} -> STEP: ${step}"
p6.email.sendEmail("p6apps@sitedrade.io", "admin-client@server.com", "Application ${appKey}:${version} is being installed");
After inserting all the services items the installation process will execute postInstall
script if one was defined in the app.
It can be used to override or insert data.
def step = p6.pipeline.get("p6.application.step")
def appKey = p6.pipeline.get("p6.application.install.appKey")
def version = p6.pipeline.get("p6.application.install.version")
log.debug "${P6_CURRENT_SCRIPT} -> STEP: ${step}"
p6.table.upsert("${appKey}.Data", [[key:'fee', value:'foo'], [key:'bar', value:'bee']])
p6.appconfig.override(appKey, "Folder", "/\${P6_TMP}/${appKey}-${p6.instance.coreVersion()}")
p6.email.sendEmail("p6apps@sitedrade.io", "admin-client@server.com", "Application ${appKey}:${version} has been installed");
Upgrading an Application¶
An application that contains an upgradeScript
can be used to upgrade an application that is already installed.
Pipeline and DSL¶
See Applications DSL for more details
The following pipeline variables are available during upgrade script execution to help the application develop make decisions about the transformations required:
Variable | Note |
---|---|
p6.application.step | Equals to upgrade |
p6.application.upgrade.to.appKey | |
p6.application.upgrade.to.id | Application UUID |
p6.application.upgrade.to.status | |
p6.application.upgrade.to.install.date | |
p6.application.upgrade.to.version | |
p6.application.upgrade.to.instance.version.min | |
p6.application.upgrade.to.instance.version.max | |
p6.application.upgrade.to.create.date | |
p6.application.upgrade.from.appKey | |
p6.application.upgrade.from.id | |
p6.application.upgrade.from.status | |
p6.application.upgrade.from.install.date | |
p6.application.upgrade.from.version | |
p6.application.upgrade.from.instance.version.min | |
p6.application.upgrade.from.instance.version.max | |
p6.application.upgrade.from.create.date |
Tables¶
Table data are preserved when upgrading an application. But, if the structure of the table changed, you must do the migration inside the script
def data = [];
p6.application.withAppUpgrade() { serviceId, itemId ->
if( serviceId == 'platform6.tables' && itemId == 'MyApp.Table') {
# Read the data before upserting the service item
data = p6.table.toList(itemId)
}
# Return true to upsert the service item
true
}
# Insert the data according to the new structure
p6.table.upsert('MyApp.Table', data.collect { it ->
[id: it.key, label: it.value, enabled: true]
})
Examples¶
As a minimum, an upgrade script must contain the following:
p6.application.withAppUpgrade() { serviceId, itemId ->
true
}
p6.pipeline.put('upgradeScriptResult', 'true')
This script will be presented (via callback) the service id and item id of each service item that is applied during an upgrade.
A return value of true
will ensure the item is applied to the installed application. A value of false
will skip the item
Finally, the whole script must confirm successful completion for processing to continue using the pipeline variable: upgradeScriptResult
A more complex upgrade script may look like:
// Permission upgrades in here
p6.permissions.appUpsert(...)
// Optionally deploy one or more bundled resources to help with the upgrade
// `true` to deploy else `false`
p6.application.withAppUpgrade() { serviceId, itemId ->
if( serviceId == 'platform6.bundledresources' && itemId == 'myapp.upgradebundle'){
true
} else {
false
}
}
// Apply each service item upgrade via callable
p6.application.withAppUpgrade() { serviceId, itemId ->
// Can add logic here to test current state of a service item using its JSON form
def jsonItem = p6.application.getServiceItem(serviceId, itemId)
// Return `true` to deploy the current service item update (false will skip the update)
// A `true` return updates the componentBundle of the App being updated to allow uninstall (and adds any previous/new item to rollback store)
true
}
// General app state storage that may be useful for ad-hoc rollback functions
p6.application.storeAppState('old_secret', '07070-967574576-777')
// Indicate the upgrade success (or fail!)
p6.pipeline.put('upgradeScriptResult', 'true')
Rollback an Application¶
If the application used to upgrade also contains a rollbackScript
it can be used to return the application to it’s previously installed state using what is referred to as rollback
Warning
If the application used to upgrade is deleted then the ability to rollback
is also removed.
Pipeline and DSL¶
See Applications DSL for more details
The following pipeline variables are available during rollback script execution to help the application develop make decisions about the transformations required:
Variable | Note |
---|---|
p6.application.step | Equals to rollback |
p6.application.rollback.to.appKey | |
p6.application.rollback.to.id | Application UUID |
p6.application.rollback.to.status | |
p6.application.rollback.to.install.date | |
p6.application.rollback.to.version | |
p6.application.rollback.to.instance.version.min | |
p6.application.rollback.to.instance.version.max | |
p6.application.rollback.to.create.date | |
p6.application.rollback.from.appKey | |
p6.application.rollback.from.id | |
p6.application.rollback.from.status | |
p6.application.rollback.from.install.date | |
p6.application.rollback.from.version | |
p6.application.rollback.from.instance.version.min | |
p6.application.rollback.from.instance.version.max | |
p6.application.rollback.from.create.date |
Examples¶
As a minimum, a rollback script must contain the following:
p6.application.applyAppRollback()
p6.pipeline.put('rollbackScriptResult', 'true')
A more complex script may look like:
// Perform any permission downgrades etc.
p6.permissions.appRemove(...)
// Access to generic state stored during upgrade
def value = p6.application.getAppState('old_secret')
// Note: The applyAppRollback() call will restore the app to it's previous Service Item state. It is an essential part of a rollback script
p6.application.applyAppRollback()
// Indicate the rollback success (or fail!)
p6.pipeline.put('rollbackScriptResult', 'true')
Uninstalling an Application¶
The uninstallation process will start immediately after selecting the Uninstall
command button by:
- running the uninstallation script if one was packaged in the app
- deleting all service items & resources related to the app and
- restarting the concerned services
Pipeline and DSL¶
See Applications DSL for more details
The following pipeline variables are available during uninstall script execution to help the application develop make decisions about the transformations required:
Variable | Note |
---|---|
p6.application.step | Equals to uninstall |
p6.application.uninstall.appKey | |
p6.application.uninstall.id | Application UUID |
p6.application.uninstall.status | |
p6.application.uninstall.version | |
p6.application.uninstall.instance.version.min | |
p6.application.uninstall.instance.version.max | |
p6.application.uninstall.date | |
p6.application.uninstall.create.date |
Examples¶
def step = p6.pipeline.get("p6.application.step")
def appKey = p6.pipeline.get("p6.application.uninstall.appKey")
def version = p6.pipeline.get("p6.application.uninstall.version")
log.debug "${P6_CURRENT_SCRIPT} -> STEP: ${step}"
p6.email.sendEmail("p6apps@sitedrade.io", "admin-client@server.com", "Application ${appKey}:${version} has been uninstalled");
Advanced¶
Single managing script file¶
To avoid multiplying the scripts files to deal with the different states of an application, you can create a single file. It is more readable and maintenable this way, and you will have fewer manipulations to do when in the UI when packaging the Application.
Example¶
def step = p6.pipeline.get("p6.application.step")
log.debug "${P6_CURRENT_SCRIPT} -> Step: ${step}"
switch(step) {
case "preinstall": {
break;
}
case "postinstall": {
break;
}
case "upgrade": {
p6.application.withAppUpgrade() { serviceId, itemId ->
true
}
p6.pipeline.put('upgradeScriptResult', 'true')
break;
}
case "rollback": {
p6.applications.applyAppRollback();
p6.pipeline.put('rollbackScriptResult', 'true')
break;
}
case "uninstall": {
break;
}
default:
log.debug "${step}: Nothing to do"
}
log.debug "--- End ---"
Dealing with versions¶
Time being, your application will grow and new items are going to be added,
breaking changes could also be introduced between your versions. To manage that, you will need to implement
some upgrade
and rollback
scripts as shown bellow
Note
If you need to share some code between multiple application management script, you can consider the import feature of the script service
Examples¶
Combined upgrade¶
In this example, the developer needs to do the full migration from a version to another.
def step = p6.pipeline.get("p6.application.step")
switch(step) {
case "upgrade": {
def fromVersion = p6.pipeline.get('p6.application.upgrade.from.version')
switch(fromVersion) {
case "1.0.0": {
// update from 1.0.0 to 1.3.0
break;
}
case "1.1.0": {
// update from 1.1.0 to 1.3.0
break;
}
case "1.2.0": {
// update from 1.2.0 to 1.3.0
break;
}
}
p6.application.withAppUpgrade() { serviceId, itemId ->
true
}
// Post upgrade
p6.pipeline.put('upgradeScriptResult', 'true')
break;
}
}
In every switch/case based on the fromVersion
you can call p6.application.withAppUpgrade()
method to do
specific actions (i.e. store the previous value into the application state or extract table data).
p6.application.withAppUpgrade() { serviceId, itemId ->
if( serviceId == 'platform6.appconfig' && itemId == 'MyApp.ConfigurationKey') {
p6.application.storeAppState("ConfigurationKey", p6.appconfig.get("MyApp", "ConfigurationKey"))
}
true
}
Step by step upgrade¶
In this example, the developer will simulate the changes made on each version.
def step = p6.pipeline.get("p6.application.step")
switch(step) {
case "upgrade": {
def fromVersion = p6.pipeline.get('p6.application.upgrade.from.version')
if(p6.version.lessThan(fromVersion, "1.1.0")) {
// update to 1.1.0
}
if(p6.version.lessThan(fromVersion, "1.2.0")) {
// update to 1.2.0
}
if(p6.version.lessThan(fromVersion, "1.3.0")) {
// update to 1.3.0
}
p6.application.withAppUpgrade() { serviceId, itemId ->
true
}
// Post upgrade
p6.pipeline.put('upgradeScriptResult', 'true')
break;
}
}
The difference with this syntax is the use of the p6.version
dsl to make the version comparaison and
each if statement based on the fromVersion
needs to convert the exising data into the next format.
Rollback¶
As seen before, during the upgrade
of an application, we can use the p6.application.storeAppState
to store data
related to a specific version. What is nice with this feature is that during the rollback you can read this value
in order to restore it.
def step = p6.pipeline.get("p6.application.step")
switch(step) {
case "upgrade": {
def fromVersion = p6.pipeline.get('p6.application.upgrade.from.version')
if(p6.version.lessThan(fromVersion, "1.1.0")) {
// update to 1.1.0
}
if(p6.version.lessThan(fromVersion, "1.2.0")) {
// update to 1.2.0
p6.application.storeAppState("ConfigurationKey", p6.appconfig.get("MyApp", "ConfigurationKey"))
}
p6.application.withAppUpgrade() { serviceId, itemId ->
true
}
// Post upgrade
p6.pipeline.put('upgradeScriptResult', 'true')
break;
}
case "rollback": {
def toVersion = p6.pipeline.get('p6.application.rollback.to.version')
p6.application.applyAppRollback();
// Post upgrade
if(p6.version.equals(toVersion, "1.0.0")) {
// rollback to 1.0.0
}
if(p6.version.equals(toVersion, "1.1.0")) {
// rollback to 1.1.0
p6.appconfig.override("MyApp", "ConfigurationKey", p6.application.readAppState("ConfigurationKey"))
}
p6.pipeline.put('upgradeScriptResult', 'true')
break;
}
}
Warning
If the rollback
script fails and has been packaged to be READ-ONLY
then you will have no other choices than
going to the database to make it editable rr, remove everything related to that application using a query into the database.