Terraform helm_release fails on set labels - amazon-web-services

I wish to set labels inside a Helm chart using the Terraform helm_release resource with a helpers function.
When using the chart values there are no issues, as seen here:
values.yaml
mylabels:
name: "foo"
type: "boo"
_helpers.tpl
{{- define "aws.labels" -}}
{{- range $k, $v := .Values.mylabels }}
{{ $k }}: {{ $v | quote }}
{{- end -}}
{{- end -}}
deployment.yaml
metadata:
labels:
{{- include "aws.labels" . | trim | nindent 4 }}
But when values are passed in from the helm_release resource it fails.
set {
name = "mylabels"
value = yamlencode(var.aws_tags)
}
The output values for the above is:
+ set {
+ name = "mylabels"
+ value = <<-EOT
"Environment": "123"
"Owner": "xyz"
EOT
}
Which generates error:
│ Error: template: aws-efs-csi-driver/templates/controller-deployment.yaml:9:6: executing "aws-efs-csi-driver/templates/controller-deployment.yaml" at <include "aws.tags" .>: error calling include: template: aws-efs-csi-driver/templates/_helpers.tpl:68:27: executing "aws.tags" at <.Values.mylabels>: range can't iterate over
│ "Environment": "123"
│ "Owner": "xyz"
Any ideas or pointers would be greatly appreciated :-)
Solution
Thank you #jordanm that worked :~)

When using set in this way, it's the same as --set on the helm cli. You can't provide a yaml value that includes multiple values and must set each individually.
set {
name = "mylabels.Environment"
value = var.aws_tags["Environment"]
}
set {
name = "mylabels.Owner"
value = var.aws_tags["Owner"]
}
You may also be able the dynamic feature of terraform to iterate over your aws_tags variable:
dynamic "set" {
for_each = var.aws_tags
content {
name = "mylabels.${set.key}"
value = set.value
}
}

Related

Ansible Yaml list conversion for winshare

Im trying to modify winshare permissions depending on yaml list that follows:
Name:
path: C:\sharepath
Full:
- DOMAIN\USER
- DOMAIN\USER2
I have imported that list to my ansible code
- name: Shareperm
win_share:
name: "{{ Name }}
path: "{{ Name.path }}"
full: "{{ Name.Full | default(omit) | join(', ') }}"
That code gives me an error
account_name _ is not a valid account, cannot get SID: Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated.
What am I missing to pass argument list containing escape character \ correct way (string - comma separated) according to
https://docs.ansible.com/ansible/latest/collections/ansible/windows/win_share_module.html

Create a new array with SecurityGroupIds instead of SecurityGroupNames with Ansible

I am relatively new to Ansible and I am struggling to understand how to perform the following scenario:
I have an array with AWS security group names looking like this
['Security-Group-Name1', 'SecurityGroup-Name2', 'SecurityGroup-Name3']
However, what I want is to have an array of SecurityGroupIds. Using Ansible I have the ec2_group_info as an option to retrieve information about a security group. So far so good ...
Now comes my question. I need to loop through the above array using ec2_group_info, set the name of the security group I need and return the retrieved Id into a new array so in the end I have something like this.
['Security-Group-Id1', 'SecurityGroup-Id2', 'SecurityGroup-Id3']
I know I need to use a loop with sort of a dynamic index. But it is not really clear to me how to do this in Ansible.
I am aware of the latest loop section of Ansible Docs and I find them more than confusing...
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
Edit:
This is the current code which works as needed:
- name: Installing pip if not existing on host
pip:
name: boto3
- name: Get SecurityGroupId information
ec2_group_info:
filters:
group_name: ['SG-One', 'SG-Two']
vpc_id: 'vpc-id'
register: my_groups
- set_fact:
my_group_ids: '{{ my_groups.security_groups | map(attribute="group_id") | list }}'
- debug:
msg: "{{ my_groups }}"
- debug:
msg: "{{ my_group_ids }}"
This is the outcome:
TASK [Gathering Facts] ***************************************************
ok: [localhost]
TASK [machine-provisioning : Installing pip if not existing on host] ************
ok: [localhost]
TASK [machine-provisioning : Get SecurityGroupId information] *************************
ok: [localhost]
TASK [machine-provisioning : set_fact] *********************************
ok: [localhost]
TASK [machine-provisioning : debug] ***********************************************
ok: [localhost] => {
"msg": [
"sg-00000000",
"sg-11111111"
]
}
On that linked page about loops, you'll observe the use of register:, which is how you'd capture the result of that ec2_group_info: lookup, then use the map jinja filter to extract map(attribute="group_id") from the resulting list of results; you have to feed the output of map into the list filter, because map and a few others are python generators, and thus need a terminal action to materialize their data. The set_fact: is how ansible does "assignment"
- ec2_group_info:
filters:
group_name: '{{ the_group_names }}'
vpc_id: '{{ my_vpc_id }}'
register: my_groups
- set_fact:
my_group_ids: '{{ my_groups.security_groups | map(attribute="group_id") | list }}'
yields:
ok: [localhost] => {"ansible_facts": {"my_group_ids": ["sg-0c5c277ed1edafb54", "sg-7597a123"]}, "changed": false}

How to use StringMap parameters in SSM documents?

I have the following step in a SSM document. The result of the call is a Json, so I wanted to parse it as a stringMap (which seems to be the correct type for it) instead of creating an output for each variable I want to reference
I've tried referencing this as both:
{{ GetLoadBalancerProperties.Description.Scheme }}
and
{{ GetLoadBalancerProperties.Description[\"LoadBalancerName\"] }}
In both cases I get an error saying the variable was never defined
{
"name": "GetLoadBalancerProperties",
"action": "aws:executeAwsApi",
"isCritical": true,
"maxAttempts": 1,
"onFailure": "step:deleteParseCloudFormationTemplate",
"inputs": {
"Service": "elb",
"Api": "describe-load-balancers",
"LoadBalancerNames": [
"{{ ResourceId }}"
]
},
"outputs": [
{
"Name": "Description",
"Selector": "$.LoadBalancerDescriptions[0]",
"Type": "StringMap"
}
]
}
This is the actual message:
Step fails when it is validating and resolving the step inputs. Failed to resolve input: GetLoadBalancerProperties.Description["LoadBalancerName"] to type String. GetLoadBalancerProperties.Description["LoadBalancerName"] is not defined in the Automation Document.. Please refer to Automation Service Troubleshooting Guide for more diagnosis details.
I believe the answer you were searching is in here:
https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-plugins.html#top-level-properties-type
Just to name a few examples:
Map type is a Python dict, hence if your output is a dict you should use StringMap in the SSM Document.
While List type is same as Python list.
So if your output is a List of Dictionary the type you want to use is MapList.
In some cases it seems that you cannot. I was able to work around this issue, by using a Python script in the SSM document to output the right type, but otherwise I believe the SSM document is not flexible enough to cover all cases.
The script I used:
- name: myMainStep
action: aws:executeScript
inputs:
Runtime: python3.6
Handler: myMainStep
InputPayload:
param: "{{ myPreviousStep.myOutput }}"
Script: |-
def myMainStep(events,context):
myOutput = events['myOutput']
for tag in myOutput:
if tag["Key"] == "myKey":
return tag["Value"]
return "myDefaultValue"
outputs:
- Name: output
Selector: "$.Payload"
Type: String
You can find out what the myOutput should be in AWS web console > SSM > Automation > Your execution, if you have already executed your automation once > executeScript step > input parameters

Parsing variables in ansible inventory in python

I'm trying to parse ansible variables using python specified in an inventory file like below:
[webservers]
foo.example.com type=news
bar.example.com type=sports
[dbservers]
mongodb.local type=mongo region=us
mysql.local type=mysql region=eu
I want to be able to parse type=news for host foo.example.com in webservers and type=mongo region=us for host mongodb.local under dbservers. Any help with this is greatly appreciated
The play below
- name: List type=news hosts in the group webservers
debug:
msg: "{{ hostvars[item].inventory_hostname }}"
loop: "{{ groups['webservers'] }}"
when: hostvars[item].type == "news"
- name: List type=mongo and region=us hosts in the group dbservers
debug:
msg: "{{ hostvars[item].inventory_hostname }}"
loop: "{{ groups['dbservers'] }}"
when:
- hostvars[item].type == "mongo"
- hostvars[item].region == "us"
gives:
"msg": "foo.example.com"
"msg": "mongodb.local"
If the playbook will be run on the host:
foo.example.com
you can get "type = news" simply by specifying "{{type}}". If you want to use in "when" conditions, then simply indicating "type"
If the playbook will be run on the host:
mongodb.local
then the value for "type" in this case will automatically be = "mongo", and "region" will automatically be = "us"
The values of the variables, if they are defined in the hosts file as you specified, will automatically be determined on the specified hosts
Thus, the playbook can be executed on all hosts and if you get a value for "type", for example:
- debug:
     msg: "{{type}}"
On each of the hosts you will get your unique values that are defined in the hosts file
I'm not sure that I understood the question correctly, but if it meant that on the foo.example.com host it was necessary to get a list of servers from the "webservers" group that have "type = news", then the answer is already given.
Rather than re-inventing the wheel, I suggest you have a look at how ansible itsef is parsing ini files to turn them into an inventory object
You could also easily get this info in json format with a very simple playbook (as suggested by #vladimirbotka), or rewrite your inventory in yaml which would be much easier to parse with any external tool
inventory.yaml
---
all:
children:
webservers:
hosts:
foo.example.com:
type: news
bar.example.com:
type: sports
dbservers:
hosts:
mongodb.local:
type: mongo
region: us
mysql.local:
type: mysql
region: eu

Ansible: Take action on something that is NOT in your dict, but is in a list

I have to set the stage a little first. The application is kafka.
End goal: I want to be able to make kafka config match what I provide in the dictionary (specifically topics). Adding topics to make kafka match is easy. The part I'm having problem is removing topics from kafka if they are not in the dictionary. The only variable I ened to provide kafka is the "topic name" in order to delete.
I can obtain a list of currently added topics from kafka as a variable we will call {{ existing }}. It looks like this:
topic1
topic2
topic3
topic4
My dictionary is as follows:
kafka_topics:
topic1:
partitions: 1
replication_factor: 1
topic2:
partitions: 1
replication_factor: 1
How do I take action on topic3 and topic4 because they are not part of the dict?
At first glance, you just say do a
if {{existing}} != {{item.key}} then do action
but that does not work because it only checks 1 key at a time. If the types were reversed the list were a dict, dict was a list) you could easily do it by saying if topics contains existing, then do action.
There has GOT to be a better way to do this.
Check if each item in existing list is in kafka_topics.keys(). Example:
---
- hosts: localhost
gather_facts: no
connection: local
vars:
kafka_topics:
topic1:
partitions: 1
replication_factor: 1
topic2:
partitions: 1
replication_factor: 1
existing:
- topic1
- topic2
- topic3
- topic4
tasks:
- debug:
msg: "{{ item }} is not in 'kafka_topics'"
with_items: "{{ existing }}"
when: item not in kafka_topics.keys()
Gets you:
TASK [debug] *******************************************************************
skipping: [localhost] => (item=None)
skipping: [localhost] => (item=None)
ok: [localhost] => (item=None) => {
"msg": "topic3 is not in 'kafka_topics'"
}
ok: [localhost] => (item=None) => {
"msg": "topic4 is not in 'kafka_topics'"
}
Replace debug with the "action" you want.
Alternatively you can use the difference filter on the above data structures.