Skip to content

Commit

Permalink
Merge pull request #1901 from Chris-Peterson444/user-data-validation
Browse files Browse the repository at this point in the history
Update validate-autoinstall-user-data script
  • Loading branch information
Chris-Peterson444 committed Aug 29, 2024
2 parents 25a2bff + 79217f4 commit 1495b36
Show file tree
Hide file tree
Showing 8 changed files with 545 additions and 50 deletions.
3 changes: 3 additions & 0 deletions doc/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ YAML
addons
balancer
boolean
datasource
dropdown
favicon
installable
Expand All @@ -36,6 +37,8 @@ namespaces
observability
reST
reStructuredText
stdin
subdirectories
subfolders
subtree
validator
264 changes: 264 additions & 0 deletions doc/howto/autoinstall-validation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
.. _autoinstall_validation:

Autoinstall Validation
=====================================

The following how-to guide demonstrates how to perform pre-validation of a autoinstall config.

Autoinstall config is validated against a :doc:`JSON schema <../reference/autoinstall-schema>` during runtime before it is applied. This check ensures existence of required keys and their data types, but does not guarantee total validity of the data provided (see the :ref:`Validator Limitations<validator-limitations>` section for more details).

Pre-validating the Autoinstall configuration
--------------------------------------------

You can validate autoinstall config prior to install time by using the `validate-autoinstall-user-data script <https://github.com/canonical/subiquity/blob/main/scripts/validate-autoinstall-user-data.py>`_ in the Subiquity GitHub repository.

Getting Started
^^^^^^^^^^^^^^^

Running the validation script requires downloading the Subiquity source code and installing the development dependencies. First, clone the Subiquity repository and ``cd`` into the root of the repository:

.. code:: none
git clone https://github.com/canonical/subiquity.git && cd subiquity/
Then the required dependencies can be installed by running:

.. code:: none
make install_deps
Now you can invoke the validation script with:

.. code:: none
./scripts/validate-autoinstall-user-data <path-to-config>
or you can feed the configuration data via stdin:


.. code:: none
# a trivial example
cat <config> | ./scripts/validate-autoinstall-user-data
.. warning::

Never run the validation script as ``sudo``.

Finally, after running the validation script it will report the result of the validation attempt:

.. code:: none
$ ./scripts/validate-autoinstall-user-data.py <path-to-config>
Success: The provided autoinstall config validated successfully
You can also use the exit codes to determine the result: 0 (success) or 1 (failure).


Choice of Delivery Method
^^^^^^^^^^^^^^^^^^^^^^^^^

By default the validation script will expect your autoinstall configuration to be passed via cloud-config and expects a valid cloud-config file containing an ``autoinstall`` section:

.. code:: none
#cloud-config
# some cloud-init directives
autoinstall:
# autoinstall directives
This allows you to use the script directly on your cloud-config data. The validation script will extract the autoinstall configuration from the provided cloud-config data and perform the validation on the extracted autoinstall section directly.


If you want to validate autoinstall configurations which will be delivered via the installation media, like the following example:

.. code:: none
autoinstall:
# autoinstall directives
then this can be signalled by passing the ``--no-expect-cloudconfig`` flag. Both formats in this delivery method, with or without a top-level ``autoinstall`` keyword, are supported in this mode.

.. _validator-limitations:

Validator Limitations
---------------------

The autoinstall validator currently has the following limitations:

1. The validator makes an assumption about the target installation media that may not necessarily be true about the actual installation media. It assumes that (1) the installation target is ubuntu-server and (2) the only valid install source is :code:`synthesized`. Some cases where this would cause the validator fail otherwise correct autoinstall configurations:

a. Missing both an :code:`identity` and :code:`user-data` section for a Desktop target, where these sections are fully optional.
b. A :code:`source` section which specifies any :code:`id` other than :code:`synthesized`, where the :code:`id` may really match a valid source on the target ISO.

2. Validity of the data provided in each section is not guaranteed as some sections cannot be reasonably validated outside of the installation runtime environment (e.g., a bad :ref:`match directive <disk_selection_extensions>`).

3. The validator is unable to replicate some of the cloud-config based :ref:`delivery checks <how_the_delivery_is_verified>`. There are some basic checks performed to catch simple delivery-related errors, which you can read more about in the examples section, but the focus of the validation is on the Autoinstall configuration *after* it has been delivered to the installer.

.. note::
See the cloud-init documentation for `how to validate your cloud-config`_.


------------

Examples
--------

Common mistake #1
^^^^^^^^^^^^^^^^^

If a top level ``autoinstall`` keyword is not found in the provided cloud-config during runtime then the installer will miss the autoinstall config and present an interactive session. To prevent occurrences of this issue, the validation script will report a failure if the provided cloud-config does not contain an autoinstall section. *This does not indicate a crash at runtime*, as you can definitely provide cloud-config without autoinstall, but it is a useful result for checking a common formatting mistake.

.. tabs::

.. tab:: Validation output


Validating cloud-config which is missing the ``autoinstall`` keyword:

.. code:: none
$ ./scripts/validate-autoinstall-user-data.py <path-to-config>
AssertionError: Expected data to be wrapped in cloud-config but could not find top level 'autoinstall' key.
Failure: The provided autoinstall config did not validate successfully
.. tab:: Faulty config

As an example, the following cloud-config contains an autoinstall section but has misspelled the ``autoinstall`` keyword:

.. code:: none
#cloud-config
autoinstll:
# autoinstall directives
Common Mistake #2
^^^^^^^^^^^^^^^^^

Another common mistake is to forget the ``#cloud-config`` header in the cloud-config file, which will result in the installer "missing" the autoinstall configuration.

.. tabs::

.. tab:: Validation output

The validator will fail the provided cloud-config data if it does not contain the right header:


.. code:: none
$ ./scripts/validate-autoinstall-user-data.py <path-to-config>
AssertionError: Expected data to be wrapped in cloud-config but first line is not '#cloud-config'. Try passing --no-expect-cloudconfig.
Failure: The provided autoinstall config did not validate successfully
.. tab:: Faulty config

Missing the ``#cloud-config`` header will mean the file is not read by cloud-init:

.. code:: none
autoinstall:
# autoinstall directives
Again, this is not indicative of a real runtime error that would appear. Instead, this case would result in having the installer presenting a fully interactive install where a partially or fully automated installation was desired instead.

Common Mistake #3
^^^^^^^^^^^^^^^^^

Another possible mistake is to think that the autoinstall config on the installation media is a cloud-config datasource (it is not):

.. tabs::

.. tab:: Validation output

When providing the autoinstall configuration using the top-level ``autoinstall`` keyword format, the installer will verify there are no other top-level keys:

.. code:: none
$ ./scripts/validate-autoinstall-user-data.py --no-expect-cloudconfig <path-to-config>
error: subiquity/load_autoinstall_config/read_config: autoinstall.yaml is not a valid cloud config datasource.
No other keys may be present alongside 'autoinstall' at the top level.
Malformed autoinstall in 'top-level keys' section
Failure: The provided autoinstall config did not validate successfully
.. tab:: Faulty config

The following config contains cloud-config directives when it is not expected to contain any:

.. code:: none
#cloud-config
# some cloud-config directives
autoinstall:
# autoinstall directives
Debugging errors
^^^^^^^^^^^^^^^^

By default, the validation script has low verbosity output:

.. code:: none
Malformed autoinstall in 'version or interactive-sections' section
Failure: The provided autoinstall config did not validate successfully
However, you can increase the output level by successively passing the ``-v`` flag. At maximum verbosity, the validation script will report errors the same way they are reported at runtime. This is great for inspecting issues in cases where the short error message isn't yet specific enough to be useful and can be used to inspect specific JSON schema validation errors.


.. code:: none
$ ./scripts/validate-autoinstall-user-data.py autoinstall.yaml -vvv
start: subiquity/load_autoinstall_config:
start: subiquity/load_autoinstall_config/read_config:
finish: subiquity/load_autoinstall_config/read_config: SUCCESS:
start: subiquity/Reporting/load_autoinstall_data:
finish: subiquity/Reporting/load_autoinstall_data: SUCCESS:
start: subiquity/Error/load_autoinstall_data:
finish: subiquity/Error/load_autoinstall_data: SUCCESS:
start: subiquity/core_validation:
finish: subiquity/core_validation: FAIL: Malformed autoinstall in 'version or interactive-sections' section
finish: subiquity/load_autoinstall_config: FAIL: Malformed autoinstall in 'version or interactive-sections' section
Malformed autoinstall in 'version or interactive-sections' section
Traceback (most recent call last):
File ".../subiquity/scripts/../subiquity/server/server.py", line 654, in validate_autoinstall
jsonschema.validate(self.autoinstall_config, self.base_schema)
File "/usr/lib/python3/dist-packages/jsonschema/validators.py", line 1080, in validate
raise error
jsonschema.exceptions.ValidationError: '*' is not of type 'array'
Failed validating 'type' in schema['properties']['interactive-sections']:
{'items': {'type': 'string'}, 'type': 'array'}
On instance['interactive-sections']:
'*'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".../subiquity/./scripts/validate-autoinstall-user-data.py", line 186, in verify_autoinstall
app.load_autoinstall_config(only_early=True, context=None)
File ".../subiquity/scripts/../subiquitycore/context.py", line 159, in decorated_sync
return meth(self, **kw)
^^^^^^^^^^^^^^^^
File ".../subiquity/scripts/../subiquity/server/server.py", line 734, in load_autoinstall_config
self.validate_autoinstall()
File ".../subiquity/scripts/../subiquity/server/server.py", line 663, in validate_autoinstall
raise new_exception from original_exception
subiquity.server.autoinstall.AutoinstallValidationError: Malformed autoinstall in 'version or interactive-sections' section
Failure: The provided autoinstall config did not validate successfully
In this case, the above output shows that ``interactive-sections`` section failed to validate against the JSON schema because the type provided was a ``string`` and not an ``array`` of ``string`` s.

.. LINKS
.. _how to validate your cloud-config: https://cloudinit.readthedocs.io/en/latest/howto/debug_user_data.html
1 change: 1 addition & 0 deletions doc/howto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Getting started with autoinstall
autoinstall-quickstart-s390x
basic-server-installation
configure-storage
autoinstall-validation

Found a problem?
----------------
Expand Down
2 changes: 2 additions & 0 deletions doc/reference/autoinstall-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,8 @@ An example storage section:
The extensions to the curtin syntax allow for disk selection and partition or logical-volume sizing.

.. _disk_selection_extensions:

Disk selection extensions
^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
26 changes: 21 additions & 5 deletions doc/reference/autoinstall-schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@
Autoinstall schema
==================

The server installer validates the provided autoinstall configuration against a :ref:`JSON schema<autoinstall_JSON_schema>`.
The server installer validates the provided autoinstall configuration against a :ref:`JSON schema<autoinstall_JSON_schema>`. The end of this reference manual presents the schema as a single document which could be used to manually pre-validate an autoinstall configuration, however the actual runtime validation process is more involved than a simple JSON schema validation. See the provided :doc:`pre-validation script <../howto/autoinstall-validation>` for how to perform autoinstall pre-validation.

.. _how_the_delivery_is_verified:

How the delivery is verified
----------------------------

To ensure expected runtime behaviour after delivering the autoinstall config, the installer performs some sanity checks to ensure one delivery method is not confused for another.

cloud-config
^^^^^^^^^^^^

When passing autoinstall via cloud-config, the installer will inspect the cloud-config data for any autoinstall-specific keywords outside of the top-level ``autoinstall`` keyword in the config and throw an error if any are encountered. If there are no misplaced keys, the data within the ``autoinstall`` section is passed to the installer.


Installation Media
^^^^^^^^^^^^^^^^^^

When passing autoinstall via the installation media and using the top-level ``autoinstall`` keyword format, the installer will inspect the passed autoinstall file to guarantee that there are no other top-level keys. This check guarantees that the autoinstall config is not mistaken for a cloud-config datasource.

How the configuration is validated
----------------------------------

This reference manual presents the schema as a single document. Use it pre-validate your configuration.

At run time, the configuration is not validated against this document. Instead, configuration sections are loaded and validated in this order:
After the configuration has been delivered to the installer successfully, the configuration sections are loaded and validated in this order:

1. The reporting section is loaded, validated and applied.
2. The error commands are loaded and validated.
Expand All @@ -35,7 +51,7 @@ Regeneration

To regenerate the schema, run ``make schema`` in the root directory of the `Subiquity source repository`_.

.. LINKS
.. LINKS
.. _JSON schema: https://json-schema.org/
.. _Subiquity source repository: https://github.com/canonical/subiquity
6 changes: 3 additions & 3 deletions doc/tutorial/providing-autoinstall.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The suggested way of providing autoinstall configuration to the Ubuntu installer

The autoinstall configuration is provided via cloud-init configuration, which is almost endlessly flexible. In most scenarios the easiest way will be to provide user data via the :external+cloud-init:ref:`datasource_nocloud` data source.

When providing autoinstall via cloud-init, the autoinstall configuration is provided as :external+cloud-init:ref:`user_data_formats-cloud_config`. This means it requires a :code:`#cloud-config` header. The autoinstall directives are placed under a top level :code:`autoinstall:` key:
When providing autoinstall via cloud-init, the autoinstall configuration is provided as :external+cloud-init:ref:`user_data_formats-cloud_config`. This means the file requires a :code:`#cloud-config` header and the autoinstall directives are placed under a top level :code:`autoinstall:` key:

.. code-block:: yaml
Expand All @@ -47,7 +47,7 @@ The autoinstall configuration provided in this way is passed to the Ubuntu insta
version: 1
....
Starting in 24.04 (Noble), to be consistent with the cloud-config based format, a top-level :code:`autoinstall:` keyword is allowed:
Starting in 24.04 (Noble), to be consistent with the cloud-config based format, a single top-level :code:`autoinstall:` keyword is allowed:

.. code-block:: yaml
Expand All @@ -69,7 +69,7 @@ Alternatively, you can pass the location of the autoinstall file on the kernel c
Order of precedence for autoinstall locations
---------------------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Because there are many ways to specify the autoinstall file, it may happen that multiple locations are specified at the same time. Subiquity searches for the autoinstall file in the following order and uses the first existing one:

Expand Down
2 changes: 1 addition & 1 deletion scripts/runtests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ validate () {
answers-core-desktop|answers-uc24)
;;
*)
python3 scripts/validate-autoinstall-user-data.py < $tmpdir/var/log/installer/autoinstall-user-data
python3 scripts/validate-autoinstall-user-data.py --legacy --check-link < $tmpdir/var/log/installer/autoinstall-user-data
# After the lunar release and the introduction of mirror testing, it
# came to our attention that new Ubuntu installations have the security
# repository configured with the primary mirror URL (i.e.,
Expand Down
Loading

0 comments on commit 1495b36

Please sign in to comment.