Lately, I’ve started my Pulumi journey. I don’t come from a Terraform (TF) world, so there are some new concepts I’m learning. Overall, things are going well and Pulumi will likely be my choice for Infrastructure as Code (IaC) going forward. Pulumi lets me use make use of the full resources of the language of my choice. That means, I’m not stuck with JSON (ARM), whatever limited functions that Bicep provides, or only partial GO support that TF allows.
What’s interesting about this is I can make my own private or public NuGet packages to share templates with my team. I can create wrappers around Pulumi that only exposes the attributes that I want downstream projects to be able to use, or black box some of the nuances that Pulumi has. With C# has my choice of language, I can also add extension methods to Pulumi classes as well.
Take a look at this configuration class I created for an Azure Container Instance (ACI).
public class Configuration
{
public string Name { get; init; } = null!;
public string ResourceGroupName { get; init; } = null!;
public Output<string> ImageName { get; init; } = null!;
public double MemoryInGb { get; init; } = 1.5;
public double CPUCores { get; init; } = 1;
public PortArgs Ports { get; init; } = null!;
}
If you aren’t familiar with ACI, it’s an instance of a docker container in Azure. You can pull public and private images from Docker Hub or from an Azure Container Registry.
ACI is a compute type resource, which means that it always costs you money regardless of what it’s doing. When you start adding more CPU or Memory to the container, it starts to get expensive.
If you allow your infrastructure team or dev team to stand up these ACIs, without any governance around those properties, you might be looking at a hefty bill.
We can mitigate that risk by using Fluent Validation. “FluentValidation is a .NET library for building strongly-typed validation rules.” It allows you to validate object values against any business rules that you define. It’s great for public API classes where you might allow property “B” to be null unless property “C” is set.
In this case, it’s great for validating business rules for your IaC. Here’s a (shortened) configuration validator that I created for an ACI.
public class ConfigurationValidation : AbstractValidator<Configuration>
{
public ConfigurationValidation()
{
RuleFor(x => x.Name).NotNull();
RuleFor(x => x.ResourceGroupName).NotNull();
RuleFor(x => x.ImageName).NotNull();
RuleFor(x => x.Ports).NotNull();
RuleFor(x => x.MemoryInGb).NotNull().LessThan(5);
RuleFor(x => x.CPUCores).NotNull().LessThan(4);
}
}
Notice the lambda expression to access the properties of my configuration class, and that Fluent Validation allows me to chain rules together. This is a basic example, and there’s a lot more you could do here.
With the validator in place, you can call validation on the configuration before creating the resource. Fluent Validation will throw an exception if any of the rules fail, which will also make the Pulumi run fail. That means, if someone misconfigures a resource, nothing will happen and the mistake can be corrected.
public class ContainerInstanceTemplate
{
public static ContainerGroup Create(Configuration config)
{
config.Validate();
var containerGroup = new ContainerGroup(config.Name, new ContainerGroupArgs
{
.../
}
}
}
The Validation function above is located on a base class that all my configurations inherit from, but you could just use the default way of using Fluent Validation.
public void Validate()
{
var validationResult = _validator.Validate((T)this);
if (validationResult.IsValid) return;
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Validation failed for {typeof(T).Name}");
validationResult.Errors.ForEach(failure =>
stringBuilder.AppendLine(
$"Property {failure.PropertyName} failed validation. Error was: {failure.ErrorMessage}"
)
);
var message = stringBuilder.ToString();
Log.Error(message);
throw new Exception(message);
}
The Log.Error
is a Pulumi logger. Whatever message you provide here will show in the console. It does log the exception, but Pulumi recommends that you explicitly call the log methods.
As always, if you have any questions or comments, please let me know! I'm happy to help. I hope this provided you with something new to try out in your Pulumi journey.
Comments
You can also comment directly on GitHub.