Continuous integration of Mashups #4 – Command line

Finally, I was able to develop a command line to deploy Infor Smart Office Mashups. This is important for me to save time when deploying many Mashups, on many environments, many times a day, so I can re-invest that time in the Mashups, instead of wasting time on their deployment in the various graphical user interfaces. Read #1, #2, and #3 for the backstory.

Disclaimer

I will use the internals of Smart Office and MangoServer which by definition are not public. Infor Product Development may change any of it at any time.

Project

I will create my C# Console Application project in the free (as in free beer) Microsoft Visual Studio 2015 Community Edition:
10.1

Add references to the Smart Office assemblies Mango.UI.dll and Mashup.Designer.dll which you can find in your Smart Office local storage (search for the path in your Log Viewer); you can also get them from the Smart Office product download, or in the Smart Office SDK:
b4

When you Build the project, Visual Studio will automatically grab the additional assemblies Mango.Core.dll, log4net.dll, and DesignSystem.dll.

Web service client

Smart Office already has client code for its web services in the namespace Mango.UI.Services.WS, but it’s missing the method DeployMashup which is precisely the one I need:
b1

It’s easy to generate the code in Visual Studio, select Add Service Reference, and enter the URL to the MangoServer /mangows/InstallationPointManager?wsdl:
b8

In the App.config file, add the <transport clientCredentialType=”Basic” /> and add the name/address of the target M3 environments, e.g. DEV, TST, EDU, PRD:

Source code

Add the C# class Deploy.cs:

 using System;
 using System.IO;
 using System.Reflection;
 using Mango.UI.Services.Mashup;
 using Mango.UI.Services.Mashup.Internal;
 using MangoServer.mangows;
 using Mashup.Designer;
 
 namespace Mashups
 {
     class Deploy
     {
         // Usage: deploy.exe Mashup1,Mashup2,Mashup3 DEV,TST
         static void Main(string[] args)
         {
             // get the M3 userid/password
             Console.Write("Userid: ");
             string userid = Console.ReadLine();
             Console.Write("Password: ");
             string password = Console.ReadLine();
 
             // for each directory
             string[] directories = args[0].Split(',');
             foreach (string directory in directories)
             {
                 // for each Mashup
                 string[] files = Directory.GetFiles(directory, "*.manifest", SearchOption.AllDirectories);
                 foreach (string file in files)
                 {
                     Console.WriteLine("Opening {0}", file);
                     ManifestDesigner manifest = new ManifestDesigner(new FileInfo(file));
                     string name = manifest.DeploymentName + Defines.ExtensionMashup;
                     Console.WriteLine("Generating {0}", name);
                     // generate the Mashup package
                     if (SaveMashupFile(manifest))
                     {
                         // get the resulting binary contents
                         byte[] bytes = File.ReadAllBytes(manifest.File.Directory + "\\" + name);
                         // for each M3 environment
                         string[] environments = args[1].Split(',');
                         foreach (string environment in environments)
                         {
                             // deploy
                             Console.WriteLine("Deploying to {0}", environment);
                             DeployMashup(name, bytes, environment, userid, password);
                         }
                         Console.WriteLine("DONE");
                     }
                 }
             }
         }
   
         /*
             Create the Mashup package (*.mashup) from the specified Mashup Manifest.
             Inspired by Mashup.Designer.DeploymentHelper.SaveMashupFile
         */
         static bool SaveMashupFile(ManifestDesigner manifest)
         {
             try
             {
                 // validate Manifest
                 typeof(DeploymentHelper).InvokeMember("ValidateManifest", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Static, null, null, new Object[] { manifest });
                 // create Mashup package
                 string defaultFileName = manifest.DeploymentName + Defines.ExtensionMashup;
                 FileInfo packageFile = new FileInfo(manifest.File.Directory + "\\" + defaultFileName);
                 PackageHelper.CreatePackageFromManifest(manifest, manifest.File, packageFile, PackageHelper.DeployTarget_LSO);
                 // check Mashup profile
                 if (manifest.GetProfileNode() != null)
                 {
                     string message = "Please note that this Mashup contains a profile section and should be deployed using Life Cycle Manager. Warning - Using the Mashup File Administration tool will not result in a merge of profile information into the profile.";
                     Console.WriteLine(message);
                 }
                 return true;
             }
             catch (Exception exception)
             {
                 Console.Error.WriteLine(exception);
                 return false;
             }
         }
 
         /*
             Deploy the specified Mashup name and file (*.mashup binary contents) to the specified M3 environment (see App.config) authenticating with the specified M3 userid and password.
             Inspired by /mangows/InstallationPointManager/DeployMashup
         */
         static void DeployMashup(string Name, byte[] MashupFile, string environment, string userid, string password)
         {
             InstallationPointManagerClient client = new MangoServer.mangows.InstallationPointManagerClient(environment);
             client.ClientCredentials.UserName.UserName = userid;
             client.ClientCredentials.UserName.Password = password;
             client.DeployMashup(Name, MashupFile);
         }
     }
 } 

Final project:
b10

Here is what the folder looks like:
10.6

Build the solution, the resulting command application will be Deploy.exe with the required assemblies and config file:b11

Usage

Suppose you have the following Mashups\ folder with Mashups inside, e.g. Mashup1, Mashup2, Mashup3, Mashup4, Mashup5, Mashup6:

And suppose you want to deploy only Mashup1, Mashup2, and Mashup3.

The usage is:

Mashups\> Deploy.exe Mashup1,Mashup2,Mashup3 DEV,TST

Where Mashup1,Mashup2,Mashup3 is the comma-separated list of Mashup folders, and where DEV,TST is the comma-separated list of target M3 environments as defined in Deploy.exe.config.

The program will recursively descend the specified folders. So you can also specify the parent folder – Mashups\ – and the program will deploy every Mashup that’s inside.

The program will ask for the M3 userid and password at the command prompt, as it’s more secure to not hard-code them in source code nor in a config file.

Result

Here is an example of deploying three Mashups on two environments:
b12

Here is the result in the MangoServer database of one of the environments:

Here is the result in Smart Office:
b15

Variations

  • Instead of using Microsoft Visual Studio, you can use Xamarin’s MonoDevelop which is free software (as in freedom) and open source and recently acquired by Microsoft, or use Microsoft’s .NET SDK’s C# command line compiler csc.exe
  • If you are more familiar with the JScript.NET programming language than C#, you can re-write the program in that language and compile it with Microsoft’s .NET SDK’s JScript.NET command line compiler jsc.exe (you could also run it as a script in Smart Office but that wouldn’t make sense)
  • Instead of generating web service client code, you can use the Smart Office method Mango.Core.DynamicWs.WSDataService.CallAsync

Future work

  • Remove the Mashups from LifeCycle Manager as Karin instructed
  • Replace our generated web service client code with the built-in Smart Office code, if and when Infor Product Development will provide it
  • Explore the Mango Admin tool
  • Explore the FeatureServlet of the latest Smart Office hotfix
  • Encrypt the password in the config file using a symmetric cipher as in the Grid applications, or use a YubiKey (hey, Sweden)

Conclusion

That was my solution to deploy Mashups at the command line. This is useful when deploying multiple Mashups, on multiple M3 environments, multiple times a day. It can potentially save hundreds of clicks and keystrokes per day compared to using the graphical user interfaces of Smart Office Mashup Designer and LifeCycle Manager. The time saved can be invested in the Mashups rather than on their deployment, aiming for continuous integration.

That’s it!

Please let me know what you think in the comments below, give me a thumbs up, click the Follow button to subscribe to this blog, share with your co-workers, and come write the next blog post with us. Pleeease, click something. Your support keeps this community going. Thank you for your support.

M3 Ideas, now 244 subscribers.

Related posts

Published by

thibaudatwork

M3 Technical Consultant

9 thoughts on “Continuous integration of Mashups #4 – Command line”

  1. NOTE: Uninstall the Mashup from LCM prior to deploying it via the command line. i.e. un-install then deploy; the timestamps matter; if you do it in reverse, i.e deploy then un-install, then the Mashup will disappear from Smart Office.

    Like

  2. NOTE: LCM does version control of Mashups: it won’t allow deploying a Mashup if that Mashup’s version number is already deployed. Now that we’re bypassing LCM, we must be the ones doing the version control. So let’s be careful with the version numbers.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s