Execution Order
Steps
Anemos uses a class called Step
to sort actions flexibly. The Step
class has a description
(for logging) and a list of numbers.
When comparing two Step
objects, the numbers in their respective lists are compared index by index.
If one list is shorter than the other, missing numbers at higher indices are treated as 0 for comparison.
For example, comparing [1, 2]
with [1]
:
- Index 0: Both have
1
. They are equal. - Index 1: The first step has
2
, the second has no number (treated as0
). Since2 > 0
, the first step[1, 2]
is considered greater than the second step[1]
.
This system allows for fine-grained control over execution sequence. Consider these standard steps provided by Anemos:
generateResources
:[5]
generateResourcesBasedOnOtherResources
:[5, 1]
modify
:[6]
Comparing these:
generateResources
[5] is less thanmodify
[6] because5 < 6
.generateResourcesBasedOnOtherResources
[5, 1] is greater thangenerateResources
[5] because at index 1,1 > 0
.generateResourcesBasedOnOtherResources
[5, 1] is less thanmodify
[6] because at index 0,5 < 6
.
This structure allows inserting custom steps between existing ones. For instance, a step like
[5, 1, 1]
would execute after [5, 1]
but before [5, 2]
. Similarly, [5, 0, 1]
would run after [5]
but before [5, 1]
.
Executing Actions
The Builder
orchestrates the execution of actions. When you call the build()
method:
- It collects all actions from all added components.
- It sorts these actions based on their assigned
Step
. - It executes the actions sequentially in the sorted order.
Example: Execution Order in Practice
Consider the following example components:
- TypeScript
- JavaScript
import * as anemos from "@ohayocorp/anemos";
class App1Component extends anemos.Component {
constructor() {
super();
this.addAction(anemos.steps.generateResources, this.generateResources);
}
generateResources = (context: anemos.BuildContext) => {
context.addDocument(
`app1-pod.yaml`,
`
apiVersion: v1
kind: Pod
metadata:
name: app1
labels:
app: app1
spec:
containers:
- name: app1
image: app1:latest
`);
};
}
class App2Component extends anemos.Component {
constructor() {
super();
this.addAction(anemos.steps.generateResources, this.generateResources);
}
generateResources = (context: anemos.BuildContext) => {
context.addDocument(
`app2-pod.yaml`,
`
apiVersion: v1
kind: Pod
metadata:
name: app2
labels:
app: app2
spec:
containers:
- name: app2
image: app2:latest
`);
};
}
class SetNodeNameComponent extends anemos.Component {
constructor() {
super();
this.addAction(anemos.steps.modify, this.modifyResources);
}
modifyResources = (context: anemos.BuildContext) => {
for (const document of context.getAllDocuments()) {
// Check if the document is a workload (e.g. Pod, Deployment, etc.) and skip if not.
if (!document.isWorkload()) {
continue;
}
// If the document does not have the label "app: app1", skip it.
if (document.getLabel("app") !== "app1") {
continue;
}
// Run app1 pods on node "worker-1".
document.ensureWorkloadSpec().set("nodeName", "worker-1");
}
};
}
const builder = new anemos.Builder("1.31", anemos.KubernetesDistribution.Minikube, anemos.EnvironmentType.Development);
builder.addComponent(new SetNodeNameComponent());
builder.addComponent(new App1Component());
builder.addComponent(new App2Component());
builder.build();
const anemos = require("@ohayocorp/anemos");
class App1Component extends anemos.Component {
constructor() {
super();
this.addAction(anemos.steps.generateResources, this.generateResources);
}
generateResources = (context) => {
context.addDocument(
`app1-pod.yaml`,
`
apiVersion: v1
kind: Pod
metadata:
name: app1
labels:
app: app1
spec:
containers:
- name: app1
image: app1:latest
`);
};
}
class App2Component extends anemos.Component {
constructor() {
super();
this.addAction(anemos.steps.generateResources, this.generateResources);
}
generateResources = (context) => {
context.addDocument(
`app2-pod.yaml`,
`
apiVersion: v1
kind: Pod
metadata:
name: app2
labels:
app: app2
spec:
containers:
- name: app2
image: app2:latest
`);
};
}
class SetNodeNameComponent extends anemos.Component {
constructor() {
super();
this.addAction(anemos.steps.modify, this.modifyResources);
}
modifyResources = (context) => {
for (const document of context.getAllDocuments()) {
// Check if the document is a workload (e.g. Pod, Deployment, etc.) and skip if not.
if (!document.isWorkload()) {
continue;
}
// If the document does not have the label "app: app1", skip it.
if (document.getLabel("app") !== "app1") {
continue;
}
// Run app1 pods on node "worker-1".
document.ensureWorkloadSpec().set("nodeName", "worker-1");
}
};
}
const builder = new anemos.Builder("1.31", anemos.KubernetesDistribution.Minikube, anemos.EnvironmentType.Development);
builder.addComponent(new SetNodeNameComponent());
builder.addComponent(new App1Component());
builder.addComponent(new App2Component());
builder.build();
In this example:
App1Component
adds an action with stepgenerateResources
.App2Component
adds an action with stepgenerateResources
.SetNodeNameComponent
adds an action with stepmodify
.
When builder.build()
is called:
- The actions are collected.
- They are ordered by their steps:
generateResources
[5]
comes beforemodify
[6]
. - Execution proceeds:
- The
generateResources
action fromApp1Component
runs. - The
generateResources
action fromApp2Component
runs. - The
modify
action fromSetNodeNameComponent
runs.
- The
Even if SetNodeNameComponent
was added to the builder before App1Component
or App2Component
,
its modify
action runs after their generateResources
actions due to the defined Step
ordering.
For actions that run during the same Step
(like the two generateResources
actions), their relative
execution sequence is determined by the order in which their parent components were added to the
Builder
. Since App1Component
was added first, its generateResources
action runs before the one
from App2Component
.