135 lines
4.8 KiB
Markdown
135 lines
4.8 KiB
Markdown
---
|
|
title: "Using Go Generics with Pulumi"
|
|
date: 2022-03-31T14:00:00Z
|
|
draft: false
|
|
meta_desc: In this article, @rawkode shows you how to take advantage of Go's latest feature, Generics, in your Pulumi programs
|
|
meta_image: meta.png
|
|
authors: ["david-flanagan"]
|
|
tags: ["community"]
|
|
---
|
|
|
|
{{% notes type="warning" %}}
|
|
This post is outdated. For the latest on Go Generics, [visit this blog post](/blog/go-generics-preview/).
|
|
{{% /notes %}}
|
|
|
|
March 15th, 2022... just two weeks ago. The Go team [released Go 1.18](https://go.dev/blog/go1.18) to the world. What seems like a trivial point release actually brings a huge new feature to the Go language: Generics.
|
|
|
|
In this article, I want to show you how you can use this new feature to build a great developer experience with your abstractions for your Pulumi programs.
|
|
|
|
<!--more-->
|
|
|
|
## Using Go 1.18
|
|
|
|
The first thing you need to do is ensure you have Go 1.18 installed. You can run `go version` to check. If you see anything less than 1.18, you'll need to go upgrade first. The second thing you need to do is update the `go.mod` in your Pulumi program to specify 1.18 as the minimum version.
|
|
|
|
```go
|
|
go 1.18
|
|
```
|
|
|
|
Once you've done these two steps, you're ready to start using generics in your Pulumi programs.
|
|
|
|
## Using Generics
|
|
|
|
Let's start by asking a question. What is a good use-case for generics? In my experience, generics work really well for allowing us to provide a common interface to our consumers (developers using our APIs) that allows them to use that same interface to accomplish a collection of similar tasks that require different implementations.
|
|
|
|
For example, let's assume we want to provide a platform to our developers and allow them to install **ANY** Kubernetes resource. Our goal is to provide an `AddComponent` method that they can call to install either pre-supported components, components created by the platform team, or their own custom components. The glue and important aspect here is that all these components conform to the same interface.
|
|
|
|
### Defining The Interface
|
|
|
|
In the simplest form, we just need an `Install` function to call that returns either an error or an array of Pulumi resources.
|
|
|
|
```go
|
|
type Component interface {
|
|
Install(ctx *pulumi.Context, name string) ([]pulumi.Resource, error)
|
|
}
|
|
```
|
|
|
|
### Creating our Components
|
|
|
|
Now we need to provide a component that satisfies this interface. So let's assume that we want to install nginx. First, we create a struct that contains fields for any points of configuration. For today's example, we'll just request the version to be installed and a name; the name being used to ensure if the component is installed more than once, it can be uniquely identified.
|
|
|
|
```go
|
|
type Nginx struct {
|
|
Version string
|
|
}
|
|
|
|
func (c *Nginx) Install(ctx *pulumi.Context, name string) ([]pulumi.Resource, error) {
|
|
return []
|
|
}
|
|
```
|
|
|
|
From there, we can begin to flesh out the `Install` implementation for nginx.
|
|
|
|
```go
|
|
func (c *Nginx) Install(ctx *pulumi.Context) ([]pulumi.Resource, error) {
|
|
deployment, err := appsv1.NewDeployment(ctx, name, &appsv1.DeploymentArgs{
|
|
Metadata: &metav1.ObjectMetaArgs{
|
|
Labels: pulumi.StringMap{
|
|
"app": pulumi.String(name),
|
|
},
|
|
},
|
|
Spec: &appsv1.DeploymentSpecArgs{
|
|
Replicas: pulumi.Int(3),
|
|
Selector: &metav1.LabelSelectorArgs{
|
|
MatchLabels: pulumi.StringMap{
|
|
"app": pulumi.String(name),
|
|
},
|
|
},
|
|
Template: &corev1.PodTemplateSpecArgs{
|
|
Metadata: &metav1.ObjectMetaArgs{
|
|
Labels: pulumi.StringMap{
|
|
"app": pulumi.String(name),
|
|
},
|
|
},
|
|
Spec: &corev1.PodSpecArgs{
|
|
Containers: corev1.ContainerArray{
|
|
&corev1.ContainerArgs{
|
|
Name: pulumi.String("nginx"),
|
|
Image: pulumi.String(fmt.Sprintf("nginx:%s", c.Version)),
|
|
Ports: corev1.ContainerPortArray{
|
|
&corev1.ContainerPortArgs{
|
|
ContainerPort: pulumi.Int(80),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []pulumi.Resource{
|
|
deployment,
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
This is rather contrived, but hopefully you can see the power of using generics as an interface for platform engineering. Our `AddComponent` implementation for nginx could return a deployment, a service, an ingress with horizontal pod auto-scalers, or any other resource that you want.
|
|
|
|
## Using the Components
|
|
|
|
Finally, we create our generic function, `AddComponent`, and start having some fun.
|
|
|
|
```go
|
|
func AddComponent[C Component](ctx *pulumi.Context, name string, component C) ([]pulumi.Resource, error) {
|
|
return component.Install(ctx, name)
|
|
}
|
|
|
|
func main() {
|
|
pulumi.Run(func(ctx *pulumi.Context) error {
|
|
AddComponent(ctx, "my-nginx", &Nginx{Version: "1.14.2"})
|
|
|
|
return nil
|
|
})
|
|
}
|
|
```
|
|
|
|
Now you and your organization can provide a repository of these components for your developers to consume with their platforms.
|
|
|
|
The possibilities are endless.
|
|
|
|
Have fun and see you soon! 👋
|