Skip to content
Open
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
138 changes: 138 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Will be specific for the platform/CI...
coredockpack/dockpack_py/dockpack_py/

*/cache/*
# Generated by Cargo
# will have compiled files and executables
Expand All @@ -22,3 +25,138 @@ Cargo.lock
.idea/

.DS_Store

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
.ruff_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.venv

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# vscode project settings
.vscode
*.code-workspace
.code-workspace
debug.log

# Files
.DS_Store
poetry.lock
1 change: 1 addition & 0 deletions coredockpack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cache/
1 change: 1 addition & 0 deletions coredockpack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
core-dockpack = { path = "../core-dockpack" }
tokio = "1.45.1"


[lib]
Expand Down
7 changes: 7 additions & 0 deletions coredockpack/dockpack_py/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dockpack_py.ffi_api import (
unpack_files_from_image,
build_files_from_image,
create_dockerfile,
)

__all__ = ["unpack_files_from_image", "build_files_from_image", "create_dockerfile"]
19 changes: 19 additions & 0 deletions coredockpack/dockpack_py/ffi_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from .dockpack_py import ffi, lib


# I tried this with a decorator to be more DRY and it was way less readable
def unpack_files_from_image(image_name: str, directory: str) -> str:
image: bytes = image_name.encode("utf-8")
dir: bytes = directory.encode("utf-8")
return ffi.string(lib.unpack_files_from_image_c(image, dir)).decode("utf-8")


def build_files_from_image(image_name: str, directory: str) -> str:
image: bytes = image_name.encode("utf-8")
dir: bytes = directory.encode("utf-8")
return ffi.string(lib.build_image_from_files_c(image, dir)).decode("utf-8")


def create_dockerfile(directory: str) -> str:
dir: bytes = directory.encode("utf-8")
return ffi.string(lib.create_dockerfile_c(dir)).decode("utf-8")
Empty file.
19 changes: 19 additions & 0 deletions coredockpack/dockpack_py/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest
import os


@pytest.fixture
def cache_dir(tmp_path):
"""A fixture for a cache directory pathlib.Path."""
return tmp_path


@pytest.fixture
def test_image_dir(tmp_path):
"""A fixture for a test_image directory which mimics pathlib.Path."""
return tmp_path


@pytest.fixture
def dockerfile_path(test_image_dir):
return os.path.join(str(test_image_dir), "test_image:latest")
39 changes: 39 additions & 0 deletions coredockpack/dockpack_py/tests/test_dockpack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from dockpack_py import (
unpack_files_from_image,
build_files_from_image,
create_dockerfile,
)
import os
import shutil


def test_execute_unpack():
image_name = "maxwellflitton/nan-one"
directory = "./cache/two"
if os.path.exists(directory):
shutil.rmtree(directory)

result = unpack_files_from_image(image_name, directory)
assert os.path.exists(directory)
assert result == directory
shutil.rmtree(directory)


# This currently fails/panics, because pushing to remote needs remote auth
# It's the same failure/behaviour as the rust crate test
def test_push(test_image_dir, dockerfile_path):
image_name = "test_image:latest"
with open(dockerfile_path, "w") as f:
f.write("FROM scratch\nCOPY . .\n")
res = build_files_from_image(image_name, "./cache")
assert res == dir


def test_build(test_image_dir):
create_dockerfile(str(test_image_dir))
dockerfile_path = os.path.join(test_image_dir, "Dockerfile")
with open(dockerfile_path, "r") as f:
file_content = f.read()
exp = "FROM scratch\nCOPY . .\n"
assert file_content == exp
print(file_content)
5 changes: 5 additions & 0 deletions coredockpack/justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build_py:
uv run maturin develop --uv

test:
just build_py && uv run pytest
24 changes: 24 additions & 0 deletions coredockpack/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[build-system]
requires = ["maturin>=1.8,<2.0"]
build-backend = "maturin"

[project]
name = "dockpack_py"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = [
"cffi",
]
dynamic = ["version"]

[tool.maturin]
bindings = "cffi"

[dependency-groups]
dev = [
"pytest>=8.3.5",
]
63 changes: 61 additions & 2 deletions coredockpack/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// Build
use core_dockpack::cmd_processes::build::build_dockerfile::create_dockerfile;
use core_dockpack::cmd_processes::pull::unpack_files::unpack_files_from_image;
// Push
use core_dockpack::cmd_processes::push::execute_push::execute_docker_build;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use tokio::runtime::Runtime;

/// Unpacks the files from a Docker image into a directory.
///
Expand All @@ -13,15 +18,17 @@ use std::os::raw::c_char;
/// On error, returns a null pointer.
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub async extern "C" fn unpack_files_from_image_c(
pub extern "C" fn unpack_files_from_image_c(
image: *const c_char,
directory: *const c_char,
) -> *const c_char {
// Convert C strings to Rust strings
let image = unsafe { CStr::from_ptr(image).to_string_lossy().into_owned() };
let directory = unsafe { CStr::from_ptr(directory).to_string_lossy().into_owned() };

match unpack_files_from_image(&image, &directory).await {
let rt = Runtime::new().unwrap();
let result = rt.block_on(unpack_files_from_image(&image, &directory));
match result {
Ok(path) => {
let c_string = CString::new(path).unwrap();
c_string.into_raw() // Return the C string
Expand All @@ -32,3 +39,55 @@ pub async extern "C" fn unpack_files_from_image_c(
}
}
}

/// Builds a docker image from a directory
///
/// # Arguments
/// * `image` - The name of the Docker image to create.
/// * `directory` - The directory to use for building the image.
///
/// # Returns
/// A C string with the path to the directory where the Docker image files are stored.
#[no_mangle]
pub extern "C" fn build_image_from_files_c(
image: *const c_char,
directory: *const c_char,
) -> *const c_char {
let image = unsafe { CStr::from_ptr(image).to_string_lossy().into_owned() };
let directory = unsafe { CStr::from_ptr(directory).to_string_lossy().into_owned() };
let rt = Runtime::new().unwrap();
let result = rt.block_on(execute_docker_build(&image, &directory));
match result {
Ok(_) => {
let c_string = CString::new(directory).unwrap();
c_string.into_raw() // Return the C string
}
Err(err) => {
eprintln!("Error unpacking image: {}", err);
std::ptr::null() // Return null on error
}
}
}

/// Packs a directory into a docker file
///
/// # Arguments
/// * `directory` - The directory into with docker.
///
/// # Returns
/// A C string with the path to the directory where the Docker image files are stored.
#[no_mangle]
pub extern "C" fn create_dockerfile_c(directory: *const c_char) -> *const c_char {
let directory = unsafe { CStr::from_ptr(directory).to_string_lossy().into_owned() };
let result = create_dockerfile(&directory);
match result {
Ok(_) => {
let c_string = CString::new(directory).unwrap();
c_string.into_raw() // Return the C string
}
Err(err) => {
eprintln!("Error unpacking image: {}", err);
std::ptr::null() // Return null on error
}
}
}
Loading