I am using data sources in Terraform to fetch a list of ids of my security groups as such:
data "aws_security_groups" "test" {
filter {
name = "group-name"
values = ["the-name"]
}
}
output "security_group_id" {
value = "The id is ${data.aws_security_groups.test.ids[*]}"
}
However, this is giving me the following error:
Error: Invalid template interpolation value
on main.tf line 11, in output "security_group_id":
11: value = "The id is ${data.aws_security_groups.test.ids[*]}"
|----------------
| data.aws_security_groups.test.ids is list of string with 1 element
Cannot include the given value in a string template: string required.
But if I use data.aws_security_groups.test.ids[0] instead it displays the ID.
Can someone help me to display the list of IDs?
First, I want to note that you don't necessarily need to combine this list with a string message at all if you don't want to, because Terraform will accept output values of any type:
output "security_group_ids" {
value = data.aws_security_groups.test.ids
}
If having them included as part of a bigger string is important for your underlying problem then you'll need to make a decision about how you want to present these multiple ids in your single string. There are various different ways you could do that, depending on what you intend to do with this information.
One relatively-straightforward answer would be to make the string include a JSON representation of the list using jsonencode, like this:
output "security_group_id_message" {
value = "The ids are ${jsonencode(data.aws_security_groups.test.ids)}"
}
If you want a more human-friendly presentation then you might prefer to use a multi-line string instead, in which case you can customize the output using string templates.
output "security_group_id_message" {
value = <<-EOT
The ids are:
%{ for id in data.aws_security_groups.test.ids ~}
- ${id}
%{ endfor ~}
EOT
}
Or, for an answer somewhere in between, you could use join to just concatenate the values together with a simple delimiter, like this:
output "security_group_id_message" {
value = "The ids are ${join(",", data.aws_security_groups.test.ids)}"
}
Note that I removed the [*] from your reference in all of these examples, since it isn't really doing anything here: data.aws_security_groups.test.ids is already an iterable collection, and so is compatible with all of the language features I used in the examples above.
IIRC the provider considers this ids attribute to be a set of strings rather than a list of strings, and so that [*] suffix could potentially be useful in other situations to force converting the set into a list if you need it to be typed that way, although if that is your intent then I'd suggest using one of the following instead so that it's clearer to a future reader what it does:
sort(data.aws_security_groups.test.ids) (if it being in lexical order is important to the behavior; Terraform uses lexical sorting by default anyway, but calling sort is a good prompt to a reader unfamiliar with Terraform to look up that function to see what the actual sort order is.)
tolist(data.aws_security_groups.test.ids) (functionally equivalent to sort above when it's a set of strings, but avoids the implication that the specific ordering is important, if all that matters is that it's a list regardless of the ordering)
Related
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
So this is my first question on the forum and I hope I am doing it correct.
General question: How can I ensure that python does not return any errors when writing a script that allows the user to input values of different datatypes depending on the context or parameter they want to change?
More specific: I am new to python and want to write a script that allows users of The Foundry's Nuke to change values on multiple nodes of the same class at once. Depending on whether the desired parameter to change is a checkbox('bool'), and RGBA input ('4 floats')... the input has to be of a different type. Searching the forum I found that the type can be checked by type() function and compared in an if statement with the isinstance() function. I guess I could work with that, but the type of e.g. a Gradenode's multiply knob returns type 'AColor_Knob'. I expected something like float. And comparing it in an isinstance() does not give me a match regardless of the datatype I am comparing to.
Mainscript so far:
nukescripts.clear_selection_recursive()
userInput = nuke.getInput('Which type of nodes would you like to select? (!!!first char has to be capitalized!!!)',
'Shuffle')
matchingNodes = []
for each in nuke.allNodes():
if each.Class() == userInput:
matchingNodes.append(each)
else:
pass
for i in matchingNodes:
i.setSelected(True)
nuke.message(str(len(
matchingNodes)) + ' matching Nodes have been found and are now selected! (if 0 there either is no node of this type or misspelling caused an error!)')
userInput_2 = nuke.getInput('Which parameter of these nodes would you like to change? \n' +
'(!!!correct spelling can be found out by hovering over parameter in Properties Pane!!!)',
'postage_stamp')
userInput_3 = nuke.getInput('To what do you want to change the specified parameter? \n' +
'(allowed input depends on parameter type (e.g. string, int, boolean(True/False)))', 'True')
for item in matchingNodes:
item.knob(userInput_2).setValue(userInput_3)
How I checked the datatypes so far:
selected = nuke.selectedNode()
knobsel = selected.knob('multiply')
print(type(knobsel))
#if type(knobsel) == bool:
if isinstance(knobsel, (str,bool,int,float,list)):
print('match')
else:
print('no match')
You can call a TCL command with nuke.tcl(). In TCL, everything is a string, so type is irrelevant (in some commands).
p = nuke.Panel('Property Changer')
p.addSingleLineInput('class', '')
p.addSingleLineInput('knob', '')
p.addSingleLineInput('value', '')
p.show()
node_class = p.value('class')
knob_name = p.value('knob')
knob_value = p.value('value')
for node in nuke.allNodes(node_class):
tcl_exp = 'knob root.{0}.{1} "{2}"'.format(node.fullName(),knob_name,knob_value)
print tcl_exp
nuke.tcl(tcl_exp)
That should answer your question. There are many ways to approach what you're trying to do - if you want to keep it all in python, you can do type checking on the value of the knob. For example:
b = nuke.nodes.Blur()
print type(b.knob('size').value()).__name__
This creates a Blur node and prints the string value of the type. Although I don't recommend this route, you can use that to convert the value:
example = '1.5'
print type(example)
exec('new = {}(example)'.format('float'))
print type(new)
An alternative route to go down might be building yourself a custom lookup table for knob types and expected values.
Edit:
TCL Nuke Commands:
http://www.nukepedia.com/reference/Tcl/group__tcl__builtin.html#gaa15297a217f60952810c34b494bdf83d
If you press X in the nuke Node Graph or go to File > Comp Script Command, you can select TCL and run:
knob root.node_name.knob_name knob_value
Example:
knob root.Grade1.white "0 3.5 2.1 1"
This will set values for the named knob.
I have elements dateselection, blackscholes and volatility within my xml file, I would like to check the values of these, trying to print out content by using std::cout gives me build errors, also i would like to check if the elements exist within the xml file, for example if(blackscholes.exists()). does such functionality exist within code synthesis?
dateselection& date = i->dateselection();
const xsd::cxx::tree::date<char,simple_type>& end = date.enddate();
const xsd::cxx::tree::sequence<bool, true>& black = i->blackscholes();
const xsd::cxx::tree::sequence<bool, true>& volatility = i->volatility();
Also can i convert the end date to a boost/date_time/gregorian/gregorian type?
thanks in advance
looking into the interface files (the hxx stubs generated) i found the the following types mapping to the xml library equivalent to strings and bool: ::xml_schema::string, ::xml_schema::date,vanillaoption::blackscholes_type. The names may vary from schema to schema but using these to process and print out values worked for me (search for names with ::xml prefixes). To check for optional fields you must place elements within a "choice", the stubs will produce a function for each element called "present". You can use this to determine whether an element exists within a config file. For example
for(quantoptions::vanillaoption_iterator i (optionConfig->vanillaoption().begin()); i != optionConfig->vanillaoption().end(); ++i)
{
dateselection& date = i->dateselection();
::xml_schema::string symbol = i->symbol();
::xml_schema::date &endDate = date.enddate();
if(i->blackscholes().present())
{
//do whatever
}
}
I am developing an application using Qt/KDE. While writing code for this, I need to read a QString that contains values like ( ; delimited)
<http://example.com/example.ext.torrent>; rel=describedby; type="application/x-bittorrent"; name="differentname.ext"
I need to read every attribute like rel, type and name into a different QString. The apporach I have taken so far is something like this
if (line.contains("describedby")) {
m_reltype = "describedby" ;
}
if (line.contains("duplicate")) {
m_reltype = "duplicate";
}
That is if I need to be bothered only by the presence of an attribute (and not its value) I am manually looking for the text and setting if the attribute is present. This approach however fails for attributes like "type" and name whose actual values need to be stored in a QString. Although I know this can be done by splitting the entire string at the delimiter ; and then searching for the attribute or its value, I wanted to know is there a cleaner and a more efficient way of doing it.
As I understand, the data is not always an URL.
So,
1: Split the string
2: For each substring, separate the identifier from the value:
id = str.mid(0,str.indexOf("="));
value = str.mid(str.indexOf("=")+1);
You can also use a RegExp:
regexp = "^([a-z]+)\s*=\s*(.*)$";
id = \1 of the regexp;
value = \2 of the regexp;
I need to read every attribute like rel, type and name into a different QString.
Is there a gurantee that this string will always be a URL?
I wanted to know is there a cleaner and a more efficient way of doing it.
Don't reinvent the wheel! You can use QURL::queryItems which would parse these query variables and return a map of name-value pairs.
However, make sure that your string is a well-formed URL (so that QURL does not reject it).
I need to use a string to access nodes and attributes in XML using E4X. It would be ideal to have this scenario (with XML already loaded):
var myXML:XML = e.target.data;
var myStr:String = "appContent.bodyText.(#name == 'My Text')";
myXML.myStr = "New Value for bodyText node where attribute('name') is equal to 'My Text'";
I ultimately need to set new values to an XML document using strings as E4X expressions.
As noted above:
I figured out a workaround
Take the string of the E4X path you want to target
Pull the E4X path and compare it to your target path
If the two are equal, do what you will with that node/attribute
It's a hack, but it works. You could even parse the XML and populate an array with the target string and the target node, then you could just access it through an item in the array. This is expandable in many ways. As long as everything is set up for proper garbage collection, you'll be okay.