chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DevOps Utility Scripts
|
||||
Provides helper functions for common DevOps tasks
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
class TerraformHelper:
|
||||
"""Helper functions for Terraform operations"""
|
||||
|
||||
@staticmethod
|
||||
def init_project(name: str, cloud: str, region: str) -> None:
|
||||
"""Initialize a new Terraform project structure"""
|
||||
base_path = Path(name)
|
||||
|
||||
# Create directory structure
|
||||
directories = [
|
||||
base_path / "environments" / "dev",
|
||||
base_path / "environments" / "staging",
|
||||
base_path / "environments" / "prod",
|
||||
base_path / "modules" / "vpc",
|
||||
base_path / "modules" / "compute",
|
||||
base_path / "modules" / "database",
|
||||
]
|
||||
|
||||
for directory in directories:
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Created: {directory}")
|
||||
|
||||
# Create main.tf template
|
||||
main_tf_content = f"""terraform {{
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {{
|
||||
{cloud} = {{
|
||||
source = "hashicorp/{cloud}"
|
||||
version = "~> 5.0"
|
||||
}}
|
||||
}}
|
||||
|
||||
backend "s3" {{
|
||||
bucket = "terraform-state-{name}"
|
||||
key = "{{{{env}}}}/terraform.tfstate"
|
||||
region = "{region}"
|
||||
encrypt = true
|
||||
dynamodb_table = "terraform-state-lock"
|
||||
}}
|
||||
}}
|
||||
|
||||
provider "{cloud}" {{
|
||||
region = "{region}"
|
||||
}}
|
||||
"""
|
||||
|
||||
for env in ["dev", "staging", "prod"]:
|
||||
env_path = base_path / "environments" / env
|
||||
(env_path / "main.tf").write_text(main_tf_content.replace("{{env}}", env))
|
||||
(env_path / "variables.tf").write_text(
|
||||
'variable "environment" {\n description = "Environment name"\n type = string\n default = "'
|
||||
+ env
|
||||
+ '"\n}\n'
|
||||
)
|
||||
(env_path / "outputs.tf").write_text("# Define outputs here\n")
|
||||
print(f"Created Terraform files in: {env_path}")
|
||||
|
||||
# Create README
|
||||
readme_content = f"""# {name} Infrastructure
|
||||
|
||||
## Structure
|
||||
- `environments/`: Environment-specific configurations
|
||||
- `modules/`: Reusable Terraform modules
|
||||
|
||||
## Usage
|
||||
|
||||
### Initialize
|
||||
```bash
|
||||
cd environments/dev
|
||||
terraform init
|
||||
```
|
||||
|
||||
### Plan
|
||||
```bash
|
||||
terraform plan -out=tfplan
|
||||
```
|
||||
|
||||
### Apply
|
||||
```bash
|
||||
terraform apply tfplan
|
||||
```
|
||||
"""
|
||||
(base_path / "README.md").write_text(readme_content)
|
||||
print(f"\nProject '{name}' initialized successfully!")
|
||||
|
||||
@staticmethod
|
||||
def validate_hcl(file_path: str) -> bool:
|
||||
"""Validate Terraform HCL syntax"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["terraform", "fmt", "-check", file_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print(f"✓ {file_path} is properly formatted")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ {file_path} needs formatting")
|
||||
print(result.stdout)
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print("Error: terraform command not found")
|
||||
return False
|
||||
|
||||
|
||||
class KubernetesHelper:
|
||||
"""Helper functions for Kubernetes operations"""
|
||||
|
||||
@staticmethod
|
||||
def validate_manifest(file_path: str, schema_version: str = "1.28") -> bool:
|
||||
"""Validate Kubernetes manifest syntax"""
|
||||
try:
|
||||
with open(file_path, "r") as f:
|
||||
docs = list(yaml.safe_load_all(f))
|
||||
|
||||
print(f"Validating {len(docs)} document(s) in {file_path}")
|
||||
|
||||
for i, doc in enumerate(docs):
|
||||
if not doc:
|
||||
continue
|
||||
|
||||
# Basic validation
|
||||
if "apiVersion" not in doc:
|
||||
print(f"✗ Document {i + 1}: Missing apiVersion")
|
||||
return False
|
||||
|
||||
if "kind" not in doc:
|
||||
print(f"✗ Document {i + 1}: Missing kind")
|
||||
return False
|
||||
|
||||
if "metadata" not in doc:
|
||||
print(f"✗ Document {i + 1}: Missing metadata")
|
||||
return False
|
||||
|
||||
print(
|
||||
f"✓ Document {i + 1}: {doc['kind']} '{doc['metadata'].get('name', 'unnamed')}'"
|
||||
)
|
||||
|
||||
print(f"✓ All documents in {file_path} are valid")
|
||||
return True
|
||||
|
||||
except yaml.YAMLError as e:
|
||||
print(f"✗ YAML syntax error: {e}")
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print(f"✗ File not found: {file_path}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def generate_deployment(
|
||||
name: str, image: str, namespace: str = "default", replicas: int = 3
|
||||
) -> str:
|
||||
"""Generate a Kubernetes deployment manifest"""
|
||||
manifest = {
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {"name": name, "namespace": namespace, "labels": {"app": name}},
|
||||
"spec": {
|
||||
"replicas": replicas,
|
||||
"selector": {"matchLabels": {"app": name}},
|
||||
"template": {
|
||||
"metadata": {"labels": {"app": name}},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": name,
|
||||
"image": image,
|
||||
"ports": [{"containerPort": 8080}],
|
||||
"resources": {
|
||||
"requests": {"memory": "256Mi", "cpu": "250m"},
|
||||
"limits": {"memory": "512Mi", "cpu": "500m"},
|
||||
},
|
||||
"livenessProbe": {
|
||||
"httpGet": {"path": "/healthz", "port": 8080},
|
||||
"initialDelaySeconds": 30,
|
||||
"periodSeconds": 10,
|
||||
},
|
||||
"readinessProbe": {
|
||||
"httpGet": {"path": "/ready", "port": 8080},
|
||||
"initialDelaySeconds": 10,
|
||||
"periodSeconds": 5,
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return yaml.dump(manifest, default_flow_style=False, sort_keys=False)
|
||||
|
||||
|
||||
class GitOpsHelper:
|
||||
"""Helper functions for GitOps workflows"""
|
||||
|
||||
@staticmethod
|
||||
def init_gitops(tool: str, environments: List[str]) -> None:
|
||||
"""Initialize GitOps directory structure"""
|
||||
base_path = Path("gitops")
|
||||
|
||||
if tool.lower() == "argocd":
|
||||
# ArgoCD structure
|
||||
for env in environments:
|
||||
env_path = base_path / "applications" / env
|
||||
env_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create application manifest
|
||||
app_manifest = {
|
||||
"apiVersion": "argoproj.io/v1alpha1",
|
||||
"kind": "Application",
|
||||
"metadata": {
|
||||
"name": f"myapp-{env}",
|
||||
"namespace": "argocd",
|
||||
},
|
||||
"spec": {
|
||||
"project": "default",
|
||||
"source": {
|
||||
"repoURL": "https://github.com/myorg/myapp.git",
|
||||
"targetRevision": "HEAD",
|
||||
"path": f"kubernetes/overlays/{env}",
|
||||
},
|
||||
"destination": {
|
||||
"server": "https://kubernetes.default.svc",
|
||||
"namespace": env,
|
||||
},
|
||||
"syncPolicy": {
|
||||
"automated": {
|
||||
"prune": True,
|
||||
"selfHeal": True,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
with open(env_path / "application.yaml", "w") as f:
|
||||
yaml.dump(app_manifest, f, default_flow_style=False)
|
||||
|
||||
print(f"Created ArgoCD application for: {env}")
|
||||
|
||||
elif tool.lower() == "flux":
|
||||
# Flux structure
|
||||
for env in environments:
|
||||
env_path = base_path / "clusters" / env
|
||||
env_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create kustomization
|
||||
kustomization = {
|
||||
"apiVersion": "kustomize.toolkit.fluxcd.io/v1",
|
||||
"kind": "Kustomization",
|
||||
"metadata": {"name": f"myapp-{env}", "namespace": "flux-system"},
|
||||
"spec": {
|
||||
"interval": "5m",
|
||||
"path": f"./kubernetes/overlays/{env}",
|
||||
"prune": True,
|
||||
"sourceRef": {"kind": "GitRepository", "name": "myapp"},
|
||||
},
|
||||
}
|
||||
|
||||
with open(env_path / "kustomization.yaml", "w") as f:
|
||||
yaml.dump(kustomization, f, default_flow_style=False)
|
||||
|
||||
print(f"Created Flux kustomization for: {env}")
|
||||
|
||||
print(f"\nGitOps structure initialized for {tool}")
|
||||
|
||||
|
||||
class SecurityHelper:
|
||||
"""Helper functions for security operations"""
|
||||
|
||||
@staticmethod
|
||||
def scan_secrets(directory: str) -> List[str]:
|
||||
"""Scan for potential secrets in files"""
|
||||
import re
|
||||
|
||||
secret_patterns = {
|
||||
"AWS Access Key": r"AKIA[0-9A-Z]{16}",
|
||||
"Private Key": r"-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----",
|
||||
"API Key": r"api[_-]?key['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9]{32,}",
|
||||
"Password": r"password['\"]?\s*[:=]\s*['\"]?[^'\"\s]{8,}",
|
||||
"Token": r"token['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9._\-]{20,}",
|
||||
}
|
||||
|
||||
findings = []
|
||||
|
||||
for root, dirs, files in os.walk(directory):
|
||||
# Skip common non-code directories
|
||||
dirs[:] = [
|
||||
d
|
||||
for d in dirs
|
||||
if d not in [".git", "node_modules", "venv", ".terraform"]
|
||||
]
|
||||
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
content = f.read()
|
||||
|
||||
for secret_type, pattern in secret_patterns.items():
|
||||
matches = re.finditer(pattern, content)
|
||||
for match in matches:
|
||||
findings.append(
|
||||
f"{file_path}:{secret_type} - {match.group()[:20]}..."
|
||||
)
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="DevOps Utility Scripts")
|
||||
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
||||
|
||||
# Terraform commands
|
||||
tf_parser = subparsers.add_parser("terraform", help="Terraform utilities")
|
||||
tf_subparsers = tf_parser.add_subparsers(dest="subcommand")
|
||||
|
||||
tf_init = tf_subparsers.add_parser("init-project", help="Initialize Terraform project")
|
||||
tf_init.add_argument("--name", required=True, help="Project name")
|
||||
tf_init.add_argument(
|
||||
"--cloud", required=True, choices=["aws", "azure", "gcp"], help="Cloud provider"
|
||||
)
|
||||
tf_init.add_argument("--region", required=True, help="Default region")
|
||||
|
||||
tf_validate = tf_subparsers.add_parser("validate", help="Validate Terraform files")
|
||||
tf_validate.add_argument("--file", required=True, help="File to validate")
|
||||
|
||||
# Kubernetes commands
|
||||
k8s_parser = subparsers.add_parser("k8s", help="Kubernetes utilities")
|
||||
k8s_subparsers = k8s_parser.add_subparsers(dest="subcommand")
|
||||
|
||||
k8s_validate = k8s_subparsers.add_parser("validate", help="Validate K8s manifest")
|
||||
k8s_validate.add_argument("--file", required=True, help="Manifest file")
|
||||
k8s_validate.add_argument(
|
||||
"--schema-version", default="1.28", help="Kubernetes version"
|
||||
)
|
||||
|
||||
k8s_generate = k8s_subparsers.add_parser("generate", help="Generate deployment")
|
||||
k8s_generate.add_argument("--name", required=True, help="Deployment name")
|
||||
k8s_generate.add_argument("--image", required=True, help="Container image")
|
||||
k8s_generate.add_argument("--namespace", default="default", help="Namespace")
|
||||
k8s_generate.add_argument("--replicas", type=int, default=3, help="Replica count")
|
||||
|
||||
# GitOps commands
|
||||
gitops_parser = subparsers.add_parser("gitops", help="GitOps utilities")
|
||||
gitops_subparsers = gitops_parser.add_subparsers(dest="subcommand")
|
||||
|
||||
gitops_init = gitops_subparsers.add_parser("init", help="Initialize GitOps structure")
|
||||
gitops_init.add_argument(
|
||||
"--tool", required=True, choices=["argocd", "flux"], help="GitOps tool"
|
||||
)
|
||||
gitops_init.add_argument(
|
||||
"--environments", required=True, help="Comma-separated environments"
|
||||
)
|
||||
|
||||
# Security commands
|
||||
security_parser = subparsers.add_parser("security", help="Security utilities")
|
||||
security_subparsers = security_parser.add_subparsers(dest="subcommand")
|
||||
|
||||
security_scan = security_subparsers.add_parser("scan-secrets", help="Scan for secrets")
|
||||
security_scan.add_argument("--directory", default=".", help="Directory to scan")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Execute commands
|
||||
if args.command == "terraform":
|
||||
if args.subcommand == "init-project":
|
||||
TerraformHelper.init_project(args.name, args.cloud, args.region)
|
||||
elif args.subcommand == "validate":
|
||||
TerraformHelper.validate_hcl(args.file)
|
||||
|
||||
elif args.command == "k8s":
|
||||
if args.subcommand == "validate":
|
||||
KubernetesHelper.validate_manifest(args.file, args.schema_version)
|
||||
elif args.subcommand == "generate":
|
||||
manifest = KubernetesHelper.generate_deployment(
|
||||
args.name, args.image, args.namespace, args.replicas
|
||||
)
|
||||
print(manifest)
|
||||
|
||||
elif args.command == "gitops":
|
||||
if args.subcommand == "init":
|
||||
environments = [env.strip() for env in args.environments.split(",")]
|
||||
GitOpsHelper.init_gitops(args.tool, environments)
|
||||
|
||||
elif args.command == "security":
|
||||
if args.subcommand == "scan-secrets":
|
||||
findings = SecurityHelper.scan_secrets(args.directory)
|
||||
if findings:
|
||||
print(f"⚠️ Found {len(findings)} potential secrets:")
|
||||
for finding in findings:
|
||||
print(f" - {finding}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("✓ No secrets found")
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user