More careful variable extraction, without regexp

This commit is contained in:
Ivan Gromov 2022-09-16 02:09:44 +05:00
parent 547711d83e
commit aa0fff068e
5 changed files with 84 additions and 49 deletions

View file

@ -1,9 +1,9 @@
import configparser import configparser
import json import json
import os import os
import re
import sys import sys
from os.path import join, dirname, expanduser from os.path import join, dirname, expanduser
from functools import reduce
import ansible_runner import ansible_runner
import yaml import yaml
@ -55,12 +55,27 @@ task_program = ''
class Status: class Status:
RUNNING = 'running' RUNNING = 'running'
ERROR = 'error' ERROR = 'failed'
CANCELLED = 'cancelled' TIMEOUT = 'timeout'
DONE = 'done' CANCELLED = 'canceled'
DONE = 'successful'
NEW = None NEW = None
def by_path(data: dict, path: str):
def get(obj, attr):
if type(obj) is dict:
return obj.get(attr, None)
elif type(obj) is list:
try:
return obj[int(attr)]
except ValueError:
return None
else:
return None
return reduce(get, path.split('.'), data)
class Playbook: class Playbook:
def __init__(self): def __init__(self):
@ -69,28 +84,40 @@ class Playbook:
self.events = [] self.events = []
self.config_vars = {} self.config_vars = {}
self._runner = None self._runner = None
def parse(self, event: dict):
data = {}
if by_path(event, 'event_data.task') == 'Set subjectAltName as a fact':
ansible_ssh_host = by_path(event, 'event_data.res.ansible_facts.IP_subject_alt_name')
if ansible_ssh_host:
data['ansible_ssh_host'] = ansible_ssh_host
if by_path(event, 'event_data.play') == 'Configure the server and install required software':
local_service_ip = by_path(event, 'event_data.res.ansible_facts.ansible_lo.ipv4_secondaries.0.address')
ipv6 = by_path(event, 'event_data.res.ansible_facts.ansible_lo.ipv6.0.address')
p12_export_password = by_path(event, 'event_data.res.ansible_facts.p12_export_password')
if local_service_ip:
data['local_service_ip'] = local_service_ip
if ipv6:
data['ipv6'] = ipv6
if p12_export_password:
data['p12_export_password'] = p12_export_password
if by_path(event, 'event_data.play') == 'Provision the server':
host_name = by_path(event, 'event_data.res.add_host.host_name')
if host_name:
data['host_name'] = host_name
return data if data else None
def event_handler(self, data: dict) -> None: def event_handler(self, data: dict) -> None:
if data['event'] == 'runner_on_ok': if self.parse(data):
# Looking for '-passout pass:"{{ CA_password }}"' self.config_vars.update(self.parse(data))
if 'Build the CA pair' in data['event_data']['task']:
m = re.match(r'-passout pass:\"(?P<password>.*)\"', data['event_data']['cmd'])
if m:
self.config_vars['CA_password'] = m.group('password')
# Looking for '-passout pass:"{{ p12_export_password }}"' self.events.append(data)
if "Build the client's p12" in data['event_data']['task']:
m = re.match(r'-passout pass:\"(?P<password>.*)\"', data['event_data']['cmd'])
if m:
self.config_vars['p12_export_password'] = m.group('password')
# Looking for 'DNS = {{ wireguard_dns_servers }}' def status_handler(self, status_data: dict, *args, **kwargs) -> None:
if "Generate QR codes" in data['event_data']['task']: self.status = status_data.get('status')
self.config_vars['host'] = data['event_data']['host']
m = re.match(r'DNS = (?P<dns>.*)\n\n', data['event_data']['cmd'])
if m:
self.config_vars['local_service_ip'] = m.group('dns')
self.events.append(data)
def cancel_handler(self) -> bool: def cancel_handler(self) -> bool:
if self.want_cancel: if self.want_cancel:
@ -103,11 +130,14 @@ class Playbook:
def run(self, extra_vars: dict) -> None: def run(self, extra_vars: dict) -> None:
self.want_cancel = False self.want_cancel = False
self.status = Status.RUNNING self.status = Status.RUNNING
_, runner = ansible_runner.run_async(private_data_dir='.', _, runner = ansible_runner.run_async(
playbook='main.yml', private_data_dir='.',
extravars=extra_vars, playbook='main.yml',
cancel_callback=self.cancel_handler, extravars=extra_vars,
event_handler=self.event_handler) status_handler=self.status_handler,
cancel_callback=self.cancel_handler,
event_handler=self.event_handler
)
self._runner = runner self._runner = runner
@ -282,12 +312,14 @@ async def check_vultr_config(request):
try: try:
open(os.environ['VULTR_API_CONFIG'], 'r').read() open(os.environ['VULTR_API_CONFIG'], 'r').read()
response['has_secret'] = True response['has_secret'] = True
response['saved_to'] = os.environ.get('VULTR_API_CONFIG')
except IOError: except IOError:
pass pass
try: try:
default_path = expanduser(join('~', '.vultr.ini')) default_path = expanduser(join('~', '.vultr.ini'))
open(default_path, 'r').read() open(default_path, 'r').read()
response['has_secret'] = True response['has_secret'] = True
response['saved_to'] = default_path
except IOError: except IOError:
pass pass
return web.json_response(response) return web.json_response(response)

View file

@ -71,10 +71,10 @@
<h1 class="mb-5 text-center" v-if="step === 'status-running'"> <h1 class="mb-5 text-center" v-if="step === 'status-running'">
<span class="spin">🙂</span> Please be patient <span class="spin">🙂</span> Please be patient
</h1> </h1>
<h1 class="mb-5 text-center" v-if="step === 'status-error'"> <h1 class="mb-5 text-center" v-if="step === 'status-failed'">
😢 Set up failed 😢 Set up failed
</h1> </h1>
<h1 class="mb-5 text-center" v-if="step === 'status-done'"> <h1 class="mb-5 text-center" v-if="step === 'status-successful'">
🥳 Congratulations, your Algo server is running! 🥳 Congratulations, your Algo server is running!
</h1> </h1>
<h1 class="mb-5 text-center" v-if="step === 'status-exit'"> <h1 class="mb-5 text-center" v-if="step === 'status-exit'">
@ -82,7 +82,7 @@
</h1> </h1>
</h1> </h1>
<transition name="fade"> <transition name="fade">
<div class="row" v-if="step == 'setup'"> <div class="row" v-if="step === 'setup'">
<user-config class="col-md-4 order-md-2 mb-4"></user-config> <user-config class="col-md-4 order-md-2 mb-4"></user-config>
<vpn-setup class="col-md-8 order-md-1" <vpn-setup class="col-md-8 order-md-1"
v-bind:extra_args="extra_args" v-bind:extra_args="extra_args"
@ -90,35 +90,35 @@
</div> </div>
</transition> </transition>
<transition name="fade"> <transition name="fade">
<provider-setup v-if="step == 'provider'" <provider-setup v-if="step === 'provider'"
v-bind:extra_args="extra_args" v-bind:extra_args="extra_args"
v-on:submit="step = 'command'"> v-on:submit="step = 'command'">
</provider-setup> </provider-setup>
</transition> </transition>
<transition name="fade"> <transition name="fade">
<command-preview v-if="step == 'command'" <command-preview v-if="step === 'command'"
v-bind:extra_args="extra_args" v-bind:extra_args="extra_args"
v-on:submit="start(); step = 'status-running';"> v-on:submit="start(); step = 'status-running';">
</command-preview> </command-preview>
</transition> </transition>
<transition name="fade"> <transition name="fade">
<status-running v-if="step == 'status-running'" <status-running v-if="step === 'status-running'"
v-on:submit="stop(); step = 'setup';" v-on:submit="stop(); step = 'setup';"
v-on:done="step = 'status-done'" v-on:successful="step = 'status-successful'"
v-on:error="step = 'status-error'" v-on:error="step = 'status-failed'"
v-on:cancelled="step = 'setup'"> v-on:cancelled="step = 'setup'">
</status-running> </status-running>
</transition> </transition>
<transition name="fade"> <transition name="fade">
<section v-if="step == 'status-error'" class="text-center"> <section v-if="step === 'status-failed'" class="text-center">
<p>Now its time to inspect console output</p> <p>Now its time to inspect console output</p>
<p>Restart console process to try again</p> <p>Restart console process to try again</p>
</section> </section>
</transition> </transition>
<transition name="fade"> <transition name="fade">
<status-done v-if="step == 'status-done'" <status-successful v-if="step === 'status-successful'"
v-on:submit="exit(); step = 'status-exit'" > v-on:submit="exit(); step = 'status-exit'" >
</status-done> </status-successful>
</transition> </transition>
</div> </div>
@ -144,13 +144,13 @@
'provider-setup': window.httpVueLoader('/static/provider-setup.vue'), 'provider-setup': window.httpVueLoader('/static/provider-setup.vue'),
'command-preview': window.httpVueLoader('/static/command-preview.vue'), 'command-preview': window.httpVueLoader('/static/command-preview.vue'),
'status-running': window.httpVueLoader('/static/status-running.vue'), 'status-running': window.httpVueLoader('/static/status-running.vue'),
'status-done': window.httpVueLoader('/static/status-done.vue') 'status-successful': window.httpVueLoader('/static/status-done.vue')
}, },
created() { created() {
fetch("/playbook") fetch("/playbook")
.then(r => r.json()) .then(r => r.json())
.catch(() => { .catch(() => {
this.step = 'status-error'; this.step = 'status-failed';
}) })
.then(data => { .then(data => {
if (data.status && data.status !== 'cancelled'){ if (data.status && data.status !== 'cancelled'){

View file

@ -85,6 +85,7 @@ module.exports = {
.then(response => { .then(response => {
if (response.has_secret) { if (response.has_secret) {
this.ui_token_from_env = true; this.ui_token_from_env = true;
this.vultr_config = response.saved_to;
this.load_regions(); this.load_regions();
} else if (response.error) { } else if (response.error) {
this.ui_config_error = response.error; this.ui_config_error = response.error;
@ -138,10 +139,8 @@ module.exports = {
}, },
submit() { submit() {
let submit_value = { let submit_value = {
region: this.region region: this.region,
} vultr_config: this.vultr_config
if (!this.ui_token_from_env) {
submit_value['vultr_config'] = this.vultr_config;
} }
this.$emit("submit", submit_value); this.$emit("submit", submit_value);
}, },
@ -150,4 +149,4 @@ module.exports = {
"region-select": window.httpVueLoader("/static/region-select.vue"), "region-select": window.httpVueLoader("/static/region-select.vue"),
} }
}; };
</script> </script>

View file

@ -7,7 +7,7 @@
<p v-if="result.local_service_ip">Local DNS resolver {{result.local_service_ip}}</p> <p v-if="result.local_service_ip">Local DNS resolver {{result.local_service_ip}}</p>
<p v-if="result.p12_export_password">The p12 and SSH keys password for new users is <code>{{result.p12_export_password}}</code></p> <p v-if="result.p12_export_password">The p12 and SSH keys password for new users is <code>{{result.p12_export_password}}</code></p>
<p v-if="result.CA_password">The CA key password is <code>{{result.CA_password}}</code></p> <p v-if="result.CA_password">The CA key password is <code>{{result.CA_password}}</code></p>
<p v-if="result.ssh_access">Shell access: <code>ssh -F configs/{{result.ansible_ssh_host}}/ssh_config {{config.server_name}}</code></p> <p v-if="result.ansible_ssh_host">Shell access: <code>ssh -F configs/{{result.ansible_ssh_host}}/ssh_config {{config.server_name}}</code></p>
<p>Read more on how to set up clients at the <a href="https://github.com/trailofbits/algo" target="_blank" rel="noopener noopener">Algo home page</a></p> <p>Read more on how to set up clients at the <a href="https://github.com/trailofbits/algo" target="_blank" rel="noopener noopener">Algo home page</a></p>
</section> </section>
<section> <section>

View file

@ -8,7 +8,7 @@
</p> </p>
<p>Dont close terminal!</p> <p>Dont close terminal!</p>
<transition-group name="console" tag="div"> <transition-group name="console" tag="div">
<code class="console-item" v-for="(event, i) in last_n_events" v-bind:key="event.counter">[{{ event.counter }}]: {{ event.stdout }}</code> <code class="console-item" v-for="(event, i) in last_n_events" v-bind:key="event.counter">{{ event.stdout }}</code>
</transition-group> </transition-group>
</section> </section>
</template> </template>
@ -43,8 +43,12 @@ module.exports = {
}) })
.then(data => { .then(data => {
this.events = data.events; this.events = data.events;
if (data.status && data.status === 'done') { if (data.status && data.status === 'successful') {
this.$emit('done'); this.$emit('successful');
throw new Error();
}
if (data.status && data.status === 'failed') {
this.$emit('error');
throw new Error(); throw new Error();
} }
if (!data.status || data.status === 'cancelled') { if (!data.status || data.status === 'cancelled') {