Constructs
Having a way to modularize our deployment code can decrease code complexity and reduce code duplication. tf-bindgen
will utilize constructs similar to Terraform CDK Constructs and will use the Construct
derive macro to implement the required traits.
In this section, we will create a construct to deploy a nginx pod to Kubernetes. To create our construct, we will start with creating a struct and adding the Construct
derive macro:
use tf_bindgen::codegen::Construct;
use tf_bindgen::Scope;
#[derive(Construct)]
pub struct Nginx {
#[construct(id)]
name: String,
#[construct(scope)]
scope: Rc<dyn Scope>
}
In addition, we added two fields to our struct: name
and scope
. Both fields are necessary and we have to add the #[construct(id)]
and #[construct(scope)]
annotation to these fields.
Using this declaration, our derive macro will do nothing more than implementing the Scope
trait for our Nginx
struct. This trait is necessary to use this struct instead of stack in resources and data sources.
Generating a Builder
In most cases, it will be necessary to pass extra information to our construct. So we need a way to add and set parameters to our construct. We can use the builder
option of the Construct
derive macro by adding #[construct(builder)]
annotation to our struct. In addition, we will add to fields namespace
and image
to our struct:
use tf_bindgen::codegen::{Construct, resource};
use tf_bindgen::Scope;
#[derive(Construct)]
#[construct(builder)]
pub struct Nginx {
#[construct(id)]
name: String,
#[construct(scope)]
scope: Rc<dyn Scope>,
#[construct(setter(into_value))]
namespace: Value<String>,
#[construct(setter(into_value))]
image: Value<String>,
}
This code snippet allows creating an Nginx construct using the following builder:
Nginx::create(scope, "<name>")
.namespace("default")
.image("nginx")
.build();
Note that we have to implement the build function ourselves. But before we will implement this function, we must consider different setter options:
#[construct(setter)]
or no annotation: You can this to generate setters taking the same type as the field as input.#[construct(setter(into))]
This annotation is used to generate setters takingInto<T>
where T is the type of the field as an argument.#[construct(setter(into_value))]
This annotation is used to generate setters takingIntoValue<T>
where T is the type of the field as an argument. In addition, this field must be of typeValue<T>
.#[construct(setter(into_value_list))]
This annotation is used to generate setters taking objects implementingIntoValueList<T>
as an argument. In addition, this field must be of typeVec<Value<T>>
.#[construct(setter(into_value_set))]
This annotation is used to generate setters taking objects implementingIntoValueSet<T>
as an argument. In addition, this field must be of typeHashSet<Value<T>>
.#[construct(setter(into_value_map)))]
This annotation is used to generate setters taking objects implementingIntoValueMap<T>
as an argument. In addition, this field must be of typeHashMap<String, Value<T>>
.
In general, it is recommended to use Value
wrapped types to ensure better compatibility with tf-bindgen
. It also allows using references as a Value.
Creating Resources
Finally, we want to create our resources. We will create the already mentioned build
function for that. It is important to note, that we will not implement the function for Nginx
but rather for NginxBuilder
, a type generated by our Construct
derive macro.
To implement our build function, we will start with creating our construct type. For that we will need to clone our name
and scope
field. Because every other field will be wrapped inside an Option
-type, we will need to clone and unwrap them (in our case, we will use expect instead). In addition, it is essential to wrap our type inside a reference counter Rc
, because it is required to use a construct as a scope.
After we created our construct, we can use it to create our resources. The following example, will show an implementation for a nginx container inside a Kubernetes pod:
impl NginxBuilder {
pub fn build(&mut self) -> Rc<Postgres> {
let this = Rc::new(Postgres {
name: self.name.clone(),
scope: self.scope.clone(),
namespace: self.namespace.clone().expect("missing field 'namespace'"),
image: self.image.clone().expect("missing field 'image'")
});
let name = &this.name;
tf_bindgen::codegen::resource! {
&this, resource "kubernetes_pod" "nginx" {
metadata {
namespace = &this.namespace
name = format!("nginx-{name}")
}
spec {
container {
name = "nginx"
image = &this.image
port {
container_port = 80
}
}
}
}
};
this
}
}
Outputs
TODO