-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add keystone-shell container #185
base: master
Are you sure you want to change the base?
Changes from 3 commits
20d00d7
d9d7d56
4e5b080
f6e029a
978b62a
d56080f
3125610
f932da2
6e42314
c86181b
c5a37dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
FROM python:3.6-alpine3.6 | ||
|
||
# To force a rebuild, pass --build-arg REBUILD="$(DATE)" when running | ||
# `docker build` | ||
ARG REBUILD=1 | ||
|
||
COPY requirements.txt stack-fix.c / | ||
|
||
RUN apk add --no-cache ca-certificates tini libffi && \ | ||
apk add --no-cache --virtual build-dep \ | ||
musl-dev linux-headers git make g++ libffi-dev openssl-dev && \ | ||
gcc -shared -fPIC /stack-fix.c -o /stack-fix.so && \ | ||
pip install urllib3 ipaddress ipython ptpython python-openstackclient && \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With monasca/python we could always install latest master, but I am not really sure if that's needed at all ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you're right, this should really be using monasca/python. The docker stuff is mostly a copy&paste hack job that came out of my frustration while debugging keystone-init. I'm fine with holding off on merging until it's converted, though. |
||
pip install -r /requirements.txt && \ | ||
rm -rf /root/.cache/pip && \ | ||
apk del build-dep | ||
|
||
COPY keystone_shell_vars.py kubernetes.py ptpython_init.py start.sh / | ||
COPY keystonerc.sh /root/.keystonerc | ||
|
||
ENV ENV="/root/.keystonerc" | ||
ENTRYPOINT ["sh", "-l", "/start.sh"] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
keystone-shell | ||
============== | ||
|
||
This image allows you to quickly interact with a keystone environment. | ||
|
||
Sources: [keystone-shell][1] · [Dockerfile][2] · [monasca-docker][3] | ||
|
||
Tags | ||
---- | ||
|
||
Images in this repository are tagged as follows: | ||
|
||
* `latest`: refers to the latest stable point release, e.g. `1.0.0` | ||
* `1.0.0`, `1.0`, `1`: standard semver tags | ||
|
||
Basic Usage | ||
----------- | ||
|
||
This is mainly intended for use in Kubernetes environments configured using | ||
[keystone-init][4]. To use, run: | ||
|
||
kubectl run keystone-shell -i -t \ | ||
--rm=true \ | ||
--restart Never \ | ||
--image=monasca/keystone-shell:latest | ||
|
||
(pass `-n <NAMESPACE>` as needed if the desired secrets are located elsewhere) | ||
|
||
Once the container starts, press enter once to start the shell. Basic usage | ||
information will be printed to the terminal. | ||
|
||
There are three commands available: | ||
- `secret <NAME>`: load keystone `OS_` variables from secret in current | ||
namespace `NAME` | ||
- `shell [NAME]`: start a Python shell (using ptipython, with highlighting | ||
and autocomplete) with pre-connected Keystone and Kubernetes clients - | ||
additional usage information will be printed on startup | ||
- if specified, secret with `NAME` will be loaded first (as with | ||
`secret NAME`) and will override existing environment variables | ||
- `openstack ...`: the standard openstack client | ||
|
||
Direct Shell | ||
------------ | ||
|
||
You can also start a shell directly: | ||
|
||
kubectl run keystone-shell \ | ||
--rm=true \ | ||
--restart Never \ | ||
--image=monasca/keystone-shell:latest \ | ||
--env="OS_USERNAME=admin" \ | ||
--env="OS_PASSWORD=secretadmin" \ | ||
--env="OS_PROJECT_NAME=admin" \ | ||
--env="OS_PROJECT_DOMAIN_NAME=Default" \ | ||
--env="OS_USER_DOMAIN_NAME=Default" \ | ||
--env="OS_AUTH_URL=http://monasca-keystone:35357/" \ | ||
--env="KEYSTONE_SHELL=true" \ | ||
--attach -i -t | ||
|
||
Note the `KEYSTONE_SHELL` variable. `OS_` variables can also be provided in | ||
lieu of a secret name (allowing the container to be run in docker rather than | ||
Kubernetes). | ||
|
||
|
||
Verifying Keystone Connectivity | ||
------------------------------- | ||
|
||
This container can be used as a one-off method to check if a Keystone instance | ||
is available. To use, try: | ||
|
||
kubectl run keystone-shell \ | ||
--rm=true \ | ||
--restart Never \ | ||
--image=monasca/keystone-shell:latest \ | ||
--env="KEYSTONE_SECRET=keystone-example-user" \ | ||
--attach | ||
|
||
Note the absence of the `-i` and `-t` parameters. Note that `KEYSTONE_SECRET` | ||
must be defined in this scenario. | ||
|
||
If Keystone is available and the credentials work, the return code will be | ||
zero. If Keystone is not available or the credentials are invalid, the return | ||
code will be 1. Log information should be printed to help narrow down the | ||
error, or at least as much as Keystone will be willing to divulge about it. | ||
|
||
Configuration | ||
------------- | ||
|
||
| Variable | Default | Description | | ||
|--------------------------|---------|----------------------------------------| | ||
| `LOG_LEVEL` | `INFO` | Python logging level | | ||
| `KEYSTONE_TIMEOUT` | `10` | Keystone connection timeout in seconds | | ||
| `KEYSTONE_VERIFY` | `true` | If `false`, don't verify SSL | | ||
| `KEYSTONE_CERT` | unset | Path to mounted alternative CA bundle | | ||
| `KEYSTONE_SECRET` | unset | Secret to auto-load on startup | | ||
| `OS_AUTH_URL` | unset | (**required**) | | ||
| `OS_USERNAME` | unset | keystone username | | ||
| `OS_PASSWORD` | unset | keystone password | | ||
| `OS_USER_DOMAIN_NAME` | unset | keystone user domain name | | ||
| `OS_PROJECT_NAME` | unset | keystone project name | | ||
| `OS_PROJECT_DOMAIN_NAME` | unset | keystone project domain name | | ||
|
||
[1]: https://github.com/monasca/monasca-docker/blob/master/keystone-shell/ | ||
[2]: https://github.com/monasca/monasca-docker/blob/master/keystone-shell/Dockerfile | ||
[3]: https://github.com/monasca/monasca-docker/ | ||
[4]: https://github.com/monasca/monasca-docker/blob/master/keystone-init/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
repository: monasca/keystone-shell | ||
variants: | ||
- tag: latest | ||
aliases: | ||
- :1.0.0 | ||
- :1.0 | ||
- :1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# (C) Copyright 2017 Hewlett Packard Enterprise Development LP | ||
# | ||
# 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 base64 | ||
import logging | ||
import os | ||
import sys | ||
|
||
from typing import Union | ||
|
||
from keystoneauth1.identity import Password | ||
from keystoneauth1.session import Session | ||
from keystoneclient.discover import Discover | ||
from requests import HTTPError | ||
|
||
from kubernetes import KubernetesAPIClient, KubernetesAPIResponse | ||
|
||
NAMESPACE_FILE = '/var/run/secrets/kubernetes.io/serviceaccount/namespace' | ||
|
||
LOG_LEVEL = logging.getLevelName(os.environ.get('LOG_LEVEL', 'INFO')) | ||
logging.basicConfig(level=LOG_LEVEL, | ||
handlers=(logging.StreamHandler(sys.stderr),)) | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
KEYSTONE_PASSWORD_ARGS = [ | ||
'auth_url', 'username', 'password', 'user_id', 'user_domain_id', | ||
'user_domain_name', 'project_id', 'project_name', 'project_domain_id', | ||
'project_domain_name', 'tenant_id', 'tenant_name', 'domain_id', | ||
'domain_name', 'trust_id', 'default_domain_id', 'default_domain_name' | ||
] | ||
KEYSTONE_TIMEOUT = int(os.environ.get('KEYSTONE_TIMEOUT', '10')) | ||
KEYSTONE_VERIFY = os.environ.get('KEYSTONE_VERIFY', 'true') == 'true' | ||
KEYSTONE_CERT = os.environ.get('KEYSTONE_CERT', None) | ||
|
||
_kubernetes_client = None | ||
|
||
|
||
def get_current_namespace() -> str: | ||
if 'NAMESPACE' in os.environ: | ||
return os.environ['NAMESPACE'] | ||
|
||
if os.path.exists(NAMESPACE_FILE): | ||
with open(NAMESPACE_FILE, 'r') as f: | ||
return f.read() | ||
|
||
logger.warning('Not running in cluster and $NAMESPACE is not set, ' | ||
'assuming \'default\'!') | ||
return 'default' | ||
|
||
|
||
def get_kubernetes_client() -> KubernetesAPIClient: | ||
global _kubernetes_client | ||
|
||
if _kubernetes_client is None: | ||
_kubernetes_client = KubernetesAPIClient() | ||
_kubernetes_client.load_auto_config() | ||
|
||
return _kubernetes_client | ||
|
||
|
||
def keystone_env_vars(): | ||
ret = {} | ||
for arg in KEYSTONE_PASSWORD_ARGS: | ||
name = 'OS_{}'.format(arg.upper()) | ||
if name in os.environ: | ||
ret[name] = os.environ[name] | ||
|
||
return ret | ||
|
||
|
||
def keystone_args_from_env(): | ||
ret = {} | ||
for arg in KEYSTONE_PASSWORD_ARGS: | ||
ret[arg] = os.environ.get('OS_{}'.format(arg.upper())) | ||
|
||
return ret | ||
|
||
|
||
def get_keystone_client(): | ||
auth = Password(**keystone_args_from_env()) | ||
session = Session(auth=auth, | ||
app_name='keystone-shell', | ||
user_agent='keystone-shell', | ||
timeout=KEYSTONE_TIMEOUT, | ||
verify=KEYSTONE_VERIFY, | ||
cert=KEYSTONE_CERT) | ||
|
||
discover = Discover(session=session) | ||
return discover.create_client() | ||
|
||
|
||
def get_kubernetes_secret(name: str, | ||
namespace: str=None) -> Union[KubernetesAPIResponse, | ||
None]: | ||
""" | ||
:return: loaded secret dict or None if it does not exist | ||
""" | ||
client = get_kubernetes_client() | ||
|
||
if namespace is None: | ||
namespace = get_current_namespace() | ||
|
||
try: | ||
return client.get('/api/v1/namespaces/{}/secrets/{}', namespace, name) | ||
except HTTPError as e: | ||
if e.response.status_code != 404: | ||
raise | ||
|
||
return None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this return should have one less indentation level. I know you run it from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not that it matters a ton since the code paths are effectively equivalent but my feeling was that (without the conditional to check for a 404) is a bit more clear about the intent. Ideally it would've been:
... which I think makes the intent more clear - that is, |
||
|
||
|
||
def main(): | ||
if len(sys.argv) == 1: | ||
logger.info('no secret name arg provided, will use default environment') | ||
return | ||
|
||
secret_name = sys.argv[1] | ||
|
||
if len(sys.argv) > 2: | ||
secret_namespace = sys.argv[2] | ||
else: | ||
secret_namespace = None | ||
|
||
# purge existing keystone vars from the environment | ||
for var in keystone_env_vars().keys(): | ||
print('unset {};'.format(var)) | ||
|
||
secret = get_kubernetes_secret(secret_name, secret_namespace) | ||
for key, val in secret.data.items(): | ||
val_bytes = base64.b64decode(val) | ||
print('export {}="{}"'.format(key, val_bytes.decode('utf-8'))) | ||
|
||
logger.info('now using account from secret %s', secret_name) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
secret() { | ||
eval "$(python /keystone_shell_vars.py $1)" | ||
} | ||
|
||
shell() { | ||
if [ "$#" -ne 1 ]; then | ||
eval "$(python /keystone_shell_vars.py $1)" | ||
fi | ||
|
||
LD_PRELOAD=/stack-fix.so ptipython -i /ptpython_init.py | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just use the monasca/python.
It should have all musl and stack-fix included already AFAIR.