I am struggling to let a task only execute when a specific value is defined.
I'm using Gradle 3.5.
task signJar(type: SignJar, dependsOn: reobfJar) {
onlyIf {
project.hasProperty('mod_keystore')
}
keyStore = project.keyStore
alias = project.keyStoreAlias
storePass = project.keyStorePass
keyPass = project.keyStoreKeyPass
inputFile = jar.archivePath
outputFile = jar.archivePath
}
As you can see, I already tried the onlyIf statement, but the task still runs. This results into a crash:
Could not get unknown property 'keyStore' for root project 'JustAnotherEnergy' of type org.gradle.api.Project.
The property 'mod_keystore' is no where defined, but the code get's executed.
task signJar(type: SignJar, dependsOn: reobfJar) {
if(project.hasProperty('mod_keystore')) {
keyStore = project.keyStore
alias = project.keyStoreAlias
storePass = project.keyStorePass
keyPass = project.keyStoreKeyPass
inputFile = jar.archivePath
outputFile = jar.archivePath
}
}
This works. The code does not get executed, but I'm running into other problems:
If the property 'mod_keystore' is not defined, Gradle can't set a value for the for example 'keyStore' property, but the task SignJar requires this values to be set.
This means this task should only be executed when the property 'mod_keystore' is defined. If it is not defined, the task should be skipped.
As you can see, I already tried the onlyIf statement, but the task still runs.
No, the task does not run. You need to distinguish between the configuration phase and the execution phase. The task closure, where you are setting your task properties, is executed during the configuration phase, right after the task is created. Only task actions (defined by the task type) and closures added via doFirst or doLast are executed during the execution phase.
If you disable or skip a task via onlyIf or enabled, you only disable / skip the execution (phase) of the task, not its configuration (phase).
As a solution for your specific problem, you can rely on your first approach with the onlyIf condition, but add a fail-safe way to access your project properties:
task signJar(type: SignJar, dependsOn: reobfJar) {
onlyIf {
hasProperty('mod_keystore')
}
keyStore = findProperty('keyStore')
alias = findProperty('keyStoreAlias')
storePass = findProperty('keyStorePass')
keyPass = findProperty('keyStoreKeyPass')
inputFile = jar.archivePath
outputFile = jar.archivePath
}
Related
We've set up our TF GKE code so that the user can specify either the region or zone for the cluster.
However, we need to then check this variable and remove the zone suffix (if it exists) for the deployment of static IP addresses.
We have the following variable:
variable "k8s_cluster_location" {
type = string
default = "europe-west2"
validation {
condition = contains(["europe-west2", "europe-west2-a", "europe-west2-b", "europe-west2-c", "us-east4", "us-east4-a", "us-east4-b", "us-east4-c", "europe-west1", "europe-west1-a", "europe-west1-b", "europe-west1-c" ], var.k8s_cluster_location)
error_message = "Given GCP location not (yet) supported. Contact X if you think it should..."
}
description = "Location of the Kubernetes cluster."
}
If, for example, the variable is "europe-west2-a", we need to remove "-a" to acquire the parent region.
Would we need to incorporate a Regex check? Or could we use something like StartsWith()/EndsWith()?
I would definitely recommend the regular expression solution here as you suggest:
variable "k8s_cluster_location" {
type = string
default = "europe-west2"
validation {
condition = can(regex("(?:europe-west[12])|(?:us-east4)", var.k8s_cluster_location))
error_message = "Given GCP location not (yet) supported. Contact X if you think it should..."
}
description = "Location of the Kubernetes cluster."
}
Note that if you are using Terraform 1.3.x, then you can also use the var.k8s_cluster_location value in the error_message instead of "Given GCP location".
For your other suggestion of startswith(), you would need to do something like anytrue(startswith(var.k8s_cluster_location, "europe-west1"), startswith(var.k8s_cluster_location, "europe-west2"), startswith(var.k8s_cluster_location, "us-east4")), but that feels slightly messier to me.
I'd like to enforce that a value is set rather than using the default "" if one of the other values is a certain string.
For example I have:
module "test_beanstalk" {
tier = "Worker"
queue = "myQueue"
///
}
in this, when tier is set to worker I'd like to enforce that queue is also set. In the above example there's a scenario where the queue can be omitted resulting in aws spawning a generic one rather than using the queue that is required for that particular application.
Such feature is not directly supported in TF. But you can force TF to error out using locals and some condition that will simply lead to error if your verification fails. For example, in your test_beanstalk you can have:
variable "tier" {
default = "Worker"
}
variable "queue" {
default = ""
}
locals {
if_queue_given = var.tier == "Worker" && var.queue == "" ? tonumber("queue can't be empty") : 1
}
The tonumber("queue can't be empty") will be executed and will lead to TF error, if the condition var.tier == "Worker" && var.queue == "" is true.
I have created a module on terraform, this module creates aws_servicecatalog_provisioned_product resources.
When I call this module from the root I am using for_each to run into a list of objects.
The module runs into this list of objects and creates the aws_servicecatalog_provisioned_product resources in parallel.
Is there a way to create the resources one by one? I want that the module will wait for the first iteration to be done and to create the next just after.
Is there a way to create the resources one by one?
Sadly, there is not such way, unless you remove for_each and create all the modules separately with depends_on.
TF is not a procedural language, and it always will do things in parallel for for_each and count.
I am using terraform templatefile that creates resources with a depends on order, and then terraform creates resources one by one.
Here is the code:
locals {
expanded_accounts = [
{
AccountEmail = example1#example.com
AccountName = example1
ManagedOrganizationalUnit = example_ou1
SSOUserEmail = example1#example.com
SSOUserFirstName = Daniel
SSOUserLastName = Wor
ou_id = ou_id1
},
{
AccountEmail = example2#example.com
AccountName = example2
ManagedOrganizationalUnit = example_ou2
SSOUserEmail = example2#example.com
SSOUserFirstName = Ben
SSOUserLastName = John
ou_id = ou_id2
}
]
previous_resource = [
for acc in local.expanded_accounts :
acc.AccountName
]
resources = { res = local.expanded_accounts, previous = concat([""], local.previous_resource)
}
resource "local_file" "this" {
content = templatefile("./provisioned_accounts.tpl", local.resources)
filename = "./generated_provisioned_accounts.tf"
directory_permission = "0777"
file_permission = "0777"
lifecycle {
ignore_changes = [directory_permission, file_permission, filename]
}
}
provisioned_accounts.tpl configuration:
%{ for acc in res }
resource "aws_servicecatalog_provisioned_product" "${acc.AccountName}" {
name = "${acc.AccountName}"
product_id = replace(data.local_file.product_name.content, "\n", "")
provisioning_artifact_id = replace(data.local_file.pa_name.content, "\n", "")
provisioning_parameters {
key = "SSOUserEmail"
value = "${acc.SSOUserEmail}"
}
provisioning_parameters {
key = "AccountEmail"
value = "${acc.AccountEmail}"
}
provisioning_parameters {
key = "AccountName"
value = "${acc.AccountName}"
}
provisioning_parameters {
key = "ManagedOrganizationalUnit"
value = "${acc.ManagedOrganizationalUnit} (${acc.ou_id})"
}
provisioning_parameters {
key = "SSOUserLastName"
value = "${acc.SSOUserLastName}"
}
provisioning_parameters {
key = "SSOUserFirstName"
value = "${acc.SSOUserFirstName}"
}
timeouts {
create = "60m"
}
%{if index != 0 }
depends_on = [aws_servicecatalog_provisioned_product.${previous[index]}]
%{ endif }
}
%{~ endfor ~}
Why do you want it to wait for the previous creation? Terraform relies on the provider to know what can happen in parallel and will run in parallel where it can.
Setting the parallelism before the apply operation would be how I would limit it artificiality if I wanted to as it's an technical workaround that keeps your Terraform code simple to read.
TF_CLI_ARGS_apply="-parallelism=1"
terraform apply
If you find this is slowing down all Terraform creations but you need this particular set of resources to be deployed one at a time then it might be time to break these particular resources out into their own Terraform config directory and apply it in a different step to the rest of the resources again with the parallelism setting.
You have to remove the for_each and use depends_on for every element if you want to make sure that they are created one after another.
If you want only the first resource to be provisioned before other resources:
Separate the first resource only and use the for_each for the remaining resources. You can put an explicit dependency using depends_on for the remaining resources to depend on the first one. Because for_each expects a set or a map, this input would require some modification to be able to exclude the provisioning of the first resource.
A more drastic approach, if you really need to provision resources one by one, would be to run the apply command with -parallelism=1. This would reduce the number of resources provisioned in parallel to 1. This would apply to the whole project. I would not recommend this, since it would increase drastically the running time for the apply.
I defined a variable map my_role in terraform and set its value in abc.tfvar file as follows. if I assign account id as actual value, it works, if I set account id as a variable, it does not work. Does it mean tfvar file only allow actual value, not variable? By the way, I use terraform workspace. Therefore my_role is different based on workspace I select.
The following works:
my_role = {
dev = "arn:aws:iam::123456789012:role/myRole"
test = ...
prod = ...
}
The following does not work:
my_role = {
dev = "arn:aws:iam::${lookup(var.aws_account_id, terraform.workspace)}:role/myRole"
test = ...
prod = ...
}
The following does not work either:
lambdarole = {
dev = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/myRole"
test = ...
prod = ...
}
does
Have you tried following the example on Input Variables?
You can define your abc.tfvars file with:
variable "my_role" {
type = "map"
default = {
"dev" = "arn:aws:iam::123456789012:role/myRole"
"test" = "..."
"prod" = "..."
}
}
And access it with "${lookup(var.my_role, terraform.workspace)}".
Also, according to the from a file, the variables defined in .tfvars files are automatically loaded if you name the file terraform.tfvars, if not, you have to pass as an argument with -var-file=...
Cannot test it right now, but probably is something in this way.
I am replying when terraform 0.12 version is latest one. Solution is simple, you can create one file say vars.tf and declare variables in it.
Example - variable "xyz" {}
Now create terraform.tfvars and initialize it.
Example - xyz="abcd"
No need to pass any runtime args, it will be picked directly.
Terraform has aws_caller_identity data source. You do not need to mention or hand code account id anywhere. It can be fetched using this source.
In any of your .tf file, just include this source and then you can fetch relevant argument value.
This is how you can do it. Define this in any *.tf file
data "aws_caller_identity" "current" {}
Now where ever you want the value of arn or account id, it can be fetched using :
For account id(For terraform0.12):
data.aws_caller_identity.current.account_id
In your case, it would be like this :
dev = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/myRole"
But in order to this work, you need to define data source like shown above in any *.tf file.
For more help, refer following:
URL : https://www.terraform.io/docs/providers/aws/d/caller_identity.html
I have multiple test suites configured (as shown below) in my gradle build
task group1(type: Test) {
useTestNG() {
includeGroups 'group1'
}
// ... some excluded tests
ext.useDefaultListeners = true
ext.workingDirectory = 'build/'
// ... some system properties required by this test
}
task group2(type: Test, dependsOn: group1) {
useTestNG() {
includeGroups 'group2'
}
// ... some excluded tests
ext.useDefaultListeners = true
ext.workingDirectory = 'build/'
// ... some system properties required by this test
}
test.dependsOn group2
When I run the gradle clean build --debug, I could see after every test task (group1, group2 etc), gradle deletes the test report and regenerates it. Hence, after the build is done, whatever report I get does not have any information. All blank!
How can I get a combined report?
Thanks,
NN
You should use a separate task of type TestReport
Check out section '23.12.7. Test reporting' in the user guide