Pattern Matching for rules parameter Gitlab CI - regex

I am trying to make use of the rules: parameter to make a job only appear in a pipeline if specified users did the push. I dont want to define the list of users for each job, so I have the following global variable:
variables:
USER_LIST: "user1 user2 user3"
and in the job, I have the following:
rules:
- if '$USER_LIST =~ /$GITLAB_USER_LOGIN/'
when: on_success
- when: never
This does not appear to be working, as I suspect the regex pattern being used is not being replaced by the variable, and using $GITLAB_USER_LOGIN as the search string. If I use an explicit search:
rules:
- if '$USER_LIST =~ /user1/'
when: on_success
- when: never
then the pattern matches just fine.
NOTE: I am aware that GITLAB_USER_LOGIN is a protected variable. I get the same problem with GITLAB_USER_EMAIL too.
So the question is, how can I put a GITLAB predifined variable into a string that will be used for pattern matching?

You are using a variable and not a regular expression in your rule. So you don't need the slashes around the $GITLAB_USER_LOGIN variable. Try something like this:
rules:
- if '$USER_LIST =~ $GITLAB_USER_LOGIN'

I have more or less the same problem setup. The issues mentioned in the comments were addressed in 15.1, see https://docs.gitlab.com/ee/ci/jobs/job_control.html#store-the-regex-pattern-in-a-variable.
Nevertheless '$USER_LIST =~ /$GITLAB_USER_LOGIN/' still doesn't work. Also, if you define a variable like pattern: /$GITLAB_USER_LOGIN/ and use that in the way described in the documentation, it doesn't work.
'$USER_LIST =~ $pattern'
What works, but isn't the solution to your or my problem is defining pattern with a fixed pattern, like pattern: /user1/. Then it works if used like this:
'$USER_LIST =~ $pattern'
But obviously, I want to use the actual user, that tries to create a pipeline.
What works on my instance (15.4) is this:
'$GITLAB_USER_LOGIN =~ $USER_LIST'
I have no idea why, cos in my opinion this is no behaviour suggested by the documentation. I looked around in several places and found no documented examples of this use, but it works.
This job gets created with a delayed start:
variables:
pattern: "user"
string: "this contains user and other stuff"
show-env:
stage: env
tags:
- ops
rules:
- if: $pattern =~ $string
when: delayed
start_in: 15m
- when: always
script:
- env
And if you remove user from string and push it again, it gets executed right away.
And it also works when I define USERLIST in the project ci/cd variables and test that against $GITLAB_USER_LOGIN.

The documentation shows that the content matching regex should look like this:
$VARIABLE =~ /^content.*/
for your code you need to add ^ and .*:
rules:
- if '$USER_LIST =~ /^user1.*/'
when: on_success
- when: never

My workaround is to build an dynamic-child-pipeline where the variable within pattern already evaluated and inserted as constant string.

Related

Regular expression help to get the opposite in a gitlab CI pipeline

I have below regular expression that check whether we do not have --deploy in a commit message
deploy_review:
<<: *deploy_review_base
except:
refs:
- tags
- master
variables:
- $CI_COMMIT_BRANCH != "review" && $CI_COMMIT_MESSAGE !~ /^.*--deploy/
Now I want to check the opposite of this, that is I want to check where this string --deploy is present in a commit message. Opposite of above expression. is there a way to achieve this ?
Appreciate any help on this
Thanks,
To invert the matching logic, just use =~ instead of !~.
Though your current logic (using !~) checks that the regex pattern does not match. =~ is used to check if the regex pattern does match. You should double check your regex pattern works as expected.

How to use regex in gitlab-ci if rules?

I would be very happy if someone could help me with the following issue.
I would like to run specific scripts only for tags starting with a given tag name.
The following rule works well for the tag 'wind-index' but what I need is a regex as I would like it to work also for tags such as 'wind-index_0.1'
rules:
- if: $CI_COMMIT_TAG == "wind-index"
when: always
I was expecting this to work but without success....
rules:
- if: $CI_COMMIT_TAG == \^wind-index\
when: always
I've tried all possible combinations with simple quotes, double quotes or \^wind-index.*\, none are working.
Any suggestion, help is more than welcome :-)
You need
/ as regex delimiters
== is equality comparison operator, you need a regex matching operator, =~.
You can use
rules:
- if: $CI_COMMIT_TAG =~ /^wind-index/
when: always
That should do the trick:
- if: $CI_COMMIT_TAG =~ /^wind-index/

Ansible: Insert word in GRUB cmdline

I'd like to use Ansible's lineinfile or replace module in order to add the word splash to the cmdline in GRUB.
It should work for all the following examples:
Example 1:
Before: GRUB_CMDLINE_DEFAULT=""
After: GRUB_CMDLINE_DEFAULT="splash"
Example 2:
Before: GRUB_CMDLINE_DEFAULT="quiet"
After: GRUB_CMDLINE_DEFAULT="quiet splash"
Example 3:
Before: GRUB_CMDLINE_DEFAULT="quiet nomodeset"
After: GRUB_CMDLINE_DEFAULT="quiet nomodeset splash"
The post Ansible: insert a single word on an existing line in a file explained well how this could be done without quotes. However, I can't get it to insert the word within the quotes.
What is the required entry in the Ansible role or playbook in order to add the word splash to the cmdline as shown?
You can do this without a shell output, with 2 lineinfiles modules.
In your example you're searching for splash:
- name: check if splash is configured in the boot command
lineinfile:
backup: true
path: /etc/default/grub
regexp: '^GRUB_CMDLINE_LINUX=".*splash'
state: absent
check_mode: true
register: grub_cmdline_check
changed_when: false
- name: insert splash if missing
lineinfile:
backrefs: true
path: /etc/default/grub
regexp: "^(GRUB_CMDLINE_LINUX=\".*)\"$"
line: '\1 splash"'
when: grub_cmdline_check.found == 0
notify: update grub
The trick is to try to remove the line if we can find splash somewhere, but doing a check only check_mode: true. If the term was found (found > 0) then we don't need to update the line. If it's not found, it means we need to insert it. We append it at the end with the backrefs.
Inspired by Adam's answer, I use this one to enable IOMMU:
- name: Enable IOMMU
ansible.builtin.lineinfile:
path: /etc/default/grub
regexp: '^GRUB_CMDLINE_LINUX_DEFAULT="((:?(?!intel_iommu=on).)*?)"$'
line: 'GRUB_CMDLINE_LINUX_DEFAULT="\1 intel_iommu=on"'
backup: true
backrefs: true
notify: update-grub
Please note I've had to set backrefs to true in order to \1 reference to work otherwise the captured group was not replaced.
Idempotency works fine as well.
EDIT: Please note this snippet only works with an Intel CPU and might to be updated to fit your platform.
A possible solution is the definition of two entries as follows:
- name: "Checking GRUB cmdline"
shell: "grep 'GRUB_CMDLINE_LINUX_DEFAULT=.*splash.*' /etc/default/grub"
register: grub_cfg_grep
changed_when: false
failed_when: false
- name: "Configuring GRUB cmdline"
replace:
path: '/etc/default/grub'
regexp: '^GRUB_CMDLINE_LINUX_DEFAULT="((\w.?)*)"$'
replace: 'GRUB_CMDLINE_LINUX_DEFAULT="\1 splash"'
when: '"splash" not in grub_cfg_grep'
Explanation: We first check if the splash keyword is present in the required line using grep. Since grep gives a negative return code when a string is not found, we suppress the errors using failed_when: false. The output of grep is saved to the grub_cfg_grep variable.
Next, we bind the replace module to the condition that the keyword splash is in the standard output of grep. The regular expression takes the old content in the quotes and adds the splash keyword behind it.
Note: In the case of an empty string before the execution, the result reads " splash" (with a space in front) but it is still a valid cmdline.
The difficulty is this line in the replace module page: "It is up to the user to maintain idempotence by ensuring that the same pattern would never match any replacements made."https://docs.ansible.com/ansible/latest/modules/replace_module.html#id4 It's easy to insert the item but actually quite tricky to make it idempotent, so the target file doesn't grow every time you run the task.
I found a way to do it in one shot with the replace module. You should be able to adapt this. My task checks the GRUB_CMDLINE_LINUX_DEFAULT line for "vt.default_red" and inserts some colour codes if not found.
My method was to copy-and-paste various nearly-there examples into the regex tester website and fiddle until it worked. I still don't grok the result, but it worked in my tests at https://www.regextester.com/ and it works in my playbook.
One problem I had was that Ansible's regex implementation apparently doesn't support conditionals, which gave me odd errors for a while.
- name: colours | configured grub command
replace:
path: /etc/default/grub
regexp: '^GRUB_CMDLINE_LINUX_DEFAULT="((:?(?!vt\.default_red).)*?)"$'
replace: 'GRUB_CMDLINE_LINUX_DEFAULT="\1 vt.default_red=0xee,..."'
The regex matches the literal string ("GRUB_CMDLINE_LINUX_DEFAULT=" and a double quote mark) at the start and the double quote mark at the end. Deconstructing the rest...
( - open capture group #1 (creates backref #1)
(:? - open a non-capturing group (not sure what the question mark is here)
(?! - negative lookahead (ie. don't match if the following string comes next)
vt\.default_red - the string to look for, literal dot is escaped
) - close negative lookahead
.) - match a single char (why?) and close the non-capturing group
* - try to match the non-capturing group zero or more times
? - ... lazily (ie. get the smallest possible match)
) - close capture group #1
What about doing this in Ansible, use perl to address your need.
- name: Change items in the file
ansible.builtin.command:
command: perl -i pe 's/DEFAULT="/DEFAULT="splash"/'
Another way of looking at it. This is an old conversation, but it is still relevant.

Complex named match group RegEx review

From this example string
$logLine = '{header[3]}_Pragmatic Praxis Initialization Log'
I am trying to extract three pieces of data
header as type
3 as an (optional) tab value
everything after that _ as a string
What I have now is
$logLine = '{header[3]}_Pragmatic Praxis Initialization Log'
if ($logLine -match '^\{(?<type>[a-z]+)(?:\[?(?<tab>\d?)\]?)\}_(?<string>.+)$') {
Write-Host "$($matches['type'])"
Write-Host "$($matches['tab'])"
Write-Host "$($matches['string'])"
}
And it's working well. But I am so unskilled in RegEx, and this is by far the most complex RegEx I have ever cobbled together from scratch, that I am wondering if anyone sees a gotcha in this approach that I am not seeing?
Or do I need to open some wine and celebrate reaching some sort of RegEx comprehension milestone?
EDIT:
So my success made me over confident. I decided to make Tab required, but add an optional Target which can be either 'console' or 'file'. So I did this
$logLine = '{header[3]}_Pragmatic Praxis Initialization Log'
if ($logLine -match '^\{(?<type>[a-z]+)(?:-(?<target>(console|file)))\[(?<tab>\d*)\]\}_(?<string>.+)$') {
Write-Host "$($matches['type'])"
Write-Host "$($matches['target'])"
Write-Host "$($matches['tab'])"
Write-Host "$($matches['string'])"
}
Which works a treat when target is present, but fails when it is not. So, looks like I get to learn something, rather than celebrate. ;)
EDIT #2:
Per #Ansgar Wiechers, I was indeed misunderstanding (?:...), specifically confusing it for (....)?. based on that, this is my revised pattern, which seems to be doing what I want. I may still make both target and tab required, since I think it makes the code more readable while also simplifying the RegEx pattern, but still good to have it working as I initially intended it to work too.
if ($logLine -match '^\{(?<type>[a-z]+)(-(?<target>(console|file)))?(\[(?<tab>\d+)\])?\}_(?<string>.+)') {
Write-Host "$($matches['type'])"
Write-Host "$($matches['target'])"
Write-Host "$($matches['tab'])"
Write-Host "$($matches['string'])"
}
Looks to me like you're misunderstanding what (?:...) does. That construct does not define an optional match, but a non-capturing group. A (sub)expression (?:-(?<target>console|file)) will require the string to contain either -console or -file and return console or file (without the leading hyphen) as a named match "target". To make the group optional you need to add another ? after the group.
^\{(?<type>[a-z]+)(?:-(?<target>console|file))?\[(?<tab>\d*)\]\}_(?<string>.+)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
Note that a trailing expression .+ or .* makes anchoring the expression at the end of the string ($) pointless, so just remove the $ from the end of your expression.
You also don't need the nested (unnamed) capturing group around console|file. The named capturing group is sufficient.

Regex help - separating controller and action

I need to separate the following url:
/myapp/public/controller/action
as $1 will be controller and $2 will be the action.
Here is the REGEX I´m using:
^([a-zA-Z0-9\/\-_]+)\.?([a-zA-Z\-_]+)?$
For some reason It is not separating, but putting the whole result in $1:
$1 = /myapp/public/controller/action
$2 = '' (empty)
PS: action is optional, as I may have /myapp/public/controller. In that case $2 shall be empty.
[EDIT]
The URL string may have the following formats:
/myapp/public/controller
/myapp/public/controller/action
/myapp/public/controller/action/param1
/myapp/public/controller/action/param1/param2/paramN
$1 shall contain always the controller with full path
$2 will receive the remaining (action, action/param1, action/param1/param2/paramN)
The controller will be always myapp/public/controller, where myapp/public is static and controller is the controller name that needs to go to $1 (the 3rd string).
At the extreme we can call /myapp/public and will be sending empty '' controller that will default to index on the application.
PS: Sometimes things that seens simple are exactly the other way.... Thanks for the questions...
^(.+)\/([^\/]+)$
See it in action
With the new requirements:
^((?:\/[^\/]+){2,3})((?:\/[^\/]+)*)$
See it in action
Explaination:
(?:\/[^\/]+) - matches a forward slash followed by characters, which are not forward slashes (like /myapp, /public, /controller, /action and so on)
{2,3} - the controller consists of the first two or three such sequences. Two in the case when you are using the default index of the application.
* - the remaining such sequences are part of the action