forked from pykickstart/pykickstart
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CONTRIBUTING
411 lines (303 loc) · 17.1 KB
/
CONTRIBUTING
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
Contributing to pykickstart
---------------------------
INTRODUCTION
pykickstart began life on October 5, 2005 on the anaconda team at Red Hat.
That's the team responsible for the installation software used by Fedora Linux,
Red Hat Enterprise Linux, CentOS, and many other distributions. The idea for a
Python module for the kickstart configuration file came from a few key problems
the team was hitting:
* There was no formal specification for the configuration file format.
* The only documentation for kickstart was what was in the Red Hat
installation guides.
* The actual processing of the kickstart file was handled by two code
paths in the installer.
What this led to is a reactive model for handling kickstart bug reports. If a
user felt something in kickstart should be possible, it would get added somehow
to the installer and then have to be supported forever. The team felt it was
necessary to separate out the file format specification and processing to a
separate project in order to establish the syntax and method for updating it.
Oh, and if you have read this far and do not know what kickstart is, go read
this:
https://pykickstart.readthedocs.io/en/latest/kickstart-docs.html
TL;DR -- It's the system that lets you automate otherwise interactive
installations.
Kickstart the Language
~~~~~~~~~~~~~~~~~~~~~~
pykickstart provides a versioned language library for the kickstart file
format. It is possible to process a kickstart file from a specific RHEL or
Fedora release or just use the latest default. This allows the file format to
deprecate commands and syntax over time as well as add new commands that do not
apply to old releases.
The ksvalidator and ksverdiff commands are available to administrators who want
to see what has changed between kickstart revisions.
Kickstart revisions are tied to distribution releases. In the case of RHEL and
CentOS, this refers to the major version number only.
Kickstart the Library
~~~~~~~~~~~~~~~~~~~~~
pykickstart provides a Python module that lets your program parse and use data
from kickstart files. By separating it out from the installer, kickstart file
handling has now found its way in to many other projects.
CONTRIBUTING TO THE LIBRARY
We welcome new features and bug fixes to pykickstart. Please read below for
some of the things you should keep in mind before sending in a contribution.
* File format consistency and comaptibility is important. For this reason,
please think long and hard before proposing a new command or options to an
existing command. Is it very similar to an existing command or is it
something very new? Is it a variation on an existing command? What versions
of Fedora and RHEL does it need to work on?
* Plan to support your new feature for 10+ years. Part of what makes kickstart
successful is the longetivity of the file format.
* Does it really need to be in kickstart? Sometimes proposals are made for now
popular things that do not make it to the 10 year mark. In those cases, it
may be better to make an anaconda addon which will allow you to supplement
kickstart syntax for automation purposes.
With that in mind, here are some rules regarding contributions:
* Please follow PEP8 Python coding standards, but also follow the coding style
of anything already there.
* Do not break long lines just to maintain a strict right column boundary. It
is perfectly ok for these lines to travel far past column 80. (That said,
this file is wrapped. ¯\_(ツ)_/¯)
* Use separate pull requests for formatting fixes and functional changes.
* One thing per pull request. Fixing a bug and changing something else related
to that? Two pull requests, please.
* Absolutely all pull requests must pass the test suite. New features and new
syntax must include new test cases. These test cases must be complete and
your code must pass.
* All pull requests are subject to code review.
This list is not exhaustive either, but tries to capture the main ideas.
EXAMPLES
So what makes a good kickstart addition vs. a bad one? Here are some lists:
Good Contributions
~~~~~~~~~~~~~~~~~~
* The ksvalidator command crashes without an error if you specify an invalid
version on the command line. It would be more helpful to display an error
and maybe tell the user how to list available versions.
This is a good contribution because it is fixing a legitimate bug.
Bad Contributions
~~~~~~~~~~~~~~~~~
* At my company we have a problem with automated installations where the link
doesn't work unless you first have the system sleep for a few seconds after
it gets a DHCP lease. Here's a patch to the 'network' command to add a
--after-dhcp-sleep=NUM option to handle these instances.
This is a bad contribution because nothing about the problem described is
the fault of kickstart. It could be local network configuration, the
hardware, or a problem in operating system network stack. Either way,
the solution described is a workaround for the actual problem and not
something that kickstart needs to capture and provide to programs using
it.
KICKSTART DEPRECATION
Over time commands and arguments change. New ones are added and old ones are
eventually removed. At the time of this writing, version 3.31, a deprecated
command means that it is no longer available to be used, but it can still be in
the kickstart. Its presence will generate a warning. A command that has been
removed will generate an error.
This is different from the usual use of the term 'deprecated', so it can be a
bit confusing. Especially since command options that have been deprecated are
still available, still parsed, and have their results available to the calling
program (eg. Anaconda). A warning will be generated, but there will be no
other effect on behavior.
'Deprecating' a kickstart command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* In pykickstart/commands/ add a new class based on the last version that
was implemented, and add DeprecatedCommand at the start of the class list.
* Override __init__ method and call DeprecatedCommand.__init__(self) instead
of the superclass.
* Override _getParser and extend the description with a ".. deprecated::"
entry. Sphinx will include this in the documentation.
* Change the command in the pykickstart/handlers/ mapping so that it points
to the new class.
* Add a test that makes sure the command can be parsed.
* Add a check to make sure the parser is a subclass of DeprecatedCommand.
Commit 1c89c8ecec865d66984d5b7e25f4133b71d634b2 which deprecates the autostep
command is a good example to follow.
Implement a new class in pykickstart/commands/autostep.py:
class F34_AutoStep(DeprecatedCommand, FC3_AutoStep):
def __init__(self): # pylint: disable=super-init-not-called
DeprecatedCommand.__init__(self)
def _getParser(self):
op = FC3_AutoStep._getParser(self)
op.description += "\n\n.. deprecated:: %s" % versionToLongString(F34)
return op
Change the handler mapping to point to it in pykickstart/handlers/f34.py:
"autostep": commands.autostep.F34_AutoStep,
Add a test in tests/commands/autostep.py
class F34_TestCase(FC3_TestCase):
def runTest(self):
# make sure we've been deprecated
parser = self.getParser("autostep")
self.assertTrue(issubclass(parser.__class__, DeprecatedCommand))
# make sure we are still able to parse it
self.assert_parse("autostep")
Removing a kickstart command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Commands can be removed after users have had sufficient time to adjust their
kickstarts. At least one release cycle should be allowed. When creating the
handler mapping for a new release, eg. F34.py, you can simply not add the
command to the map. Or you can add a new version of the command that subclasses
RemovedCommand. It should append ..versionremoved: to the description with the
version that it is being removed in so that it is included in the
documentation. When it is just removed from the handler the docs do not have
any note about removal. Only ksvalidator and ksverdiff will say anything about
it.
Do not remove any of the code or tests. pykickstart can select any of its
previous releases to process a kickstart file, so the command's code needs to
remain. It will only generate an error if it is used in a kickstart with the
new release.
Deprecating a Command Option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Command option deprecation really does mean what you expect. But it is a bit
more complex to implement. A deprecated option will generate a warning to the
user, and it's state will still be available to Anaconda. Output from
ksflatten or anaconda may change depending on what the change is. By setting
the option parser's deprecated value to a release, eg. F34, Spinx will then
include that information in the documentation so that users know what to
expect.
* In pykickstart/commands/ add a new class based on the last version that
was implemented. Do *not* add DeprecatedCommand to this class.
* Override _getParser and add 'deprecated=F34' to the add_argument call used to
setup the option. This should match the previous call to add_argument, with
the addition of the deprecated release version.
If the add_argument is not the same it can result in unexpected behavior when
parsing the option.
* If the output needs to change, override __str__ and modify it to fit the
situation. Make sure not to lose output of the non-deprecated options.
This may require copy and pasting much of the previous __str__ method.
* Change the command in the pykickstart/handlers/ mapping so that it points
to the new class.
* Add a test that makes sure the non-deprecated options still work, and that
the deprecated option generates a warning. Sometimes this can be done by
calling the previous tests, and other times requires copy and pasting
much of the previous one. It all depends on what changed and what the
expected output is.
Commit 4a0e1e55d4837bc4637dcefb9811d48e7cafa357 which deprecates the logging
--level argument is a good example to follow.
class F34_Logging(FC6_Logging):
def __str__(self):
retval = KickstartCommand.__str__(self)
if self.host:
retval += "# Installation logging level\nlogging --host=%s" % self.host
if self.port:
retval += " --port=%s" % self.port
retval = retval + "\n"
return retval
def _getParser(self):
op = super()._getParser()
op.add_argument("--level", deprecated=F34)
return op
Change the handler mapping to point to it in pykickstart/handlers/f34.py:
"logging": commands.logging.F34_Logging,
Add a test in tests/commands/logging.py
class F34_TestCase(CommandTest):
command = "logging"
def runTest(self):
# deprecated
self.assert_deprecated("logging", "--level")
with self.assertWarns(KickstartDeprecationWarning):
self.assert_parse("logging --level=info", "")
with self.assertWarns(KickstartDeprecationWarning):
self.assert_parse("logging --level=info --host=HOSTNAME", "logging --host=HOSTNAME\n")
with self.assertWarns(KickstartDeprecationWarning):
self.assert_parse("logging --level=info --host=HOSTNAME --port=PORT", "logging --host=HOSTNAME --port=PORT\n")
Depending on the type of tests performed, and the changes to the deprecated
option, you may or may not want to call the superclass's tests. In some cases
you may need to copy and paste them to the new test class.
Deprecating options like this results in a generic warning being printed:
Ignoring deprecated option on line 16: The --level option has been deprecated
and no longer has any effect. It may be removed from future releases, which
will result in a fatal error from kickstart. Please modify your kickstart file
to remove this option.
Custom Option Deprecation Message
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In some situations you may want to direct the user to use a different command
or argument. The current solution for that is to not set the deprecated value
for the option, and instead to do the following:
* In pykickstart/commands/ add a new class, based on the last version that
was implemented. Do *not* add DeprecatedCommand to this class.
* Override the parse method, and call the superclass's parse()
* Add checks to this new parse method and call warnings.warn with the category
set to KickstartDeprecationWarning
* Change the command in the pykickstart/handlers/ mapping so that it points
to the new class.
* Add a test that runs all of the previous tests, and also checks to make
sure that warnings are generated for the deprecated options.
This deprecation method isn't ideal though. It doesn't set the option's
deprecated value, so Sphinx doesn't have any way to note that in the
documentation. Users will only see the warning output from ksvalidator
and in the Anaconda logs.
An example of this method is the deprecation of the timezone --ntpservers and
--nontp options in commit 5f7c0dd57a8b7422bf4fee8c85df1ab82d722f3e
Add a new class in pykickstart/commands/timezone.py
class F33_Timezone(F32_Timezone):
def __init__(self, writePriority=0, *args, **kwargs):
F32_Timezone.__init__(self, writePriority, *args, **kwargs)
self.op = self._getParser()
def parse(self, args):
F32_Timezone.parse(self, args)
if self.ntpservers:
warnings.warn(_("The option --ntpservers will be deprecated in future releases. Please "
"modify your kickstart file to replace this option with "
"timesource --ntp-server <server hostname> command invocation, "
"one per NTP server."),
KickstartDeprecationWarning)
if self.nontp:
warnings.warn(_("The option --nontp will be deprecated in future releases. Please "
"modify your kickstart file to replace this option with "
"timesource --ntp-disable command invocation."),
KickstartDeprecationWarning)
return self
Change the handler mapping to point to it in pykickstart/handlers/f33.py:
"timezone": commands.timezone.F33_Timezone,
Add a test in tests/commands/timezone.py
class F33_TestCase(F32_TestCase):
command = "timezone"
def setUp(self):
super().setUp()
def runTest(self):
F32_TestCase.runTest(self)
# As of Fedora 33 the --ntpservers and --nontp options are considered deprecated.
# Check using --ntpservers returns appropriate deprecation warning
with self.assertWarns(KickstartDeprecationWarning):
self.assert_parse("timezone --ntpservers foo,bar,baz")
# Check using --ntpservers returns appropriate deprecation warning
with self.assertWarns(KickstartDeprecationWarning):
self.assert_parse("timezone --nontp")
Removing a Command Option
~~~~~~~~~~~~~~~~~~~~~~~~~
When an option has spent enough time being deprecated, at least one release
cycle, it can be removed. It will then generate an error when found in the
kickstart.
* In pykickstart/commands/ add a new class, based on the last version that
was implemented. Do *not* add DeprecatedCommand to this class.
* Override _getParser, can call remove_argument() on the argument, passing in
the version it was removed in.
* Change the command in the pykickstart/handlers/ mapping so that it points
to the new class.
* Add a new test to make sure that the option had been removed
* Adjust other test cases as needed. The presence of the option can be checked
in self.optionList to distinguish between deprecation and removal.
Commit dc808179dd403b109bac112a2c35305ba396ce6b which removes the bootloader
--upgrade option is a good example of this:
Add a new class in pykickstart/commands/bootloader.py
class F34_Bootloader(F29_Bootloader):
removedKeywords = F29_Bootloader.removedKeywords
removedAttrs = F29_Bootloader.removedAttrs
def _getParser(self):
op = F29_Bootloader._getParser(self)
op.remove_argument("--upgrade", version=F34)
return op
Change the handler mapping to point to it in pykickstart/handlers/f34.py:
"bootloader": commands.bootloader.F34_Bootloader,
Adjust the existing tests in tests/commands/bootloader.py to account for
--upgrade possibly being removed:
if "--upgrade" in self.optionList:
self.assert_parse("bootloader --upgrade", "bootloader %s--location=mbr --upgrade\n" % linear)
if "--upgrade" in self.optionList:
self.assert_deprecated("bootloader", "--upgrade")
# deprecated options should also raise a deprecation warning - test that somewhere
with self.assertWarns(KickstartDeprecationWarning):
self.assert_parse("bootloader --upgrade")
Add a new class to test to make sure it has been removed:
class F34_TestCase(F29_TestCase):
def runTest(self, iscrypted=False):
F29_TestCase.runTest(self, iscrypted=iscrypted)
self.assert_removed("bootloader", "--upgrade")