Skip to content

Commit b89c030

Browse files
Add kubernetes connector.
1 parent b47eed0 commit b89c030

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

pyinfra/api/connectors/kubernetes.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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+
kubectl_command = ['kubectl', 'exec', '-i']
64+
if get_pty:
65+
kubectl_command += ['-t']
66+
kubectl_command += ['-n', host.host_data['namespace']]
67+
if 'container' in host.host_data:
68+
kubectl_command += ['-c', host.host_data['container']]
69+
kubectl_command += [host.host_data['pod'], '--', 'sh', '-c', command]
70+
kubectl_command = StringCommand(*kubectl_command)
71+
72+
return run_local_shell_command(
73+
state, host, kubectl_command,
74+
timeout=timeout,
75+
stdin=stdin,
76+
success_exit_codes=success_exit_codes,
77+
print_output=print_output,
78+
print_input=print_input,
79+
return_combined_output=return_combined_output,
80+
)
81+
82+
83+
def put_file(
84+
state, host, filename_or_io, remote_filename,
85+
print_output=False, print_input=False,
86+
**kwargs # ignored (sudo/etc)
87+
):
88+
'''
89+
Upload a file/IO object to the target pod by copying it to a
90+
temporary location and then uploading it into the container using
91+
``kubectl cp``.
92+
'''
93+
94+
_, temp_filename = mkstemp()
95+
96+
try:
97+
# Load our file or IO object and write it to the temporary file
98+
with get_file_io(filename_or_io) as file_io:
99+
with open(temp_filename, 'wb') as temp_f:
100+
data = file_io.read()
101+
102+
if isinstance(data, six.text_type):
103+
data = data.encode()
104+
105+
temp_f.write(data)
106+
107+
if 'container' in host.host_data:
108+
container = ['-c', host.host_data['container']]
109+
else:
110+
container = []
111+
112+
kubectl_command = StringCommand(
113+
'kubectl', 'cp',
114+
temp_filename,
115+
'{0}/{1}:{2}'.format(host.host_data['namespace'],
116+
host.host_data['pod'],
117+
remote_filename),
118+
*container
119+
)
120+
121+
status, _, stderr = run_local_shell_command(
122+
state, host, kubectl_command,
123+
print_output=print_output,
124+
print_input=print_input,
125+
)
126+
127+
finally:
128+
os.remove(temp_filename)
129+
130+
if not status:
131+
raise IOError('\n'.join(stderr))
132+
133+
if print_output:
134+
click.echo('{0}file uploaded to container: {1}'.format(
135+
host.print_prefix, remote_filename,
136+
), err=True)
137+
138+
return status
139+
140+
141+
def get_file(
142+
state, host, remote_filename, filename_or_io,
143+
print_output=False, print_input=False,
144+
**kwargs # ignored (sudo/etc)
145+
):
146+
'''
147+
Download a file from the target pod by copying it to a temporary
148+
location and then reading that into our final file/IO object.
149+
'''
150+
151+
_, temp_filename = mkstemp()
152+
153+
try:
154+
if 'container' in host.host_data:
155+
container = ['-c', host.host_data['container']]
156+
else:
157+
container = []
158+
159+
kubectl_command = StringCommand(
160+
'kubectl', 'cp',
161+
'{0}/{1}:{2}'.format(host.host_data['namespace'],
162+
host.host_data['pod'],
163+
remote_filename),
164+
temp_filename,
165+
*container
166+
)
167+
168+
status, _, stderr = run_local_shell_command(
169+
state, host, kubectl_command,
170+
print_output=print_output,
171+
print_input=print_input,
172+
)
173+
174+
# Load the temporary file and write it to our file or IO object
175+
with open(temp_filename) as temp_f:
176+
with get_file_io(filename_or_io, 'wb') as file_io:
177+
data = temp_f.read()
178+
179+
if isinstance(data, six.text_type):
180+
data = data.encode()
181+
182+
file_io.write(data)
183+
finally:
184+
os.remove(temp_filename)
185+
186+
if not status:
187+
raise IOError('\n'.join(stderr))
188+
189+
if print_output:
190+
click.echo('{0}file downloaded from pod: {1}'.format(
191+
host.print_prefix, remote_filename,
192+
), err=True)
193+
194+
return status
195+
196+
197+
def get_pods(selector, namespace='default', all_namespaces=False, container=None):
198+
199+
command = ['kubectl', 'get', 'pods']
200+
if all_namespaces:
201+
command += ['-A']
202+
else:
203+
command += ['-n', namespace]
204+
command += ['-l', selector]
205+
command += [
206+
'--template',
207+
r'{{range .items}}'
208+
r'@kubernetes/{{.metadata.namespace}}/{{.metadata.name}}{{"\n"}}'
209+
r'{{end}}',
210+
]
211+
212+
return_code, combined_output = run_local_process(['"$@"', '-'] + command)
213+
stdout, stderr = split_combined_output(combined_output)
214+
215+
if return_code == 0:
216+
data = {}
217+
if container:
218+
data['container'] = container
219+
return list(map(lambda s: (s, data), stdout))
220+
else:
221+
raise InventoryError('kubectl failed (status {0}): {1}'.
222+
format(return_code, '\n'.join(stderr)))
223+
224+
225+
EXECUTION_CONNECTOR = True

setup.py

Lines changed: 1 addition & 0 deletions
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)