Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nayaverdier committed Oct 24, 2021
0 parents commit 5c3c8d4
Show file tree
Hide file tree
Showing 31 changed files with 1,513 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
max-line-length = 120
ignore = E203, W503
62 changes: 62 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: CI

on: [push, pull_request]

jobs:
test:
name: Test Python ${{ matrix.python-version }}
strategy:
fail-fast: false
matrix:
python-version: ['3.7', '3.8', '3.9']
env:
PYTHON: ${{ matrix.python-version }}

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: set up python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: install
run: make install-dev
- name: Lint
run: make check
- name: test
run: make test
- name: coverage
run: make coverage

- uses: codecov/codecov-action@v1
with:
file: coverage.xml
env_vars: PYTHON

deploy:
name: Build and Deploy
needs: test
if: "success() && startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: set up python
uses: actions/setup-python@v2
with:
python-version: 3.7

- name: install
run: make install-dev

- name: build
run: make build

- name: upload to pypi
run: twine upload dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.pypi_token }}
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Distribution / packaging
build/
dist/
pip-wheel-metadata/
wheels/
share/python-wheels/
*.egg-info/
*.egg
MANIFEST

# Unit test / coverage reports
.coverage
.coverage.*
coverage.xml
.pytest_cache/

# Environments
.venv

# mkdocs documentation
/site

# mypy
.mypy_cache/
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## 0.1.0 2021-07-29

- Initial release
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Naya Verdier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include README.md
include CHANGELOG.md
include LICENSE
include instater/VERSION
55 changes: 55 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.DEFAULT_GOAL := all

black = black instater tests
flake8 = flake8 instater tests
isort = isort instater tests
mypy = mypy instater
install-pip = python -m pip install -U setuptools pip wheel
test = pytest --cov=instater --cov-report term-missing tests/

.PHONY: install
install:
$(install-pip)
pip install -e .

.PHONY: install-dev
install-dev:
$(install-pip)
pip install -e ".[dev]"

.PHONY: format
format:
$(isort)
$(black)

.PHONY: check
check:
$(isort) --check-only --df
$(black) --check --diff
$(flake8)
$(mypy)

.PHONY: test
test:
$(test)

.PHONY: coverage
coverage:
coverage xml

.PHONY: build
build:
python setup.py sdist bdist_wheel
twine check dist/*

.PHONY: clean
clean:
rm -rf `find . -name __pycache__`
rm -rf .pytest_cache
rm -rf .mypy_cache
rm -rf build
rm -rf dist
rm -rf *.egg-info
rm -f .coverage
rm -f .coverage.*
rm -f coverage.xml
123 changes: 123 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# instater

An easy solution for system/dotfile configuration

Loosely based off of Ansible for the task and file organization

## Installation

```bash
pip3 install instater
```

## Usage

See the File Structure Example below to set up variables, files, and tasks.

Once a `setup.yml` file is created, it can be run using

```bash
instater

# or:
instater --setup-file setup.yml
```

To see what changed will be made, but not actually make then, use `--dry-run`:

```bash
instater --dry-run
```

For a complete example, see [dotfiles](https://github.com/nayaverdier/dotfiles)

### File Structure Example

First, create a `setup.yml` file:

```yaml
# Lots of ways to prompt for data at the beginning of execution
vars_prompt:
- name: my_var
- name: custom_prompt
prompt: Enter something here
- name: private_var
private: true
- name: private_confirm_var
private: true
confirm: true
- name: allow_empty_var
allow_empty: true

# variables that can be used within tasks/files can be populated
# from a static file, in this case vars/common.yml
vars_files:
- vars/common.yml
# variables can be used within the file names
- "vars/{{ vars_file }}.yml"

# All of the tasks to perform are enumerated
tasks:
- name: Copy file
# {{ username }} is replaced with the variable `username`
copy:
content: "The contents of a new file in here"
dest: "/home/{{ username }}/Downloads/file1"
mode: "600"
# if desired, the output of this task can be registered to use as
# a condition for subsequent tasks
register: file1_copy
- name: Run a command if file1 written
command: "touch /home/{{ username }}/testfile"
when: file1_copy.changed
```
Then, create a `vars/` directory and `common.yml` within:

```yaml
my_test: 1
some_var: "{{ my_test + 2 }}"
vars_file: "second"
username: something
```

And `vars/second.yml` (since `common.yml` set `vars_file` to `second`):

```yaml
from_second_yml: data in here
```

Now in all of the tasks, `my_test`, `username`, `from_second_yml`, etc will be
present and accessible.

### Tasks

All currently defined tasks are listed below

#### aur

#### command

#### copy

#### debug

#### directory

#### file

#### get_url (alias of `copy`)

#### git

#### group

#### include

#### pacman

#### service

#### template (alias of `copy`)

#### user
1 change: 1 addition & 0 deletions instater/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
18 changes: 18 additions & 0 deletions instater/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from importlib import resources

from rich.traceback import install

from . import tasks
from .exceptions import InstaterError
from .main import run_tasks

install()

VERSION = resources.read_text("instater", "VERSION").strip()

__all__ = [
"InstaterError",
"VERSION",
"run_tasks",
"tasks",
]
57 changes: 57 additions & 0 deletions instater/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import json
from argparse import ArgumentParser

from rich.console import Console

from instater import InstaterError, run_tasks


def _parse_variables(vars: str) -> dict:
if vars is None:
return {}

try:
parsed = json.loads(vars)
if not isinstance(parsed, dict):
raise InstaterError(f"JSON variables from --vars must be a dictionary, found '{type(parsed)}'")
return parsed
except json.JSONDecodeError:
pass

split = vars.replace(";", " ").split(" ")
parsed = {}
for item in split:
item = item.strip()
if not item:
continue

if "=" not in item:
raise InstaterError(f"Invalid argument to --vars: '{item}' (missing '=')")
key, value = item.split("=")
parsed[key] = value

return parsed


def main():
parser = ArgumentParser(description="An easy solution for system/dotfile configuration")
parser.add_argument("--setup-file", default="setup.yml", help="The setup file to execute")
parser.add_argument("--tags", nargs="*", help="Run only a subset of tasks by their tag")
parser.add_argument("--vars", help="Variables to override prompts or variable files")
parser.add_argument(
"--dry-run",
action="store_true",
help="Display operations that would be performed without actually running them",
)

args = parser.parse_args()

tags = args.tags

try:
variables = _parse_variables(args.vars)
run_tasks(args.setup_file, variables, tags, args.dry_run)
except InstaterError as e:
console = Console()
console.print(e, style="red")
console.print("Exiting...", style="red bold")
Loading

0 comments on commit 5c3c8d4

Please sign in to comment.