Backend Component

Each plugin must have a backend component and is used for the core interactions with IcePanel. They can use one of two processor types.

  • child_processor (Fork) - Used by Engine plugins
  • vm2 (VM with VMScript) - Used by App, Network, Tool and Volume plugins

Backend plugins can only have static methods called while they are still a prototype. Once deployed into an environment then one class instance is created for each deployed item.

Dependencies should be bundled with your plugin to avoid version conflicts and to allow them to be self contained. See the recommended Webpack config for more information on how to do do this while excluding packages that are supplied by IcePanel as standard.

Plugin classes

To create a plugin type extend one of the following classes from the @icepanel/core package.

Use the appropriate base class and interface type then export it as the default class for your module.

import { AppPlugin, IAppPlugin } from '@icepanel/core'

// exporting the class will make it available for IcePanel to instantiate
export default class MyApp extends AppPlugin implements IAppPlugin {}

Each plugin type has an IcePanel defined state object which is used to access common properties.

See the below example for how to get and set the status from the plugin state.

import { AppPlugin, IAppPlugin, IAppStatus } from '@icepanel/core'

export default class MyApp extends AppPlugin implements IAppPlugin {

  async setStatus(status: IAppStatus) {

    // root objects are merged when set
    await this.setState({ status })
    console.log(`set app status to ${status}`)
  }

  getStatus() {
    return this.state.status
  }
}

Persistent data store

You can persist and retrieve Javascript objects using the plugin store. Just specify your store schema and pass it into the plugin class as a generic type. For example a terminal tool plugin may need to save a history of the last commands executed.

See the below example for how to set and get from the plugin store.

import { AppPlugin, IAppPlugin } from '@icepanel/core'

// interfaces can be used to specify the data types of for an object
export interface IMyAppStore {
  vegetable?: string
  fruit?: string
}

export default class MyApp extends AppPlugin<IMyAppStore>
  implements IAppPlugin<IMyAppStore> {

  async setVegetable(vegetable: string) {

    // root objects are merged when set
    await this.setStore({ vegetable })
    console.log(`set vegetable ${vegetable}`)
  }

  getVegetable() {
    return this.store.vegetable
  }
}

The store is serialized into BJSON documents and so some limits apply.

Lifecycle hooks

Check out the IPlugin interface for lifecycle hooks common to all plugin types.

See the below example on how to check for changes to the store.

import { AppPlugin, IAppPlugin } from '@icepanel/core'

export interface IMyAppStore {
  vegetable: string
  fruit?: string
}

export default class MyApp extends AppPlugin<IMyAppStore>
  implements IAppPlugin<IMyAppStore> {

  async init() {
    // we must call the superclass method
    await super.init()

    // set the default vegetable
    await this.setStore({
      vegetable: 'cabbage'
    })
  }

  // triggered when a change has happened to a key inside the store
  storeDidUpdate(prevStore: IMyAppStore) {

    // check whether store vegetable has changed
    if (this.store.vegetable !== prevStore.vegetable) {
      console.log(`vegetable changed to ${this.store.vegetable}`)
    }
  }

  async destroy() {
    // we must call the superclass method
    await this.destroy()

    console.log(`last vegetable is ${this.store.vegetable}`)
  }
}

Required static methods

The following static methods are required when writing App, Tool, Network and Volume plugins so that Engine plugins can deploy them.

import {
  AppPlugin,
  IAppPlugin,
  Export,
  IModel,
  PluginType,
  createToolModel,
  IAppModel,
  IAppStatus,
  IPrototypeModel,
  IEnvironmentModel,
  createAppModel,
  createConnectorModel
} from '@icepanel/core'

export default class MyApp extends AppPlugin implements IAppPlugin {

  // export is required to make this method available to external plugins
  @Export()
  static createPrototype(prototype: IPrototypeModel, name: string): IAppModel {
    return {
      ...createAppModel(),

      // these properties are passed through to createEngineConfig
      engine: {
        image: 'httpd:latest'
      },

      // these node connectors are shown in the UI
      connectors: [
        {
          ...createConnectorModel(),
          name: 'network',
          type: 'network'
        }
      ]
    }
  }

  @Export()
  static createEngineConfig(environment: IEnvironmentModel, model: IAppModel) {

    // this is passed through into the engine plugin so it can be mapped into the engine itself
    return model.engine
  }

  @Export()
  static createToolPrototype(target: IModel): IToolModel | null {
    if (target.plugin.type === PluginType.App) {

      // returning an object here will allow this plugin to be installed as a tool
      return createToolModel()
    } else {

      // returning null here will hide this plugin from the tool install dropdown
      return null
    }
  }
}