Retire Solum: remove repo content

Solum project is retiring
- https://review.opendev.org/c/openstack/governance/+/919211

this commit remove the content of this project repo

Change-Id: Ide6f26a5437553c19a40b402c56e931f51d025eb
This commit is contained in:
Ghanshyam Mann 2024-05-10 12:33:08 -07:00 committed by Ghanshyam
parent b9b0d8314c
commit 31e8a21890
79 changed files with 8 additions and 9331 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = solumclient
omit = solumclient/tests/*
[report]
ignore_errors = True

53
.gitignore vendored
View File

@ -1,53 +0,0 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
.idea
cover
nosetests.xml
.stestr/
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -1,3 +0,0 @@
[DEFAULT]
test_path=${OS_TEST_PATH:-./solumclient/tests}
top_dir=./

View File

@ -1,6 +0,0 @@
- project:
templates:
- check-requirements
- openstack-cover-jobs
- openstack-python3-jobs
- publish-openstack-docs-pti

View File

@ -1,16 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
https://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
https://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/python-solumclient

View File

@ -1,4 +0,0 @@
Solum Client Style Commandments
===============================
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/

175
LICENSE
View File

@ -1,175 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,35 +1,10 @@
========================
Team and repository tags
========================
This project is no longer maintained.
.. image:: https://governance.openstack.org/tc/badges/python-solumclient.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
.. Change things from this point on
Python bindings to the Solum API
================================
This is a client library for Solum built on the Solum API.
* Free software: Apache license
* Documentation: https://wiki.openstack.org/wiki/Solum
Installation
============
At the command line::
$ pip install python-solumclient
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv python-solumclient
$ pip install python-solumclient
Usage
========
To use python-solumclient in a project::
import solumclient
For any further questions, please email
openstack-discuss@lists.openstack.org or join #openstack-dev on
OFTC.

View File

@ -1 +0,0 @@
[python: **.py]

View File

@ -1,311 +0,0 @@
#!/usr/bin/env python
# Copyright 2014 - Rackspace Hosting
#
# 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.
"""
Setup everything for a Github-hosted repo to use solum as its CI tool.
"""
import argparse
import base64
import getpass
import json
import os
import random
import re
import string
import tempfile
import httplib2
import six
import yaml
from solumclient import client as solum_client
from solumclient.common import cliutils
SOLUM_API_VERSION = '1'
CREDENTIALS = {}
PLAN_TEMPLATE = {"version": 1,
"name": "chef",
"description": "chef testy",
"artifacts": []}
def _get_solum_client():
args = {}
args['os_username'] = os.getenv('OS_USERNAME', '')
args['os_password'] = os.getenv('OS_PASSWORD', '')
args['os_tenant_name'] = os.getenv('OS_TENANT_NAME', '')
args['os_auth_url'] = os.getenv('OS_AUTH_URL', '')
args['solum_url'] = os.getenv('SOLUM_URL', '')
try:
client = solum_client.get_client(SOLUM_API_VERSION, **args)
return client
except Exception as ex:
print("Error in getting Solum client: %s" % ex)
exit(1)
def _get_token(git_url):
# Get an OAuth token with the scope of 'repo' for the user
if git_url in CREDENTIALS and 'token' in CREDENTIALS[git_url]:
return CREDENTIALS[git_url]['token']
repo_pat = re.compile(r'github\.com[:/](.+?)/(.+?)($|/$|\.git$|\.git/$)')
match = repo_pat.search(git_url)
if match:
user_org_name = match.group(1)
repo = match.group(2)
else:
print('Failed parsing %s' % git_url)
exit(1)
full_repo_name = '/'.join([user_org_name, repo])
username = six.moves.input("Username for repo '%s' [%s]: " %
(full_repo_name, user_org_name))
if not username:
username = user_org_name
password = getpass.getpass("Password: ")
# TODO(james_li): add support for two-factor auth
CREDENTIALS[git_url] = {}
CREDENTIALS[git_url]['user'] = username
CREDENTIALS[git_url]['password'] = password
CREDENTIALS[git_url]['full_repo'] = full_repo_name
http = httplib2.Http()
auth = base64.encodestring(username + ':' + password)
headers = {'Authorization': 'Basic ' + auth,
'Content-Type': 'application/json'}
# 'note' field has to be unique
note = 'Solum-status-' + ''.join(random.sample(string.lowercase, 5))
data = {'scopes': 'repo', 'note': note}
# TODO(james_li): make the url configurable
resp, content = http.request('https://api.github.com/authorizations',
'POST', headers=headers,
body=json.dumps(data))
if resp['status'] == '201' or resp['status'] == '200':
content_dict = json.loads(content)
CREDENTIALS[git_url]['token'] = str(content_dict['token'])
return CREDENTIALS[git_url]['token']
else:
print('Failed to get token from Github')
exit(1)
def _filter_trigger_url(url):
filtered_url = url
url_pattern = re.compile(r'^(http://)(.+)')
match = url_pattern.search(url)
if match:
filtered_url = ''.join(['https://', match.group(2)])
else:
print('Cannot filter trigger url, to use the original one')
return filtered_url
def get_planfile(git_uri, app_name, cmd, public):
plan_dict = dict.copy(PLAN_TEMPLATE)
plan_dict['name'] = app_name
plan_dict['description'] = git_uri # Put repo uri as plan desc.
arti = {"name": "chef", "artifact_type": "chef",
"content": {}, "language_pack": "auto"}
arti['content']['href'] = git_uri
if not public:
arti['content']['private'] = True
arti['unittest_cmd'] = cmd
plan_dict['artifacts'].append(arti)
# Create a Github token and insert it into plan file
for arti in plan_dict['artifacts']:
arti['status_token'] = _get_token(arti['content']['href'])
plan_file = tempfile.NamedTemporaryFile(suffix='.yaml',
prefix='solum_',
delete=False)
plan_file.write(yaml.dump(plan_dict, default_flow_style=False))
plan_file_name = plan_file.name
plan_file.close()
return plan_file_name
def create_plan(client, plan_file):
cmd = ['solum', 'app', 'create', plan_file]
print(' '.join(cmd))
with open(plan_file) as definition_file:
definition = definition_file.read()
plan = client.plans.create(definition)
fields = ['uuid', 'name', 'description', 'uri']
data = dict([(f, getattr(plan, f, ''))
for f in fields])
cliutils.print_dict(data, wrap=72)
if data['uri'] is None:
print('Error: no uri found in plan creation')
exit(1)
# get public keys in the case of private repos
artifacts = getattr(plan, 'artifacts', [])
for arti in artifacts:
content = getattr(arti, 'content', {})
if 'public_key' in content and 'href' in content:
CREDENTIALS[content['href']]['pub_key'] = content['public_key']
return data['uri']
def create_assembly(client, app_name, plan_uri):
cmd = ['solum', 'assembly', 'create', app_name, plan_uri]
print(' '.join(cmd))
assembly = client.assemblies.create(name=app_name, plan_uri=plan_uri)
fields = ['uuid', 'name', 'description', 'status', 'application_uri',
'trigger_uri']
data = dict([(f, getattr(assembly, f, ''))
for f in fields])
cliutils.print_dict(data, wrap=72)
trigger_uri = data['trigger_uri']
if trigger_uri is None:
print('Error in trigger uri')
exit(1)
return trigger_uri
def create_webhook(trigger_uri):
# Create github web hooks for pull requests
for key in CREDENTIALS.keys():
user = CREDENTIALS[key]['user']
password = CREDENTIALS[key]['password']
auth = base64.encodestring(user + ':' + password)
http = httplib2.Http()
# TODO(james_li): make this url configurable
github_url = ('https://api.github.com/repos/%s/hooks' %
CREDENTIALS[key]['full_repo'])
headers = {'Authorization': 'Basic ' + auth,
'Content-Type': 'application/json'}
data = {'name': 'web',
'events': ['pull_request', 'commit_comment'],
'config': {'content_type': 'json',
'url': trigger_uri}}
resp, _ = http.request(github_url, 'POST',
headers=headers,
body=json.dumps(data))
if resp['status'] != '201' and resp['status'] != '200':
print("Failed to create web hooks")
print("Make sure you have access to repo '%s'" % key)
exit(1)
def add_ssh_keys(args):
if args.public:
return
# add public keys
for key in CREDENTIALS.keys():
if ('pub_key' in CREDENTIALS[key] and
CREDENTIALS[key]['pub_key'] is not None):
user = CREDENTIALS[key]['user']
password = CREDENTIALS[key]['password']
auth = base64.encodestring(user + ':' + password)
http = httplib2.Http()
if args.user_key:
# TODO(james_li): make the url configurable
github_url = 'https://api.github.com/user/keys'
else:
github_url = ('https://api.github.com/repos/%s/keys' %
CREDENTIALS[key]['full_repo'])
headers = {'Authorization': 'Basic ' + auth,
'Content-Type': 'application/json'}
data = {'title': 'devops@Solum',
'key': CREDENTIALS[key]['pub_key']}
resp, _ = http.request(github_url, 'POST',
headers=headers,
body=json.dumps(data))
if resp['status'] != '201' and resp['status'] != '200':
if args.user_key:
print("Failed to add a ssh key to the account %s" % user)
else:
print("Failed to add a deploy key to the repo %s" % key)
exit(1)
def validate_args(args):
if len(args.command) == 0 or len(args.git_uri) == 0:
print("Please input for --test-cmd and --git-uri")
exit(1)
# try to use a correct git uri
pat = re.compile(r'github\.com[:/](.+?)/(.+?)($|/.*$|\.git$|\.git/.*$)')
match = pat.search(args.git_uri)
if match:
user_org_name = match.group(1)
repo = match.group(2)
if args.public:
correct_uri = 'https://github.com/%s/%s' % (user_org_name, repo)
else:
correct_uri = 'git@github.com:%s/%s.git' % (user_org_name, repo)
return correct_uri
else:
print("The input git uri seems not right")
if args.public:
print("The correct format is: https://github.com/<USER>/<REPO>")
else:
print("The correct format is: git@github.com:<USER>/<REPO>.git")
exit(1)
def main(args):
git_uri = validate_args(args)
client = _get_solum_client()
plan_file = get_planfile(git_uri, args.app_name, args.command, args.public)
print('\n')
print("************************* Starting setup *************************")
print('\n')
plan_uri = create_plan(client, plan_file)
add_ssh_keys(args)
try:
os.remove(plan_file)
except OSError:
print('Cannot remove %s. Skip and move forward...' % plan_file)
trigger_uri = create_assembly(client, args.app_name, plan_uri)
create_webhook(_filter_trigger_url(trigger_uri))
print('Successfully created Solum plan, assembly and webhooks!')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('app_name', help="app name")
parser.add_argument('--git-uri', required=True, dest='git_uri',
help="git repo uri")
parser.add_argument('--test-cmd', required=True, dest='command',
help="entrypoint to run tests")
parser.add_argument('--public', action='store_true', default=False,
dest='public', help="public repo, defaults to False")
parser.add_argument('--user-key', action='store_true', default=False,
dest='user_key', help="add SSH key to the user account"
" rather than the repo,"
" defaults to False")
args = parser.parse_args()
main(args)

View File

@ -1,5 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
openstackdocstheme>=2.2.1 # Apache-2.0
sphinx>=2.0.0,!=2.1.0 # BSD

View File

@ -1,30 +0,0 @@
===========================
Command-line Tool Reference
===========================
In order to use the CLI, you must provide your OpenStack username,
password, tenant, and auth endpoint. Use the corresponding
configuration options (``--os-username``, ``--os-password``,
``--os-tenant-id``, and ``--os-auth-url``) or set them in environment
variables::
export OS_USERNAME=user
export OS_PASSWORD=pass
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
The command line tool will attempt to reauthenticate using your
provided credentials for every request. You can override this behavior
by manually supplying an auth token using ``--os-solum-url`` and
``--os-auth-token``. You can alternatively set these environment
variables::
export OS_SOLUM_URL=http://solum.example.org:9777/
export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155
Once you've configured your authentication parameters, you can run
``solum help`` to see a complete listing of available commands.
.. toctree::
solum

View File

@ -1,108 +0,0 @@
=============================================================================
Software Development Lifecycle Automation service (solum) command-line client
=============================================================================
The solum client is the command-line interface (CLI) for
the Software Development Lifecycle Automation service (solum) API and its
extensions.
This chapter documents :command:`solum` version ``2.4.0``.
.. _solum_command_usage:
solum usage
~~~~~~~~~~~
``solum help``
Show this help message.
``solum info``
Show Solum endpoint and API release version.
``solum --version``
Show current Solum client version and exit.
``solum lp help``
Show a help message specific to languagepack commands.
``solum lp create <NAME> <GIT_REPO_URL>``
Create a new language pack from a git repo.
``solum lp list``
Print and index of all available language packs.
``solum lp show <NAME|UUID>``
Print the details of a language pack.
``solum lp delete <NAME|UUID>``
Destroy a language pack.
``solum lp logs <NAME|UUID>``
Show logs for a language pack.
``solum app help``
Show a help message specific to app commands.
``solum app list``
Print an index of all deployed applications.
``solum app show <NAME|UUID>``
Print detailed information about one application.
``solum app create``
Register a new application with Solum.
.. code-block:: console
solum app create [--app-file <AppFile>] [--git-url <GIT_URL>]
[--lp <LANGUAGEPACK>]
[--param-file <PARAMFILE>]
[--setup-trigger]
[--trigger-workflow <CUSTOM-WORKFLOW>]
<CUSTOM-WORKFLOW>=(unittest | build | unittest+build)
Without the ``--trigger-workflow`` flag, the workflow ``unittest+build+deploy``
is triggered (this is the default workflow).
``solum app deploy <NAME|UUID>``
Deploy an application, building any applicable artifacts first.
du-id is optional flag. It can be used to pass in ID of a previously
created deployment unit. If passed, this command will deploy the du
referenced by the provided ``du-id`` instead of building one first.
``solum app delete <NAME|UUID>``
Delete an application and all related artifacts.
``solum app logs <NAME|UUID> [--wf-id <wf-id>]``
Show the logs of an application for all the workflows.
``wf-id`` is optional flag which can be used to pass in ID of one of
the existing workflows. If provided, the logs only for that workflow
are displayed.
``solum app scale <APP_NAME|UUID> <target>``
``solum workflow list <APP_NAME|UUID>``
List all application workflows.
``solum workflow show <APP_NAME|UUID> <WORKFLOW_ID|UUID>``
Print the details of a workflow.
``solum workflow logs <APP_NAME|UUID> <WORKFLOW_ID|UUID>``
List all the logs of a given workflow.
**SOON TO BE DEPRECATED:**
``solum oldapp create``
Register a new application with Solum.
.. code-block:: console
solum oldapp create [--plan-file <PLANFILE>] [--git-url <GIT_URL>]
[--lp <LANGUAGEPACK>] [--run-cmd <RUN_CMD>]
[--unittest-cmd <UNITTEST_CMD>]
[--name <NAME>] [--port <PORT>]
[--param-file <PARAMFILE>]
[--desc <DESCRIPTION>]
[--setup-trigger]
[--private-repo]
[--trigger-workflow <WORKFLOW>]

View File

@ -1,83 +0,0 @@
# 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 os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'openstackdocstheme',
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
copyright = u'2013, OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
# html_static_path = ['static']
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
#html_theme = 'nature'
html_theme = 'openstackdocs'
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/python-solumclient'
openstackdocs_bug_project = 'python-solumclient'
openstackdocs_bug_tag = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'python-solumclientdoc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'python-solumclient.tex',
u'python-solumclient Documentation',
u'OpenStack Foundation', 'manual'),
]
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}

View File

@ -1,12 +0,0 @@
================================
python-solumclient documentation
================================
This is a client for the OpenStack Application Lifecycle Management API.
There's a Python API (the :mod:`solumclient` module) and a
:doc:`command-line script <cli/solum>` (installed as :program:`solum`).
.. toctree::
:maxdepth: 2
cli/index

View File

@ -1,6 +0,0 @@
---
upgrade:
- |
Python 2.7 support has been dropped. Last release of python-solumclient
to support python 2.7 is OpenStack Train. The minimum version of Python now
supported by python-solumclient is Python 3.6.

View File

@ -1,22 +0,0 @@
# Requirements lower bounds listed here are our best effort to keep them up to
# date but we do not test them so no guarantee of having them all correct. If
# you find any incorrect lower bounds, let us know or propose a fix.
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
Babel!=2.4.0,>=2.3.4 # BSD
oslo.i18n>=3.15.3 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0
httplib2>=0.18.1 # MIT
iso8601>=0.1.11 # MIT
jsonschema>=3.2.0 # MIT
requests>=2.14.2 # Apache-2.0
python-keystoneclient>=3.8.0 # Apache-2.0
PyYAML>=3.13 # MIT
stevedore>=2.0.1 # Apache-2.0
PrettyTable>=0.7.2 # BSD
debtcollector>=1.2.0 # Apache-2.0

View File

@ -1,46 +0,0 @@
[metadata]
name = python-solumclient
summary = Client library for Solum API
description-file =
README.rst
author = OpenStack
author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/python-solumclient/latest/
python-requires = >=3.6
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
[files]
packages =
solumclient
scripts =
contrib/setup-tools/solum-app-setup.py
[entry_points]
console_scripts =
solum = solumclient.solum:main
[compile_catalog]
directory = solumclient/locale
domain = solumclient
[update_catalog]
domain = solumclient
output_dir = solumclient/locale
input_file = solumclient/locale/solumclient.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = solumclient/locale/solumclient.pot

View File

@ -1,20 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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 setuptools
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)

View File

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# 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 pbr.version
__version__ = pbr.version.VersionInfo(
'python-solumclient').version_string()

View File

@ -1,63 +0,0 @@
# Copyright 2014 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import client as api_client
from solumclient.common import auth
from solumclient.common import client
API_NAME = 'builder'
VERSION_MAP = {
'1': 'solumclient.builder.v1.client.Client',
}
def Client(version, **kwargs):
client_class = api_client.BaseClient.get_class(API_NAME, version,
VERSION_MAP)
keystone_auth = auth.KeystoneAuthPlugin(
username=kwargs.get('username'),
password=kwargs.get('password'),
tenant_name=kwargs.get('tenant_name'),
token=kwargs.get('token'),
auth_url=kwargs.get('auth_url'),
endpoint=kwargs.get('endpoint'))
http_client = client.HTTPClient(keystone_auth)
return client_class(http_client)
def get_client(api_version, **kwargs):
"""Get an authtenticated client.
This is based on the credentials in the keyword args.
:param api_version: the API version to use
:param kwargs: keyword args containing credentials, either:
* os_auth_token: pre-existing token to re-use
* endpoint: solum API endpoint
or:
* os_username: name of user
* os_password: user's password
* os_auth_url: endpoint to authenticate against
* os_tenant_name: name of tenant
"""
cli_kwargs = {
'username': kwargs.get('os_username'),
'password': kwargs.get('os_password'),
'tenant_name': kwargs.get('os_tenant_name'),
'token': kwargs.get('os_auth_token'),
'auth_url': kwargs.get('os_auth_url'),
'endpoint': kwargs.get('solum_url')
}
return Client(api_version, **cli_kwargs)

View File

@ -1,27 +0,0 @@
# Copyright 2014 - Noorul Islam K M
#
# 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.
from solumclient.builder.v1 import image
from solumclient.common.apiclient import client
class Client(client.BaseClient):
"""Client for the Solum v1 API."""
service_type = "image_builder"
def __init__(self, http_client, extensions=None):
"""Initialize a new client for the Builder v1 API."""
super(Client, self).__init__(http_client, extensions)
self.images = image.ImageManager(self)

View File

@ -1,41 +0,0 @@
# Copyright 2014 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import base as apiclient_base
from solumclient.common import base as solum_base
class Image(apiclient_base.Resource):
def __repr__(self):
return "<Image %s>" % self._info
class ImageManager(solum_base.CrudManager, solum_base.FindMixin):
resource_class = Image
collection_key = 'images'
key = 'image'
def create(self, **kwargs):
return super(ImageManager, self).create(base_url="/v1", **kwargs)
def get(self, **kwargs):
return super(ImageManager, self).get(base_url="/v1", **kwargs)
def list(self, **kwargs):
return super(ImageManager, self).list(base_url="/v1", **kwargs)
def find(self, **kwargs):
name_or_uuid = kwargs['name_or_id']
return super(ImageManager, self).get(base_url="/v1",
image_id=name_or_uuid)

View File

@ -1,235 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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 contextlib
import time
from keystoneclient import adapter
from oslo_utils import strutils
from solumclient.common.apiclient import client as api_client
from solumclient.common import auth
from solumclient.common import exc
API_NAME = 'solum'
VERSION_MAP = {
'1': 'solumclient.v1.client.Client',
}
def Client(version, *args, **kwargs):
client_class = api_client.BaseClient.get_class(API_NAME, version,
VERSION_MAP)
kwargs['token'] = kwargs.get('token') or kwargs.get('auth_token')
return client_class(version, *args, **kwargs)
def _adjust_params(kwargs):
timeout = kwargs.get('timeout')
if timeout is not None:
timeout = int(timeout)
if timeout <= 0:
timeout = None
insecure = strutils.bool_from_string(kwargs.get('insecure'))
verify = kwargs.get('verify')
if verify is None:
if insecure:
verify = False
else:
verify = kwargs.get('cacert') or True
cert = kwargs.get('cert_file')
key = kwargs.get('key_file')
if cert and key:
cert = cert, key
return {'verify': verify, 'cert': cert, 'timeout': timeout}
def get_client(version, **kwargs):
"""Get an authenticated client, based on the credentials in the kwargs.
:param api_version: the API version to use ('1')
:param kwargs: keyword args containing credentials, either:
* session: a keystoneauth/keystoneclient session object
* service_type: The default service_type for URL discovery
* service_name: The default service_name for URL discovery
* interface: The default interface for URL discovery
(Default: public)
* region_name: The default region_name for URL discovery
* endpoint_override: Always use this endpoint URL for requests
for this solumclient
* auth: An auth plugin to use instead of the session one
* user_agent: The User-Agent string to set
(Default is python-solumclient)
* connect_retries: the maximum number of retries that should be
attempted for connection errors
* logger: A logging object
or (DEPRECATED):
* os_token: pre-existing token to re-use
* os_endpoint: Solum API endpoint
or (DEPRECATED):
* os_username: name of user
* os_password: user's password
* os_user_id: user's id
* os_user_domain_id: the domain id of the user
* os_user_domain_name: the domain name of the user
* os_project_id: the user project id
* os_tenant_id: V2 alternative to os_project_id
* os_project_name: the user project name
* os_tenant_name: V2 alternative to os_project_name
* os_project_domain_name: domain name for the user project
* os_project_domain_id: domain id for the user project
* os_auth_url: endpoint to authenticate against
* os_cert|os_cacert: path of CA TLS certificate
* os_key: SSL private key
* insecure: allow insecure SSL (no cert verification)
"""
endpoint = kwargs.get('os_endpoint')
cli_kwargs = {
'username': kwargs.get('os_username'),
'password': kwargs.get('os_password'),
'tenant_id': (kwargs.get('os_tenant_id')
or kwargs.get('os_project_id')),
'tenant_name': (kwargs.get('os_tenant_name')
or kwargs.get('os_project_name')),
'auth_url': kwargs.get('os_auth_url'),
'region_name': kwargs.get('os_region_name'),
'service_type': kwargs.get('os_service_type'),
'endpoint_type': kwargs.get('os_endpoint_type'),
'cacert': kwargs.get('os_cacert'),
'cert_file': kwargs.get('os_cert'),
'key_file': kwargs.get('os_key'),
'token': kwargs.get('os_token') or kwargs.get('os_auth_token'),
'user_domain_name': kwargs.get('os_user_domain_name'),
'user_domain_id': kwargs.get('os_user_domain_id'),
'project_domain_name': kwargs.get('os_project_domain_name'),
'project_domain_id': kwargs.get('os_project_domain_id'),
}
cli_kwargs.update(kwargs)
cli_kwargs.update(_adjust_params(cli_kwargs))
return Client(version, endpoint, **cli_kwargs)
def get_auth_plugin(**kwargs):
auth_plugin = auth.KeystoneAuthPlugin(
auth_url=kwargs.get('auth_url'),
service_type=kwargs.get('service_type'),
token=kwargs.get('token'),
endpoint_type=kwargs.get('endpoint_type'),
endpoint=kwargs.get('endpoint'),
cacert=kwargs.get('cacert'),
tenant_id=kwargs.get('project_id') or kwargs.get('tenant_id'),
username=kwargs.get('username'),
password=kwargs.get('password'),
tenant_name=kwargs.get('tenant_name') or kwargs.get('project_name'),
user_domain_name=kwargs.get('user_domain_name'),
user_domain_id=kwargs.get('user_domain_id'),
project_domain_name=kwargs.get('project_domain_name'),
project_domain_id=kwargs.get('project_domain_id')
)
return auth_plugin
LEGACY_OPTS = ('auth_plugin', 'auth_url', 'token', 'insecure', 'cacert',
'tenant_id', 'project_id', 'username', 'password',
'project_name', 'tenant_name',
'user_domain_name', 'user_domain_id',
'project_domain_name', 'project_domain_id',
'key_file', 'cert_file', 'verify', 'timeout', 'cert')
def construct_http_client(**kwargs):
kwargs = kwargs.copy()
if kwargs.get('session') is not None:
# Drop legacy options
for opt in LEGACY_OPTS:
kwargs.pop(opt, None)
service_type_get = kwargs.pop('service_type',
'application_deployment')
return SessionClient(
session=kwargs.pop('session'),
service_type=service_type_get or 'application_deployment',
interface=kwargs.pop('interface', kwargs.pop('endpoint_type',
'publicURL')),
region_name=kwargs.pop('region_name', None),
user_agent=kwargs.pop('user_agent', 'python-solumclient'),
auth=kwargs.get('auth', None),
timings=kwargs.pop('timings', None),
**kwargs)
else:
return api_client.BaseClient(api_client.HTTPClient(
auth_plugin=kwargs.get('auth_plugin'),
region_name=kwargs.get('region_name'),
endpoint_type=kwargs.get('endpoint_type'),
original_ip=kwargs.get('original_ip'),
verify=kwargs.get('verify'),
cert=kwargs.get('cert'),
timeout=kwargs.get('timeout'),
timings=kwargs.get('timings'),
keyring_saver=kwargs.get('keyring_saver'),
debug=kwargs.get('debug'),
user_agent=kwargs.get('user_agent'),
http=kwargs.get('http')
))
@contextlib.contextmanager
def record_time(times, enabled, *args):
"""Record the time of a specific action.
:param times: A list of tuples holds time data.
:type times: list
:param enabled: Whether timing is enabled.
:type enabled: bool
:param args: Other data to be stored besides time data, these args
will be joined to a string.
"""
if not enabled:
yield
else:
start = time.time()
yield
end = time.time()
times.append((' '.join(args), start, end))
class SessionClient(adapter.LegacyJsonAdapter):
def __init__(self, *args, **kwargs):
self.times = []
self.timings = kwargs.pop('timings', False)
super(SessionClient, self).__init__(*args, **kwargs)
def request(self, url, method, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
raise_exc = kwargs.pop('raise_exc', True)
with record_time(self.times, self.timings, method, url):
resp, body = super(SessionClient, self).request(url,
method,
raise_exc=False,
**kwargs)
if raise_exc and resp.status_code >= 400:
raise exc.from_response(resp, body)
return resp

View File

@ -1,216 +0,0 @@
# Copyright 2013 OpenStack Foundation
# Copyright 2013 Spanish National Research Council.
# All Rights Reserved.
#
# 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.
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
import abc
import argparse
import os
from stevedore import extension
from solumclient.common.apiclient import exceptions
_discovered_plugins = {}
def discover_auth_systems():
"""Discover the available auth-systems.
This won't take into account the old style auth-systems.
"""
global _discovered_plugins
_discovered_plugins = {}
def add_plugin(ext):
_discovered_plugins[ext.name] = ext.plugin
ep_namespace = "solumclient.common.apiclient.auth"
mgr = extension.ExtensionManager(ep_namespace)
mgr.map(add_plugin)
def load_auth_system_opts(parser):
"""Load options needed by the available auth-systems into a parser.
This function will try to populate the parser with options from the
available plugins.
"""
group = parser.add_argument_group("Common auth options")
BaseAuthPlugin.add_common_opts(group)
for name, auth_plugin in _discovered_plugins.items():
group = parser.add_argument_group(
"Auth-system '%s' options" % name,
conflict_handler="resolve")
auth_plugin.add_opts(group)
def load_plugin(auth_system):
try:
plugin_class = _discovered_plugins[auth_system]
except KeyError:
raise exceptions.AuthSystemNotFound(auth_system)
return plugin_class(auth_system=auth_system)
def load_plugin_from_args(args):
"""Load required plugin and populate it with options.
Try to guess auth system if it is not specified. Systems are tried in
alphabetical order.
:type args: argparse.Namespace
:raises: AuthPluginOptionsMissing
"""
auth_system = args.os_auth_system
if auth_system:
plugin = load_plugin(auth_system)
plugin.parse_opts(args)
plugin.sufficient_options()
return plugin
for plugin_auth_system in sorted(_discovered_plugins.keys()):
plugin_class = _discovered_plugins[plugin_auth_system]
plugin = plugin_class()
plugin.parse_opts(args)
try:
plugin.sufficient_options()
except exceptions.AuthPluginOptionsMissing:
continue
return plugin
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
class BaseAuthPlugin(object, metaclass=abc.ABCMeta):
"""Base class for authentication plugins.
An authentication plugin needs to override at least the authenticate
method to be a valid plugin.
"""
auth_system = None
opt_names = []
common_opt_names = [
"auth_system",
"username",
"password",
"tenant_name",
"token",
"auth_url",
]
def __init__(self, auth_system=None, **kwargs):
self.auth_system = auth_system or self.auth_system
self.opts = dict((name, kwargs.get(name))
for name in self.opt_names)
@staticmethod
def _parser_add_opt(parser, opt):
"""Add an option to parser in two variants.
:param opt: option name (with underscores)
"""
dashed_opt = opt.replace("_", "-")
env_var = "OS_%s" % opt.upper()
arg_default = os.environ.get(env_var, "")
arg_help = "Defaults to env[%s]." % env_var
parser.add_argument(
"--os-%s" % dashed_opt,
metavar="<%s>" % dashed_opt,
default=arg_default,
help=arg_help)
parser.add_argument(
"--os_%s" % opt,
metavar="<%s>" % dashed_opt,
help=argparse.SUPPRESS)
@classmethod
def add_opts(cls, parser):
"""Populate the parser with the options for this plugin."""
for opt in cls.opt_names:
# use `BaseAuthPlugin.common_opt_names` since it is never
# changed in child classes
if opt not in BaseAuthPlugin.common_opt_names:
cls._parser_add_opt(parser, opt)
@classmethod
def add_common_opts(cls, parser):
"""Add options that are common for several plugins."""
for opt in cls.common_opt_names:
cls._parser_add_opt(parser, opt)
@staticmethod
def get_opt(opt_name, args):
"""Return option name and value.
:param opt_name: name of the option, e.g., "username"
:param args: parsed arguments
"""
return (opt_name, getattr(args, "os_%s" % opt_name, None))
def parse_opts(self, args):
"""Parse the actual auth-system options if any.
This method is expected to populate the attribute `self.opts` with a
dict containing the options and values needed to make authentication.
"""
self.opts.update(dict(self.get_opt(opt_name, args)
for opt_name in self.opt_names))
def authenticate(self, http_client):
"""Authenticate using plugin defined method.
The method usually analyses `self.opts` and performs
a request to authentication server.
:param http_client: client object that needs authentication
:type http_client: HTTPClient
:raises: AuthorizationFailure
"""
self.sufficient_options()
self._do_authenticate(http_client)
@abc.abstractmethod
def _do_authenticate(self, http_client):
"""Protected method for authentication."""
def sufficient_options(self):
"""Check if all required options are present.
:raises: AuthPluginOptionsMissing
"""
missing = [opt
for opt in self.opt_names
if not self.opts.get(opt)]
if missing:
raise exceptions.AuthPluginOptionsMissing(missing)
@abc.abstractmethod
def token_and_endpoint(self, endpoint_type, service_type):
"""Return token and endpoint.
:param service_type: Service type of the endpoint
:type service_type: string
:param endpoint_type: Type of endpoint.
Possible values: public or publicURL,
internal or internalURL,
admin or adminURL
:type endpoint_type: string
:returns: tuple of token and endpoint strings
:raises: EndpointException
"""

View File

@ -1,517 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2012 Grid Dynamics
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# 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.
"""
Base utilities to build API operation managers and objects on top of.
"""
# E1102: %s is not callable
# pylint: disable=E1102
import abc
import copy
from urllib import parse
from solumclient.common.apiclient import exceptions
from solumclient.i18n import _
from oslo_utils import strutils
def getid(obj):
"""Return id if argument is a Resource.
Abstracts the common pattern of allowing both an object or an object's ID
(UUID) as a parameter when dealing with relationships.
"""
try:
if obj.uuid:
return obj.uuid
except AttributeError:
pass
try:
return obj.id
except AttributeError:
return obj
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
class HookableMixin(object):
"""Mixin so classes can register and run hooks."""
_hooks_map = {}
@classmethod
def add_hook(cls, hook_type, hook_func):
"""Add a new hook of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param hook_func: hook function
"""
if hook_type not in cls._hooks_map:
cls._hooks_map[hook_type] = []
cls._hooks_map[hook_type].append(hook_func)
@classmethod
def run_hooks(cls, hook_type, *args, **kwargs):
"""Run all hooks of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param args: args to be passed to every hook function
:param kwargs: kwargs to be passed to every hook function
"""
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
hook_func(*args, **kwargs)
class BaseManager(HookableMixin):
"""Basic manager type providing common operations.
Managers interact with a particular type of API (servers, flavors, images,
etc.) and provide CRUD operations for them.
"""
resource_class = None
def __init__(self, client):
"""Initializes BaseManager with `client`.
:param client: instance of BaseClient descendant for HTTP requests
"""
super(BaseManager, self).__init__()
self.client = client
def _list(self, url, response_key=None, obj_class=None, json=None):
"""List the collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'. If response_key is None - all response body
will be used.
:param obj_class: class for constructing the returned objects
(self.resource_class will be used by default)
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
"""
if json:
body = self.client.post(url, json=json).json()
else:
body = self.client.get(url).json()
if obj_class is None:
obj_class = self.resource_class
data = body[response_key] if response_key is not None else body
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
try:
data = data['values']
except (KeyError, TypeError):
pass
return [obj_class(self, res, loaded=True) for res in data if res]
def _get(self, url, response_key=None):
"""Get an object from collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'server'. If response_key is None - all response body
will be used.
"""
body = self.client.get(url).json()
data = body[response_key] if response_key is not None else body
return self.resource_class(self, data, loaded=True)
def _head(self, url):
"""Retrieve request headers for an object.
:param url: a partial URL, e.g., '/servers'
"""
resp = self.client.head(url)
return resp.status_code == 204
def _post(self, url, json, response_key=None, return_raw=False):
"""Create an object.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'server'. If response_key is None - all response body
will be used.
:param return_raw: flag to force returning raw JSON instead of
Python object of self.resource_class
"""
body = self.client.post(url, json=json).json()
data = body[response_key] if response_key is not None else body
if return_raw:
return data
return self.resource_class(self, data)
def _put(self, url, json=None, response_key=None):
"""Update an object with PUT method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'. If response_key is None - all response body
will be used.
"""
resp = self.client.put(url, json=json)
# PUT requests may not return a body
if resp.content:
body = resp.json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _patch(self, url, json=None, response_key=None):
"""Update an object with PATCH method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'. If response_key is None - all response body
will be used.
"""
body = self.client.patch(url, json=json).json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _delete(self, url):
"""Delete an object.
:param url: a partial URL, e.g., '/servers/my-server'
"""
return self.client.delete(url)
class ManagerWithFind(BaseManager, metaclass=abc.ABCMeta):
"""Manager with additional `find()`/`findall()` methods."""
@abc.abstractmethod
def list(self):
pass
def find(self, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch()
else:
return matches[0]
def findall(self, **kwargs):
"""Find all items with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
found = []
searches = kwargs.items()
for obj in self.list():
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
found.append(obj)
except AttributeError:
continue
return found
class CrudManager(BaseManager):
"""Base manager class for manipulating entities.
Children of this class are expected to define a `collection_key` and `key`.
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
objects containing a list of member resources (e.g. `{'entities': [{},
{}, {}]}`).
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
refer to an individual member of the collection.
"""
collection_key = None
key = None
def build_url(self, base_url=None, **kwargs):
"""Builds a resource URL for the given kwargs.
Given an example collection where `collection_key = 'entities'` and
`key = 'entity'`, the following URL's could be generated.
By default, the URL will represent a collection of entities, e.g.::
/entities
If kwargs contains an `entity_id`, then the URL will represent a
specific member, e.g.::
/entities/{entity_id}
:param base_url: if provided, the generated URL will be appended to it
"""
url = base_url if base_url is not None else ''
url += '/%s' % self.collection_key
# do we have a specific entity?
entity_id = kwargs.get('%s_id' % self.key)
if entity_id is not None:
url += '/%s' % entity_id
return url
def _filter_kwargs(self, kwargs):
"""Drop null values and handle ids."""
for key, ref in kwargs.copy().items():
if ref is None:
kwargs.pop(key)
else:
if isinstance(ref, Resource):
kwargs.pop(key)
kwargs['%s_id' % key] = getid(ref)
return kwargs
def create(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._post(
self.build_url(**kwargs),
{self.key: kwargs},
self.key)
def get(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._get(
self.build_url(**kwargs),
self.key)
def head(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._head(self.build_url(**kwargs))
def list(self, base_url=None, **kwargs):
"""List the collection.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
def put(self, base_url=None, **kwargs):
"""Update an element.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._put(self.build_url(base_url=base_url, **kwargs))
def update(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
params = kwargs.copy()
params.pop('%s_id' % self.key)
return self._patch(
self.build_url(**kwargs),
{self.key: params},
self.key)
def delete(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._delete(
self.build_url(**kwargs))
def find(self, base_url=None, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
rl = self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
num = len(rl)
if num == 0:
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(404, msg)
elif num > 1:
raise exceptions.NoUniqueMatch
else:
return rl[0]
class Extension(HookableMixin):
"""Extension descriptor."""
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
manager_class = None
def __init__(self, name, module):
super(Extension, self).__init__()
self.name = name
self.module = module
self._parse_extension_module()
def _parse_extension_module(self):
self.manager_class = None
for attr_name, attr_value in self.module.__dict__.items():
if attr_name in self.SUPPORTED_HOOKS:
self.add_hook(attr_name, attr_value)
else:
try:
if issubclass(attr_value, BaseManager):
self.manager_class = attr_value
except TypeError:
pass
def __repr__(self):
return "<Extension '%s'>" % self.name
class Resource(object):
"""Base class for OpenStack resources (tenant, user, etc.).
This is pretty much just a bag for attributes.
"""
HUMAN_ID = False
NAME_ATTR = 'name'
def __init__(self, manager, info, loaded=False):
"""Populate and bind to a manager.
:param manager: BaseManager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
"""
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
def __repr__(self):
reprkeys = sorted(k
for k in self.__dict__.keys()
if k[0] != '_' and k != 'manager')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
@property
def human_id(self):
"""Human-readable ID which can be used for bash completion."""
if self.HUMAN_ID:
name = getattr(self, self.NAME_ATTR, None)
if name is not None:
return strutils.to_slug(name)
return None
def _add_details(self, info):
for (k, v) in info.items():
try:
setattr(self, k, v)
self._info[k] = v
except AttributeError:
# In this case we already defined the attribute on the class
pass
def __getattr__(self, k):
if k not in self.__dict__:
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
raise AttributeError(k)
else:
return self.__dict__[k]
def get(self):
"""Support for lazy loading details.
Some clients, such as novaclient have the option to lazy load the
details, details which can be loaded with this function.
"""
# set_loaded() first ... so if we have to bail, we know we tried.
self.set_loaded(True)
if not hasattr(self.manager, 'get'):
return
new = self.manager.get(self.id)
if new:
self._add_details(new._info)
def __eq__(self, other):
if not isinstance(other, Resource):
return NotImplemented
# two resources of different types are not equal
if not isinstance(other, self.__class__):
return False
if hasattr(self, 'id') and hasattr(other, 'id'):
return self.id == other.id
return self._info == other._info
def __ne__(self, other):
return not self.__eq__(other)
def is_loaded(self):
return self._loaded
def set_loaded(self, val):
self._loaded = val
def to_dict(self):
return copy.deepcopy(self._info)

View File

@ -1,377 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2011 Piston Cloud Computing, Inc.
# Copyright 2013 Alessio Ababilov
# Copyright 2013 Grid Dynamics
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# 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.
"""
OpenStack Client interface. Handles the REST calls and responses.
"""
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
import time
try:
import simplejson as json
except ImportError:
import json
from oslo_log import log as logging
from oslo_utils import importutils
import requests
from solumclient.common.apiclient import exceptions
from solumclient.i18n import _
_logger = logging.getLogger(__name__)
class HTTPClient(object):
"""This client handles sending HTTP requests to OpenStack servers.
Features:
- share authentication information between several clients to different
services (e.g., for compute and image clients);
- reissue authentication request for expired tokens;
- encode/decode JSON bodies;
- raise exceptions on HTTP errors;
- pluggable authentication;
- store authentication information in a keyring;
- store time spent for requests;
- register clients for particular services, so one can use
`http_client.identity` or `http_client.compute`;
- log requests and responses in a format that is easy to copy-and-paste
into terminal and send the same request with curl.
"""
user_agent = "solumclient.common.apiclient"
def __init__(self,
auth_plugin,
region_name=None,
endpoint_type="publicURL",
original_ip=None,
verify=True,
cert=None,
timeout=None,
timings=False,
keyring_saver=None,
debug=False,
user_agent=None,
http=None):
self.auth_plugin = auth_plugin
self.endpoint_type = endpoint_type
self.region_name = region_name
self.original_ip = original_ip
self.timeout = timeout
self.verify = verify
self.cert = cert
self.keyring_saver = keyring_saver
self.debug = debug
self.user_agent = user_agent or self.user_agent
self.times = [] # [("item", starttime, endtime), ...]
self.timings = timings
# requests within the same session can reuse TCP connections from pool
self.http = http or requests.Session()
self.cached_token = None
def _http_log_req(self, method, url, kwargs):
if not self.debug:
return
string_parts = [
"curl -i",
"-X '%s'" % method,
"'%s'" % url,
]
for element in kwargs['headers']:
header = "-H '%s: %s'" % (element, kwargs['headers'][element])
string_parts.append(header)
_logger.debug("REQ: %s" % " ".join(string_parts))
if 'data' in kwargs:
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
def _http_log_resp(self, resp):
if not self.debug:
return
_logger.debug(
"RESP: [%s] %s\n",
resp.status_code,
resp.headers)
if resp._content_consumed:
_logger.debug(
"RESP BODY: %s\n",
resp.text)
def serialize(self, kwargs):
if kwargs.get('json') is not None:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs['json'])
try:
del kwargs['json']
except KeyError:
pass
def get_timings(self):
return self.times
def reset_timings(self):
self.times = []
def request(self, method, url, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
setting headers, JSON encoding/decoding, and error handling.
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument
"""
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs["headers"]["User-Agent"] = self.user_agent
if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
self.original_ip, self.user_agent)
if self.timeout is not None:
kwargs.setdefault("timeout", self.timeout)
kwargs.setdefault("verify", self.verify)
if self.cert is not None:
kwargs.setdefault("cert", self.cert)
self.serialize(kwargs)
self._http_log_req(method, url, kwargs)
if self.timings:
start_time = time.time()
resp = self.http.request(method, url, **kwargs)
if self.timings:
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
self._http_log_resp(resp)
if resp.status_code >= 400:
_logger.debug(
"Request returned failure status: %s",
resp.status_code)
raise exceptions.from_response(resp, method, url)
return resp
@staticmethod
def concat_url(endpoint, url):
"""Concatenate endpoint and final URL.
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
"http://keystone/v2.0/tokens".
:param endpoint: the base URL
:param url: the final URL
"""
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
def client_request(self, client, method, url, **kwargs):
"""Send an http request using `client`'s endpoint and specified `url`.
If request was rejected as unauthorized (possibly because the token is
expired), issue one authorization attempt and send the request once
again.
:param client: instance of BaseClient descendant
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
`HTTPClient.request`
"""
filter_args = {
"endpoint_type": client.endpoint_type or self.endpoint_type,
"service_type": client.service_type,
}
token, endpoint = (self.cached_token, client.cached_endpoint)
just_authenticated = False
if not (token and endpoint):
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
except exceptions.EndpointException:
pass
if not (token and endpoint):
self.authenticate()
just_authenticated = True
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
if not (token and endpoint):
raise exceptions.AuthorizationFailure(
_("Cannot find endpoint or token for request"))
old_token_endpoint = (token, endpoint)
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
self.cached_token = token
client.cached_endpoint = endpoint
# Perform the request once. If we get Unauthorized, then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.
try:
return self.request(
method, self.concat_url(endpoint, url), **kwargs)
except exceptions.ConnectionRefused:
print('ERROR: Cannot connect to %s. Make sure it ' +
'is valid: %s.' % endpoint)
raise
except exceptions.EndpointException:
print('ERROR: Service catalog endpoint is invalid. ' +
'Please check your ' +
'endpoint is valid: %s.' % endpoint)
raise
except requests.ConnectionError:
print('ERROR: Unable to connect to endpoint. ' +
'Make sure that ' +
'your endpoint (%s) is valid.' % endpoint)
raise
except exceptions.Unauthorized as unauth_ex:
if just_authenticated:
raise
self.cached_token = None
client.cached_endpoint = None
self.authenticate()
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
except exceptions.EndpointException:
raise unauth_ex
if (not (token and endpoint) or
old_token_endpoint == (token, endpoint)):
raise unauth_ex
self.cached_token = token
client.cached_endpoint = endpoint
kwargs["headers"]["X-Auth-Token"] = token
return self.request(
method, self.concat_url(endpoint, url), **kwargs)
def add_client(self, base_client_instance):
"""Add a new instance of :class:`BaseClient` descendant.
`self` will store a reference to `base_client_instance`.
Example:
>>> def test_clients():
... from keystoneclient.auth import keystone
... from openstack.apiclient import client
... auth = keystone.KeystoneAuthPlugin(
... username="user", password="pass", tenant_name="tenant",
... auth_url="http://auth:5000/v2.0")
... openstack_client = client.HTTPClient(auth)
... # create nova client
... from novaclient.v1_1 import client
... client.Client(openstack_client)
... # create keystone client
... from keystoneclient.v2_0 import client
... client.Client(openstack_client)
... # use them
... openstack_client.identity.tenants.list()
... openstack_client.compute.servers.list()
"""
service_type = base_client_instance.service_type
if service_type and not hasattr(self, service_type):
setattr(self, service_type, base_client_instance)
def authenticate(self):
self.auth_plugin.authenticate(self)
# Store the authentication results in the keyring for later requests
if self.keyring_saver:
self.keyring_saver.save(self)
class BaseClient(object):
"""Top-level object to access the OpenStack API.
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
will handle a bunch of issues such as authentication.
"""
service_type = None
endpoint_type = None # "publicURL" will be used
cached_endpoint = None
def __init__(self, http_client, extensions=None):
self.http_client = http_client
http_client.add_client(self)
# Add in any extensions...
if extensions:
for extension in extensions:
if extension.manager_class:
setattr(self, extension.name,
extension.manager_class(self))
def client_request(self, method, url, **kwargs):
return self.http_client.client_request(
self, method, url, **kwargs)
def head(self, url, **kwargs):
return self.client_request("HEAD", url, **kwargs)
def get(self, url, **kwargs):
return self.client_request("GET", url, **kwargs)
def post(self, url, **kwargs):
return self.client_request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.client_request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.client_request("DELETE", url, **kwargs)
def patch(self, url, **kwargs):
return self.client_request("PATCH", url, **kwargs)
@staticmethod
def get_class(api_name, version, version_map):
"""Returns the client class for the requested API version
:param api_name: the name of the API, e.g. 'compute', 'image', etc
:param version: the requested API version
:param version_map: a dict of client classes keyed by version
:rtype: a client class for the requested API version
"""
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
msg = _("Invalid %(api_name)s client version '%(version)s'. "
"Must be one of: %(version_map)s") % {
'api_name': api_name,
'version': version,
'version_map': ', '.join(version_map.keys())}
raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path)

View File

@ -1,463 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 Nebula, Inc.
# Copyright 2013 Alessio Ababilov
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# 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.
"""
Exception definitions.
"""
import inspect
import sys
from solumclient.i18n import _
class ClientException(Exception):
"""The base exception class for all exceptions this library raises."""
pass
class MissingArgs(ClientException):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = _("Missing arguments: %s") % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
class ValidationError(ClientException):
"""Error in validation on API client side."""
pass
class UnsupportedVersion(ClientException):
"""User is trying to use an unsupported version of the API."""
pass
class CommandError(ClientException):
"""Error in CLI tool."""
pass
class AuthorizationFailure(ClientException):
"""Cannot authorize API client."""
pass
class ConnectionRefused(ClientException):
"""Cannot connect to API service."""
pass
class AuthPluginOptionsMissing(AuthorizationFailure):
"""Auth plugin misses some options."""
def __init__(self, opt_names):
super(AuthPluginOptionsMissing, self).__init__(
_("Authentication failed. Missing options: %s") %
", ".join(opt_names))
self.opt_names = opt_names
class AuthSystemNotFound(AuthorizationFailure):
"""User has specified an AuthSystem that is not installed."""
def __init__(self, auth_system):
super(AuthSystemNotFound, self).__init__(
_("AuthSystemNotFound: %s") % repr(auth_system))
self.auth_system = auth_system
class NoUniqueMatch(ClientException):
"""Multiple entities found instead of one."""
pass
class EndpointException(ClientException):
"""Something is rotten in Service Catalog."""
pass
class EndpointNotFound(EndpointException):
"""Could not find requested endpoint in Service Catalog."""
pass
class AmbiguousEndpoints(EndpointException):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
super(AmbiguousEndpoints, self).__init__(
_("AmbiguousEndpoints: %s") % repr(endpoints))
self.endpoints = endpoints
class HttpError(ClientException):
"""The base exception class for all HTTP exceptions."""
http_status = 0
message = _("HTTP Error")
def __init__(self, message=None, details=None,
response=None, request_id=None,
url=None, method=None, http_status=None):
self.http_status = http_status or self.http_status
self.message = message or self.message
self.details = details
self.request_id = request_id
self.response = response
self.url = url
self.method = method
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
if request_id:
formatted_string += " (Request-ID: %s)" % request_id
super(HttpError, self).__init__(formatted_string)
class HTTPRedirection(HttpError):
"""HTTP Redirection."""
message = _("HTTP Redirection")
class HTTPClientError(HttpError):
"""Client-side HTTP error.
Exception for cases in which the client seems to have erred.
"""
message = _("HTTP Client Error")
class HttpServerError(HttpError):
"""Server-side HTTP error.
Exception for cases in which the server is aware that it has
erred or is incapable of performing the request.
"""
message = _("HTTP Server Error")
class MultipleChoices(HTTPRedirection):
"""HTTP 300 - Multiple Choices.
Indicates multiple options for the resource that the client may follow.
"""
http_status = 300
message = _("Multiple Choices")
class BadRequest(HTTPClientError):
"""HTTP 400 - Bad Request.
The request cannot be fulfilled due to bad syntax.
"""
http_status = 400
message = _("Bad Request")
class Unauthorized(HTTPClientError):
"""HTTP 401 - Unauthorized.
Similar to 403 Forbidden, but specifically for use when authentication
is required and has failed or has not yet been provided.
"""
http_status = 401
message = _("Unauthorized")
class PaymentRequired(HTTPClientError):
"""HTTP 402 - Payment Required.
Reserved for future use.
"""
http_status = 402
message = _("Payment Required")
class Forbidden(HTTPClientError):
"""HTTP 403 - Forbidden.
The request was a valid request, but the server is refusing to respond
to it.
"""
http_status = 403
message = _("Forbidden")
class NotFound(HTTPClientError):
"""HTTP 404 - Not Found.
The requested resource could not be found but may be available again
in the future.
"""
http_status = 404
message = _("Not Found")
class MethodNotAllowed(HTTPClientError):
"""HTTP 405 - Method Not Allowed.
A request was made of a resource using a request method not supported
by that resource.
"""
http_status = 405
message = _("Method Not Allowed")
class NotAcceptable(HTTPClientError):
"""HTTP 406 - Not Acceptable.
The requested resource is only capable of generating content not
acceptable according to the Accept headers sent in the request.
"""
http_status = 406
message = _("Not Acceptable")
class ProxyAuthenticationRequired(HTTPClientError):
"""HTTP 407 - Proxy Authentication Required.
The client must first authenticate itself with the proxy.
"""
http_status = 407
message = _("Proxy Authentication Required")
class RequestTimeout(HTTPClientError):
"""HTTP 408 - Request Timeout.
The server timed out waiting for the request.
"""
http_status = 408
message = _("Request Timeout")
class Conflict(HTTPClientError):
"""HTTP 409 - Conflict.
Indicates that the request could not be processed because of conflict
in the request, such as an edit conflict.
"""
http_status = 409
message = _("Conflict")
class Gone(HTTPClientError):
"""HTTP 410 - Gone.
Indicates that the resource requested is no longer available and will
not be available again.
"""
http_status = 410
message = _("Gone")
class LengthRequired(HTTPClientError):
"""HTTP 411 - Length Required.
The request did not specify the length of its content, which is
required by the requested resource.
"""
http_status = 411
message = _("Length Required")
class PreconditionFailed(HTTPClientError):
"""HTTP 412 - Precondition Failed.
The server does not meet one of the preconditions that the requester
put on the request.
"""
http_status = 412
message = _("Precondition Failed")
class RequestEntityTooLarge(HTTPClientError):
"""HTTP 413 - Request Entity Too Large.
The request is larger than the server is willing or able to process.
"""
http_status = 413
message = _("Request Entity Too Large")
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
class RequestUriTooLong(HTTPClientError):
"""HTTP 414 - Request-URI Too Long.
The URI provided was too long for the server to process.
"""
http_status = 414
message = _("Request-URI Too Long")
class UnsupportedMediaType(HTTPClientError):
"""HTTP 415 - Unsupported Media Type.
The request entity has a media type which the server or resource does
not support.
"""
http_status = 415
message = _("Unsupported Media Type")
class RequestedRangeNotSatisfiable(HTTPClientError):
"""HTTP 416 - Requested Range Not Satisfiable.
The client has asked for a portion of the file, but the server cannot
supply that portion.
"""
http_status = 416
message = _("Requested Range Not Satisfiable")
class ExpectationFailed(HTTPClientError):
"""HTTP 417 - Expectation Failed.
The server cannot meet the requirements of the Expect request-header field.
"""
http_status = 417
message = _("Expectation Failed")
class UnprocessableEntity(HTTPClientError):
"""HTTP 422 - Unprocessable Entity.
The request was well-formed but was unable to be followed due to semantic
errors.
"""
http_status = 422
message = _("Unprocessable Entity")
class InternalServerError(HttpServerError):
"""HTTP 500 - Internal Server Error.
A generic error message, given when no more specific message is suitable.
"""
http_status = 500
message = _("Internal Server Error")
# NotImplemented is a python keyword.
class HttpNotImplemented(HttpServerError):
"""HTTP 501 - Not Implemented.
The server either does not recognize the request method, or it lacks
the ability to fulfill the request.
"""
http_status = 501
message = _("Not Implemented")
class BadGateway(HttpServerError):
"""HTTP 502 - Bad Gateway.
The server was acting as a gateway or proxy and received an invalid
response from the upstream server.
"""
http_status = 502
message = _("Bad Gateway")
class ServiceUnavailable(HttpServerError):
"""HTTP 503 - Service Unavailable.
The server is currently unavailable.
"""
http_status = 503
message = _("Service Unavailable")
class GatewayTimeout(HttpServerError):
"""HTTP 504 - Gateway Timeout.
The server was acting as a gateway or proxy and did not receive a timely
response from the upstream server.
"""
http_status = 504
message = _("Gateway Timeout")
class HttpVersionNotSupported(HttpServerError):
"""HTTP 505 - HttpVersion Not Supported.
The server does not support the HTTP protocol version used in the request.
"""
http_status = 505
message = _("HTTP Version Not Supported")
# _code_map contains all the classes that have http_status attribute.
_code_map = dict(
(getattr(obj, 'http_status', None), obj)
for name, obj in vars(sys.modules[__name__]).items()
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
)
def from_response(response, method, url):
"""Returns an instance of :class:`HttpError` or subclass based on response.
:param response: instance of `requests.Response` class
:param method: HTTP method used for request
:param url: URL used for request
"""
req_id = response.headers.get("x-openstack-request-id")
# NOTE(hdd) true for older versions of nova and cinder
if not req_id:
req_id = response.headers.get("x-compute-request-id")
kwargs = {
"http_status": response.status_code,
"response": response,
"method": method,
"url": url,
"request_id": req_id,
}
if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"]
content_type = response.headers.get("Content-Type", "")
if content_type.startswith("application/json"):
try:
body = response.json()
except ValueError:
pass
else:
if isinstance(body, dict) and isinstance(body.get("error"), dict):
error = body["error"]
kwargs["message"] = error.get("message")
kwargs["details"] = error.get("details")
elif content_type.startswith("text/"):
kwargs["details"] = response.text
try:
cls = _code_map[response.status_code]
except KeyError:
if 500 <= response.status_code < 600:
cls = HttpServerError
elif 400 <= response.status_code < 500:
cls = HTTPClientError
else:
cls = HttpError
return cls(**kwargs)

View File

@ -1,173 +0,0 @@
# Copyright 2013 OpenStack Foundation
# All Rights Reserved.
#
# 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.
"""
A fake server that "responds" to API methods with pre-canned responses.
All of these responses come from the spec, so if for some reason the spec's
wrong the tests might raise AssertionError. I've indicated in comments the
places where actual behavior differs from the spec.
"""
# W0102: Dangerous default value %s as argument
# pylint: disable=W0102
import json
import requests
from urllib import parse
from solumclient.common.apiclient import client
def assert_has_keys(dct, required=None, optional=None):
required = required or []
optional = optional or []
for k in required:
try:
assert k in dct
except AssertionError:
extra_keys = set(dct.keys()).difference(set(required + optional))
raise AssertionError("found unexpected keys: %s" %
list(extra_keys))
class TestResponse(requests.Response):
"""Wrap requests.Response and provide a convenient initialization."""
def __init__(self, data):
super(TestResponse, self).__init__()
self._content_consumed = True
if isinstance(data, dict):
self.status_code = data.get('status_code', 200)
# Fake the text attribute to streamline Response creation
text = data.get('text', "")
if isinstance(text, (dict, list)):
self._content = json.dumps(text)
default_headers = {
"Content-Type": "application/json",
}
else:
self._content = text
default_headers = {}
if isinstance(self._content, str):
self._content = self._content.encode('utf-8', 'strict')
self.headers = data.get('headers') or default_headers
else:
self.status_code = data
def __eq__(self, other):
return (self.status_code == other.status_code and
self.headers == other.headers and
self._content == other._content)
def __ne__(self, other):
return not self.__eq__(other)
class FakeHTTPClient(client.HTTPClient):
def __init__(self, *args, **kwargs):
self.callstack = []
self.fixtures = kwargs.pop("fixtures", None) or {}
if not args and "auth_plugin" not in kwargs:
args = (None, )
super(FakeHTTPClient, self).__init__(*args, **kwargs)
def assert_called(self, method, url, body=None, pos=-1):
"""Assert than an API method was just called."""
expected = (method, url)
called = self.callstack[pos][0:2]
assert self.callstack, \
"Expected %s %s but no calls were made." % expected
assert expected == called, 'Expected %s %s; got %s %s' % (
expected + called)
if body is not None:
if self.callstack[pos][3] != body:
raise AssertionError('%r != %r' %
(self.callstack[pos][3], body))
def assert_called_anytime(self, method, url, body=None):
"""Assert than an API method was called anytime in the test."""
expected = (method, url)
assert self.callstack, \
"Expected %s %s but no calls were made." % expected
found = False
entry = None
for entry in self.callstack:
if expected == entry[0:2]:
found = True
break
assert found, 'Expected %s %s; got %s' % (method, url, self.callstack)
if body is not None:
assert entry[3] == body, "%s != %s" % (entry[3], body)
self.callstack = []
def clear_callstack(self):
self.callstack = []
def authenticate(self):
pass
def client_request(self, client, method, url, **kwargs):
# Check that certain things are called correctly
if method in ["GET", "DELETE"]:
assert "json" not in kwargs
# Note the call
self.callstack.append(
(method,
url,
kwargs.get("headers") or {},
kwargs.get("json") or kwargs.get("data")))
try:
fixture = self.fixtures[url][method]
except KeyError:
pass
else:
return TestResponse({"headers": fixture[0],
"text": fixture[1]})
# Call the method
args = parse.parse_qsl(parse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
resp = getattr(self, callback)(**kwargs)
if len(resp) == 3:
status, headers, body = resp
else:
status, body = resp
headers = {}
return TestResponse({
"status_code": status,
"text": body,
"headers": headers,
})

View File

@ -1,225 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth.identity import v3 as v3_auth
from keystoneclient import discover
from keystoneclient import exceptions as ks_exc
from keystoneclient import session
from oslo_utils import strutils
from urllib import parse as urlparse
from solumclient.common.apiclient import auth
from solumclient.common.apiclient import exceptions
from solumclient.common import exc
def _discover_auth_versions(session, auth_url):
# discover the API versions the server is supporting based on the
# given URL
v2_auth_url = None
v3_auth_url = None
try:
ks_discover = discover.Discover(session=session, auth_url=auth_url)
v2_auth_url = ks_discover.url_for('2.0')
v3_auth_url = ks_discover.url_for('3.0')
except ks_exc.DiscoveryFailure:
raise
except exceptions.ClientException:
# Identity service may not support discovery. In that case,
# try to determine version from auth_url
url_parts = urlparse.urlparse(auth_url)
(scheme, netloc, path, params, query, fragment) = url_parts
path = path.lower()
if path.startswith('/v3'):
v3_auth_url = auth_url
elif path.startswith('/v2'):
v2_auth_url = auth_url
else:
raise exc.CommandError('Unable to determine the Keystone '
'version to authenticate with '
'using the given auth_url.')
return v2_auth_url, v3_auth_url
def _get_keystone_session(**kwargs):
# TODO(fabgia): the heavy lifting here should be really done by Keystone.
# Unfortunately Keystone does not support a richer method to perform
# discovery and return a single viable URL. A bug against Keystone has
# been filed: https://bugs.launchpad.net/python-keystoneclient/+bug/1330677
# first create a Keystone session
cacert = kwargs.pop('cacert', None)
cert = kwargs.pop('cert', None)
key = kwargs.pop('key', None)
insecure = kwargs.pop('insecure', False)
auth_url = kwargs.pop('auth_url', None)
project_id = kwargs.pop('project_id', None)
project_name = kwargs.pop('project_name', None)
if insecure:
verify = False
else:
verify = cacert or True
if cert and key:
# passing cert and key together is deprecated in favour of the
# requests lib form of having the cert and key as a tuple
cert = (cert, key)
# create the keystone client session
ks_session = session.Session(verify=verify, cert=cert)
v2_auth_url, v3_auth_url = _discover_auth_versions(ks_session, auth_url)
username = kwargs.pop('username', None)
user_id = kwargs.pop('user_id', None)
user_domain_name = kwargs.pop('user_domain_name', None)
user_domain_id = kwargs.pop('user_domain_id', None)
project_domain_name = kwargs.pop('project_domain_name', None)
project_domain_id = kwargs.pop('project_domain_id', None)
auth = None
use_domain = (user_domain_id or user_domain_name or
project_domain_id or project_domain_name)
use_v3 = v3_auth_url and (use_domain or (not v2_auth_url))
use_v2 = v2_auth_url and not use_domain
if use_v3:
# the auth_url as v3 specified
# e.g. http://no.where:5000/v3
# Keystone will return only v3 as viable option
auth = v3_auth.Password(
v3_auth_url,
username=username,
password=kwargs.pop('password', None),
user_id=user_id,
user_domain_name=user_domain_name,
user_domain_id=user_domain_id,
project_name=project_name,
project_id=project_id,
project_domain_name=project_domain_name,
project_domain_id=project_domain_id)
elif use_v2:
# the auth_url as v2 specified
# e.g. http://no.where:5000/v2.0
# Keystone will return only v2 as viable option
auth = v2_auth.Password(
v2_auth_url,
username,
kwargs.pop('password', None),
tenant_id=project_id,
tenant_name=project_name)
else:
raise exc.CommandError('Unable to determine the Keystone version '
'to authenticate with using the given '
'auth_url.')
ks_session.auth = auth
return ks_session
def _get_endpoint(ks_session, **kwargs):
"""Get an endpoint using the provided keystone session."""
# set service specific endpoint types
endpoint_type = kwargs.get('endpoint_type') or 'publicURL'
service_type = kwargs.get('service_type') or 'application_deployment'
endpoint = ks_session.get_endpoint(service_type=service_type,
interface=endpoint_type,
region_name=kwargs.get('region_name'))
return endpoint
class KeystoneAuthPlugin(auth.BaseAuthPlugin):
opt_names = ['tenant_id', 'region_name', 'auth_token',
'service_type', 'endpoint_type', 'cacert',
'auth_url', 'insecure', 'cert_file', 'key_file',
'cert', 'key', 'tenant_name', 'project_name',
'project_id', 'project_domain_id', 'project_domain_name',
'user_id', 'user_domain_id', 'user_domain_name',
'password', 'username', 'endpoint']
def __init__(self, auth_system=None, **kwargs):
self.opt_names.extend(self.common_opt_names)
super(KeystoneAuthPlugin, self).__init__(auth_system, **kwargs)
def _do_authenticate(self, http_client):
token = self.opts.get('token') or self.opts.get('auth_token')
endpoint = self.opts.get('endpoint')
if not (token and endpoint):
project_id = (self.opts.get('project_id') or
self.opts.get('tenant_id'))
project_name = (self.opts.get('project_name') or
self.opts.get('tenant_name'))
ks_kwargs = {
'username': self.opts.get('username'),
'password': self.opts.get('password'),
'user_id': self.opts.get('user_id'),
'user_domain_id': self.opts.get('user_domain_id'),
'user_domain_name': self.opts.get('user_domain_name'),
'project_id': project_id,
'project_name': project_name,
'project_domain_name': self.opts.get('project_domain_name'),
'project_domain_id': self.opts.get('project_domain_id'),
'auth_url': self.opts.get('auth_url'),
'cacert': self.opts.get('cacert'),
'cert': self.opts.get('cert'),
'key': self.opts.get('key'),
'insecure': strutils.bool_from_string(
self.opts.get('insecure')),
'endpoint_type': self.opts.get('endpoint_type'),
}
# retrieve session
ks_session = _get_keystone_session(**ks_kwargs)
token = ks_session.get_token()
endpoint = (self.opts.get('endpoint') or
_get_endpoint(ks_session, **ks_kwargs))
self.opts['token'] = token
self.opts['endpoint'] = endpoint
def token_and_endpoint(self, endpoint_type, service_type):
token = self.opts.get('token')
if callable(token):
token = token()
return token, self.opts.get('endpoint')
def sufficient_options(self):
"""Check if all required options are present.
:raises: AuthPluginOptionsMissing
"""
has_token = self.opts.get('token') or self.opts.get('auth_token')
no_auth = has_token and self.opts.get('endpoint')
has_project = (self.opts.get('project_id')
or (self.opts.get('project_name')
and (self.opts.get('user_domain_name')
or self.opts.get('user_domain_id'))))
has_tenant = self.opts.get('tenant_id') or self.opts.get('tenant_name')
has_credential = (self.opts.get('username')
and (has_project or has_tenant)
and self.opts.get('password')
and self.opts.get('auth_url'))
missing = not (no_auth or has_credential)
if missing:
missing_opts = []
opts = ['token', 'endpoint', 'username', 'password', 'auth_url',
'tenant_id', 'tenant_name']
for opt in opts:
if not self.opts.get(opt):
missing_opts.append(opt)
raise exceptions.AuthPluginOptionsMissing(missing_opts)

View File

@ -1,104 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from urllib import parse as urlparse
from solumclient.common.apiclient import base
from solumclient.common.apiclient import exceptions
class FindMixin(object):
"""Just `findone()`/`findall()` methods.
Note: this is largely a copy of apiclient.base.ManagerWithFind
but without the inheritance and the find() method which
does not clash with the Manager find() - now called findone().
"""
def findone(self, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
raise exceptions.NotFound(msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch()
else:
return matches[0]
def findall(self, **kwargs):
"""Find all items with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
found = []
searches = kwargs.items()
for obj in self.list(app_id=kwargs.get('app_id')):
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
found.append(obj)
except AttributeError:
continue
return found
class CrudManager(base.CrudManager):
def list(self, base_url=None, **kwargs):
"""List the collection.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % urlparse.urlencode(kwargs) if kwargs else '',
})
def get(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._get(
self.build_url(**kwargs))
def create(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._post(
self.build_url(**kwargs), kwargs)
def update(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
params = kwargs.copy()
params.pop('%s_id' % self.key)
return self._put(
self.build_url(**kwargs), params)
def patch(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
params = kwargs.copy()
params.pop('%s_id' % self.key)
params.pop('base_url')
return self._patch(
self.build_url(**kwargs), params)

View File

@ -1,298 +0,0 @@
# Copyright (c) 2014 Rackspace
#
# 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
from oslo_log import log as logging
from solumclient import client as solum_client
from solumclient.common import cliutils
from solumclient.common import exc
class CommandsBase(object):
"""Base command parsing class."""
parser = None
solum = None
json_output = False
verify = True
def __init__(self, parser):
self.parser = parser
self._get_global_flags()
try:
self._get_auth_flags()
except exc.CommandError as ce:
print(self.__doc__)
print("ERROR: %s" % ce.message)
return
self.parser.add_argument('action',
default='help',
help='Action to perform on resource')
parsed, _ = self.parser.parse_known_args()
action = vars(parsed).get('action')
client_args = vars(parsed)
client_args.pop('insecure', None)
client_args['verify'] = self.verify
if 'os_auth_token' in client_args:
del client_args['os_auth_token']
self.client = solum_client.get_client(parsed.solum_api_version,
**client_args)
if action in self._actions:
try:
return self._actions[action]()
except exc.CommandError as ce:
print("ERROR: %s" % ce.message)
else:
print(self.__doc__)
def _get_auth_flags(self):
self.parser.add_argument('--os-username',
default=env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME]')
self.parser.add_argument('--os-password',
default=env('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD]')
self.parser.add_argument('--os-tenant-name',
default=env('OS_TENANT_NAME'),
help='Defaults to env[OS_TENANT_NAME]')
self.parser.add_argument('--os-tenant-id',
default=env('OS_TENANT_ID'),
help='Defaults to env[OS_TENANT_ID]')
self.parser.add_argument('--os-project-name',
default=env('OS_PROJECT_NAME'),
help='Defaults to env[OS_PROJECT_NAME]')
self.parser.add_argument('--os-project-id',
default=env('OS_PROJECT_ID'),
help='Defaults to env[OS_PROJECT_ID]')
self.parser.add_argument('--os-project-domain-name',
default=env('OS_PROJECT_DOMAIN_NAME'),
help='Defaults to '
'env[OS_PROJECT_DOMAIN_NAME]')
self.parser.add_argument('--os-project-domain-id',
default=env('OS_PROJECT_DOMAIN_ID'),
help='Defaults to '
'env[OS_PROJECT_DOMAIN_NAME]')
self.parser.add_argument('--os-user-domain-name',
default=env('OS_USER_DOMAIN_NAME'),
help='Defaults to env[OS_USER_DOMAIN_NAME]')
self.parser.add_argument('--os-user-domain-id',
default=env('OS_USER_DOMAIN_ID'),
help='Defaults to env[OS_USER_DOMAIN_NAME]')
self.parser.add_argument('--os-region-name',
default=env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME]')
self.parser.add_argument('--os-auth-url',
default=env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL]')
self.parser.add_argument('--os-auth-token',
default=env('OS_AUTH_TOKEN'),
help='Defaults to env[OS_AUTH_TOKEN]')
self.parser.add_argument('--solum-url',
default=env('SOLUM_URL'),
help='Defaults to env[SOLUM_URL]')
api_version = env('SOLUM_API_VERSION', default='1')
self.parser.add_argument('--solum-api-version',
default=api_version,
help='Defaults to env[SOLUM_API_VERSION] '
'or 1')
parsed, _ = self.parser.parse_known_args()
client_args = vars(parsed)
if not parsed.os_auth_token:
# Remove arguments that are not to be passed to the client in this
# case.
del client_args['os_auth_token']
if not parsed.os_username:
raise exc.CommandError("You must provide a username via "
"either --os-username or via "
"env[OS_USERNAME]")
if not parsed.os_password:
raise exc.CommandError("You must provide a password via "
"either --os-password or via "
"env[OS_PASSWORD]")
if not (parsed.os_tenant_name or parsed.os_project_name):
raise exc.CommandError("You must provide a tenant_name via "
"either --os-tenant-name or via "
"env[OS_TENANT_NAME]")
if not parsed.os_auth_url:
raise exc.CommandError("You must provide an auth url via "
"either --os-auth-url or via "
"env[OS_AUTH_URL]")
@property
def _actions(self):
"""Action handler."""
return dict((attr, getattr(self, attr))
for attr in dir(self)
if not attr.startswith('_')
and callable(getattr(self, attr)))
def _get_global_flags(self):
"""Get global flags."""
# Good location to add_argument() global options like --verbose
self.parser.add_argument('--json',
action='store_true',
help='JSON formatted output')
self.parser.add_argument('-k', '--insecure',
action='store_true',
help='Explicitly allow the client to perform'
' \"insecure SSL\" (https) requests.'
' The server\'s certificate will not be'
' verified against any certificate'
' authorities. This option should be'
' used with caution.')
self.parser.add_argument('-d', '--debug',
action='store_true',
help='Print out request and response '
'details.')
args, _ = self.parser.parse_known_args()
if args.json:
self.json_output = True
if args.insecure:
self.verify = False
if args.debug:
logging.basicConfig(
format="%(levelname)s (%(module)s) %(message)s",
level=logging.DEBUG)
logging.getLogger('iso8601').setLevel(logging.WARNING)
urllibpool = 'urllib3.connectionpool'
logging.getLogger(urllibpool).setLevel(logging.WARNING)
def _sanitized_fields(self, fields):
def allowed(field):
if field.startswith('_'):
return False
if field == 'manager':
return False
if field == 'artifacts':
return False
return True
return [f for f in fields
if allowed(f)]
def _print_dict(self, obj, fields, dict_property="Property", wrap=0):
fields = self._sanitized_fields(fields)
try:
# datsun180b: I have no idea why, but following a PATCH
# app resources need to have this evaluated once or else
# the subset assignment below fails.
obj.attrs
except TypeError:
pass
except AttributeError:
pass
subset = dict([(f, getattr(obj, f, '')) for f in fields])
if self.json_output:
print(json.dumps(subset, indent=2, sort_keys=True))
else:
cliutils.print_dict(subset, dict_property, wrap)
def _print_list(self, objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None, field_labels=None):
fields = self._sanitized_fields(fields)
if self.json_output:
subsets = [dict([(f, getattr(obj, f, '')) for f in fields])
for obj in objs]
print(json.dumps(subsets, indent=2, sort_keys=True))
else:
cliutils.print_list(objs, fields, formatters, sortby_index,
mixed_case_fields, field_labels)
class NoSubCommands(CommandsBase):
"""Command parsing class that lacks an 'action'."""
parser = None
solum = None
json_output = False
verify = True
def __init__(self, parser):
self.parser = parser
self._get_global_flags()
try:
self._get_auth_flags()
except exc.CommandError as ce:
print(self.__doc__)
print("ERROR: %s" % ce.message)
return
parsed, _ = self.parser.parse_known_args()
client_args = vars(parsed)
client_args.pop('insecure', None)
client_args['verify'] = self.verify
if 'os_auth_token' in client_args:
del client_args['os_auth_token']
self.client = solum_client.get_client(parsed.solum_api_version,
**client_args)
return self.info()
def info(self):
pass
def env(*vars, **kwargs):
"""Search for the first defined of possibly many env vars
Returns the first environment variable defined in vars, or
returns the default defined in kwargs.
"""
for v in vars:
value = os.environ.get(v, None)
if value:
return value
return kwargs.get('default', '')
def filter_ready_lps(lp_list):
filtered_list = []
for lp in lp_list:
if lp.status == 'READY':
filtered_list.append(lp)
return filtered_list

View File

@ -1,71 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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 time
from oslo_log import log as logging
from solumclient.common.apiclient import client as api_client
from solumclient.common import exc
from solumclient import config
_logger = logging.getLogger(__name__)
class HTTPClient(api_client.HTTPClient):
def request(self, method, url, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
setting headers, JSON encoding/decoding, and error handling.
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
' requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument
"""
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs["headers"]["User-Agent"] = self.user_agent
kwargs["headers"]["X-User-ID"] = config.username
kwargs["headers"]["X-Password"] = config.password
kwargs["headers"]["X-Project"] = config.tenant
if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
self.original_ip, self.user_agent)
if self.timeout is not None:
kwargs.setdefault("timeout", self.timeout)
kwargs.setdefault("verify", self.verify)
if self.cert is not None:
kwargs.setdefault("cert", self.cert)
self.serialize(kwargs)
self._http_log_req(method, url, kwargs)
if self.timings:
start_time = time.time()
resp = self.http.request(method, url, **kwargs)
if self.timings:
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
self._http_log_resp(resp)
if resp.status_code >= 400:
_logger.debug(
"Request returned failure status: %s",
resp.status_code)
raise exc.from_response(resp, method, url)
return resp

View File

@ -1,323 +0,0 @@
# Copyright 2012 Red Hat, Inc.
#
# 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.
# W0603: Using the global statement
# W0621: Redefining name %s from outer scope
# pylint: disable=W0603,W0621
import getpass
import inspect
import os
import sys
import textwrap
import prettytable
from solumclient.common.apiclient import exceptions
from solumclient.i18n import _
from oslo_utils import encodeutils
from oslo_utils import strutils
from oslo_utils import uuidutils
def validate_args(fn, *args, **kwargs):
"""Check that the supplied args are sufficient for calling a function.
>>> validate_args(lambda a: None)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): a
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): b, d
:param fn: the function to check
:param arg: the positional arguments supplied
:param kwargs: the keyword arguments supplied
"""
argspec = inspect.getargspec(fn)
num_defaults = len(argspec.defaults or [])
required_args = argspec.args[:len(argspec.args) - num_defaults]
def isbound(method):
return getattr(method, '__self__', None) is not None
if isbound(fn):
required_args.pop(0)
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
if missing:
raise exceptions.MissingArgs(missing)
def arg(*args, **kwargs):
"""Decorator for CLI args.
Example:
>>> @arg("name", help="Name of the new entity")
... def entity_create(args):
... pass
"""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def env(*args, **kwargs):
"""Returns the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def add_arg(func, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(func, 'arguments'):
func.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in func.arguments:
# Because of the semantics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
func.arguments.insert(0, (args, kwargs))
def unauthenticated(func):
"""Adds 'unauthenticated' attribute to decorated function.
Usage:
>>> @unauthenticated
... def mymethod(f):
... pass
"""
func.unauthenticated = True
return func
def isunauthenticated(func):
"""Checks if the function does not require authentication.
Mark such functions with the `@unauthenticated` decorator.
:returns: bool
"""
return getattr(func, 'unauthenticated', False)
def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None, field_labels=None):
"""Print a list or objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
:param fields: attributes that correspond to columns, in order
:param formatters: `dict` of callables for field formatting
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
:param field_labels: Labels to use in the heading of the table, default to
fields.
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
field_labels = field_labels or fields
if len(field_labels) != len(fields):
raise ValueError(_("Field labels list %(labels)s has different number "
"of elements than fields list %(fields)s"),
{'labels': field_labels, 'fields': fields})
if sortby_index is None:
kwargs = {}
else:
kwargs = {'sortby': field_labels[sortby_index]}
pt = prettytable.PrettyTable(field_labels, caching=False)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
if field in formatters:
row.append(formatters[field](o))
else:
if field in mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
row.append(data)
pt.add_row(row)
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0):
"""Print a `dict` as a table of two columns.
:param dct: `dict` to print
:param dict_property: name of the first column
:param wrap: wrapping for the second column
"""
pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False)
pt.align = 'l'
for k, v in dct.items():
# convert dict to str to check length
if isinstance(v, dict):
v = str(v)
if wrap > 0:
v = textwrap.fill(str(v), wrap)
elif wrap < 0:
raise ValueError(_("Wrap argument should be a positive integer"))
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, str) and r'\n' in v:
lines = v.strip().split(r'\n')
col1 = k
for line in lines:
pt.add_row([col1, line])
col1 = ''
else:
pt.add_row([k, v])
print(encodeutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3):
"""Read password from TTY."""
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
pw = None
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
# Check for Ctrl-D
try:
for __ in range(max_password_prompts):
pw1 = getpass.getpass("OS Password: ")
if verify:
pw2 = getpass.getpass("Please verify: ")
else:
pw2 = pw1
if pw1 == pw2 and pw1:
pw = pw1
break
except EOFError:
pass
return pw
def find_resource(manager, name_or_id, **find_args):
"""Look for resource in a given manager.
Used as a helper for the _find_* methods.
Example:
.. code-block:: python
def _find_hypervisor(cs, hypervisor):
#Get a hypervisor by name or ID.
return cliutils.find_resource(cs.hypervisors, hypervisor)
"""
# first try to get entity as integer id
try:
return manager.get(int(name_or_id))
except (TypeError, ValueError, exceptions.NotFound):
pass
# now try to get entity as uuid
try:
if isinstance(name_or_id, str):
tmp_id = encodeutils.safe_encode(name_or_id)
else:
tmp_id = encodeutils.safe_decode(name_or_id)
if uuidutils.is_uuid_like(tmp_id):
return manager.get(tmp_id)
except (TypeError, ValueError, exceptions.NotFound):
pass
# for str id which is not uuid
if getattr(manager, 'is_alphanum_id_allowed', False):
try:
return manager.get(name_or_id)
except exceptions.NotFound:
pass
try:
try:
return manager.find(human_id=name_or_id, **find_args)
except exceptions.NotFound:
pass
# finally try to find entity by name
try:
resource = getattr(manager, 'resource_class', None)
name_attr = resource.NAME_ATTR if resource else 'name'
kwargs = {name_attr: name_or_id}
kwargs.update(find_args)
return manager.find(**kwargs)
except exceptions.NotFound:
msg = _("No %(name)s with a name or "
"ID of '%(name_or_id)s' exists.") % {
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)
except exceptions.NoUniqueMatch:
msg = _("Multiple %(name)s matches found for "
"'%(name_or_id)s', use an ID to be more specific.") % {
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)
def service_type(stype):
"""Adds 'service_type' attribute to decorated function.
Usage:
.. code-block:: python
@service_type('volume')
def mymethod(f):
...
"""
def inner(f):
f.service_type = stype
return f
return inner
def get_service_type(f):
"""Retrieves service type from function."""
return getattr(f, 'service_type', None)
def pretty_choice_list(value):
return ', '.join("'%s'" % i for i in value)
def exit(msg=''):
if msg:
print(msg, file=sys.stderr)
sys.exit(1)

View File

@ -1,77 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import exceptions
class CommandException(Exception):
"""An error occurred."""
def __init__(self, message=None):
self.message = message
def __str__(self):
return self.message or self.__class__.__doc__
class CommandError(CommandException):
"""Invalid usage of CLI."""
class NotUnique(CommandException):
"""Name refers to more than one of a given resource."""
def __init__(self, resource='resource'):
message = "More than one %s by that name. Retry with the UUID."
self.message = message % resource
def from_response(response, method, url):
"""Returns an instance of :class:`HttpError` or subclass based on response.
:param response: instance of `requests.Response` class
:param method: HTTP method used for request
:param url: URL used for request
"""
kwargs = {
"http_status": response.status_code,
"response": response,
"method": method,
"url": url,
"request_id": response.headers.get("x-compute-request-id"),
}
if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"]
content_type = response.headers.get("Content-Type", "")
if content_type.startswith("application/json"):
try:
body = response.json()
except ValueError:
pass
else:
if isinstance(body, dict):
kwargs["message"] = body.get("faultstring")
kwargs["details"] = body.get("debuginfo")
elif content_type.startswith("text/"):
kwargs["details"] = response.text
try:
cls = exceptions._code_map[response.status_code]
except KeyError:
if 500 <= response.status_code < 600:
cls = exceptions.HttpServerError
elif 400 <= response.status_code < 500:
cls = exceptions.HTTPClientError
else:
cls = exceptions.HttpError
return cls(**kwargs)

View File

@ -1,197 +0,0 @@
# Copyright (c) 2015 Rackspace
#
# 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.
# Tools for interacting with Github.
import base64
import getpass
import json
import random
import re
import string
import httplib2
class GitHubException(Exception):
def __init__(self, status_code, message):
self.status_code = status_code
self.message = message
def __str__(self):
return "GitHub Exception: %s: %s" % (self.status_code, self.message)
class GitHubAuth(object):
_github_auth_url = 'https://api.github.com/authorizations'
_github_repo_hook_url = 'https://api.github.com/repos/%s/hooks'
_github_user_key_url = 'https://api.github.com/user/keys'
_github_repo_regex = r'github\.com[:/](.+?)/(.+?)($|/$|\.git$|\.git/$)'
def __init__(self, git_url, username=None, password=None, repo_token=None):
self.git_url = git_url
user_org_name, repo = '', ''
repo_pat = re.compile(self._github_repo_regex)
match = repo_pat.search(self.git_url)
if match:
user_org_name, repo = match.group(1), match.group(2)
else:
raise ValueError("Failed to parse %s." % git_url)
self.user_org_name = user_org_name
self.full_repo_name = '/'.join([user_org_name, repo])
self._repo_token = repo_token
self._username = username
self._password = password
self._otp_required = False
@property
def username(self):
if self._username is None:
prompt = ("Username for repo '%s' [%s]:" %
(self.full_repo_name, self.user_org_name))
self._username = input(prompt) or self.user_org_name
return self._username
@property
def password(self):
if self._password is None:
self._password = getpass.getpass("Password: ")
return self._password
@property
def onetime_password(self):
# This is prompted for every time it's needed.
print("Two-Factor Authentication required.")
return getpass.getpass("2FA Token: ")
@property
def repo_token(self):
if self._repo_token is None:
self.create_repo_token()
return self._repo_token
@property
def auth_header(self):
header = {
'Content-Type': 'application/json',
}
# The token on its own should suffice
if self._repo_token:
header['Authorization'] = 'token %s' % self._repo_token
return header
# This will prompt the user if either name or pass is missing.
authstring = '%s:%s' % (self.username, self.password)
basic_auth = base64.encodebytes(bytes(authstring, 'utf-8'))
basic_auth = basic_auth.decode('utf-8')
basic_auth = basic_auth.strip()
header['Authorization'] = 'Basic %s' % basic_auth
# This will prompt for the OTP.
if self._otp_required:
header['x-github-otp'] = self.onetime_password
return header
def _send_authed_request(self, url, body_dict):
body_text = json.dumps(body_dict)
resp, content = httplib2.Http().request(
url, 'POST',
headers=self.auth_header,
body=body_text)
if resp.get('status') in ['401']:
if resp.get('x-github-otp', '').startswith('required'):
self._otp_required = True
resp, content = httplib2.Http().request(
url, 'POST',
headers=self.auth_header,
body=body_text)
return resp, content
def create_repo_token(self):
print("Creating repo token")
note = ''.join(random.sample(string.lowercase, 5))
auth_info = {
'scopes': ['repo', 'write:public_key', 'write:repo_hook'],
'note': 'Solum-status-%s' % note,
}
resp, content = self._send_authed_request(
self._github_auth_url,
auth_info)
status_code = int(resp.get('status', '500'))
response_body = json.loads(content)
if status_code in [200, 201]:
self._repo_token = response_body.get('token')
print("Successfully created repo token %s." % auth_info['note'])
return self._repo_token
elif status_code >= 400 and status_code < 600:
message = response_body.get('message',
'No error message provided.')
raise GitHubException(status_code, message)
def create_webhook(self, trigger_uri, workflow=None):
print("Creating webhook for repo.")
hook_url = self._github_repo_hook_url % self.full_repo_name
if workflow is not None:
# workflow is a list of strings, likely
# ['unittest', 'build', 'deploy'].
# They're joined with + and appended to the
# trigger_uri here.
wf_query = "?workflow=%s" % '+'.join(workflow)
trigger_uri += wf_query
hook_info = {
'name': 'web',
'events': ['pull_request', 'commit_comment'],
'config': {
'content_type': 'json',
'url': trigger_uri,
}
}
resp, content = self._send_authed_request(hook_url, hook_info)
if resp.get('status') in ['200', '201']:
print("Successfully created webhook.")
else:
print("Error creating webhook.")
def add_ssh_key(self, public_key=None):
if not public_key:
print("No public key to upload.")
return
print("Uploading public key to user account.")
api_url = self._github_user_key_url
key_info = {
'title': 'devops@Solum',
'key': public_key,
}
resp, content = self._send_authed_request(api_url, key_info)
if resp.get('status') in ['200', '201']:
print("Successfully uploaded public key.")
else:
print("Error uploading public key.")

View File

@ -1,50 +0,0 @@
# 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.
from debtcollector import removals
import yaml
removals.removed_module(
'solumclient.common.yamlutils', version='3.0.0', removal_version='4.0.0',
message='The solumclient.common.yamlutils will be removed')
if hasattr(yaml, 'CSafeLoader'):
yaml_loader = yaml.CSafeLoader
else:
yaml_loader = yaml.SafeLoader
if hasattr(yaml, 'CSafeDumper'):
yaml_dumper = yaml.CSafeDumper
else:
yaml_dumper = yaml.SafeDumper
def load(s):
try:
yml_dict = yaml.load(s, yaml_loader)
except yaml.YAMLError as exc:
msg = 'An error occurred during YAML parsing.'
if hasattr(exc, 'problem_mark'):
msg += ' Error position: (%s:%s)' % (exc.problem_mark.line + 1,
exc.problem_mark.column + 1)
raise ValueError(msg)
if not isinstance(yml_dict, dict) and not isinstance(yml_dict, list):
raise ValueError('The source is not a YAML mapping or list.')
if isinstance(yml_dict, dict) and len(yml_dict) < 1:
raise ValueError('Could not find any element in your YAML mapping.')
return yml_dict
def dump(s):
return yaml.dump(s, Dumper=yaml_dumper)

View File

@ -1,21 +0,0 @@
# Copyright (c) 2016 Rackspace
#
# 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.
username = ''
password = ''
tenant = ''
auth_url = ''
tenant_name = ''

View File

@ -1,24 +0,0 @@
# 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.
"""oslo.i18n integration module.
See https://docs.openstack.org/oslo.i18n/latest/user/usage.html
"""
import oslo_i18n
_translators = oslo_i18n.TranslatorFactory(domain='solumclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary

View File

@ -1,159 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: python-solumclient VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2018-01-24 07:29+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-10-06 07:48+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en_GB\n"
"X-Generator: Zanata 4.3.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#, python-format
msgid "AmbiguousEndpoints: %s"
msgstr "AmbiguousEndpoints: %s"
#, python-format
msgid "AuthSystemNotFound: %s"
msgstr "AuthSystemNotFound: %s"
#, python-format
msgid "Authentication failed. Missing options: %s"
msgstr "Authentication failed. Missing options: %s"
msgid "Bad Gateway"
msgstr "Bad Gateway"
msgid "Bad Request"
msgstr "Bad Request"
msgid "Cannot find endpoint or token for request"
msgstr "Cannot find endpoint or token for request"
msgid "Conflict"
msgstr "Conflict"
msgid "Expectation Failed"
msgstr "Expectation Failed"
#, python-format
msgid ""
"Field labels list %(labels)s has different number of elements than fields "
"list %(fields)s"
msgstr ""
"Field labels list %(labels)s has different number of elements than fields "
"list %(fields)s"
msgid "Forbidden"
msgstr "Forbidden"
msgid "Gateway Timeout"
msgstr "Gateway Timeout"
msgid "Gone"
msgstr "Gone"
msgid "HTTP Client Error"
msgstr "HTTP Client Error"
msgid "HTTP Error"
msgstr "HTTP Error"
msgid "HTTP Redirection"
msgstr "HTTP Redirection"
msgid "HTTP Server Error"
msgstr "HTTP Server Error"
msgid "HTTP Version Not Supported"
msgstr "HTTP Version Not Supported"
msgid "Internal Server Error"
msgstr "Internal Server Error"
#, python-format
msgid ""
"Invalid %(api_name)s client version '%(version)s'. Must be one of: "
"%(version_map)s"
msgstr ""
"Invalid %(api_name)s client version '%(version)s'. Must be one of: "
"%(version_map)s"
msgid "Length Required"
msgstr "Length Required"
msgid "Method Not Allowed"
msgstr "Method Not Allowed"
#, python-format
msgid "Missing arguments: %s"
msgstr "Missing arguments: %s"
#, python-format
msgid ""
"Multiple %(name)s matches found for '%(name_or_id)s', use an ID to be more "
"specific."
msgstr ""
"Multiple %(name)s matches found for '%(name_or_id)s', use an ID to be more "
"specific."
msgid "Multiple Choices"
msgstr "Multiple Choices"
#, python-format
msgid "No %(name)s matching %(args)s."
msgstr "No %(name)s matching %(args)s."
#, python-format
msgid "No %(name)s with a name or ID of '%(name_or_id)s' exists."
msgstr "No %(name)s with a name or ID of '%(name_or_id)s' exists."
msgid "Not Acceptable"
msgstr "Not Acceptable"
msgid "Not Found"
msgstr "Not Found"
msgid "Not Implemented"
msgstr "Not Implemented"
msgid "Payment Required"
msgstr "Payment Required"
msgid "Precondition Failed"
msgstr "Precondition Failed"
msgid "Proxy Authentication Required"
msgstr "Proxy Authentication Required"
msgid "Request Entity Too Large"
msgstr "Request Entity Too Large"
msgid "Request Timeout"
msgstr "Request Timeout"
msgid "Request-URI Too Long"
msgstr "Request-URI Too Long"
msgid "Requested Range Not Satisfiable"
msgstr "Requested Range Not Satisfiable"
msgid "Service Unavailable"
msgstr "Service Unavailable"
msgid "Unauthorized"
msgstr "Unauthorised"
msgid "Unprocessable Entity"
msgstr "Unprocessable Entity"
msgid "Unsupported Media Type"
msgstr "Unsupported Media Type"
msgid "Wrap argument should be a positive integer"
msgstr "Wrap argument should be a positive integer"

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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 fixtures
from oslotest import base
import testscenarios
class TestCase(testscenarios.WithScenarios, base.BaseTestCase):
"""Test case base class for all unit tests."""
def setUp(self):
"""Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
self.log_fixture = self.useFixture(fixtures.FakeLogger())

View File

@ -1,31 +0,0 @@
# Copyright 2014 - Noorul Islam K M
#
# 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.
from unittest import mock
from solumclient.builder import client
from solumclient.common.apiclient import exceptions
from solumclient.common import auth
from solumclient.tests import base
class ClientTest(base.TestCase):
def test_client_unsupported_version(self):
self.assertRaises(exceptions.UnsupportedVersion,
client.Client, '111.11', **{})
def test_client(self):
with mock.patch.object(auth, 'KeystoneAuthPlugin'):
client.Client('1', **{})

View File

@ -1,73 +0,0 @@
# Copyright 2014 - Noorul Islam K M
#
# 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.
from solumclient.builder.v1 import client as builder_client
from solumclient.builder.v1 import image
from solumclient.common.apiclient import fake_client
from solumclient.tests import base
image_fixture = {
'uri': 'http://example.com/v1/images/i1',
'name': 'php-web-app',
'source_uri': 'git://example.com/project/app.git',
'type': 'image',
'description': 'A php web application',
'tags': ['small'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
}
fixtures_get = {
'/v1/images/i1': {
'GET': (
{},
image_fixture
),
}
}
fixtures_create = {
'/v1/images': {
'POST': (
{},
image_fixture
),
}
}
class ImageManagerTest(base.TestCase):
def test_create(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_create)
api_client = builder_client.Client(fake_http_client)
mgr = image.ImageManager(api_client)
image_obj = mgr.create()
self.assertIn('Image', repr(image_obj))
self.assertEqual(image_fixture['uri'], image_obj.uri)
self.assertEqual(image_fixture['type'], image_obj.type)
self.assertEqual(image_fixture['project_id'], image_obj.project_id)
self.assertEqual(image_fixture['user_id'], image_obj.user_id)
def test_get(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_get)
api_client = builder_client.Client(fake_http_client)
mgr = image.ImageManager(api_client)
image_obj = mgr.get(image_id='i1')
self.assertIn('Image', repr(image_obj))
self.assertEqual(image_fixture['uri'], image_obj.uri)
self.assertEqual(image_fixture['type'], image_obj.type)
self.assertEqual(image_fixture['project_id'], image_obj.project_id)
self.assertEqual(image_fixture['user_id'], image_obj.user_id)

View File

@ -1,101 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from unittest import mock
from keystoneclient.v2_0 import client as ksclient
import testtools
from solumclient.common import auth
from solumclient.common import client
from solumclient.tests import base
@mock.patch.object(ksclient, 'Client')
class KeystoneAuthPluginTest(base.TestCase):
def setUp(self):
super(KeystoneAuthPluginTest, self).setUp()
plugin = auth.KeystoneAuthPlugin(
username="fake-username",
password="fake-password",
tenant_name="fake-tenant-name",
project_domain_name="default",
user_domain_name="default",
auth_url="http://auth")
self.cs = client.HTTPClient(auth_plugin=plugin)
@testtools.skip("Skip it when found solution")
def test_authenticate(self, mock_ksclient):
self.cs.authenticate()
mock_ksclient.assert_called_with(
username="fake-username",
password="fake-password",
tenant_name="fake-tenant-name",
project_domain_name="default",
user_domain_name="default",
auth_url="http://auth")
def test_token_and_endpoint(self, mock_ksclient):
plugin = auth.KeystoneAuthPlugin(
endpoint="http://solum",
token="test_token")
self.cs = client.HTTPClient(auth_plugin=plugin)
self.cs.authenticate()
(token, endpoint) = self.cs.auth_plugin.token_and_endpoint(
"fake-endpoint-type", "fake-service-type")
self.assertEqual(token, "test_token")
self.assertEqual("http://solum", endpoint)
def test_token_and_endpoint_before_auth(self, mock_ksclient):
(token, endpoint) = self.cs.auth_plugin.token_and_endpoint(
"fake-endpoint-type", "fake-service-type")
self.assertIsNone(token, None)
self.assertIsNone(endpoint, None)
@testtools.skip("Skip it when found solution")
def test_endpoint_with_no_token(self, mock_ksclient):
plugin = auth.KeystoneAuthPlugin(
username="fake-username",
password="fake-password",
tenant_name="fake-tenant-name",
project_domain_name="default",
user_domain_name="default",
auth_url="http://auth",
endpoint="http://solum")
self.cs = client.HTTPClient(auth_plugin=plugin)
self.cs.authenticate()
mock_ksclient.assert_called_with(
username="fake-username",
password="fake-password",
tenant_name="fake-tenant-name",
auth_url="http://auth")
(token, endpoint) = self.cs.auth_plugin.token_and_endpoint(
"fake-endpoint-type", "fake-service-type")
self.assertIsInstance(token, mock.MagicMock)
self.assertEqual("http://solum", endpoint)
@mock.patch.object(ksclient, 'Client')
class KeystoneAuthPluginTokenTest(base.TestCase):
def test_token_and_endpoint(self, mock_ksclient):
plugin = auth.KeystoneAuthPlugin(
token="fake-token",
endpoint="http://solum")
cs = client.HTTPClient(auth_plugin=plugin)
cs.authenticate()
(token, endpoint) = cs.auth_plugin.token_and_endpoint(
"fake-endpoint-type", "fake-service-type")
self.assertEqual('fake-token', token)
self.assertEqual('http://solum', endpoint)

View File

@ -1,115 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import base
from solumclient.common.apiclient import client
from solumclient.common.apiclient import fake_client
from solumclient.tests import base as test_base
fixture1 = {
'/foo_resource': {
'GET': (
{},
{'id': 1, 'name': 'foo'}
),
}
}
fixture2 = {
'/foo_resource': {
'GET': (
{},
{'foo_resource': {'id': 1, 'name': 'foo'}}
),
}
}
fixture3 = {
'/foo_resources': {
'GET': (
{},
[
{'id': 1, 'name': 'foo'},
{'id': 2, 'name': 'bar'}
]
),
}
}
fixture4 = {
'/foo_resources': {
'GET': (
{},
{'foo_resources': [
{'id': 1, 'name': 'foo'},
{'id': 2, 'name': 'bar'}
]}
),
}
}
class FooResource(base.Resource):
pass
class FooResourceManager(base.BaseManager):
resource_class = FooResource
def get(self):
return self._get("/foo_resource")
def get_with_response_key(self):
return self._get("/foo_resource", "foo_resource")
def list(self):
return self._list("/foo_resources")
def list_with_response_key(self):
return self._list("/foo_resources", "foo_resources")
class TestClient(client.BaseClient):
service_type = "test"
def __init__(self, http_client, extensions=None):
super(TestClient, self).__init__(
http_client, extensions=extensions)
self.foo_resource = FooResourceManager(self)
class BaseManagerTest(test_base.TestCase):
def test_get(self):
http_client = fake_client.FakeHTTPClient(fixtures=fixture1)
tc = TestClient(http_client)
tc.foo_resource.get()
def test_get_with_response_key(self):
http_client = fake_client.FakeHTTPClient(fixtures=fixture2)
tc = TestClient(http_client)
tc.foo_resource.get_with_response_key()
def test_list(self):
http_client = fake_client.FakeHTTPClient(fixtures=fixture3)
tc = TestClient(http_client)
tc.foo_resource.list()
def test_list_with_response_key(self):
http_client = fake_client.FakeHTTPClient(fixtures=fixture4)
tc = TestClient(http_client)
tc.foo_resource.list_with_response_key()

View File

@ -1,99 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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 requests
from unittest import mock
from solumclient.common.apiclient import auth
from solumclient.common.apiclient import client as api_client
from solumclient.common.apiclient import exceptions
from solumclient.common import client
from solumclient.tests import base
class TestClient(api_client.BaseClient):
service_type = "test"
class FakeAuthPlugin(auth.BaseAuthPlugin):
auth_system = "fake"
attempt = -1
def _do_authenticate(self, http_client):
pass
def token_and_endpoint(self, endpoint_type, service_type):
self.attempt = self.attempt + 1
return ("token-%s" % self.attempt, "/endpoint-%s" % self.attempt)
class ClientTest(base.TestCase):
def test_client_request(self):
http_client = client.HTTPClient(FakeAuthPlugin())
mock_request = mock.Mock()
mock_request.return_value = requests.Response()
mock_request.return_value.status_code = 200
with mock.patch("requests.Session.request", mock_request):
http_client.client_request(
TestClient(http_client), "GET", "/resource", json={"1": "2"})
requests.Session.request.assert_called_with(
"GET",
"/endpoint-0/resource",
headers={
"User-Agent": http_client.user_agent,
"Content-Type": "application/json",
"X-Auth-Token": "token-0",
"X-Password": "",
"X-User-ID": "",
"X-Project": ""
},
data='{"1": "2"}',
verify=True)
def test_client_with_response_404_status_code(self):
http_client = client.HTTPClient(FakeAuthPlugin())
mock_request = mock.Mock()
mock_request.return_value = requests.Response()
mock_request.return_value.status_code = 404
with mock.patch("requests.Session.request", mock_request):
self.assertRaises(
exceptions.HttpError, http_client.client_request,
TestClient(http_client), "GET", "/resource")
def test_client_with_invalid_endpoint(self):
http_client = client.HTTPClient(FakeAuthPlugin())
mock_request = mock.Mock()
mock_request.side_effect = requests.ConnectionError
with mock.patch("requests.Session.request", mock_request):
self.assertRaises(
requests.ConnectionError, http_client.client_request,
TestClient(http_client), "GET", "/resource")
def test_client_with_invalid_service_catalog(self):
http_client = client.HTTPClient(FakeAuthPlugin())
mock_request = mock.Mock()
mock_request.side_effect = exceptions.EndpointException
with mock.patch("requests.Session.request", mock_request):
self.assertRaises(
exceptions.EndpointException, http_client.client_request,
TestClient(http_client), "GET", "/resource")
def test_client_with_connection_refused(self):
http_client = client.HTTPClient(FakeAuthPlugin())
mock_request = mock.Mock()
mock_request.side_effect = exceptions.ConnectionRefused
with mock.patch("requests.Session.request", mock_request):
self.assertRaises(
exceptions.ConnectionRefused, http_client.client_request,
TestClient(http_client), "GET", "/resource")

View File

@ -1,143 +0,0 @@
# Copyright (c) 2014 Rackspace
#
# 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.
from unittest import mock
import fixtures
from solumclient import client as solum_client
from solumclient.common import cli_utils
import solumclient.solum
from solumclient.tests import base
class TestCli_Utils(base.TestCase):
"Test commandbase"
scenarios = [
('username', dict(
fake_env={'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': '',
'OS_TENANT_ID': '',
'OS_PROJECT_NAME': 'project_name',
'OS_PROJECT_ID': '',
'OS_PROJECT_DOMAIN_NAME': 'default',
'OS_PROJECT_DOMAIN_ID': '',
'OS_USER_DOMAIN_NAME': 'default',
'OS_USER_DOMAIN_ID': '',
'OS_REGION_NAME': 'RegionOne',
'OS_AUTH_URL': 'http://no.where'},
output={'solum_api_version': '1',
'os_username': 'username',
'solum_url': '',
'os_tenant_name': '',
'os_auth_url': 'http://no.where',
'os_password': 'password',
'os_tenant_id': '',
'os_project_name': 'project_name',
'os_project_id': '',
'os_project_domain_name': 'default',
'os_project_domain_id': '',
'os_user_domain_name': 'default',
'os_user_domain_id': '',
'os_region_name': 'RegionOne',
'action': 'create',
'json': False,
'verify': True,
'debug': False
})),
('token', dict(
fake_env={'OS_AUTH_TOKEN': '123456',
'SOLUM_URL': 'http://10.0.2.15:9777'},
output={'os_auth_url': '',
'solum_url': 'http://10.0.2.15:9777',
'solum_api_version': '1',
'os_username': '',
'os_tenant_name': '',
'os_password': '',
'os_tenant_id': '',
'os_project_name': '',
'os_project_id': '',
'os_project_domain_name': '',
'os_project_domain_id': '',
'os_user_domain_name': '',
'os_user_domain_id': '',
'os_region_name': '',
'action': 'create',
'json': False,
'verify': True,
'debug': False
})),
('solum_url_with_no_token', dict(
fake_env={'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': '',
'OS_TENANT_ID': '',
'OS_PROJECT_NAME': 'project_name',
'OS_PROJECT_ID': '',
'OS_PROJECT_DOMAIN_NAME': 'default',
'OS_PROJECT_DOMAIN_ID': '',
'OS_USER_DOMAIN_NAME': 'default',
'OS_USER_DOMAIN_ID': '',
'OS_REGION_NAME': 'RegionOne',
'OS_AUTH_URL': 'http://no.where',
'SOLUM_URL': 'http://10.0.2.15:9777'},
output={'os_auth_url': 'http://no.where',
'solum_url': 'http://10.0.2.15:9777',
'solum_api_version': '1',
'os_username': 'username',
'os_tenant_name': '',
'os_password': 'password',
'os_tenant_id': '',
'os_project_name': 'project_name',
'os_project_id': '',
'os_project_domain_name': 'default',
'os_project_domain_id': '',
'os_user_domain_name': 'default',
'os_user_domain_id': '',
'os_region_name': 'RegionOne',
'action': 'create',
'json': False,
'verify': True,
'debug': False
})),
]
# Patch os.environ to avoid reading auth info
# from environment or command line.
def make_env(self, exclude=None):
env = dict((k, v) for k, v in self.fake_env.items() if k != exclude)
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
def fake_argv(self):
self.useFixture(fixtures.MonkeyPatch('sys.argv', ['foo', 'create']))
@mock.patch.object(solum_client, "get_client")
def test_env_parsing(self, mock_get_client):
parser = solumclient.solum.PermissiveParser()
self.make_env()
self.fake_argv()
FakeCommands(parser)
mock_get_client.assert_called_once_with(
self.output['solum_api_version'], **self.output)
class FakeCommands(cli_utils.CommandsBase):
"""Fake command class."""
def create(self):
"""Fake Create Method."""
return

View File

@ -1,51 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import exceptions
from solumclient.common import exc
from solumclient.tests import base
class FakeResponse(object):
json_data = {}
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def json(self):
return self.json_data
class ExceptionTest(base.TestCase):
def test_from_response_with_status_code_404(self):
json_data = {"faultstring": "fake message",
"debuginfo": "fake details"}
method = 'GET'
status_code = 404
url = 'http://example.com:9777/v1/assemblies/fake-id'
ex = exc.from_response(
FakeResponse(status_code=status_code,
headers={"Content-Type": "application/json"},
json_data=json_data),
method,
url
)
self.assertIsInstance(ex, exceptions.HttpError)
self.assertEqual(json_data["faultstring"], ex.message)
self.assertEqual(json_data["debuginfo"], ex.details)
self.assertEqual(method, ex.method)
self.assertEqual(url, ex.url)
self.assertEqual(status_code, ex.http_status)

View File

@ -1,164 +0,0 @@
# Copyright (c) 2015 Rackspace
#
# 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
from unittest import mock
from solumclient.common import github
from solumclient.tests import base
class TestGitHubAuth(base.TestCase):
fake_repo = "http://github.com/fakeuser/fakerepo.git"
fake_trigger = "http://example.com/trigger/1"
fake_username = 'fakeuser'
fake_password = 'fakepassword'
fake_token = 'faketoken'
def test_invalid_repo(self):
self.assertRaises(ValueError,
github.GitHubAuth,
"http://example.com")
def test_auth_header_username_password(self):
gha = github.GitHubAuth(self.fake_repo,
username=self.fake_username,
password=self.fake_password)
# base64.b64encode('fakeuser:fakepassword') yields 'ZmFrZX...'
expected_auth_header = {
'Content-Type': 'application/json',
'Authorization': 'Basic ZmFrZXVzZXI6ZmFrZXBhc3N3b3Jk',
}
self.assertEqual(expected_auth_header, gha.auth_header)
@mock.patch('getpass.getpass')
def test_auth_header_username_password_2fa(self, fake_getpass):
gha = github.GitHubAuth(self.fake_repo,
username=self.fake_username,
password=self.fake_password)
gha._otp_required = True
fake_getpass.return_value = 'fakeonetime'
expected_auth_header = {
'Content-Type': 'application/json',
'Authorization': 'Basic ZmFrZXVzZXI6ZmFrZXBhc3N3b3Jk',
'x-github-otp': 'fakeonetime',
}
self.assertEqual(expected_auth_header, gha.auth_header)
def test_auth_header_repo_token(self):
gha = github.GitHubAuth(self.fake_repo,
repo_token=self.fake_token)
expected_auth_header = {
'Content-Type': 'application/json',
'Authorization': 'token %s' % self.fake_token,
}
self.assertEqual(expected_auth_header, gha.auth_header)
@mock.patch('httplib2.Http.request')
def test_create_webhook(self, fake_request):
gha = github.GitHubAuth(self.fake_repo,
repo_token=self.fake_token)
fake_request.return_value = ({'status': '200'},
'{"token": "%s"}' % self.fake_token)
gha.create_repo_token = mock.MagicMock()
gha.create_repo_token.return_value = 'token123'
gha.create_webhook(self.fake_trigger)
fake_request.assert_called_once_with(
'https://api.github.com/repos/fakeuser/fakerepo/hooks',
'POST',
headers=mock.ANY,
body=mock.ANY)
expected_body = {
"config": {
"url": self.fake_trigger,
"content_type": "json"},
"name": "web",
"events": ["pull_request", "commit_comment"]}
actual_body = json.loads(fake_request.call_args[1]['body'])
self.assertEqual(expected_body, actual_body)
@mock.patch('httplib2.Http.request')
def test_create_webhook_unittest_only(self, fake_request):
gha = github.GitHubAuth(self.fake_repo,
username=self.fake_username,
password=self.fake_password)
fake_request.return_value = ({'status': '200'},
'{"token": "foo"}')
gha.create_repo_token = mock.MagicMock()
gha.create_repo_token.return_value = 'token123'
gha.create_webhook(self.fake_trigger, workflow=['unittest'])
fake_request.assert_called_once_with(
'https://api.github.com/repos/fakeuser/fakerepo/hooks',
'POST',
headers=mock.ANY,
body=mock.ANY)
expected_body = {
"config": {
"url": self.fake_trigger + "?workflow=unittest",
"content_type": "json"},
"name": "web",
"events": ["pull_request", "commit_comment"]}
actual_body = json.loads(fake_request.call_args[1]['body'])
self.assertEqual(expected_body, actual_body)
@mock.patch('httplib2.Http.request')
def test_create_webhook_unittest_build(self, fake_request):
gha = github.GitHubAuth(self.fake_repo,
username=self.fake_username,
password=self.fake_password)
fake_request.return_value = ({'status': '200'},
'{"token": "foo"}')
gha.create_repo_token = mock.MagicMock()
gha.create_repo_token.return_value = 'token123'
gha.create_webhook(self.fake_trigger, workflow=['unittest', 'build'])
fake_request.assert_called_once_with(
'https://api.github.com/repos/fakeuser/fakerepo/hooks',
'POST',
headers=mock.ANY,
body=mock.ANY)
expected_body = {
"config": {
"url": self.fake_trigger + "?workflow=unittest+build",
"content_type": "json"},
"name": "web",
"events": ["pull_request", "commit_comment"]}
actual_body = json.loads(fake_request.call_args[1]['body'])
self.assertEqual(expected_body, actual_body)
@mock.patch('httplib2.Http.request')
def test_add_ssh_key(self, fake_request):
gha = github.GitHubAuth(self.fake_repo,
username=self.fake_username,
password=self.fake_password)
fake_request.return_value = ({'status': '200'},
'{"token": "foo"}')
fake_pub_key = 'foo'
gha.add_ssh_key(public_key=fake_pub_key)
fake_request.assert_called_once_with(
'https://api.github.com/user/keys',
'POST',
headers=mock.ANY,
body=mock.ANY)
expected_body = {"key": "foo", "title": "devops@Solum"}
actual_body = json.loads(fake_request.call_args[1]['body'])
self.assertEqual(expected_body, actual_body)

View File

@ -1,48 +0,0 @@
# 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.
from unittest import mock
import yaml
from solumclient.common import yamlutils
from solumclient.tests import base
class TestYamlUtils(base.TestCase):
def setUp(self):
super(TestYamlUtils, self).setUp()
def test_load_yaml(self):
yml_dict = yamlutils.load('a: x\nb: y\n')
self.assertEqual({'a': 'x', 'b': 'y'}, yml_dict)
def test_load_empty_yaml(self):
self.assertRaises(ValueError, yamlutils.load, '{}')
def test_load_empty_list(self):
yml_dict = yamlutils.load('[]')
self.assertEqual([], yml_dict)
def test_load_invalid_yaml_syntax(self):
self.assertRaises(ValueError, yamlutils.load, "}invalid: y'm'l3!")
def test_load_invalid_yaml_type(self):
self.assertRaises(ValueError, yamlutils.load, 'invalid yaml type')
@mock.patch('solumclient.common.yamlutils.yaml.dump')
def test_dump_yaml(self, dump):
if hasattr(yaml, 'CSafeDumper'):
yaml_dumper = yaml.CSafeDumper
else:
yaml_dumper = yaml.SafeDumper
yamlutils.dump('version: 1')
dump.assert_called_with('version: 1', Dumper=yaml_dumper)

View File

@ -1,31 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from unittest import mock
from solumclient import client
from solumclient.common.apiclient import exceptions
from solumclient.common import auth
from solumclient.tests import base
class ClientTest(base.TestCase):
def test_client_unsupported_version(self):
self.assertRaises(exceptions.UnsupportedVersion,
client.Client, '111.11', **{})
def test_client(self):
with mock.patch.object(auth, 'KeystoneAuthPlugin'):
client.Client('1', **{})

View File

@ -1,458 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from io import StringIO
from unittest import mock
import collections
import re
import sys
import fixtures
from oslo_utils import uuidutils
from stevedore import extension
import testtools
from testtools import matchers
from solumclient import client
from solumclient.common.apiclient import auth
from solumclient.common import yamlutils
from solumclient import solum
from solumclient.tests import base
from solumclient.v1 import component
from solumclient.v1 import languagepack
from solumclient.v1 import pipeline
from solumclient.v1 import plan
FAKE_ENV = {'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'tenant_name',
'OS_AUTH_URL': 'http://no.where'}
languagepack_file_data = (
'{"language-pack-type":"Java", "language-pack-name":"Java version 1.4."}')
class MockEntrypoint(object):
def __init__(self, name, plugin):
self.name = name
self.plugin = plugin
class BaseFakePlugin(auth.BaseAuthPlugin):
def _do_authenticate(self, http_client):
pass
def token_and_endpoint(self, endpoint_type, service_type):
pass
class TestSolum(base.TestCase):
"""Test the Solum CLI."""
re_options = re.DOTALL | re.MULTILINE
# Patch os.environ to avoid required auth info.
def make_env(self, exclude=None):
env = dict((k, v) for k, v in FAKE_ENV.items() if k != exclude)
self.useFixture(fixtures.MonkeyPatch('os.environ', env))
@mock.patch.object(extension.ExtensionManager, "map")
def shell(self, argstr, mock_mgr_map, exit_code=0):
class FakePlugin(BaseFakePlugin):
def authenticate(self, cls):
cls.request(
"POST", "http://auth/tokens",
json={"fake": "me"}, allow_redirects=True)
mock_mgr_map.side_effect = (
lambda func: func(MockEntrypoint("fake", FakePlugin)))
orig = sys.stdout
try:
sys.stdout = StringIO()
argv = [__file__, ]
argv.extend(argstr.split())
self.useFixture(
fixtures.MonkeyPatch('sys.argv', argv))
solum.main()
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertEqual(exit_code, exc_value.code)
finally:
out = sys.stdout.getvalue()
sys.stdout.close()
sys.stdout = orig
return out
@testtools.skip("Skip it when found solution")
def test_get_client_debug(self):
env = dict((k, v) for k, v in FAKE_ENV.items() if k is not None)
test_client = client.get_client('1', **env)
self.assertFalse(test_client.http_client.debug)
test_client = client.get_client('1', debug=True)
self.assertTrue(test_client.http_client.debug)
@testtools.skip("Skip it when found solution")
def test_get_client_insecure(self):
env = dict((k, v) for k, v in FAKE_ENV.items() if k is not None)
test_client = client.get_client('1', **env)
self.assertTrue(test_client.http_client.verify)
test_client = client.get_client('1', verify=False)
self.assertFalse(test_client.http_client.verify)
def test_help(self):
required = [
'.*?^Available commands:'
]
help_text = self.shell('--help')
for r in required:
self.assertThat(help_text,
matchers.MatchesRegex(r,
self.re_options))
# Workflow Tests #
def test_workflow_error(self):
self.make_env()
out = self.shell("workflow unknown-command")
self.assertIn("Available commands", out)
# Pipeline Tests #
@mock.patch.object(pipeline.PipelineManager, "list")
def test_pipeline_list(self, mock_pipeline_list):
self.make_env()
self.shell("pipeline list")
mock_pipeline_list.assert_called_once_with()
@mock.patch.object(pipeline.PipelineManager, "create")
def test_pipeline_create(self, mock_pipeline_create):
self.make_env()
self.shell("pipeline create http://example.com/a.yaml workbook test")
mock_pipeline_create.assert_called_once_with(
name='test',
workbook_name='workbook',
plan_uri='http://example.com/a.yaml')
@mock.patch.object(pipeline.PipelineManager, "create")
def test_pipeline_create_without_name(self, mock_pipeline_create):
self.make_env()
self.shell("pipeline create http://example.com/a.yaml workbook",
exit_code=2)
@mock.patch.object(plan.PlanManager, "find")
@mock.patch.object(pipeline.PipelineManager, "create")
def test_pipeline_create_with_plan_name(self, mock_pipeline_create,
mock_plan_find):
class FakePlan(object):
uri = 'http://example.com/the-plan.yaml'
self.make_env()
mock_plan_find.return_value = FakePlan()
self.shell("pipeline create the-plan-name workbook test")
mock_plan_find.assert_called_once_with(name_or_id='the-plan-name')
mock_pipeline_create.assert_called_once_with(
name='test',
workbook_name='workbook',
plan_uri='http://example.com/the-plan.yaml')
@mock.patch.object(pipeline.PipelineManager, "delete")
@mock.patch.object(pipeline.PipelineManager, "find")
def test_pipeline_delete(self, mock_pipeline_find, mock_pipeline_delete):
self.make_env()
the_id = uuidutils.generate_uuid()
self.shell("pipeline delete %s" % the_id)
mock_pipeline_find.assert_called_once_with(
name_or_id=the_id)
self.assertEqual(1, mock_pipeline_delete.call_count)
@mock.patch.object(pipeline.PipelineManager, "find")
def test_pipeline_get(self, mock_pipeline_find):
self.make_env()
the_id = uuidutils.generate_uuid()
self.shell("pipeline show %s" % the_id)
mock_pipeline_find.assert_called_once_with(name_or_id=the_id)
@mock.patch.object(pipeline.PipelineManager, "find")
def test_pipeline_get_by_name(self, mock_pipeline_find):
self.make_env()
self.shell("pipeline show app2")
mock_pipeline_find.assert_called_once_with(name_or_id='app2')
# App Tests #
def test_app_create_with_missing_workflow_config(self):
raw_data = 'version: 1\nname: ex_app\nworkflow_config:\n'
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("app create --app-file /dev/null")
self.assertEqual("ERROR: Workflow config cannot be empty\n", out)
def test_app_create_with_missing_trigger_actions(self):
raw_data = 'version: 1\nname: ex_app\ntrigger_actions:\n'
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("app create --app-file /dev/null")
self.assertEqual("ERROR: Trigger actions cannot be empty\n", out)
def test_app_create_with_bad_name(self):
raw_data = '\n'.join([
'version: 1',
'name: ex=app1',
'description: python web app',
'workflow_config:',
' run_cmd: python app.py',
'ports: [5000]'])
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("app create --app-file /dev/null")
self.assertEqual("ERROR: Application name must be 1-100 "
"characters long, only contain a-z,0-9,-,_ and "
"start with an alphabet character.\n", out)
# OldApp Tests #
def test_oldapp_create_with_missing_artifacts(self):
raw_data = 'version: 1\nname: ex_plan1\ndescription: dsc1.'
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("oldapp create --plan-file /dev/null")
self.assertEqual("ERROR: Missing artifacts section\n", out)
def test_oldapp_create_with_bad_name(self):
raw_data = '\n'.join([
'version: 1',
'name: ex=plan1',
'description: python web app',
'artifacts:',
'- name: web',
' content:',
' href: https://github.com/user/repo.git',
' language_pack: auto',
' unittest_cmd: ./unit_tests.sh',
' run_cmd: python app.py',
' ports: 5000'])
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("oldapp create --plan-file /dev/null")
self.assertEqual("ERROR: Application name must be 1-100 "
"characters long, only contain a-z,0-9,-,_ and "
"start with an alphabet character.\n", out)
@testtools.skip("Skip it when found solution")
def test_oldapp_create_with_bad_artifact_name(self):
raw_data = '\n'.join([
'version: 1',
'name: explan1',
'description: python web app',
'artifacts:',
'- name:',
' content:',
' href: https://github.com/user/repo.git',
' language_pack: auto',
' unittest_cmd: ./unit_tests.sh',
' run_cmd: python app.py',
' ports: 5000'])
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("oldapp create --plan-file /dev/null")
# No part of the plan is in error; the next step in the test
# is authorization, which is deliberately mocked.
self.assertIn("ERROR: Authorization Failed:", out)
self.assertIn("http://no.where/tokens", out)
def test_oldapp_create_with_artifacts_empty(self):
raw_data = 'version: 1\nname: ex_plan1\ndescription: dsc1.\nartifacts:'
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("oldapp create --plan-file /dev/null")
self.assertEqual("ERROR: Artifacts cannot be empty\n", out)
def test_oldapp_create_with_artifacts_no_content(self):
raw_data = 'version: 1\nname: ex_plan1\ndescription: d1.\nartifacts:\n'
raw_data += '- name: asdfds\n'
raw_data += ' artifact_type: heroku\n'
raw_data += ' language_pack: lp'
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
out = self.shell("oldapp create --plan-file /dev/null")
self.assertEqual("ERROR: Artifact content missing\n", out)
# Plan Tests #
@mock.patch.object(plan.PlanManager, "create")
def test_plan_create(self, mock_plan_create):
FakeResource = collections.namedtuple("FakeResource",
"uuid name description uri")
mock_plan_create.return_value = FakeResource('foo', 'foo', 'foo',
'foo')
raw_data = 'version: 1\nname: ex_plan1\ndescription: dsc1.'
plan_data = yamlutils.dump(yamlutils.load(raw_data))
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
self.shell("plan create /dev/null")
mock_plan_create.assert_called_once_with(plan_data)
@mock.patch.object(plan.PlanManager, "create")
def test_plan_create_with_private_github_repo(self, mock_plan_create):
FakeResource = collections.namedtuple(
"FakeResource", "uuid name description uri artifacts")
mock_plan_create.return_value = FakeResource('foo', 'foo', 'foo',
'foo', 'artifacts')
expected_printed_dict_args = mock_plan_create.return_value._asdict()
expected_printed_dict_args.pop('artifacts')
raw_data = 'version: 1\nname: ex_plan1\ndescription: dsc1.'
plan_data = yamlutils.dump(yamlutils.load(raw_data))
mopen = mock.mock_open(read_data=raw_data)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
self.shell("plan create /dev/null")
mock_plan_create.assert_called_once_with(plan_data)
@mock.patch.object(plan.PlanManager, "list")
def test_plan_list(self, mock_plan_list):
self.make_env()
self.shell("plan list")
mock_plan_list.assert_called_once_with()
@mock.patch.object(plan.PlanManager, "delete")
@mock.patch.object(plan.PlanManager, "find")
def test_plan_delete(self, mock_plan_find, mock_plan_delete):
self.make_env()
the_id = uuidutils.generate_uuid()
self.shell("plan delete %s" % the_id)
mock_plan_find.assert_called_once_with(name_or_id=the_id)
self.assertEqual(1, mock_plan_delete.call_count)
@mock.patch.object(plan.PlanManager, "find")
def test_plan_get(self, mock_plan_find):
self.make_env()
the_id = uuidutils.generate_uuid()
self.shell("plan show %s" % the_id)
mock_plan_find.assert_called_once_with(name_or_id=the_id)
@mock.patch.object(plan.PlanManager, "find")
def test_plan_get_private_github_repo(self, mock_plan_find):
self.make_env()
the_id = uuidutils.generate_uuid()
FakeResource = collections.namedtuple(
"FakeResource", "uuid name description uri artifacts")
mock_plan_find.return_value = FakeResource('foo', 'foo', 'foo', 'foo',
'artifacts')
self.shell("plan show %s" % the_id)
mock_plan_find.assert_called_once_with(name_or_id=the_id)
# LanguagePack Tests #
@mock.patch.object(languagepack.LanguagePackManager, "list")
def test_languagepack_list(self, mock_lp_list):
self.make_env()
self.shell("languagepack list")
self.assertEqual(1, mock_lp_list.call_count)
@mock.patch.object(languagepack.LanguagePackManager, "create")
def test_languagepack_create(self, mock_lp_create):
FakeResource = collections.namedtuple("FakeResource",
"uuid name description "
"compiler_versions os_platform")
mock_lp_create.return_value = FakeResource(
'foo', 'foo', 'foo', 'foo', 'foo')
lp_metadata = '{"OS": "Ubuntu"}'
mopen = mock.mock_open(read_data=lp_metadata)
with mock.patch('%s.open' % solum.__name__, mopen, create=True):
self.make_env()
self.shell("languagepack create lp_name github.com/test "
"--lp_metadata=/dev/null")
mock_lp_create.assert_called_once_with(
name='lp_name',
source_uri='github.com/test',
lp_metadata=lp_metadata,
lp_params={})
@mock.patch.object(languagepack.LanguagePackManager, "delete")
def test_languagepack_delete(self, mock_lp_delete):
self.make_env()
self.shell("languagepack delete fake-lp-id")
mock_lp_delete.assert_called_once_with(lp_id='fake-lp-id')
@mock.patch.object(languagepack.LanguagePackManager, "find")
def test_languagepack_get(self, mock_lp_get):
self.make_env()
self.shell("languagepack show fake-lp-id1")
mock_lp_get.assert_called_once_with(name_or_id='fake-lp-id1')
# Component Tests #
@mock.patch.object(component.ComponentManager, "list")
def test_component_list(self, mock_component_list):
self.make_env()
self.shell("component list")
mock_component_list.assert_called_once_with()
@mock.patch.object(component.ComponentManager, "find")
def test_component_get(self, mock_component_find):
self.make_env()
the_id = uuidutils.generate_uuid()
self.shell("component show %s" % the_id)
mock_component_find.assert_called_once_with(name_or_id=the_id)
@mock.patch.object(component.ComponentManager, "find")
def test_component_get_by_name(self, mock_component_find):
self.make_env()
self.shell("component show comp1")
mock_component_find.assert_called_once_with(name_or_id='comp1')
def test_transform_git_url(self):
private_uri = 'git@github.com:solum/python.git'
public_uri = 'https://github.com/solum/python.git'
private = True
public = False
result = solum.transform_git_url(private_uri, private)
self.assertEqual(result, private_uri)
result = solum.transform_git_url(public_uri, private)
self.assertEqual(result, private_uri)
result = solum.transform_git_url(private_uri, public)
self.assertEqual(result, public_uri)
result = solum.transform_git_url(public_uri, public)
self.assertEqual(result, public_uri)

View File

@ -1,194 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import client
from solumclient.common.apiclient import exceptions
from solumclient.common.apiclient import fake_client
from solumclient.tests import base
from solumclient.v1 import component
component_list = [
{
'uri': 'http://example.com/v1/components/c1',
'name': 'php-web-app',
'type': 'component',
'description': 'A php web application component',
'tags': ['web_app'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'assembly_link': {
' href': 'http://example.com:9777/v1/assembly/a1',
'target_name': 'a1'},
'service_links': [{
'href': 'http://example.com:9777/v1/services/s1',
'target_name': 's1'}],
'operations_uri': 'http://example.com:9777/v1/operations/o1',
'sensors_uri': 'http://example.com:9777/v1/sensors/s1'
},
{
'uri': 'http://example.com/v1/components/c2',
'name': 'mysql-db',
'type': 'component',
'description': 'A mysql db component',
'tags': ['database'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'assembly_link': {
'href': 'http://example.com:9777/v1/assembly/a2',
'target_name': 'a2'},
'service_links': [{
'href': 'http://example.com:9777/v1/services/s2',
'target_name': 's2'}],
'operations_uri': 'http://example.com:9777/v1/operations/o2',
'sensors_uri': 'http://example.com:9777/v1/sensors/s2'
}
]
component_fixture = {
'uri': 'http://example.com/v1/components/c1',
'name': 'mysql-db',
'type': 'component',
'description': 'A mysql db component',
'tags': ['database'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'assembly_link': {
'href': 'http://example.com:9777/v1/assembly/a1',
'target_name': 'a1'},
'service_links': [{
'href': 'http://example.com:9777/v1/services/s1',
'target_name': 's1'}],
'operations_uri': 'http://example.com:9777/v1/operations/o1',
'sensors_uri': 'http://example.com:9777/v1/sensors/o2'
}
fixtures_list = {
'/v1/components': {
'GET': (
{},
component_list
),
}
}
fixtures_get = {
'/v1/components/c1': {
'GET': (
{},
component_fixture
),
}
}
fixtures_create = {
'/v1/components': {
'POST': (
{},
component_fixture
),
}
}
fixtures_put = {
'/v1/components/c1': {
'PUT': (
{},
component_fixture
),
}
}
class ComponentManagerTest(base.TestCase):
def test_list_all(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
self.mgr = component.ComponentManager(api_client)
components = self.mgr.list()
self.assertEqual(2, len(components))
self.assertIn('Component', repr(components[0]))
self.assertEqual('http://example.com/v1/components/c1',
components[0].uri)
self.assertEqual('http://example.com/v1/components/c2',
components[1].uri)
def test_create(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_create)
api_client = client.BaseClient(fake_http_client)
mgr = component.ComponentManager(api_client)
component_obj = mgr.create()
self.assertIn('Component', repr(component_obj))
self.assertEqual('http://example.com/v1/components/c1',
component_obj.uri)
self.assertEqual('component',
component_obj.type)
self.assertEqual('1dae5a09ef2b4d8cbf3594b0eb4f6b94',
component_obj.project_id)
self.assertEqual('55f41cf46df74320b9486a35f5d28a11',
component_obj.user_id)
def test_get(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_get)
api_client = client.BaseClient(fake_http_client)
mgr = component.ComponentManager(api_client)
component_obj = mgr.get(component_id='c1')
self.assertIn('Component', repr(component_obj))
self.assertEqual('http://example.com/v1/components/c1',
component_obj.uri)
self.assertEqual('component',
component_obj.type)
self.assertEqual('1dae5a09ef2b4d8cbf3594b0eb4f6b94',
component_obj.project_id)
self.assertEqual('55f41cf46df74320b9486a35f5d28a11',
component_obj.user_id)
def test_put(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_put)
api_client = client.BaseClient(fake_http_client)
mgr = component.ComponentManager(api_client)
component_obj = mgr.put(component_id='c1')
self.assertIn('Component', repr(component_obj))
self.assertEqual('http://example.com/v1/components/c1',
component_obj.uri)
self.assertEqual('component',
component_obj.type)
self.assertEqual('1dae5a09ef2b4d8cbf3594b0eb4f6b94',
component_obj.project_id)
self.assertEqual('55f41cf46df74320b9486a35f5d28a11',
component_obj.user_id)
def test_find_one(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = component.ComponentManager(api_client)
components = mgr.findall(name='php-web-app')
self.assertEqual(1, len(components))
self.assertIn('Component', repr(components[0]))
self.assertEqual(component_list[0]['uri'], components[0].uri)
def test_find_one_only(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = component.ComponentManager(api_client)
result = mgr.find(name_or_id='php-web-app')
self.assertEqual(component_list[0]['uri'], result.uri)
def test_find_none(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = component.ComponentManager(api_client)
self.assertRaises(exceptions.NotFound, mgr.find, name_or_id='test')

View File

@ -1,161 +0,0 @@
# Copyright 2014 - Rackspace
#
# 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.
from solumclient.builder.v1 import image
from solumclient.common.apiclient import client
from solumclient.common.apiclient import fake_client
from solumclient.tests import base
from solumclient.v1 import languagepack
languagepack_list = [
{
'uri': 'http://example.com/v1/language_packs/x1',
'name': 'database',
'language_pack_type': 'python',
'description': 'Python Language pack',
'tags': ['python'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'language_implementation': 'python',
'compiler_versions': '2.6',
'os_platform': 'ubuntu 12.04',
'attr_blob': '',
'service_id': 1
},
{
'uri': 'http://example.com/v1/language_packs/x2',
'name': 'database',
'language_pack_type': 'java',
'description': 'Java Language pack',
'tags': ['java'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'language_implementation': 'java',
'compiler_versions': '7.0',
'os_platform': 'ubuntu 12.04',
'attr_blob': '',
'service_id': 1
}
]
languagepack_fixture = {
'uri': 'http://example.com/v1/language_packs/x1',
'name': 'database',
'language_pack_type': 'java',
'description': 'Java Language pack',
'tags': ['java'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'language_implementation': 'java',
'compiler_versions': '7.0',
'os_platform': 'ubuntu 12.04',
'attr_blob': '',
'service_id': 1
}
fixtures_list = {
'/v1/language_packs': {
'GET': (
{},
languagepack_list
),
}
}
fixtures_get = {
'/v1/language_packs/x1': {
'GET': (
{},
languagepack_fixture
),
}
}
fixtures_create = {
'/v1/language_packs': {
'POST': (
{},
languagepack_fixture
),
}
}
image_fixture = {
'name': 'lp1',
'source_uri': 'github.com/test',
'lp_metadata': 'sample_lp_metadata'
}
fixtures_build = {
'/v1/images': {
'POST': (
{},
image_fixture
),
}
}
class LanguagePackManagerTest(base.TestCase):
def assert_lp_object(self, lp_obj):
self.assertIn('LanguagePack', repr(lp_obj))
self.assertEqual(languagepack_fixture['uri'], lp_obj.uri)
self.assertEqual(languagepack_fixture['language_pack_type'],
lp_obj.language_pack_type)
self.assertEqual(languagepack_fixture['project_id'], lp_obj.project_id)
self.assertEqual(languagepack_fixture['user_id'], lp_obj.user_id)
def test_list_all(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = languagepack.LanguagePackManager(api_client)
languagepacks = mgr.list()
self.assertEqual(2, len(languagepacks))
self.assertIn('LanguagePack', repr(languagepacks[0]))
self.assertEqual(languagepack_list[0]['uri'],
languagepacks[0].uri)
self.assertEqual(languagepack_list[1]['uri'],
languagepacks[1].uri)
def test_create(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_create)
api_client = client.BaseClient(fake_http_client)
mgr = languagepack.LanguagePackManager(api_client)
languagepack_obj = mgr.create()
self.assert_lp_object(languagepack_obj)
def test_get(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_get)
api_client = client.BaseClient(fake_http_client)
mgr = languagepack.LanguagePackManager(api_client)
languagepack_obj = mgr.get(lp_id='x1')
self.assert_lp_object(languagepack_obj)
def test_build(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_build)
api_client = client.BaseClient(fake_http_client)
mgr = image.ImageManager(api_client)
image_obj = mgr.create(name='lp1',
source_uri='github.com/test',
lp_metadata='sample_lp_metadata')
self.assert_image_object(image_obj)
def assert_image_object(self, image_obj):
self.assertIn('Image', repr(image_obj))
self.assertEqual(image_fixture['source_uri'], image_obj.source_uri)
self.assertEqual(image_fixture['name'], image_obj.name)
self.assertEqual(image_fixture['lp_metadata'], image_obj.lp_metadata)

View File

@ -1,183 +0,0 @@
# Copyright 2014 - Rackspace Hosting
#
# 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.
from solumclient.common.apiclient import client
from solumclient.common.apiclient import exceptions
from solumclient.common.apiclient import fake_client
from solumclient.tests import base
from solumclient.v1 import pipeline
pipeline_list = [
{
'uri': 'http://example.com/v1/pipelines/x1',
'name': 'database',
'type': 'pipeline',
'description': 'A mysql database',
'tags': ['small'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'component_links': [{
'href': 'http://example.com:9777/v1/components/x1',
'target_name': 'x1'}],
'operations_uri': 'http://example.com:9777/v1/operations/o1',
'sensors_uri': 'http://example.com:9777/v1/sensors/s1'
},
{
'uri': 'http://example.com/v1/pipelines/x2',
'name': 'load_balancer',
'type': 'pipeline',
'description': 'A load balancer',
'tags': ['small'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'component_links': [{
'href': 'http://example.com:9777/v1/components/x2',
'target_name': 'x2'}],
'operations_uri': 'http://example.com:9777/v1/operations/o2',
'sensors_uri': 'http://example.com:9777/v1/sensors/s2'
}
]
pipeline_fixture = {
'uri': 'http://example.com/v1/pipelines/x1',
'name': 'database',
'type': 'pipeline',
'description': 'A mysql database',
'tags': ['small'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'component_links': [{
'href': 'http://example.com:9777/v1/components/x1',
'target_name': 'x1'}],
'operations_uri': 'http://example.com:9777/v1/operations/o1',
'sensors_uri': 'http://example.com:9777/v1/sensors/s1'
}
fixtures_list = {
'/v1/pipelines': {
'GET': (
{},
pipeline_list
),
}
}
fixtures_get = {
'/v1/pipelines/x1': {
'GET': (
{},
pipeline_fixture
),
}
}
fixtures_create = {
'/v1/pipelines': {
'POST': (
{},
pipeline_fixture
),
}
}
fixtures_put = {
'/v1/pipelines/x1': {
'PUT': (
{},
pipeline_fixture
),
}
}
fixtures_delete = {
'/v1/pipelines/x1': {
'DELETE': (
{},
{},
),
}
}
class PipelineManagerTest(base.TestCase):
def assert_pipeline_object(self, pipeline_obj):
self.assertIn('Pipeline', repr(pipeline_obj))
self.assertEqual(pipeline_fixture['uri'], pipeline_obj.uri)
self.assertEqual(pipeline_fixture['type'], pipeline_obj.type)
self.assertEqual(pipeline_fixture['project_id'],
pipeline_obj.project_id)
self.assertEqual(pipeline_fixture['user_id'], pipeline_obj.user_id)
def test_list_all(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
pipelines = mgr.list()
self.assertEqual(2, len(pipelines))
self.assertIn('Pipeline', repr(pipelines[0]))
self.assertEqual(pipeline_list[0]['uri'], pipelines[0].uri)
self.assertEqual(pipeline_list[1]['uri'], pipelines[1].uri)
def test_find_one(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
pipelines = mgr.findall(name='database')
self.assertEqual(1, len(pipelines))
self.assertIn('Pipeline', repr(pipelines[0]))
self.assertEqual(pipeline_list[0]['uri'], pipelines[0].uri)
def test_find_one_only(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
result = mgr.find(name_or_id='database')
self.assertEqual(pipeline_list[0]['uri'], result.uri)
def test_find_none(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
self.assertRaises(exceptions.NotFound, mgr.find, name_or_id='what')
def test_create(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_create)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
pipeline_obj = mgr.create()
self.assert_pipeline_object(pipeline_obj)
def test_get(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_get)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
pipeline_obj = mgr.get(pipeline_id='x1')
self.assert_pipeline_object(pipeline_obj)
def test_put(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_put)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
pipeline_obj = mgr.put(pipeline_id='x1')
self.assert_pipeline_object(pipeline_obj)
def test_delete(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_delete)
api_client = client.BaseClient(fake_http_client)
mgr = pipeline.PipelineManager(api_client)
mgr.delete(pipeline_id='x1')
fake_http_client.assert_called('DELETE', '/v1/pipelines/x1')

View File

@ -1,201 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from unittest import mock
from solumclient.common.apiclient import client
from solumclient.common.apiclient import fake_client
from solumclient.tests import base
from solumclient.v1 import plan
plan_list = [
{
'name': 'Example plan 1',
'artifacts': (
[{'name': 'My python app',
'artifact_type': 'git_pull',
'content': {'href': 'git://example.com/project.git'},
'requirements': [{
'requirement_type': 'git_pull',
'language_pack': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'fulfillment': '1dae5a09ef2b4d8cbf3594b0eb4f6b94'}]}]),
'services': [{'name': 'Build Service',
'id': 'build',
'characteristics': ['python_build_service']}],
'description': 'A plan with no services or artifacts shown'
},
{
'name': 'Example plan 2',
'artifacts': (
[{'name': 'My java app',
'artifact_type': 'git_pull',
'content': {'href': 'git://example.com/project.git'},
'requirements': [{
'requirement_type': 'git_pull',
'language_pack': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'fulfillment': '1dae5a09ef2b4d8cbf3594b0eb4f6b94'}]}]),
'services': [{'name': 'Build Service',
'id': 'build',
'characteristics': ['python_build_service']}],
'description': 'A plan with no services or artifacts shown'
},
]
artifacts = [{'name': 'My python app',
'artifact_type': 'git_pull',
'content': {'href': 'git://example.com/project.git'},
'requirements': [{
'requirement_type': 'git_pull',
'language_pack': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'fulfillment': '1dae5a09ef2b4d8cbf3594b0eb4f6b94'}]}]
services = [{'name': 'Build Service',
'id': 'build',
'characteristics': ['python_build_service']}]
plan_fixture = {
'uri': 'http://example.com/v1/plans/p1',
'name': 'Example plan',
'type': 'plan',
'tags': ['small'],
'artifacts': artifacts,
'services': services,
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'description': 'A plan with no services or artifacts shown'
}
fixtures_list = {
'/v1/plans': {
'GET': (
{},
plan_list
),
}
}
fixtures_list_empty = {
'/v1/plans': {
'GET': (
{},
[]
),
}
}
fixtures_get = {
'/v1/plans/p1': {
'GET': (
{},
plan_fixture
),
}
}
fixtures_create = {
'/v1/plans': {
'POST': (
{},
plan_fixture
),
}
}
fixtures_put = {
'/v1/plans/p1': {
'PUT': (
{},
plan_fixture
),
}
}
class PlanManagerTest(base.TestCase):
def assert_plan_obj(self, plan_obj):
self.assertIn('Plan', repr(plan_obj))
self.assertIn('Artifact', repr(plan_obj.artifacts[0]))
self.assertIn('ServiceReference', repr(plan_obj.services[0]))
self.assertEqual(plan_fixture['uri'], plan_obj.uri)
self.assertEqual(plan_fixture['type'], plan_obj.type)
self.assertEqual(plan_fixture['project_id'], plan_obj.project_id)
self.assertEqual(plan_fixture['user_id'], plan_obj.user_id)
def test_list_all(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list)
api_client = client.BaseClient(fake_http_client)
plan.PlanManager(api_client)
# NOTE(stannie): will re-enable this test once
# https://bugs.launchpad.net/solum/+bug/1331093 is committed.
# FakeHTTPClient doesn't manage YAML properly but since this method
# will use the json content-type once implemented in the API, this can
# stay temporary disabled.
def test_list_empty(self):
fake_http_client = fake_client.FakeHTTPClient(
fixtures=fixtures_list_empty)
api_client = client.BaseClient(fake_http_client)
mgr = plan.PlanManager(api_client)
self.assertEqual([], mgr.list())
def test_create(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_create)
api_client = client.BaseClient(fake_http_client)
mgr = plan.PlanManager(api_client)
plan_obj = mgr.create('version: 1\nname: ex_plan1\ndescription: dsc1.')
self.assert_plan_obj(plan_obj)
def test_plan_create_post_failure(self):
api_client = mock.MagicMock()
api_client.post.side_effect = Exception("Bad data")
try:
mgr = plan.PlanManager(api_client)
mgr.create('version: 1\nname: ex_plan1\ndescription: dsc1.')
except Exception:
self.assertTrue(True)
def test_plan_create_post_success(self):
api_client = mock.MagicMock()
dummy_data = 'version: 1\nname: ex_plan1\ndescription: dsc1.'
response = mock.MagicMock()
setattr(response, 'content', dummy_data)
api_client.post.return_value = response
try:
mgr = plan.PlanManager(api_client)
plan_obj = mgr.create(dummy_data)
assert plan_obj is not None
assert plan_obj.name == 'ex_plan1'
assert plan_obj.description == 'dsc1.'
assert plan_obj.version == 1
except Exception:
self.assertFalse(True)
def test_get(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_get)
api_client = client.BaseClient(fake_http_client)
mgr = plan.PlanManager(api_client)
plan_obj = mgr.get(plan_id='p1')
self.assert_plan_obj(plan_obj)
def test_update(self):
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_put)
api_client = client.BaseClient(fake_http_client)
mgr = plan.PlanManager(api_client)
plan_obj = mgr.update('version: 1\nname: ex_plan1\ndescription: dsc1.',
plan_id='p1')
self.assert_plan_obj(plan_obj)

View File

@ -1,59 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import client
from solumclient.common.apiclient import fake_client
from solumclient.tests import base
from solumclient.v1 import platform
fixtures = {
'/v1': {
'GET': (
{},
{
'uri': 'http://example.com/v1',
'name': 'solum',
'type': 'platform',
'tags': ['solid'],
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
'user_id': '55f41cf46df74320b9486a35f5d28a11',
'description': 'solum native implementation',
'implementation_version': '2014.1.1',
'assemblies_uri': 'http://example.com:9777/v1/assemblies',
'services_uri': 'http://example.com:9777/v1/services',
'components_uri': 'http://example.com:9777/v1/components',
'extenstions_uri': 'http://example.com:9777/v1/extenstions'
}
),
}
}
class PlatformManagerTest(base.TestCase):
def setUp(self):
super(PlatformManagerTest, self).setUp()
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
api_client = client.BaseClient(fake_http_client)
self.mgr = platform.PlatformManager(api_client)
def test_get(self):
platform = self.mgr.get()
self.assertIn('Platform', repr(platform))
self.assertEqual('http://example.com/v1', platform.uri)
self.assertEqual('platform', platform.type)
self.assertEqual('1dae5a09ef2b4d8cbf3594b0eb4f6b94',
platform.project_id)
self.assertEqual('55f41cf46df74320b9486a35f5d28a11', platform.user_id)

View File

@ -1,65 +0,0 @@
# Copyright 2015 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import base as apiclient_base
from solumclient.common.apiclient import exceptions
from solumclient.common import base as solum_base
from solumclient.common import exc
from oslo_utils import uuidutils
class App(apiclient_base.Resource):
def __repr__(self):
return "<App %s>" % self._info
class AppManager(solum_base.CrudManager, solum_base.FindMixin):
resource_class = App
collection_key = 'apps'
key = 'app'
def list(self, **kwargs):
return super(AppManager, self).list(base_url="/v1", **kwargs)
def create(self, **kwargs):
return super(AppManager, self).create(base_url="/v1", **kwargs)
def get(self, **kwargs):
return super(AppManager, self).get(base_url="/v1", **kwargs)
def put(self, **kwargs):
return super(AppManager, self).put(base_url="/v1", **kwargs)
def patch(self, **kwargs):
return super(AppManager, self).patch(base_url="/v1", **kwargs)
def delete(self, **kwargs):
return super(AppManager, self).delete(base_url="/v1", **kwargs)
def find(self, **kwargs):
if 'app_id' in kwargs:
return super(AppManager, self).get(base_url="/v1", **kwargs)
elif 'name_or_id' in kwargs:
name_or_uuid = kwargs['name_or_id']
try:
if uuidutils.is_uuid_like(name_or_uuid):
return super(AppManager, self).get(
base_url="/v1",
app_id=name_or_uuid)
else:
return super(AppManager, self).findone(
name=name_or_uuid)
except exceptions.NoUniqueMatch:
raise exc.NotUnique(resource='App')

View File

@ -1,43 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient import client as solum_client
from solumclient.v1 import app
from solumclient.v1 import component
from solumclient.v1 import languagepack
from solumclient.v1 import pipeline
from solumclient.v1 import plan
from solumclient.v1 import platform
from solumclient.v1 import workflow
class Client(object):
"""Client for the Solum v1 API."""
service_type = "application_deployment"
def __init__(self, *args, **kwargs):
"""Initialize a new client for the Solum v1 API."""
if not kwargs.get('auth_plugin'):
kwargs['auth_plugin'] = solum_client.get_auth_plugin(**kwargs)
self.auth_plugin = kwargs.get('auth_plugin')
self.http_client = solum_client.construct_http_client(**kwargs)
self.apps = app.AppManager(self.http_client)
self.components = component.ComponentManager(self.http_client)
self.pipelines = pipeline.PipelineManager(self.http_client)
self.platform = platform.PlatformManager(self.http_client)
self.plans = plan.PlanManager(self.http_client)
self.languagepacks = languagepack.LanguagePackManager(self.http_client)
self.workflows = workflow.WorkflowManager(self.http_client)

View File

@ -1,59 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import base as apiclient_base
from solumclient.common.apiclient import exceptions
from solumclient.common import base as solum_base
from solumclient.common import exc
from oslo_utils import uuidutils
class Component(apiclient_base.Resource):
def __repr__(self):
return "<Component %s>" % self._info
class ComponentManager(solum_base.CrudManager, solum_base.FindMixin):
resource_class = Component
collection_key = 'components'
key = 'component'
def list(self, **kwargs):
return super(ComponentManager, self).list(base_url="/v1", **kwargs)
def create(self, **kwargs):
return super(ComponentManager, self).create(base_url="/v1", **kwargs)
def get(self, **kwargs):
return super(ComponentManager, self).get(base_url="/v1", **kwargs)
def put(self, **kwargs):
return super(ComponentManager, self).put(base_url="/v1", **kwargs)
def find(self, **kwargs):
if 'component_id' in kwargs:
return super(ComponentManager, self).get(base_url="/v1", **kwargs)
elif 'name_or_id' in kwargs:
name_or_uuid = kwargs['name_or_id']
try:
if uuidutils.is_uuid_like(name_or_uuid):
return super(ComponentManager, self).get(
base_url="/v1",
component_id=name_or_uuid)
else:
return super(ComponentManager, self).findone(
name=name_or_uuid)
except exceptions.NoUniqueMatch:
raise exc.NotUnique(resource='Component')

View File

@ -1,60 +0,0 @@
# Copyright 2014 - Rackspace
#
# 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.
from solumclient.common.apiclient import base as apiclient_base
from solumclient.common import base as solum_base
class LanguagePack(apiclient_base.Resource):
def __repr__(self):
return "<LanguagePack %s>" % self._info
class UserLog(apiclient_base.Resource):
def __repr__(self):
return "<Log %s>" % self._info
class LanguagePackManager(solum_base.CrudManager):
resource_class = LanguagePack
collection_key = 'language_packs'
key = 'lp'
def list(self, **kwargs):
return super(LanguagePackManager, self).list(base_url="/v1", **kwargs)
def create(self, **kwargs):
return super(LanguagePackManager,
self).create(base_url="/v1", **kwargs)
def get(self, **kwargs):
return super(LanguagePackManager,
self).get(base_url="/v1", **kwargs)
def delete(self, **kwargs):
return super(LanguagePackManager,
self).delete(base_url="/v1", **kwargs)
def find(self, **kwargs):
name_or_uuid = kwargs['name_or_id']
return super(LanguagePackManager, self).get(base_url="/v1",
lp_id=name_or_uuid)
def logs(self, **kwargs):
self.resource_class = UserLog
languagepack = self.find(name_or_id=kwargs['lp_id'])
kwargs['lp_id'] = languagepack.uuid
url = self.build_url(base_url="/v1", **kwargs)
url += '/logs/'
return self._list(url)

View File

@ -1,62 +0,0 @@
# Copyright 2014 - Rackspace Hosting
#
# 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.
from solumclient.common.apiclient import base as apiclient_base
from solumclient.common.apiclient import exceptions
from solumclient.common import base as solum_base
from solumclient.common import exc
from oslo_utils import uuidutils
class Pipeline(apiclient_base.Resource):
def __repr__(self):
return "<Pipeline %s>" % self._info
class PipelineManager(solum_base.CrudManager, solum_base.FindMixin):
resource_class = Pipeline
collection_key = 'pipelines'
key = 'pipeline'
def list(self, **kwargs):
return super(PipelineManager, self).list(base_url="/v1", **kwargs)
def create(self, **kwargs):
return super(PipelineManager, self).create(base_url="/v1", **kwargs)
def get(self, **kwargs):
return super(PipelineManager, self).get(base_url="/v1", **kwargs)
def put(self, **kwargs):
return super(PipelineManager, self).put(base_url="/v1", **kwargs)
def delete(self, **kwargs):
return super(PipelineManager, self).delete(base_url="/v1", **kwargs)
def find(self, **kwargs):
if 'pipeline_id' in kwargs:
return super(PipelineManager, self).get(base_url="/v1", **kwargs)
elif 'name_or_id' in kwargs:
name_or_uuid = kwargs['name_or_id']
try:
if uuidutils.is_uuid_like(name_or_uuid):
return super(PipelineManager, self).get(
base_url="/v1",
pipeline_id=name_or_uuid)
else:
return super(PipelineManager, self).findone(
name=name_or_uuid)
except exceptions.NoUniqueMatch:
raise exc.NotUnique(resource='Pipeline')

View File

@ -1,160 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import base as apiclient_base
from solumclient.common.apiclient import exceptions
from solumclient.common import base as solum_base
from solumclient.common import exc
from solumclient.common import yamlutils
from oslo_utils import uuidutils
class Requirement(apiclient_base.Resource):
def __repr__(self):
return "<Requirement %s>" % self._info
class ServiceReference(apiclient_base.Resource):
def __repr__(self):
return "<ServiceReference %s>" % self._info
class Artifact(apiclient_base.Resource):
def __repr__(self):
return "<Artifact %s>" % self._info
def _add_requirements_details(self, req_list):
return [Requirement(None, res, loaded=True)
for res in req_list if req_list]
def _add_details(self, info):
for (k, v) in info.items():
try:
if k == 'requirements':
v = self._add_requirements_details(v)
setattr(self, k, v)
self._info[k] = v
except AttributeError:
# In this case we already defined the attribute on the class
pass
class Plan(apiclient_base.Resource):
def __repr__(self):
return "<Plan %s>" % self._info
def _add_artifact_details(self, artf_list):
return [Artifact(None, res, loaded=True)
for res in artf_list if artf_list]
def _add_services_details(self, serv_list):
return [ServiceReference(None, res, loaded=True)
for res in serv_list if serv_list]
def _add_details(self, info):
for (k, v) in info.items():
try:
if k == 'artifacts':
v = self._add_artifact_details(v)
elif k == 'services':
v = self._add_services_details(v)
setattr(self, k, v)
self._info[k] = v
except AttributeError:
# In this case we already defined the attribute on the class
pass
class PlanManager(solum_base.CrudManager, solum_base.FindMixin):
resource_class = Plan
collection_key = 'plans'
key = 'plan'
def list(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs['headers']['Content-Type'] = 'x-application/yaml'
resp = self.client.get(
self.build_url(base_url="/v1", **kwargs), **kwargs)
try:
resp_plan = yamlutils.load(resp.content)
except ValueError as e:
raise exc.CommandException(message='Could not load Plan. '
'Reason: %s' % e.message)
return [Plan(self, res, loaded=True) for res in resp_plan if res]
def create(self, plan, **kwargs):
kwargs = self._filter_kwargs(kwargs)
kwargs['data'] = plan
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs['headers']['Content-Type'] = 'x-application/yaml'
try:
resp = self.client.post(
self.build_url(base_url="/v1", **kwargs), **kwargs)
except Exception as e:
message = vars(e).get('details', str(e))
raise exceptions.BadRequest(message=message)
try:
resp_plan = yamlutils.load(resp.content)
except ValueError as e:
raise exc.CommandException(message='Could not load Plan. '
'Reason: %s' % e.message)
return Plan(self, resp_plan)
def _get(self, url, response_key=None):
kwargs = {'headers': {}}
kwargs['headers']['Content-Type'] = 'x-application/yaml'
resp = self.client.get(url, **kwargs)
try:
resp_plan = yamlutils.load(resp.content)
except ValueError as e:
raise exc.CommandException(message='Could not load Plan. '
'Reason: %s' % e.message)
return Plan(self, resp_plan, loaded=True)
def get(self, **kwargs):
return super(PlanManager, self).get(base_url="/v1", **kwargs)
def find(self, **kwargs):
if 'plan_id' in kwargs:
return super(PlanManager, self).get(base_url="/v1", **kwargs)
elif 'name_or_id' in kwargs:
name_or_uuid = kwargs['name_or_id']
try:
if uuidutils.is_uuid_like(name_or_uuid):
return super(PlanManager, self).get(base_url="/v1",
plan_id=name_or_uuid)
else:
return super(PlanManager, self).findone(name=name_or_uuid)
except exceptions.NoUniqueMatch:
raise exc.NotUnique(resource='Plan')
def update(self, plan, **kwargs):
kwargs = self._filter_kwargs(kwargs)
kwargs['data'] = plan
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs['headers']['Content-Type'] = 'x-application/yaml'
resp = self.client.put(self.build_url(base_url="/v1", **kwargs),
**kwargs)
try:
resp_plan = yamlutils.load(resp.content)
except ValueError as e:
raise exc.CommandException(message='Could not load Plan. '
'Reason: %s' % e.message)
return self.resource_class(self, resp_plan)
def delete(self, **kwargs):
return super(PlanManager, self).delete(base_url="/v1", **kwargs)

View File

@ -1,27 +0,0 @@
# Copyright 2013 - Noorul Islam K M
#
# 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.
from solumclient.common.apiclient import base
class Platform(base.Resource):
def __repr__(self):
return "<Platform %s>" % self._info
class PlatformManager(base.BaseManager):
resource_class = Platform
def get(self, **kwargs):
return self._get('/v1')

View File

@ -1,91 +0,0 @@
# Copyright 2015 - Rackspace
#
# 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.
from solumclient.common.apiclient import base as apiclient_base
from solumclient.common.apiclient import exceptions
from solumclient.common import base as solum_base
from solumclient.common import exc
from oslo_utils import uuidutils
class Workflow(apiclient_base.Resource):
def __repr__(self):
return "<Workflow %s>" % self._info
class UserLog(apiclient_base.Resource):
def __repr__(self):
return "<Log %s>" % self._info
class WorkflowManager(solum_base.CrudManager, solum_base.FindMixin):
resource_class = Workflow
collection_key = 'workflows'
key = 'workflow'
def list(self, **kwargs):
self.app_id = kwargs.pop('app_id')
self.base_url = '/v1/apps/%s' % self.app_id
return (super(WorkflowManager, self).list(
base_url=self.base_url, **kwargs))
def create(self, **kwargs):
self.app_id = kwargs.get('app_id')
self.base_url = '/v1/apps/%s' % self.app_id
return (super(WorkflowManager, self).create(
base_url=self.base_url, **kwargs))
def get(self, **kwargs):
self.app_id = kwargs.pop('app_id')
self.base_url = '/v1/apps/%s' % self.app_id
return (super(WorkflowManager, self).get(
base_url=self.base_url, **kwargs))
def logs(self, **kwargs):
self.app_id = kwargs.get('app_id')
self.base_url = '/v1/apps/%s' % self.app_id
self.resource_class = UserLog
url = self.build_url(self.base_url, **kwargs)
rev_or_uuid = kwargs['revision_or_id']
try:
if uuidutils.is_uuid_like(rev_or_uuid):
workflow_id = rev_or_uuid
else:
wf = self.find(**kwargs)
workflow_id = wf.id
except exceptions.NoUniqueMatch:
raise exc.NotUnique(resource='Workflow')
url += '/%s/logs/' % workflow_id
return self._list(url)
def find(self, **kwargs):
self.app_id = kwargs.get('app_id')
self.base_url = '/v1/apps/%s' % self.app_id
if 'workflow_id' in kwargs:
return (super(WorkflowManager, self).get(
base_url=self.base_url, **kwargs))
elif 'revision_or_id' in kwargs:
rev_or_uuid = kwargs['revision_or_id']
try:
if uuidutils.is_uuid_like(rev_or_uuid):
return super(WorkflowManager, self).get(
base_url=self.base_url,
workflow_id=rev_or_uuid)
else:
return super(WorkflowManager, self).findone(
app_id=self.app_id, wf_id=rev_or_uuid)
except exceptions.NoUniqueMatch:
raise exc.NotUnique(resource='Workflow')

View File

@ -1,10 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking>=3.1.0,<3.2.0 # Apache-2.0
coverage!=4.4,>=4.0 # Apache-2.0
fixtures>=3.0.0 # Apache-2.0/BSD
oslotest>=4.4.1 # Apache-2.0
stestr>=2.0.0 # Apache-2.0
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=2.2.0 # MIT

53
tox.ini
View File

@ -1,53 +0,0 @@
[tox]
minversion = 3.2.0
envlist = py38,pep8
skipsdist = True
ignore_basepython_conflict = True
[testenv]
basepython = python3
usedevelop = True
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
allowlist_externals = find
setenv = VIRTUAL_ENV={envdir}
commands =
find . -type f -name "*.py[c|o]" -delete
find . -type d -name "__pycache__" -delete
stestr run {posargs}
[testenv:pep8]
commands = flake8
[testenv:venv]
commands = {posargs}
[testenv:cover]
setenv =
{[testenv]setenv}
PYTHON=coverage run --source solumclient --parallel-mode
commands =
stestr run {posargs}
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
[testenv:debug]
commands = oslo_debug_helper -t solumclient/tests {posargs}
[testenv:docs]
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/requirements.txt
-r{toxinidir}/doc/requirements.txt
commands = sphinx-build -b html doc/source doc/build/html
[flake8]
show-source = True
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
[hacking]
import_exceptions = solumclient.i18n