Skip to content

Commit 5186173

Browse files
committedSep 4, 2020
Add kubernetes connector.
1 parent b47eed0 commit 5186173

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