-
Notifications
You must be signed in to change notification settings - Fork 50
/
ansible-completion.bash
183 lines (151 loc) · 5.7 KB
/
ansible-completion.bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/bin/env bash
if [[ -z "$ANSIBLE_COMPLETION_CACHE_TIMEOUT" ]]; then
ANSIBLE_COMPLETION_CACHE_TIMEOUT=120 # sec
fi
_ansible() {
local current_word=${COMP_WORDS[COMP_CWORD]}
local previous_word=${COMP_WORDS[COMP_CWORD - 1]}
if [[ "$previous_word" == "-m" ]] || [[ "$previous_word" == "--module-name" ]]; then
_ansible_complete_option_module_name "$current_word"
elif [[ "$current_word" == -* ]]; then
_ansible_complete_options "$current_word"
else
_ansible_complete_host "$current_word"
fi
}
complete -o default -F _ansible ansible
# Compute completion with the available hosts,
# if a inventory file is specified in
# the command line the completion will use it
_ansible_complete_host() {
local current_word=$1
local first_words=${current_word%:*}
local last_word=${current_word##*:}
local inventory_file=$(_ansible_get_inventory_file)
local grep_opts="-o"
# if $inventory_file is empty and a ansible.cfg file exist
# search in the ansible.cfg for a hostfile entry
if [ -z "$inventory_file" ]; then
[ -f /etc/ansible/ansible.cfg ] && inventory_file=$(awk \
'/^(hostfile[[:space:]]*=[[:space:]]*|inventory[[:space:]]*=[[:space:]]*)/{ print $3 }' /etc/ansible/ansible.cfg)
[ -f ${HOME}/.ansible.cfg ] && inventory_file=$(awk \
'/^(hostfile[[:space:]]*=|inventory[[:space:]]*=[[:space:]]*)/{ print $3 }' ${HOME}/.ansible.cfg)
[ -f ansible.cfg ] && inventory_file=$(awk \
'/^(hostfile[[:space:]]*=[[:space:]]*|inventory[[:space:]]*=[[:space:]]*)/{ print $3 }' ansible.cfg)
fi
inventory_file="${inventory_file/\~\//$HOME/}"
# if the $inventory_file value is a variable (e.g $HOME), we evaluate that
# variable to get the value.
if [[ "$inventory_file" == \$* ]]; then
inventory_file=$(eval echo $inventory_file)
fi
# if inventory_file points to a directory, search recursively
[ -d "$inventory_file" ] && grep_opts="$grep_opts -hR"
local hosts=$(ansible ${inventory_file:+-i "$inventory_file"} all --list-hosts 2> /dev/null \
&& [ -e "$inventory_file" ] \
&& [ -d "$inventory_file" -o ! -x "$inventory_file" ] \
&& grep $grep_opts '\[.*\]' "$inventory_file" | tr -d [] | cut -d: -f1)
# list the hostnames with ansible command line and complete the list
# by searching the group labels in the inventory file (if we have it)
hosts="$hosts
$(echo "$hosts" | sed -e 's/\([^[:space:]]\)/\&\1/p' -e 's/&/!/p' )"
# add the !, & notation to the hostname
if [ "$first_words" != "$last_word" ]; then
COMPREPLY=( $( compgen -P "$first_words:" -W "$hosts" -- "$last_word" ) )
else
COMPREPLY=( $( compgen -W "$hosts" -- "$last_word" ) )
fi
}
# Look inside COMP_WORDS to find a value for the inventory-file argument
# and echo the value (or echo an empty string)
_ansible_get_inventory_file() { # @todo refactor with _ansible_get_module_path
local index=0
for word in ${COMP_WORDS[@]}; do
index=$(expr $index + 1)
if [ "$word" != "${COMP_WORDS[COMP_CWORD]}" ]; then
if [[ "$word" == "-i" ]] || [[ "$word" == "--inventory-file" ]]; then
echo ${COMP_WORDS[$index]}
return 0
fi
fi
done
echo ""
return 1
}
_ansible_get_module_path() { # @todo @see _ansible_get_inventory_file
local index=0
for word in ${COMP_WORDS[@]}; do
index=$(expr $index + 1)
if [ "$word" != "${COMP_WORDS[COMP_CWORD]}" ]; then
if [[ "$word" == "-M" ]] || [[ "$word" == "--module-path" ]]; then
echo ${COMP_WORDS[$index]}
return 0
fi
fi
done
echo ""
return 1
}
# Compute completion for the generics options
_ansible_complete_options() {
local current_word=$1
local options=$( \
ansible --help | \
sed '1,/Options/d' | \
grep -Eoie "--?[a-z-]+" \
)
COMPREPLY=( $( compgen -W "$options" -- "$current_word" ) )
}
_ansible_get_module_list() {
local module_path=$(_ansible_get_module_path)
local hash_module_path=$(_md5 "$module_path")
# /tmp/<pid>.<hash of the module path if exsist>.module-name.ansible.completion
local cache_file=/tmp/${$}.${module_path:+"$hash_module_path".}module-name.ansible.completion
if [ -f "$cache_file" ]; then
local timestamp=$(expr $(_timestamp) - $(_timestamp_last_modified $cache_file))
if [ "$timestamp" -gt "$ANSIBLE_COMPLETION_CACHE_TIMEOUT" ]; then
rm -f $cache_file > /dev/null 2>&1
_generate_module_cache $cache_file $module_path
fi
else
# We need to cache the output because ansible-doc is so fucking slow
_generate_module_cache $cache_file $module_path
fi
echo $(cat $cache_file)
}
_generate_module_cache() {
local cache_file=$1
local module_path=$2
ansible-doc ${module_path:+-M "$module_path"} -l | awk '{print $1}' > $cache_file
}
_ansible_complete_option_module_name() {
local current_word=$1
local module_list=$(_ansible_get_module_list)
COMPREPLY=( $( compgen -W "$module_list" -- "$current_word" ) )
}
_timestamp() {
echo $(date +%s)
}
_timestamp_last_modified() {
local timestamp=''
if [[ "$OSTYPE" == "linux"* ]]; then
# linux
timestamp=$(stat -c "%Z" $1)
else
# freebsd/darwin
timestamp=$(stat -f "%Sm" -t "%s" $1)
fi
echo $timestamp
}
_md5() {
local to_hash=$1
local md5_hash=''
if hash md5 2>/dev/null; then
# freebsd/darwin
md5_hash=$(md5 -q -s "$to_hash")
else
# linux
md5_hash=$(echo "$to_hash" | md5sum |awk {'print $1'})
fi
echo $md5_hash
}