1
1
import structlog
2
2
import os
3
3
import sys
4
+ import sh
4
5
import json
5
6
import subprocess
6
7
import importlib .util
@@ -77,6 +78,9 @@ def pyval(k, fn, dflt=None):
77
78
return v
78
79
79
80
81
+ env_key_on_missing = '__env_key_on_missing__'
82
+
83
+
80
84
def env (key , dflt = None ):
81
85
v = os .environ .get (key , nil )
82
86
if v == nil :
@@ -87,12 +91,33 @@ def env(key, dflt=None):
87
91
if str (v ).startswith ('pass:' ):
88
92
x = _secrets .get (v , nil )
89
93
if x == nil :
90
- x = run (['pass' , 'show' , v [5 :]], capture_output = True , text = True ) or dflt
94
+ try :
95
+ x = pass_ (key ).show (v [5 :], _err = [1 , 2 ]).strip ()
96
+ except Exception as _ :
97
+ # special case: allows to calc and set the value later:
98
+ if dflt == env_key_on_missing :
99
+ return v
91
100
_secrets [v ] = x
92
101
v = x
93
102
return v
94
103
95
104
105
+ def add_to_pass (key , val ):
106
+ log .warn ('Adding key to pass' , n = key [5 :])
107
+ pass_ (key ).insert ('-m' , key [5 :], _in = val )
108
+ if not need_env (key ) == val :
109
+ die ('Failed to update pass' , key = key [5 :], value = val )
110
+
111
+
112
+ def pass_ (key = '' ):
113
+ p = getattr (sh , 'pass' , None )
114
+ h = 'Install pass: https://www.passwordstore.org/'
115
+ h += '- or supply a wrapper, supporting show and insert [-m] methods, e.g. for reading/writing files'
116
+ if p is None :
117
+ die ('pass utility not found' , hint = h , required_for = key )
118
+ return p
119
+
120
+
96
121
def dt (_ , __ , e ):
97
122
e ['timestamp' ] = f'{ now () - T0 :>4} '
98
123
return e
@@ -112,40 +137,51 @@ def dt(_, __, e):
112
137
log = structlog .get_logger ()
113
138
114
139
115
- def die (msg , ** kw ):
140
+ def die (msg , only_raise = False , ** kw ):
116
141
log .fatal (msg , ** kw )
142
+ if only_raise :
143
+ raise Exception (msg )
117
144
sys .exit (1 )
118
145
119
146
120
- def run (cmd , bg = False , ** kw ):
147
+ def run (cmd , bg = False , no_fail = False , ** kw ):
148
+ i = kw .get ('input' )
149
+ if i is not None :
150
+ kw ['input' ] = i .encode () if isinstance (i , str ) else i
121
151
if isinstance (cmd , str ):
122
152
cmd = cmd .split ()
123
153
pipe = kw .get ('pipe' , '' )
124
154
pipe = pipe if not len (pipe ) > 20 else f'{ pipe [:20 ]} ...'
125
- log .debug ('⚙️ Cmd' , cmd = cmd , pipe = pipe )
155
+ lw = {}
156
+ if pipe :
157
+ lw ['pipe' ] = pipe
158
+ log .debug (f'⚙️ { " " .join (cmd )} ' , ** lw )
126
159
if bg :
127
160
r = subprocess .Popen (cmd , start_new_session = True )
128
161
# r.communicate()
129
162
return r
130
163
131
164
r = subprocess .run (cmd , ** kw )
132
165
if r .returncode != 0 :
133
- die ('Command failed' , cmd = cmd , returncode = r .returncode )
166
+ die ('Command failed' , cmd = cmd , returncode = r .returncode , only_raise = no_fail )
134
167
return r .stdout .strip () if r .stdout else ''
135
168
136
169
137
- def need_env (k , dflt = None ):
170
+ def need_env (k , dflt = None , _home_repl = False ):
138
171
v = env (k , dflt )
139
172
if v is None :
140
173
die (f'Missing env var ${ k } ' )
174
+ if _home_repl :
175
+ for k in '~' , '$HOME' :
176
+ v = v .replace (k , os .environ ['HOME' ])
141
177
return v
142
178
143
179
144
180
def ssh (ip , port = None , cmd = None , input = None , send_env = None , capture_output = True , ** kw ):
145
181
cmd = cmd if cmd is not None else 'bash -s'
146
182
port = port if port is not None else int (env ('SSH_PORT' , 22 ))
147
183
c = f'ssh -p { port } -o StrictHostKeyChecking=accept-new -i' .split ()
148
- cmd = c + [need_env ('FN_SSH_KEY' ), f'root@{ ip } ' , cmd ]
184
+ cmd = c + [need_env ('FN_SSH_KEY' , _home_repl = True ), f'root@{ ip } ' , cmd ]
149
185
kw ['capture_output' ] = capture_output
150
186
for key in send_env or []:
151
187
cmd .insert (1 , f'SendEnv={ key } ' )
0 commit comments