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:
parent
b9b0d8314c
commit
31e8a21890
@ -1,7 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = solumclient
|
||||
omit = solumclient/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
53
.gitignore
vendored
53
.gitignore
vendored
@ -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
|
3
.mailmap
3
.mailmap
@ -1,3 +0,0 @@
|
||||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
@ -1,3 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_path=${OS_TEST_PATH:-./solumclient/tests}
|
||||
top_dir=./
|
@ -1,6 +0,0 @@
|
||||
- project:
|
||||
templates:
|
||||
- check-requirements
|
||||
- openstack-cover-jobs
|
||||
- openstack-python3-jobs
|
||||
- publish-openstack-docs-pti
|
@ -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
|
@ -1,4 +0,0 @@
|
||||
Solum Client Style Commandments
|
||||
===============================
|
||||
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
175
LICENSE
175
LICENSE
@ -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.
|
41
README.rst
41
README.rst
@ -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.
|
||||
|
@ -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)
|
@ -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
|
@ -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
|
@ -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>]
|
@ -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}
|
@ -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
|
@ -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.
|
@ -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
|
46
setup.cfg
46
setup.cfg
@ -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
|
20
setup.py
20
setup.py
@ -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)
|
@ -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()
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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
|
||||
"""
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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,
|
||||
})
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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.")
|
@ -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)
|
@ -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 = ''
|
@ -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
|
@ -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"
|
1934
solumclient/solum.py
1934
solumclient/solum.py
File diff suppressed because it is too large
Load Diff
@ -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())
|
@ -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', **{})
|
@ -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)
|
@ -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)
|
@ -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()
|
@ -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")
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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', **{})
|
@ -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)
|
@ -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')
|
@ -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)
|
@ -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')
|
@ -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)
|
@ -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)
|
@ -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')
|
@ -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)
|
@ -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')
|
@ -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)
|
@ -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')
|
@ -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)
|
@ -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')
|
@ -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')
|
@ -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
53
tox.ini
@ -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
|
Loading…
x
Reference in New Issue
Block a user