Skip to content

Commit 9b40b99

Browse files
Add kubernetes connector.
1 parent b47eed0 commit 9b40b99

File tree

2 files changed

+233
-0
lines changed

2 files changed

+233
-0
lines changed

pyinfra/api/connectors/kubernetes.py

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import os
2+
3+
from tempfile import mkstemp
4+
5+
import click
6+
import six
7+
8+
from pyinfra import logger
9+
from pyinfra.api import QuoteString, StringCommand
10+
from pyinfra.api.exceptions import InventoryError
11+
from pyinfra.api.util import get_file_io, memoize
12+
13+
from .local import run_shell_command as run_local_shell_command
14+
from .util import make_unix_command, run_local_process, split_combined_output
15+
16+
17+
@memoize
18+
def show_warning():
19+
logger.warning('The @kubernetes connector is in beta!')
20+
21+
22+
def make_names_data(pod=None):
23+
if not pod:
24+
raise InventoryError('No pod provided!')
25+
26+
namespace = 'default'
27+
if '/' in pod:
28+
namespace, pod = pod.split('/', 2)
29+
30+
show_warning()
31+
32+
# Save the namespace and pod name as the hostname, @kubernetes group
33+
yield '@kubernetes/{0}/{1}'.format(namespace, pod), \
34+
{'namespace': namespace, 'pod': pod}, ['@kubernetes']
35+
36+
37+
def connect(state, host, for_fact=None):
38+
return True
39+
40+
41+
def disconnect(state, host):
42+
return True
43+
44+
45+
def run_shell_command(
46+
state, host, command,
47+
get_pty=False,
48+
timeout=None,
49+
stdin=None,
50+
success_exit_codes=None,
51+
print_output=False,
52+
print_input=False,
53+
return_combined_output=False,
54+
**command_kwargs
55+
):
56+
# Don't sudo/su, see docker connector.
57+
for key in ('sudo', 'su_user'):
58+
command_kwargs.pop(key, None)
59+
60+
command = make_unix_command(command, **command_kwargs)
61+
command = QuoteString(command)
62+
63+
if 'container' in host.host_data:
64+
container = ['-c', host.host_data['container']]
65+
else:
66+
container = []
67+
68+
kubectl_flags = '-it' if get_pty else '-i'
69+
kubectl_command = StringCommand(
70+
'kubectl', 'exec', kubectl_flags,
71+
'-n', host.host_data['namespace'],
72+
*container,
73+
host.host_data['pod'],
74+
'--', 'sh', '-c', command
75+
)
76+
77+
return run_local_shell_command(
78+
state, host, kubectl_command,
79+
timeout=timeout,
80+
stdin=stdin,
81+
success_exit_codes=success_exit_codes,
82+
print_output=print_output,
83+
print_input=print_input,
84+
return_combined_output=return_combined_output,
85+
)
86+
87+
88+
def put_file(
89+
state, host, filename_or_io, remote_filename,
90+
print_output=False, print_input=False,
91+
**kwargs # ignored (sudo/etc)
92+
):
93+
'''
94+
Upload a file/IO object to the target pod by copying it to a
95+
temporary location and then uploading it into the container using
96+
``kubectl cp``.
97+
'''
98+
99+
_, temp_filename = mkstemp()
100+
101+
try:
102+
# Load our file or IO object and write it to the temporary file
103+
with get_file_io(filename_or_io) as file_io:
104+
with open(temp_filename, 'wb') as temp_f:
105+
data = file_io.read()
106+
107+
if isinstance(data, six.text_type):
108+
data = data.encode()
109+
110+
temp_f.write(data)
111+
112+
if 'container' in host.host_data:
113+
container = ['-c', host.host_data['container']]
114+
else:
115+
container = []
116+
117+
kubectl_command = StringCommand(
118+
'kubectl', 'cp',
119+
temp_filename,
120+
'{0}/{1}:{2}'.format(host.host_data['namespace'],
121+
host.host_data['pod'],
122+
remote_filename),
123+
*container
124+
)
125+
126+
status, _, stderr = run_local_shell_command(
127+
state, host, kubectl_command,
128+
print_output=print_output,
129+
print_input=print_input,
130+
)
131+
132+
finally:
133+
os.remove(temp_filename)
134+
135+
if not status:
136+
raise IOError('\n'.join(stderr))
137+
138+
if print_output:
139+
click.echo('{0}file uploaded to container: {1}'.format(
140+
host.print_prefix, remote_filename,
141+
), err=True)
142+
143+
return status
144+
145+
146+
def get_file(
147+
state, host, remote_filename, filename_or_io,
148+
print_output=False, print_input=False,
149+
**kwargs # ignored (sudo/etc)
150+
):
151+
'''
152+
Download a file from the target pod by copying it to a temporary
153+
location and then reading that into our final file/IO object.
154+
'''
155+
156+
_, temp_filename = mkstemp()
157+
158+
try:
159+
if 'container' in host.host_data:
160+
container = ['-c', host.host_data['container']]
161+
else:
162+
container = []
163+
164+
kubectl_command = StringCommand(
165+
'kubectl', 'cp',
166+
'{0}/{1}:{2}'.format(host.host_data['namespace'],
167+
host.host_data['pod'],
168+
remote_filename),
169+
temp_filename,
170+
*container
171+
)
172+
173+
status, _, stderr = run_local_shell_command(
174+
state, host, kubectl_command,
175+
print_output=print_output,
176+
print_input=print_input,
177+
)
178+
179+
# Load the temporary file and write it to our file or IO object
180+
with open(temp_filename) as temp_f:
181+
with get_file_io(filename_or_io, 'wb') as file_io:
182+
data = temp_f.read()
183+
184+
if isinstance(data, six.text_type):
185+
data = data.encode()
186+
187+
file_io.write(data)
188+
finally:
189+
os.remove(temp_filename)
190+
191+
if not status:
192+
raise IOError('\n'.join(stderr))
193+
194+
if print_output:
195+
click.echo('{0}file downloaded from pod: {1}'.format(
196+
host.print_prefix, remote_filename,
197+
), err=True)
198+
199+
return status
200+
201+
202+
def get_pods(selector, namespace='default', all_namespaces=False, container=None):
203+
204+
command = ['kubectl', 'get', 'pods']
205+
if all_namespaces:
206+
command += ['-A']
207+
else:
208+
command += ['-n', namespace]
209+
command += ['-l', selector]
210+
command += [
211+
'--template',
212+
r'{{range .items}}'
213+
r'@kubernetes/{{.metadata.namespace}}/{{.metadata.name}}{{"\n"}}'
214+
r'{{end}}'
215+
]
216+
217+
print(command)
218+
return_code, combined_output = run_local_process(['"$@"', "-"] + command)
219+
stdout, stderr = split_combined_output(combined_output)
220+
print(stdout)
221+
222+
if return_code == 0:
223+
data = {}
224+
if container:
225+
data['container'] = container
226+
return list(map(lambda s: (s, data), stdout))
227+
else:
228+
raise InventoryError('kubectl failed (status {0}): {1}'.
229+
format(return_code, '\n'.join(stderr)))
230+
231+
232+
EXECUTION_CONNECTOR = True

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
'ansible = pyinfra.api.connectors.ansible',
105105
'chroot = pyinfra.api.connectors.chroot',
106106
'docker = pyinfra.api.connectors.docker',
107+
'kubernetes = pyinfra.api.connectors.kubernetes',
107108
'local = pyinfra.api.connectors.local',
108109
'mech = pyinfra.api.connectors.mech',
109110
'ssh = pyinfra.api.connectors.ssh',

0 commit comments

Comments
 (0)