I have a small question about Terraform but I can't find a solution online. Imagine the following scenario:
data "aws_vpc" "vpc_name" {
id = var.vpc_id
}
data "aws_subnet_ids" "vpc_subnets" {
vpc_id = data.aws_vpc.vpc_name.id
}
And imagine I create an EC2 instance like this:
resource "aws_instance" "ec2_test" {
...
ami = ...
instance_type = ...
subnet_id = element(tolist(data.aws_subnet_ids.vpc_subnets.ids), 1)
...
In the subnet, I have selected an element using the element function and converted the list of subnet ids to a list.
Now, I have two questions:
Is it possible to select the first subnet without doing the conversion to list (tolist)? I wonder why I can do something like data.aws_subnet_ids.vpc_subnets[1] or something like that?
How can I retrieve a random id from the list of subnets?
Regards,
You need the tolist operation, because aws_subnet_ids.ids is a set. You could clean it up a little and say tolist(data.aws_subnet_ids.vpc_subnets.ids)[0].
To get a random number, use the random provider.
Okay, but what if you have the following stupid thing going on:
output "ids" {
value = values(tomap({
for k, public in aws_subnet.public : k => public.id
}))
}
I am trying to get any value from this list that I generated after maybe 5 hours of my life. :) I fully get it, that this might not be seen at all, but imagine you have 3 subnets for public, 3 for private, 3 for something else. You try to get the 3 public going on. The case is on random. Each time a random value from these 3 gets picked up. Reason you might ask trying to outsmart yourself.
Related
I am trying to combine all subnets to be able to attach an ACL to them.
I have two subnets that exist which different resource names, so it's forcing me to have two ACL blocks which I don't want.
Right now the ACL subnet_id blocks for both ACL blocks read as:
resource "aws_network_acl" "prod_public" {
vpc_id = aws_vpc.prod.id
subnet_ids = aws_subnet.prod_public.*.id
}
resource "aws_network_acl" "prod2_public" {
vpc_id = aws_vpc.prod.id
subnet_ids = aws_subnet.prod2_public.*.id
}
this works, but I'm want something that will create a list of BOTH set of subnet_ids so I can just have be one block.
I've tried something like this, but didn't work.
resource "aws_network_acl" "prods_public" {
vpc_id = aws_vpc_prod_id
subnet_ids = [aws_subnet.prod_public.*.id, aws_subnet.prod2_public.*.id]
}
I also tried using tostring and toset which didn't work either.
You should use concat:
subnet_ids = concat(aws_subnet.prod_public.*.id, aws_subnet.prod2_public.*.id)
Because neither of these resources is using the count argument, the splat operator * is redundant here and it would be sufficient to just refer to the single object for each resource:
resource "aws_network_acl" "prods_public" {
vpc_id = aws_vpc_prod_id
subnet_ids = [
aws_subnet.prod_public.id,
aws_subnet.prod2_public.id,
]
}
Using the legacy splat operator .* (or its modern equivalent [*]) makes Terraform convert each single object into a one-element list containing that object, and so in your example you created a list of lists of strings rather than just a list of strings.
If you were using count for these resources then their values would then actually be lists, and so in that case it would be appropriate to use the splat operator to select just the id attribute of each one but you'd then need to use a function to combine the multiple lists together. Because subnet_ids expects a set of subnet ID strings (the subnets have no meaningful order), the clearest way to represent that would be using the setunion function, which will produce a set of strings containing all of the distinct elements across all of the given lists:
resource "aws_network_acl" "prods_public" {
vpc_id = aws_vpc_prod_id
subnet_ids = setunion(
aws_subnet.prod_public[*].id,
aws_subnet.prod2_public[*].id,
)
}
Using the concat function would also work, but ultimately Terraform will convert its result into a set of strings to pass into subnet_ids anyway and so using concat might be misleading to a future reader who might assume that means that the ordering of the subnet IDs is somehow significant.
Terraform v0.12.x
I'm creating an AWS Route53 record, like this, and it's created with no issues.
data "aws_route53_zone" "zone" {
name = "my.domain.com."
private_zone = true
}
resource "aws_route53_record" "record" {
zone_id = data.aws_route53_zone.zone.zone_id
name = "${var.record_name}.${data.aws_route53_zone.zone.name}"
type = "A"
alias {
name = var.alias_record
zone_id = var.alias_zone_id
evaluate_target_health = false
}
}
Now I want to output the value the alias's name, and I tried
output "alias_name" {
value = aws_route53_record.record.alias.name
}
or
output "alias_name" {
value = aws_route53_record.record.alias["name"]
}
but get the error
Block type "alias" is represented by a set of objects, and set elements do not
have addressable keys. To find elements matching specific criteria, use a
"for" expression with an "if" clause.
What's the correct syntax?
The alias is set of objects and sets are:
a collection of unique values that do not have any secondary identifiers or ordering.
So you can't index them. Thus in your case to output the alias values you can use:
output "alias_name" {
value = aws_route53_record.record.alias.*.name
}
# or
output "alias_name2" {
value = aws_route53_record.record.alias[*].name
}
The error message Terraform returned here is a generic one it returns for all block types that are represented as a set of objects, and so it's giving you some general advice for selecting a set element based on its value or nested values. For example:
output "example" {
# Select the first element from the
# set whose name is "foo".
value = [
for a in aws_route53_record.record.alias : a
if a.name == "foo"
][0]
}
That advice is not actually suitable for the specific problem you have here, because you are not "finding elements matching specific criteria", but rather just trying to select the one element you know you wrote, because you've statically defined only a single alias block anyway so you know that it will always be there and always be the right one.
To do that, one option is to explicitly convert the set to a list using the tolist function. The documentation warns that the resulting elements will not be in any particular order, but when there's only one element anyway there can therefore only be one ordering of that element, and so this is safe:
output "example" {
# Select the first element from the
# set whose name is "foo".
value = tolist(aws_route53_record.record.alias)[0].name
}
Providers typically use a set representation for a particular block type if the underlying API considers the objects to be unordered and to have no specific unique identifier. The result is unfortunately then less convenient to use, but using this accurate modelling of the underlying behavior avoids problems where a configuration might work at first but then misbehave later if the remote API starts returning items in a different (arbitrary) order.
The choice of sets for this alias block is a little odd since it's defined to only allow zero or one blocks anyway; I suspect this was a historical design quirk that the provider is now preserving for backward compatibility.
Combining both Marcin's and Martin's answers (thanks to both), here's what I ended up using
output "alias_name" {
value = tolist(aws_route53_record.record.alias.*.name)[0]
}
Marcin's answer gave
alias_name = [
"my-record.domain.com",
]
While Martin's answer gave
alias_name = {
"evaluate_target_health" = false
"name" = "my-record.domain.com"
"zone_id" = "Z1234ABCDE"
}
But combining the two answers like I have above gives
alias_name = my-record.domain.com
In Camunda (7.12) I can query processes by variable value:
runtimeService.createProcessInstanceQuery()
.variableValueEquals("someVar", "someValue")
.list();
I can even query processes for null-value variables:
runtimeService.createProcessInstanceQuery()
.variableValueEquals("someVar", null)
.list();
But how can I query processes that do not have variable someVar?
I am not sure why you wouldn't have figured that out, but if i am correct what i think you are looking for , then looks like its pretty simple . The ProcessInstanceQuery class has also a method called variableValueNotEquals(String name, Object value) , that allows you select the processes that do not match a variable . In the Camunda Java API Docs it is stated as :
variableValueNotEquals(String name, Object value)
Only select process instances which have a global variable with the given name, but with a different value than the passed value.
Documentation page for your reference:
https://docs.camunda.org/javadoc/camunda-bpm-platform/7.12/?org/camunda/bpm/engine/RuntimeService.html
So i believe you can simply do :
runtimeService.createProcessInstanceQuery()
.variableValueNotEquals("someVar", null)
.list();
Let me know if that helps you .
First, get list of ids of all process instances which have "someVar"
Second, get list of all process ids in camunda
Get ids, from second list, which are not contained in first list.
Here is Kotlin sample as it's shorter than Java code, but concept is the same:
val idsOfProcessesWithVar = runtimeService.createVariableInstanceQuery().variableName("someVar").list().map {
it.processInstanceId
}
val allProcessesIds = runtimeService.createProcessInstanceQuery().list().map { it.id }
allProcessesIds.minus(idsOfProcessesWithVar)
I am wondering if it's possible to get the total count of a list in terraform, I've looked at the tf website and don't see anything for the total count just the use of count.index.
An example list
var "testList"
type = "list"
default [
{
type = "test1"
},
{
type = "test2"
}
]
So here I want to get 2 as the total count of testlist
Thanks
you can use terraform built-in function length() to get the count.
count = "${length(var.testList)}"
For details, please go through terraform documents:
terraform length list
I have a script that generates the configuration for some campus wireless mobility switches. The user needs to supply a set of IP addresses for the configuration.
Among other constraints, these IP's must all be in the same /24 subnet (always /24). To avoid typos where an IP is in a different subnet (which messes up the configuration and it happened already), I would like to tell the user which IP is at fault. I wrote the following, but I feel there could be a better way to do it.
Suppose the list is this, where the 3rd IP is wrong:
ips = ['10.0.0.1', '10.0.0.2', '10.0.10.3', '10.0.0.4', '10.0.0.5']
The following gets the job done:
subnets = set()
for ip in ips:
subnets.add('.'.join(ip.split('.')[0:3]))
# This would result in subnet being set(['10.0.10', '10.0.0'])
if len(subnets) > 1:
seen_subnets = defaultdict(list)
for sn in subnets:
for ip in ips:
if sn in ip:
seen_subnets[sn].append(ip)
suspect = ''
for sn in seen_subnets.keys():
if len(seen_subnets[sn]) == 1:
suspect = seen_subnets[sn][0]
if not suspect:
# Do something to tell the user one or more IP's are incorrect
# but I couldn't tell which ones
NOTE: The smallest list that I could have has 3 items, and I'm basing this on the assumption that probably most if not all mistakes will be just 1 IP.
Is there perhaps a more straightforward way to do this? I feel the solution has to be based on using a set either way but it didn't seem to me any of its methods would be more helpful than this.
Something like this would work:
from itertools import groupby
ips = ['10.0.0.1', '10.0.0.2', '10.0.10.3', '10.0.0.4', '10.0.0.5']
def get_subnet(ip):
return '.'.join(ip.split('.')[:3])
groups = {} # group ips by subnet
for k, g in groupby(sorted(ips), key=get_subnet):
groups[k] = list(g)
# Sort the groups by most common first
for row, (subnet, ips) in enumerate(
sorted(groups.iteritems(), key=lambda (k, v): len(v), reverse=True)
):
if row > 0: # not the most common subnet
print('You probably entered these incorrectly: {}'.format(ips))