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.
@@ -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
+ ![Architecture Diagram](./images/StackForDev.png)
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
+ ![Architecture Diagram](./images/StackForDev.png)
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