Skip to content
Open
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
124 changes: 124 additions & 0 deletions .github/workflows/my-app-complete.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: my-app CI/CD
'on':
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
inputs:
environment:
description: Environment to deploy to
required: true
default: dev
type: choice
options:
- dev
- test
- staging
- prod
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Set up build environment
run: echo 'Setting up build environment for DevOps-OS'
- name: Install Python dependencies
run: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Build Python package
run: if [ -f setup.py ]; then pip install -e .; elif [ -f pyproject.toml ];
then pip install -e .; fi
- name: Install Node.js dependencies
run: if [ -f package.json ]; then npm ci; fi
- name: Build JavaScript/TypeScript
run: if [ -f package.json ]; then npm run build --if-present; fi
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: dist/
retention-days: 1
if-no-files-found: warn
test:
needs:
- build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Set up test environment
run: echo 'Setting up test environment for DevOps-OS'
- name: Install Python dependencies
run: |
python -m pip install -r cli/requirements.txt -r mcp_server/requirements.txt
python -m pip install pytest pytest-cov
- name: Run Python tests
run: if [ -d tests ]; then python -m pytest --cov=./ --cov-report=xml; fi
- name: Run Pylint
run: if command -v pylint &> /dev/null; then pylint --disable=C0111 **/*.py;
fi
- name: Install Node.js dependencies
run: if [ -f package.json ]; then npm ci; fi
- name: Run JavaScript tests
run: if [ -f package.json ]; then npm test; fi
- name: Run ESLint
run: if [ -f package.json ] && grep -q eslint package.json; then npm run lint;
fi
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-reports/
retention-days: 1
if-no-files-found: warn
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml,./coverage/lcov.info
fail_ci_if_error: false
deploy:
needs:
- test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up deployment environment
run: echo 'Setting up deployment environment for DevOps-OS'
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Docker Image
if: github.ref == 'refs/heads/main'
env:
IMAGE_NAME: ghcr.io/${{ github.repository }}
run: |
docker build -t "$IMAGE_NAME:latest" -t "$IMAGE_NAME:${{ github.sha }}" .
docker push "$IMAGE_NAME:latest"
docker push "$IMAGE_NAME:${{ github.sha }}"
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,24 @@ python -m cli.devopsos scaffold gha --name my-app --languages python --type comp

# With Kubernetes deployment via Kustomize
python -m cli.devopsos scaffold gha --name my-app --languages python --kubernetes --k8s-method kustomize

# Return a machine-readable result for automation pipelines
python -m cli.devopsos scaffold gha --name my-app --json
```

With `--json`, the command keeps the generated files unchanged and writes only
the result object to standard output, for example:

```json
{
"workflow": ".github/workflows/my-app-complete.yml",
"type": "github_actions"
}
```

The flag is available on every `scaffold` target. Multi-file generators return
the generated paths in a `files` array.

---

### 4 — Generate other pipelines & configs
Expand Down
39 changes: 28 additions & 11 deletions cli/devopsos.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import enum
import io
import sys
import typer
from InquirerPy import inquirer
import json
import os
from pathlib import Path
from typing import Optional
from contextlib import redirect_stdout

# Import scaffold modules — used as libraries by the unified scaffold sub-commands
import cli.scaffold_cicd as scaffold_cicd
Expand Down Expand Up @@ -90,7 +92,7 @@ def main(
app.add_typer(scaffold_app, name="scaffold")


def _run_scaffold(module_main, flags: list):
def _run_scaffold(module_main, flags: list, json_output: bool = False):
"""Call *module_main()* with the given CLI flag list via sys.argv.

Each scaffold module uses argparse internally. We temporarily replace
Expand All @@ -100,9 +102,15 @@ def _run_scaffold(module_main, flags: list):
_saved = sys.argv[:]
sys.argv = sys.argv[:1] + flags
try:
module_main()
if json_output:
with redirect_stdout(io.StringIO()):
result = module_main()
typer.echo(json.dumps(result, indent=2))
else:
result = module_main()
finally:
sys.argv = _saved
return result


def _show_help_if_no_opts(ctx: typer.Context) -> None:
Expand All @@ -123,6 +131,7 @@ def _show_help_if_no_opts(ctx: typer.Context) -> None:
@scaffold_app.command("gha")
def scaffold_gha_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
name: str = typer.Option("DevOps-OS", envvar="DEVOPS_OS_GHA_NAME",
help="Workflow name"),
workflow_type: str = typer.Option("complete", "--type", envvar="DEVOPS_OS_GHA_TYPE",
Expand Down Expand Up @@ -181,14 +190,15 @@ def scaffold_gha_cmd(
flags += ["--custom-values", custom_values]
if env_file:
flags += ["--env-file", env_file]
_run_scaffold(scaffold_gha.main, flags)
_run_scaffold(scaffold_gha.main, flags, json_output)


# ── scaffold jenkins ────────────────────────────────────────────────────────

@scaffold_app.command("jenkins")
def scaffold_jenkins_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
name: str = typer.Option("DevOps-OS", envvar="DEVOPS_OS_JENKINS_NAME",
help="Pipeline name"),
pipeline_type: str = typer.Option("complete", "--type", envvar="DEVOPS_OS_JENKINS_TYPE",
Expand Down Expand Up @@ -242,14 +252,15 @@ def scaffold_jenkins_cmd(
flags += ["--custom-values", custom_values]
if env_file:
flags += ["--env-file", env_file]
_run_scaffold(scaffold_jenkins.main, flags)
_run_scaffold(scaffold_jenkins.main, flags, json_output)


# ── scaffold gitlab ─────────────────────────────────────────────────────────

@scaffold_app.command("gitlab")
def scaffold_gitlab_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
name: str = typer.Option("my-app", envvar="DEVOPS_OS_GITLAB_NAME",
help="Application / pipeline name"),
pipeline_type: str = typer.Option("complete", "--type", envvar="DEVOPS_OS_GITLAB_TYPE",
Expand Down Expand Up @@ -295,14 +306,15 @@ def scaffold_gitlab_cmd(
flags += ["--kube-namespace", kube_namespace]
if custom_values:
flags += ["--custom-values", custom_values]
_run_scaffold(scaffold_gitlab.main, flags)
_run_scaffold(scaffold_gitlab.main, flags, json_output)


# ── scaffold argocd ─────────────────────────────────────────────────────────

@scaffold_app.command("argocd")
def scaffold_argocd_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
name: str = typer.Option("my-app", envvar="DEVOPS_OS_ARGOCD_NAME",
help="Application name"),
method: str = typer.Option("argocd", envvar="DEVOPS_OS_ARGOCD_METHOD",
Expand Down Expand Up @@ -358,14 +370,15 @@ def scaffold_argocd_cmd(
flags.append("--rollouts")
if allow_any_source_repo:
flags.append("--allow-any-source-repo")
_run_scaffold(scaffold_argocd.main, flags)
_run_scaffold(scaffold_argocd.main, flags, json_output)


# ── scaffold sre ────────────────────────────────────────────────────────────

@scaffold_app.command("sre")
def scaffold_sre_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
name: str = typer.Option("my-app", envvar="DEVOPS_OS_SRE_NAME",
help="Application / service name"),
team: str = typer.Option("platform", envvar="DEVOPS_OS_SRE_TEAM",
Expand Down Expand Up @@ -414,14 +427,15 @@ def scaffold_sre_cmd(
]
if pagerduty_key:
flags += ["--pagerduty-key", pagerduty_key]
_run_scaffold(scaffold_sre.main, flags)
_run_scaffold(scaffold_sre.main, flags, json_output)


# ── scaffold devcontainer ───────────────────────────────────────────────────

@scaffold_app.command("devcontainer")
def scaffold_devcontainer_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
languages: str = typer.Option("python", envvar="DEVOPS_OS_DEVCONTAINER_LANGUAGES",
help="Comma-separated languages to enable (default: python)"),
cicd_tools: str = typer.Option("docker,github_actions", "--cicd-tools",
Expand Down Expand Up @@ -506,14 +520,15 @@ def scaffold_devcontainer_cmd(
"--grafana-version", grafana_version,
"--output-dir", output_dir,
]
_run_scaffold(scaffold_devcontainer.main, flags)
_run_scaffold(scaffold_devcontainer.main, flags, json_output)


# ── scaffold cicd ───────────────────────────────────────────────────────────

@scaffold_app.command("cicd")
def scaffold_cicd_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
name: str = typer.Option("DevOps-OS", help="CI/CD pipeline name"),
cicd_type: str = typer.Option("complete", "--type",
help="Pipeline type: build | test | deploy | complete"),
Expand Down Expand Up @@ -569,14 +584,15 @@ def scaffold_cicd_cmd(
flags.append("--all")
if custom_values:
flags += ["--custom-values", custom_values]
_run_scaffold(scaffold_cicd.main, flags)
_run_scaffold(scaffold_cicd.main, flags, json_output)


# ── scaffold unittest ────────────────────────────────────────────────────────

@scaffold_app.command("unittest")
def scaffold_unittest_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
name: str = typer.Option("my-app", envvar="DEVOPS_OS_UNITTEST_NAME",
help="Project / application name"),
languages: str = typer.Option("python", envvar="DEVOPS_OS_UNITTEST_LANGUAGES",
Expand Down Expand Up @@ -636,14 +652,15 @@ def scaffold_unittest_cmd(
if not coverage:
# coverage defaults to True; only pass flag when False
flags.append("--no-coverage")
_run_scaffold(scaffold_unittest.main, flags)
_run_scaffold(scaffold_unittest.main, flags, json_output)


# ── scaffold hardening ──────────────────────────────────────────────────────

@scaffold_app.command("hardening")
def scaffold_hardening_cmd(
ctx: typer.Context,
json_output: bool = typer.Option(False, "--json", help="Print the generated result as JSON"),
standard: str = typer.Option("all", envvar="DEVOPS_OS_HARDENING_STANDARD",
help=(
"Hardening standard: cis-k8s, stig-k8s, nsa-k8s, "
Expand Down Expand Up @@ -689,7 +706,7 @@ def scaffold_hardening_cmd(
]
if compliance_framework:
flags += ["--compliance-framework", compliance_framework]
_run_scaffold(scaffold_hardening.main, flags)
_run_scaffold(scaffold_hardening.main, flags, json_output)


@app.command()
Expand Down
2 changes: 2 additions & 0 deletions cli/scaffold_argocd.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ def main():
for p in generated:
print(f" {p}")

return {"files": generated, "type": args.method}


if __name__ == "__main__":
main()
Loading