My requirement is to do some repetitive file configuration stuff using a loop, Something like following,
$no_of_managers = 2
$array = ['One','two','Three']
define loop() {
notice("Configuring The Manager Nodes!!")
if ($name == $no_of_managers+1) {
notice("Loop Iteration Finished!!!")
}
else
{
notice("Iteration Number : $name \n")
# Doing All Stuff Here
resource {$array:}
$next = $name + 1
loop { $next: }
}
}
loop { "1":}
define resource () {
# Doing my other Stuff
notice ("The Parsed value Name : ${name}\n")
}
Now when The second iteration is running the following error occurs,
Error: Duplicate declaration: Resource[One] is already declared in file
How can I overcome this, What I'm doing is a cluster setup. Is there a workaround to do this, I'm a newbie for puppet so Your kind guidance highly appreciated.
The Use Case :
I'm trying to setup a cluster which have multiple Manager/Worker nodes, So using this script the user has the privilege to select how many manager nodes he needs. So the first loop is for that to copy necessary files and create required number of nodes.
The second loop is there to push all .erb templates. Because each Node has slightly different configs the .erb files have there own logic inside them.
So after each Iteration I want to push the .erb templates to the respective node.
In Puppet 3.x, you cannot build a loop in the fashion you are trying.
resource { $array: }
is a loop over the contents of $array if you will.
It is not really clear what you are trying to solve. If you can make your question a bit more concrete, we may be able to suggest an actual implementation.
Update
If you really want to go down this road, you need to generate unique names for your derived resources.
$local_names = regsubst($array, '$', "-$name")
resource { $local_names: }
In your defined type, you will have to retrieve the original meaning by removing the suffix.
define resource() {
$orig_name = regsubst($name, '-[0-9]+$', '')
# use $orig_name where you used $name before
}
Note that even exported resources must have unique names. So the transformation may have to happen on in the manifest of the receiving node.
Related
I have a sql server terraform module that outputs the name of a sql server for the databases to get created in. However, some environments should use an external server outside of the terraform project instead. Most datacenters we have do not have this external server, just a few.
I've set up the external server using data sources as usual, and made both the output, normal server and datasource conditional on a variable thats passed in like this:
variable "use_external_sql_server" {
type = bool
}
resource "azurerm_mssql_server" "sqlserver" {
count = var.use_external_sql_server ? 0 : 1
name = "sql-interal-sql_server"
....
}
data "azurerm_mssql_server" "external_sql_server" {
count = var.use_external_sql_server ? 1 : 0
name = "sql-${var.env}-${var.location}"
resource_group_name = "rg-${var.env}-${var.location}"
}
output "sql_server_name" {
value = var.use_external_sql_server ? data.azurerm_mssql_server.external_sql_server.name : azurerm_mssql_server.sqlserver[0].name
depends_on = [
azurerm_mssql_server.sqlserver,
data.azurerm_mssql_server.external_sql_server
]
}
However, I'm running into issues with the output. It requires data.azurerm_mssql_server.external_sql_server to exist to evaulate the condition, even if "use_external_server" is false. This is not ideal as I have to manual create dummy servers to fix this condition, so that that conditional can evaulate to true.
Is there a way to do this conditional without having to have "data.azurerm_mssql_server.external_sql_server" actually exist?
You could get rid of the conditional in the output and just use a try.
try evaluates all of its argument expressions in turn and returns the result of the first one that does not produce any errors.
This is a special function that is able to catch errors produced when evaluating its arguments, which is particularly useful when working with complex data structures whose shape is not well-known at implementation time.
You could then possibly write something like
output "sql_server_name" {
value = try(data.azurerm_mssql_server.external_sql_server[0].name, azurerm_mssql_server.sqlserver[0].name, "")
depends_on = [
azurerm_mssql_server.sqlserver,
data.azurerm_mssql_server.external_sql_server
]
}
One of my modules - let it be a - has an output definition as
output "data_table_arn" {
value = aws_dynamodb_table.data_table.*.arn
}
This is accessed one level above in module b
module "b" {
source = "../c"
data_lookup_table_arn = module.a.data_table_arn
The code above makes the variable module.a.data_table_arn accessible to module c through the variable data_lookup_table_arn. And now I am trying to access it in module c in an aws policy document definition
data "aws_iam_policy_document" "dynamo-read-policy-document" {
count = local.one_if_uses_dynamo
statement {
actions = ["dynamodb:GetItem"]
resources = [
var.data_table_arn
]
}
}
The exception I am getting is
Inappropriate value for attribute "resources": element 0: string required.
I want to debug this thing somehow and I cannot find a way to inspect the outputs or the variables defined inside a module. I can list the resources using terraform state list <resource_name> but this is not what I really want to do here.
How can I inspect the variables and outputs of a TF module?
Is this even possible?
Can you see anything faulty with my approach
I though of also using a heredoc instead of a aws_iam_policy_document but I would still need to make use of the output mentioned above - but this time in an interpolation I guess. Is this better or worse? They should be the same thing right?
I'm working to fine-tune some of my Terraform modules, specifically around the google_compute_vpn_tunnel, google_compute_router_interface, and google_compute_router_peer resources. I'd like to make things similar to AWS, where pre-shared keys and tunnel interface IP addresses are randomized by default, but can be overridden by the user (provided they are within a certain range).
The random option is working fine. For example, to create a 20-character random password, I do this:
resource "random_password" "RANDOM_PSK" {
length = 20
special = false
}
But, I only want to use this value if an input variable called vpn_shared_secret was not defined. Seems like this should work:
variable "vpn_shared_secret" {
type = string
default = null
}
locals {
vpn_shared_secret = try(var.vpn_shared_secret, random_password.RANDOM_PSK.result)
}
resource "google_compute_vpn_tunnel" "VPN_TUNNEL" {
shared_secret = local.vpn_shared_secret
}
Instead, it seems to ignore the vpn_shared_secret input variable and just go with the randomly generated one each time.
Is try() the correct way to be doing this? I'm just now learning Terraform if/else and map statements.
How about the coalesce() function?
The coalesce function takes any number of arguments, and returns the first argument that isn't null or an empty string.
locals {
vpn_shared_secret = coalesce(var.vpn_shared_secret, random_password.RANDOM_PSK.result)
}
hiera data
ae::namespace_by_fqdn_pattern:
'((^dfw-oel6)|(^dfw-oel7)|(^dfw-ubuntu1604))-((client))([0-9]{2}).pp-devcos-ae.us-central1.gcp.dev.blah.com': '/test/blah/regression/client'
'((^dfw-oel6)|(^dfw-oel7)|(^dfw-ubuntu1604))-((server))([0-9]{2}).pp-devcos-ae.us-central1.gcp.dev.blah.com': '/test/blah/regression/server'
class
class ae {
$namespace = hiera('ae::namespace')
$target_host_patterns = hiera('ae::target_host_patterns')
hiera_hash('ae::namespace_by_fqdn_pattern').each |String $pattern, String $ns| {
if $facts['networking']['fqdn'].match($pattern) {
$ae::namespace = "${ns}"
}
}
<snip>
... yields
Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Illegal attempt to assign to 'ae::enforcerd_namespace'. Cannot assign to variables in other namespaces (file: /etc/puppetlabs/code/environments/ar/modules/ae/manifests/init.pp, line: 21, column: 13) on node dfw-ubuntu1604-client02.pp-devcos.us-central1.gcp.dev.blah.com
... anyone here know how to do this correctly? trying to conditionally override that $ae::namespace variable but i'm too puppet-ignorant to know how to get it working : (
the loop and the pattern matching bits work. just can't figure out how to correctly set that class variable from within the hiera_hash().each loop.
How to set a puppet class variable from within a hiera_hash each loop?
You cannot. The associated block of an each() call establishes a local scope for each iteration. Variable assignments within apply to that local scope, and therefore last only for the duration of one execution of the block. You cannot anyway assign a new value to a variable during its lifetime, so even if you could assign to a class variable from within an each() call, it would be difficult to use that capability (and your approach would not work).
There are several ways you could approach the problem without modifying the form of the data. You could leverage the filter() function, for example, but my personal recommendation would be to use the reduce() function, something like this:
$namespace = lookup('ae::target_host_patterns').reduce(lookup('ae::namespace')) |$ns, $entry| {
$facts['networking']['fqdn'].match($entry[0]) ? { true => $entry[1], default => $ns }
}
That does pretty much exactly what your original code seems to be trying to do, except that the selected namespace is returned by the reduce() call, to be assigned to a variable by code at class scope, instead of the lambda trying to assign it directly. Note also that it takes care not only of testing the patterns but of assigning the default namespace when none of the patterns match, as it needs to do because you can only assign to the namespace variable once.
so the solution i landed on was to change the hiera data to:
ae::namespace : '/test/blah/regression'
ae::namespace_patterns: ['((^dfw-oel6)|(^dfw-oel7)|(^dfw-ubuntu1604))-((client))([0-9]{2}).pp-devcos-ae.us-central1.gcp.dev.blah.com', '((^dfw-oel6)|(^dfw-oel7)|(^dfw-ubuntu1604))-((server))([0-9]{2}).pp-devcos-ae.us-central1.gcp.dev.blah.com']
ae::namespace_by_pattern:
'((^dfw-oel6)|(^dfw-oel7)|(^dfw-ubuntu1604))-((client))([0-9]{2}).pp-devcos-ae.us-central1.gcp.dev.blah.com': '/test/paypal/regression/client'
'((^dfw-oel6)|(^dfw-oel7)|(^dfw-ubuntu1604))-((server))([0-9]{2}).pp-devcos-ae.us-central1.gcp.dev.blah.com': '/test/paypal/regression/server'
then the class code to:
$pattern = hiera_hash('ae::namespace_patterns').filter |$pattern| {
$facts['networking']['fqdn'] =~ $pattern
}
if length($pattern) {
$namespace = hiera('ae::namespace_by_pattern')[$pattern[0]]
} else {
$namespace = hiera('ae::namespace')
}
definitely still open to better answers. just what my own hacking produced as workable so far through much trial and error.
I struck to pass multiple arguments in define.
The following is my code. I would like to pass two array inside the define, But I'm able to pass only one as like the following.
class test {
$path = [$path1,$path2]
$filename = [$name1,$name2]
define testscript { $filename: } // Can able to pass one value.
}
define testscript () {
file {"/etc/init.d/${title}": //Can able to receive the file name.
ensure => file,
content => template('test/test.conf.erb'),
}
From my above code, I could retrieve the filename inside the define resource. I also need path to set the value in the template. I`m not able to send / retrieve second argument in template.
Is there any way to improve my code to pass two values ( $path and $filename ) inside define resource ?
Any help is much appreciated.
Is there any way to improve my code to pass the two values ( $path and $filename ) inside define resource ?
Puppet has good documentation, which covers this area well.
To begin, you need to appreciate that a defined type is a resource type, in almost every way analogous to any built-in or extension type. If your defined type accepts parameters, then you bind values to those parameters just as you would in any other resource declaration. For example:
class mymodule::test {
mymodule::testscript { $name1: path => $path1 }
mymodule::testscript { $name2: path => $path2 }
}
define mymodule::testscript ($path) {
file {"${path}/${title}":
ensure => 'file',
content => template('test/test.conf.erb')
}
}
Additionally, because defined types are resource types, you should discard the concept of "passing" values as to them as if they were instead functions. That mental model is likely to betray you. In particular, it will certainly give you the wrong expectation about what would happen if you specify an array or a hash as your resource title.
In particular, you need to understand that in any resource declaration, if you give the resource title as an array, then that means a separate resource for each array member, with the array member as that resource's title. In that case, every one of those resources receives the same parameter values, as declared in the body of the declaration. Moreover, resource titles are always strings. Except for one level of arrays, as described above, if you give anything else as a resource title then it will be converted to a string.