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
2 changes: 2 additions & 0 deletions craft_providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .errors import ProviderError
from .executor import Executor
from .provider import Provider
from .bases._factory import get_base

try:
from ._version import __version__
Expand All @@ -38,4 +39,5 @@
"Executor",
"ProviderError",
"Provider",
"get_base",
]
13 changes: 13 additions & 0 deletions craft_providers/bases/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
from enum import Enum
import sys
from typing import Literal, NamedTuple, overload
import warnings

from craft_providers.errors import BaseCompatibilityError, BaseConfigurationError
from craft_providers.base import Base

from . import almalinux, centos
from . import ubuntu
from . import ubuntu as buildd
from ._factory import get_base
from .checks import ensure_guest_compatible
from .ubuntu import BuilddBase, BuilddBaseAlias

Expand All @@ -46,6 +48,7 @@
"BuilddBaseAlias",
"BaseCompatibilityError",
"BaseConfigurationError",
"get_base",
]


Expand Down Expand Up @@ -88,6 +91,11 @@ def get_base_alias(
def get_base_alias(base_name: BaseName) -> BaseAlias: ...
def get_base_alias(base_name: tuple[str, str]) -> BaseAlias:
"""Return a Base alias from a base (name, version) tuple."""
warnings.warn(
"get_base_alias is deprecated. Use craft_providers.get_base instead.",
category=DeprecationWarning,
stacklevel=2,
)
base_name = BaseName(*base_name)
if base_name.name == "ubuntu" and base_name in BASE_NAME_TO_BASE_ALIAS:
return BASE_NAME_TO_BASE_ALIAS[base_name]
Expand All @@ -110,6 +118,11 @@ def get_base_from_alias(
) -> type[almalinux.AlmaLinuxBase]: ...
def get_base_from_alias(alias: BaseAlias) -> type[Base[Enum]]:
"""Return a Base class from a known base alias."""
warnings.warn(
"get_base_from_alias is deprecated. Use craft_providers.get_base instead.",
category=DeprecationWarning,
stacklevel=2,
)
match alias:
case ubuntu.BuilddBaseAlias():
return ubuntu.BuilddBase
Expand Down
160 changes: 160 additions & 0 deletions craft_providers/bases/_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Copyright 2025 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""A factory for providing bases."""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal, overload

if TYPE_CHECKING:
import enum
import pathlib

from craft_providers.actions.snap_installer import Snap
from craft_providers.base import Base

# These are used in code, but we import them there so we don't have to import
# them if they don't get used.
from craft_providers.bases.almalinux import AlmaLinuxBase
from craft_providers.bases.centos import CentOSBase
from craft_providers.bases.ubuntu import BuilddBase


@overload
def get_base(
*,
distribution: Literal["almalinux"],
series: str,
compatibility_tag: str | None = None,
environment: dict[str, str | None] | None = None,
hostname: str = "craft-instance",
snaps: list[Snap] | None = None,
packages: list[str] | None = None,
use_default_packages: bool = True,
cache_path: pathlib.Path | None = None,
) -> AlmaLinuxBase: ...
@overload
def get_base(
*,
distribution: Literal["centos"],
series: str,
compatibility_tag: str | None = None,
environment: dict[str, str | None] | None = None,
hostname: str = "craft-instance",
snaps: list[Snap] | None = None,
packages: list[str] | None = None,
use_default_packages: bool = True,
cache_path: pathlib.Path | None = None,
) -> CentOSBase: ...
@overload
def get_base(
*,
distribution: Literal["ubuntu"],
series: str,
compatibility_tag: str | None = None,
environment: dict[str, str | None] | None = None,
hostname: str = "craft-instance",
snaps: list[Snap] | None = None,
packages: list[str] | None = None,
use_default_packages: bool = True,
cache_path: pathlib.Path | None = None,
) -> BuilddBase: ...
@overload
def get_base(
*,
distribution: str,
series: str,
compatibility_tag: str | None = None,
environment: dict[str, str | None] | None = None,
hostname: str = "craft-instance",
snaps: list[Snap] | None = None,
packages: list[str] | None = None,
use_default_packages: bool = True,
cache_path: pathlib.Path | None = None,
) -> Base[enum.Enum]: ...
def get_base( # noqa: PLR0913
*,
distribution: str,
series: str,
compatibility_tag: str | None = None,
environment: dict[str, str | None] | None = None,
hostname: str = "craft-instance",
snaps: list[Snap] | None = None,
packages: list[str] | None = None,
use_default_packages: bool = True,
cache_path: pathlib.Path | None = None,
) -> Base[enum.Enum]:
"""Get a base according to the provided distribution and series.

:param distribution: The distribution of the base (e.g. ubuntu)
:param series: The series of the base (e.g. 26.04)
:param compatibility_tag: Tag/Version for variant of build configuration and
setup. Any change to this version would indicate that prior [versioned]
instances are incompatible and must be cleaned. As such, any new value
should be unique to old values (e.g. incrementing). It is suggested to
extend this tag, not overwrite it, e.g.: compatibility_tag =
f"{appname}-{Base.compatibility_tag}.{apprevision}" to ensure base
compatibility levels are maintained.
:param environment: Environment to set in /etc/environment.
:param hostname: Hostname to configure.
:param snaps: Optional list of snaps to install on the base image.
:param packages: Optional list of system packages to install on the base image.
:param use_default_packages: Optional bool to enable/disable default packages.
:param cache_path: Optional path to the shared cache directory. If this is
provided, shared cache directories will be mounted as appropriate.
"""
# We're importing within the function here so we don't have to import bases
# that we aren't going to use.
alias: BuilddBaseAlias | AlmaLinuxBaseAlias | CentOSBaseAlias
cls: type[BuilddBase | AlmaLinuxBase | CentOSBase]
match distribution:
case "ubuntu":
from .ubuntu import BuilddBase, BuilddBaseAlias # noqa: PLC0415

try:
alias = BuilddBaseAlias(series)
except ValueError:
raise ValueError(f"Unknown Ubuntu series: {series}") from None
cls = BuilddBase
case "almalinux":
from .almalinux import AlmaLinuxBase, AlmaLinuxBaseAlias # noqa: PLC0415

try:
alias = AlmaLinuxBaseAlias(series)
except ValueError:
raise ValueError(f"Unknown Alma Linux series: {series}") from None
cls = AlmaLinuxBase
case "centos":
from .centos import CentOSBase, CentOSBaseAlias # noqa: PLC0415

try:
alias = CentOSBaseAlias(series)
except ValueError:
raise ValueError(f"Unknown CentOS series: {series}") from None
cls = CentOSBase
case _:
raise ValueError(f"Unknown distribution {distribution!r}")

return cls(
# Ignore argument type here because we set the matching pair above.
alias=alias, # type: ignore[arg-type]
compatibility_tag=compatibility_tag,
environment=environment,
hostname=hostname,
snaps=snaps,
packages=packages,
use_default_packages=use_default_packages,
cache_path=cache_path,
)
2 changes: 1 addition & 1 deletion craft_providers/lxd/lxc.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _run_lxc(
check: bool = True,
project: str | None = None,
stdin: StdinType = StdinType.INTERACTIVE,
text: Literal[False, None] = None,
text: Literal[False] | None = None,
encoding: None = None,
errors: None = None,
**kwargs: Any,
Expand Down
14 changes: 13 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,21 @@

# These axes were empty, so hide their scaffolding for now
"tutorials",
"how-to-guides",
]

# Links to ignore when checking links
linkcheck_ignore = [
# GNU's site is a bit unreliable
"https://www.gnu.org/.*",
# https://github.com/rust-lang/crates.io/issues/788
"https://crates.io/",
# Ignore releases, since we'll include the next release before it exists.
"https://github.com/canonical/[a-z]*craft[a-z-]*/releases/.*",
# returns a 403 from GitHub CI
"https://rsync.samba.org",
]


extensions.extend(
[
"sphinx.ext.autodoc",
Expand Down
2 changes: 2 additions & 0 deletions docs/how-to-guides/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ How-to guides

.. toctree::
:maxdepth: 1

launch-instance
52 changes: 52 additions & 0 deletions docs/how-to-guides/launch-instance.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.. _how-to-launch:

Launch a VM or container
========================

In order to launch a virtual machine or container with Craft Providers, you must know:

1. Which provider to use.
2. What distribution and series name you want to launch.

The provider is an instance of any :py:class:`~craft_providers.provider.Provider`
subclass. Craft Providers provides
:py:class:`~craft_providers.lxd.lxd_provider.LXDProvider` and
:py:class:`~craft_providers.multipass.multipass_provider.MultipassProvider` classes
for this purpose.

.. code-block:: python

from craft_providers.lxd.lxd_provider import LXDProvider

provider = LXDProvider(project="my-project")

Each Provider class has a
:py:class:`~craft_providers.provider.Provider.launched_environment` context manager,
which provides an :py:class:`~craft_providers.executor.Executor` instance. It is
just a matter of passing some project details and the base to this provider. The
:py:func:`~craft_providers.get_base` function provides a convenient way to get a base
object from its distribution name and series.

.. code-block:: python

import pathlib
import craft_providers

base = craft_providers.get_base(distribution="ubuntu", series="24.04")

with provider.launched_environment(
project_name="my-project",
project_path=pathlib.Path(),
base_configuration=base,
instance_name="my-instance",
) as executor:
instance_info = executor.execute_run(
["cat", "/etc/os-release"],
capture_output=True,
text=True,
).stdout

When the context of a launched environment is exited, Craft Providers will shut down
the provider according to the value of the ``shutdown_delay_mins`` parameter.
If no shutdown delay is specified, the provider will be shut down while exiting the
context manager.
4 changes: 4 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ framework that need to provide support for additional build environments.
:maxdepth: 1
:hidden:

how-to-guides/index
reference/index
explanation/index

.. list-table::

* - |
- | :ref:`How-to Guides <how-to-guides>`
| **Step-by-step guides** covering key operations and common tasks
* - | :ref:`Reference <reference>`
| **Technical information** about Craft Providers
- | :ref:`Explanation <explanation>`
Expand Down
10 changes: 10 additions & 0 deletions docs/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ Changelog
See the `Releases page`_ on GitHub for a complete list of commits that are
included in each version.

.. _release-3.2.0:

3.2.0 (unreleased)
------------------

- Add a

For a complete list of commits, check out the `3.2.0`_ release on GitHub.

3.1.0 (2025-09-08)
------------------

Expand Down Expand Up @@ -467,3 +476,4 @@ Note: The provided name for a LXD executor object is converted to comply with
- Update documentation

.. _Releases page: https://github.com/canonical/craft-providers/releases
.. _3.2.0: https://github.com/canonical/craft-providers/releases/tag/3.2.0
2 changes: 2 additions & 0 deletions docs/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Reference
changelog
executors

.. autofunction:: craft_providers.get_base

Indices and tables
------------------

Expand Down
Loading
Loading