I'm trying to understand boolean expressions in GitHub actions. The manual gives the following example of literals of different types, including boolean as ${{ false }} and ${{ true }}.
Following their lead I use the following step with an env section that sets VARF to false and VART to true and then in the run key has some test output:
- name: Var test
env:
VARF: ${{ false }}
VART: ${{ true }}
run: |
echo "VART=${{ env.VARF }}"
echo "VART=${{ env.VART }}"
echo "VARF && VART=${{ env.VARF && env.VART }}"
echo "VARF || VART=${{ env.VARF || env.VART }}"
echo "!VARF=${{ ! env.VARF }}"
echo "!!VART=${{ !! env.VART }}"
The output is as follows:
VART=false
VART=true
VARF && VART=true
VARF || VART=false
!VARF=false
!!VART=true
The plain output is as expected, showing true and false. The output of the && and || operators is the opposite of expected, giving false && true == true and false || true == false. The output of ${{ ! env.VARF }} is false when I'd expect true.
What's going on here? Possibly variables in the env context get coerced to strings, but that still doesn't explain all the results.
There is some inconsistency between the input context (dispatch_workflow vs workflow_call) and the way booleans are treated by GitHub Actions. I have a short write-up on this. Hope you find this helpful
GitHub Actions: Passing Boolean input variables to reusable workflow_call
The result is completely valid and easy to understand if you know JavaScript (or Python, ...). You can test it in browser console in JavaScript:
'false' && 'true' // "true"
'false' || 'true' // "false"
!'false' // false
!!'true' // true
If you are not familiar to JavaScript, here is the explanation:
Yes, variables in the env context are just strings.
Any non-empty string is considered truthy in logical operation. !'false' == !true == false. Also !! is often used to convert something to boolean in JavaScript.
Short-circuit evaluation. In short and and or return first part which determines the final value.
For example in 'false' && 'true', 'false' is evaluated to true, and the final value of 'false' && 'true' is determined by 'true'. So 'true' is returned.
In 'false' || 'true', 'false' is evaluated to true, the final value is determined. So it returns 'false'.
Related
I have a workflow that is triggered by workflow_dispatch events with a few non-required string inputs and am trying to figure out how to determine if the value was provided or not.
on:
workflow_dispatch:
inputs:
input1:
description: first input
required: false
type: string
input2:
description: second input
required: false
type: string
The documentation says that unset inputs that are of type string will be equated to an empty string in the workflow but when I check that in an if clause condition for a job, it doesn't seem to be evaluating properly.
jobs:
jobA:
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.input1 != '' }}
# ...
Even when I dispatch the workflow with the input empty, both steps are ran.
What is the idiomatic way of checking if an input value was unset or not if this is not it?
You don't need the ${{ }} in this case, just using:
if: github.event_name == 'workflow_dispatch' && github.event.inputs.input1 != ''
Will work
I made an example here if you want to have a look:
workflow file
workflow run (input1 NOT empty)
workflow run (input1 IS empty)
I'm writing a JavaScript action for GitHub Actions that has inputs, some of which are required. A simple example:
name: 'My action'
description: 'My description'
author: 'me'
inputs:
thisOneIsOptional:
description: 'An optional input'
required: false
thisOneIsRequired:
description: 'A required input'
required: true
runs:
using: 'node12'
main: '../lib/main.js'
What I find surprising is that I can use this action in a workflow without providing the required parameter and GitHub does not complain. It seems as though it is up to the action itself to validate that the required inputs were in fact provided. Is that right?
Is there anyway to get GitHub to validate this for me before my action code gets called?
Currently GitHub does not check if required input has been passed. This is being tracked in this issue.
However, you can implement validation yourself using bash, e.g.
- run: |
[[ "${{ inputs.docker_image_name }}" ]] || { echo "docker_image_name input is empty" ; exit 1; }
[[ "${{ inputs.doppler_token }}" ]] || { echo "doppler_token input is empty" ; exit 1; }
shell: bash
How to best deal with long conditional expressions in GitHub Actions Workflow?
I have a workflow that I want to run in 2 cases:
when a pull request is closed
when a comment containing a specific string is created on a pull request
This leads to a workflow definition with a long if expression:
on:
pull_request:
types: [ closed ]
issue_comment:
types: [ created ]
jobs:
do:
if: ${{ github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, 'specific string')) }}
steps:
...
A solution I see is to split the workflow in 2 definitions, but I'd like to avoid this due to DRY.
Ideas I searched for, but did not find working solutions for:
define the expression on multiple lines
split the if in multiple if expressions
It's easy to define the expression on multiple lines using yaml multiline strings (ref: https://yaml-multiline.info/). For example:
if: > # Either `>`, `>-`, `|`, `|-` will be OK
github.event_name == 'pull_request' ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, 'specific string')
)
To use expression syntax (${{ }}), the final new line should be trimmed. Because expression should end with }}, no trailing white space.
if: >- # Use `>-` or `|-`
${{
github.event_name == 'pull_request' ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, 'specific string')
)
}}
See https://github.com/AllanChain/test-action-if/blob/main/.github/workflows/test-if.yml for the demo.
I'm using Ansible with Jinja2 templates, and this is a scenario that I can't find a solution for in Ansible's documentation or googling around for Jinja2 examples. Here's the logic that I want to achieve in Ansible:
if {{ existing_ansible_var }} == "string1"
new_ansible_var = "a"
else if {{ existing_ansible_var }} == "string2"
new_ansible_var = "b"
<...>
else
new_ansible_var = ""
I could probably do this by combining several techniques, the variable assignment from here: Set variable in jinja, the conditional comparison here: http://jinja.pocoo.org/docs/dev/templates/#if-expression, and the defaulting filter here: https://docs.ansible.com/playbooks_filters.html#defaulting-undefined-variables ,
...but I feel like that's overkill. Is there a simpler way to do this?
If you just want to output a value in your template depending on the value of existing_ansible_var you simply could use a dict and feed it with existing_ansible_var.
{{ {"string1": "a", "string2": "b"}[existing_ansible_var] | default("") }}
You can define a new variable the same way:
{% set new_ansible_var = {"string1": "a", "string2": "b"}[existing_ansible_var] | default("") -%}
In case existing_ansible_var might not necessarily be defined, you need to catch this with a default() which does not exist in your dict:
{"string1": "a", "string2": "b"}[existing_ansible_var | default("this key does not exist in the dict")] | default("")
You as well can define it in the playbook and later then use new_ansible_var in the template:
vars:
myDict:
string1: a
string2: b
new_ansible_var: '{{myDict[existing_ansible_var | default("this key does not exist in the dict")] | default("") }}'
Something like this would work, but it's ugly. And as #podarok mentioned in his answer, it's likely unnecessary depending on exactly what you're attempting to do:
- name: set default
set_fact: new_ansible_var= ""
- name: set to 'a'
set_fact: new_ansible_var= "a"
when: "{{ existing_ansible_var }} == string1"
- name: set to 'b'
set_fact: new_ansible_var= "b"
when: "{{ existing_ansible_var }} == string2"
etc.
you don't need to set var, because I'm guessing that you trying to set var for some condition later.
Just make condition there like
- name: Later task
shell: "command is here"
when: {{ existing_ansible_var }} == "string1"
and get a profit
Based on extra vars parameter I Need to write variable value in ansible playbook
ansible-playbook playbook.yml -e "param1=value1 param2=value2 param3=value3"
If only param1 passed
myvariable: 'param1'
If only param1,param2 passed
myvariable: 'param1,param2'
If param1,param2,param3 are passed then variable value will be
myvariable: 'param1,param2,param3'
When I try to create variable dynamically through template then my playbook always takes previous variable value. But inside dest=roles/myrole/vars/main.yml its writing correct value.
What I make a try here
- hosts: local
user: roop
gather_facts: yes
connection: local
tasks:
- template: src=roles/myrole/templates/myvar.j2 dest=roles/myrole/vars/main.yml
- debug: var=myvariable
roles:
- { role: myrole }
So inside myrole directory I have created template and vars
- roles
- myrole
- vars/main.yml
- templates/myvar.j2
templates/myvar.j2
{% if param1 is defined and param2 is defined and param3 is defined %}
myvariable: 'param1,param2,param3'
{% elif param1 is defined and param2 is defined %}
myvariable: 'param1,param2'
{% elif param1 is defined %}
myvariable: 'param1'
{% else %}
myvariable: 'default-param'
{% endif %}
As I know if only two condition then I can do this using inline expression like below
{{ 'param1,param2' if param1 is defined and param2 is defined else 'default-param' }}
<do something> if <something is true> else <do something else>
Is it possible if - elif - else in inline expression like above. Or any other way to assign value dynamically in ansible playbook?
I am sure there is a smarter way for doing what you want but this should work:
- name : Test var
hosts : all
gather_facts : no
vars:
myvariable : false
tasks:
- name: param1
set_fact:
myvariable: "{{param1}}"
when: param1 is defined
- name: param2
set_fact:
myvariable: "{{ param2 if not myvariable else myvariable + ',' + param2 }}"
when: param2 is defined
- name: param3
set_fact:
myvariable: "{{ param3 if not myvariable else myvariable + ',' + param3 }}"
when: param3 is defined
- name: default
set_fact:
myvariable: "default"
when: not myvariable
- debug:
var=myvariable
Hope that helps. I am not sure if you can construct variables dynamically and do this in an iterator. But you could also write a small python code or any other language and plug it into ansible
my_var: the variable declared
VAR: the variable, whose value is to be checked
param_1, param_2: values of the variable VAR
value_1, value_2, value_3: the values to be assigned to my_var according to the values of my_var
my_var: "{{ 'value_1' if VAR == 'param_1' else 'value_2' if VAR == 'param_2' else 'value_3' }}"
I would first suggest that you step back and look at organizing your plays to not require such complexity, but if you really really do, use the following:
vars:
myvariable: "{{[param1|default(''), param2|default(''), param3|default('')]|join(',')}}"