Upgrading Legacy Plans to OtterScript
  • 21 Feb 2022
  • 8 Minutes to read
  • Dark
    Light
  • PDF

Upgrading Legacy Plans to OtterScript

  • Dark
    Light
  • PDF

In BuildMaster v5, we introduced OtterScript, which was a complete rewrite of what we now call "legacy plans". OtterScript took 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 OtterScript was implemented side-by-side with legacy plans. Although OtterScript cannot run a legacy plan (nor can legacy plans run OtterScript), you can convert legacy plans to OtterScript on a plan-by-plan basis, and then edit the corresponding pipeline to use the new plan.

Legacy plans are available through BuildMaster 6.1, which means you won't be able to upgrade to newer versions until you've removed them all from your instance.

Transitioning to OtterScript

OtterScript represents a big paradigm shift, and you should think of "converting to OtterScript" as more of a "machine translation from one language to another." It works fine for simple legacy plans. But for complex legacy plans, the OtterScript will be more like a starting point, and an opportunity to refactor and utilize all the new features and constructs. As such, it's important to take the time to read and understand the differences between the OtterScript and legacy plans, how conversion works, and how to mitigate risks.

Recommended Migration Approach
Convert your frequently-used plans (especially in lower, non-production environments) to OtterScript 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.

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

How to 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 submit a support ticket, and we will make sure to investigate the issue. If your OtterScript does not execute for you correctly, just use the legacy plan in the meantime.

Legacy Plans vs OtterScript 

OtterScript 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 is a domain-specific language that uses 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 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. OtterScript has a formal grammar, and the statement names in the parenthesis refer to statements in the grammar.

Action GroupIn OtterScript

Server Selection

General Block with a Server (Set Context Statement).
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 (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 on 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.

ActionsIn OtterScript

Server Selection

General Block with a Server (Set Context Statement).
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 a 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.


Was this article helpful?