Packages
caffeine_lang
4.3.6
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/dynatrace.gleam
import caffeine_lang/linker/ir.{type IntermediateRepresentation, ir_to_identifier}
import caffeine_lang/codegen/generator_utils
import caffeine_lang/constants
import caffeine_lang/errors.{
type CompilationError, GeneratorTerraformResolutionError,
}
import caffeine_query_language/generator as cql_generator
import gleam/dict
import gleam/int
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 Dynatrace IntermediateRepresentations.
pub fn generate_terraform(
irs: List(IntermediateRepresentation),
) -> Result(String, CompilationError) {
use resources <- 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 Dynatrace IRs (no config/provider).
@internal
pub fn generate_resources(
irs: List(IntermediateRepresentation),
) -> Result(List(terraform.Resource), CompilationError) {
irs |> list.try_map(ir_to_terraform_resource)
}
/// Terraform settings block with required Dynatrace provider.
@internal
pub fn terraform_settings() -> terraform.TerraformSettings {
terraform.TerraformSettings(
required_version: option.None,
required_providers: dict.from_list([
#(
constants.provider_dynatrace,
terraform.ProviderRequirement(
"dynatrace-oss/dynatrace",
option.Some("~> 1.0"),
),
),
]),
backend: option.None,
cloud: option.None,
)
}
/// Dynatrace provider configuration using variables for credentials.
@internal
pub fn provider() -> terraform.Provider {
terraform.Provider(
name: constants.provider_dynatrace,
alias: option.None,
attributes: dict.from_list([
#("dt_env_url", hcl.ref("var.dynatrace_env_url")),
#("dt_api_token", hcl.ref("var.dynatrace_api_token")),
]),
blocks: [],
)
}
/// Variables for Dynatrace environment URL and API token.
@internal
pub fn variables() -> List(terraform.Variable) {
[
terraform.Variable(
name: "dynatrace_env_url",
type_constraint: option.Some(hcl.Identifier("string")),
default: option.None,
description: option.Some("Dynatrace environment URL"),
sensitive: option.None,
nullable: option.None,
validation: [],
),
terraform.Variable(
name: "dynatrace_api_token",
type_constraint: option.Some(hcl.Identifier("string")),
default: option.None,
description: option.Some("Dynatrace API token"),
sensitive: option.Some(True),
nullable: option.None,
validation: [],
),
]
}
/// Convert a single IntermediateRepresentation to a Dynatrace Terraform Resource.
/// Produces a single `dynatrace_slo_v2` resource.
@internal
pub fn ir_to_terraform_resource(
ir: IntermediateRepresentation,
) -> Result(terraform.Resource, CompilationError) {
let identifier = ir_to_identifier(ir)
let resource_name = common.sanitize_terraform_identifier(ir.unique_identifier)
// Extract structured SLO fields from IR.
use slo <- result.try(
ir.get_slo_fields(ir.artifact_data)
|> option.to_result(GeneratorTerraformResolutionError(
vendor: constants.vendor_dynatrace,
msg: "expectation '" <> identifier <> "' - missing SLO artifact data",
context: errors.empty_context(),
)),
)
// Extract the evaluation expression, then resolve it through the CQL pipeline.
use evaluation_expr <- result.try(
slo.evaluation
|> option.to_result(GeneratorTerraformResolutionError(
vendor: constants.vendor_dynatrace,
msg: "expectation '"
<> identifier
<> "' - missing evaluation for Dynatrace SLO",
context: errors.empty_context(),
)),
)
use metric_expression <- result.try(
cql_generator.resolve_slo_to_expression(evaluation_expr, slo.indicators)
|> result.map_error(fn(err) {
GeneratorTerraformResolutionError(
vendor: constants.vendor_dynatrace,
msg: "expectation '" <> identifier <> "' - " <> err,
context: errors.empty_context(),
)
}),
)
let evaluation_window = window_to_evaluation_window(slo.window_in_days)
let description = build_description(ir)
let resource =
terraform.Resource(
type_: "dynatrace_slo_v2",
name: resource_name,
attributes: dict.from_list([
#("name", hcl.StringLiteral(ir.metadata.friendly_label)),
#("enabled", hcl.BoolLiteral(True)),
#("custom_description", hcl.StringLiteral(description)),
#("evaluation_type", hcl.StringLiteral("AGGREGATE")),
#("evaluation_window", hcl.StringLiteral(evaluation_window)),
#("metric_expression", hcl.StringLiteral(metric_expression)),
#("metric_name", hcl.StringLiteral(resource_name)),
#("target_success", hcl.FloatLiteral(slo.threshold)),
]),
blocks: [],
meta: hcl.empty_meta(),
lifecycle: option.None,
)
Ok(resource)
}
/// Build a description string for the SLO.
fn build_description(ir: IntermediateRepresentation) -> String {
let runbook = case ir.get_slo_fields(ir.artifact_data) {
option.Some(slo) -> slo.runbook
option.None -> option.None
}
case runbook {
option.Some(url) -> "[Runbook](" <> url <> ")"
option.None ->
"Managed by Caffeine ("
<> ir.metadata.org_name
<> "/"
<> ir.metadata.team_name
<> "/"
<> ir.metadata.service_name
<> ")"
}
}
/// Convert window_in_days to Dynatrace evaluation_window format ("-{N}d").
/// Range (1-90) is guaranteed by the standard library type constraint.
@internal
pub fn window_to_evaluation_window(days: Int) -> String {
"-" <> int.to_string(days) <> "d"
}