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:
- TypeScript
- JavaScript
import * as anemos from "@ohayocorp/anemos";
export class Component extends anemos.Component {
constructor() {
super();
// Actions will be added here.
}
}
const anemos = require("@ohayocorp/anemos");
class Component extends anemos.Component {
constructor() {
super();
// Actions will be added here.
}
}
module.exports = Component;
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.
- TypeScript
- JavaScript
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
}
]
}
});
};
}
const anemos = require("@ohayocorp/anemos");
class Component extends anemos.Component {
constructor() {
super();
this.addAction(anemos.steps.generateResources, this.generateResources);
}
generateResources = (context) => {
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
}
]
}
});
};
}
module.exports = Component;
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:
- TypeScript
- JavaScript
const component = new anemos.Component();
component.addAction(anemos.steps.generateResources, (context: anemos.BuildContext) => {
context.addDocument(document);
});
builder.addComponent(component);
const component = new anemos.Component();
component.addAction(anemos.steps.generateResources, (context) => {
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:
- TypeScript
- JavaScript
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();
const anemos = require("@ohayocorp/anemos");
const Component = require("./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:
- TypeScript
- JavaScript
anemos build --tsc . dist/index.js
anemos build index.js