Today, we are delighted to release the functionality to create templates using management functions.
Management Functions are a powerful new primitive that unlocks the capability to:
This new function type expands on the programmable nature of System Initiative, with a new interface for automating configuration of multiple assets at once. The power lies in the inputs and outputs for the function and the ability to create and remove management connections on the fly.
The inputs to a Management Function are the properties and connections of the current component the Management Function is attached to, as well as any components you've drawn connections to. This means components can manage themselves, which we leverage for Import, and it means you can create specific assets that are able to manage other components as well, which we leverage for Templates.
The outputs for the Management Function enable you to:
The Management Connection is new type of relationship, that can be dynamically added and removed to fit your needs. For example, if you have a collection of components you need to modify together, that don't currently have a Management Connection, you can write the function to make the necessary changes, create the relationships between the components you need to modify, run the function, then remove the Management Connection when you're done.
Let's look more closely at the key use cases for Management Functions, and how these primitives are used to enable Import, Templates, and Configuration of multiple existing components.
A management function allows the creation, configuration, and management of other components on the graph. The function can manipulate connections between components, enqueue actions on the components. This means a user can create a management function that codifies the configuration needed to create a segment of infrastructure - a template!
Let’s look at an example of creating an AWS VPC that spans multiple availability zones and segments the network into private and public subnets. There are many connected components required to form the structure of a VPC, but a user generally has to specify the AWS region in which to create the infrastructure, the base CIDR block, and the number of public and private subnets. These pieces of information will become the schema for the asset. The verbosity of the component configuration, business logic, and connections are encapsulated in the management function.
The components that the template generates are added to the diagram as ordinary components and can be reconfigured just as if they were added manually by the user. This means that edge cases, exceptions, and alternative configurations can be handled by direct reconfiguration of the components created by the template. For example, if the management function generates an internet gateway but no gateway is required, the user can just delete it. In traditional IaC, if the module is not written in a manner to support this workflow, the user would need to act in a non-sustainable way, i.e. forking the module and making the changes, creating resources outside of the module or keep those changes outside of IaC because it’s too difficult to change. Management functions support both workflows. The user can easily reconfigure the components created by the template function, and they can also modify the template to suit their needs by editing the management function.
A sample of the management function in the example above would be as follows:
async function main({ thisComponent, components }: Input): Promise<Output> {
const cidrBlock = thisComponent.properties.domain.BaseCidrBlock;
const region = thisComponent.properties.domain.Region;
const tags = thisComponent.properties.domain.Tags;
const baseName = thisComponent.properties.domain.BaseNamingConvention;
const compsToCreate: {
[key: string]: unknown;
} = {};
let counter = 0;
if (!cidrBlock) {
return {
status: "error",
message: "Cidr Block is missing",
};
}
if (!region) {
return {
status: "error",
message: "Region is missing",
};
}
const vpcName = `${baseName}-vpc`;
if (
!_.some(
Object.values(components),
(comp) => comp.properties.si?.name === vpcName,
)
) {
compsToCreate[vpcName] = {
kind: "VPC",
properties: {
si: {
name: vpcName,
type: "configurationFrameDown",
},
domain: {
CidrBlock: cidrBlock,
EnableDnsHostnames: true,
EnableDnsResolution: true,
tags,
region,
},
},
};
counter++;
}
const publicRouteTableName = `${baseName}-public-route-table`;
if (
!_.some(
Object.values(components),
(comp) => comp.properties.si?.name === publicRouteTableName,
)
) {
compsToCreate[publicRouteTableName] = {
kind: "Route Table",
properties: {
si: {
name: publicRouteTableName,
},
domain: {
Tags: tags,
Region: region,
},
},
parent: vpcName,
};
counter++;
}
return {
status: "ok",
message: `Created ${counter} new components`,
ops: {
create: compsToCreate,
},
};
}
This is an idempotent template that, when configured and executed by the user, will create a VPC configuration frame with the user-specified CIDR block and region and put a public route table component as a child of that VPC configuration frame. The resulting components are now under the management of the template asset and have had the correct actions scheduled to create the components when the change set is merged to HEAD.
The simplicity of authoring within System Initiative empowers you to create non-monolithic templates. The template can be written such that different parts of the template are responsible for different components under management. A single template can have a function responsible for the VPC components, one for the subnets, and one for the tags across all resulting components. These functions are more targeted, concise, and, therefore, easier to write and maintain.
A common use case for teams that use the cloud, is to ensure that the resources that they create, follow specific tagging conventions. These tagging conventions allow crucial tasks like cost management and budget tracking to be carried out effectively. Ensuring tagging conventions across resources in System Initiative becomes much easier with the release of management functions.
As before, the user can create an asset, tag manager
that specifies a list of tags. The asset can then have a function attached as follows:
async function main({ thisComponent, components }: Input): Promise<Output> {
const managedTags = thisComponent.properties?.domain?.Tags;
let counter = 0;
const updatedComponents: {
[key: string]: unknown;
} = {};
for (let [id, component] of Object.entries(components)) {
console.log(`Looking at component ${component.properties.si.name}`);
console.log(`Adding Tags ${managedTags}`);
if (component.properties.domain.hasOwnProperty("tags")) {
updatedComponents[id] = {
properties: {
...component.properties,
domain: {
tags: {
...component.properties.domain.tags,
...managedTags,
},
},
},
};
counter++;
} else {
console.log("tags property not found");
}
}
return {
status: "ok",
message: `Updated ${counter} components`,
ops: {
update: updatedComponents,
},
};
}
Using a management edge from the tag manager to the components to be managed, allows the management function to have all the context about the components it needs to manage. When we execute the management function it will set the correct data on each of the components in its context. In this case, it would ensure that specific tags are available on the component.
Management functions are available today! At launch, we have published a VPC Template
asset that you can use to create a best practice AWS VPC that spans multiple availability zones. We will continue to make more management functions available to use as templates and for managing infrastructure. If you have any questions, feedback on the feature, ideas for a template, or want help to create a management function, please join us on Discord and talk to us there. You can message me directly at SI_Stack72.
Paul is an engineer turned product manager who is passionate about the Continuous Delivery and DevOps movements and how they are critical in helping businesses deliver value to their customers.