TDS Post Deploy Action – Save Deployed Items, Publish Later

December 12, 2017

Blog | Development | TDS Post Deploy Action – Save Deployed Items, Publish Later
TDS Post Deploy Action - Save Deployed Items, Publish Later

Team Development for Sitecore (TDS) Classic Post Deploy Actions can be extremely useful. In my previous post, I rewrote the default publish action to be more efficient. It provides the ability to publish all items from a package to one or more target databases. This works great in a single-server environment but is not ideal in a distributed environment. It is common to have two (possibly more) Web databases in a production environment. In this scenario, the publish action does not solve the final piece of the problem. See the diagram below:

In the example above, the standard Post Deploy Action works great for Dev, Test and UAT, and even Production Authoring (assuming all of these servers rely on the Web database for anonymous visitors). The issue lies within the Production Delivery environment, which uses the Live database.

Scenario 1: Non-Ideal

We could set our Post Deploy Action to publish to Web and Live, however in a given deployment process, we must first deploy our code to Production Authoring, then upon success, deploy our code to Production Delivery. This means we could be publishing updated items to the Live database before the Production Delivery environment has had its code base updated.

If the Production Authoring deployment fails for some reason and the Post Deploy Action succeeds, now we are left with a Production Delivery environment in a non-ideal state that may be difficult to recover from, i.e the code base does not work with the given item set in the Live database.

Scenario 2: A More Controlled Deployment

Instead of publishing all changes to both Web and Live at the same time, it would be far more ideal to publish our changes after we have successfully deployed the code base to Production Delivery. To do this, I have created another Post Deploy Action- Write Deployed Items To File:

[Description("Writes deployed item IDs to ~/Data/DeployedItems/DeployedItems.txt")]
public class WriteDeployedItemsToFile : IPostDeployAction
{
    private static readonly string DeployedItemsDirectory = Sitecore.Configuration.Settings.DataFolder + @"\DeployedItems";

    private readonly string _deployedItemsFile;

    public WriteDeployedItemsToFile()
    {
        _deployedItemsFile = DeployedItemsDirectory + @"\DeployedItems.txt";
    }

    public void RunPostDeployAction(XDocument deployedItems, IPostDeployActionHost host, string parameter)
    {
        if(!Directory.Exists(DeployedItemsDirectory))
            Directory.CreateDirectory(DeployedItemsDirectory);
            
        if(File.Exists(_deployedItemsFile))
            File.Delete(_deployedItemsFile);
            
        using(var sw = File.CreateText(_deployedItemsFile))
            PostDeployActionSupport.ExecuteOnAllDeployedItems(deployedItems, delegate (XElement deployedItemInPackage, Guid deployedItemId, string databaseName)
            {
                if (databaseName != "core")
                {
                    sw.WriteLine(deployedItemId);
                }
            });
    }
}

This action writes the Guid of each deployed item, one line per Guid, to $(dataFolder)\DeployedItems\DeployedItems.txt. It effectively saves a record of the deployed items that we can later consume. Example of DeployedItems.txt below:

db8f1a04-d2b4-442b-b604-96b4b904588a
1c13df04-cd6f-4459-acd3-207d2031e0a3
fefd0fd4-47fd-4d01-a9a5-f2f20e8301ff
f7a222c3-8acc-4abf-aa12-172b12a6cc9f
8425ec15-5ee9-48ab-8dc6-0f4329e887cd
d9e44555-02a6-407a-b4fc-96b9026caadd
9cd3035a-8a43-468f-bbdf-300e386460c2
76051825-8b60-41fb-8af2-c10477269873
b33f2fb4-e3f6-4b8c-819c-62fa1bb875b7
d7c031c8-66b6-4167-8456-c63ea4bf389f
33f131c5-e62b-4f70-bb19-71e11491119a
d9053098-212b-4a3a-a349-8245d630e3c0
e16b7afe-a532-47d5-a6b7-c837f340928d
db89b62c-6d47-4fde-98db-57e9c03ecd5c

Initiating the Live Database Publish

Now that we have a record of deployed items, the final step is to initiate the publish after a successful code push to Production Delivery. Similar to how Hedgehog has built Sitecore Package Deployer, I have created a utility file that can be requested via a standard GET request. The release process needs only to make a call to http://prod.authoring/PublishDeployedItems.aspx?publishingDatabases=Live to initiate the publishing process.

I have intentionally excluded the markup from PublishDeployedItems.aspx as it is very simple and its main focus is the code behind file:

public class PublishDeployedItems : Page
{
    protected override void OnLoad(EventArgs e)
    {
        var publishingDatabasesParam = Request.QueryString["publishingDatabases"];

        if (string.IsNullOrWhiteSpace(publishingDatabasesParam))
        {
            Log.Warn("Must pass in publishing databases that match connection strings names, delimited by '|' character, ex. '?publishingDatabases=web|live'", this);
            return;
        }

        var publishingDatabaseNames = publishingDatabasesParam.Split('|');

        if (publishingDatabaseNames.Length <= 0)
        {
            Log.Warn("No publishing database names found, ex. '?publishingDatabases=web|live'", this);
            return;
        }

        var targetDatabases = publishingDatabaseNames.Select(Database.GetDatabase).Where(db => db != null).ToArray();

        if (!targetDatabases.Any())
        {
            Log.Warn("No databases found from parameter: " + publishingDatabasesParam, this);
            return;
        }

        var source = new List<Language>();
        foreach (var database in targetDatabases)
        {
            source.AddRange(LanguageManager.GetLanguages(database));
        }
        var languages = source.Distinct().ToArray();

        var deployedItemsFile = Sitecore.Configuration.Settings.DataFolder + @"\DeployedItems\DeployedItems.txt";

        if (!File.Exists(deployedItemsFile))
        {
            Log.Warn("Unable to find DeployedItems.txt file at: " + deployedItemsFile, this);
            return;
        }

        var itemIds = File.ReadAllLines(deployedItemsFile);

        if (!itemIds.Any())
        {
            Log.Warn("No items found in file: " + deployedItemsFile + " :: no publish executed", this);
            return;
        }

        var sourceDatabase = Database.GetDatabase("master");

        PublishUtils.CreateAndPublishQueue(sourceDatabase, targetDatabases, languages, itemIds.Select(i => new ID(i)), false);
            
        var itemsPublishedFile = Sitecore.Configuration.Settings.DataFolder + @"\DeployedItems\PublishedItems.txt";

        if (File.Exists(itemsPublishedFile))
            File.Delete(itemsPublishedFile);

        // copy file to notify publish was successful
        File.Copy(deployedItemsFile, itemsPublishedFile);
    }
}

Much like my previous Post Deploy Action, this action also relies on code from this public Gist. This code initiates a much more efficient publishing process. Once the publish has finished, it copies the DeployedItems.txt file to a new file called PublishedItems.txt in the same location- this is a nice measure of success. The standard Sitecore log and Publishing log will contain info on the publishing process as well.

It is worth noting that this file can be further locked down by moving it to within the \sitecore\admin folder of the web root and pass credentials properly, possibly via PowerShell. We also do NOT deploy this file to the Production Delivery environment for security concerns.

The use-case described here is one of possibly many where this action may prove useful. When used in the given scenario, it provides assurance that our code base and database are in sync in the most important environment of all – Production.

John Rappel

.NET Practice Lead
Tags
  • Sitecore
  • TDS

Recent Work

Check out what else we've been working on