]> arthur.barton.de Git - ansible-collection-boilerplate.git/blob - bin/ansible-boilerplate
Initial commit
[ansible-collection-boilerplate.git] / bin / ansible-boilerplate
1 #!/bin/sh -e
2 #
3 # Ansible Boilerplate Collection: Setup & Maintenance Script.
4 #
5
6 # Detect commands to use:
7 PYTHON="${PYTHON:-$(command -v python3 2>/dev/null || echo "python3")}"
8 PIP="${PIP:-$(command -v pip 2>/dev/null || echo "pip")}"
9 ANSIBLE_GALAXY="${ANSIBLE_GALAXY:-$(command -v ansible-galaxy 2>/dev/null || echo "ansible-galaxy")}"
10
11 BASE_D="ansible_galaxy/ansible_collections/alexbarton/boilerplate"
12
13 #
14 # Show usage information on stderr.
15 #
16 Usage() {
17         {
18                 echo "$0 <command>"
19                 echo
20                 echo "  help       Show this help text and exit."
21                 echo "  init       Initialize project and boilerplate code."
22                 echo "  upgrade    Upgrade boilerplate code and dependencies. [Alias: update, up]"
23                 echo "    --force  Force overwriting an existing role or collection."
24                 echo
25         } >&2
26 }
27
28 #
29 # Initialize a new project.
30 #
31 # Create some default files and call Upgrade() afterwards. This function does not
32 # overwrite any already existing file.
33 #
34 Init() {
35         if [ $# -ne 0 ]; then
36                 Usage
37                 exit 1
38         fi
39         if [ -e Makefile.boilerplate ]; then
40                 echo "This is the upstream project! Don't call \"init\" on it!" >&2
41                 exit 1
42         fi
43         echo "Initialize project:"
44
45         for file in \
46                 README.md \
47                 LICENSE \
48         ; do
49                 test -e "${file}" || touch "${file}"
50         done
51         mkdir -p .vscode playbooks roles
52         test -e "hosts.ini" || Init_HostsIni
53         test -e "Makefile" || Init_Makefile
54         test -e "requirements.yml" || Init_RequirementsYml
55
56         Upgrade --init
57 }
58
59 #
60 # Create a Makefile template file.
61 #
62 Init_Makefile() {
63         echo "Creating \"Makefile\" ..."
64         cat >Makefile <<EOF
65 #
66 # Makefile
67 #
68
69 SOURCE_ROOT ?= \$(CURDIR)
70
71 # Make sure that the Ansible Boilerplate Collection project is set up (its
72 # files are "available"), and initialize it if not!
73 _DUMMY := \$(shell test -e ansible_galaxy/ansible_collections/alexbarton/boilerplate/Makefile.boilerplate || bin/ansible-boilerplate upgrade >&2)
74
75 default: all
76
77 # Include the Ansible Boilerplate Collection Makefile fragment:
78 include ansible_galaxy/ansible_collections/alexbarton/boilerplate/Makefile.boilerplate
79
80 all:
81
82 check:
83
84 install:
85
86 clean:
87
88 distclean: clean
89
90 maintainer-clean: distclean
91
92 .PHONY: default all check install clean distclean maintainer-clean
93 EOF
94 }
95
96 #
97 # Create a hosts.ini template file.
98 #
99 Init_HostsIni() {
100         echo "Creating \"hosts.ini\" ..."
101         cat >hosts.ini <<EOF
102 # Ansible hosts list
103
104 [all_managed]
105 localhost
106 EOF
107 }
108
109 #
110 # Create a requirements.yml template file.
111 #
112 Init_RequirementsYml() {
113         echo "Creating \"requirements.yml\" ..."
114         cat >requirements.yml <<EOF
115 ---
116 # Ansible dependencies
117
118 collections:
119   - ${BOILERPLATE_COLLECTION_SRC:-alexbarton.boilerplate}
120
121 roles:
122   []
123 EOF
124 }
125
126 #
127 # Upgrade a project.
128 #
129 # - Initialize a Python virtual environment (when a ".venv" directory exists
130 #   or the ansible-galaxy command is not found).
131 # - Install Ansible when ansible-galaxy command is not found.
132 # - Install "ansible-boilerplate" collection when not found.
133 # - Update local "ansible-boilerplate" setup: copy script, create links, ...
134 # - Upgrade template files.
135 # - Install/upgrade Python dependencies (from requirements.txt file).
136 # - Install/upgrade Ansible Galaxy dependencies (from requirements.yml file).
137 #
138 # --force: Passed to "ansible-galaxy install" command.
139 # --init: Upgrade() is called by the Init() function.
140 #
141 Upgrade() {
142         unset do_force
143         while [ $# -gt 0 ]; do
144                 case "$1" in
145                         "--force")
146                                 do_force="--force"
147                                 ;;
148                         "--init")
149                                 is_init="--init"
150                                 ;;
151                         *)
152                                 Usage
153                                 exit 1
154                 esac
155                 shift
156         done
157         [ -z "${is_init}" ] && echo "Upgrade project:"
158
159         # Check Python virtual environment
160         if [ -d .venv ] || ! command -v "${ANSIBLE_GALAXY}" >/dev/null; then
161                 # Either an existing ".venv" folder was found or the
162                 # ansible-galaxy(1) command was not found on the system, so
163                 # let's use a Python virtual environment!
164                 echo "Using a Python virtual environment."
165                 PIP="./.venv/bin/pip"
166                 ANSIBLE_GALAXY="./.venv/bin/ansible-galaxy"
167                 if ! [ -x .venv/bin/pip ]; then
168                         echo "Initializing Python virtual environment ..."
169                         "${PYTHON}" -m venv .venv
170                         "${PIP}" install -U pip setuptools
171                 fi
172         fi
173         for var in PYTHON PIP ANSIBLE_GALAXY; do
174                 eval 'echo " - ${var} is \"$'"${var}"'\"."'
175         done
176
177         if ! [ -x "${ANSIBLE_GALAXY}" ]; then
178                 echo "Installing Ansible ..."
179                 "${PIP}" install -U ansible
180         fi
181
182         # Are we running in a dependent project? If so, perform specific upgrade tasks!
183         # shellcheck disable=SC2086
184         [ -e Makefile.boilerplate ] || Upgrade_Dependent ${is_init}
185
186         if [ -r requirements.txt ]; then
187                 echo "Installing Python dependencies ..."
188                 "${PIP}" install -U -r requirements.txt
189         fi
190
191         if [ -r requirements.yml ]; then
192                 echo "Upgrading Ansible Galaxy dependencies ..."
193                 # shellcheck disable=SC2248
194                 "${ANSIBLE_GALAXY}" collection install -U -r requirements.yml ${do_force}
195                 # shellcheck disable=SC2248
196                 "${ANSIBLE_GALAXY}" role install -r requirements.yml ${do_force}
197         fi
198 }
199
200 #
201 # Upgrade steps for dependent projects only.
202 #
203 # --init: Upgrade() is called by the Init() function.
204 #
205 Upgrade_Dependent() {
206         collection="${BOILERPLATE_COLLECTION_SRC:-alexbarton.boilerplate}"
207         echo "Installing/upgrading \"${collection}\" ..."
208         "${ANSIBLE_GALAXY}" collection install -U -p ansible_galaxy "${collection}"
209
210         echo "Copying \"boilerplate\" script into bin/ directory ..."
211         mkdir -p bin
212         cp -av "${BASE_D}/bin/ansible-boilerplate" "bin/ansible-boilerplate"
213
214         echo "Creating symbolic links to files inside of the Boilerplate Collection ..."
215         for file in \
216                 bin/a \
217                 bin/ap \
218                 bin/aps \
219         ; do
220                 # Create (new) symbolic links, when the target already is a symbolic link or
221                 # does not yet exists. Don't overwrite existing regular files etc.!
222                 test -L "${file}" && ln -fsv "../${BASE_D}/${file}" "${file}"
223                 test -e "${file}" || ln -fsv "../${BASE_D}/${file}" "${file}"
224         done
225
226         echo "Upgrading template files from the Boilerplate Collection ..."
227         for file in \
228                 .ansible-lint \
229                 .editorconfig \
230                 .gitignore \
231                 .vscode/settings.json \
232                 .yamllint.yml \
233                 ansible.cfg \
234                 requirements.txt \
235         ; do
236                 # shellcheck disable=SC2086
237                 Upgrade_Template "${file}" ${is_init}
238         done
239
240         # Verify that the Boilerplate Collection is available now!
241         "${ANSIBLE_GALAXY}" collection verify --offline alexbarton.boilerplate
242 }
243
244 #
245 # Upgrade a template file.
246 #
247 # --init: Initialize a new project, therefore create the template file if it
248 #         does not yet exist.
249 #
250 Upgrade_Template() {
251         # Does the target directory exist? Skip this template file if not!
252         [ -d "$(dirname "$1")" ] || return 0
253
254         # Return when the target file does not exist and not in "init mode":
255         [ ! -e "$1" ] && [ "$2" != "--init" ] && return 0
256
257         # Remove the target when it is a symbolic link.
258         [ -L "$1" ] && rm -v "$1"
259
260         # Do not override the target when it exists already!
261         if [ -e "$1" ]; then
262                 # Target already exists. Is it different?
263                 if ! cmp "$1" "${BASE_D}/$1"; then
264                         # Files are not the same! Install new version in parallel:
265                         install -b -m 0644 -p -v "${BASE_D}/$1" "$1.new"
266                 fi
267         else
268                 # Target does not yet exist:
269                 install -b -m 0644 -p -v "${BASE_D}/$1" "$1"
270         fi
271 }
272
273 cmd="$1"
274 [ $# -gt 0 ] && shift
275
276 case "${cmd}" in
277         "init")
278                 Init "$@"
279                 ;;
280         "upgrade"|"update"|"up")
281                 Upgrade "$@"
282                 ;;
283         "help"|"--help")
284                 Usage
285                 ;;
286         *)
287                 Usage
288                 exit 1
289 esac
290 exit 0