107 lines
3.9 KiB
Python
107 lines
3.9 KiB
Python
# Copyright 2018 The Kubernetes Authors.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
from .config_exception import ConfigException
|
|
|
|
|
|
class ExecProvider(object):
|
|
"""
|
|
Implementation of the proposal for out-of-tree client
|
|
authentication providers as described here --
|
|
https://github.com/kubernetes/community/blob/master/contributors/design-proposals/auth/kubectl-exec-plugins.md
|
|
|
|
Missing from implementation:
|
|
|
|
* TLS cert support
|
|
* caching
|
|
"""
|
|
|
|
def __init__(self, exec_config, cwd, cluster=None):
|
|
"""
|
|
exec_config must be of type ConfigNode because we depend on
|
|
safe_get(self, key) to correctly handle optional exec provider
|
|
config parameters.
|
|
"""
|
|
for key in ['command', 'apiVersion']:
|
|
if key not in exec_config:
|
|
raise ConfigException(
|
|
'exec: malformed request. missing key \'%s\'' % key)
|
|
self.api_version = exec_config['apiVersion']
|
|
self.args = [exec_config['command']]
|
|
if exec_config.safe_get('args'):
|
|
self.args.extend(exec_config['args'])
|
|
self.env = os.environ.copy()
|
|
if exec_config.safe_get('env'):
|
|
additional_vars = {}
|
|
for item in exec_config['env']:
|
|
name = item['name']
|
|
value = item['value']
|
|
additional_vars[name] = value
|
|
self.env.update(additional_vars)
|
|
if exec_config.safe_get('provideClusterInfo'):
|
|
self.cluster = cluster
|
|
else:
|
|
self.cluster = None
|
|
self.cwd = cwd or None
|
|
|
|
def run(self, previous_response=None):
|
|
is_interactive = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
|
|
kubernetes_exec_info = {
|
|
'apiVersion': self.api_version,
|
|
'kind': 'ExecCredential',
|
|
'spec': {
|
|
'interactive': is_interactive
|
|
}
|
|
}
|
|
if previous_response:
|
|
kubernetes_exec_info['spec']['response'] = previous_response
|
|
if self.cluster:
|
|
kubernetes_exec_info['spec']['cluster'] = self.cluster
|
|
|
|
self.env['KUBERNETES_EXEC_INFO'] = json.dumps(kubernetes_exec_info)
|
|
process = subprocess.Popen(
|
|
self.args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=sys.stderr if is_interactive else subprocess.PIPE,
|
|
stdin=sys.stdin if is_interactive else None,
|
|
cwd=self.cwd,
|
|
env=self.env,
|
|
universal_newlines=True,
|
|
shell=True)
|
|
(stdout, stderr) = process.communicate()
|
|
exit_code = process.wait()
|
|
if exit_code != 0:
|
|
msg = 'exec: process returned %d' % exit_code
|
|
stderr = stderr.strip()
|
|
if stderr:
|
|
msg += '. %s' % stderr
|
|
raise ConfigException(msg)
|
|
try:
|
|
data = json.loads(stdout)
|
|
except ValueError as de:
|
|
raise ConfigException(
|
|
'exec: failed to decode process output: %s' % de)
|
|
for key in ('apiVersion', 'kind', 'status'):
|
|
if key not in data:
|
|
raise ConfigException(
|
|
'exec: malformed response. missing key \'%s\'' % key)
|
|
if data['apiVersion'] != self.api_version:
|
|
raise ConfigException(
|
|
'exec: plugin api version %s does not match %s' %
|
|
(data['apiVersion'], self.api_version))
|
|
return data['status']
|