Make sure that jjb-cleanup.sh allows unbound vars
[releng/global-jjb.git] / docs / best-practices.rst
1 .. _global-jjb-best-practices:
2
3 ##############
4 Best Practices
5 ##############
6
7 JJB YAML Layout
8 ===============
9
10 .. note::
11
12     While some of this applies to the Global JJB project other recommendations
13     are generally useful to projects that might be defining JJB templates.
14
15 The Global JJB project is a useful example project to look at so we recommend
16 referring to the Maven job definitions as an example as you read the
17 documentation below:
18
19 https://github.com/lfit/releng-global-jjb/blob/master/jjb/lf-maven-jobs.yaml
20
21 We recommend sectioning off the template into 3 general sections in order:
22
23 1. Job Groups (optional)
24 2. Common Functions
25 3. Job Template Definitions
26
27 In section 1) not all configurations need this so is an optional section. Job
28 groups are useful in cases where there are jobs that are generally useful
29 together. For example the OpenDaylight uses a lot of Merge and Verify job
30 combinations so every new project will want both job types defined in their
31 project.
32
33 In section 2) we want to define all common functions (anchors, aliases, macros)
34 that are generally useful to all jobs in the file. This allows job template
35 developers to look at the top of the file to see if there are useful functions
36 already defined that they can reuse.
37
38 In section 3) we can declare our job definitions. In the Global JJB project we
39 create Gerrit and GitHub versions of the jobs so the format we use here
40 might look strange at first but is well layed out for code reuse if we need to
41 define 2 or more versions of the same job template for different systems. We
42 will define this in more detail in the next section.
43
44 Job Template Layout
45 -------------------
46
47 1. Comment of Job Template Name
48 2. Macro containing build definition of the job
49    a. Macro named after job
50    b. Complete documentation of the job parameters
51    c. Default parameters defined by the job
52    d. Job configuration
53 3. job-template definition containing build triggers
54
55 In section 1) we need to declare a in large comment text to identify the job
56 section.
57
58 In section 2) we declare the actual job definition. This is so that we have a
59 single macro that we call in all the real job-template sections that is
60 reusable and not duplicating any code. First we declare the macro as the job
61 name. Then in 2.b) we provide the complete documentation of the job parameters
62 this is so that we can link users of the job to this file and they can
63 understand fully what options they can configure for this particular job.
64 Then we define defaults for any parameters that are optional. The last section
65 we define the job configuration which completes the macro.
66
67 In section 3) we declare the actual job-template. Because of all the
68 preparations above job template definitions should be small and simple. It
69 needs to define the scm and job triggers. The Global JJB project needs to
70 support both Gerrit and GitHub versions of the same job so the job definitions
71 there have 2 templates for each job defined.
72
73
74 Passing parameters to shell scripts
75 ===================================
76
77 There are 2 ways to pass parameters into scripts:
78
79 1. JJB variables in the format {var}
80 2. Environment variables in the format ${VAR}
81
82 We recommend avoiding using method 1 (Pass JJB variables) into shell scripts
83 and instead always use method 2 (Environment variables). This makes
84 troubleshooting JJB errors easier and does not require escaping curly braces.
85
86 This method requires 3 steps:
87
88 1. Declare a parameter section or inject the variable as properties-content.
89 2. Invoke the shell script with `include-raw-escape` instead of `include-raw`.
90 3. Use the shell variable in shell script.
91
92
93 The benefit of this method is that parameters will always be at the top
94 of the job page and when clicking the Build with Parameters button in Jenkins
95 we can see the parameters before running the job. We can review the
96 parameters retro-actively by visiting the job parameters page
97 ``job/lastSuccessfulBuild/parameters/``. Injecting variables as
98 properties-content makes the variable local to the specific macro, while
99 declaring it as parameter makes the variable global.
100
101 .. note::
102
103     When a macro which invokes a shell script has no JJB parameters defined
104     `!include-raw-escape` will insert extra curly braces, in such cases its
105     recommended to use `!include-raw`.
106
107 .. _shell-scripts:
108
109 Shell scripts
110 =============
111
112 When developing shell scripts for JJB we recommend to create shell scripts as
113 a separate file instead of inlining in YAML. This way we can ensure that the
114 ShellCheck linter can catch potential issues with the scripts.
115
116 When writing the script itself, we recommend to redeclare all expected
117 inputs at the top of the file using lowercase variable names before setting
118 ``set -u`` after the inputs section. This ensures that all variables the
119 script expects are at the top of the file which is useful for others to review
120 and debug the script at a later stage. The ``set -u`` configuration before the
121 start of the script code ensures that we catch any of these undeclared
122 variables at the top of the file.
123
124 Example:
125
126 .. code-block:: bash
127
128    #!/bin/bash
129
130    # Inputs
131    tox_dir="${TOX_DIR:-$WORKSPACE}"
132    tox_envs="${TOX_ENVS:-}"
133
134    # Script start
135    set -eux -o pipefail
136
137    # ... script code goes here
138
139 Usage of config-file-provider
140 =============================
141
142 When using the config-file-provider plugin in Jenkins to provide a config file.
143 We recommend using a macro so that we can configure the builder to
144 remove the config file as a last step. This ensures
145 that credentials do not exist on the system for longer than it needs to.
146
147 ship-logs example:
148
149 .. code-block:: yaml
150
151     - builder:
152         name: lf-ship-logs
153         builders:
154           - config-file-provider:
155               files:
156                 - file-id: jenkins-log-archives-settings
157                   variable: SETTINGS_FILE
158           - shell: !include-raw:
159               - ../shell/logs-get-credentials.sh
160           - shell: !include-raw:
161               - ../shell/lftools-install.sh
162               - ../shell/logs-deploy.sh
163           - shell: !include-raw:
164               - ../shell/logs-clear-credentials.sh
165           - description-setter:
166               regexp: '^Build logs: .*'
167
168 In this example the script logs-deploy requires a config file to authenticate
169 with Nexus to push logs up. We declare a macro here so that we can ensure that
170 we remove credentials from the system after the scripts
171 complete running via the logs-clear-credentials.sh script. This script contains
172 3 basic steps:
173
174 1. Provide credentials via config-file-provider
175 2. Run the build scripts in this case lftools-install.sh and logs-deploy.sh
176 3. Remove credentials provided by config-file-provider
177
178 .. _preserve-variable-refs:
179
180 Preserving Objects in Variable References
181 =========================================
182
183 JJB has an option to preserve a data structure object when you want to pass
184 it to a template.
185 https://docs.openstack.org/infra/jenkins-job-builder/definition.html#variable-references
186
187 One thing that is not explicitly covered is the format of the variable name
188 that you pass the object to. When you use the `{obj:key}` notation to preserve
189 the original data structure object, it will not work if the variable name has a
190 dash `-` in it. The standard that we follow, and recommend, is to use an underscore
191 `_` instead of a dash.
192
193 Example:
194
195 .. code-block:: yaml
196
197    .. literalinclude:: _static/github-pr-trigger.example
198
199 In the above example note the use of underscores in ``github_pr_whitelist``,
200 ``github_pr_admin_list``, and ``github_included_regions``.
201
202 Using single quotes around variables
203 ====================================
204
205 Its recommended to use single quotes around JJB variables '{variable}-field'
206 during variable substitution or when using a variable in a string field, in
207 other cases its recommended to drop the single quotes.
208
209 Example:
210
211 .. code-block:: yaml
212
213     - builder:
214         name: lf-user-logs
215         builders:
216           - inject:
217               properties-content: |
218                   'HOME={user-home}'
219           - build-file:
220               settings: '{settings-file}'
221               file-version: '{file-version}'
222
223
224 Variable expansion and Defaults
225 ===============================
226
227 .. This documentation uses work originally provided by Thanh Ha on
228 .. the OpenDaylight dev mailing list.
229 .. https://lists.opendaylight.org/pipermail/dev/2017-October/004184.html
230
231 JJB has a concept called :ref:`Defaults <defaults>` which is what JJB will
232 replace a variable with if unset. Variables can configure dynamic content
233 in :ref:`job-template <job-template>` sections and allow certain options in
234 these sections to be configurable.
235
236 The section that expands Defaults is :ref:`Job Templates <job-template>` no
237 other sections will expand a default. This documentation will explain how
238 variables and defaults expansion works and which take precedence in JJB's
239 variable expansion logic for the following configuration sections.
240
241 - macro
242 - job-template
243 - project
244 - default
245
246 Macro sections
247 --------------
248
249 :ref:`Macro <macro>` sections can contain variables but do **NOT** support
250 default values getting filled in both at the macro definition level and at the
251 defaults configuration level. :ref:`Macros <macro>` and
252 :ref:`Job Templates <job-template>` can use Macros but any variables defined in
253 a Macro needs to pass a value or a new variable redefined in the
254 :ref:`Job Template <job-template>` if you want to pass on the configuration.
255
256 So for example if you have a macro that has a '{msg}' variable:
257
258 Example:
259
260 .. code-block:: yaml
261
262    - builder:
263        name: echo-msg
264        builders:
265          - shell: "echo {msg}"
266
267 Any downstream job-templates or macros that use this macro **MUST** pass in a
268 `msg: Hello` definition or redefine the msg variable `msg: {msg}`.
269
270
271 Job Template sections
272 ---------------------
273
274 :ref:`Job Template <job-template>` sections can use defaults in two ways.
275
276 1. Configure the message:
277
278    .. code-block:: yaml
279
280       - job-template:
281           name: echo-hello-world
282           builders:
283             - echo-msg:
284                 msg: 'Hello World'
285
286 2) Re-define '{msg}' variable
287
288    .. code-block:: yaml
289
290       - job-template:
291           name: echo-message
292           builders:
293             - echo-msg:
294                 msg: '{message}'
295
296 In option 2, we redefine the variable msg as `{message}` which a user of the
297 job-template can now pass into the job their own custom message which is
298 different than option 1, where we set a static message to pass in. We purposely
299 redefined the **{msg}** to **{message}** here to show that you do not need to
300 redefine it with the same name but we could have used the same name `{msg}` in
301 the template too if we wanted to keep it the same.
302
303 Job Templates can also default a default variable for the variables it defines.
304
305 Example:
306
307 .. code-block:: yaml
308
309     - job-template:
310       name: echo-message
311       message: 'Hello World'
312       builders:
313         - echo-msg:
314             msg: '{message}'
315
316 This creates a job template variable called '{message}' which will default to
317 "Hello World" if the user of the template does not explicitly pass in a message.
318
319 We should be aware of 2 Defaults concepts:
320
321 1. Default as defined in the :ref:`job-template <job-template>`
322 2. Default as defined in a :ref:`defaults <defaults>` configuration
323    (typically defaults.yaml)
324
325 In this case there is a default '{message}' set in the
326 :ref:`job-template <job-template>`. JJB will use this default if the user
327 (project section) does not declare a {message}.
328
329 If we do not declare a default in the :ref:`job-template <job-template>` then
330 JJB will fallback to checking the "defaults configuration".
331
332 This means that the precendence of defaults is as follows:
333
334 1. User-provided
335 2. Job Template
336 3. Defaults.yaml
337
338 Project sections
339 ----------------
340
341 :ref:`Project <project>` sections define real jobs and pass in variables as
342 necessary. Projects sections do NOT expand defaults.yaml. So you cannot
343 configure a setting with {var} in here and expect defaults.yaml to fill it in
344 for you. Define required configuration here.
345
346 Example:
347
348 .. code-block:: yaml
349
350    - project
351        name: foo
352        jobs:
353          - 'echo-message'
354        message: 'I am foo'
355
356
357 Defaults sections
358 -----------------
359
360 :ref:`Defaults <defaults>` sections are the absolute last thing JJB checks if a
361 variable is not configured in a job-template and user did not pass in the
362 variable. JJB will fill in whatever is in the defaults configuration.
363
364 Variable expansion order of precedence seems to be:
365
366 1. job-group section definition
367 2. project section definition
368 3. job-template variable definition
369 4. defaults.yaml variable definition
370
371 .. note:: Defaults set variables in job-templates and are NOT used in Macros.
372
373 global-jjb should not provide job-group definitions and leave it up to users of
374 global-jjb to create their own as a job-group as a variable defined in a job
375 group the highest precendence. Global JJB should strive to be purely a
376 job-template and macro library for downstream consumers.
377
378 Final thoughts
379 --------------
380
381 For any :ref:`Basic Job Configuration <job>` for example "concurrent", "jdk",
382 "node" etc... we cannot set defaults with the same name as JJB will not expand
383 them. To use "node" we need to give the variable for that setting a
384 different name such as "build-node" instead, if we want JJB to perform
385 expansion for those settings. This issue affects top level job configuration,
386 it does not appear to affect items below the top level such as calling a
387 builder, wrapper or parameter.