Internet Explorer is no longer supported. Many things will still work, but your experience will be degraded and some things won't function. Please use a modern browser such as Edge, Chrome, or Firefox.

Upgrading Legacy Plans to OtterScript

Modified on July 19, 2024view on GitHub

In BuildMaster v5, we introduced OtterScript, which was a complete rewrite of what we now call "legacy plans." OtterScript incorporates years of experience in supporting complex multi-server orchestrations with easy-to-use deployment plans and includes a tremendous amount of technological advances.

To mitigate upgrade risk, OtterScript was implemented in parallel with legacy plans. Although OtterScript cannot run legacy plans (nor can legacy plans run OtterScript), you can convert legacy plans to OtterScript on a plan-by-plan basis and then edit the appropriate pipeline to use the new plan.

Legacy plans are available until BuildMaster 6.1, which means you cannot upgrade to newer versions until you remove them all from your instance.

Transitioning to OtterScript

OtterScript represents a major paradigm shift, and you should think of "converting to OtterScript" more as "machine translation from one language to another" For simple legacy plans, this works well. But for complex legacy plans, OtterScript is more of a starting point and an opportunity to refactor and use all the new features and constructs. So it's important to take the time to read the differences between OtterScript and legacy plans and understand how the conversion works and how to minimize the risks.

Recommended Migration Approach
Convert your frequently used plans (especially in lower, non-production environments) to OtterScript to ensure they are functional; this not only provides some immediate performance improvements but also makes your plans easier to maintain.

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

How to Convert Legacy Plans

Since the conversion process is a one-way process and the existing legacy plan is preserved, there is no risk in trying to convert your plans. Navigate to the Plans tab in the main or application navigation, then click the Convert button next to the legacy plan.

After you click Convert to OtterScript, a new plan is created that you can view and edit. To use this plan in a deployment, edit the corresponding target in a pipeline. You can convert it back at any time.

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 running the plan as usual.

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

Troubleshooting

The first step in troubleshooting a converted plan is to determine the cause of the problem. Locate 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 take care of the problem. In the meantime, if your OtterScript is not executing correctly, simply use the legacy plan.

Legacy Plans vs. OtterScript 

OtterScript represents a paradigm shift in the way deployment plans are modeled.

Legacy plans modeled a deployment plan as a set of Action Groups, each of which can be linked to another plan, skipped using predicates, executed in parallel, used as a target for a server, etc. These Action Groups are comprised of Actions (e.g., deploy artifact, stop service, etc.), each of which can be configured with a different error behavior, number of retries, time limit, etc.

OtterScript is a domain-specific language that uses a series of statements and blocks interpreted from the top down; they are equally intuitive and easy to visualize and use, but for those familiar with scripting and programming concepts, they will seem obvious.

Actions Groups vs. Blocks

These are similar in some ways, except that there are multiple types of blocks and blocks can be nested. When using the OtterScript converter, Action Groups within a legacy plan are translated into one or more blocks, depending on the functions it is configured with, using the following OtterScript constructs. OtterScript has a formal grammar, and the statement names in the parentheses 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 facilitate error handling in legacy plans, a Failure Behavior feature was added to action groups. This acts as a GOTO statement and jumps to a previous or subsequent action group. Since OtterScript does not have a GOTO statement, this function is ignored during conversion.

GOTO statements are a fairly primitive way of dealing with errors, which is why OtterScript has a Try/Catch. After converting the plan, you should wrap the error-prone blocks in a Try block and put the Error-handling statements in the Catch block.

Linked Action Groups

To facilitate sharing of common deployment plan logic, legacy plans provide the ability to link action groups across plans; this works with a kind of "inheritance" model where an instance of a linked group can override some properties (such as name, description, server targeting, etc.).

The closest OtterScript concept to this is a template. These tend to be quite a bit better since they can accommodate any number of parameters and often accomplish what most users have tried to do with linked action groups. However, since these are quite different concepts, it wasn't very practical or useful to try to translate them into the OtterScript converter.

When you convert a legacy plan to OtterScript, all linked action groups are simply carried over as if they were normal action groups.

To ensure reusability, you should create a template based on the original common group (and parameterize the parts to be "inherited" like the original), or generalize your entire deployment plan so that it can be used for different pipeline stages.

Actions vs. Operations

These are conceptually the same: 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 with other statements and blocks. This allows for more intuitive and less awkward multi-action behavior.

Most Actions translate directly to the corresponding Operation; however, if the Action utilized any of the following common "features", they translate 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 are 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 did one of two different things: it either updated the value of an external variable (release, build, etc.) or set the value of a new runtime variable. Unfortunately, it is impossible to know what it would do until runtime, so all Set Variable actions are replaced with a Set Variable statement.

To update an external configuration variable, you must use the Set Configuration Variable operation.

Also, variables in a legacy plan were always scaled globally, whereas OtterScript has block-level scoping. To reproduce this behavior, a set $variable=; statement is added to the beginning of the OtterScript plan after conversion.

Execute Global Deployment Plan

Since the new execution engine cannot execute legacy plans, it's not possible to convert this action into an operation. Instead, the global plan should be converted to a template plan and then called from the new plan. This also provides the advantage of parameters.

Create Build and Release

Since the corresponding operations are simpler and create only a single release or build (release package) in a single application, a translated version of this action can be wrapped in multiple looping and asynchronous blocks. Functionally it is the same and has the advantage of being better visualized and logged.

Source and Target Directories

Many Actions utilize the concept of a default directory (also represented as $CurrentDirectory), which was intended to be the working directory for both input ($SourceDirectory) and output ($TargetDirectory) files. If an Action uses both an input and an output file (e.g. when compiling) and the default is used for both actions, the input file is replaced by the output file once the action is no longer executing.

Unfortunately, this behavior is unintuitive and has been a major source of confusion and frustration. Therefore, Operations simply have a working directory and can specify different directories for the output.

Predicates

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