Terraform : The "each" object does not support this operation - google-cloud-platform

I'm trying to create "Parent" and "Child" resources with Terraform, from a variable like this:
variable "tags" {
default = {
parent1 = ["child1", "child2"]
parent2 = ["child2", "child4", "child5"]
}
}
For each key in my variable I want to create a Parent resource, which I do and works fine:
resource "google_tags_tag_key" "parent" {
for_each = toset(keys(var.tags))
parent = "organizations/1234567890"
short_name = each.key
description = ""
}
My problem is regarding child resources ... For each item in a list, I want to create a child resource. But I can't seem to do a nested loop on the "for_each" argument.
So I tried to use the "setproduct" function to flatten my variable. as follows.
locals {
helper_list = [for k, v in var.tags:
setproduct(list(k), v)
]
}
This is a workaround, but despite everything, I still cannot resolve my problem by trying in the following way.
resource "google_tags_tag_value" "child" {
for_each = local.helper_list
parent = "tagKeys/${google_tags_tag_key.parent[each.0]}"
short_name = each.1
description = ""
}
This returns the folling error: The "each" object does not support this operation.
How can I solve my problem by going through a helper_list or without?

I would do this as follows:
variable "tags" {
default = {
parent1 = ["child1", "child2"]
parent2 = ["child2", "child4", "child5"]
}
}
locals {
# construct flat map
helper_map = merge([
for parent, children in var.tags:
{
for child in children:
"${parent}-${child}" => {
"parent" = parent
"child" = child
}
}
]...)
}
output "helper_map" {
value = local.helper_map
}
which gives:
helper_map = {
"parent1-child1" = {
"child" = "child1"
"parent" = "parent1"
}
"parent1-child2" = {
"child" = "child2"
"parent" = "parent1"
}
"parent2-child2" = {
"child" = "child2"
"parent" = "parent2"
}
"parent2-child4" = {
"child" = "child4"
"parent" = "parent2"
}
"parent2-child5" = {
"child" = "child5"
"parent" = "parent2"
}
}
Then you can do, assuming I understand your desired outcome:
resource "google_tags_tag_value" "child" {
for_each = local.helper_map
parent = "tagKeys/${google_tags_tag_key.parent[each.value.parent]}"
short_name = each.value.child
description = ""
}

Related

Terraform expand map variable to independent parameters

I've the following variable:
variable "mymap" {
type = map(string)
default = {
"key1" = "val1"
"key2" = "val2"
}
}
I am trying to expand this to create individual parameters in this resource:
resource "aws_elasticache_parameter_group" "default" {
name = "cache-params"
family = "redis2.8"
parameter {
name = "activerehashing"
value = "yes"
}
parameter {
name = "min-slaves-to-write"
value = "2"
}
}
My desired state for this example would be:
resource "aws_elasticache_parameter_group" "default" {
name = "cache-params"
family = "redis2.8"
parameter {
name = "key1"
value = "val1"
}
parameter {
name = "key2"
value = "val2"
}
}
I don't see this supported explicitly in the docs; am I even taking the correct approach to doing this?
(I'm mainly looking at leveraging 'dynamic' and 'for_each' keywords, but haven't been able to have success)
To achieve the desired state, you would have to do a couple of things. One could be to use dynamic meta-argument [1] with for_each [2]. The code would have to be changed to the following:
resource "aws_elasticache_parameter_group" "default" {
name = "cache-params"
family = "redis2.8"
dynamic "parameter" {
for_each = var.mymap
content {
name = parameter.value.name
value = parameter.value.value
}
}
}
However, you would also have to adjust the variable:
variable "mymap" {
type = map(map(string))
description = "Map of parameters for Elasticache."
default = {
"parameter1" = {
"value" = "value1"
"name" = "name1"
}
}
}
Then, you can define the values for the variable mymap in a tfvars file (e.g., terraform.tfvars) like this:
mymap = {
"parameter1" = {
"name" = "activerehashing"
"value" = "yes"
}
"parameter2" = {
"name" = "min-slaves-to-write"
"value" = "2"
}
}
[1] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
You can use a dynamic block to dynamically declare zero or more nested configuration blocks based on a collection.
resource "aws_elasticache_parameter_group" "default" {
name = "cache-params"
family = "redis2.8"
dynamic "parameter" {
for_each = var.mymap
content {
name = parameter.key
value = parameter.value
}
}
}
The above tells Terraform to generate one parameter block for each element of var.varmap, and to populate the name and value arguments of each generated block based on the key and value from each map element respectively.
The parameter symbol inside the content block represents the current element of the collection. This symbol is by default named after the block type being generated, which is why it was named parameter in this case. It's possible to override that generated name using an additional iterator argument in the dynamic block, but that's necessary only if you are generating multiple levels of nesting where a nested block type has the same name as its container.

Terraform - Optional Nested Variable

I'm trying to create a module for Sagemaker endpoints. There's an optional object variable called async_inference_config. If you omit it, the endpoint being deployed is synchronous, but if you include it, the endpoint deployed is asynchronous. To satisfy both of these usecases, the async_inference_config needs to be an optional block.
I am unsure of how to make this block optional though.
Any guidance would be greatly appreciated. See example below of structure of the optional parameter.
Example:
resource "aws_sagemaker_endpoint_configuration" "sagemaker_endpoint_configuration" {
count = var.create ? 1 : 0
name = var.endpoint_configuration_name
production_variants {
instance_type = var.instance_type
initial_instance_count = var.instance_count
model_name = var.model_name
variant_name = var.variant_name
}
async_inference_config {
output_config {
s3_output_path = var.s3_output_path
}
client_config {
max_concurrent_invocations_per_instance = var.max_concurrent_invocations_per_instance
}
}
lifecycle {
create_before_destroy = true
ignore_changes = ["name"]
}
tags = var.tags
depends_on = [aws_sagemaker_model.sagemaker_model]
}
Update: What I tried based on the below suggestion, which seemed to work
dynamic "async_inference_config" {
for_each = var.async_inference_config == null ? [] : [true]
content {
output_config {
s3_output_path = lookup(var.async_inference_config, "s3_output_path", null)
}
client_config {
max_concurrent_invocations_per_instance = lookup(var.async_inference_config, "max_concurrent_invocations_per_instance", null)
}
}
}
You could use a dynamic block [1] in combination with for_each meta-argument [2]. It would look something like:
dynamic "async_inference_config" {
for_each = var.s3_output_path != null && var.max_concurrent_invocations_per_instance != null ? [1] : []
content {
output_config {
s3_output_path = var.s3_output_path
}
client_config {
max_concurrent_invocations_per_instance = var.max_concurrent_invocations_per_instance
}
}
}
Of course, you could come up with a different variable, say enable_async_inference_config (probalby of type bool) and base the for_each on that, e.g.:
dynamic "async_inference_config" {
for_each = var.enable_async_inference_config ? [1] : []
content {
output_config {
s3_output_path = var.s3_output_path
}
client_config {
max_concurrent_invocations_per_instance = var.max_concurrent_invocations_per_instance
}
}
}
[1] https://www.terraform.io/language/expressions/dynamic-blocks
[2] https://www.terraform.io/language/meta-arguments/for_each

How do I populate sourceInfo in SSM Association using TerraForm

I am building a very basic Systems Manager Association in TerraForm but I do not understand what the sourceInfo field is asking for. It requires a string but even simple strings like "test" cause it to reject the input.
resource "aws_ssm_association" "sslscanssm" {
name = "AWS-RunInspecChecks"
association_name = "test"
targets = {
key = "tag:os"
values = ["linux"]
}
parameters {
sourceType = "GitHub"
sourceInfo = "{"owner":"awslabs","repository":"amazon-ssm","path":"Compliance/InSpec/PortCheck","getOptions":"branch:master"}"
#^this line doesn't work
#sourceInfo = "test"
#^this line doesn't work either
}
}
Instead of escaping all of your strings you could also use the jsonencode function to turn a map into the JSON you want:
locals {
source_info = {
owner = "awslabs"
repository = "amazon-ssm"
path = "Compliance/InSpec/PortCheck"
getOptions = "branch:master"
}
}
resource "aws_ssm_association" "sslscanssm" {
name = "AWS-RunInspecChecks"
association_name = "test"
targets = {
key = "tag:os"
values = ["linux"]
}
parameters {
sourceType = "GitHub"
sourceInfo = "${jsonencode(local.source_info)}"
}
}
I wasn't aware sourceInfo expects parentheses and all inner double quotes to be escaped or it won't work.
resource "aws_ssm_association" "sslscanssm" {
name = "AWS-RunInspecChecks"
association_name = "test"
targets = {
key = "tag:os"
values = ["linux"]
}
parameters {
sourceType = "GitHub"
sourceInfo = "{\"owner\":\"awslabs\",\"repository\":\"amazon-ssm\",\"path\":\"Compliance/InSpec/PortCheck\",\"getOptions\":\"branch:master\"}"
}
}
There is a mistake in the code shared (no equal sign after targets but after parameters). The correct syntax of the resource is :
resource "aws_ssm_association" "sslscanssm" {
name = "AWS-RunInspecChecks"
association_name = "test"
targets {
key = "tag:os"
values = ["linux"]
}
parameters = {
sourceType = "GitHub"
sourceInfo = "${jsonencode(local.source_info)}"
}
}

model item passed into dictionary is of type 'system.Collections.Generic.List'1[System.String]',but dictionary is model B

I am trying to create a create combobox with performcallback, but I got the error. here this error "the model item passed into dictionary is of type 'system.Collections.Generic.Lost'1[System.String]', but this dix=ctionary requires a model item of type 'DIS_iDealer.Models.SalesMonitoringModel'"
I don't know which code exactly I need to paste but this is what I have:
view combobox :
#model DIS_iDealer.Models.SalesMonitoringModel
#Html.DevExpress().ComboBoxFor(m => m.mpmGroupLine.DESCRIPTION, settings =>
{
settings.Name = "Desc_ID_CB";
settings.Properties.IncrementalFilteringMode = IncrementalFilteringMode.Contains;
settings.Properties.DropDownStyle = DropDownStyle.DropDownList;
settings.CallbackRouteValues = new { Controller = "Report", Action = "cbPartialCategoryDetail" };
settings.Properties.CallbackPageSize = 50;
settings.Properties.ValueField = "DESCRIPTION";
settings.Properties.TextField = "DESCRIPTION";
settings.Width = 150;
settings.SelectedIndex = 0;
settings.Properties.ClientSideEvents.BeginCallback = "function(s,e){e.customArgs['group_Id'] = Category_Id_CB.GetValue()}";
settings.Properties.ValidationSettings.ErrorTextPosition = ErrorTextPosition.Right;
settings.Properties.ValidationSettings.ErrorDisplayMode = ErrorDisplayMode.ImageWithText;
settings.Properties.ValidationSettings.Display = Display.Dynamic;
}).BindList((List<string>)new DIS_iDealer.DataAccess.SalesMonitoringDAC().GetProductGroupDetail(Model.mpmGroupLine.GROUPID).ToList()).GetHtml()
controller :
namespace DIS_iDealer.Controllers
{
public class ReportController : BaseController
{
[HttpGet]
public ActionResult Report_SalesMonitoring_2()
{
SalesMonitoringModel mode = new SalesMonitoringModel();
//MPMPRODUCTGROUPLINE itemB = new MPMPRODUCTGROUPLINE();
/*List<string> mpmCate = mode.GetProductGroup();
if (mpmCate != null)
{
itemB.DESCRIPTION = mode.mpmGroupLine.DESCRIPTION;
}*/
ReportModels modelReport = new ReportModels();
if (TempData["ReportSalesMonitoring2"] != null)
{
modelReport = (ReportModels)TempData["ReportSalesMonitoring2"];
string reportParam = string.Empty;
foreach (string item in modelReport.ParameterReport)
{
reportParam += item;
}
ViewBag.IframeURL = modelReport.WebURL + reportParam;
}
return View(mode);
}
[ValidateInput(false)]
public ActionResult cbPartialCategoryDetail(string group_Id)
{
//SalesMonitoringModel model = new SalesMonitoringModel();
SalesMonitoringDAC model = new SalesMonitoringDAC();
List<string> itemDetail = model.GetProductGroupDetail(group_Id);
return PartialView("_cbPartialCategoryDetail", itemDetail);
}
}
}
Please let me know if you need more info. Thanks.

MVCxGridView Bind List<> with collection to column names

i have some task: I have a List that I pass in View from Controller, But this model in itself is a collection. How should I bind a column in the method of constructing?
c#:
public static void GridColumns(List<ParameterCollection> model, MVCxGridViewColumnCollection columns)
{
columns.Add(??????????);
}
View:
#model List<TFlex.DOCs.Model.Parameters.ParameterCollection>
#using TFlexDOCsWeb;
#Html.DevExpress().GridView(settings =>
{
settings.Name = "GridView";
//settings.ClientSideEvents.Init = "OnInit";
settings.KeyFieldName = "SystemFields.Id";
settings.SettingsBehavior.ConfirmDelete = true;
settings.CallbackRouteValues = new { Controller = "Catalogues", Action = "_GridViewPartial" };
settings.CommandColumn.Visible = true;
settings.CommandColumn.Caption = "*";
settings.CommandColumn.Width = System.Web.UI.WebControls.Unit.Percentage(5);
settings.SettingsPager.Visible = true;
settings.SettingsBehavior.AllowSelectByRowClick = false;
TFlexDOCsWeb.DevEx.GridViewBuilding.<bold>GridColumns</bold>(Model, settings.Columns);
var headerFilterMode = true ? HeaderFilterMode.CheckedList : HeaderFilterMode.List;
foreach (GridViewDataColumn column in settings.Columns)
column.Settings.HeaderFilterMode = headerFilterMode;
settings.Settings.ShowFilterRow = true;
settings.Settings.ShowFilterRowMenu = true;
}).Bind(Model).GetHtml()<bold>
</bold>
model ParameterCollection consist from , where parameter.Value = is value, and parameter.ParameterInfo = Caption of this parameter(name of field). I Dont know how to bind column names for each ParameterCollection Parameter.parameterInfo.name
You just bind it to the name of the property on the object(s) that your list holds in the column settings where you make your gridviewsettings.
See below simplified example.
MODEL
namespace Some.Name.Space
{
public class RecipientListViewModel : List<RecipientViewModel> // HOwever you define your list
{
}
public class RecipientViewModel
{
public string Id {get; set;}
public string FullName { get; set; }
}
}
VIEW
#Html.DevExpress().GridView(settings =>
{
//General settings
settings.Name = "GridViewName";
settings.KeyFieldName = "Id"; // actual parameter name of keyfield on your object that is in your list
//other settings etc
settings.Columns.Add(column =>
{
column.Name = "FullName"; // actual parameter name on your object that is in your list that you want on the column
column.FieldName = "FullName"; // actual parameter name on your object that is in your list that you want on the column
column.Caption = LanguageHelper.GetText("UserConfig_Recipients_RecipientNameHeader");
//column.Width = System.Web.UI.WebControls.Unit.Pixel(200);
});
//other columns etc
}).Bind(YourModel).GetHtml()
CONTROLLER
public ActionResult Overview()
{
RecipientListViewModel model = PopulateInformationView(); // Populates the list with data
return View("YOURVIEWNAME", model);
}