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 Action
s. Action
s 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