# drive


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

[`Deploy`](https://vedicreader.github.io/pullup/deploy.html#deploy) runs
the whole deploy through one script. When something goes wrong halfway,
it helps to run the parts separately and see what each one printed.
[`Drive`](https://vedicreader.github.io/pullup/drive.html#drive) is a
pipeline whose steps are six short Python snippets, each calling one
library function: write the Dockerfile, write the compose stack, open
the tunnel, point an extra hostname at it, provision the server, and
push the secrets to GitHub. You can read each snippet, run it, fix the
cause of a failure and run it again.

[`Drive`](https://vedicreader.github.io/pullup/drive.html#drive) saves
its plan to `drive.json`, apart from the
[`Deploy`](https://vedicreader.github.io/pullup/deploy.html#deploy)
plan, so neither overwrites the other. `DriveError` is the same class as
[`PipelineError`](https://vedicreader.github.io/pullup/pipeline.html#pipelineerror).

``` python
DRIVE_FILE, DriveError is PipelineError
```

    ('drive.json', True)

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/drive.py#L38"
target="_blank" style="float:right; font-size:smaller">source</a>

### Part

``` python
def Part(
    id:str, package:str, label:str, doc:str, code:str, needs:list=<factory>, writes:list=<factory>,
    produces:list=<factory>
)->None:
```

*One deploy operation as a code template that can run as a step on its
own.*

A part is one deploy operation written as a code template.
[`Part`](https://vedicreader.github.io/pullup/drive.html#part) holds the
package the code calls, a label and description for display, the `code`
with `{placeholders}` for the project’s values, the keys the code reads
(`needs`), the keys it stores (`writes`), and the files it creates
(`produces`).

`PARTS` holds the six parts in the order they run. `image` writes the
Dockerfile that dockeasy detects. `stack` wraps that image in a compose
file with Caddy and cloudflared. `tunnel` creates or reuses the
Cloudflare tunnel and stores its token. `dns` points `SECOND_HOST` at
the same tunnel, and stops with a message when that key is unset.
`server` provisions the Hetzner server if it is missing, copies the
project over and starts the stack. `secrets` pushes the declared keys to
GitHub so that CI can run the same deploy.

``` python
for p in PARTS: print(f'{p.id:8} {p.package:9} {p.label:18} needs={p.needs} writes={p.writes}')
```

    image    dockeasy  Dockerfile         needs=[] writes=[]
    stack    vpseasy   compose stack      needs=[] writes=[]
    tunnel   cfeasy    Cloudflare tunnel  needs=['CLOUDFLARE_API_TOKEN'] writes=['CF_TUNNEL_TOKEN']
    dns      cfeasy    extra hostname     needs=['CLOUDFLARE_API_TOKEN'] writes=[]
    server   vpseasy   server and rsync   needs=['HCLOUD_TOKEN'] writes=['HETZNER_IP']
    secrets  gheasy    push secrets       needs=['GITHUB_TOKEN'] writes=[]

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/drive.py#L113"
target="_blank" style="float:right; font-size:smaller">source</a>

### context

``` python
def context(
    root, env:NoneType=None
):
```

*The project values every part’s template is filled with: names, host,
tunnel, port and file lists.*

[`context`](https://vedicreader.github.io/pullup/drive.html#context)
works out, once, every value the templates use. The app name comes from
gheasy’s config, then the name in `pyproject.toml`, then the folder
name. `DOMAIN` from the store is split into a subdomain and a base
domain when it has more than one dot, and the tunnel name follows the
same rule as in `deploy.py`. `keys` lists the keys in gheasy’s
`env_schema`, or just `DOMAIN` when there is none.

``` python
ctx = context(root, env)
{k: ctx[k] for k in ('app', 'host', 'domain', 'subdomain', 'tunnel', 'port', 'keys')}
```

    {'app': 'demo',
     'host': 'app.example.com',
     'domain': 'example.com',
     'subdomain': 'app',
     'tunnel': 'app_example.com',
     'port': '8080',
     'keys': ['DOMAIN']}

[`_step`](https://vedicreader.github.io/pullup/drive.html#_step) fills a
part’s template from the context and wraps it in a
[`Step`](https://vedicreader.github.io/pullup/project.html#step) whose
`argv` runs the code with `python -c`. The filled-in code is also kept
in `meta['code']`, so a user interface can show exactly what will run.
This is the tunnel part for the example project:

``` python
s = _step(PARTS[2], 'python', ctx)
print(s.label, '|', s.cmd, '|', s.needs)
print(s.meta['code'])
```

    cfeasy · Cloudflare tunnel | python -c … cfeasy | ['CLOUDFLARE_API_TOKEN']
    from cfeasy import CF
    from dockeasy import env_get, env_set
    cf = CF(token=env_get('CLOUDFLARE_API_TOKEN'))
    tid, tok = cf.setup_tunnel('example.com', 'app' or None, tunnel_name='app_example.com')
    env_set('CF_TUNNEL_TOKEN', tok)
    print('tunnel', tid, 'for', 'app.example.com')

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/drive.py#L153"
target="_blank" style="float:right; font-size:smaller">source</a>

### parts

``` python
def parts(
    root, env:NoneType=None, python:str=''
):
```

*Every part as a runnable
[`Step`](https://vedicreader.github.io/pullup/project.html#step), its
template filled with this project’s values.*

[`parts`](https://vedicreader.github.io/pullup/drive.html#parts) does
the same for all six parts, with one context and the interpreter you
choose (this process’s Python by default). This is the last one, which
pushes the secrets:

``` python
steps = parts(root, env)
test_eq(steps[0].argv[0], sys.executable)
print(steps[-1].meta['code'].replace(str(root), '/proj'))
```

    from gheasy import gh_push_env
    from dockeasy import env_get
    keys = ['DOMAIN']
    vals = {k: v for k in keys if (v := env_get(k, default=''))}
    print('pushing', len(vals), 'of', len(keys), 'declared keys')
    gh_push_env(vals, path='/proj')

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/pullup/blob/main/pullup/drive.py#L159"
target="_blank" style="float:right; font-size:smaller">source</a>

### Drive

``` python
def Drive(
    root, python:NoneType=None, env:NoneType=None, dir:str='.pullup'
):
```

*The deploy as six separate library calls, each a step you can run on
its own.*

[`Drive`](https://vedicreader.github.io/pullup/drive.html#drive) uses
these rendered parts as its default plan. Until you save a plan,
`refresh` (which `state` calls) renders the parts again whenever the
values in the store change, so changing `DOMAIN` changes the snippets.
Once a plan is saved it is used as written. `state` also returns the
context and the four packages involved.

``` python
d = Drive(root, env=env)
st = d.state()
[r['id'] for r in st['steps']], st['packages'], st['needs'], st['configured']
```

    (['image', 'stack', 'tunnel', 'dns', 'server', 'secrets'],
     ['cfeasy', 'dockeasy', 'gheasy', 'vpseasy'],
     ['CLOUDFLARE_API_TOKEN', 'HCLOUD_TOKEN', 'GITHUB_TOKEN'],
     False)
