Article Managing Automation Source and Distribution

Automations support simple importing and exporting for deployment, and, working with a factory default automation provider can enable source management within a team working on automations, but, if your project requires automated deployment of automations along with other custom application extensions, it may be useful to implement a self-installing/versioning factory default automation provider.

Basic Distribution Without Self Installation

To distribute automations, it is not necessary to create a self-installed automation provider (which we'll do in the next section). Basic distribution can be achieved by exporting automations from Automation Studio and providing these exports for download and installation through Automation administration (Administration > Automation > Automations) or Automation Studio.

This approach is simple and requires no custom development. As updates are available, a new export/import process can be performed and changes can be reviewed manually within Automation Studio before publishing the new import. 

With this approach, there will not be a factory default implementation in the target community and the sole implementation will still be editable without a revert option. Otherwise, there is no functional difference in the final automation or its capabilities within the target community.

Self Installation and Versioning

If you are developing a custom application that includes other custom assemblies, it may be useful to include the installation and versioning of automations with the overall installation/versioning behavior of the larger customization. 

Sample

The following sample implements a self-installed/versioned factory default automation provider.

using System;
using System.Collections.Generic;
using Telligent.Evolution.Extensibility.Automation.Version1;
using Telligent.Evolution.Extensibility.Storage.Version1;
using Telligent.Evolution.Extensibility.Version1;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility.Api.Entities.Version1;
using System.IO;

namespace Samples
{
    public class FactoryDefaultAutomationProvider : IPlugin, IAutomationFactoryDefaultProvider, IInstallablePlugin
	{
		// TODO: Define the automation provider ID (this needs to be unique).
		private readonly Guid _id = new Guid("UNIQUE_GUID");

		// TODO: Specify the current version of the automations being installed.
		private readonly Version _version = new Version(1, 0, 0, 0);
		private IAutomationFactoryDefaultController _automationController;

		#region IPlugin Members

		// TODO: Update the name of the plugin.
		public string Name => "My Automation Provider";

		// TODO: Update the description of the plugin.
		public string Description => "Automations that I have created.";

		public void Initialize() { }

		#endregion

		#region IAutomationFactoryDefaultProvider Members

		public Guid AutomationFactoryDefaultIdentifier => _id;

		public void SetController(IAutomationFactoryDefaultController controller)
		{
			_automationController = controller;
		}

		#endregion

		#region IInstallablePlugin Members

		public Version Version => _version;

		public void Install(Version lastInstalledVersion)
		{
#if !DEBUG
			var automationFiles = new List<InstallableFile>();

			// TODO: Define embedded resources and their target location in the CFS relative to the defaultautomations/ folder.
			//       Resource paths are defined relative to this assembly, so a file in resources\path\file.xml would have a resource path
			//       of @"resources\path\file.xml"
			automationFiles.Add(new InstallableFile(Version, "defaultautomations", "CFS_PATH", "CFS_FILENAME", "EMBEDDED_RESOURCE_PATH", false));

			var result = _automationController.ApplyUpdatedFiles(lastInstalledVersion, automationFiles);
			if (result.HasChanges && !string.IsNullOrEmpty(result.VersionMessage))
			{
				// TODO: Update the subject and introduction to the upgrade notes provided by the versioning API.
				Telligent.Evolution.Extensibility.Apis.Get<ISystemNotifications>().Create("Updates to My Custom Automations", string.Concat(
					"<p>My Custom Automations was upgraded to version ",
					_version.ToString(),
					" and includes the following updates for your review:</p>\n",
					result.VersionMessage
					));
			}
#endif
		}

		public void Uninstall()
		{
#if !DEBUG
			CentralizedFileStorage.GetFileStore("defaultautomations")?.Delete(_id.ToString("N"));			
#endif
		}

#endregion
	}

	public class InstallableFile : IInstallableCfsFile
	{
		private string _resourcePath;

		public InstallableFile(Version lastModifiedVersion, string cfsFileStoreKey, string cfsPath, string cfsFileName, string resourcePath, bool isDeleted)
		{
			LastModifiedVersion = lastModifiedVersion;
			FileStoreKey = cfsFileStoreKey;
			Path = cfsPath;
			FileName = cfsFileName;
			_resourcePath = resourcePath;
			IsDeleted = isDeleted;
		}

		public Version LastModifiedVersion { get; private set; }
		public string FileName { get; private set; }
		public string Path { get; private set; }
		public string FileStoreKey { get; private set; }
		public bool IsDeleted { get; private set; }

		public Stream OpenReadStream()
		{
			var name = new System.Text.StringBuilder(GetType().Assembly.GetName().Name);
			var components = _resourcePath.Split(System.IO.Path.DirectorySeparatorChar);
			int r;
			for (int i = 0; i < components.Length; i++)
			{
				// folders are seperated with a period
				name.Append(".");

				// numeric paths are prefixed with an underbar
				if (i < components.Length - 1 && int.TryParse(components[i].Substring(0, 1), out r))
					name.Append("_");

				name.Append(components[i]);
			}

			return GetType().Assembly.GetManifestResourceStream(name.ToString());
		}
	}
}

This sample extends the basic factory default automation provider example by 

  1. Storing the AutomationController (line 39). This controller provides access to the installation/versioning API for automations defined by this factory default provider.
  2. Implements IInstallablePlugin (line 12, 44-79). When not in debug mode (developing locally), the implementation of IInstallablePlugin handles the installation and uninstallation of the source files associated to this factory default automation provider. 

This sample works by embedding the automation source files in the .net assembly using embedded resources. The embedded resources should match the file storage location of the factory default automations associated with this factory default automation provider. The manifest of files is defined on line 56.

The installation logic (lines 58-67) providers the file manifest to the automation controller's ApplyUpdatedFiles() method. This method uses the file manifest to install/version automation files using upgrade safe upgrading logic:

  • If the automation already exists in the community,
    • If the automation and its supplemental files are unchanged, no action is taken.
    • If the automation or any of its supplemental files are changed, the existing automation is versioned (so a custom version will exist if it doesn't already), the factory default implementation of the automation is installed, and the change is noted.
  • If the automation doesn't already exist in the community, it is installed but not noted as a change.

If there are changes (and not just new installs), the result will include a VersionMessage value. This is an English string identifying the updated automations with links to review them within Automation Studio. In this sample, if a VersionMessage is returned, the system notification API is used to send a system notification to the administrators of the site with the details of the upgraded automations. If this was part of a larger installation, the automation upgrade message could be included in a larger upgrade notification for all upgraded functionality.

Defining Embedded Files

In the sample, embedded files are defined on line 56. For each file that exists (or was deleted), a new InstallableFile instance should be defined and added to the automationFiles list for review by the ApplyUpdatedFiles() method. Each InstallableFile requires the following parameters:

  • Last Modified Version. This is the version of the last edit of this file. Ideally, this would be accurate with respect to multiple releases (it will aide change tracking). It can be simplified, however, as in this example, by using the current version. This will ensure that every file is reviewed for changes.
  • CFS File Store Key. This is the file store key into which files will be installed. For factory default automations, this is always "defaultautomations". The ApplyUpdatedFiles() will ignore files in other file stores.
  • CFS Path. This is the path into which the file should be installed. For factory default automations, this is either the AutomationFactoryDefaultIdentifier.ToString("N") (for automation definition XML files) or AutomationFactoryDefaultIdentifier.ToString("N") + "." + ID_OF_AUTOMATION.ToString("N") (for automation supplementary files). Files stored outside of the folder associated to this factory default automation provider will be ignored by ApplyUpdatedFiles().
  • CFS File Name. This is the file name of the file to be installed.
  • Embedded Resource Path. This is the path, relative to the root of the current project, of the embedded resource representing the file to be installed. For a file embedded as "resources\path\file.xml", the resource path would be "resources\path\file.xml".
  • Is Deleted. If the file was deleted and should be removed as part of an upgrade of the automation factory default provider, it can be defined here and the "is deleted" flag can be set to true. In this case, the file does not need to be embedded in the assembly (it will never be read by ApplyUpdatedFiles()).