Add a phase for creating extra ISOs

Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
This commit is contained in:
Lubomír Sedlář 2018-05-09 15:39:36 +02:00
parent 4544b454f8
commit b2f995d516
14 changed files with 1057 additions and 156 deletions

View File

@ -107,6 +107,7 @@ def run(config, topdir, has_old):
pungi.phases.OSTreePhase(compose),
pungi.phases.ProductimgPhase(compose, pkgset_phase),
pungi.phases.CreateisoPhase(compose, buildinstall_phase),
pungi.phases.ExtraIsosPhase(compose),
pungi.phases.LiveImagesPhase(compose),
pungi.phases.LiveMediaPhase(compose),
pungi.phases.ImageBuildPhase(compose),

View File

@ -300,6 +300,7 @@ def run_compose(compose, create_latest_link=True, latest_link_status=None):
ostree_phase = pungi.phases.OSTreePhase(compose)
productimg_phase = pungi.phases.ProductimgPhase(compose, pkgset_phase)
createiso_phase = pungi.phases.CreateisoPhase(compose, buildinstall_phase)
extra_isos_phase = pungi.phases.ExtraIsosPhase(compose)
liveimages_phase = pungi.phases.LiveImagesPhase(compose)
livemedia_phase = pungi.phases.LiveMediaPhase(compose)
image_build_phase = pungi.phases.ImageBuildPhase(compose)
@ -313,7 +314,7 @@ def run_compose(compose, create_latest_link=True, latest_link_status=None):
extrafiles_phase, createiso_phase, liveimages_phase,
livemedia_phase, image_build_phase, image_checksum_phase,
test_phase, ostree_phase, ostree_installer_phase,
osbs_phase):
extra_isos_phase, osbs_phase):
if phase.skip():
continue
try:
@ -402,6 +403,7 @@ def run_compose(compose, create_latest_link=True, latest_link_status=None):
# Start all phases for image artifacts
compose_images_schema = (
createiso_phase,
extra_isos_phase,
liveimages_phase,
image_build_phase,
livemedia_phase,

BIN
doc/_static/phases.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

300
doc/_static/phases.svg vendored
View File

@ -9,12 +9,12 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="839.33331"
height="220.33334"
viewBox="0 0 839.33334 220.33335"
width="771.66455"
height="221.50018"
viewBox="0 0 771.66458 221.50019"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="phases.svg"
inkscape:export-filename="/home/lsedlar/repos/pungi/doc/_static/phases.png"
inkscape:export-xdpi="90"
@ -26,21 +26,25 @@
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="1.6532468"
inkscape:cx="337.4932"
inkscape:cy="70.825454"
inkscape:zoom="1.169022"
inkscape:cx="396.63448"
inkscape:cy="97.894202"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1020"
inkscape:window-height="1016"
inkscape:window-x="1920"
inkscape:window-y="31"
inkscape:window-y="27"
inkscape:window-maximized="1"
units="px"
inkscape:document-rotation="0"
showguides="true"
inkscape:guide-bbox="true" />
inkscape:guide-bbox="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs4">
<marker
@ -67,12 +71,12 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="matrix(1.066667,0,0,1.066667,0,-902.18643)"
transform="matrix(1.066667,0,0,1.066667,-2.473231,-910.85239)"
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Vrstva 1">
@ -115,8 +119,7 @@
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="556.95709"
y="971.54041"
id="text3384-0"
sodipodi:linespacing="0%"><tspan
id="text3384-0"><tspan
y="971.54041"
x="556.95709"
sodipodi:role="line"
@ -139,8 +142,7 @@
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="557.61566"
y="971.33813"
id="text3396"
sodipodi:linespacing="0%"><tspan
id="text3396"><tspan
sodipodi:role="line"
id="tspan3398"
x="557.61566"
@ -162,8 +164,7 @@
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="6.2600794"
y="891.1604"
id="text3356"
sodipodi:linespacing="0%"><tspan
id="text3356"><tspan
sodipodi:role="line"
id="tspan3358"
x="6.2600794"
@ -173,7 +174,7 @@
<path
inkscape:connector-curvature="0"
id="path3642"
d="m 100.90864,859.8891 553.31842,0"
d="M 100.90864,859.8891 H 654.22706"
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.17466855px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow1Lend)" />
<g
id="g241"
@ -187,7 +188,6 @@
y="400.8551"
transform="matrix(0,1,1,0,0,0)" />
<text
sodipodi:linespacing="0%"
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="403.15945"
@ -212,7 +212,6 @@
id="rect3342"
style="fill:#fcaf3e;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
sodipodi:linespacing="0%"
id="text3364"
y="891.06732"
x="105.76799"
@ -235,7 +234,6 @@
id="rect3344"
style="fill:#729fcf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
sodipodi:linespacing="0%"
id="text3368"
y="923.25934"
x="106.1384"
@ -261,7 +259,6 @@
transform="matrix(0,1,1,0,0,0)" />
</g>
<text
sodipodi:linespacing="0%"
id="text3372"
y="923.25934"
x="165.23042"
@ -284,7 +281,6 @@
id="rect3348"
style="fill:#e9b96e;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
sodipodi:linespacing="0%"
id="text3376"
y="921.86945"
x="243.95874"
@ -308,7 +304,6 @@
id="rect3350-3"
style="fill:#729fcf;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
sodipodi:linespacing="0%"
id="text3380-2"
y="840.3219"
x="256.90588"
@ -324,7 +319,7 @@
transform="translate(-328.39105,-85.517823)"
id="g288">
<g
transform="translate(0.56706579,0)"
transform="translate(0.56706579)"
id="g3653">
<rect
style="fill:#fcaf3e;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
@ -335,141 +330,156 @@
y="490.33765"
transform="matrix(0,1,1,0,0,0)" />
<text
sodipodi:linespacing="0%"
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.99999714px;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="492.642"
y="1039.4121"
id="text3430"><tspan
id="tspan283"
sodipodi:role="line"
x="492.642"
y="1039.4121">OSTreeInstaller</tspan></text>
y="1039.4121"
style="font-size:11.99999714px;line-height:0">OSTreeInstaller</tspan></text>
</g>
</g>
</g>
<g
id="g236"
transform="translate(-60.108974,42.1407)">
<g
transform="translate(88.832932,-122.61379)"
id="g3458">
<rect
transform="matrix(0,1,1,0,0,0)"
style="fill:#edd400;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect3338"
width="26.295755"
height="102.36562"
x="953.49097"
y="420.13605" />
<text
id="text3384"
y="971.54041"
id="g3458"
transform="translate(28.723958,-80.473035)">
<rect
y="420.13605"
x="953.49097"
height="102.36562"
width="26.295755"
id="rect3338"
style="fill:#edd400;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
transform="matrix(0,1,1,0,0,0)" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="422.99252"
y="971.54041"
id="text3384"><tspan
style="font-size:13.14787769px;line-height:1.25"
sodipodi:role="line"
id="tspan3386"
x="422.99252"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"
sodipodi:linespacing="0%"><tspan
y="971.54041"
x="422.99252"
id="tspan3386"
sodipodi:role="line"
style="font-size:13.14787769px;line-height:1.25">Createiso</tspan></text>
</g>
<g
id="g3453"
transform="translate(88.576485,-121.89312)">
<rect
transform="matrix(0,1,1,0,0,0)"
y="420.39337"
x="989.65247"
height="101.85102"
width="26.295755"
id="rect3352"
style="fill:#73d216;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
id="text3388"
y="1006.4276"
y="971.54041">Createiso</tspan></text>
</g>
<g
transform="translate(28.467511,-84.181232)"
id="g3453">
<rect
style="fill:#73d216;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect3352"
width="26.295755"
height="101.85102"
x="989.65247"
y="420.39337"
transform="matrix(0,1,1,0,0,0)" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="422.69772"
y="1006.4276"
id="text3388"><tspan
style="font-size:13.14787769px;line-height:1.25"
sodipodi:role="line"
id="tspan3390"
x="422.69772"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"
sodipodi:linespacing="0%"><tspan
y="1006.4276"
x="422.69772"
id="tspan3390"
sodipodi:role="line"
style="font-size:13.14787769px;line-height:1.25">LiveImages</tspan></text>
</g>
<g
id="g3448"
transform="translate(88.576485,-121.42489)">
<rect
transform="matrix(0,1,1,0,0,0)"
y="420.39337"
x="1026.0664"
height="101.85102"
width="26.295755"
id="rect3354"
style="fill:#f57900;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
id="text3392"
y="1042.8416"
y="1006.4276">LiveImages</tspan></text>
</g>
<g
transform="translate(28.467511,-88.141877)"
id="g3448">
<rect
style="fill:#f57900;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect3354"
width="26.295755"
height="101.85102"
x="1026.0664"
y="420.39337"
transform="matrix(0,1,1,0,0,0)" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="422.69772"
y="1042.8416"
id="text3392"><tspan
style="font-size:13.14787769px;line-height:1.25"
sodipodi:role="line"
id="tspan3394"
x="422.69772"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"
sodipodi:linespacing="0%"><tspan
y="1042.8416"
x="422.69772"
id="tspan3394"
sodipodi:role="line"
style="font-size:13.14787769px;line-height:1.25">ImageBuild</tspan></text>
</g>
<g
id="g3443"
transform="translate(87.869393,-121.31225)">
<rect
style="fill:#edd400;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect3422"
width="26.295755"
height="101.85102"
x="1062.8359"
y="421.10046"
transform="matrix(0,1,1,0,0,0)" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="423.40482"
y="1042.8416">ImageBuild</tspan></text>
</g>
<g
transform="translate(27.760419,-92.458101)"
id="g3443">
<rect
transform="matrix(0,1,1,0,0,0)"
y="421.10046"
x="1062.8359"
height="101.85102"
width="26.295755"
id="rect3422"
style="fill:#edd400;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
id="text3424"
y="1079.6111"
x="423.40482"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-size:13.14787769px;line-height:1.25"
y="1079.6111"
id="text3424"
sodipodi:linespacing="0%"><tspan
id="tspan3434"
sodipodi:role="line"
x="423.40482"
y="1079.6111"
style="font-size:13.14787769px;line-height:1.25">LiveMedia</tspan></text>
</g>
<g
transform="translate(18.63221,-71.202583)"
id="g306">
<rect
style="fill:#c17d11;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect290"
width="26.295755"
height="101.85102"
x="1048.9327"
y="490.33765"
transform="matrix(0,1,1,0,0,0)" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:11.99999714px;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="423.40482"
sodipodi:role="line"
id="tspan3434">LiveMedia</tspan></text>
</g>
<g
id="g306"
transform="translate(-41.476764,-46.1016)">
<rect
transform="matrix(0,1,1,0,0,0)"
y="490.33765"
x="1048.9327"
height="101.85102"
width="26.295755"
id="rect290"
style="fill:#c17d11;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
id="text294"
y="1065.7078"
x="492.642"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-size:11.99999714px;line-height:0"
id="tspan301"
sodipodi:role="line"
x="492.642"
y="1065.7078"
id="text294"
sodipodi:linespacing="0%"><tspan
y="1065.7078"
x="492.642"
sodipodi:role="line"
id="tspan301">OSBS</tspan></text>
</g>
y="1065.7078">OSBS</tspan></text>
</g>
<g
id="g3819"
transform="translate(0,-16.949078)">
<rect
transform="matrix(0,1,1,0,0,0)"
y="448.86087"
x="1052.2335"
height="101.85102"
width="26.295755"
id="rect3801"
style="fill:#73d216;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<text
id="text3805"
y="1069.0087"
x="451.16522"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan3812"
y="1069.0087"
x="451.16522"
sodipodi:role="line"
style="font-size:13.14787769px;line-height:1.25">ExtraIsos</tspan></text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1450,6 +1450,85 @@ Example config
}
Extra ISOs
==========
Create an ISO image that contains packages from multiple variants. Such ISO
always belongs to one variant, and will be stored in ISO directory of that
variant.
The ISO will be bootable if buildinstall phase runs for the parent variant. It
will reuse boot configuration from that variant.
**extra_isos**
(*dict*) -- a mapping from variant UID regex to a list of configuration
blocks.
* ``include_variants`` -- (*list*) list of variant UIDs from which content
should be added to the ISO; the variant of this image is added
automatically.
Rest of configuration keys is optional.
* ``filename`` -- (*str*) template for naming the image. In addition to the
regular placeholders ``filename`` is available with the name generated
using ``image_name_format`` option.
* ``volid`` -- (*str*) template for generating volume ID. Again ``volid``
placeholder can be used similarly as for file name. This can also be a
list of templates that will be tried sequentially until one generates a
volume ID that fits into 32 character limit.
* ``extra_files`` -- (*list*) a list of :ref:`scm_dict <scm_support>`
objects. These files will be put in the top level directory of the image.
* ``arches`` -- (*list*) a list of architectures for which to build this
image. By default all arches from the variant will be used. This option
can be used to limit them.
* ``failable_arches`` -- (*list*) a list of architectures for which the
image can fail to be generated and not fail the entire compose.
* ``skip_src`` -- (*bool*) allows to disable creating an image with source
packages.
Example config
--------------
::
extra_isos = {
'Server': [{
# Will generate foo-DP-1.0-20180510.t.43-Server-x86_64-dvd1.iso
'filename': 'foo-{filename}',
'volid': 'foo-{arch}',
'extra_files': [{
'scm': 'git',
'repo': 'https://pagure.io/pungi.git',
'file': 'setup.py'
}],
'include_variants': ['Client']
}]
}
# This should create image with the following layout:
# .
# ├── Client
# │   ├── Packages
# │   │ ├── a
# │   │ └── b
# │   └── repodata
# ├── Server
# │   ├── extra_files.json # extra file from Server
# │   ├── LICENSE # extra file from Server
# │   ├── Packages
# │   │ ├── a
# │   │ └── b
# │   └── repodata
# └── setup.py
Media Checksums Settings
========================

View File

@ -99,6 +99,15 @@ packages fit on a single image.
There can also be images with source repositories. These are never bootable.
ExtraIsos
---------
This phase is very similar to ``createiso``, except it combines content from
multiple variants onto a single image. Packages, repodata and extra files from
each configured variant are put into a subdirectory. Additional extra files can
be put into top level of the image. The image will be bootable if the main
variant is bootable.
LiveImages, LiveMedia
---------------------

View File

@ -863,6 +863,47 @@ def make_schema():
"$ref": "#/definitions/list_of_strings"
}),
"extra_isos": {
"type": "object",
"patternProperties": {
# Warning: this pattern is a variant uid regex, but the
# format does not let us validate it as there is no regular
# expression to describe all regular expressions.
".+": {
"type": "array",
"items": {
"type": "object",
"properties": {
"include_variants": {"$ref": "#/definitions/strings"},
"extra_files": _one_or_list({
"type": "object",
"properties": {
"scm": {"type": "string"},
"repo": {"type": "string"},
"branch": {"$ref": "#/definitions/optional_string"},
"file": {"$ref": "#/definitions/strings"},
"target": {"type": "string"},
},
"additionalProperties": False,
}),
"filename": {"type": "string"},
"volid": {"$ref": "#/definitions/strings"},
"arches": {"$ref": "#/definitions/list_of_strings"},
"failable_arches": {
"$ref": "#/definitions/list_of_strings"
},
"skip_src": {
"type": "boolean",
"default": False,
},
},
"required": ["include_variants"],
"additionalProperties": False
}
}
}
},
"live_media": {
"type": "object",
"patternProperties": {

View File

@ -264,6 +264,18 @@ class WorkPaths(object):
makedirs(path)
return path
def extra_iso_extra_files_dir(self, arch, variant, create_dir=True):
"""
Examples:
work/x86_64/Server/extra-iso-extra-files
"""
if arch == "global":
raise RuntimeError("Global extra files dir makes no sense.")
path = os.path.join(self.topdir(arch, create_dir=create_dir), variant.uid, "extra-iso-extra-files")
if create_dir:
makedirs(path)
return path
def repo_package_list(self, arch, variant, pkg_type=None, create_dir=True):
"""
Examples:

View File

@ -25,6 +25,7 @@ from .product_img import ProductimgPhase # noqa
from .buildinstall import BuildinstallPhase # noqa
from .extra_files import ExtraFilesPhase # noqa
from .createiso import CreateisoPhase # noqa
from .extra_isos import ExtraIsosPhase # noqa
from .live_images import LiveImagesPhase # noqa
from .image_build import ImageBuildPhase # noqa
from .test import TestPhase # noqa

View File

@ -395,13 +395,7 @@ def prepare_iso(compose, arch, variant, disc_num=1, disc_count=None, split_iso_d
if i in ti.checksums.checksums.keys():
del ti.checksums.checksums[i]
# make a copy of isolinux/isolinux.bin, images/boot.img - they get modified when mkisofs is called
for i in ("isolinux/isolinux.bin", "images/boot.img"):
src_path = os.path.join(tree_dir, i)
dst_path = os.path.join(iso_dir, i)
if os.path.exists(src_path):
makedirs(os.path.dirname(dst_path))
shutil.copy2(src_path, dst_path)
copy_boot_images(tree_dir, iso_dir)
if disc_count > 1:
# remove repodata/repomd.xml from checksums, create a new one later
@ -454,3 +448,15 @@ def prepare_iso(compose, arch, variant, disc_num=1, disc_count=None, split_iso_d
gp = "%s-graft-points" % iso_dir
iso.write_graft_points(gp, data, exclude=["*/lost+found", "*/boot.iso"])
return gp
def copy_boot_images(src, dest):
"""When mkisofs is called it tries to modify isolinux/isolinux.bin and
images/boot.img. Therefore we need to make copies of them.
"""
for i in ("isolinux/isolinux.bin", "images/boot.img"):
src_path = os.path.join(src, i)
dst_path = os.path.join(dest, i)
if os.path.exists(src_path):
makedirs(os.path.dirname(dst_path))
shutil.copy2(src_path, dst_path)

201
pungi/phases/extra_isos.py Normal file
View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://gnu.org/licenses/>.
import os
from kobo.shortcuts import force_list
from kobo.threads import ThreadPool, WorkerThread
from pungi import createiso
from pungi.phases.base import ConfigGuardedPhase, PhaseBase, PhaseLoggerMixin
from pungi.phases.createiso import (add_iso_to_metadata, copy_boot_images,
run_createiso_command)
from pungi.util import failable, get_format_substs, get_variant_data, get_volid
from pungi.wrappers import iso
from pungi.wrappers.scm import get_dir_from_scm, get_file_from_scm
class ExtraIsosPhase(PhaseLoggerMixin, ConfigGuardedPhase, PhaseBase):
name = "extra_isos"
def __init__(self, compose):
super(ExtraIsosPhase, self).__init__(compose)
self.pool = ThreadPool(logger=self.logger)
def validate(self):
for variant in self.compose.get_variants(types=['variant']):
for config in get_variant_data(self.compose.conf, self.name, variant):
extra_arches = set(config.get('arches', [])) - set(variant.arches)
if extra_arches:
self.compose.log_warning(
'Extra iso config for %s mentions non-existing arches: %s'
% (variant, ', '.join(sorted(extra_arches))))
def run(self):
commands = []
for variant in self.compose.get_variants(types=['variant']):
for config in get_variant_data(self.compose.conf, self.name, variant):
arches = set(variant.arches)
if config.get('arches'):
arches &= set(config['arches'])
if not config['skip_src']:
arches.add('src')
for arch in sorted(arches):
commands.append((config, variant, arch))
for (config, variant, arch) in commands:
self.pool.add(ExtraIsosThread(self.pool))
self.pool.queue_put((self.compose, config, variant, arch))
self.pool.start()
class ExtraIsosThread(WorkerThread):
def process(self, item, num):
self.num = num
compose, config, variant, arch = item
can_fail = arch in config.get('failable_arches', [])
with failable(compose, can_fail, variant, arch, 'extra_iso', logger=self.pool._logger):
self.worker(compose, config, variant, arch)
def worker(self, compose, config, variant, arch):
filename = get_filename(compose, variant, arch, config.get('filename'))
volid = get_volume_id(compose, variant, arch, config.get('volid', []))
iso_dir = compose.paths.compose.iso_dir(arch, variant)
iso_path = os.path.join(iso_dir, filename)
msg = "Creating ISO (arch: %s, variant: %s): %s" % (arch, variant, filename)
self.pool.log_info("[BEGIN] %s" % msg)
get_extra_files(compose, variant, arch, config.get('extra_files', []))
bootable = arch != "src" and compose.conf['bootable']
graft_points = get_iso_contents(compose, variant, arch,
config['include_variants'],
filename, bootable)
opts = createiso.CreateIsoOpts(
output_dir=iso_dir,
iso_name=filename,
volid=volid,
graft_points=graft_points,
arch=arch,
supported=compose.supported,
)
if bootable:
opts = opts._replace(buildinstall_method=compose.conf['buildinstall_method'])
script_file = os.path.join(compose.paths.work.tmp_dir(arch, variant),
'extraiso-%s.sh' % filename)
with open(script_file, 'w') as f:
createiso.write_script(opts, f)
run_createiso_command(compose.conf["runroot"], self.num, compose, bootable, arch,
['bash', script_file], [compose.topdir],
log_file=compose.paths.log.log_file(
arch, "extraiso-%s" % os.path.basename(iso_path)),
with_jigdo=False)
add_iso_to_metadata(compose, variant, arch, iso_path, bootable, 1, 1)
self.pool.log_info("[DONE ] %s" % msg)
def get_extra_files(compose, variant, arch, extra_files):
"""Clone the configured files into a directory from where they can be
included in the ISO.
"""
extra_files_dir = compose.paths.work.extra_iso_extra_files_dir(arch, variant)
for scm_dict in extra_files:
getter = get_file_from_scm if 'file' in scm_dict else get_dir_from_scm
target_path = os.path.join(extra_files_dir, scm_dict.get('target', '').lstrip('/'))
getter(scm_dict, target_path, logger=compose._logger)
def get_iso_contents(compose, variant, arch, include_variants, filename, bootable):
"""Find all files that should be on the ISO. For bootable image we start
with the boot configuration. Then for each variant we add packages,
repodata and extra files. Finally we add top-level extra files.
"""
iso_dir = compose.paths.work.iso_dir(arch, filename)
files = {}
if bootable:
buildinstall_dir = compose.paths.work.buildinstall_dir(arch, create_dir=False)
if compose.conf['buildinstall_method'] == 'lorax':
buildinstall_dir = os.path.join(buildinstall_dir, variant.uid)
copy_boot_images(buildinstall_dir, iso_dir)
files = iso.get_graft_points([buildinstall_dir, iso_dir])
variants = [variant.uid] + include_variants
for variant_uid in variants:
var = compose.all_variants[variant_uid]
# Get packages...
package_dir = compose.paths.compose.packages(arch, var)
for k, v in iso.get_graft_points([package_dir]).items():
files[os.path.join(var.uid, 'Packages', k)] = v
# Get repodata...
tree_dir = compose.paths.compose.repository(arch, var)
repo_dir = os.path.join(tree_dir, 'repodata')
for k, v in iso.get_graft_points([repo_dir]).items():
files[os.path.join(var.uid, 'repodata', k)] = v
# Get extra files...
extra_files_dir = compose.paths.work.extra_files_dir(arch, var)
for k, v in iso.get_graft_points([extra_files_dir]).items():
files[os.path.join(var.uid, k)] = v
# Add extra files specific for the ISO
extra_files_dir = compose.paths.work.extra_iso_extra_files_dir(arch, variant)
files.update(iso.get_graft_points([extra_files_dir]))
gp = "%s-graft-points" % iso_dir
iso.write_graft_points(gp, files, exclude=["*/lost+found", "*/boot.iso"])
return gp
def get_filename(compose, variant, arch, format):
disc_type = compose.conf['disc_types'].get('dvd', 'dvd')
base_filename = compose.get_image_name(
arch, variant, disc_type=disc_type, disc_num=1)
if not format:
return base_filename
kwargs = {
'arch': arch,
'disc_type': disc_type,
'disc_num': 1,
'suffix': '.iso',
'filename': base_filename,
'variant': variant,
}
args = get_format_substs(compose, **kwargs)
try:
return (format % args).format(**args)
except KeyError as err:
raise RuntimeError('Failed to create image name: unknown format element: %s' % err)
def get_volume_id(compose, variant, arch, formats):
disc_type = compose.conf['disc_types'].get('dvd', 'dvd')
# Get volume ID for regular ISO so that we can substitute it in.
volid = get_volid(compose, arch, variant, disc_type=disc_type)
return get_volid(compose, arch, variant, disc_type=disc_type,
formats=force_list(formats), volid=volid)

View File

@ -330,7 +330,8 @@ def _apply_substitutions(compose, volid):
return volid
def get_volid(compose, arch, variant=None, escape_spaces=False, disc_type=False):
def get_volid(compose, arch, variant=None, escape_spaces=False, disc_type=False,
formats=None, **kwargs):
"""Get ISO volume ID for arch and variant"""
if variant and variant.type == "addon":
# addons are part of parent variant media
@ -359,9 +360,10 @@ def get_volid(compose, arch, variant=None, escape_spaces=False, disc_type=False)
all_products = layered_products + products
else:
all_products = products
formats = formats or all_products
tried = set()
for i in all_products:
for i in formats:
if not variant_uid and "%(variant)s" in i:
continue
try:
@ -372,7 +374,8 @@ def get_volid(compose, arch, variant=None, escape_spaces=False, disc_type=False)
arch=arch,
disc_type=disc_type or '',
base_product_short=base_product_short,
base_product_version=base_product_version)
base_product_version=base_product_version,
**kwargs)
volid = (i % args).format(**args)
except KeyError as err:
raise RuntimeError('Failed to create volume id: unknown format element: %s' % err)

View File

@ -114,4 +114,11 @@ createiso_skip = [
}),
]
extra_isos = {
'^Server$': [{
'include_variants': ['Client']
'filename': 'extra-{filename}',
}]
}
create_jigdo = False

View File

@ -0,0 +1,529 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
try:
import unittest2 as unittest
except ImportError:
import unittest
import mock
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from tests import helpers
from pungi.phases import extra_isos
@mock.patch('pungi.phases.extra_isos.ThreadPool')
class ExtraIsosPhaseTest(helpers.PungiTestCase):
def test_logs_extra_arches(self, ThreadPool):
cfg = {
'include_variants': ['Client'],
'arches': ['x86_64', 'ppc64le', 'aarch64'],
}
compose = helpers.DummyCompose(self.topdir, {
'extra_isos': {
'^Server$': [cfg]
}
})
phase = extra_isos.ExtraIsosPhase(compose)
phase.validate()
self.assertEqual(len(compose.log_warning.call_args_list), 1)
def test_one_task_for_each_arch(self, ThreadPool):
cfg = {
'include_variants': ['Client'],
}
compose = helpers.DummyCompose(self.topdir, {
'extra_isos': {
'^Server$': [cfg]
}
})
phase = extra_isos.ExtraIsosPhase(compose)
phase.run()
self.assertEqual(len(ThreadPool.return_value.add.call_args_list), 3)
self.assertItemsEqual(
ThreadPool.return_value.queue_put.call_args_list,
[mock.call((compose, cfg, compose.variants['Server'], 'x86_64')),
mock.call((compose, cfg, compose.variants['Server'], 'amd64')),
mock.call((compose, cfg, compose.variants['Server'], 'src'))]
)
def test_filter_arches(self, ThreadPool):
cfg = {
'include_variants': ['Client'],
'arches': ['x86_64'],
}
compose = helpers.DummyCompose(self.topdir, {
'extra_isos': {
'^Server$': [cfg]
}
})
phase = extra_isos.ExtraIsosPhase(compose)
phase.run()
self.assertEqual(len(ThreadPool.return_value.add.call_args_list), 2)
self.assertItemsEqual(
ThreadPool.return_value.queue_put.call_args_list,
[mock.call((compose, cfg, compose.variants['Server'], 'x86_64')),
mock.call((compose, cfg, compose.variants['Server'], 'src'))]
)
def test_skip_source(self, ThreadPool):
cfg = {
'include_variants': ['Client'],
'skip_src': True,
}
compose = helpers.DummyCompose(self.topdir, {
'extra_isos': {
'^Server$': [cfg]
}
})
phase = extra_isos.ExtraIsosPhase(compose)
phase.run()
self.assertEqual(len(ThreadPool.return_value.add.call_args_list), 2)
self.assertItemsEqual(
ThreadPool.return_value.queue_put.call_args_list,
[mock.call((compose, cfg, compose.variants['Server'], 'x86_64')),
mock.call((compose, cfg, compose.variants['Server'], 'amd64'))]
)
@mock.patch('pungi.phases.extra_isos.get_volume_id')
@mock.patch('pungi.phases.extra_isos.get_filename')
@mock.patch('pungi.phases.extra_isos.get_iso_contents')
@mock.patch('pungi.phases.extra_isos.get_extra_files')
@mock.patch('pungi.phases.extra_isos.run_createiso_command')
@mock.patch('pungi.phases.extra_isos.add_iso_to_metadata')
class ExtraIsosThreadTest(helpers.PungiTestCase):
def test_binary_bootable_image(self, aitm, rcc, gef, gic, gfn, gvi):
compose = helpers.DummyCompose(self.topdir, {
'bootable': True,
'buildinstall_method': 'lorax'
})
server = compose.variants['Server']
cfg = {
'include_variants': ['Client'],
}
gfn.return_value = 'my.iso'
gvi.return_value = 'my volume id'
gic.return_value = '/tmp/iso-graft-points'
t = extra_isos.ExtraIsosThread(mock.Mock())
with mock.patch('time.sleep'):
t.process((compose, cfg, server, 'x86_64'), 1)
self.assertEqual(gfn.call_args_list,
[mock.call(compose, server, 'x86_64', None)])
self.assertEqual(gvi.call_args_list,
[mock.call(compose, server, 'x86_64', [])])
self.assertEqual(gef.call_args_list,
[mock.call(compose, server, 'x86_64', [])])
self.assertEqual(gic.call_args_list,
[mock.call(compose, server, 'x86_64', ['Client'], 'my.iso', True)])
self.assertEqual(
rcc.call_args_list,
[mock.call(False, 1, compose, True, 'x86_64',
['bash', os.path.join(self.topdir, 'work/x86_64/tmp-Server/extraiso-my.iso.sh')],
[self.topdir],
log_file=os.path.join(self.topdir, 'logs/x86_64/extraiso-my.iso.x86_64.log'),
with_jigdo=False)]
)
self.assertEqual(
aitm.call_args_list,
[mock.call(compose, server, 'x86_64',
os.path.join(self.topdir, 'compose/Server/x86_64/iso/my.iso'),
True, 1, 1)]
)
def test_binary_image_custom_naming(self, aitm, rcc, gef, gic, gfn, gvi):
compose = helpers.DummyCompose(self.topdir, {})
server = compose.variants['Server']
cfg = {
'include_variants': ['Client'],
'filename': 'fn',
'volid': ['v1', 'v2'],
}
gfn.return_value = 'my.iso'
gvi.return_value = 'my volume id'
gic.return_value = '/tmp/iso-graft-points'
t = extra_isos.ExtraIsosThread(mock.Mock())
with mock.patch('time.sleep'):
t.process((compose, cfg, server, 'x86_64'), 1)
self.assertEqual(gfn.call_args_list,
[mock.call(compose, server, 'x86_64', 'fn')])
self.assertEqual(gvi.call_args_list,
[mock.call(compose, server, 'x86_64', ['v1', 'v2'])])
self.assertEqual(gef.call_args_list,
[mock.call(compose, server, 'x86_64', [])])
self.assertEqual(gic.call_args_list,
[mock.call(compose, server, 'x86_64', ['Client'], 'my.iso', False)])
self.assertEqual(
rcc.call_args_list,
[mock.call(False, 1, compose, False, 'x86_64',
['bash', os.path.join(self.topdir, 'work/x86_64/tmp-Server/extraiso-my.iso.sh')],
[self.topdir],
log_file=os.path.join(self.topdir, 'logs/x86_64/extraiso-my.iso.x86_64.log'),
with_jigdo=False)]
)
self.assertEqual(
aitm.call_args_list,
[mock.call(compose, server, 'x86_64',
os.path.join(self.topdir, 'compose/Server/x86_64/iso/my.iso'),
False, 1, 1)]
)
def test_source_is_not_bootable(self, aitm, rcc, gef, gic, gfn, gvi):
compose = helpers.DummyCompose(self.topdir, {
'bootable': True,
'buildinstall_method': 'lorax'
})
server = compose.variants['Server']
cfg = {
'include_variants': ['Client'],
}
gfn.return_value = 'my.iso'
gvi.return_value = 'my volume id'
gic.return_value = '/tmp/iso-graft-points'
t = extra_isos.ExtraIsosThread(mock.Mock())
with mock.patch('time.sleep'):
t.process((compose, cfg, server, 'src'), 1)
self.assertEqual(gfn.call_args_list,
[mock.call(compose, server, 'src', None)])
self.assertEqual(gvi.call_args_list,
[mock.call(compose, server, 'src', [])])
self.assertEqual(gef.call_args_list,
[mock.call(compose, server, 'src', [])])
self.assertEqual(gic.call_args_list,
[mock.call(compose, server, 'src', ['Client'], 'my.iso', False)])
self.assertEqual(
rcc.call_args_list,
[mock.call(False, 1, compose, False, 'src',
['bash', os.path.join(self.topdir, 'work/src/tmp-Server/extraiso-my.iso.sh')],
[self.topdir],
log_file=os.path.join(self.topdir, 'logs/src/extraiso-my.iso.src.log'),
with_jigdo=False)]
)
self.assertEqual(
aitm.call_args_list,
[mock.call(compose, server, 'src',
os.path.join(self.topdir, 'compose/Server/source/iso/my.iso'),
False, 1, 1)]
)
def test_failable_failed(self, aitm, rcc, gef, gic, gfn, gvi):
compose = helpers.DummyCompose(self.topdir, {})
server = compose.variants['Server']
cfg = {
'include_variants': ['Client'],
'failable_arches': ['x86_64'],
}
gfn.return_value = 'my.iso'
gvi.return_value = 'my volume id'
gic.return_value = '/tmp/iso-graft-points'
rcc.side_effect = helpers.mk_boom()
t = extra_isos.ExtraIsosThread(mock.Mock())
with mock.patch('time.sleep'):
t.process((compose, cfg, server, 'x86_64'), 1)
self.assertEqual(aitm.call_args_list, [])
def test_non_failable_failed(self, aitm, rcc, gef, gic, gfn, gvi):
compose = helpers.DummyCompose(self.topdir, {})
server = compose.variants['Server']
cfg = {
'include_variants': ['Client'],
}
gfn.return_value = 'my.iso'
gvi.return_value = 'my volume id'
gic.return_value = '/tmp/iso-graft-points'
rcc.side_effect = helpers.mk_boom(RuntimeError)
t = extra_isos.ExtraIsosThread(mock.Mock())
with self.assertRaises(RuntimeError):
with mock.patch('time.sleep'):
t.process((compose, cfg, server, 'x86_64'), 1)
self.assertEqual(aitm.call_args_list, [])
@mock.patch('pungi.phases.extra_isos.get_file_from_scm')
@mock.patch('pungi.phases.extra_isos.get_dir_from_scm')
class GetExtraFilesTest(helpers.PungiTestCase):
def setUp(self):
super(GetExtraFilesTest, self).setUp()
self.compose = helpers.DummyCompose(self.topdir, {})
self.variant = self.compose.variants['Server']
self.arch = 'x86_64'
def test_no_config(self, get_dir, get_file):
extra_isos.get_extra_files(self.compose, self.variant, self.arch, [])
self.assertEqual(get_dir.call_args_list, [])
self.assertEqual(get_file.call_args_list, [])
def test_get_file(self, get_dir, get_file):
cfg = {
'scm': 'git',
'repo': 'https://pagure.io/pungi.git',
'file': 'GPL',
'target': 'legalese',
}
extra_isos.get_extra_files(self.compose, self.variant, self.arch, [cfg])
self.assertEqual(get_dir.call_args_list, [])
self.assertEqual(get_file.call_args_list,
[mock.call(cfg,
os.path.join(self.topdir, 'work',
self.arch, self.variant.uid,
'extra-iso-extra-files/legalese'),
logger=self.compose._logger)])
def test_get_dir(self, get_dir, get_file):
cfg = {
'scm': 'git',
'repo': 'https://pagure.io/pungi.git',
'dir': 'docs',
'target': 'foo',
}
extra_isos.get_extra_files(self.compose, self.variant, self.arch, [cfg])
self.assertEqual(get_file.call_args_list, [])
self.assertEqual(get_dir.call_args_list,
[mock.call(cfg,
os.path.join(self.topdir, 'work',
self.arch, self.variant.uid,
'extra-iso-extra-files/foo'),
logger=self.compose._logger)])
@mock.patch('pungi.wrappers.iso.write_graft_points')
@mock.patch('pungi.wrappers.iso.get_graft_points')
class GetIsoContentsTest(helpers.PungiTestCase):
def setUp(self):
super(GetIsoContentsTest, self).setUp()
self.compose = helpers.DummyCompose(self.topdir, {})
self.variant = self.compose.variants['Server']
def test_non_bootable_binary(self, ggp, wgp):
gp = {
'compose/Client/x86_64/os/Packages': {'f/foo.rpm': '/mnt/f/foo.rpm'},
'compose/Client/x86_64/os/repodata': {'primary.xml': '/mnt/repodata/primary.xml'},
'compose/Server/x86_64/os/Packages': {'b/bar.rpm': '/mnt/b/bar.rpm'},
'compose/Server/x86_64/os/repodata': {'repomd.xml': '/mnt/repodata/repomd.xml'},
'work/x86_64/Client/extra-files': {'GPL': '/mnt/GPL'},
'work/x86_64/Server/extra-files': {'AUTHORS': '/mnt/AUTHORS'},
'work/x86_64/Server/extra-iso-extra-files': {'EULA': '/mnt/EULA'},
}
ggp.side_effect = lambda x: gp[x[0][len(self.topdir) + 1:]]
gp_file = os.path.join(self.topdir, 'work/x86_64/iso/my.iso-graft-points')
self.assertEqual(
extra_isos.get_iso_contents(self.compose, self.variant, 'x86_64',
['Client'], 'my.iso', False),
gp_file
)
expected = {
'Client/GPL': '/mnt/GPL',
'Client/Packages/f/foo.rpm': '/mnt/f/foo.rpm',
'Client/repodata/primary.xml': '/mnt/repodata/primary.xml',
'EULA': '/mnt/EULA',
'Server/AUTHORS': '/mnt/AUTHORS',
'Server/Packages/b/bar.rpm': '/mnt/b/bar.rpm',
'Server/repodata/repomd.xml': '/mnt/repodata/repomd.xml',
}
self.assertItemsEqual(
ggp.call_args_list,
[mock.call([os.path.join(self.topdir, x)]) for x in gp]
)
self.assertEqual(len(wgp.call_args_list), 1)
self.assertEqual(wgp.call_args_list[0][0][0], gp_file)
self.assertDictEqual(dict(wgp.call_args_list[0][0][1]), expected)
self.assertEqual(wgp.call_args_list[0][1], {'exclude': ["*/lost+found", "*/boot.iso"]})
def test_source(self, ggp, wgp):
gp = {
'compose/Client/source/tree/Packages': {'f/foo.rpm': '/mnt/f/foo.rpm'},
'compose/Client/source/tree/repodata': {'primary.xml': '/mnt/repodata/primary.xml'},
'compose/Server/source/tree/Packages': {'b/bar.rpm': '/mnt/b/bar.rpm'},
'compose/Server/source/tree/repodata': {'repomd.xml': '/mnt/repodata/repomd.xml'},
'work/src/Client/extra-files': {'GPL': '/mnt/GPL'},
'work/src/Server/extra-files': {'AUTHORS': '/mnt/AUTHORS'},
'work/src/Server/extra-iso-extra-files': {'EULA': '/mnt/EULA'},
}
ggp.side_effect = lambda x: gp[x[0][len(self.topdir) + 1:]]
gp_file = os.path.join(self.topdir, 'work/src/iso/my.iso-graft-points')
self.assertEqual(
extra_isos.get_iso_contents(self.compose, self.variant, 'src',
['Client'], 'my.iso', False),
gp_file
)
expected = {
'Client/GPL': '/mnt/GPL',
'Client/Packages/f/foo.rpm': '/mnt/f/foo.rpm',
'Client/repodata/primary.xml': '/mnt/repodata/primary.xml',
'EULA': '/mnt/EULA',
'Server/AUTHORS': '/mnt/AUTHORS',
'Server/Packages/b/bar.rpm': '/mnt/b/bar.rpm',
'Server/repodata/repomd.xml': '/mnt/repodata/repomd.xml',
}
self.assertItemsEqual(
ggp.call_args_list,
[mock.call([os.path.join(self.topdir, x)]) for x in gp]
)
self.assertEqual(len(wgp.call_args_list), 1)
self.assertEqual(wgp.call_args_list[0][0][0], gp_file)
self.assertDictEqual(dict(wgp.call_args_list[0][0][1]), expected)
self.assertEqual(wgp.call_args_list[0][1], {'exclude': ["*/lost+found", "*/boot.iso"]})
def test_bootable(self, ggp, wgp):
self.compose.conf['buildinstall_method'] = 'lorax'
bi_dir = os.path.join(self.topdir, 'work/x86_64/buildinstall/Server')
iso_dir = os.path.join(self.topdir, 'work/x86_64/iso/my.iso')
helpers.touch(os.path.join(bi_dir, 'isolinux/isolinux.bin'))
helpers.touch(os.path.join(bi_dir, 'images/boot.img'))
gp = {
'compose/Client/x86_64/os/Packages': {'f/foo.rpm': '/mnt/f/foo.rpm'},
'compose/Client/x86_64/os/repodata': {'primary.xml': '/mnt/repodata/primary.xml'},
'compose/Server/x86_64/os/Packages': {'b/bar.rpm': '/mnt/b/bar.rpm'},
'compose/Server/x86_64/os/repodata': {'repomd.xml': '/mnt/repodata/repomd.xml'},
'work/x86_64/Client/extra-files': {'GPL': '/mnt/GPL'},
'work/x86_64/Server/extra-files': {'AUTHORS': '/mnt/AUTHORS'},
'work/x86_64/Server/extra-iso-extra-files': {'EULA': '/mnt/EULA'},
}
bi_gp = {
'isolinux/isolinux.bin': os.path.join(iso_dir, 'isolinux/isolinux.bin'),
'images/boot.img': os.path.join(iso_dir, 'images/boot.img'),
}
ggp.side_effect = lambda x: gp[x[0][len(self.topdir) + 1:]] if len(x) == 1 else bi_gp
gp_file = os.path.join(self.topdir, 'work/x86_64/iso/my.iso-graft-points')
self.assertEqual(
extra_isos.get_iso_contents(self.compose, self.variant, 'x86_64',
['Client'], 'my.iso', True),
gp_file
)
self.maxDiff = None
expected = {
'Client/GPL': '/mnt/GPL',
'Client/Packages/f/foo.rpm': '/mnt/f/foo.rpm',
'Client/repodata/primary.xml': '/mnt/repodata/primary.xml',
'EULA': '/mnt/EULA',
'Server/AUTHORS': '/mnt/AUTHORS',
'Server/Packages/b/bar.rpm': '/mnt/b/bar.rpm',
'Server/repodata/repomd.xml': '/mnt/repodata/repomd.xml',
'isolinux/isolinux.bin': os.path.join(iso_dir, 'isolinux/isolinux.bin'),
'images/boot.img': os.path.join(iso_dir, 'images/boot.img'),
}
self.assertItemsEqual(
ggp.call_args_list,
[mock.call([os.path.join(self.topdir, x)]) for x in gp] + [mock.call([bi_dir, iso_dir])]
)
self.assertEqual(len(wgp.call_args_list), 1)
self.assertEqual(wgp.call_args_list[0][0][0], gp_file)
self.assertDictEqual(dict(wgp.call_args_list[0][0][1]), expected)
self.assertEqual(wgp.call_args_list[0][1], {'exclude': ["*/lost+found", "*/boot.iso"]})
# Check files were copied to temp directory
self.assertTrue(os.path.exists(os.path.join(iso_dir, 'isolinux/isolinux.bin')))
self.assertTrue(os.path.exists(os.path.join(iso_dir, 'images/boot.img')))
class GetFilenameTest(helpers.PungiTestCase):
def test_use_original_name(self):
compose = helpers.DummyCompose(self.topdir, {})
fn = extra_isos.get_filename(compose, compose.variants['Server'], 'x86_64',
'foo-{variant}-{arch}-{filename}')
self.assertEqual(fn, 'foo-Server-x86_64-image-name')
def test_use_default_without_format(self):
compose = helpers.DummyCompose(self.topdir, {})
fn = extra_isos.get_filename(compose, compose.variants['Server'], 'x86_64',
None)
self.assertEqual(fn, 'image-name')
def test_reports_unknown_placeholder(self):
compose = helpers.DummyCompose(self.topdir, {})
with self.assertRaises(RuntimeError) as ctx:
extra_isos.get_filename(compose, compose.variants['Server'], 'x86_64',
'foo-{boom}')
self.assertIn('boom', str(ctx.exception))
class GetVolumeIDTest(helpers.PungiTestCase):
def test_use_original_volume_id(self):
compose = helpers.DummyCompose(self.topdir, {})
volid = extra_isos.get_volume_id(compose, compose.variants['Server'],
'x86_64',
'f-{volid}')
self.assertEqual(volid, 'f-test-1.0 Server.x86_64')
def test_falls_back_to_shorter(self):
compose = helpers.DummyCompose(self.topdir, {})
volid = extra_isos.get_volume_id(compose, compose.variants['Server'],
'x86_64',
['long-foobar-{volid}', 'f-{volid}'])
self.assertEqual(volid, 'f-test-1.0 Server.x86_64')
def test_reports_unknown_placeholder(self):
compose = helpers.DummyCompose(self.topdir, {})
with self.assertRaises(RuntimeError) as ctx:
extra_isos.get_volume_id(compose, compose.variants['Server'],
'x86_64', 'f-{boom}')
self.assertIn('boom', str(ctx.exception))
if __name__ == '__main__':
unittest.main()