Skip to main content

Components and Actions

In Anemos, the generation process is orchestrated by a Builder. Builder consists of multiple Component objects. Each Component represents a logical unit of work, often corresponding to a specific application or a configuration task. Component objects themselves are composed of multiple Actions. Actions are functions that are responsible for performing specific tasks in a defined sequence.

When Builder.build() is called, all actions from all components are collected, sorted by their steps, and executed sequentially. Following diagram illustrates the execution flow of a Builder. More detailed information about the execution of steps can be found in Execution Order.

Components

A Component encapsulates a set of operations needed for a specific application or task. For example, you might have a component for generating backend service manifests, or a component that modifies ingress resources to add some specific annotations. Each component can be thought of as a plugin that can be added to the Builder to extend its functionality.

Components are generally defined by extending the Component class. It is also possible to create a component using the constructor directly, but extending the Component class encapsulates the logic and provides a cleaner interface.

The Component class provides methods to manage actions. It is designed to be flexible and extensible, allowing you to create custom components that fit your specific needs.

Creating a Custom Component

To create a custom component, you extend the Component class and add the necessary actions within its constructor. The Component class provides the addAction method to register these actions along with their steps. Let's create a new file and define a custom component:

component.ts
import * as anemos from "@ohayocorp/anemos";

export class Component extends anemos.Component {
constructor() {
super();
// Actions will be added here.
}
}

Actions

Components themselves don't perform tasks; they serve as containers for actions. Actions do the actual work, such as generating documents, modifying existing ones, or performing other operations.

Actions are executed in a specific order, defined when adding them to a component using the addAction method. This sequence is important, as dependencies might exist between tasks (e.g. generating resources before modifying them). Anemos provides predefined steps like steps.generateResources and steps.modify to help manage common sequences.

Actions are simple functions that accept a BuildContext object as an argument. The BuildContext provides access to shared state, builder options, existing documents generated during previous steps, and other utilities necessary for the action's execution.

Let's add an action to our custom component. We will move the code from the previous section that generates the Kubernetes manifests into the action. This will allow us to generate the documents when the action is executed.

component.ts
import * as anemos from "@ohayocorp/anemos";

export class Component extends anemos.Component {
constructor() {
super();

this.addAction(anemos.steps.generateResources, this.generateResources);
}

generateResources = (context: anemos.BuildContext) => {
const name = "example-app";
const namespace = "default";
const image = "nginx";
const replicas = 1;

context.addDocument(
`deployment.yaml`,
`
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${name}
namespace: ${namespace}
spec:
replicas: ${replicas}
selector:
matchLabels:
app: ${name}
template:
metadata:
labels:
app: ${name}
spec:
containers:
- name: app
image: ${image}
ports:
- containerPort: 80
`);

context.addDocument(
`service.yaml`,
{
apiVersion: "v1",
kind: "Service",
metadata: {
name: name,
namespace: namespace,
},
spec: {
selector: {
app: name
},
ports: [
{
protocol: "TCP",
port: 80,
targetPort: 80
}
]
}
});
};
}
note

Instead of using builder.addDocument, we are now using context.addDocument to add documents to the context.

The builder.addDocument method that was used in the previous sections is a shorthand for creating a component that adds the given document to the context during the steps.generateResources step. It is similar to the following code block:

const component = new anemos.Component();

component.addAction(anemos.steps.generateResources, (context: anemos.BuildContext) => {
context.addDocument(document);
});

builder.addComponent(component);

Now that we have defined our custom component, we can use it in our main script. Replace the previous code that generates the Kubernetes manifests in the main script with the following code that creates an instance of our custom component and adds it to the builder:

index.ts
import * as anemos from "@ohayocorp/anemos";
import { Component } from "./component";

const builder = new anemos.Builder("1.31", anemos.KubernetesDistribution.Minikube, anemos.EnvironmentType.Development);

builder.addComponent(new Component());

builder.build();

Now, run Anemos build to generate the manifests and see that the output is the same as before:

anemos build --tsc . dist/index.js