Working with Turnkey–mixing in your own code

I wanted to automate the framework deploy to Turnkey-sites. I wanted to allow for the user to request a deploy and have that request trigger a build on the build machine. This is just my example use-case, but it is easy to see scenarios where you may want to use turnkey information to trigger non turnkey actions (a build with ms-build and ms-deploy in my case).

This is what I did on the License and Ticket model:

image

On the TurnkeySite class I added a state machine:

image

 

I added some actions for the users to use:

image

All that will happen is that StateMachine moves into the DeployRequested-state…

I then created a new Turnkey-WPF app in c# and added these lines to allow for login and showing a UI:

    private DispatcherTimer _timer;
    private StreamingViewModelClient _client;

    private void AutoDeployButton_Click(object sender, RoutedEventArgs e)
    {
      var login = new WPFTurnkeyLogin();
      login.SetSite("https://licenseandticket.azurewebsites.net");
      if (login.ShowDialog().Value)
      {
        var x = new WindowWecpofTest();
        x.Init("https://licenseandticket.azurewebsites.net", "", false);
        x.Show();

        _client = x.TurnkeyWPFWecpof.CurrentClient();
        _client.Navigate("TurnkeySitesForBuild", "$null$");


        _timer = new DispatcherTimer();
        _timer.Interval = new TimeSpan(0, 0, 15);
        _timer.Tick += _timer_Tick;
        _timer.Start();

      }
    }

 

The code assumes there is a ViewModel name TurnkeySitesForBuild and I have defined that like this:

image

The UI has the ability to search for all TurnkeySite objects that are in the state I want to catch for building:

image

I also expose the actions that are important for the build-process that is tracked by my state machine:

image

This all it takes to have the UI show up:

image

In the c# code I will then trigger a search – see if there are any hits – if so do the build and deploy – report how it went.

   private async void _timer_Tick(object sender, EventArgs e)
    {
      if (!_timer.IsEnabled)
        return;
      try
      {
        _timer.IsEnabled = false;
        try
        {
          _client.ActionExecute("TurnkeySitesForBuild", "Search");
          _client.ExecuteAfterFullRoundtrip("CheckIfThereWereAnyHits", null, (arg2) =>
          {
            if (_client.Root.ContainsKey("SearchResults"))
            {
              var listtobuild = _client.Root["SearchResults"];
              foreach (System.Dynamic.ExpandoObject pando in listtobuild as IList<System.Dynamic.ExpandoObject>)
              {
                if ((pando as IDictionary<string, object>).ContainsKey("State"))
                {
                  string state = (string)(pando as IDictionary<string, object>)["State"];
                  if (state == "DeployRequested")
                  {
                    HarvestToBuildList(pando);
                    break;
                  }
                }

              }
            }
          });
        }
        finally
        {
          _timer.IsEnabled = true;
        }
      }
      catch (Exception excp)
      {
        string s = excp.Message;
      }
    }

 

The code “clicks” the actions in the ViewModel. Since everything is async in Turnkey I sometimes must wait for completion like I do with the call “ExecuteAfterFullRoundtrip”. The callback from this method is guaranteed to get called when the queue resulting from previous actions is emptied – so in this case I know that the Search result is up-to-date before I iterate it.

Then I do the build:

    private bool _currentlyBuilding;
    private byte[] _buildbytes;
    private List<string> _buildinfolist;
    private void HarvestToBuildList(ExpandoObject pando)
    {
      if (_currentlyBuilding)
        return;
      _currentlyBuilding = true;
      _buildinfolist = new List<string>();


      string identifier = CommonObjectMethods.GetVMClassId(pando);
      string adr = _client.Apipath + "GetVMFile?vmid=" + _client.CurrentVMId + "&vmclassid=" + identifier + "&col=PublishingProfile";
      var httpc = _client.GetHttpClient();
      _buildbytes = httpc.GetByteArrayAsync(adr).Result;
      (pando as IDictionary<string, object>)["vCurrent"] = true;
      _client.ActionExecute("SearchResultGrid", "TurnkeySite_Trigger_AutomaticDeployStarted");
      _client.ExecuteAfterFullRoundtrip("WaitingForDeployStarted", null, DoOnDeployStarted);
    }

The code gets the PublishingProfile for the specific TurnkeySite – this is why it is important that you upload it to LicenseAndTicket even if you run from your own Azure account.

Noteworthy is that since the PublishingProfile is of type blob(byte array) it is not downloaded directly by Turnkey-client – instead we just get a reference that we need to use.

Once I have all I need to build I do some further checks, set a few log-values, start the actual build, follow up how it went to report new state etc:

    private void DoOnDeployStarted(object arg)
    {
      _client.SetVariable("vDateTimeForBuild", DateTime.Now);
      try
      {
        XElement xelem = null;
        string site;
        string pwd;
        try
        {
          var x = UTF8Encoding.Default.GetString(_buildbytes);
          var xdoc = XDocument.Parse(x);
          xelem = xdoc.Root.Element("publishProfile");
          site = xelem.Attribute("msdeploySite").Value;
          pwd = xelem.Attribute("userPWD").Value;
        }
        catch (Exception ex1)
        {
          _buildinfolist.Add(ex1.Message);
          _buildinfolist.Add("Likely a problem with the publishing profile - is it available in LicenseAndTicket?");
          _client.ActionExecute("SearchResultGrid", "TurnkeySite_Trigger_DeployFailed");

          throw;
        }
        try
        {
          RefreshDeployStuff.AddBuildInfoHere(_buildinfolist);
          string path = @"C:\CapableObjectsBuild2013\";
          if (!Directory.Exists(path))
            path = @"C:\CapableObjectsWush\";

          string versioninfo = "7.0.0.";
          try
          {
            var svninfo = XDocument.Load(path + @"stage\svninfo.xml");
            versioninfo += svninfo.Root.Element("entry").Attribute("revision").Value;
          }
          catch
          {
            versioninfo += "???";
          }


          RefreshDeployStuff.Deploy(path, site, pwd);
          _client.SetVariable("vSuccess", DateTime.Now);
          _client.SetVariable("vVersion", versioninfo);
          _client.ActionExecute("SearchResultGrid", "TurnkeySite_Trigger_AutomaticDeployDone");
          _client.ExecuteAfterFullRoundtrip("WaitingForDeployDone", null, (arg2) => { _client.ActionExecute("SearchResultGrid", "GetSiteGoingAfterDeploy"); });
        }
        catch (Exception exc)
        {
          _buildinfolist.Add(exc.Message + "\r\n");
          _client.ActionExecute("SearchResultGrid", "TurnkeySite_Trigger_AutomaticDeployFailed");
        }
      }
      finally
      {
        string m = "";
        foreach (var i in _buildinfolist)
          m += i + "\r\n";
        _client.SetVariable("vMessage", m);
        _client.ActionExecute("SearchResultGrid", "AddDeployLog");
        _client.ExecuteAfterFullRoundtrip("WaitingForAddDeployLog", null, (somearg) => { _client.ActionExecute("GLOBAL", "Save"); });
        _currentlyBuilding = false;
      }
    }

To the user (you and me) it will look like this in LicenseAndTicket:

image

Conclusion

Even if Turnkey cannot do everything by itself – it is easy to use the information within turnkey from tools that can do what you need. In my case the user is given the perception of automatic build and deploy from a phone or anywhere. But what really happens is my legacy MS-Build script is used by an app that only reacts to state changes in the turnkey information.

This entry was posted in Turnkey and tagged . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*