Legacy Plans in BuildMaster 5.0

KB#1118: Last Updated Jan 15, 2018

The below applies to BuildMaster users upgrading to v5

One of the major features of BuildMaster 5.0 is a brand new execution engine. It's a complete rewrite and takes into account years of experience supporting complex, multi-server orchestrations with easy-to-use deployment plans, and incorporates a tremendous amount of technological advancements.

To mitigate upgrade risk, the new execution engine is implemented side-by-side with the legacy execution engine. Although the new execution cannot run a legacy plan (nor can the legacy engine run a new OtterScript plan), you can convert legacy plans to OtterScript on a plan-by-plan basis, and then edit the corresponding pipeline to use the new plan.

If you’d like to learn how the engines differ, see Legacy Plans vs OtterScript Plans

Transitioning to the New Executing Engine

Because the new execution engine represents such a big paradigm shift, think of "converting to OtterScript" as more of a "machine translation from one language to another." It works fine for simple legacy plans; for complex legacy plans, the OtterScript will be more like a starting point, and an opportunity to refactor and utilize the all the new features and constructs.

The legacy execution engine will be supported indefinitely, so there’s no rush to convert everything.

However, it is legacy, and it’s important to take the time to read and understand the differences between the new and legacy execution engines, how conversion works, and how to mitigate risks.

Recommendation: convert your frequently-used plans (especially in lower, non-production environments) to a v5 plan to ensure that they are functional; this will not only offer some immediate performance improvements, but it will ensure that your plans can be more easily maintained and shared in the future.

The conversion is not permanent; you can always use the legacy plan if there are any errors in the OtterScript-based plan.

HOWTO: Converting Legacy Plans

Because the conversion process is a one-way process, and the existing legacy plan will be preserved, there is no risk in trying to convert your plans. Navigate to the [Plans] tab under the main or application navigation, then click the [convert] button next to the Legacy Plan.

After clicking [Convert to OtterScript], a new plan will be created that you can view and edit. To use this plan in a deployment, edit the appropriate target in a pipeline. You can always switch it back.

Testing Converted Plans

The first stage of testing a converted plan should be a visual inspection of the imported OtterScript; if everything looks correct, try executing the plan like normal.

If the execution completes successfully, verify that all of the execution results (such as build artifacts, deployed files, etc.) are correct.

Troubleshooting

The first step in troubleshooting an issue with a converted plan is isolating where the problem is. Find the action where an error occurred or incorrect output was generated and examine the execution logs. You can always contact us, and we will make sure to investigate the issue. If an OtterScript plan does not execute for you correctly, just use the legacy plan in the meantime.

 

Legacy Plans vs OtterScript Plans

The OtterScript-plans represent a paradigm shift in the way deployment plans are modeled.

Legacy plans model a deployment plan as a series of Action Groups, each of which may be linked to another plan, skipped using predicates, run in parallel, used to target a server, etc. These Action Groups are comprised of Actions (such as deploy artifact, stop service, etc.) that can each be configured with a different failure behavior, retry count, timeout, etc.

OtterScript plans model a deployment plan using a series of statements and blocks that are interpreted in a top-down manner; they are just as intuitive and simple to visualize and use, but they will seem second-nature to those familiar with scripting and programming concepts.

Actions Groups vs Blocks

These are somewhat similar, except there are several types of blocks available, and blocks can be nested. When using the OtterScript plan converter, Action Groups within a legacy plan will be translated into one or more blocks depending on which features it is configured with, using the following OtterScript constructs.

Action Group In OtterScript

Server Selection

General Block with a Server (Set Context Statement in OtterScript).
for server INTAPPSV1
{ 
  «operation1»
  «operation2»
  «operationN»
}

Enabled

If/Else Block (if false)
if false
{ 
  «operation1»
  «operation2»
  «operationN»
}

Deployable

General Block with a Deployable (Set Context Statement).
for deployable WebComponents 
{ 
  «operation1»
  «operation2»
  «operationN»
}
If execute once for each deployable was specified, a Loop Block with a Deployable (or Context Iteration Statement) will be created
foreach deployable in @DeployablesInRelease
{ 
  «operation1»
  «operation2»
  «operationN»
}

Predicates

If/Else Block and convert the Predicates to a Predicate Expression
if $ExecutionStatus=failed
{ 
  «operation1»
  «operation2»
  «operationN»
}

Server Group Iteration

Loop Block with a Server (or Context Iteration Statement)
foreach server in @ServersInGroup(APPSVGROUP)
{ 
  «operation1»
  «operation2»
  «operationN»
}
If Iterate at action level was specified, each converted operation will be wrapped in its own loop block:
{
  foreach server in @ServersInGroup(APPSVGROUP){ «operation1» }
  foreach server in @ServersInGroup(APPSVGROUP){ «operation2» }
  foreach server in @ServersInGroup(APPSVGROUP){ «operationN» }
}
Because Server Groups are now considered a legacy feature (replaced with Server Roles), you should migrate towards those as well.

Execute in Parallel

Each adjacent set of parallel action groups become a General Block with Asynchronous (Execution Directive Statement), and end with an Await statement:
{ 
  with async { «converted-action-group1» }
  with async { «converted-action-group2» }
  with async { «converted-action-groupN» }
  await;
}

Failure Behavior

To assist with error handling in legacy plans, a Failure Behavior feature was added to action groups. This effectively acted as a GOTO statement, and jumped to a previous or subsequent action group. Because OtterScript does not have a GOTO statement, this feature is ignored during conversion.

GOTO statements are a fairly primitive way to handle errors, which is why OtterScript features a Try/Catch. After converting the plan, you should wrap the failure-prone blocks in a Try block and put the Error-handling statements in the Catch block.

Linked Action Groups

To facilitate the sharing of common deployment plan logic, legacy plans offer the ability to link action groups across plans; this works with a sort of “inheritance” model, in that an instance of a linked group may override some properties (like name, description, server targeting, etc).

The closest OtterScript concept is a template. These are generally quite a bit better, because they can take in any number of parameters, and often accomplish what most users attempted with linked action groups. However, because these are quite different concepts, it wasn’t very feasible or sensible to try translating in the OtterScript converter.

When you convert a legacy plan to OtterScript, any linked action groups will simply be brought over as if it were a regular action group.

For reusability, consider creating a template based off of the original shared group (and parameterizing the pieces that you wish to be “inherited” like the original), or consider generalizing your entire deployment plan so that it can be used for different pipeline stages.

Actions vs Operations

These are conceptually the same thing: they perform a task on a remote server. While Operations are technologically superior, Actions have several “features” that were incorporated into OtterScript at a higher level using other statements and blocks. This allows for more intuitive and less awkward multi-action behavior.

Most Actions will be translated directly into the corresponding Operation; however, if the Action utilized any of the following common “features”, they will be translated using the following OtterScript constructs.

Actions In OtterScript

Server Selection

General Block with a Server (Set Context Statement in OtterScript).
for server INTAPPSV1
{ 
  «operation»
}

Enabled

If/Else Block (if false)
if false
{ 
  «operation»
}

Resume Next

Try/Catch with Set Status (Error) in the Catch
try
{ 
  «operation»
}
catch
{
  error;
}

Timeout

General Block with a Timeout (Execution Directive Statement)
with timeout=600{
   «operation»
}

Retry Count

General Block with a Retry (Execution Directive Statement)
with retry=3{
   «operation»
}

Log Errors as Warnings

Try/Catch with Set Status (Warn) in the Catch
try
{ 
  «operation»
}
catch
{
  warn;
}

Executing Legacy Action in OtterScript

The special Execute-LegacyAction operation can be used to execute actions in an OtterScript plan. It has two properties:

  • Config - the XML-based configuration of the action; this can be discovered by exporting a legacy plan as XML
  • Profile - the extension configuration profile to use

When converting a legacy plan to OtterScript, any actions that is not decorated with a ConvertibleToOperationAttribute will be wrapped with this operation.

Unsupported Actions and Special Conversion

Some of the actions simply could not be translated into operations, or were handled differently.

Set Variable Action

This action actually did one of two different things: it either updated the value of an external (release, build, etc.) variable, or set the value of new runtime variable. Unfortunately, it’s impossible to know which it would do until runtime, so all Set Variable actions are replaced with a Set Variable statement.

To update an external configuration variable, you will need to use the Set-Configuration-Variable operation.

In addition, variables in a legacy plan were always globally-scoped, whereas OtterScript has block-level scoping. To reproduce this behavior, a set $variable=<unitialized>; statement is added to the top of the OtterScript plan after conversion.

Execute Global Deployment Plan

Because the new execution engine cannot run legacy plans, it’s not possible to convert this action to an operation. Instead, the global plan should be converted to a template plan, and then called from the new plan. This will also give the benefit of parameters.

Create Build and Release

Because the corresponding operations are simpler, and only create a single release or build (release package) in a single application, a translated version of this action may be wrapped in several loop and async blocks. Functionally, it will be the same and will have the benefit of being visualized and logged better.

Source and Target Directories

Many Actions utilize the concept of a "default directory" (also represented as $CurrentDirectory), which was thought of as the working directory for both input ($SourceDirectory) and output ($TargetDirectory) files. When an action utilizes both an input and output of files (compiling, for example) and "default" is used for both of those actions, the input set of files is replaced by the output set of files once the action is no longer executing.

Unfortunately, this behavior is unintuitive, and has been a major source of confusion and frustration. As such, operations simply have a “working directory”, and may specify different directories for output.

Predicates

Legacy plans feature an extensible component called Predicates that are used to determine whether or not an action group will execute. In OtterScript plans, Variable Functions are used within an If/Else block instead and are much more naturally integrated.