Skip to content

Commit

Permalink
schema: migrate cc_apk_configure and add schema tests
Browse files Browse the repository at this point in the history
  • Loading branch information
blackboxsw committed Jan 13, 2022
1 parent 1710170 commit 3bf0220
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 103 deletions.
103 changes: 2 additions & 101 deletions cloudinit/config/cc_apk_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from cloudinit import log as logging
from cloudinit import temp_utils, templater, util
from cloudinit.config.schema import get_meta_doc, validate_cloudconfig_schema
from cloudinit.config.schema import get_meta_doc
from cloudinit.settings import PER_INSTANCE

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -102,104 +102,7 @@
"frequency": frequency,
}

schema = {
"type": "object",
"properties": {
"apk_repos": {
"type": "object",
"properties": {
"preserve_repositories": {
"type": "boolean",
"default": False,
"description": dedent(
"""\
By default, cloud-init will generate a new repositories
file ``/etc/apk/repositories`` based on any valid
configuration settings specified within a apk_repos
section of cloud config. To disable this behavior and
preserve the repositories file from the pristine image,
set ``preserve_repositories`` to ``true``.
The ``preserve_repositories`` option overrides
all other config keys that would alter
``/etc/apk/repositories``.
"""
),
},
"alpine_repo": {
"type": ["object", "null"],
"properties": {
"base_url": {
"type": "string",
"default": DEFAULT_MIRROR,
"description": dedent(
"""\
The base URL of an Alpine repository, or
mirror, to download official packages from.
If not specified then it defaults to ``{}``
""".format(
DEFAULT_MIRROR
)
),
},
"community_enabled": {
"type": "boolean",
"default": False,
"description": dedent(
"""\
Whether to add the Community repo to the
repositories file. By default the Community
repo is not included.
"""
),
},
"testing_enabled": {
"type": "boolean",
"default": False,
"description": dedent(
"""\
Whether to add the Testing repo to the
repositories file. By default the Testing
repo is not included. It is only recommended
to use the Testing repo on a machine running
the ``Edge`` version of Alpine as packages
installed from Testing may have dependancies
that conflict with those in non-Edge Main or
Community repos."
"""
),
},
"version": {
"type": "string",
"description": dedent(
"""\
The Alpine version to use (e.g. ``v3.12`` or
``edge``)
"""
),
},
},
"required": ["version"],
"minProperties": 1,
"additionalProperties": False,
},
"local_repo_base_url": {
"type": "string",
"description": dedent(
"""\
The base URL of an Alpine repository containing
unofficial packages
"""
),
},
},
"minProperties": 1, # Either preserve_repositories or alpine_repo
"additionalProperties": False,
}
},
}

__doc__ = get_meta_doc(meta, schema)
__doc__ = get_meta_doc(meta)


def handle(name, cfg, cloud, log, _args):
Expand All @@ -222,8 +125,6 @@ def handle(name, cfg, cloud, log, _args):
)
return

validate_cloudconfig_schema(cfg, schema)

# If "preserve_repositories" is explicitly set to True in
# the configuration do nothing.
if util.get_cfg_option_bool(apk_section, "preserve_repositories", False):
Expand Down
49 changes: 49 additions & 0 deletions config/cloud-init-schema-1.0.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,54 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$defs": {
"cc_apk_configure": {
"type": "object",
"properties": {
"apk_repos": {
"type": "object",
"properties": {
"preserve_repositories": {
"type": "boolean",
"default": false,
"description": "By default, cloud-init will generate a new repositories file ``/etc/apk/repositories`` based on any valid configuration settings specified within a apk_repos section of cloud config. To disable this behavior and preserve the repositories file from the pristine image, set ``preserve_repositories`` to ``true``.\n\n The ``preserve_repositories`` option overrides all other config keys that would alter ``/etc/apk/repositories``."
},
"alpine_repo": {
"type": ["object", "null"],
"properties": {
"base_url": {
"type": "string",
"default": "https://alpine.global.ssl.fastly.net/alpine",
"description": "The base URL of an Alpine repository, or mirror, to download official packages from. If not specified then it defaults to ``https://alpine.global.ssl.fastly.net/alpine``"
},
"community_enabled": {
"type": "boolean",
"default": false,
"description": "Whether to add the Community repo to the repositories file. By default the Community repo is not included."
},
"testing_enabled": {
"type": "boolean",
"default": false,
"description": "Whether to add the Testing repo to the repositories file. By default the Testing repo is not included. It is only recommended to use the Testing repo on a machine running the ``Edge`` version of Alpine as packages installed from Testing may have dependancies that conflict with those in non-Edge Main or Community repos."
},
"version": {
"type": "string",
"description": "The Alpine version to use (e.g. ``v3.12`` or ``edge``)"
}
},
"required": ["version"],
"minProperties": 1,
"additionalProperties": false
},
"local_repo_base_url": {
"type": "string",
"description": "The base URL of an Alpine repository containing unofficial packages"
}
},
"minProperties": 1,
"additionalProperties": false
}
}
},
"cc_apt_pipelining": {
"type": "object",
"properties": {
Expand All @@ -15,6 +63,7 @@
}
},
"allOf": [
{ "$ref": "#/$defs/cc_apk_configure" },
{ "$ref": "#/$defs/cc_apt_pipelining" }
]
}
96 changes: 95 additions & 1 deletion tests/unittests/config/test_cc_apk_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,25 @@

import logging
import os
import re
import textwrap
from pathlib import Path

import pytest

from cloudinit import cloud, helpers, util
from cloudinit.config import cc_apk_configure
from tests.unittests.helpers import FilesystemMockingTestCase, mock
from cloudinit.config.schema import (
SchemaValidationError,
get_schema,
validate_cloudconfig_schema,
)
from tests.unittests.helpers import (
FilesystemMockingTestCase,
cloud_init_project_dir,
mock,
skipUnlessJsonSchema,
)

REPO_FILE = "/etc/apk/repositories"
DEFAULT_MIRROR_URL = "https://alpine.global.ssl.fastly.net/alpine"
Expand Down Expand Up @@ -322,4 +336,84 @@ def test_edge_main_community_testing_local_repos(self):
self.assertEqual(expected_content, util.load_file(REPO_FILE))


class TestApkConfigureSchema:
@pytest.mark.parametrize(
"config, error_msg",
(
# Valid schemas
({"apk_repos": {"preserve_repositories": True}}, None),
({"apk_repos": {"alpine_repo": None}}, None),
({"apk_repos": {"alpine_repo": {"version": "v3.21"}}}, None),
(
{
"apk_repos": {
"alpine_repo": {
"base_url": "http://yep",
"community_enabled": True,
"testing_enabled": True,
"version": "v3.21",
}
}
},
None,
),
({"apk_repos": {"local_repo_base_url": "http://some"}}, None),
# Invalid schemas
(
{"apk_repos": {"alpine_repo": {"version": False}}},
"apk_repos.alpine_repo.version: False is not of type"
" 'string'",
),
(
{
"apk_repos": {
"alpine_repo": {"version": "v3.12", "bogus": 1}
}
},
re.escape(
"apk_repos.alpine_repo: Additional properties are not"
" allowed ('bogus' was unexpected)"
),
),
(
{"apk_repos": {"alpine_repo": {}}},
"apk_repos.alpine_repo: 'version' is a required property,"
" apk_repos.alpine_repo: {} does not have enough properties",
),
(
{"apk_repos": {"alpine_repo": True}},
"apk_repos.alpine_repo: True is not of type 'object', 'null'",
),
(
{"apk_repos": {"preserve_repositories": "wrongtype"}},
"apk_repos.preserve_repositories: 'wrongtype' is not of type"
" 'boolean'",
),
(
{"apk_repos": {}},
"apk_repos: {} does not have enough properties",
),
(
{"apk_repos": {"local_repo_base_url": None}},
"apk_repos.local_repo_base_url: None is not of type 'string'",
),
),
)
@skipUnlessJsonSchema()
@mock.patch("cloudinit.config.schema.read_cfg_paths")
def test_schema_validation(self, read_cfg_paths, config, error_msg, paths):
read_cfg_paths.return_value = paths
# New-style schema $defs exist in config/cloud-init-schema*.json
for schema_file in Path(cloud_init_project_dir("config/")).glob(
"cloud-init-schema*.json"
):
util.copy(schema_file, paths.schema_dir)
schema = get_schema()
if error_msg is None:
validate_cloudconfig_schema(config, schema, strict=True)
else:
with pytest.raises(SchemaValidationError, match=error_msg):
validate_cloudconfig_schema(config, schema, strict=True)


# vi: ts=4 expandtab
2 changes: 1 addition & 1 deletion tests/unittests/config/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def test_get_schema_coalesces_known_schema(self, read_cfg_paths, paths):
assert ["$defs", "$schema", "allOf"] == sorted(list(schema.keys()))
# New style schema should be defined in static schema file in $defs
expected_subschema_defs = [
{"$ref": "#/$defs/cc_apk_configure"},
{"$ref": "#/$defs/cc_apt_pipelining"},
]
found_subschema_defs = []
Expand All @@ -157,7 +158,6 @@ def test_get_schema_coalesces_known_schema(self, read_cfg_paths, paths):
assert expected_subschema_defs == found_subschema_defs
# This list will dwindle as we move legacy schema to new $defs
assert [
"apk_repos",
"apt",
"bootcmd",
"chef",
Expand Down

0 comments on commit 3bf0220

Please sign in to comment.