pyforge-deploy 0.1.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyforge_deploy-0.1.7/LICENSE +25 -0
- pyforge_deploy-0.1.7/MANIFEST.in +21 -0
- pyforge_deploy-0.1.7/PKG-INFO +154 -0
- pyforge_deploy-0.1.7/README.md +111 -0
- pyforge_deploy-0.1.7/pyproject.toml +107 -0
- pyforge_deploy-0.1.7/requirements-dev.txt +15 -0
- pyforge_deploy-0.1.7/setup.cfg +4 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/__about__.py +1 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/__init__.py +0 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/builders/__init__.py +0 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/builders/docker.py +170 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/builders/docker_engine.py +259 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/builders/pypi.py +175 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/builders/version_engine.py +237 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/cli.py +140 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/colors.py +24 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/py.typed +0 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy/templates/Dockerfile.j2 +27 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy.egg-info/PKG-INFO +154 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy.egg-info/SOURCES.txt +28 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy.egg-info/dependency_links.txt +1 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy.egg-info/entry_points.txt +2 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy.egg-info/requires.txt +17 -0
- pyforge_deploy-0.1.7/src/pyforge_deploy.egg-info/top_level.txt +1 -0
- pyforge_deploy-0.1.7/tests/test_cli.py +104 -0
- pyforge_deploy-0.1.7/tests/test_colors.py +23 -0
- pyforge_deploy-0.1.7/tests/test_docker.py +212 -0
- pyforge_deploy-0.1.7/tests/test_docker_engine.py +166 -0
- pyforge_deploy-0.1.7/tests/test_pypi.py +173 -0
- pyforge_deploy-0.1.7/tests/test_version_engine.py +215 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ertan Tunç Türk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person
|
|
6
|
+
obtaining a copy of this software and associated documentation
|
|
7
|
+
files (the "Software"), to deal in the Software without
|
|
8
|
+
restriction, including without limitation the rights to use,
|
|
9
|
+
copy, modify, merge, publish, distribute, sublicense, and/or
|
|
10
|
+
sell copies of the Software, and to permit persons to whom
|
|
11
|
+
the Software is furnished to do so, subject to the following
|
|
12
|
+
conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall
|
|
15
|
+
be included in all copies or substantial portions of the
|
|
16
|
+
Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
19
|
+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
21
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
|
22
|
+
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
23
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
24
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
25
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Include documentation files in source distribution
|
|
2
|
+
include LICENSE
|
|
3
|
+
include README.md
|
|
4
|
+
include pyproject.toml
|
|
5
|
+
include requirements-dev.txt
|
|
6
|
+
|
|
7
|
+
# Include scripts
|
|
8
|
+
include scripts/setup.py
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Include Jinja2 templates
|
|
12
|
+
include src/pyforge_deploy/templates/*
|
|
13
|
+
|
|
14
|
+
# Include type stubs marker
|
|
15
|
+
include src/pyforge_deploy/py.typed
|
|
16
|
+
|
|
17
|
+
# Exclude development/test artifacts
|
|
18
|
+
prune .github
|
|
19
|
+
global-exclude __pycache__
|
|
20
|
+
global-exclude *.py[cod]
|
|
21
|
+
global-exclude *.egg-info
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyforge-deploy
|
|
3
|
+
Version: 0.1.7
|
|
4
|
+
Summary: Lightweight automation tool designed to streamline the transition from development to distribution.
|
|
5
|
+
Author-email: Ertan Tunç Türk <ertantuncturk61@gmail.com>
|
|
6
|
+
Maintainer-email: Ertan Tunç Türk <ertantuncturk61@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/ertanturk/pyforge-deploy
|
|
9
|
+
Project-URL: Repository, https://github.com/ertanturk/pyforge-deploy
|
|
10
|
+
Project-URL: Documentation, https://github.com/ertanturk/pyforge-deploy#readme
|
|
11
|
+
Project-URL: Issues, https://github.com/ertanturk/pyforge-deploy/issues
|
|
12
|
+
Keywords: python,docker,pypi,cli,devops,automation,workflow,packaging
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: toml
|
|
27
|
+
Requires-Dist: jinja2
|
|
28
|
+
Requires-Dist: packaging
|
|
29
|
+
Requires-Dist: python-dotenv
|
|
30
|
+
Requires-Dist: build
|
|
31
|
+
Requires-Dist: twine
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: ruff; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
37
|
+
Requires-Dist: bandit; extra == "dev"
|
|
38
|
+
Requires-Dist: pip-audit; extra == "dev"
|
|
39
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
40
|
+
Requires-Dist: coverage; extra == "dev"
|
|
41
|
+
Requires-Dist: types-toml; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# pyforge-deploy
|
|
45
|
+
|
|
46
|
+
> **Note:** This is a personal/educational project. It is not intended to compete with established
|
|
47
|
+
> tools
|
|
48
|
+
|
|
49
|
+
[](https://pypi.org/project/pyforge-deploy/)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
> **pyforge-deploy** is a lightweight automation CLI for Python projects, streamlining the transition from development to distribution. It automates Docker image creation, version management, and PyPI publishing with a simple, intelligent interface.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
* **Docker Integration:** Generates a project-specific Dockerfile from a Jinja2 template and detects the required Python version and dependencies.
|
|
60
|
+
* **Version Management:** Increments the project version (patch, minor, major) and verifies the new version is greater than the latest release on PyPI.
|
|
61
|
+
* **PyPI Deployment:** Builds source and wheel distributions and uploads them to PyPI or TestPyPI using token authentication.
|
|
62
|
+
* **Dependency Detection:** Scans for dependencies using pyproject.toml, requirements.txt, or import analysis.
|
|
63
|
+
* **CLI Commands:** Provides commands for building, deploying, and inspecting the project.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
Available on PyPI: https://pypi.org/project/pyforge-deploy/.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install pyforge-deploy
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> **Note:** Docker must be installed and running for Docker-related features.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
Get a list of all available commands and options:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pyforge-deploy --help
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Common Commands
|
|
88
|
+
|
|
89
|
+
- **Build a Docker Image:**
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pyforge-deploy docker-build --entry-point src/pyforge_deploy/cli.py --image-tag my-app:1.0.0
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- **Deploy to PyPI (Test):**
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pyforge-deploy deploy-pypi --test --bump patch
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- **Deploy a Specific Version:**
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pyforge-deploy deploy-pypi --version 2.1.0
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- **Show Detected Dependencies:**
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pyforge-deploy show-deps
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- **Show Project Version:**
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pyforge-deploy show-version
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
For PyPI publishing, provide an API token. Create a `.env` file in your project root:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
PYPI_TOKEN=pypi-your-token-here
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Or export `PYPI_TOKEN` as an environment variable in your shell or CI/CD system.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## How It Works
|
|
134
|
+
|
|
135
|
+
- **Version Engine:** Resolves project version from `pyproject.toml`, `__about__.py`, or `.version_cache`. Fetches latest PyPI version to avoid conflicts, writes final version to `src/<package_name>/__about__.py`.
|
|
136
|
+
- **Docker Builder:** Detects dependencies and Python version, renders `Dockerfile.j2`, and builds the image.
|
|
137
|
+
- **PyPI Distributor:** Cleans build artifacts, runs `python -m build`, uploads distributions with `twine`.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Testing
|
|
142
|
+
|
|
143
|
+
Run the full test suite with:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pytest
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Unit tests cover the CLI and builder components, located in the `tests/` directory.
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT License. See [LICENSE](LICENSE) for details..
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# pyforge-deploy
|
|
2
|
+
|
|
3
|
+
> **Note:** This is a personal/educational project. It is not intended to compete with established
|
|
4
|
+
> tools
|
|
5
|
+
|
|
6
|
+
[](https://pypi.org/project/pyforge-deploy/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
> **pyforge-deploy** is a lightweight automation CLI for Python projects, streamlining the transition from development to distribution. It automates Docker image creation, version management, and PyPI publishing with a simple, intelligent interface.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
* **Docker Integration:** Generates a project-specific Dockerfile from a Jinja2 template and detects the required Python version and dependencies.
|
|
17
|
+
* **Version Management:** Increments the project version (patch, minor, major) and verifies the new version is greater than the latest release on PyPI.
|
|
18
|
+
* **PyPI Deployment:** Builds source and wheel distributions and uploads them to PyPI or TestPyPI using token authentication.
|
|
19
|
+
* **Dependency Detection:** Scans for dependencies using pyproject.toml, requirements.txt, or import analysis.
|
|
20
|
+
* **CLI Commands:** Provides commands for building, deploying, and inspecting the project.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Available on PyPI: https://pypi.org/project/pyforge-deploy/.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install pyforge-deploy
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> **Note:** Docker must be installed and running for Docker-related features.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
Get a list of all available commands and options:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pyforge-deploy --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Common Commands
|
|
45
|
+
|
|
46
|
+
- **Build a Docker Image:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pyforge-deploy docker-build --entry-point src/pyforge_deploy/cli.py --image-tag my-app:1.0.0
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Deploy to PyPI (Test):**
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pyforge-deploy deploy-pypi --test --bump patch
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- **Deploy a Specific Version:**
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pyforge-deploy deploy-pypi --version 2.1.0
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- **Show Detected Dependencies:**
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pyforge-deploy show-deps
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **Show Project Version:**
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pyforge-deploy show-version
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
For PyPI publishing, provide an API token. Create a `.env` file in your project root:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
PYPI_TOKEN=pypi-your-token-here
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Or export `PYPI_TOKEN` as an environment variable in your shell or CI/CD system.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## How It Works
|
|
91
|
+
|
|
92
|
+
- **Version Engine:** Resolves project version from `pyproject.toml`, `__about__.py`, or `.version_cache`. Fetches latest PyPI version to avoid conflicts, writes final version to `src/<package_name>/__about__.py`.
|
|
93
|
+
- **Docker Builder:** Detects dependencies and Python version, renders `Dockerfile.j2`, and builds the image.
|
|
94
|
+
- **PyPI Distributor:** Cleans build artifacts, runs `python -m build`, uploads distributions with `twine`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Testing
|
|
99
|
+
|
|
100
|
+
Run the full test suite with:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pytest
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Unit tests cover the CLI and builder components, located in the `tests/` directory.
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT License. See [LICENSE](LICENSE) for details..
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyforge-deploy"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Lightweight automation tool designed to streamline the transition from development to distribution."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"toml",
|
|
14
|
+
"jinja2",
|
|
15
|
+
"packaging",
|
|
16
|
+
"python-dotenv",
|
|
17
|
+
"build",
|
|
18
|
+
"twine"
|
|
19
|
+
]
|
|
20
|
+
keywords = [
|
|
21
|
+
"python",
|
|
22
|
+
"docker",
|
|
23
|
+
"pypi",
|
|
24
|
+
"cli",
|
|
25
|
+
"devops",
|
|
26
|
+
"automation",
|
|
27
|
+
"workflow",
|
|
28
|
+
"packaging",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
authors = [
|
|
32
|
+
{ name = "Ertan Tunç Türk", email = "ertantuncturk61@gmail.com" },
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
maintainers = [
|
|
36
|
+
{ name = "Ertan Tunç Türk", email = "ertantuncturk61@gmail.com" },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
classifiers = [
|
|
40
|
+
"Development Status :: 3 - Alpha",
|
|
41
|
+
"Intended Audience :: Developers",
|
|
42
|
+
"Intended Audience :: Education",
|
|
43
|
+
"Operating System :: OS Independent",
|
|
44
|
+
"Programming Language :: Python :: 3",
|
|
45
|
+
"Programming Language :: Python :: 3.12",
|
|
46
|
+
"Typing :: Typed",
|
|
47
|
+
"Topic :: Software Development :: Libraries",
|
|
48
|
+
"Topic :: Software Development :: Testing",
|
|
49
|
+
"Topic :: Software Development :: Build Tools",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.dynamic]
|
|
53
|
+
version = {attr = "pyforge_deploy.__about__.__version__"}
|
|
54
|
+
|
|
55
|
+
[project.optional-dependencies]
|
|
56
|
+
dev = [
|
|
57
|
+
"ruff",
|
|
58
|
+
"mypy",
|
|
59
|
+
"pytest",
|
|
60
|
+
"pytest-cov",
|
|
61
|
+
"bandit",
|
|
62
|
+
"pip-audit",
|
|
63
|
+
"pre-commit",
|
|
64
|
+
"coverage",
|
|
65
|
+
"types-toml"
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[project.urls]
|
|
69
|
+
Homepage = "https://github.com/ertanturk/pyforge-deploy"
|
|
70
|
+
Repository = "https://github.com/ertanturk/pyforge-deploy"
|
|
71
|
+
Documentation = "https://github.com/ertanturk/pyforge-deploy#readme"
|
|
72
|
+
Issues = "https://github.com/ertanturk/pyforge-deploy/issues"
|
|
73
|
+
|
|
74
|
+
[project.scripts]
|
|
75
|
+
pyforge-deploy = "pyforge_deploy.cli:main"
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
line-length = 88
|
|
79
|
+
target-version = "py312"
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
select = ["E", "F", "I", "B", "UP"]
|
|
83
|
+
ignore = []
|
|
84
|
+
|
|
85
|
+
[tool.mypy]
|
|
86
|
+
python_version = "3.12"
|
|
87
|
+
strict = true
|
|
88
|
+
ignore_missing_imports = true
|
|
89
|
+
warn_unused_configs = true
|
|
90
|
+
|
|
91
|
+
[tool.pytest.ini_options]
|
|
92
|
+
addopts = "-v --tb=short"
|
|
93
|
+
testpaths = ["tests"]
|
|
94
|
+
minversion = "7.0"
|
|
95
|
+
|
|
96
|
+
[tool.coverage.run]
|
|
97
|
+
branch = true
|
|
98
|
+
source = ["."]
|
|
99
|
+
omit = ["tests/*", ".venv/*"]
|
|
100
|
+
|
|
101
|
+
[tool.coverage.report]
|
|
102
|
+
show_missing = true
|
|
103
|
+
fail_under = 70
|
|
104
|
+
|
|
105
|
+
[tool.bandit]
|
|
106
|
+
exclude_dirs = ["tests", ".venv"]
|
|
107
|
+
skips = ["B101"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.7"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# nosec B404: subprocess usage is safe, no shell=True, command is a list
|
|
2
|
+
import subprocess # nosec
|
|
3
|
+
import sys as _sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
8
|
+
|
|
9
|
+
from pyforge_deploy.colors import color_text, is_ci_environment
|
|
10
|
+
|
|
11
|
+
from .docker_engine import detect_dependencies, get_python_version
|
|
12
|
+
|
|
13
|
+
_sys.modules.setdefault("src.pyforge_deploy.builders.docker", _sys.modules[__name__])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DockerBuilder:
|
|
17
|
+
"""Main class responsible for rendering the Dockerfile template
|
|
18
|
+
and building the Docker image.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
entry_point: str | None = None,
|
|
24
|
+
image_tag: str | None = None,
|
|
25
|
+
verbose: bool = False,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.base_dir: Path = Path.cwd()
|
|
28
|
+
self.verbose: bool = verbose
|
|
29
|
+
if (
|
|
30
|
+
entry_point is not None
|
|
31
|
+
and not entry_point.replace("_", "").replace("-", "").isalnum()
|
|
32
|
+
):
|
|
33
|
+
raise ValueError(
|
|
34
|
+
color_text(
|
|
35
|
+
"Error: entry_point must be alphanumeric, underscore, or hyphen.",
|
|
36
|
+
"red",
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
self.entry_point: str | None = entry_point
|
|
40
|
+
# Validate image_tag for Docker safety
|
|
41
|
+
valid_chars = "-./_:"
|
|
42
|
+
sanitized_tag = image_tag if image_tag else ""
|
|
43
|
+
for char in valid_chars:
|
|
44
|
+
sanitized_tag = sanitized_tag.replace(char, "")
|
|
45
|
+
|
|
46
|
+
if image_tag is not None and not sanitized_tag.isalnum():
|
|
47
|
+
raise ValueError(
|
|
48
|
+
color_text(
|
|
49
|
+
"Error: image_tag must be alphanumeric or contain safe characters (- . / _ :).", # noqa: E501
|
|
50
|
+
"red",
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
self.image_tag: str = image_tag or self.base_dir.name.lower().replace(" ", "-")
|
|
54
|
+
self.dockerfile_path: Path = self.base_dir / "Dockerfile"
|
|
55
|
+
self.req_docker_path: Path = self.base_dir / "requirements-docker.txt"
|
|
56
|
+
|
|
57
|
+
def _generate_docker_requirements(self, final_list: list[str]) -> None:
|
|
58
|
+
"""Writes the detected dependencies to requirements-docker.txt."""
|
|
59
|
+
try:
|
|
60
|
+
with open(self.req_docker_path, "w", encoding="utf-8") as f:
|
|
61
|
+
f.write("# Auto-generated by pyforge-deploy AST/Venv scan\n")
|
|
62
|
+
if final_list:
|
|
63
|
+
for pkg in final_list:
|
|
64
|
+
f.write(f"{pkg}\n")
|
|
65
|
+
|
|
66
|
+
if self.verbose or is_ci_environment():
|
|
67
|
+
print(color_text("\n--- Detected Docker Requirements ---", "blue"))
|
|
68
|
+
if final_list:
|
|
69
|
+
for pkg in final_list:
|
|
70
|
+
print(f" -> {pkg}")
|
|
71
|
+
else:
|
|
72
|
+
print(" (No external dependencies needed!)")
|
|
73
|
+
print(color_text("------------------------------------\n", "blue"))
|
|
74
|
+
|
|
75
|
+
except Exception as err:
|
|
76
|
+
raise RuntimeError(
|
|
77
|
+
color_text(
|
|
78
|
+
f"Error: Failed to write requirements-docker.txt: {err}", "red"
|
|
79
|
+
)
|
|
80
|
+
) from err
|
|
81
|
+
|
|
82
|
+
def render_template(self) -> None:
|
|
83
|
+
"""Renders the Dockerfile template based on detected dependencies."""
|
|
84
|
+
python_version: str = get_python_version()
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
report: dict[str, Any] = detect_dependencies(str(self.base_dir))
|
|
88
|
+
self._generate_docker_requirements(report.get("final_list", []))
|
|
89
|
+
except Exception:
|
|
90
|
+
report = {
|
|
91
|
+
"has_pyproject": False,
|
|
92
|
+
"requirement_files": [],
|
|
93
|
+
"final_list": [],
|
|
94
|
+
"detected_imports": [],
|
|
95
|
+
"dev_tools": [],
|
|
96
|
+
}
|
|
97
|
+
self._generate_docker_requirements([])
|
|
98
|
+
|
|
99
|
+
current_module_dir: Path = Path(__file__).parent
|
|
100
|
+
templates_dir: Path = current_module_dir.parent / "templates"
|
|
101
|
+
|
|
102
|
+
if not templates_dir.exists():
|
|
103
|
+
raise FileNotFoundError(
|
|
104
|
+
color_text(
|
|
105
|
+
f"Error: Templates directory not found at {templates_dir}", "red"
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
env: Environment = Environment(
|
|
110
|
+
loader=FileSystemLoader(str(templates_dir)),
|
|
111
|
+
autoescape=select_autoescape(["j2", "html", "xml"]),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
template = env.get_template("Dockerfile.j2")
|
|
116
|
+
rendered_content: str = template.render(
|
|
117
|
+
python_version=python_version,
|
|
118
|
+
report=report,
|
|
119
|
+
entry_point=self.entry_point,
|
|
120
|
+
)
|
|
121
|
+
except Exception as err:
|
|
122
|
+
raise RuntimeError(
|
|
123
|
+
color_text(f"Error: Failed to render Dockerfile template: {err}", "red")
|
|
124
|
+
) from err
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
with open(self.dockerfile_path, "w", encoding="utf-8") as f:
|
|
128
|
+
f.write(rendered_content)
|
|
129
|
+
except Exception as err:
|
|
130
|
+
raise RuntimeError(
|
|
131
|
+
color_text(f"Error: Failed to write Dockerfile: {err}", "red")
|
|
132
|
+
) from err
|
|
133
|
+
|
|
134
|
+
def build_image(self) -> None:
|
|
135
|
+
"""Builds the Docker image using the rendered Dockerfile."""
|
|
136
|
+
print(
|
|
137
|
+
color_text(f"Building Docker image with tag: '{self.image_tag}'...", "blue")
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
cmd: list[str] = ["docker", "build", "-t", self.image_tag, "."] # nosec B603: no user input, safe
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
subprocess.run(cmd, check=True, cwd=str(self.base_dir)) # nosec B603
|
|
144
|
+
print(
|
|
145
|
+
color_text(
|
|
146
|
+
f"Docker image '{self.image_tag}' built successfully!", "green"
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if self.req_docker_path.exists():
|
|
151
|
+
self.req_docker_path.unlink()
|
|
152
|
+
|
|
153
|
+
except subprocess.CalledProcessError as err:
|
|
154
|
+
print(color_text(f"Error: Docker build failed: {err}", "red"))
|
|
155
|
+
raise RuntimeError(
|
|
156
|
+
color_text("Docker build process failed. Check the logs above.", "red")
|
|
157
|
+
) from err
|
|
158
|
+
except FileNotFoundError as err:
|
|
159
|
+
print(color_text(f"Error: Docker executable not found: {err}", "red"))
|
|
160
|
+
raise RuntimeError(
|
|
161
|
+
color_text(
|
|
162
|
+
"Docker executable not found. Please ensure Docker is installed and available in your PATH.", # noqa: E501
|
|
163
|
+
"red",
|
|
164
|
+
)
|
|
165
|
+
) from err
|
|
166
|
+
|
|
167
|
+
def deploy(self) -> None:
|
|
168
|
+
"""Main method to render Dockerfile and build the image."""
|
|
169
|
+
self.render_template()
|
|
170
|
+
self.build_image()
|