stackfordev 0.1.0__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.
- stackfordev-0.1.0/LICENSE +21 -0
- stackfordev-0.1.0/PKG-INFO +138 -0
- stackfordev-0.1.0/README.md +114 -0
- stackfordev-0.1.0/pyproject.toml +42 -0
- stackfordev-0.1.0/src/__init__.py +0 -0
- stackfordev-0.1.0/src/cli/__init__.py +0 -0
- stackfordev-0.1.0/src/cli/api_client.py +37 -0
- stackfordev-0.1.0/src/cli/commands/__init__.py +0 -0
- stackfordev-0.1.0/src/cli/commands/generate.py +85 -0
- stackfordev-0.1.0/src/cli/config.py +38 -0
- stackfordev-0.1.0/src/cli/display.py +29 -0
- stackfordev-0.1.0/src/cli/interactive.py +51 -0
- stackfordev-0.1.0/src/cli/main.py +14 -0
- stackfordev-0.1.0/src/docker_templates/__init__.py +0 -0
- stackfordev-0.1.0/src/docker_templates/go_template.py +27 -0
- stackfordev-0.1.0/src/docker_templates/javascript_template.py +27 -0
- stackfordev-0.1.0/src/docker_templates/python_template.py +30 -0
- stackfordev-0.1.0/src/generate_dockerfile.py +106 -0
- stackfordev-0.1.0/src/generator_core.py +108 -0
- stackfordev-0.1.0/src/s3_helper.py +47 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stackfordev
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate tailored Dockerfiles for development environments
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Keywords: docker,dockerfile,development,devcontainer
|
|
7
|
+
Author: ZisisKostakakisGithubPersonal
|
|
8
|
+
Author-email: dev@zisiskostakakis.com
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
19
|
+
Requires-Dist: click (>=8.1,<9.0)
|
|
20
|
+
Requires-Dist: httpx (>=0.27,<0.28)
|
|
21
|
+
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
22
|
+
Requires-Dist: rich (>=13.0,<14.0)
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# StackForDev
|
|
26
|
+
|
|
27
|
+
A service that generates optimized Dockerfiles based on project requirements.
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
- AWS Lambda (Container Runtime)
|
|
34
|
+
- Amazon API Gateway
|
|
35
|
+
- Amazon ECR
|
|
36
|
+
- Amazon S3
|
|
37
|
+
- Terraform Cloud
|
|
38
|
+
|
|
39
|
+
## Prerequisites
|
|
40
|
+
|
|
41
|
+
- AWS Account
|
|
42
|
+
- Terraform Cloud Account
|
|
43
|
+
- Docker installed locally
|
|
44
|
+
- Python 3.11+
|
|
45
|
+
- Poetry for Python dependency management
|
|
46
|
+
|
|
47
|
+
## Local Development
|
|
48
|
+
|
|
49
|
+
1. Install dependencies:
|
|
50
|
+
```bash
|
|
51
|
+
poetry install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
2. Set up environment variables:
|
|
55
|
+
```bash
|
|
56
|
+
export AWS_ACCESS_KEY_ID="your_access_key"
|
|
57
|
+
export AWS_SECRET_ACCESS_KEY="your_secret_key"
|
|
58
|
+
export AWS_REGION="your_region"
|
|
59
|
+
export AWS_ACCOUNT_ID="your_account_id"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. Initialize Terraform:
|
|
63
|
+
```bash
|
|
64
|
+
cd aws_resources
|
|
65
|
+
terraform init
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Deployment
|
|
69
|
+
|
|
70
|
+
The project uses Terraform Cloud for infrastructure management. Deployments are automated through GitHub integration.
|
|
71
|
+
|
|
72
|
+
If you want to deploy through the Terraform Cloud UI, you can do so by following these steps:
|
|
73
|
+
|
|
74
|
+
1. Create a new workspace in Terraform Cloud
|
|
75
|
+
2. Add the GitHub repository to the workspace
|
|
76
|
+
3. Create a new variable set in the workspace and add the AWS credentials as variables
|
|
77
|
+
4. Run the plan and apply buttons in the Terraform Cloud UI
|
|
78
|
+
|
|
79
|
+
### Manual Deployment Steps
|
|
80
|
+
|
|
81
|
+
1. Push changes to GitHub
|
|
82
|
+
2. Terraform Cloud automatically plans and applies changes
|
|
83
|
+
3. New Docker image is built and pushed to ECR
|
|
84
|
+
4. Lambda function is updated with the latest image
|
|
85
|
+
|
|
86
|
+
## API Usage
|
|
87
|
+
|
|
88
|
+
### Generate Dockerfile
|
|
89
|
+
```bash
|
|
90
|
+
curl -X POST https://api.example.com/prod/generate-dockerfile \
|
|
91
|
+
-H "x-api-key: your_api_key" \
|
|
92
|
+
-H "Content-Type: application/json" \
|
|
93
|
+
-d '{
|
|
94
|
+
"config": {
|
|
95
|
+
"language": "python",
|
|
96
|
+
"dependency_stack": "Django",
|
|
97
|
+
"extra_dependencies": ["pandas", "numpy"],
|
|
98
|
+
"language_version": "3.11"
|
|
99
|
+
}
|
|
100
|
+
}'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Project Structure
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
├── aws_resources/ # Terraform infrastructure code
|
|
107
|
+
├── docker_templates/ # Dockerfile templates
|
|
108
|
+
├── generate_dockerfile.py # Main Lambda function
|
|
109
|
+
├── pyproject.toml # Python dependencies
|
|
110
|
+
└── README.md
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Infrastructure
|
|
114
|
+
|
|
115
|
+
- **API Gateway**: Regional endpoint with API key authentication
|
|
116
|
+
- **Lambda**: Container-based function with 30s timeout
|
|
117
|
+
- **S3**: Private bucket for Dockerfile storage
|
|
118
|
+
- **ECR**: Container registry for Lambda images
|
|
119
|
+
- **IAM**: Least-privilege access policies
|
|
120
|
+
|
|
121
|
+
## Contributing
|
|
122
|
+
|
|
123
|
+
1. Fork the repository
|
|
124
|
+
2. Create a feature branch
|
|
125
|
+
3. Commit your changes
|
|
126
|
+
4. Push to the branch
|
|
127
|
+
5. Create a Pull Request
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
132
|
+
|
|
133
|
+
## Security
|
|
134
|
+
|
|
135
|
+
- S3 bucket with public access blocked
|
|
136
|
+
- IAM role-based access control
|
|
137
|
+
- API Gateway with usage plans and API keys
|
|
138
|
+
- ECR repository with specific permissions
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# StackForDev
|
|
2
|
+
|
|
3
|
+
A service that generates optimized Dockerfiles based on project requirements.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
- AWS Lambda (Container Runtime)
|
|
10
|
+
- Amazon API Gateway
|
|
11
|
+
- Amazon ECR
|
|
12
|
+
- Amazon S3
|
|
13
|
+
- Terraform Cloud
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- AWS Account
|
|
18
|
+
- Terraform Cloud Account
|
|
19
|
+
- Docker installed locally
|
|
20
|
+
- Python 3.11+
|
|
21
|
+
- Poetry for Python dependency management
|
|
22
|
+
|
|
23
|
+
## Local Development
|
|
24
|
+
|
|
25
|
+
1. Install dependencies:
|
|
26
|
+
```bash
|
|
27
|
+
poetry install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. Set up environment variables:
|
|
31
|
+
```bash
|
|
32
|
+
export AWS_ACCESS_KEY_ID="your_access_key"
|
|
33
|
+
export AWS_SECRET_ACCESS_KEY="your_secret_key"
|
|
34
|
+
export AWS_REGION="your_region"
|
|
35
|
+
export AWS_ACCOUNT_ID="your_account_id"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. Initialize Terraform:
|
|
39
|
+
```bash
|
|
40
|
+
cd aws_resources
|
|
41
|
+
terraform init
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Deployment
|
|
45
|
+
|
|
46
|
+
The project uses Terraform Cloud for infrastructure management. Deployments are automated through GitHub integration.
|
|
47
|
+
|
|
48
|
+
If you want to deploy through the Terraform Cloud UI, you can do so by following these steps:
|
|
49
|
+
|
|
50
|
+
1. Create a new workspace in Terraform Cloud
|
|
51
|
+
2. Add the GitHub repository to the workspace
|
|
52
|
+
3. Create a new variable set in the workspace and add the AWS credentials as variables
|
|
53
|
+
4. Run the plan and apply buttons in the Terraform Cloud UI
|
|
54
|
+
|
|
55
|
+
### Manual Deployment Steps
|
|
56
|
+
|
|
57
|
+
1. Push changes to GitHub
|
|
58
|
+
2. Terraform Cloud automatically plans and applies changes
|
|
59
|
+
3. New Docker image is built and pushed to ECR
|
|
60
|
+
4. Lambda function is updated with the latest image
|
|
61
|
+
|
|
62
|
+
## API Usage
|
|
63
|
+
|
|
64
|
+
### Generate Dockerfile
|
|
65
|
+
```bash
|
|
66
|
+
curl -X POST https://api.example.com/prod/generate-dockerfile \
|
|
67
|
+
-H "x-api-key: your_api_key" \
|
|
68
|
+
-H "Content-Type: application/json" \
|
|
69
|
+
-d '{
|
|
70
|
+
"config": {
|
|
71
|
+
"language": "python",
|
|
72
|
+
"dependency_stack": "Django",
|
|
73
|
+
"extra_dependencies": ["pandas", "numpy"],
|
|
74
|
+
"language_version": "3.11"
|
|
75
|
+
}
|
|
76
|
+
}'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Project Structure
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
├── aws_resources/ # Terraform infrastructure code
|
|
83
|
+
├── docker_templates/ # Dockerfile templates
|
|
84
|
+
├── generate_dockerfile.py # Main Lambda function
|
|
85
|
+
├── pyproject.toml # Python dependencies
|
|
86
|
+
└── README.md
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Infrastructure
|
|
90
|
+
|
|
91
|
+
- **API Gateway**: Regional endpoint with API key authentication
|
|
92
|
+
- **Lambda**: Container-based function with 30s timeout
|
|
93
|
+
- **S3**: Private bucket for Dockerfile storage
|
|
94
|
+
- **ECR**: Container registry for Lambda images
|
|
95
|
+
- **IAM**: Least-privilege access policies
|
|
96
|
+
|
|
97
|
+
## Contributing
|
|
98
|
+
|
|
99
|
+
1. Fork the repository
|
|
100
|
+
2. Create a feature branch
|
|
101
|
+
3. Commit your changes
|
|
102
|
+
4. Push to the branch
|
|
103
|
+
5. Create a Pull Request
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
108
|
+
|
|
109
|
+
## Security
|
|
110
|
+
|
|
111
|
+
- S3 bucket with public access blocked
|
|
112
|
+
- IAM role-based access control
|
|
113
|
+
- API Gateway with usage plans and API keys
|
|
114
|
+
- ECR repository with specific permissions
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "stackfordev"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Generate tailored Dockerfiles for development environments"
|
|
5
|
+
authors = ["ZisisKostakakisGithubPersonal <dev@zisiskostakakis.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{include = "src"}]
|
|
8
|
+
keywords = ["docker", "dockerfile", "development", "devcontainer"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Environment :: Console",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Topic :: Software Development :: Build Tools",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.poetry.scripts]
|
|
17
|
+
stackfordev = "src.cli.main:cli"
|
|
18
|
+
|
|
19
|
+
[tool.poetry.dependencies]
|
|
20
|
+
python = "^3.11"
|
|
21
|
+
pydantic = "^2.9.2"
|
|
22
|
+
click = "^8.1"
|
|
23
|
+
rich = "^13.0"
|
|
24
|
+
httpx = "^0.27"
|
|
25
|
+
|
|
26
|
+
[tool.poetry.group.lambda.dependencies]
|
|
27
|
+
boto3 = "^1.35.62"
|
|
28
|
+
python-dotenv = "^1.0.1"
|
|
29
|
+
boto3-stubs = {extras = ["essential"], version = "^1.35.62"}
|
|
30
|
+
|
|
31
|
+
[tool.poetry.group.dev.dependencies]
|
|
32
|
+
pytest = "^8.3.3"
|
|
33
|
+
respx = "^0.21"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["poetry-core"]
|
|
37
|
+
build-backend = "poetry.core.masonry.api"
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
pythonpath = [
|
|
41
|
+
"src"
|
|
42
|
+
]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""HTTP client for the StackForDev public API endpoint."""
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from src.cli.config import API_URL
|
|
6
|
+
from src.generator_core import GenerateDockerfileRequest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def generate_via_api(config: GenerateDockerfileRequest, timeout: float = 15.0) -> dict:
|
|
10
|
+
"""POST to the public CLI endpoint and return the response body.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
dict with keys: message, key, dockerfile
|
|
14
|
+
|
|
15
|
+
Raises:
|
|
16
|
+
RuntimeError on network/HTTP errors with user-friendly messages.
|
|
17
|
+
"""
|
|
18
|
+
payload = {
|
|
19
|
+
"config": {
|
|
20
|
+
"language": config.language,
|
|
21
|
+
"dependency_stack": config.dependency_stack,
|
|
22
|
+
"extra_dependencies": config.extra_dependencies,
|
|
23
|
+
"language_version": config.language_version,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
response = httpx.post(API_URL, json=payload, timeout=timeout)
|
|
29
|
+
response.raise_for_status()
|
|
30
|
+
return response.json()
|
|
31
|
+
except httpx.TimeoutException:
|
|
32
|
+
raise RuntimeError("Request timed out. Try again or use --local to generate offline.")
|
|
33
|
+
except httpx.ConnectError:
|
|
34
|
+
raise RuntimeError("Could not connect to the API. Check your internet or use --local.")
|
|
35
|
+
except httpx.HTTPStatusError as e:
|
|
36
|
+
body = e.response.text
|
|
37
|
+
raise RuntimeError(f"API returned {e.response.status_code}: {body}")
|
|
File without changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""stackfordev generate command."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from src.cli.config import (
|
|
9
|
+
LANGUAGE_VERSIONS,
|
|
10
|
+
LANGUAGE_STACKS,
|
|
11
|
+
validate_language,
|
|
12
|
+
validate_version,
|
|
13
|
+
validate_stack,
|
|
14
|
+
)
|
|
15
|
+
from src.cli.display import print_dockerfile, print_saved
|
|
16
|
+
from src.generator_core import GenerateDockerfileRequest, DockerfileGenerator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command()
|
|
20
|
+
@click.option("--language", "-l", type=str, default=None, help="Language: python, javascript, go")
|
|
21
|
+
@click.option("--stack", "-s", type=str, default=None, help="Dependency stack (e.g. 'Django Stack')")
|
|
22
|
+
@click.option("--version", "-v", "lang_version", type=str, default=None, help="Language version (e.g. 3.11)")
|
|
23
|
+
@click.option("--extras", "-e", type=str, default=None, help="Comma-separated extra dependencies")
|
|
24
|
+
@click.option("--output", "-o", type=click.Path(), default=None, help="Save Dockerfile to path")
|
|
25
|
+
@click.option("--local", is_flag=True, default=False, help="Generate offline (no API call)")
|
|
26
|
+
@click.option("--json-output", "--json", "json_mode", is_flag=True, default=False, help="Output raw JSON")
|
|
27
|
+
def generate(language, stack, lang_version, extras, output, local, json_mode):
|
|
28
|
+
"""Generate a Dockerfile for a development environment."""
|
|
29
|
+
# If any flag is missing and we're in a TTY, go interactive
|
|
30
|
+
if (language is None or stack is None or lang_version is None) and sys.stdin.isatty():
|
|
31
|
+
from src.cli.interactive import prompt_config
|
|
32
|
+
language, lang_version, stack, extras_list = prompt_config(language, lang_version, stack)
|
|
33
|
+
elif language is None or stack is None or lang_version is None:
|
|
34
|
+
click.echo(
|
|
35
|
+
"Error: --language, --stack, and --version are required in non-interactive mode.\n"
|
|
36
|
+
"Example: stackfordev generate -l python -s 'Django Stack' -v 3.11",
|
|
37
|
+
err=True,
|
|
38
|
+
)
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
else:
|
|
41
|
+
extras_list = [e.strip() for e in extras.split(",") if e.strip()] if extras else []
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
lang = validate_language(language)
|
|
45
|
+
validate_version(lang, lang_version)
|
|
46
|
+
validate_stack(lang, stack)
|
|
47
|
+
except ValueError as e:
|
|
48
|
+
click.echo(f"Error: {e}", err=True)
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
config = GenerateDockerfileRequest(
|
|
52
|
+
language=lang,
|
|
53
|
+
dependency_stack=stack,
|
|
54
|
+
extra_dependencies=extras_list,
|
|
55
|
+
language_version=lang_version,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if local:
|
|
59
|
+
generator = DockerfileGenerator(config=config)
|
|
60
|
+
dockerfile_content = generator.generate_dockerfile()
|
|
61
|
+
else:
|
|
62
|
+
try:
|
|
63
|
+
from src.cli.api_client import generate_via_api
|
|
64
|
+
result = generate_via_api(config)
|
|
65
|
+
dockerfile_content = result["dockerfile"]
|
|
66
|
+
except Exception as e:
|
|
67
|
+
click.echo(f"API error: {e}\nTip: use --local to generate offline.", err=True)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
if json_mode:
|
|
71
|
+
click.echo(json.dumps({
|
|
72
|
+
"language": lang,
|
|
73
|
+
"stack": stack,
|
|
74
|
+
"version": lang_version,
|
|
75
|
+
"dockerfile": dockerfile_content,
|
|
76
|
+
}))
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
if output:
|
|
80
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
81
|
+
f.write(dockerfile_content)
|
|
82
|
+
print_saved(output)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
print_dockerfile(dockerfile_content, lang, stack)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""CLI configuration: language versions, stacks, and validation."""
|
|
2
|
+
|
|
3
|
+
from src.generator_core import STACK_PACKAGES
|
|
4
|
+
|
|
5
|
+
LANGUAGE_VERSIONS: dict[str, list[str]] = {
|
|
6
|
+
"python": ["3.12", "3.11", "3.10", "3.9"],
|
|
7
|
+
"javascript": ["22", "20", "18"],
|
|
8
|
+
"go": ["1.23", "1.22", "1.21"],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
LANGUAGE_STACKS: dict[str, list[str]] = {
|
|
12
|
+
"python": ["Django Stack", "Flask Stack", "Data Science Stack", "Web Scraping Stack", "Machine Learning Stack"],
|
|
13
|
+
"javascript": ["Express Stack", "React Stack", "Vue.js Stack", "Node.js API Stack", "Full-Stack JavaScript"],
|
|
14
|
+
"go": ["Gin Stack", "Beego Stack", "Web Framework Stack", "Microservices Stack", "Data Processing Stack"],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
API_URL = "https://f88slnkaa6.execute-api.eu-west-2.amazonaws.com/prod/cli/generate-dockerfile"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def validate_language(language: str) -> str:
|
|
21
|
+
lang = language.lower()
|
|
22
|
+
if lang not in LANGUAGE_VERSIONS:
|
|
23
|
+
raise ValueError(f"Unsupported language: {language}. Supported: {', '.join(LANGUAGE_VERSIONS)}")
|
|
24
|
+
return lang
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_version(language: str, version: str) -> str:
|
|
28
|
+
versions = LANGUAGE_VERSIONS[language]
|
|
29
|
+
if version not in versions:
|
|
30
|
+
raise ValueError(f"Unsupported version '{version}' for {language}. Supported: {', '.join(versions)}")
|
|
31
|
+
return version
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def validate_stack(language: str, stack: str) -> str:
|
|
35
|
+
stacks = LANGUAGE_STACKS[language]
|
|
36
|
+
if stack not in stacks:
|
|
37
|
+
raise ValueError(f"Unsupported stack '{stack}' for {language}. Supported: {', '.join(stacks)}")
|
|
38
|
+
return stack
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Rich display helpers for CLI output."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.syntax import Syntax
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def is_tty() -> bool:
|
|
11
|
+
return sys.stdout.isatty()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def print_dockerfile(content: str, language: str, stack: str) -> None:
|
|
15
|
+
"""Print Dockerfile with Rich syntax highlighting if TTY, raw otherwise."""
|
|
16
|
+
if not is_tty():
|
|
17
|
+
sys.stdout.write(content)
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
syntax = Syntax(content, "dockerfile", theme="monokai", line_numbers=True)
|
|
22
|
+
console.print(Panel(syntax, title=f"[bold green]Dockerfile — {language} / {stack}[/]", border_style="green"))
|
|
23
|
+
console.print()
|
|
24
|
+
console.print("[dim]Tip: docker build -t myapp -f Dockerfile . && docker run -it -v $(pwd):/usr/src/app myapp[/]")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def print_saved(path: str) -> None:
|
|
28
|
+
console = Console(stderr=True)
|
|
29
|
+
console.print(f"[green]Saved to {path}[/]")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Interactive prompts for when CLI flags are missing."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.prompt import Prompt
|
|
5
|
+
|
|
6
|
+
from src.cli.config import LANGUAGE_VERSIONS, LANGUAGE_STACKS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def prompt_config(
|
|
10
|
+
language: str | None = None,
|
|
11
|
+
lang_version: str | None = None,
|
|
12
|
+
stack: str | None = None,
|
|
13
|
+
) -> tuple[str, str, str, list[str]]:
|
|
14
|
+
"""Prompt the user interactively for missing config values.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
(language, version, stack, extras_list)
|
|
18
|
+
"""
|
|
19
|
+
console = Console()
|
|
20
|
+
console.print("[bold]StackForDev[/] — Generate a Dockerfile\n")
|
|
21
|
+
|
|
22
|
+
languages = list(LANGUAGE_VERSIONS.keys())
|
|
23
|
+
if language is None:
|
|
24
|
+
console.print("Available languages:")
|
|
25
|
+
for i, l in enumerate(languages, 1):
|
|
26
|
+
console.print(f" {i}. {l}")
|
|
27
|
+
raw = Prompt.ask("Select language", choices=[str(i) for i in range(1, len(languages) + 1)], default="1")
|
|
28
|
+
language = languages[int(raw) - 1]
|
|
29
|
+
language = language.lower()
|
|
30
|
+
|
|
31
|
+
versions = LANGUAGE_VERSIONS[language]
|
|
32
|
+
if lang_version is None:
|
|
33
|
+
console.print("\nAvailable versions:")
|
|
34
|
+
for i, v in enumerate(versions, 1):
|
|
35
|
+
console.print(f" {i}. {v}")
|
|
36
|
+
raw = Prompt.ask("Select version", choices=[str(i) for i in range(1, len(versions) + 1)], default="1")
|
|
37
|
+
lang_version = versions[int(raw) - 1]
|
|
38
|
+
|
|
39
|
+
stacks = LANGUAGE_STACKS[language]
|
|
40
|
+
if stack is None:
|
|
41
|
+
console.print("\nAvailable stacks:")
|
|
42
|
+
for i, s in enumerate(stacks, 1):
|
|
43
|
+
console.print(f" {i}. {s}")
|
|
44
|
+
number_choices = [str(i) for i in range(1, len(stacks) + 1)]
|
|
45
|
+
raw = Prompt.ask("Select stack", choices=number_choices, default="1")
|
|
46
|
+
stack = stacks[int(raw) - 1]
|
|
47
|
+
|
|
48
|
+
extras_input = Prompt.ask("Extra dependencies (comma-separated, Enter to skip)", default="")
|
|
49
|
+
extras_list = [e.strip() for e in extras_input.split(",") if e.strip()]
|
|
50
|
+
|
|
51
|
+
return language, lang_version, stack, extras_list
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""StackForDev CLI entrypoint."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from src.cli.commands.generate import generate
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
@click.version_option(package_name="stackfordev")
|
|
10
|
+
def cli():
|
|
11
|
+
"""StackForDev — Generate tailored Dockerfiles for development environments."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
cli.add_command(generate)
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""File to generate a Dockerfile for a Go application."""
|
|
2
|
+
START_OF_TEMPLATE = """# Help
|
|
3
|
+
|
|
4
|
+
# To execute Go code in the container:
|
|
5
|
+
# docker exec manager go version
|
|
6
|
+
|
|
7
|
+
# To start an interactive shell:
|
|
8
|
+
# docker exec -it manager bash
|
|
9
|
+
|
|
10
|
+
FROM golang:GO_VERSION-bullseye
|
|
11
|
+
|
|
12
|
+
WORKDIR /usr/src/app
|
|
13
|
+
|
|
14
|
+
# Install system dependencies
|
|
15
|
+
RUN apt-get update && apt-get install -y \\
|
|
16
|
+
git \\
|
|
17
|
+
curl \\
|
|
18
|
+
build-essential \\
|
|
19
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
20
|
+
|
|
21
|
+
# Install Go packages
|
|
22
|
+
RUN go install DEPENDENCY_STACK EXTRA_DEPENDENCIES
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
END_OF_TEMPLATE = """
|
|
26
|
+
CMD ["bash"]
|
|
27
|
+
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""File to generate a Dockerfile for a JavaScript/Node.js application."""
|
|
2
|
+
START_OF_TEMPLATE = """# Help
|
|
3
|
+
|
|
4
|
+
# To execute Node.js code in the container:
|
|
5
|
+
# docker exec manager node --version
|
|
6
|
+
|
|
7
|
+
# To start an interactive Node.js shell:
|
|
8
|
+
# docker exec -it manager node
|
|
9
|
+
|
|
10
|
+
FROM node:NODE_VERSION-bullseye
|
|
11
|
+
|
|
12
|
+
WORKDIR /usr/src/app
|
|
13
|
+
|
|
14
|
+
# Install system dependencies
|
|
15
|
+
RUN apt-get update && apt-get install -y \\
|
|
16
|
+
git \\
|
|
17
|
+
curl \\
|
|
18
|
+
build-essential \\
|
|
19
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
20
|
+
|
|
21
|
+
# Install global packages
|
|
22
|
+
RUN npm install -g DEPENDENCY_STACK EXTRA_DEPENDENCIES
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
END_OF_TEMPLATE = """
|
|
26
|
+
CMD ["bash"]
|
|
27
|
+
"""
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""File to generate a Dockerfile for a Python application."""
|
|
2
|
+
START_OF_TEMPLATE = """# Help
|
|
3
|
+
|
|
4
|
+
# To execute Python code in the container:
|
|
5
|
+
# docker exec manager python3 --version
|
|
6
|
+
|
|
7
|
+
# To start an interactive python shell:
|
|
8
|
+
# docker exec -it manager python3
|
|
9
|
+
|
|
10
|
+
FROM python:PYTHON_VERSION-bullseye
|
|
11
|
+
|
|
12
|
+
WORKDIR /usr/src/app
|
|
13
|
+
|
|
14
|
+
# Install system dependencies
|
|
15
|
+
RUN apt-get update && apt-get install -y \\
|
|
16
|
+
git \\
|
|
17
|
+
curl \\
|
|
18
|
+
build-essential \\
|
|
19
|
+
python3-dev \\
|
|
20
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
21
|
+
|
|
22
|
+
ENV PYTHONUNBUFFERED=1
|
|
23
|
+
|
|
24
|
+
# Install extra dependencies
|
|
25
|
+
RUN pip install DEPENDENCY_STACK EXTRA_DEPENDENCIES
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
END_OF_TEMPLATE = """
|
|
29
|
+
CMD ["bash"]
|
|
30
|
+
"""
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Script to generate the dockerfile based on the API parameters"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
from src.generator_core import (
|
|
10
|
+
TEMPLATE_REGISTRY,
|
|
11
|
+
SUPPORTED_LANGUAGES,
|
|
12
|
+
STACK_PACKAGES,
|
|
13
|
+
GenerateDockerfileRequest,
|
|
14
|
+
DockerfileGenerator,
|
|
15
|
+
generate_dockerfile_key_name,
|
|
16
|
+
)
|
|
17
|
+
from src.s3_helper import upload_to_s3, check_if_file_exists_in_s3
|
|
18
|
+
|
|
19
|
+
load_dotenv()
|
|
20
|
+
|
|
21
|
+
# Re-export for backward compatibility
|
|
22
|
+
__all__ = [
|
|
23
|
+
"TEMPLATE_REGISTRY",
|
|
24
|
+
"SUPPORTED_LANGUAGES",
|
|
25
|
+
"STACK_PACKAGES",
|
|
26
|
+
"GenerateDockerfileRequest",
|
|
27
|
+
"DockerfileGenerator",
|
|
28
|
+
"generate_dockerfile_key_name",
|
|
29
|
+
"is_running_on_lambda",
|
|
30
|
+
"validate_env_vars",
|
|
31
|
+
"lambda_handler",
|
|
32
|
+
"CORS_HEADERS",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def is_running_on_lambda() -> bool:
|
|
37
|
+
"""Determine if the code is executing in an AWS Lambda environment."""
|
|
38
|
+
return bool(os.getenv("AWS_LAMBDA_FUNCTION_NAME"))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_env_vars() -> None:
|
|
42
|
+
"""Validate required environment variables are present."""
|
|
43
|
+
if not os.getenv("S3_BUCKET"):
|
|
44
|
+
raise ValueError("S3_BUCKET environment variable is not set")
|
|
45
|
+
if not os.getenv("AWS_REGION"):
|
|
46
|
+
raise ValueError("AWS_REGION environment variable is not set")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
CORS_HEADERS = {
|
|
50
|
+
"Access-Control-Allow-Origin": "*",
|
|
51
|
+
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
|
|
52
|
+
"Access-Control-Allow-Methods": "POST,OPTIONS",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _response(status_code: int, body: dict) -> dict:
|
|
57
|
+
return {
|
|
58
|
+
"statusCode": status_code,
|
|
59
|
+
"headers": CORS_HEADERS,
|
|
60
|
+
"body": json.dumps(body),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def lambda_handler(event: dict[str, Any], context: Optional[dict] = None) -> dict:
|
|
65
|
+
"""AWS Lambda handler for the Dockerfile generation API endpoint."""
|
|
66
|
+
try:
|
|
67
|
+
validate_env_vars()
|
|
68
|
+
config = GenerateDockerfileRequest.from_event(event)
|
|
69
|
+
generator = DockerfileGenerator(config=config)
|
|
70
|
+
dockerfile_content = generator.generate_dockerfile()
|
|
71
|
+
|
|
72
|
+
dockerfile_key_name = generate_dockerfile_key_name(config)
|
|
73
|
+
path = f"{config.language.lower()}-images/"
|
|
74
|
+
|
|
75
|
+
if not is_running_on_lambda():
|
|
76
|
+
os.makedirs(path, exist_ok=True)
|
|
77
|
+
with open(os.path.join(path, dockerfile_key_name), "w", encoding="utf-8") as f:
|
|
78
|
+
f.write(dockerfile_content)
|
|
79
|
+
|
|
80
|
+
aws_region = os.getenv("AWS_REGION")
|
|
81
|
+
bucket = os.getenv("S3_BUCKET")
|
|
82
|
+
|
|
83
|
+
if is_running_on_lambda():
|
|
84
|
+
s3_key = os.path.join(path, dockerfile_key_name)
|
|
85
|
+
if not check_if_file_exists_in_s3(
|
|
86
|
+
bucket=bucket,
|
|
87
|
+
key=s3_key,
|
|
88
|
+
region_name=aws_region,
|
|
89
|
+
):
|
|
90
|
+
try:
|
|
91
|
+
upload_to_s3(
|
|
92
|
+
file_path=s3_key,
|
|
93
|
+
bucket=bucket,
|
|
94
|
+
content=dockerfile_content,
|
|
95
|
+
region_name=aws_region,
|
|
96
|
+
)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
return _response(500, {"error": f"Failed to upload Dockerfile to S3, {e}"})
|
|
99
|
+
|
|
100
|
+
return _response(200, {
|
|
101
|
+
"message": "Dockerfile generated successfully",
|
|
102
|
+
"key": dockerfile_key_name,
|
|
103
|
+
"dockerfile": dockerfile_content,
|
|
104
|
+
})
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return _response(400, {"error": str(e)})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Core Dockerfile generation logic, decoupled from AWS dependencies."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, field_validator
|
|
7
|
+
|
|
8
|
+
from src.docker_templates import python_template, javascript_template, go_template
|
|
9
|
+
|
|
10
|
+
TEMPLATE_REGISTRY: dict[str, tuple] = {
|
|
11
|
+
"python": (python_template.START_OF_TEMPLATE, python_template.END_OF_TEMPLATE, "PYTHON_VERSION"),
|
|
12
|
+
"javascript": (javascript_template.START_OF_TEMPLATE, javascript_template.END_OF_TEMPLATE, "NODE_VERSION"),
|
|
13
|
+
"go": (go_template.START_OF_TEMPLATE, go_template.END_OF_TEMPLATE, "GO_VERSION"),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
SUPPORTED_LANGUAGES = set(TEMPLATE_REGISTRY.keys())
|
|
17
|
+
|
|
18
|
+
STACK_PACKAGES: dict[str, str] = {
|
|
19
|
+
# Python
|
|
20
|
+
"Django Stack": "django djangorestframework psycopg2-binary Pillow djangorestframework-simplejwt requests",
|
|
21
|
+
"Flask Stack": "flask flask-restful flask-sqlalchemy flask-migrate requests",
|
|
22
|
+
"Data Science Stack": "pandas numpy matplotlib scikit-learn jupyter",
|
|
23
|
+
"Web Scraping Stack": "beautifulsoup4 scrapy requests lxml pandas",
|
|
24
|
+
"Machine Learning Stack": "scikit-learn tensorflow keras numpy matplotlib",
|
|
25
|
+
# JavaScript (npm global packages)
|
|
26
|
+
"Express Stack": "express mongoose body-parser cors dotenv",
|
|
27
|
+
"React Stack": "react react-router redux axios",
|
|
28
|
+
"Vue.js Stack": "vue@latest vue-router vuex axios",
|
|
29
|
+
"Node.js API Stack": "express mongoose jsonwebtoken",
|
|
30
|
+
"Full-Stack JavaScript": "express mongoose react redux",
|
|
31
|
+
# Go (go install paths)
|
|
32
|
+
"Gin Stack": "github.com/gin-gonic/gin@latest",
|
|
33
|
+
"Beego Stack": "github.com/beego/beego/v2@latest",
|
|
34
|
+
"Web Framework Stack": "github.com/labstack/echo/v4@latest",
|
|
35
|
+
"Microservices Stack": "github.com/go-kit/kit@latest",
|
|
36
|
+
"Data Processing Stack": "github.com/onsi/ginkgo/v2@latest",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GenerateDockerfileRequest(BaseModel):
|
|
41
|
+
"""Pydantic model representing the API request for Dockerfile generation."""
|
|
42
|
+
|
|
43
|
+
language: str = Field(
|
|
44
|
+
..., description="Programming language for the Dockerfile (e.g. python, node)"
|
|
45
|
+
)
|
|
46
|
+
dependency_stack: str = Field(
|
|
47
|
+
..., description="Primary framework or dependency stack (e.g. Django, FastAPI)"
|
|
48
|
+
)
|
|
49
|
+
extra_dependencies: list[str] = Field(
|
|
50
|
+
default_factory=list, description="List of additional packages to install"
|
|
51
|
+
)
|
|
52
|
+
language_version: str = Field(
|
|
53
|
+
..., description="Version of the programming language (e.g. 3.11)"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@field_validator("extra_dependencies", mode="before")
|
|
57
|
+
@classmethod
|
|
58
|
+
def sanitize_dependencies(cls, v: list[str]) -> list[str]:
|
|
59
|
+
"""Validate extra dependencies against injection attacks."""
|
|
60
|
+
pattern = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._\-\[\]>=<!, ]*$")
|
|
61
|
+
for dep in v:
|
|
62
|
+
if not pattern.match(dep):
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Invalid dependency name: '{dep}'. "
|
|
65
|
+
"Only alphanumeric characters, hyphens, dots, and version specifiers are allowed."
|
|
66
|
+
)
|
|
67
|
+
return v
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def extra_dependencies_str(self) -> str:
|
|
71
|
+
"""Convert the list of extra dependencies to a space-separated string."""
|
|
72
|
+
return " ".join(self.extra_dependencies)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_event(cls, event: dict[str, Any]) -> "GenerateDockerfileRequest":
|
|
76
|
+
"""Create a GenerateDockerfileRequest instance from an API Gateway event."""
|
|
77
|
+
import json
|
|
78
|
+
dict_obj = json.loads(event["body"])
|
|
79
|
+
return cls(**dict_obj["config"])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DockerfileGenerator(BaseModel):
|
|
83
|
+
"""Service class for generating Dockerfile content."""
|
|
84
|
+
|
|
85
|
+
config: GenerateDockerfileRequest
|
|
86
|
+
|
|
87
|
+
def generate_dockerfile(self) -> str:
|
|
88
|
+
"""Generate Dockerfile content using the template and configuration."""
|
|
89
|
+
language = self.config.language.lower()
|
|
90
|
+
if language not in TEMPLATE_REGISTRY:
|
|
91
|
+
raise ValueError(f"Unsupported language: {self.config.language}. Supported: {', '.join(SUPPORTED_LANGUAGES)}")
|
|
92
|
+
|
|
93
|
+
start_template, end_template, version_placeholder = TEMPLATE_REGISTRY[language]
|
|
94
|
+
stack_packages = STACK_PACKAGES.get(self.config.dependency_stack, self.config.dependency_stack)
|
|
95
|
+
return (
|
|
96
|
+
start_template.replace(version_placeholder, self.config.language_version)
|
|
97
|
+
.replace("DEPENDENCY_STACK", stack_packages)
|
|
98
|
+
.replace("EXTRA_DEPENDENCIES", self.config.extra_dependencies_str)
|
|
99
|
+
+ end_template
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def generate_dockerfile_key_name(config: GenerateDockerfileRequest) -> str:
|
|
104
|
+
"""Generate a unique S3 key name for the Dockerfile."""
|
|
105
|
+
return (
|
|
106
|
+
f"dockerfile-{config.language}-{config.dependency_stack}-"
|
|
107
|
+
+ f"{config.language_version}-{'-'.join(config.extra_dependencies)}.dockerfile"
|
|
108
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Helper script with S3 functions"""
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import boto3
|
|
4
|
+
from mypy_boto3_s3.client import S3Client
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def upload_to_s3(
|
|
8
|
+
file_path: str,
|
|
9
|
+
bucket: Optional[str],
|
|
10
|
+
content: str,
|
|
11
|
+
region_name: Optional[str],
|
|
12
|
+
) -> None:
|
|
13
|
+
"""Upload to S3
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
file_path: S3 File path to save
|
|
17
|
+
bucket:
|
|
18
|
+
content: Body of the file
|
|
19
|
+
region_name:
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
ValueError:
|
|
23
|
+
"""
|
|
24
|
+
if not bucket:
|
|
25
|
+
raise ValueError("Bucket is required")
|
|
26
|
+
|
|
27
|
+
if not region_name:
|
|
28
|
+
raise ValueError("Region name is required")
|
|
29
|
+
|
|
30
|
+
s3_client: S3Client = boto3.client("s3", region_name=region_name)
|
|
31
|
+
s3_client.put_object(Bucket=bucket, Key=file_path, Body=content)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_if_file_exists_in_s3(bucket: Optional[str], key: str, region_name: Optional[str]) -> bool:
|
|
35
|
+
"""Check if a file exists in S3"""
|
|
36
|
+
if not bucket:
|
|
37
|
+
raise ValueError("Bucket is required")
|
|
38
|
+
|
|
39
|
+
if not region_name:
|
|
40
|
+
raise ValueError("Region name is required")
|
|
41
|
+
|
|
42
|
+
s3_client: S3Client = boto3.client("s3", region_name=region_name)
|
|
43
|
+
try:
|
|
44
|
+
s3_client.head_object(Bucket=bucket, Key=key)
|
|
45
|
+
return True
|
|
46
|
+
except Exception:
|
|
47
|
+
return False
|