Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion kiwi/schema/kiwi.rnc
Original file line number Diff line number Diff line change
Expand Up @@ -3563,6 +3563,26 @@ div {
## The boxname as it's written into the json file
## If not specified the image name is used
attribute boxname { text }
k.vagrantconfig.info.element =
## The info.json is a freeform document with info to display
## with the box when running `vagrant box list -i`
element info {
attribute name { text },
text
}
>> sch:pattern [ id = "vagrant_info_structure"
sch:rule [
context = "info"
sch:assert [
test = "@name"
"vagrantconfig info element must have a name attribute"
]
sch:assert [
test = "normalize-space(.) != ''"
"vagrantconfig info element must contain a non-empty value"
]
]
]
k.vagrantconfig.attlist =
k.vagrantconfig.provider.attribute &
k.vagrantconfig.virtualsize.attribute &
Expand All @@ -3573,7 +3593,8 @@ div {
## The vagrantconfig element specifies the Vagrant meta
## configuration options which are used inside a vagrant box
element vagrantconfig {
k.vagrantconfig.attlist
k.vagrantconfig.attlist,
k.vagrantconfig.info.element*
}
}

Expand Down
17 changes: 17 additions & 0 deletions kiwi/schema/kiwi.rng
Original file line number Diff line number Diff line change
Expand Up @@ -5352,6 +5352,20 @@ virtual disk when it creates the VM.</a:documentation>
If not specified the image name is used</a:documentation>
</attribute>
</define>
<define name="k.vagrantconfig.info.element">
<element name="info">
<a:documentation>The info.json is a freeform document with info to display
with the box when running `vagrant box list -i`</a:documentation>
<attribute name="name"/>
<text/>
</element>
<sch:pattern id="vagrant_info_structure">
<sch:rule context="info">
<sch:assert test="@name">vagrantconfig info element must have a name attribute</sch:assert>
<sch:assert test="normalize-space(.) != ''">vagrantconfig info element must contain a non-empty value</sch:assert>
</sch:rule>
</sch:pattern>
</define>
<define name="k.vagrantconfig.attlist">
<interleave>
<ref name="k.vagrantconfig.provider.attribute"/>
Expand All @@ -5372,6 +5386,9 @@ If not specified the image name is used</a:documentation>
<a:documentation>The vagrantconfig element specifies the Vagrant meta
configuration options which are used inside a vagrant box</a:documentation>
<ref name="k.vagrantconfig.attlist"/>
<zeroOrMore>
<ref name="k.vagrantconfig.info.element"/>
</zeroOrMore>
</element>
</define>
</div>
Expand Down
24 changes: 23 additions & 1 deletion kiwi/storage/subformat/vagrant_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def create_image_format(self) -> None:
* creation of box metadata.json
* creation of box Vagrantfile (either from scratch or by using the user
provided Vagrantfile)
* creation of box info.json
* creation of result format tarball from the files created above
"""
if not self.image_format or not self.provider:
Expand Down Expand Up @@ -155,14 +156,20 @@ def create_image_format(self) -> None:

vagrant.write(embedded_vagrantfile)

info_json = os.path.join(temp_image_dir.name, 'info.json')
if box_info := self._create_box_info():
with open(info_json, 'w') as info:
info.write(box_info)

Command.run(
[
'tar', '-C', temp_image_dir.name,
'-czf', self.get_target_file_path_for_format(
self.image_format
),
os.path.basename(metadata_json),
os.path.basename(vagrantfile)
os.path.basename(vagrantfile),
os.path.basename(info_json)
] + [
os.path.basename(box_img_file)
for box_img_file in box_img_files
Expand Down Expand Up @@ -221,6 +228,21 @@ def get_additional_vagrant_config_settings(self) -> str:
"""
return ''

def _create_box_info(self) -> Optional[str]:
"""Serialize Vagrant box info as a JSON string, if present"""
vagrant_info = self.vagrantconfig.get_info()
if not vagrant_info:
return None
info: Dict[str, str] = {}
for item in vagrant_info:
name = item.get_name()
value = item.get_valueOf_()

# just use the last one if someone sends two of the same...
info[name] = value
return json.dumps(
info, sort_keys=True, indent=2, separators=(',', ': '))

def _create_box_metadata(self):
metadata = self.get_additional_metadata() or {}
metadata['provider'] = self.provider
Expand Down
118 changes: 114 additions & 4 deletions kiwi/xml_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -8714,18 +8714,112 @@ def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
# end class oemconfig


class info(GeneratedsSuper):
"""The info.json is a freeform document with info to display with the
box when running `vagrant box list -i`"""
subclass = None
superclass = None
def __init__(self, name=None, valueOf_=None, mixedclass_=None, content_=None):
self.original_tagname_ = None
self.name = _cast(None, name)
self.valueOf_ = valueOf_
if mixedclass_ is None:
self.mixedclass_ = MixedContainer
else:
self.mixedclass_ = mixedclass_
if content_ is None:
self.content_ = []
else:
self.content_ = content_
self.valueOf_ = valueOf_
def factory(*args_, **kwargs_):
if CurrentSubclassModule_ is not None:
subclass = getSubclassFromModule_(
CurrentSubclassModule_, info)
if subclass is not None:
return subclass(*args_, **kwargs_)
if info.subclass:
return info.subclass(*args_, **kwargs_)
else:
return info(*args_, **kwargs_)
factory = staticmethod(factory)
def get_name(self): return self.name
def set_name(self, name): self.name = name
def get_valueOf_(self): return self.valueOf_
def set_valueOf_(self, valueOf_): self.valueOf_ = valueOf_
def hasContent_(self):
if (
(1 if type(self.valueOf_) in [int,float] else self.valueOf_)
):
return True
else:
return False
def export(self, outfile, level, namespaceprefix_='', name_='info', namespacedef_='', pretty_print=True):
imported_ns_def_ = GenerateDSNamespaceDefs_.get('info')
if imported_ns_def_ is not None:
namespacedef_ = imported_ns_def_
if pretty_print:
eol_ = '\n'
else:
eol_ = ''
if self.original_tagname_ is not None:
name_ = self.original_tagname_
showIndent(outfile, level, pretty_print)
outfile.write('<%s%s%s' % (namespaceprefix_, name_, namespacedef_ and ' ' + namespacedef_ or '', ))
already_processed = set()
self.exportAttributes(outfile, level, already_processed, namespaceprefix_, name_='info')
outfile.write('>')
self.exportChildren(outfile, level + 1, namespaceprefix_, name_, pretty_print=pretty_print)
outfile.write(self.convert_unicode(self.valueOf_))
outfile.write('</%s%s>%s' % (namespaceprefix_, name_, eol_))
def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='', name_='info'):
if self.name is not None and 'name' not in already_processed:
already_processed.add('name')
outfile.write(' name=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.name), input_name='name')), ))
def exportChildren(self, outfile, level, namespaceprefix_='', name_='info', fromsubclass_=False, pretty_print=True):
pass
def build(self, node):
already_processed = set()
self.buildAttributes(node, node.attrib, already_processed)
self.valueOf_ = get_all_text_(node)
if node.text is not None:
obj_ = self.mixedclass_(MixedContainer.CategoryText,
MixedContainer.TypeNone, '', node.text)
self.content_.append(obj_)
for child in node:
nodeName_ = Tag_pattern_.match(child.tag).groups()[-1]
self.buildChildren(child, node, nodeName_)
return self
def buildAttributes(self, node, attrs, already_processed):
value = find_attr_value_('name', node)
if value is not None and 'name' not in already_processed:
already_processed.add('name')
self.name = value
def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
if not fromsubclass_ and child_.tail is not None:
obj_ = self.mixedclass_(MixedContainer.CategoryText,
MixedContainer.TypeNone, '', child_.tail)
self.content_.append(obj_)
pass
# end class info


class vagrantconfig(GeneratedsSuper):
"""The vagrantconfig element specifies the Vagrant meta configuration
options which are used inside a vagrant box"""
subclass = None
superclass = None
def __init__(self, provider=None, virtualsize=None, boxname=None, virtualbox_guest_additions_present=None, embedded_vagrantfile=None):
def __init__(self, provider=None, virtualsize=None, boxname=None, virtualbox_guest_additions_present=None, embedded_vagrantfile=None, info=None):
self.original_tagname_ = None
self.provider = _cast(None, provider)
self.virtualsize = _cast(int, virtualsize)
self.boxname = _cast(None, boxname)
self.virtualbox_guest_additions_present = _cast(bool, virtualbox_guest_additions_present)
self.embedded_vagrantfile = _cast(None, embedded_vagrantfile)
if info is None:
self.info = []
else:
self.info = info
def factory(*args_, **kwargs_):
if CurrentSubclassModule_ is not None:
subclass = getSubclassFromModule_(
Expand All @@ -8737,6 +8831,11 @@ def factory(*args_, **kwargs_):
else:
return vagrantconfig(*args_, **kwargs_)
factory = staticmethod(factory)
def get_info(self): return self.info
def set_info(self, info): self.info = info
def add_info(self, value): self.info.append(value)
def insert_info_at(self, index, value): self.info.insert(index, value)
def replace_info_at(self, index, value): self.info[index] = value
def get_provider(self): return self.provider
def set_provider(self, provider): self.provider = provider
def get_virtualsize(self): return self.virtualsize
Expand All @@ -8749,7 +8848,7 @@ def get_embedded_vagrantfile(self): return self.embedded_vagrantfile
def set_embedded_vagrantfile(self, embedded_vagrantfile): self.embedded_vagrantfile = embedded_vagrantfile
def hasContent_(self):
if (

self.info
):
return True
else:
Expand All @@ -8771,6 +8870,7 @@ def export(self, outfile, level, namespaceprefix_='', name_='vagrantconfig', nam
if self.hasContent_():
outfile.write('>%s' % (eol_, ))
self.exportChildren(outfile, level + 1, namespaceprefix_='', name_='vagrantconfig', pretty_print=pretty_print)
showIndent(outfile, level, pretty_print)
outfile.write('</%s%s>%s' % (namespaceprefix_, name_, eol_))
else:
outfile.write('/>%s' % (eol_, ))
Expand All @@ -8791,7 +8891,12 @@ def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='
already_processed.add('embedded_vagrantfile')
outfile.write(' embedded_vagrantfile=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.embedded_vagrantfile), input_name='embedded_vagrantfile')), ))
def exportChildren(self, outfile, level, namespaceprefix_='', name_='vagrantconfig', fromsubclass_=False, pretty_print=True):
pass
if pretty_print:
eol_ = '\n'
else:
eol_ = ''
for info_ in self.info:
info_.export(outfile, level, namespaceprefix_, name_='info', pretty_print=pretty_print)
def build(self, node):
already_processed = set()
self.buildAttributes(node, node.attrib, already_processed)
Expand Down Expand Up @@ -8832,7 +8937,11 @@ def buildAttributes(self, node, attrs, already_processed):
already_processed.add('embedded_vagrantfile')
self.embedded_vagrantfile = value
def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
pass
if nodeName_ == 'info':
obj_ = info.factory()
obj_.build(child_)
self.info.append(obj_)
obj_.original_tagname_ = 'info'
# end class vagrantconfig


Expand Down Expand Up @@ -10276,6 +10385,7 @@ def main():
"ignore",
"image",
"include",
"info",
"initrd",
"installmedia",
"installoption",
Expand Down
21 changes: 17 additions & 4 deletions test/unit/storage/subformat/vagrant_base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def setup(self):
self.vagrantconfig.get_virtualsize = Mock(
return_value=42
)
self.vagrantconfig.get_info = Mock(return_value=[])
self.runtime_config = Mock()
self.runtime_config.get_bundle_compression.return_value = False
kiwi.storage.subformat.base.RuntimeConfig = Mock(
Expand Down Expand Up @@ -87,6 +88,17 @@ def test_create_image_format(
}
''').strip()

info_json = dedent('''
{
"repo": "my-kiwi-descriptions"
}
''').strip()

vagrantconfig_info = [Mock()]
vagrantconfig_info[0].get_name = Mock(return_value='repo')
vagrantconfig_info[0].get_valueOf_ = Mock(return_value='my-kiwi-descriptions')
self.vagrantconfig.get_info.return_value = vagrantconfig_info

expected_vagrantfile = dedent('''
Vagrant.configure("2") do |config|
end
Expand All @@ -102,17 +114,18 @@ def test_create_image_format(

assert mock_file.call_args_list == [
call('tmpdir/metadata.json', 'w'),
call('tmpdir/Vagrantfile', 'w')
call('tmpdir/Vagrantfile', 'w'),
call('tmpdir/info.json', 'w')
]
assert file_handle.write.call_args_list == [
call(metadata_json), call(expected_vagrantfile)
call(metadata_json), call(expected_vagrantfile), call(info_json)
]

mock_command.assert_called_once_with(
[
'tar', '-C', 'tmpdir', '-czf',
'target_dir/some-disk-image.x86_64-1.2.3.vagrant.libvirt.box',
'metadata.json', 'Vagrantfile'
'metadata.json', 'Vagrantfile', 'info.json'
]
)

Expand Down Expand Up @@ -144,7 +157,7 @@ def test_user_provided_vagrantfile(
assert mock_file.call_args_list == [
call('tmpdir/metadata.json', 'w'),
call('tmpdir/Vagrantfile', 'w'),
call('./example_Vagrantfile', 'r')
call('./example_Vagrantfile', 'r'),
]
assert file_handle.write.call_args_list[1] == call(
expected_vagrantfile
Expand Down
Loading