rework creating admin context - use keystoneauth1 lib
rather than own creator of session and auth_plugin from keystoneclient library. Change-Id: Id7f71e08de13e77586832f6d12cba1caff3772fe
This commit is contained in:
parent
590ff04f07
commit
d67adb7223
@ -28,6 +28,7 @@ EC2API_CONF_DIR=${EC2API_CONF_DIR:-/etc/ec2api}
|
|||||||
EC2API_CONF_FILE=${EC2API_CONF_DIR}/ec2api.conf
|
EC2API_CONF_FILE=${EC2API_CONF_DIR}/ec2api.conf
|
||||||
EC2API_DEBUG=${EC2API_DEBUG:-True}
|
EC2API_DEBUG=${EC2API_DEBUG:-True}
|
||||||
EC2API_STATE_PATH=${EC2API_STATE_PATH:=$DATA_DIR/ec2api}
|
EC2API_STATE_PATH=${EC2API_STATE_PATH:=$DATA_DIR/ec2api}
|
||||||
|
EC2API_AUTH_CACHE_DIR=${EC2API_AUTH_CACHE_DIR:-/var/cache/ec2api}
|
||||||
|
|
||||||
EC2API_SERVICE_PORT=${EC2API_SERVICE_PORT:-8788}
|
EC2API_SERVICE_PORT=${EC2API_SERVICE_PORT:-8788}
|
||||||
EC2API_S3_SERVICE_PORT=${EC2API_S3_SERVICE_PORT:-3334}
|
EC2API_S3_SERVICE_PORT=${EC2API_S3_SERVICE_PORT:-3334}
|
||||||
@ -178,12 +179,9 @@ function configure_ec2api {
|
|||||||
# ec2api Api Configuration
|
# ec2api Api Configuration
|
||||||
#-------------------------
|
#-------------------------
|
||||||
|
|
||||||
iniset $EC2API_CONF_FILE DEFAULT admin_tenant_name $SERVICE_TENANT_NAME
|
configure_auth_token_middleware $EC2API_CONF_FILE $EC2API_ADMIN_USER $EC2API_AUTH_CACHE_DIR
|
||||||
iniset $EC2API_CONF_FILE DEFAULT admin_user $EC2API_ADMIN_USER
|
|
||||||
iniset $EC2API_CONF_FILE DEFAULT admin_password $SERVICE_PASSWORD
|
|
||||||
|
|
||||||
iniset $EC2API_CONF_FILE DEFAULT ec2api_workers "$API_WORKERS"
|
iniset $EC2API_CONF_FILE DEFAULT ec2api_workers "$API_WORKERS"
|
||||||
iniset $EC2API_CONF_FILE DEFAULT keystone_url "$KEYSTONE_SERVICE_URI"
|
|
||||||
iniset $EC2API_CONF_FILE DEFAULT keystone_ec2_tokens_url "$KEYSTONE_SERVICE_URI_V3/ec2tokens"
|
iniset $EC2API_CONF_FILE DEFAULT keystone_ec2_tokens_url "$KEYSTONE_SERVICE_URI_V3/ec2tokens"
|
||||||
iniset $EC2API_CONF_FILE DEFAULT region_list "$REGION_NAME"
|
iniset $EC2API_CONF_FILE DEFAULT region_list "$REGION_NAME"
|
||||||
|
|
||||||
|
@ -47,6 +47,10 @@ LOG = logging.getLogger(__name__)
|
|||||||
ec2_opts = [
|
ec2_opts = [
|
||||||
cfg.StrOpt('keystone_url',
|
cfg.StrOpt('keystone_url',
|
||||||
default='http://localhost:5000/',
|
default='http://localhost:5000/',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='code was switched to common section '
|
||||||
|
'"keystone_authtoken"',
|
||||||
|
deprecated_since='Newton',
|
||||||
help='URL for getting admin session.'),
|
help='URL for getting admin session.'),
|
||||||
cfg.StrOpt('keystone_ec2_tokens_url',
|
cfg.StrOpt('keystone_ec2_tokens_url',
|
||||||
default='http://localhost:5000/v3/ec2tokens',
|
default='http://localhost:5000/v3/ec2tokens',
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from cinderclient import client as cinderclient
|
from cinderclient import client as cinderclient
|
||||||
from glanceclient import client as glanceclient
|
from glanceclient import client as glanceclient
|
||||||
|
from keystoneauth1 import loading as ks_loading
|
||||||
from keystoneclient.auth.identity.generic import password as keystone_auth
|
from keystoneclient.auth.identity.generic import password as keystone_auth
|
||||||
from keystoneclient import client as keystoneclient
|
from keystoneclient import client as keystoneclient
|
||||||
from keystoneclient import session as keystone_session
|
from keystoneclient import session as keystone_session
|
||||||
@ -31,8 +32,16 @@ logger = logging.getLogger(__name__)
|
|||||||
ec2_opts = [
|
ec2_opts = [
|
||||||
cfg.BoolOpt('ssl_insecure',
|
cfg.BoolOpt('ssl_insecure',
|
||||||
default=False,
|
default=False,
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='code was switched to common section '
|
||||||
|
'"keystone_authtoken"',
|
||||||
|
deprecated_since='Newton',
|
||||||
help="Verify HTTPS connections."),
|
help="Verify HTTPS connections."),
|
||||||
cfg.StrOpt('ssl_ca_file',
|
cfg.StrOpt('ssl_ca_file',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='code was switched to common section '
|
||||||
|
'"keystone_authtoken"',
|
||||||
|
deprecated_since='Newton',
|
||||||
help="CA certificate file to use to verify "
|
help="CA certificate file to use to verify "
|
||||||
"connecting clients"),
|
"connecting clients"),
|
||||||
cfg.StrOpt('nova_service_type',
|
cfg.StrOpt('nova_service_type',
|
||||||
@ -45,20 +54,34 @@ ec2_opts = [
|
|||||||
default='volumev2',
|
default='volumev2',
|
||||||
help='Service type of Volume API, registered in Keystone '
|
help='Service type of Volume API, registered in Keystone '
|
||||||
'catalog.'),
|
'catalog.'),
|
||||||
# TODO(andrey-mp): keystone v3 allows to pass domain_name
|
|
||||||
# or domain_id to auth. This code should support this feature.
|
|
||||||
cfg.StrOpt('admin_user',
|
cfg.StrOpt('admin_user',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='code was switched to common section '
|
||||||
|
'"keystone_authtoken"',
|
||||||
|
deprecated_since='Newton',
|
||||||
help=_("Admin user to access specific cloud resourses")),
|
help=_("Admin user to access specific cloud resourses")),
|
||||||
cfg.StrOpt('admin_password',
|
cfg.StrOpt('admin_password',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='code was switched to common section '
|
||||||
|
'"keystone_authtoken"',
|
||||||
|
deprecated_since='Newton',
|
||||||
help=_("Admin password"),
|
help=_("Admin password"),
|
||||||
secret=True),
|
secret=True),
|
||||||
cfg.StrOpt('admin_tenant_name',
|
cfg.StrOpt('admin_tenant_name',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='code was switched to common section '
|
||||||
|
'"keystone_authtoken"',
|
||||||
|
deprecated_since='Newton',
|
||||||
help=_("Admin tenant name")),
|
help=_("Admin tenant name")),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_opts(ec2_opts)
|
CONF.register_opts(ec2_opts)
|
||||||
|
|
||||||
|
GROUP_AUTHTOKEN = 'keystone_authtoken'
|
||||||
|
ks_loading.register_session_conf_options(CONF, GROUP_AUTHTOKEN)
|
||||||
|
ks_loading.register_auth_conf_options(CONF, GROUP_AUTHTOKEN)
|
||||||
|
|
||||||
|
|
||||||
# Nova API version with microversions support
|
# Nova API version with microversions support
|
||||||
REQUIRED_NOVA_API_VERSION = '2.1'
|
REQUIRED_NOVA_API_VERSION = '2.1'
|
||||||
@ -106,8 +129,8 @@ def cinder(context):
|
|||||||
|
|
||||||
|
|
||||||
def keystone(context):
|
def keystone(context):
|
||||||
auth_url = context.session.get_endpoint(service_type='identity')
|
url = context.session.get_endpoint(service_type='identity')
|
||||||
return keystoneclient.Client(auth_url=auth_url,
|
return keystoneclient.Client(auth_url=url,
|
||||||
session=context.session)
|
session=context.session)
|
||||||
|
|
||||||
|
|
||||||
@ -202,12 +225,7 @@ class _rpc_RequestContextSerializer(messaging.NoOpSerializer):
|
|||||||
_admin_session = None
|
_admin_session = None
|
||||||
|
|
||||||
|
|
||||||
def get_os_admin_session():
|
def get_session_from_deprecated():
|
||||||
"""Create a context to interact with OpenStack as an administrator."""
|
|
||||||
# NOTE(ft): this is a singletone because keystone's session looks thread
|
|
||||||
# safe for both regular and token renewal requests
|
|
||||||
global _admin_session
|
|
||||||
if not _admin_session:
|
|
||||||
auth = keystone_auth.Password(
|
auth = keystone_auth.Password(
|
||||||
username=CONF.admin_user,
|
username=CONF.admin_user,
|
||||||
password=CONF.admin_password,
|
password=CONF.admin_password,
|
||||||
@ -217,12 +235,31 @@ def get_os_admin_session():
|
|||||||
)
|
)
|
||||||
params = {'auth': auth}
|
params = {'auth': auth}
|
||||||
update_request_params_with_ssl(params)
|
update_request_params_with_ssl(params)
|
||||||
_admin_session = keystone_session.Session(**params)
|
return keystone_session.Session(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def get_os_admin_session():
|
||||||
|
"""Create a context to interact with OpenStack as an administrator."""
|
||||||
|
# NOTE(ft): this is a singletone because keystone's session looks thread
|
||||||
|
# safe for both regular and token renewal requests
|
||||||
|
global _admin_session
|
||||||
|
if not _admin_session:
|
||||||
|
if not CONF[GROUP_AUTHTOKEN].auth_type:
|
||||||
|
_admin_session = get_session_from_deprecated()
|
||||||
|
else:
|
||||||
|
auth_plugin = ks_loading.load_auth_from_conf_options(
|
||||||
|
CONF, GROUP_AUTHTOKEN)
|
||||||
|
_admin_session = ks_loading.load_session_from_conf_options(
|
||||||
|
CONF, GROUP_AUTHTOKEN, auth=auth_plugin)
|
||||||
|
|
||||||
return _admin_session
|
return _admin_session
|
||||||
|
|
||||||
|
|
||||||
def update_request_params_with_ssl(params):
|
def update_request_params_with_ssl(params):
|
||||||
|
if not CONF[GROUP_AUTHTOKEN].auth_type:
|
||||||
verify = CONF.ssl_ca_file or not CONF.ssl_insecure
|
verify = CONF.ssl_ca_file or not CONF.ssl_insecure
|
||||||
|
else:
|
||||||
|
verify = (CONF[GROUP_AUTHTOKEN].cafile or
|
||||||
|
not CONF[GROUP_AUTHTOKEN].insecure)
|
||||||
if verify is not True:
|
if verify is not True:
|
||||||
params['verify'] = verify
|
params['verify'] = verify
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import operator
|
||||||
|
|
||||||
|
from keystoneauth1 import loading as ks_loading
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
import ec2api.clients
|
import ec2api.clients
|
||||||
import ec2api.db.api
|
import ec2api.db.api
|
||||||
@ -21,6 +25,9 @@ import ec2api.utils
|
|||||||
import ec2api.wsgi
|
import ec2api.wsgi
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
def list_opts():
|
||||||
return [
|
return [
|
||||||
('DEFAULT',
|
('DEFAULT',
|
||||||
@ -34,3 +41,20 @@ def list_opts():
|
|||||||
ec2api.wsgi.wsgi_opts,
|
ec2api.wsgi.wsgi_opts,
|
||||||
)),
|
)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
GROUP_AUTHTOKEN = 'keystone_authtoken'
|
||||||
|
|
||||||
|
|
||||||
|
def list_auth_opts():
|
||||||
|
opt_list = ks_loading.register_session_conf_options(CONF, GROUP_AUTHTOKEN)
|
||||||
|
opt_list.insert(0, ks_loading.get_auth_common_conf_options()[0])
|
||||||
|
# NOTE(mhickey): There are a lot of auth plugins, we just generate
|
||||||
|
# the config options for a few common ones
|
||||||
|
plugins = ['password', 'v2password', 'v3password']
|
||||||
|
for name in plugins:
|
||||||
|
for plugin_option in ks_loading.get_auth_plugin_conf_options(name):
|
||||||
|
if all(option.name != plugin_option.name for option in opt_list):
|
||||||
|
opt_list.append(plugin_option)
|
||||||
|
opt_list.sort(key=operator.attrgetter('name'))
|
||||||
|
return [(GROUP_AUTHTOKEN, opt_list)]
|
||||||
|
@ -20,22 +20,59 @@ from oslo_config import fixture as config_fixture
|
|||||||
from oslo_context import context
|
from oslo_context import context
|
||||||
from oslotest import base as test_base
|
from oslotest import base as test_base
|
||||||
|
|
||||||
|
from ec2api import clients
|
||||||
from ec2api import context as ec2_context
|
from ec2api import context as ec2_context
|
||||||
|
|
||||||
|
|
||||||
cfg.CONF.import_opt('keystone_url', 'ec2api.api')
|
cfg.CONF.import_opt('keystone_url', 'ec2api.api')
|
||||||
|
GROUP_AUTHTOKEN = 'keystone_authtoken'
|
||||||
|
|
||||||
|
|
||||||
class ContextTestCase(test_base.BaseTestCase):
|
class ContextTestCase(test_base.BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
@mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
|
||||||
super(ContextTestCase, self).setUp()
|
@mock.patch('keystoneauth1.loading.load_session_from_conf_options')
|
||||||
|
def test_get_os_admin_context(self, session, auth):
|
||||||
conf = config_fixture.Config()
|
conf = config_fixture.Config()
|
||||||
|
clients._admin_session = None
|
||||||
|
conf.config(auth_type='fake', group=GROUP_AUTHTOKEN)
|
||||||
|
|
||||||
|
imp.reload(ec2_context)
|
||||||
|
# NOTE(ft): initialize a regular context to populate oslo_context's
|
||||||
|
# local storage to prevent admin context to populate it.
|
||||||
|
# Used to implicitly validate overwrite=False argument of the call
|
||||||
|
# RequestContext constructor from inside get_os_admin_context
|
||||||
|
if not context.get_current():
|
||||||
|
ec2_context.RequestContext(None, None)
|
||||||
|
|
||||||
|
ctx = ec2_context.get_os_admin_context()
|
||||||
|
conf = cfg.CONF
|
||||||
|
auth.assert_called_once_with(conf, GROUP_AUTHTOKEN)
|
||||||
|
auth_plugin = auth.return_value
|
||||||
|
session.assert_called_once_with(conf, GROUP_AUTHTOKEN,
|
||||||
|
auth=auth_plugin)
|
||||||
|
self.assertIsNone(ctx.user_id)
|
||||||
|
self.assertIsNone(ctx.project_id)
|
||||||
|
self.assertIsNone(ctx.auth_token)
|
||||||
|
self.assertEqual([], ctx.service_catalog)
|
||||||
|
self.assertTrue(ctx.is_os_admin)
|
||||||
|
self.assertIsNotNone(ctx.session)
|
||||||
|
self.assertIsNotNone(ctx.session.auth)
|
||||||
|
self.assertNotEqual(context.get_current(), ctx)
|
||||||
|
|
||||||
|
session.reset_mock()
|
||||||
|
ec2_context.get_os_admin_context()
|
||||||
|
self.assertFalse(session.called)
|
||||||
|
|
||||||
|
@mock.patch('keystoneclient.auth.identity.generic.password.Password')
|
||||||
|
def test_get_os_admin_context_deprecated(self, password_plugin):
|
||||||
|
conf = config_fixture.Config()
|
||||||
|
clients._admin_session = None
|
||||||
|
conf.config(auth_type=None, group=GROUP_AUTHTOKEN)
|
||||||
conf.config(admin_user='admin',
|
conf.config(admin_user='admin',
|
||||||
admin_password='password',
|
admin_password='password',
|
||||||
admin_tenant_name='service')
|
admin_tenant_name='service')
|
||||||
|
|
||||||
@mock.patch('keystoneclient.auth.identity.generic.password.Password')
|
|
||||||
def test_get_os_admin_context(self, password_plugin):
|
|
||||||
imp.reload(ec2_context)
|
imp.reload(ec2_context)
|
||||||
# NOTE(ft): initialize a regular context to populate oslo_context's
|
# NOTE(ft): initialize a regular context to populate oslo_context's
|
||||||
# local storage to prevent admin context to populate it.
|
# local storage to prevent admin context to populate it.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
output_file = etc/ec2api/ec2api.conf.sample
|
output_file = etc/ec2api/ec2api.conf.sample
|
||||||
wrap_width = 79
|
wrap_width = 79
|
||||||
namespace = ec2api
|
namespace = ec2api
|
||||||
|
namespace = keystoneauth1
|
||||||
namespace = ec2api.api
|
namespace = ec2api.api
|
||||||
namespace = ec2api.metadata
|
namespace = ec2api.metadata
|
||||||
namespace = ec2api.s3
|
namespace = ec2api.s3
|
||||||
|
17
install.sh
17
install.sh
@ -4,6 +4,8 @@
|
|||||||
SERVICE_USERNAME=ec2api
|
SERVICE_USERNAME=ec2api
|
||||||
SERVICE_PASSWORD=ec2api
|
SERVICE_PASSWORD=ec2api
|
||||||
SERVICE_TENANT=service
|
SERVICE_TENANT=service
|
||||||
|
# this domain name will be used for project and user
|
||||||
|
SERVICE_DOMAIN_NAME=Default
|
||||||
EC2API_PORT=8788
|
EC2API_PORT=8788
|
||||||
CONNECTION="mysql://ec2api:ec2api@127.0.0.1/ec2api?charset=utf8"
|
CONNECTION="mysql://ec2api:ec2api@127.0.0.1/ec2api?charset=utf8"
|
||||||
LOG_DIR=/var/log/ec2api
|
LOG_DIR=/var/log/ec2api
|
||||||
@ -267,15 +269,22 @@ iniset $CONF_FILE DEFAULT api_paste_config $APIPASTE_FILE
|
|||||||
iniset $CONF_FILE DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(user_name)s %(project_name)s] %(instance)s%(message)s"
|
iniset $CONF_FILE DEFAULT logging_context_format_string "%(asctime)s.%(msecs)03d %(levelname)s %(name)s [%(request_id)s %(user_name)s %(project_name)s] %(instance)s%(message)s"
|
||||||
iniset $CONF_FILE DEFAULT log_dir "$LOG_DIR"
|
iniset $CONF_FILE DEFAULT log_dir "$LOG_DIR"
|
||||||
iniset $CONF_FILE DEFAULT verbose True
|
iniset $CONF_FILE DEFAULT verbose True
|
||||||
iniset $CONF_FILE DEFAULT keystone_url "$OS_AUTH_URL"
|
|
||||||
iniset $CONF_FILE DEFAULT keystone_ec2_tokens_url "$OS_AUTH_URL/v3/ec2tokens"
|
iniset $CONF_FILE DEFAULT keystone_ec2_tokens_url "$OS_AUTH_URL/v3/ec2tokens"
|
||||||
iniset $CONF_FILE database connection "$CONNECTION"
|
iniset $CONF_FILE database connection "$CONNECTION"
|
||||||
iniset $CONF_FILE DEFAULT full_vpc_support "$VPC_SUPPORT"
|
iniset $CONF_FILE DEFAULT full_vpc_support "$VPC_SUPPORT"
|
||||||
iniset $CONF_FILE DEFAULT external_network "$EXTERNAL_NETWORK"
|
iniset $CONF_FILE DEFAULT external_network "$EXTERNAL_NETWORK"
|
||||||
|
|
||||||
iniset $CONF_FILE DEFAULT admin_user $SERVICE_USERNAME
|
GROUP_AUTHTOKEN="keystone_authtoken"
|
||||||
iniset $CONF_FILE DEFAULT admin_password $SERVICE_PASSWORD
|
iniset $CONF_FILE $GROUP_AUTHTOKEN signing_dir "$AUTH_CACHE_DIR"
|
||||||
iniset $CONF_FILE DEFAULT admin_tenant_name $SERVICE_TENANT
|
iniset $CONF_FILE $GROUP_AUTHTOKEN auth_uri "$OS_AUTH_URL"
|
||||||
|
iniset $CONF_FILE $GROUP_AUTHTOKEN auth_url "$OS_AUTH_URL"
|
||||||
|
iniset $CONF_FILE $GROUP_AUTHTOKEN username $SERVICE_USERNAME
|
||||||
|
iniset $CONF_FILE $GROUP_AUTHTOKEN password $SERVICE_PASSWORD
|
||||||
|
iniset $CONF_FILE $GROUP_AUTHTOKEN project_name $SERVICE_TENANT
|
||||||
|
iniset $CONF_FILE $GROUP_AUTHTOKEN project_domain_name $SERVICE_DOMAIN_NAME
|
||||||
|
iniset $CONF_FILE $GROUP_AUTHTOKEN user_domain_name $SERVICE_DOMAIN_NAME
|
||||||
|
iniset $CONF_FILE $GROUP_AUTHTOKEN auth_type password
|
||||||
|
|
||||||
|
|
||||||
if [[ -f "$NOVA_CONF" ]]; then
|
if [[ -f "$NOVA_CONF" ]]; then
|
||||||
# NOTE(ft): use swift instead internal s3 server if enabled
|
# NOTE(ft): use swift instead internal s3 server if enabled
|
||||||
|
@ -20,6 +20,7 @@ oslo.utils>=3.16.0 # Apache-2.0
|
|||||||
Paste # MIT
|
Paste # MIT
|
||||||
PasteDeploy>=1.5.0 # MIT
|
PasteDeploy>=1.5.0 # MIT
|
||||||
pbr>=1.6 # Apache-2.0
|
pbr>=1.6 # Apache-2.0
|
||||||
|
keystoneauth1>=2.10.0 # Apache-2.0
|
||||||
python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
|
python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
|
||||||
python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0
|
python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0
|
||||||
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
|
python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
|
@ -28,6 +28,7 @@ setup-hooks =
|
|||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
ec2api = ec2api.opts:list_opts
|
ec2api = ec2api.opts:list_opts
|
||||||
ec2api.api = ec2api.api.opts:list_opts
|
ec2api.api = ec2api.api.opts:list_opts
|
||||||
|
keystoneauth1 = ec2api.opts:list_auth_opts
|
||||||
ec2api.metadata = ec2api.metadata.opts:list_opts
|
ec2api.metadata = ec2api.metadata.opts:list_opts
|
||||||
ec2api.s3 = ec2api.s3.opts:list_opts
|
ec2api.s3 = ec2api.s3.opts:list_opts
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user