{"id":15049990,"url":"https://github.com/linsomniac/uplaybook","last_synced_at":"2025-09-03T12:46:10.283Z","repository":{"id":197703839,"uuid":"699156630","full_name":"linsomniac/uplaybook","owner":"linsomniac","description":"A python-centric IT automation system.","archived":false,"fork":false,"pushed_at":"2025-08-25T16:31:28.000Z","size":771,"stargazers_count":9,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-28T17:56:28.256Z","etag":null,"topics":["ansible","automation","cookiecutter","declarative","python"],"latest_commit_sha":null,"homepage":"https://linsomniac.github.io/uplaybook/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/linsomniac.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-02T03:34:17.000Z","updated_at":"2025-07-29T18:22:34.000Z","dependencies_parsed_at":"2023-10-02T04:44:36.347Z","dependency_job_id":"d67a5f36-d9b6-4f58-858f-718bcf10c7f4","html_url":"https://github.com/linsomniac/uplaybook","commit_stats":null,"previous_names":["linsomniac/uplaybook2","linsomniac/uplaybook"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/linsomniac/uplaybook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linsomniac%2Fuplaybook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linsomniac%2Fuplaybook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linsomniac%2Fuplaybook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linsomniac%2Fuplaybook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/linsomniac","download_url":"https://codeload.github.com/linsomniac/uplaybook/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linsomniac%2Fuplaybook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273445904,"owners_count":25107150,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansible","automation","cookiecutter","declarative","python"],"created_at":"2024-09-24T21:24:06.539Z","updated_at":"2025-09-03T12:46:10.212Z","avatar_url":"https://github.com/linsomniac.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"docs/assets/logo.png\" width=\"25%\" height=\"25%\" /\u003e\u003cbr /\u003euPlaybook\u003c/p\u003e\n\n# Unleash the Power of Automation with uPlaybook\n\nuPlaybook (pronounced \"Micro Playbook\") fits in between templaters like Cookiecutter and\nfull \"fleet level\" Configuration Management/Infrastructure as Code ecosystem like Ansible.\nIf Ansible was a shell script: you'd have uPlaybook.\n\nuPlaybook only targets running on a\nsingle system, so the complexities of managing your \"fleet inventory\" are gone.  On top of\nthat is\n\nuPlaybook consists of \"playbooks\" of declarative \"tasks\" with the full power of Python,\nproviding for a richer scripting experience than YAML (as done in Ansible).  The\ndeclarative nature allows you to specify the desired state of a system or service, and the\nplaybook will make the required changes.  Modify the playbook and re-run it to gain new\nstate, and keep the playbook in git to version and reuse it.\n\n# Tutorial\n\n[Jump to the Tutorial](https://linsomniac.github.io/uplaybook/tutorial/)\n\n# Benefits\n\n- Targets running on the local system: Does away with the complexities of managing a\n  \"fleet infrastructure\".\n- Playbook discoverability: Run \"up\" and a list of available playbooks is displayed.\n  System, user, and project level playbooks are found via a search path.\n- Argument handling: Easy CLI argument handling, playbooks can create project scaffolding,\n  deploy and configure Apache modules, etc...\n- Shell scripts, but declarative rather than command-based.  Built in arg parsing,\n  templating, and \"handlers\" called when changes are made (as in Ansible).\n\n# Use-cases\n\n- Streamline your workflows\n- Automate repetitive tasks\n- Configure projects\n- Install and deploy software or services\n- Manage workstation or server installation and configuration\n- Project templating.\n- IT Automation.\n\nCommand-line argument processing enables playbook specialization at run-time.\nIn \"examples/new-uplaybook\" is a sample which creates a skeleton playbook by\nrunning the command: `up new-uplaybook my-example-playbook`.\n\nuPlaybook takes ideas from Ansible and Cookiecutter, trading their YAML syntax for\nPython. Playbooks are like shell scripts, oriented specifically towards setting up\nsystems or environments.  Playbooks include CLI argument handling, which traditionally\nhas been tricky to do well in shell scripts.\n\nDespite its simplicity, uPlaybook doesn't compromise on functionality.  The core\ntasks can be augmented by arbitrary Python code for ultimate power.\n\n## High-level Ideas\n\nuPlaybook delivers the following ideas:\n\n    - Python syntax\n    - First-class CLI argument handling.\n    - Declare the state you want to end up at.\n    - Tasks communicate whether they have changed something.\n    - Changed tasks can trigger a handler (if this config file changes, restart a service).\n    - Jinja2 templating of arguments and files delivered via fs.copy()\n    - Status output.\n\n## Installation\n\nInstallation can be done with:\n\n    pipx install uplaybook\n\nor:\n\n    pip install uplaybook\n\nOr to run from the repository:\n\n    git clone git@github.com:linsomniac/uplaybook.git\n    pip install poetry\n    poetry shell\n\nThe above starts a shell with uPlaybook available to run.\n\n## Getting Started\n\nYou can create a skeleton playbook, in the uplaybook git checkout, with:\n\n    up new-uplaybook my-test-playbook\n\nWhich creates a \"my-test-playbook\" directory with the skeleton playbook file\n\"playbook\".  The playbook used to create this skeleton is in\n`.uplaybooks/new-uplaybook/playbook`\n\n## Documentation\n\n[Full uPlaybook Documentation](https://linsomniac.github.io/uplaybook)\n\n## Simple Examples\n\nDeploy an Apache site:\n\n```python\nfrom uplaybook import fs, core, pyinfra\n\n#  Restart apache, but only if the site config or symlink to sites-enabled notify\n#  that it has changed\ndef restart_apache():\n    pyinfra.systemd.service(service=\"apache2\", restarted=True)\n\npyinfra.apt.packages(packages=[\"apache2\"])\nfs.cp(src=\"my-site.conf.j2\", path=\"/etc/apache2/sites-available/my-site.conf\").notify(restart_apache)\nfs.ln(src=\"/etc/apache2/sites-available/my-site.conf\", path=\"/etc/apache2/sites-enabled/\", symbolic=True).notify(restart_apache)\n```\n\nEnable an Apache module:\n\n```python\nfrom uplaybook import fs, core, pyinfra\n\ncore.playbook_args(options=[\n        core.Argument(name=\"module_name\", description=\"Name of module\"),\n        core.Argument(name=\"remove\", type=\"bool\", default=False,\n                      description=\"Remove the module rather than install it\"),\n        ])\n\ndef restart_and_enable_apache():\n    pyinfra.systemd.service(service=\"apache2\", restarted=True, enabled=True)\n\nif not ARGS.remove:\n    pyinfra.apt.packages(packages=[\"apache2\", f\"libapache2-mod-{ARGS.module_name}\"]\n          ).notify(restart_and_enable_apache)\n    fs.ln(src=\"/etc/apache2/mods-available/{{ ARGS.module_name }}.load\",\n          path=\"/etc/apache2/mods-enabled/{{ ARGS.module_name }}.load\",\n          symbolic=True).notify(restart_and_enable_apache)\nelse:\n    fs.rm(path=\"/etc/apache2/mods-enabled/{{ ARGS.module_name }}.load\",\n          ).notify(restart_and_enable_apache)\n    pyinfra.apt.packages(packages=[f\"libapache2-mod-{ARGS.module_name}\"], present=False,\n          ).notify(restart_and_enable_apache)\n```\n\n## Example Playbooks\n\n- [New uPlaybook](examples/new-uplaybook/playbook)\n\n## State\n\nThis is Beta software: The core is done but as it gets real world use things\nmay change in incompatible ways before final release.\n\nCurrently (Late Nov 2023) I'm working on:\n\n- Documentation.\n- Test driving uPlaybook to find rough edges.\n- Adding more Tasks.\n\nIf you look at it, your feedback would be appreciated.\n\n## Compared to...\n\n### Ansible\n\nThe primary benefits uPlaybook has is using a Python syntax and ease of running against\nthe local system.  Ansible has a much richer task ecosystem, and the ability to run\nplays on many remote systems at once.\n\n### Cookiecutter\n\nuPlaybook has a richer configuration syntax and more flexibility since it is based on the\nPython language.  Cookiecutter has more available cookiecutters, and the limited syntax\ndoes provide more safety against what third-party cookiecutters can do when you run them.\n\n### Shell\n\nuPlaybook has first class CLI argument handling, is declarative and the ability (not yet\nimplemented) to ask what changes the playbook will make, and tries to bring some shell\nfirst class actions into Python.  Shell is better at blindly running commands and\npipelines.\n\n## Features\n\n### Uplifting of variables into templating\n\n```python\nversion = \"3.2\"\nfs.mkdir(path=\"myprogram-{{version}}\")  # Makes directory \"myprogram-3.2\"\nfs.template(path=\"foo\", src=\"foo.j2\")   # \"foo.j2\" can access \"{{version}}\"\n```\n\n### Jinja2 templating of most arguments\n\n```python\npath=\"/etc/rsyslog.d/49-haproxy.conf\"\nfs.template(src=\"{{ path | basename }}.j2\")  # src becomes \"49-haproxy.conf.j2\"\n```\n\n### Tasks only take effect if they change the system\n\n```python\nfs.mkfile(path=\"foo\")   #  Only takes action if \"foo\" does not exist\nfs.mkfile(path=\"foo\")   #  Never takes action because \"foo\" would be created above\n```\n\n### Taks can trigger handlers if they change something\n\n```python\ndef restart_apache()\n    core.service(name=\"apache2\", state=\"restarted\")\n\nfs.template(path=\"/etc/apache2/sites-enabled/test.conf\", src=\"test.conf.j2\").notify(restart_apache)\nfs.template(path=\"/etc/apache2/sites-enabled/other_site.conf\", src=\"other_site.conf.j2\").notify(restart_apache)\n```\n\nThe above will retart apache if either of the config files get created or updated.\nIt will only restart apache once even if both files get updated.\n\n### Running a playbook displays status of tasks\n\n```python\ncore.run(\"rm -rf testdir\")\nfs.builder(state=\"directory\", path=\"testdir\", mode=\"a=rX,u+w\")\nfs.cd(\"testdir\")\nfs.builder(state=\"exists\", path=\"testfile\", mode=\"a=rX\")\nfs.chown(path=\"testfile\", group=\"docker\")\nfs.builder(state=\"exists\", path=\"testfile\", mode=\"a=rX\", group=\"sean\")\n```\n\nProduces the following status output:\n\n```\n=\u003e run(command=rm -rf testdir, shell=True, ignore_failure=False, change=True)\n==\u003e mkdir(path=testdir, mode=a=rX,u+w, parents=True)\n==# chmod(path=testdir, mode=493)\n=\u003e builder(path=testdir, mode=a=rX,u+w, state=directory)\n=# cd(path=testdir)\n==\u003e mkfile(path=testfile, mode=a=rX)\n==# chmod(path=testfile, mode=292)\n=\u003e builder(path=testfile, mode=a=rX, state=exists)\n=\u003e chown(path=testfile, group=docker) (group)\n===# chmod(path=testfile, mode=292)\n==# mkfile(path=testfile, mode=a=rX)\n==# chmod(path=testfile, mode=292)\n==\u003e chown(path=testfile, group=sean) (group)\n=# builder(path=testfile, mode=a=rX, group=sean, state=exists)\n\n*** RECAP:  total=14 changed=7 failure=0\n```\n\nWhere \"\u003e\" in the status means a change occurred, \"#\" means no change happened, and additional \"=\"\nindentations indicate a task triggered by a parent task.  The parent task is the dedented task after\nthe extra indents (the \"builder\") tasks above.\n\n## License\n\nCreative Commons Zero v1.0 Universal\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinsomniac%2Fuplaybook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flinsomniac%2Fuplaybook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinsomniac%2Fuplaybook/lists"}