Skip to content

Commit

Permalink
sources: add the plain file source handler (#315)
Browse files Browse the repository at this point in the history
* sources: add the plain file source handler

Expose source type `file` to use source files that don't require
provisioning.

Signed-off-by: Claudio Matsuoka <[email protected]>
  • Loading branch information
cmatsuoka authored Nov 8, 2022
1 parent f0a54d7 commit 25a7cd3
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 0 deletions.
83 changes: 83 additions & 0 deletions craft_parts/sources/file_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2022 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, see <http://www.gnu.org/licenses/>.

"""Implement the plain file source handler."""

from pathlib import Path
from typing import List, Optional

from overrides import overrides

from craft_parts.dirs import ProjectDirs

from . import errors
from .base import FileSourceHandler


class FileSource(FileSourceHandler):
"""The plain file source handler."""

# pylint: disable-next=too-many-arguments
def __init__(
self,
source: str,
part_src_dir: Path,
*,
cache_dir: Path,
source_tag: Optional[str] = None,
source_commit: Optional[str] = None,
source_branch: Optional[str] = None,
source_depth: Optional[int] = None,
source_submodules: Optional[List[str]] = None,
source_checksum: Optional[str] = None,
project_dirs: Optional[ProjectDirs] = None,
ignore_patterns: Optional[List[str]] = None,
):
super().__init__(
source,
part_src_dir,
cache_dir=cache_dir,
source_commit=source_commit,
source_tag=source_tag,
source_branch=source_branch,
source_depth=source_depth,
source_checksum=source_checksum,
source_submodules=source_submodules,
project_dirs=project_dirs,
ignore_patterns=ignore_patterns,
)

if source_commit:
raise errors.InvalidSourceOption(source_type="file", option="source-commit")

if source_tag:
raise errors.InvalidSourceOption(source_type="file", option="source-tag")

if source_depth:
raise errors.InvalidSourceOption(source_type="file", option="source-depth")

if source_branch:
raise errors.InvalidSourceOption(source_type="file", option="source-branch")

@overrides
def provision(
self,
dst: Path,
keep: bool = False,
src: Optional[Path] = None,
) -> None:
"""Process the source file to extract its payload."""
# No payload to extract
2 changes: 2 additions & 0 deletions craft_parts/sources/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
from . import errors
from .base import SourceHandler
from .deb_source import DebSource
from .file_source import FileSource
from .git_source import GitSource
from .local_source import LocalSource
from .snap_source import SnapSource
Expand All @@ -107,6 +108,7 @@
"snap": SnapSource,
"zip": ZipSource,
"deb": DebSource,
"file": FileSource,
}


Expand Down
70 changes: 70 additions & 0 deletions tests/integration/sources/test_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2022 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, see <http://www.gnu.org/licenses/>.

import textwrap
from pathlib import Path

import pytest
import yaml

import craft_parts
from craft_parts import Action, Step


def test_source_file(new_dir):
_parts_yaml = textwrap.dedent(
"""\
parts:
foo:
plugin: make
source: foobar
source-type: file
"""
)

Path("foobar").write_text("content")

parts = yaml.safe_load(_parts_yaml)
lf = craft_parts.LifecycleManager(
parts, application_name="test_file", cache_dir=new_dir
)

with lf.action_executor() as ctx:
ctx.execute(Action("foo", Step.PULL))

foo_src_dir = Path("parts", "foo", "src")
assert Path(foo_src_dir, "foobar").read_text() == "content"


def test_source_file_error(new_dir):
_parts_yaml = textwrap.dedent(
"""\
parts:
foo:
plugin: make
source: foobar
source-type: file
"""
)

parts = yaml.safe_load(_parts_yaml)
Path("foobar").mkdir() # not a file
lf = craft_parts.LifecycleManager(
parts, application_name="test_file", cache_dir=new_dir
)

with pytest.raises(IsADirectoryError), lf.action_executor() as ctx:
ctx.execute(Action("foo", Step.PULL))
65 changes: 65 additions & 0 deletions tests/unit/sources/test_file_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2022 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, see <http://www.gnu.org/licenses/>.

from pathlib import Path

import pytest
import requests

from craft_parts.sources import sources


@pytest.mark.http_request_handler("FakeFileHTTPRequestHandler")
class TestFileSource:
"""Tests for the plain file source handler."""

def test_pull_file_must_download_to_sourcedir(self, new_dir, mocker, http_server):
dest_dir = Path("parts/foo/src")
dest_dir.mkdir(parents=True)
file_name = "test.tar"
source = (
f"http://{http_server.server_address[0]}:"
f"{http_server.server_address[1]}/{file_name}"
)

file_source = sources.FileSource(source, dest_dir, cache_dir=new_dir)

file_source.pull()

source_file = dest_dir / file_name
assert source_file.read_text() == "Test fake file"

def test_pull_twice_downloads_once(self, new_dir, mocker, http_server):
"""If a source checksum is defined, the cache should be tried first."""

source = (
f"http://{http_server.server_address[0]}:"
f"{http_server.server_address[1]}/test.tar"
)

expected_checksum = (
"sha384/d9da1f5d54432edc8963cd817ceced83f7c6d61d3"
"50ad76d1c2f50c4935d11d50211945ca0ecb980c04c98099"
"085b0c3"
)
file_source = sources.FileSource(
source, Path(), cache_dir=new_dir, source_checksum=expected_checksum
)

download_spy = mocker.spy(requests, "get")
file_source.pull()
file_source.pull()
assert download_spy.call_count == 1

0 comments on commit 25a7cd3

Please sign in to comment.