Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow numbers in partition names and namespaces #790

Merged
merged 3 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 6 additions & 3 deletions craft_parts/lifecycle_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2021-2023 Canonical Ltd.
# Copyright 2021-2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand Down Expand Up @@ -76,8 +76,11 @@ class LifecycleManager:
matching this name.
:param project_vars: A dictionary containing project variables.
:param partitions: A list of partitions to use when the partitions feature is
enabled. The first partition must be "default" and all partitions must be
lowercase alphabetical.
enabled. The first partition must be "default" and partitions must contain only
lowercase alphabetical and numbers. Partitions may have an optional namespace
prefix separated by a forward slash. Namespaces must contain only lowercase
alphabetical and numbers and namespaced partitions must only contain lowercase
alphabetical, numbers, and hyphens.
mr-cal marked this conversation as resolved.
Show resolved Hide resolved
:param custom_args: Any additional arguments that will be passed directly
to callbacks.
"""
Expand Down
23 changes: 14 additions & 9 deletions craft_parts/utils/partition_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,28 @@
from craft_parts import errors, features

# regex for a valid partition name
_VALID_PARTITION_REGEX = re.compile(r"[a-z]+", re.ASCII)
_VALID_PARTITION_REGEX = re.compile(r"[a-z0-9]+", re.ASCII)

# regex for a valid namespaced partition name
_VALID_NAMESPACED_PARTITION_REGEX = re.compile(r"[a-z]+/(?!-)[a-z\-]+(?<!-)", re.ASCII)
_VALID_NAMESPACED_PARTITION_REGEX = re.compile(
r"[a-z0-9]+/(?!-)[a-z0-9\-]+(?<!-)", re.ASCII
)


def validate_partition_names(partitions: Optional[Sequence[str]]) -> None:
"""Validate the partition feature set.

If the partition feature is enabled, then:
- the first partition must be "default"
- each partition must contain only lowercase alphabetical characters
- each partition must contain only lowercase alphabetical characters and numbers
mr-cal marked this conversation as resolved.
Show resolved Hide resolved
- partitions are unique

Namespaced partitions can also be validated in addition to regular (or
'non-namespaced') partitions. The format is `<namespace>/<partition>`.

Namespaced partitions have the following naming convention:
- the namespace must contain only lowercase alphabetical characters
- the partition must contain only lowercase alphabetical characters and hyphens
- the namespace must contain only lowercase alphabetical characters and numbers
- the partition must contain only lowercase alphabetical characters, numbers, and hyphens
mr-cal marked this conversation as resolved.
Show resolved Hide resolved
- the partition cannot begin or end with a hyphen

:param partitions: Partition data to verify.
Expand Down Expand Up @@ -109,15 +111,16 @@ def _validate_partition_naming_convention(partitions: Sequence[str]) -> None:
message=f"Namespaced partition {partition!r} is invalid.",
details=(
"Namespaced partitions are formatted as `<namespace>/"
"<partition>`. Namespaces must only contain lowercase letters. "
"Namespaced partitions must only contain lowercase letters and "
"hyphens and cannot start or end with a hyphen."
"<partition>`. Namespaces must only contain lowercase letters "
"and numbers. Namespaced partitions must only contain lowercase "
"letters, numbers, and hyphens and cannot start or end with a "
"hyphen."
),
)

raise errors.FeatureError(
message=f"Partition {partition!r} is invalid.",
details="Partitions must only contain lowercase letters.",
details="Partitions must only contain lowercase letters and numbers.",
)


Expand All @@ -127,6 +130,8 @@ def _validate_namespace_conflicts(partitions: Sequence[str]) -> None:
For example, `foo` conflicts in ['default', 'foo', 'foo/bar'].
Assumes partition names are valid.

This does not catch conflicts with hyphens in partitions (#789).

:raises FeatureError: for namespace conflicts
"""
namespaced_partitions: Set[str] = set()
Expand Down
1 change: 1 addition & 0 deletions docs/common/craft-parts/craft-parts.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Multipass
NOOP
NPM
NVM
Namespaces
Namespaced
NetworkRequestError
NilPlugin
Expand Down
13 changes: 7 additions & 6 deletions tests/unit/features/partitions/executor/test_environment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2023 Canonical Ltd.
# Copyright 2023-2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -23,14 +23,15 @@
[
(["default"], {"CRAFT_DEFAULT_STAGE", "CRAFT_DEFAULT_PRIME"}),
(
["default", "abc", "foo/bar-baz"],
# exercise lowercase alphabetical, numbers, and hyphens
["default", "abc123", "foo1/bar-baz2"],
{
"CRAFT_DEFAULT_STAGE",
"CRAFT_DEFAULT_PRIME",
"CRAFT_ABC_STAGE",
"CRAFT_ABC_PRIME",
"CRAFT_FOO_BAR_BAZ_STAGE",
"CRAFT_FOO_BAR_BAZ_PRIME",
"CRAFT_ABC123_STAGE",
"CRAFT_ABC123_PRIME",
"CRAFT_FOO1_BAR_BAZ2_STAGE",
"CRAFT_FOO1_BAR_BAZ2_PRIME",
},
),
],
Expand Down
11 changes: 5 additions & 6 deletions tests/unit/features/partitions/test_lifecycle_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""Unit tests for the lifecycle manager with the partitions feature."""
import sys
import textwrap
from string import ascii_lowercase
from string import ascii_lowercase, digits
from textwrap import dedent
from typing import Any, Dict

Expand All @@ -33,7 +33,9 @@

def valid_non_namespaced_partition_strategy():
"""A strategy for a list of valid non-namespaced partitions."""
return strategies.text(strategies.sampled_from(ascii_lowercase), min_size=1)
return strategies.text(
strategies.sampled_from(ascii_lowercase + digits), min_size=1
)


@strategies.composite
Expand All @@ -44,7 +46,7 @@ def valid_namespaced_partition_strategy(draw):
)

partition_strategy = strategies.text(
strategies.sampled_from(ascii_lowercase + "-"), min_size=1
strategies.sampled_from(ascii_lowercase + digits + "-"), min_size=1
).filter(
lambda partition: (
not partition.startswith("-")
Expand Down Expand Up @@ -197,7 +199,6 @@ def test_partitions_default_not_first(self, new_dir, parts_data, partitions):
["default", "-"],
["default", "Test"],
["default", "TEST"],
["default", "test1"],
["default", "te-st"],
pytest.param(
["default", "tеst"], # noqa: RUF001 (ambiguous character)
Expand Down Expand Up @@ -244,10 +245,8 @@ def test_partitions_invalid(self, new_dir, parts_data, partitions):
["default", "-a-/b"],
["default", "a/Test"],
["default", "a/TEST"],
["default", "a/test1"],
["default", "Test/a"],
["default", "TEST/a"],
["default", "test1/a"],
["default", "te-st/a"],
pytest.param(
["default", "test/ο"], # noqa: RUF001 (ambiguous character)
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/features/partitions/test_parts.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ def invalid_fileset(self):
"""Return a fileset of invalid uses of partition names.

These filepaths are not necessarily violating the naming convention for
partitions, but the partitions here are unknown (and thus invalid) to a
LifecycleManager was created with the partitions "default", "a/b", "a/c-d",
and "kernel".
partitions, but the partitions here are unknown and thus invalid to a
LifecycleManager that was created with the partitions "default", "a/b",
"a/c-d", and "kernel".
"""
return [
# unknown partition names
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/utils/test_partition_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ def test_validate_partitions_failure_feature_disabled(partitions, message):
[
["default"],
["default", "mypart"],
["default", "mypart1"],
["default", "mypart", "test/foo"],
["default", "mypart", "test1/foo2"],
["default", "mypart", "test/foo-bar"],
["default", "mypart", "test1/foo-bar2"],
],
)
def test_validate_partitions_success_feature_enabled(partitions):
"""Test validation of partition names."""
partition_utils.validate_partition_names(partitions)


Expand Down
Loading