Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ==============================================================================
# METADEPLOY HYPER-EXHAUSTIVE TEST CONFIGURATION
# ==============================================================================

# --- [GIT & AUTH] ---
GIT_URL=https://github.com/OpsGuild/MetalDeploy.git
GIT_USER=hordunlarmy
GIT_AUTH_METHOD=none

# --- [REMOTE TARGETS] ---
ENVIRONMENT=dev
REMOTE_USER=root
REMOTE_HOST=localhost
REMOTE_PORT=2222
REMOTE_DIR=/opt/metaldeploy

# --- [ENVIRONMENT FILE GENERATION] ---
ENV_FILES_GENERATE=true
ENV_FILES_STRUCTURE=auto
ENV_FILES_FORMAT=auto

# ==============================================================================
# EXHAUSTIVE TEST SECRETS (Multi-Format & Components)
# ==============================================================================

# --- [STANDARD ENV BLOBS] ---
ENV_APP=APP_BASE_URL=https://app.com\nAPP_PORT=8000\nAPP_DEBUG=true
ENV_PROD_APP=APP_PORT=9000\nAPP_SECRET=prod-exclusive-secret

# --- [JSON BLOBS] ---
ENV_DATABASE={"DB_URL": "postgres://db:5432", "DB_USER": "json-admin"}
ENV_PROD_DATABASE={"DB_USER": "prod-json-admin"}

# --- [YAML BLOBS] ---
ENV_REDIS="REDIS_HOST: redis-yaml-master\nREDIS_PORT: 6379"
ENV_PROD_REDIS="REDIS_HOST: redis-prod-yaml-cluster\nREDIS_PORT: 6379"

# --- [INDIVIDUAL OVERRIDES] ---
ENV_MINIO_ENDPOINT=http://minio:9000
ENV_S3_BUCKET=my-assets
ENV_KAFKA_TOPIC=events-main
ENV_ELASTIC_URL=http://elastic:9200
ENV_API_KEY=api-key-12345

# --- [MULTI-ENV OVERRIDES] ---
ENV_STAGING_APP_PORT=3000
ENV_DEV_APP_PORT=8000
ENV_PROD_APP_PORT=9000
4 changes: 4 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]
max-line-length = 100
ignore = E501, W503, E203
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,*.egg-info,.venv,venv,generated_envs,generated_envs_2,tests/generated_envs*,tests/integration/generated_envs*
26 changes: 0 additions & 26 deletions .github/workflows/example-baremetal.yml

This file was deleted.

30 changes: 0 additions & 30 deletions .github/workflows/example-docker.yml

This file was deleted.

27 changes: 0 additions & 27 deletions .github/workflows/example-k8s.yml

This file was deleted.

30 changes: 30 additions & 0 deletions .github/workflows/release-tagging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Update Major Version Tags

on:
push:
tags:
- 'v*.*.*' # Trigger only on full semantic version tags

jobs:
tag:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Update Major Tag
run: |
# Extract major version (e.g., v1 from v1.2.3)
MAJOR=$(echo "${{ github.ref_name }}" | cut -d. -f1)

echo "Updating major tag: $MAJOR to point to ${{ github.ref_name }}"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Force create/move major tag
git tag -f "$MAJOR" "${{ github.ref_name }}"
git push origin "$MAJOR" --force
48 changes: 5 additions & 43 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ jobs:
run: |
poetry install --with dev

- name: Run unit tests
run: |
poetry run pytest tests/ -v --cov=src --cov=main --cov-report=xml --cov-report=html -m "not integration and not slow"
- name: Run tests
run: make test

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
Expand All @@ -48,28 +47,7 @@ jobs:
fail_ci_if_error: false

- name: Validate action.yml
run: |
# Check if action.yml is valid YAML
poetry run python -c "import yaml; yaml.safe_load(open('action.yml'))"
echo "✅ action.yml is valid YAML"

# Check for required fields
if ! grep -q "name:" action.yml; then
echo "❌ Missing 'name' in action.yml"
exit 1
fi

if ! grep -q "description:" action.yml; then
echo "❌ Missing 'description' in action.yml"
exit 1
fi

if ! grep -q "runs:" action.yml; then
echo "❌ Missing 'runs' in action.yml"
exit 1
fi

echo "✅ action.yml validation passed"
run: make validate

- name: Check Python syntax
run: |
Expand Down Expand Up @@ -98,21 +76,5 @@ jobs:
run: |
poetry install --with dev

- name: Run unit tests
run: |
poetry run pytest tests/ -v -m "not integration and not slow"

- name: Run flake8
run: |
poetry run flake8 . --max-line-length=100 --ignore=E501,W503 --exclude=.git,__pycache__,*.pyc,.pytest_cache,.venv,venv
continue-on-error: true

- name: Check code formatting with black
run: |
poetry run black --check .
continue-on-error: true

- name: Check import sorting with isort
run: |
poetry run isort --check-only .
continue-on-error: true
- name: Run linting
run: make lint
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ htmlcov/
.pytest_cache/
.tox/
.hypothesis/
generated_envs/
generated_envs_2/
**/generated_envs/
**/generated_envs_2/

# IDEs
.vscode/
Expand Down
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
exclude: '^tests/.*generated_envs/.*'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
Expand Down Expand Up @@ -29,5 +30,5 @@ repos:
- id: flake8
args:
- --max-line-length=100
- --ignore=E501,W503
- --ignore=E501,W503,E203
additional_dependencies: [flake8]
106 changes: 59 additions & 47 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,66 +1,78 @@
pipeline {
agent {
docker {
image "ghcr.io/opsguild/metal-deploy:latest"
// Mount docker socket if performing docker/k8s deployments
args '-v /var/run/docker.sock:/var/run/docker.sock'
image "ghcr.io/opsguild/metaldeploy:latest"
registryUrl "https://ghcr.io"
registryCredentialsId "github-registry-auth"
// Reset entrypoint to allow running shell commands
args '--entrypoint="" -v /var/run/docker.sock:/var/run/docker.sock'
}
}

parameters {
string(name: 'GIT_URL', defaultValue: '', description: 'Git repository URL to clone and deploy')
choice(name: 'GIT_AUTH_METHOD', choices: ['token', 'ssh', 'none'], description: 'Git authentication method')
password(name: 'GIT_TOKEN', defaultValue: '', description: 'GitHub token (if using token auth)')
string(name: 'GIT_USER', defaultValue: '', description: 'GitHub username (if using token auth)')
string(name: 'ENVIRONMENT', defaultValue: 'dev', description: 'Deployment environment (dev, staging, prod)')
string(name: 'REMOTE_HOST', defaultValue: '', description: 'SSH remote host IP or domain')
string(name: 'REMOTE_USER', defaultValue: 'root', description: 'SSH remote user')
password(name: 'SSH_KEY', defaultValue: '', description: 'SSH private key (base64 encoded or raw)')
choice(name: 'DEPLOYMENT_TYPE', choices: ['baremetal', 'docker', 'k8s'], description: 'Deployment type')
}

environment {
// Map Jenkins parameters to environment variables used by the script
GIT_URL = "${params.GIT_URL}"
GIT_AUTH_METHOD = "${params.GIT_AUTH_METHOD}"
GIT_TOKEN = "${params.GIT_TOKEN}"
GIT_USER = "${params.GIT_USER}"
ENVIRONMENT = "${params.ENVIRONMENT}"
REMOTE_HOST = "${params.REMOTE_HOST}"
REMOTE_USER = "${params.REMOTE_USER}"
SSH_KEY = "${params.SSH_KEY}"
DEPLOYMENT_TYPE = "${params.DEPLOYMENT_TYPE}"

// Environment File Generation examples:
// Variables starting with ENV_ will automatically be converted to .env files
ENV_FILES_GENERATE = "true"
// ENV_APP_DATABASE_URL = "postgres://user:pass@host:5432/db" // Map from credentials
GIT_AUTH_METHOD = 'token'
GIT_URL = "${env.GIT_URL}"
GIT_USER = 'hordunlarmy'
USE_SUDO = 'true'
DEPLOYMENT_TYPE= 'baremetal'
ENV_FILES_GENERATE = 'true'
ENV_FILES_STRUCTURE = 'auto'
ENV_FILES_CREATE_ROOT = 'false'
ENV_FILES_FORMAT = 'auto'
}

stages {
stage('Deploy') {
// --- STAGING ---
stage('Deploy Staging') {
when { branch 'staging' }
steps {
script {
// Automatically map ALL Jenkins parameters to environment variables
// This means any parameter you define (e.g. ENV_APP_DB)
// is automatically available to the script without manual mapping.
def paramEnv = params.collect { k, v -> "${k}=${v}" }
withCredentials([
// Staging Secrets (Use unique IDs!)
string(credentialsId: 'git-token', variable: 'GIT_TOKEN'),
string(credentialsId: 'staging-remote-pass', variable: 'REMOTE_PASSWORD'),
string(credentialsId: 'staging-remote-host', variable: 'REMOTE_HOST'),

withEnv(paramEnv) {
echo "Starting deployment to ${env.REMOTE_HOST} (${env.ENVIRONMENT})..."
sh "python main.py"
}
file(credentialsId: 'staging-app-env', variable: 'ENV_APP'),
file(credentialsId: 'staging-atlas-env', variable: 'ENV_ATLAS'),
file(credentialsId: 'staging-database-env', variable: 'ENV_DATABASE'),
file(credentialsId: 'staging-minio-env', variable: 'ENV_MINIO'),
file(credentialsId: 'staging-redis-env', variable: 'ENV_REDIS')
]) {
script {
def cmd = 'export env=staging && make up && make migrate'
withEnv(["ENVIRONMENT=staging", "DEPLOY_COMMAND=${cmd}"]) {
// Use absolute path to the tool inside the container
sh "python /app/main.py"
}
}
}
}
}
}

post {
success {
echo "Deployment successful!"
}
failure {
echo "Deployment failed!"
// --- PRODUCTION ---
stage('Deploy Production') {
when { branch 'main' }
steps {
withCredentials([
// Production Secrets
string(credentialsId: 'git-token', variable: 'GIT_TOKEN'),
string(credentialsId: 'prod-remote-pass', variable: 'REMOTE_PASSWORD'),
string(credentialsId: 'prod-remote-host', variable: 'REMOTE_HOST'),

file(credentialsId: 'prod-app-env', variable: 'ENV_APP'),
file(credentialsId: 'prod-atlas-env', variable: 'ENV_ATLAS'),
file(credentialsId: 'prod-database-env', variable: 'ENV_DATABASE'),
file(credentialsId: 'prod-minio-env', variable: 'ENV_MINIO'),
file(credentialsId: 'prod-redis-env', variable: 'ENV_REDIS')
]) {
script {
def cmd = 'export env=prod && make up && make migrate'
withEnv(["ENVIRONMENT=prod", "DEPLOY_COMMAND=${cmd}"]) {
sh "python /app/main.py"
}
}
}
}
}
}
}
Loading