Check file exists and create a symlink - salt-stack

I want to do something like that:
if file A exists or there is no symlink B, I want to create a symlink B -> A.
For now I have:
B:
file:
- symlink:
- target: A
- exists:
- name: A
But this is bad it checks not the thing I want. How can I achive this simple thing in salt ?

We can use file.directory_exists
{% if not salt['file.directory_exists' ]('/symlink/path/A') %}
symlink:
file.symlink:
- name: /path/to/A
- target: /symlink/path/A
{% endif %}

You should use Dan Garthwaite's excellent answer here as a basis for how to check for the existence of a file. I have modified his solution to answer your question.
{% if 1 == salt['cmd.retcode']('test -f /path/to/A') %}
/path/to/A:
file.symlink:
- target: /symlink/path/A
{% endif %}

/path/to/symlink/B:
file.symlink:
- target: /path/to/target/A
- onlyif:
- test -f /path/to/target/A # check that the target exists
- test ! -L /path/to/symlink/B # check that B is not a symlink
This will require both conditions to be True for the symlink to be created. Note that -L will also return 1 (False) if the file exists but is not a symlink.
From the docs:
The onlyif requisite specifies that if each command listed in onlyif returns True, then the state is run. If any of the specified commands return False, the state will not run.
NOTE: Under the hood onlyif calls cmd.retcode with python_shell=True. This means the commands referenced by onlyif will be parsed by a shell, so beware of side-effects as this shell will be run with the same privileges as the salt-minion. Also be aware that the boolean value is determined by the shell's concept of True and False, rather than Python's concept of True and False.

Related

How to symlink in saltstack latest version or file

ln -fs /opt/app/$(ls -rt file-*.jar | tail -n1) /opt/app/file.jar
works in bash very well
dir contains
file-1.jar
file-2.jar
file-3.jar
How can I do this in a salt stack state sls formula ?
To achieve this in Saltstack we need roughly two steps:
Get the latest file
Link the file
For the first part, we can use some salt module such as file.find, but I feel the existing logic of using ls -rt is simpler.
So we can use this command to get the latest JAR file into a variable. Then use a Salt state to link the file.
Example:
{% set latest_jar = salt.cmd.run('ls -rt /opt/app/file-*.jar | tail -n1') %}
link-latest-jar:
file.symlink:
- name: /opt/app/file.jar
- target: {{ latest_jar }}
Update:
With newer version of Saltstack we need to use salt.cmd.shell to set the latest_jar variable.
{% set latest_jar = salt['cmd.shell']('ls -rt /opt/app/file-*.jar | tail -n1') %}
link-latest-jar:
file.symlink:
- name: /opt/app/file.jar
- target: {{ latest_jar }}
works for me... thx for help

Recursive function based on 2 conditions

I want to write a recursive function which exits on 2 conditions.
Let's say I want to make a directory and ask the user for input. He may enter something like this:
Valid: /existing-dir1/existing-DIR2/non-existence-dir1/non-existence-dir2
Invalid: /existing-dir1/existing-FILE1/non-existence-dir1/non-existence-dir2
To loop through the filename, I have the function dirname() which take /foo/bar and returns /foo. I also have function exist() to check if a filename exist and isdir() to see if it is a file or directory.
Basically, I need to loop recursively from the end of the filename, ignore non-existence nodes, and check if any node is a file - which is invalid. The recursion ends when one of the 2 conditions happens, whichever comes first:
A file is found
dirname() returns /
I am not familiar with recursion, and 2 conditions is a bit too much for me. I am using POSIX script but code samples in C++ / Java / C# are all good.
Edit: I know I can do a mkdir -p and get its status code, but it will create the directory. Nontheless, I want to do that in recursion for the purpose of learning.
In JS, you might write the recursion like this:
const isValid = (path) =>
path === '/'
? 'valid'
: exist(path) && !isdir(path)
? 'invalid'
: isValid(dirname(path))
You might be able to skip the exist check depending upon how your isdir function works.
This should show the logic, but I'm not sure how to write this in your Posix script environment.
I solved it myself. Written in POSIX script, but it is quite easy to read and port to other languages:
RecursivelyCheckFilename ()
{
if [ -e "$1" ] # if exists
then
if [ -d "$1" ] # if is directory
then
if [ "$1" = "/" ]
then
return 0;
else
RecursivelyCheckFilename "$(dirname -- "$1")";
return $?; # returns the value returned by previous function
fi
else
return 1;
fi
else
RecursivelyCheckFilename "$(dirname -- "$1")";
return $?;
fi
}
There are still a few issues with filename ending with a trailing slash, which I must find a way to deal with. But the code above is how I want it to work.

Ansible Fetch Quote Error

I am trying to retrieve a file from a remote host. However, after looking at some examples on stackoverflow, the bottom two methods result in the follow errors:
- shell: ls -f ubuntu_s*
register: file_name
- fetch: src=/home/ubuntu/{{file_name.stdout_lines}} dest=/home/user
- shell: ls -f ubuntu_s*
register: file_name
- fetch: src={{item}} dest=/home/user
with_items: "{{file_name.stdout_lines}}"
Error:
ERROR! this task 'fetch' has extra params, which is only allowed in the following modules: command, shell, script, include, include_vars, add_host, group_by, set_fact, raw, meta
The error appears to have been in '/home/user/BuildPkg.yml': line 49, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
register: file_name
- fetch: src=/home/ubuntu/{{file_name.stdout_lines}} dest=/home/user
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
The error appears to have been in '/home/user/BuildPkg.yml': line 49, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
register: file_name
- fetch: src=/home/ubuntu/{{file_name.stdout_lines}} dest=/home/user
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
Both approaches give the same error. What seems to be wrong?
Try to avoid shell where possible. Modules – this is the Ansible way.
If you need to get a list of files from remote host and fetch them:
- find:
pattern: ubuntu_s*
path: /home/ubuntu/
register: myfiles
- fetch:
src: "{{ item.path }}"
dest: /home/user
flat: yes
with_items: "{{ myfiles.files }}"
The correct way is looping over file globs, like:
- fetch: src={{ item }} dest=/home/user
with_fileglob:
- ubuntu_s*
Note: know what you may face whenever trying to parse ls output

Reload aliases on change automatically without closing the shell window

I have my aliases stored in ~/.zsh_aliases and sourced in ~/.zshrc:
# Access custom aliases in the shell
[ -e "${HOME}/.zsh_aliases" ] && source "${HOME}/.zsh_aliases"
However, when changing the name of an alias, I have to always close the current shell window and open a new one for the change to become active.
Can Zsh automatically reload aliases on change to make them available without having to close the shell window?
You do not actually need to close and reopen your terminal for that, just running source ~/.zsh_aliases (loads the new and changed aliases) or maybe exec zsh (replaces the current shell with a new one) would work, too.
If you really want to re-source ~/.zsh_aliases whenever it is modified, I would suggest adding the following to your ~/.zshrc:
# File containing aliases;
ALIAS_FILE="${HOME}/.zsh_aliases
reload_aliases () {
# do nothing if there is no $ALIAS_FILE
[[ -e ALIAS_FILE ]] || return 1
# check if $ALIAS_FILE has been modified since last reload
# the modifier `(:A)` resolves any symbolic links
if [[ $LAST_ALIAS_RELOAD < $(stat -c %Y ${ALIAS_FILE}(:A)) ]]; then
# remove all aliases; optional!
# only do this if all of your aliases are defined in $ALIAS_FILE
# also affects aliases defined on the command line
unalias -m '*'
# load aliases
source $ALIAS_FILE
# update date of last reload
LAST_ALIAS_RELOAD=$(date +%s)
fi
}
# make reload_aliases to be run before each prompt
autoload -Uz add-zsh-hook
add-zsh-hook precmd reload_aliases
Note, that any changes will only be available on a new prompt. That means, if you modify ~/.zsh_aliases, you need to press at least Enter once in the all terminals for the changes to take effect.
I use an alias, thusly: -
alias vialias='vi ~/.oh-my-zsh/custom/alias.zsh ; source ~/.oh-my-zsh/custom/alias.zsh'
When I run vialias, I edit my aliases, then when I leave vi, the change(s) take effect.
To simplify the accepted answer add:
source ~/.zsh_aliases
in the ~/.zshrc below the plugins section.
Then add an alias inside the ~/.zsh_aliases like so:
alias f="exec zsh"
To refresh zsh & aliases type f

How to make zsh search configuration in $XDG_CONFIG_HOME

Looking to make my ~ a cleaner place, I would like to move as much user configuration files into $XDG_CONFIG_HOME, which is ~/.config by default. So I would like to store all my zsh user files in $XDG_CONFIG_HOME/zsh/. So far already have this:
% ls $XDG_CONFIG_HOME/zsh/
histfile zsh_cache zshrc
Easy, you just have to fill your ~/.zshrc. Now the trickiest part seems to make zsh read directly $XDG_CONFIG_HOME/zsh/zshrc without sourcing it from ~/.zshrc. How would you proceed?
One may edit /etc/zsh/zshenv to set $XDG_CONFIG_HOME directories and $ZDOTDIR. This require write privilegies on this files though.
So provided that $HOME is defined when zsh read it (I don't know if it's the case), you may add to your /etc/zsh/zshenv:
if [[ -z "$XDG_CONFIG_HOME" ]]
then
export XDG_CONFIG_HOME="$HOME/.config/"
fi
if [[ -d "$XDG_CONFIG_HOME/zsh" ]]
then
export ZDOTDIR="$XDG_CONFIG_HOME/zsh/"
fi
It is good practice to not put a / at the end of any variable holding a certain path.
For example, $XDG_CONFIG_HOME/zsh translates to "$HOME/.config//zsh" and the / repeats because XDG_CONFIG_HOME ends with a /.
So I think your answer should be -
if [[ -z "$XDG_CONFIG_HOME" ]]
then
export XDG_CONFIG_HOME="$HOME/.config"
fi
if [[ -d "$XDG_CONFIG_HOME/zsh" ]]
then
export ZDOTDIR="$XDG_CONFIG_HOME/zsh"
fi
Variation to psychoslave's answer which uses ${HOME}/.zshenv to initiate the environment. No root access needed.
export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:=${HOME}/.config}
export ZDOTDIR=${ZDOTDIR:=${XDG_CONFIG_HOME}/zsh}
source $ZDOTDIR/.zshenv
This was discussed on this thread on the zsh-users mailing list.
You may want to consider saving history in XDG_DATA_HOME. Specifications can be found at XDG Base Directory Specification.
Write a wrapper for zsh that executes zsh after setting the environment variable ZDOTDIR to where you want zsh to look for the config files.
See: http://zsh.sourceforge.net/Intro/intro_3.html

Resources