Packages
caffeine_lang
4.4.0
6.3.1
6.3.0
6.2.2
6.2.1
6.2.0
6.1.2
6.1.1
6.1.0
6.0.0
5.6.0
5.5.0
5.4.4
5.4.3
5.4.2
5.4.1
5.4.0
5.3.0
5.2.0
5.1.1
5.1.0
5.0.12
5.0.11
5.0.10
5.0.8
5.0.7
5.0.6
5.0.5
5.0.4
5.0.1
5.0.0
4.10.0
4.9.0
4.8.3
4.8.2
4.8.1
4.8.0
4.7.9
4.7.8
4.7.7
4.7.6
4.7.5
4.6.7
4.6.6
4.6.5
4.6.4
4.6.3
4.6.2
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.7
4.3.6
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.0.2
1.0.1
0.1.0
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.2
0.0.1
A compiler for generating reliability artifacts from service expectation definitions.
Current section
Files
Jump to
Current section
Files
src/caffeine_lang/codegen/honeycomb.gleam
import caffeine_lang/codegen/generator_utils
import caffeine_lang/constants
import caffeine_lang/errors.{type CompilationError}
import caffeine_lang/helpers
import caffeine_lang/linker/ir.{type IntermediateRepresentation}
import gleam/dict
import gleam/list
import gleam/option
import gleam/result
import terra_madre/common
import terra_madre/hcl
import terra_madre/terraform
/// Generate Terraform HCL from a list of Honeycomb IntermediateRepresentations.
pub fn generate_terraform(
irs: List(IntermediateRepresentation),
) -> Result(String, CompilationError) {
use #(resources, _warnings) <- result.try(generate_resources(irs))
Ok(generator_utils.render_terraform_config(
resources: resources,
settings: terraform_settings(),
providers: [provider()],
variables: variables(),
))
}
/// Generate only the Terraform resources for Honeycomb IRs (no config/provider).
@internal
pub fn generate_resources(
irs: List(IntermediateRepresentation),
) -> Result(#(List(terraform.Resource), List(String)), CompilationError) {
use resource_lists <- result.try(
irs |> list.try_map(ir_to_terraform_resources),
)
Ok(#(list.flatten(resource_lists), []))
}
/// Terraform settings block with required Honeycomb provider.
@internal
pub fn terraform_settings() -> terraform.TerraformSettings {
terraform.TerraformSettings(
required_version: option.None,
required_providers: dict.from_list([
#(
constants.provider_honeycombio,
terraform.ProviderRequirement(
"honeycombio/honeycombio",
option.Some("~> 0.31"),
),
),
]),
backend: option.None,
cloud: option.None,
)
}
/// Honeycomb provider configuration using variables for credentials.
@internal
pub fn provider() -> terraform.Provider {
terraform.Provider(
name: constants.provider_honeycombio,
alias: option.None,
attributes: dict.from_list([
#("api_key", hcl.ref("var.honeycomb_api_key")),
]),
blocks: [],
)
}
/// Variables for Honeycomb API credentials and dataset.
@internal
pub fn variables() -> List(terraform.Variable) {
[
terraform.Variable(
name: "honeycomb_api_key",
type_constraint: option.Some(hcl.Identifier("string")),
default: option.None,
description: option.Some("Honeycomb API key"),
sensitive: option.Some(True),
nullable: option.None,
validation: [],
),
terraform.Variable(
name: "honeycomb_dataset",
type_constraint: option.Some(hcl.Identifier("string")),
default: option.None,
description: option.Some("Honeycomb dataset slug"),
sensitive: option.None,
nullable: option.None,
validation: [],
),
]
}
/// Convert a single IntermediateRepresentation to Honeycomb Terraform Resources.
/// Produces a derived_column for the SLI and an SLO that references it.
@internal
pub fn ir_to_terraform_resources(
ir: IntermediateRepresentation,
) -> Result(List(terraform.Resource), CompilationError) {
let resource_name = common.sanitize_terraform_identifier(ir.unique_identifier)
// Extract structured SLO fields from IR.
use slo <- result.try(generator_utils.require_slo_fields(
ir,
vendor: constants.vendor_honeycomb,
))
let threshold = slo.threshold
let window_in_days = slo.window_in_days
let indicators = slo.indicators
// Extract the evaluation expression, then resolve it through the CQL pipeline
// by substituting indicator names into the evaluation formula.
use evaluation_expr <- result.try(generator_utils.require_evaluation(
slo,
ir,
vendor: constants.vendor_honeycomb,
))
use sli_expression <- result.try(generator_utils.resolve_cql_expression(
evaluation_expr,
indicators,
ir,
vendor: constants.vendor_honeycomb,
))
let time_period = window_to_time_period(window_in_days)
let derived_column_alias = resource_name <> "_sli"
// Resource 1: honeycombio_derived_column for the SLI.
let derived_column =
terraform.Resource(
type_: "honeycombio_derived_column",
name: derived_column_alias,
attributes: dict.from_list([
#("alias", hcl.StringLiteral(derived_column_alias)),
#("expression", hcl.StringLiteral(sli_expression)),
#("dataset", hcl.ref("var.honeycomb_dataset")),
]),
blocks: [],
meta: hcl.empty_meta(),
lifecycle: option.None,
)
// Resource 2: honeycombio_slo.
let slo_description = generator_utils.build_description(ir)
let slo_attributes = [
#("name", hcl.StringLiteral(ir.metadata.friendly_label)),
#("description", hcl.StringLiteral(slo_description)),
#("dataset", hcl.ref("var.honeycomb_dataset")),
#(
"sli",
hcl.ref("honeycombio_derived_column." <> derived_column_alias <> ".alias"),
),
#("target_percentage", hcl.FloatLiteral(threshold)),
#("time_period", hcl.IntLiteral(time_period)),
#("tags", build_tags(ir)),
]
let slo =
terraform.Resource(
type_: "honeycombio_slo",
name: resource_name,
attributes: dict.from_list(slo_attributes),
blocks: [],
meta: hcl.empty_meta(),
lifecycle: option.None,
)
Ok([derived_column, slo])
}
/// Build tags as a map expression for Honeycomb.
/// Honeycomb uses a map of string to string for tags.
fn build_tags(ir: IntermediateRepresentation) -> hcl.Expr {
// Build system tags from shared helper. For Honeycomb, misc tags with multiple
// values are joined with commas since the tag format is a flat map.
let system_tag_pairs =
helpers.build_system_tag_pairs(
org_name: ir.metadata.org_name,
team_name: ir.metadata.team_name,
service_name: ir.metadata.service_name,
blueprint_name: ir.metadata.blueprint_name,
friendly_label: ir.metadata.friendly_label,
artifact_refs: [],
misc: ir.metadata.misc,
)
|> collapse_multi_value_tags
// Build user-provided tags from structured artifact data.
let user_tag_pairs = case ir.get_slo_fields(ir.artifact_data) {
option.Some(slo) -> slo.tags
option.None -> []
}
let all_tags =
list.append(system_tag_pairs, user_tag_pairs)
|> list.map(fn(pair) { #(hcl.IdentKey(pair.0), hcl.StringLiteral(pair.1)) })
hcl.MapExpr(all_tags)
}
/// Collapse tag pairs that share the same key by joining values with commas.
/// The shared helper produces one pair per misc value, but Honeycomb needs a
/// single key-value entry per tag key. Preserves insertion order of first occurrence.
fn collapse_multi_value_tags(
pairs: List(#(String, String)),
) -> List(#(String, String)) {
let #(order, merged) =
pairs
|> list.fold(#([], dict.new()), fn(acc, pair) {
let #(keys, seen) = acc
let #(key, value) = pair
case dict.get(seen, key) {
Ok(existing) -> #(
keys,
dict.insert(seen, key, existing <> "," <> value),
)
Error(_) -> #([key, ..keys], dict.insert(seen, key, value))
}
})
order
|> list.reverse
|> list.filter_map(fn(key) {
dict.get(merged, key)
|> result.map(fn(value) { #(key, value) })
})
}
/// Convert window_in_days to Honeycomb time_period (in days).
/// Range (1-90) is guaranteed by the standard library type constraint.
@internal
pub fn window_to_time_period(days: Int) -> Int {
days
}