Skip to content

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.