How to create an Eventarc trigger in terraform for Pub/Sub? - terraform

I need to create an eventarc trigger on a Pub/Sub message published. I do not know where to put the Pub/Sub topic ID.
resource "google_eventarc_trigger" "eventarc_trigger" {
name = "test-trigger"
service_account = var.service_account
project = local.project
location = local.region
destination {
workflow = google_workflows_workflow.example.id
}
matching_criteria {
attribute = "type"
value = "google.cloud.pubsub.topic.v1.messagePublished"
}
}

You can define the target transport like that
resource "google_eventarc_trigger" "eventarc_trigger" {
name = "test-trigger"
service_account = var.service_account
project = local.project
location = local.region
destination {
workflow = google_workflows_workflow.example.id
}
matching_criteria {
attribute = "type"
value = "google.cloud.pubsub.topic.v1.messagePublished"
}
transport {
pubsub {
topic = "projects/{PROJECT_ID}/topics/{TOPIC_NAME}"
}
}
}

Related

Create aws_ses_receipt_rule in predefined order with for_each loop

I am trying to define SES rule sets with an order defined by a collection of rules in a variable.
I have tried the solution in https://github.com/hashicorp/terraform-provider-aws/issues/24067 to use the after property of the resource, and It does create the first rule, and fails when creating the second, and all subsequent rules, because the first rule does not exist yet (the one with after=null). I guess it needs some time to finalize. depends_on does not work with dynamic dependencies, as far as i know, so this will not make it either.
If I re-run the apply, then the second rule is created, but all the other rules fail.
my recipients_by_position map is indexed by 0-padded position (i.e. "01", "02", etc):
This is my code
locals {
recipients = {
"mail1-recipient1" = {
short_name = "mail1"
domain = "mail1.domain.com"
recipient = "recipient1"
position = 1
target_bucket = "bucket1"
}
"mail1-recipient2" = {
short_name = "mail1"
domain = "mail1.domain.com"
recipient = "recipient2"
position = 2
target_bucket = "bucket1"
}
"mail2-recipient1" = {
short_name = "mail2"
domain = "mail2.domain.com"
recipient = "recipient1"
position = 3
target_bucket = "bucket2"
}
}
spec_by_domain = {
"mail1.domain.com" = {
irrelevant ={}
}
"mail2.domain.com" = {
irrelevant ={}
}
}
recipients_by_position = {for r in local.recipients: "${format("%02s",r.position)}" => r}
}
resource "aws_ses_domain_identity" "domains" {
for_each = local.spec_by_domain
domain = each.key
}
resource "aws_ses_receipt_rule_set" "main" {
rule_set_name = "new-rules"
}
# store it in S3
resource "aws_ses_receipt_rule" "store" {
for_each = local.recipients_by_position
after = each.value.position == 1 ? null : "${format("%02s",each.value.position - 1)}"
# name = "${each.value.short_name}-store_on_s3-${each.value.recipient}"
name = each.key
rule_set_name = aws_ses_receipt_rule_set.main.rule_set_name
recipients = ["${each.value.recipient}#${each.value.domain}"]
enabled = true
scan_enabled = true
s3_action {
bucket_name = aws_s3_bucket.mailboxes[each.value.domain].bucket
object_key_prefix = each.value.recipient
position = 1
}
}
apply fails with a bunch of
Error: Error creating SES rule: RuleDoesNotExist: Rule does not exist: xx
with xx from 01 to whatever number of rules were defined

How to reference instance argument value created with for_each meta-argument in another instance in the same map

Updated with a more illustrative example.
My end goal is to have Terraform create instances of a resource generated with the for_each meta argument in a specific sequence. HCL is known to be a declarative language and when Terraform applies a configuration it can create resources randomly unless you use the depends_on argument or refer from one resource (instance) to another. However, the depends_on argument does not take values that are "calculated", so I don't know how to use it in modules.
For this reason, in order to force Terraform to create instances of a resource in a specific sequence, I decided to try to make the value of a certain argument in an instance it creates "calculated" based on the values of the same argument from another instance.
Below you can find a more practical example based on using one of the providers, but the question is more general and pertains to Terraform as such.
Let's take a test module that instantiates the cloudflare_page_rule resource:
# Module is placed to module\main.tf
terraform {
experiments = [module_variable_optional_attrs]
}
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = ">= 3.10.0"
}
}
}
variable "zone" {
type = string
description = "The DNS zone name which will be added, e.g. example.com."
}
variable "page_rules" {
type = list(object({
page_rule_name = string
target = string
actions = object({
forwarding_url = optional(object({
url = string
status_code = number
}))
})
priority = optional(number)
status = optional(string)
depends_on = optional(string)
}))
description = "Zone's page rules."
default = []
}
//noinspection HILUnresolvedReference
locals {
page_rule_dependencies = { for p in var.page_rules : p.page_rule_name => p.depends_on if p.depends_on != null }
}
# https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone
resource "cloudflare_zone" "this" {
zone = var.zone
}
# https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/page_rule
//noinspection HILUnresolvedReference
resource "cloudflare_page_rule" "this" {
for_each = var.page_rules != null ? { for p in var.page_rules : p.page_rule_name => p } : {}
zone_id = cloudflare_zone.this.id
target = each.value.target
actions {
//noinspection HILUnresolvedReference
forwarding_url {
status_code = each.value.actions.forwarding_url.status_code
url = each.value.actions.forwarding_url.url
}
}
priority = each.value.depends_on != null ? cloudflare_page_rule.this[local.page_rule_dependencies[each.key]].priority + 1 : each.value.priority
status = each.value.status
}
output "page_rule_dependencies" {
value = local.page_rule_dependencies
}
And a configuration that is used to create resources:
terraform {
required_version = ">= 0.15.0"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = ">= 3.10.1"
}
}
}
variable "cloudflare_api_token" {
type = string
sensitive = true
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
module "acme_com" {
source = "./module"
zone = "acme.com"
page_rules = [
{
page_rule_name = "page_rule_1"
target = "acme.com/url1"
actions = {
forwarding_url = {
status_code = 301
url = "https://www.example.com/url1"
}
}
priority = 1
},
{
page_rule_name = "page_rule_2"
target = "acme.com/url2"
actions = {
forwarding_url = {
status_code = 301
url = "https://www.example.com/url2"
}
}
priority = 2
depends_on = "page_rule_1"
},
{
page_rule_name = "page_rule_3"
target = "acme.com/url3"
actions = {
forwarding_url = {
status_code = 301
url = "https://www.example.com/url3"
}
}
priority = 3
depends_on = "page_rule_2"
}
]
}
output "page_rule_dependencies" {
value = module.acme_com.page_rule_dependencies
}
In this particular example, I've added the depends_on argument to the page_rules variable (don't confuse this argument with the depends_on meta argument). For the value of the depends_on argument, I specified the name of a page_fule on which another page_fule depends.
Next, I created a local variable page_rule_dependencies, the value of which, after calculations, is the following (you can check this yourself by replacing the priority = each.value.depends_on != null ? cloudflare_page_rule.this[local.page_rule_dependencies[each.key]].priority + 1 : each.value.priority construct with priority = each.value.priority and executing terraform apply):
page_rule_dependencies = {
"page_rule_2" = "page_rule_1"
"page_rule_3" = "page_rule_2"
}
Further, in the priority = each.value.depends_on != null ? cloudflare_page_rule.this[local.page_rule_dependencies[each.key]].priority + 1 : each.value.priority construct, I refer to the values ​​of the local variable, thereby forming a "reference" to the page_fule instance, on which the current instance depends:
When creating page_rule_1, the value of its argument priority = 1.
When creating page_rule_2, the value of its argument priority = cloudflare_page_rule.this["page_rule_1"].priority + 1.
When creating page_rule_3, the value of its argument priority = cloudflare_page_rule.this["page_rule_2"].priority + 1.
However, I get an Error: Cycle: module.acme_com.cloudflare_page_rule.this["page_rule_3"], module.acme_com.cloudflare_page_rule.this["page_rule_2"], module.acme_com.cloudflare_page_rule.this["page_rule_1"] error.
Either I'm doing something wrong, or it's some kind of Terraform limitation/bug. Is there a way to get rid of this error?
P.S. Resulting graph after terraform graph -draw-cycles | dot -Tsvg > graph.svg or terraform graph -draw-cycles -type=plan | dot -Tsvg > graph-plan.svg (the same result):
P.P.S. I use Terraform v1.1.7.

Await for ChangeResourceRecordSetsAsync hangs forever when trying to create Route53 DNS record

I am trying to create a TXT record in AWS Route53, but while awaiting the call, it will never complete.
The record is not being created in the hosted zone. The account has the correct permissions which I tested using the AWS CLI.
I am able to list hosted zones as also shown in the code below.
public static async Task CreateRecordAsync()
{
var route53Client = new AmazonRoute53Client("<myAccessKey>", "<mySecretKey>", RegionEndpoint.EUCentral1);
var test = new ListHostedZonesByNameRequest();
var testResponse = route53Client.ListHostedZonesByNameAsync(test);
foreach (var zone in testResponse.Result.HostedZones)
{
Console.WriteLine($"{zone.Name}{zone.Id}");
}
var response = await route53Client.ChangeResourceRecordSetsAsync(new ChangeResourceRecordSetsRequest
{
ChangeBatch = new ChangeBatch
{
Changes = new List<Change> {
new Change {
Action = "CREATE",
ResourceRecordSet = new ResourceRecordSet {
Name = "my.domain.net",
Type = "TXT",
TTL = 60,
ResourceRecords = new List<ResourceRecord>
{
new ResourceRecord
{
Value = "test txt value"
}
}
}
}
},
Comment = "Test Entry"
},
HostedZoneId = "Z2Q***********"
});
}
The problem was that the TXT record had to be enclosed in double quotes. I just had to catch the correct exception.
try
{
....
ChangeResourceRecordSetsResponse recordsetResponse =
await route53Client.ChangeResourceRecordSetsAsync(recordsetRequest);
....
}
catch (InvalidChangeBatchException ex)
{
Console.WriteLine(ex);
}
Which means that I should have constructed my record like this using.
ResourceRecordSet recordSet = new ResourceRecordSet
{
Name = chosenDomain,
TTL = 60,
Type = RRType.TXT,
ResourceRecords = new List<ResourceRecord>
{
new ResourceRecord
{
Value = "\"This is my test txt record\""
}
}
};

Create dynamic block in kubernetes container block using terraform

I want to create a dynamic block that will able to dynamically create envs for docker container inside kubernetes using terraform.
I already tried creating a var of list and iterate over the envs but I am getting syntax error
Error: Reference to "count" in non-counted context
on kubernetes/kubernetes.main.tf line 68, in resource "kubernetes_deployment" "kube_deployment":
This is due to usage of count out of resource block.
I am looking now to create multiple envs like this
...
env {
name = "NAME"
value = "VALUE"
}
env {
name = "NAME"
value = "VALUE"
}
.
.
.
is there anyway to create this iteration or any hacks to create dynamic envs in container block. I understand that dynamic blocks are only inside resource, data, provider, and provisioner.
I was previously using helm to do this kind of templating but now I want to fully move to terraform.
I would love any directions to solve such issue.
Thanks
resource "kubernetes_deployment" "kube_deployment" {
metadata {
name = var.deployment_name
labels = {
App = var.deployment_name
}
}
spec {
replicas = 1
selector {
match_labels = {
App = var.deployment_name
}
}
template {
metadata {
labels = {
App = var.deployment_name
}
}
spec {
container {
image = var.container_image
name = var.container_name
env {
name = "NAME"
value = "VALUE"
}
port {
container_port = var.container_port
}
}
}
}
}
}
It was actually possible even if inside nested block of type resource, data, provider, and provisione..
here is a working code
resource "kubernetes_deployment" "kube_deployment" {
metadata {
name = var.deployment_name
labels = {
App = var.deployment_name
}
}
spec {
replicas = 1
selector {
match_labels = {
App = var.deployment_name
}
}
template {
metadata {
labels = {
App = var.deployment_name
}
}
spec {
container {
image = var.container_image
name = var.container_name
dynamic "env" {
for_each = var.envs
content {
name = env.value.name
value = env.value.value
}
}
port {
container_port = var.container_port
}
}
}
}
}
}

Terraform extract account id from aws_organizations_organization.main.accounts

Given an account name, is it possible to extract the account id from
resource "aws_organizations_organization" "main" {
}
So something like:
output "account_id" {
value = "aws_organizations_organization.main.accounts[name == 'account1']"
}
account_id = 012345678901
accounts = [
{
"arn" = "arn:aws:organizations::012345678901:account/o-abc123/012345678901"
"email" = "account1#email.com"
"id" = "012345678901"
"name" = "account1"
},
{
"arn" = "arn:aws:organizations::012345678902:account/o-abc123/012345678902"
"email" = "account2#email.com"
"id" = "012345678902"
"name" = "account2"
},
{
"arn" = "arn:aws:organizations::012345678903:account/o-abc123/012345678903"
"email" = "account3#email.com"
"id" = "320413348752"
"name" = "account3"
}
]
In case anyone else stumbles upon this, I was able to solve with the following:
data "aws_organizations_organization" "main" {}
locals {
account-name = "account1"
account-index = index(data.aws_organizations_organization.main.accounts[*].name, local.account-name)
account-id = data.aws_organizations_organization.main.accounts[local.account-index].id
}
output "account_id" {
value = local.account-id
}
Theoretically, if you get version 2.21.0 or newer, you should be able to use the new aws_organizations_organization data source and filter it based on the account name. For example, though not tested:
data "aws_organizations_organization" "org" {
filter = {
name = "name"
values = ["account1"]
}
}
And then where you need the account id use data.aws_organizations_organization.org.id
You can use null_data_source for creating an email list. and then extract accounts using matchkeys.
data "aws_organizations_organization" "main" {}
data "null_data_source" "main" {
count = length(data.aws_organizations_organization.main.accounts)
inputs = {
emails = data.aws_organizations_organization.main.accounts[count.index]["email"]
}
}
output "accounts" {
value = matchkeys(data.aws_organizations_organization.main.accounts, data.null_data_source.main.*.outputs.emails, list("account1"))
}

Resources