Skip to content

Commit

Permalink
creating upload with many CLIUI feats
Browse files Browse the repository at this point in the history
  • Loading branch information
Jochem Berends committed Nov 1, 2017
1 parent 11262e2 commit 9cc638d
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .idea/kecpkg-tools.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kecpkg/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.2.0'
__version__ = '0.3.0'
13 changes: 7 additions & 6 deletions kecpkg/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,24 @@ def build(package=None, **options):
settings = load_settings(package_dir=package_dir)

# ensure build directory is there
build_dir = os.path.join(package_dir, 'dist')
build_dir = settings.get('build_dir', 'dist')
build_path = os.path.join(package_dir, build_dir)

if options.get('clean_first'):
remove_path(build_dir)
ensure_dir_exists(build_dir)
remove_path(build_path)
ensure_dir_exists(build_path)

# do package building
build_package(package_dir, build_dir, settings, verbose=options.get('verbose'))
build_package(package_dir, build_path, settings, verbose=options.get('verbose'))


def build_package(package_dir, build_dir, settings, verbose=False):
def build_package(package_dir, build_path, settings, verbose=False):
"""Perform the actual building of the kecpkg zip."""
artifacts = get_artifacts_on_disk(package_dir, verbose=verbose)
dist_filename = '{}-{}-py{}.kecpkg'.format(settings.get('package_name'), settings.get('version'),
settings.get('python_version'))
echo_info('Creating package name `{}`'.format(dist_filename))

with ZipFile(os.path.join(build_dir, dist_filename), 'x') as dist_zip:
with ZipFile(os.path.join(build_path, dist_filename), 'x') as dist_zip:
for artifact in artifacts:
dist_zip.write(os.path.join(package_dir, artifact), arcname=artifact)
10 changes: 5 additions & 5 deletions kecpkg/commands/purge.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import click

from kecpkg.commands.utils import CONTEXT_SETTINGS, echo_warning, echo_failure, echo_success
from kecpkg.utils import remove_path
from kecpkg.utils import remove_path, get_package_dir


@click.command(context_settings=CONTEXT_SETTINGS,
Expand All @@ -19,13 +19,13 @@ def purge(package, **options):
:return:
"""
package_name = package or click.prompt('Provide package name')
package_path = os.path.join(os.getcwd(), package_name)
package_dir = get_package_dir(package_name)

if os.path.exists(package_path):
if os.path.exists(package_dir):
if options.get('force') or click.confirm(
"Do you want to purge and completely remove '{}'?".format(package_name)):
remove_path(package_path)
if not os.path.exists(package_path):
remove_path(package_dir)
if not os.path.exists(package_dir):
echo_success('Package `{}` is purged and removed from disk'.format(package_name))
else:
echo_failure('Something went wrong pruning pacakage `{}`'.format(package_name))
Expand Down
179 changes: 175 additions & 4 deletions kecpkg/commands/upload.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,181 @@
import click as click
import os

from kecpkg.commands.utils import CONTEXT_SETTINGS
import sys

import requests
from requests.compat import urljoin
from pykechain import Client, get_project

from kecpkg.commands.utils import CONTEXT_SETTINGS, echo_info, echo_success, echo_failure
from kecpkg.settings import load_settings
from kecpkg.utils import get_package_dir, get_package_name


@click.command(context_settings=CONTEXT_SETTINGS,
short_help="Upload package to a KE-chain 2 scope")
def upload(**options):
"""Upload built kecpkg to KE-chain."""
print('UPLOAD BUILDED KECPKG HERE ')
@click.argument('package', required=False)
@click.option('--url', '-U', help="URL of the KE-chain instance (eg. https://<instance>.ke-chain.com)")
@click.option('--username', '-u', help="username for KE-chain", default=os.environ.get('USER', ''))
@click.option('--password', '-p', help="password for KE-chain")
@click.option('--token', help="token for KE-chain access")
@click.option('--scope', help="scope name to upload the kecpkg to")
@click.option('--scope-id', help="UUID of the scope to upload the kecpkg to", type=click.UUID)
@click.option('--interactive', '-i', is_flag=True, help="interactive mode; guide me through the upload")
def upload(package=None, url=None, username=None, password=None, token=None, scope=None, scope_id=None, **options):
"""
Upload built kecpkg to KE-chain.
If no options are provided, the interactive mode is triggered.
"""
package_name = package or get_package_name() or click.prompt('Package name')
settings = load_settings(package_dir=get_package_dir(package_name))

if not url or not (username and password) or not (token) or not (scope or scope_id) or options.get('interactive'):
url = click.prompt('Url (incl http(s)://)', default=url)
username = click.prompt('Username', default=username)
password = click.prompt('Password', hide_input=True)

client = Client(url)
client.login(username=username, password=password)
scopes = client.scopes()
str_tpl = "{number} - {id} - {name}"
scope_matcher = [dict(number=i, scope_id=scope.id, scope=scope.name) for i, scope in
zip(range(1, len(scopes)), scopes)]

# nice UI
echo_info('Choose from following scopes:')
for match_dict in scope_matcher:
echo_info("{number} | {scope_id:.8} | {scope}".format(**match_dict))

scope_match = None
while not scope_match:
scope_guess = click.prompt('Row number, part of Id or Scope')
scope_match = validate_scopes(scope_guess, scope_matcher)

echo_success("Scope selected: '{scope}' ({scope_id})".format(**scope_match))
scope_id = scope_match['scope_id']

# do upload
build_path = os.path.join(get_package_dir(package_name), settings.get('build_dir'))
if not build_path:
echo_failure('Cannot find build path, please do build kecpkg first')
sys.exit(400)
scope_to_upload = get_project(url, username, password, token, scope_id=scope_id)
upload_package(scope_to_upload, build_path, settings)


def upload_package(scope, build_path, settings):
"""
Upload the package from build_path to the right scope, create a new KE-chain SIM service
:param scope: scope object (pykechain)
:param build_path: path to the build directory in which the to-be uploaded script resides
:param settings: settings of the package
:return: None
"""
built_kecpkgs = os.listdir(build_path)
if len(built_kecpkgs) > 1 and settings.get('version'):
built_kecpkgs = [f for f in built_kecpkgs if settings.get('version') in f]
if len(built_kecpkgs) == 1:
kecpkg_path = os.path.join(build_path, built_kecpkgs[0])
else:
echo_info('Provide correct filename to upload')
echo_info('\n'.join(os.listdir(build_path)))
kecpkg_filename = click.prompt('Filename')
kecpkg_path = os.path.join(build_path, kecpkg_filename)

if kecpkg_path and os.path.exists(kecpkg_path):
# ready to upload
echo_info('Ready to upload `{}`'.format(os.path.basename(kecpkg_path)))
else:
echo_failure('Unable to locate kecpkg to upload')
sys.exit(404)

# get mata and prepare 2 stage submission
# 1. fill service information
# 2. do upload

payload = dict(
name=settings.get('package_name'),
description=settings.get('description', ''),
script_version=settings.get('version', ''),
script_type='PYTHON SCRIPT',
env_version=settings.get('python_version'),
id='',
scope=scope.id
)

create_uri = '/api/services.json'
r = scope._client._request('POST', urljoin(scope._client.api_root, create_uri), data=payload)
if r.status_code != requests.codes.created:
echo_failure('Unexpected response from the server: {}'.format((r, r.text)))
sys.exit(r.status_code)
response = r.json()
new_service = response.get('results')[0]

# now upload
r = scope._client._request('POST', "{}/{}".format(new_service.get('url'),'upload.json'),
files={'attachment': (os.path.basename(kecpkg_path), open(kecpkg_path, 'rb'))})

if r.status_code != requests.codes.accepted:
echo_failure('Unexpected response from the server: {}'.format((r, r.text)))
sys.exit(r.status_code)

echo_success("kecpkg `{}` successfully uploaded to KE-chain.".format(os.path.basename(kecpkg_path)))
success_url = "{api_root}/#scopes/{scope_id}/scripts/{script_id}".format(
api_root=scope._client.api_root,
scope_id=scope.id,
script_id=new_service.get('id')
)
echo_success("To view the newly created service, go to: `{}`".format(success_url))


def validate_scopes(scope_guess, scope_matcher):
"""Check the scope guess against a set of possible scopes and return correct scope_id"""
for scope_match in scope_matcher:
if scope_guess == str(scope_match['number']):
return scope_match
if len(scope_guess) >= 2 and scope_guess.lower() in scope_match['scope_id'].lower():
return scope_match
if scope_guess.lower() in scope_match['scope'].lower():
return scope_match
return None


"""
NEW SCRIPT
Request:
URL: https://kec2api.ke-chain.com/api/services.json?_dc=1509571322573
Request Method:POST
Status Code:201 Created
{"name":"name","description":"descr","script_version":"0.0.1",
"script_type":"PYTHON SCRIPT","env_version":"3.5",
"id":"Service-1",
"scope":"6f7bc9f0-228e-4d3a-9dc0-ec5a75d73e1d"}
Response:
{"results":[
{"id":"be473cc2-9342-4b33-85dc-8b16ae67d79b",
"url":"https://kec2api.ke-chain.com/api/services/be473cc2-9342-4b33-85dc-8b16ae67d79b",
"name":"name","description":"descr",
"scope":"6f7bc9f0-228e-4d3a-9dc0-ec5a75d73e1d",
"script_file_name":"","script_type":"PYTHON SCRIPT",
"script_version":"0.0.1","env_version":"3.5",
"permissions":{"read":true,"destroy":true,"update":true,"create":true,"write":true,"retrieve_execute":true}}]}
UPLOAD SCRIPT
Request URL:https://kec2api.ke-chain.com/api/services/be473cc2-9342-4b33-85dc-8b16ae67d79b/upload.json
Request Method:POST
Payload:
------WebKitFormBoundarygA6v9Rm8hnD5tPfv
Content-Disposition: form-data; name="attachment"; filename="some_pkg-0.0.1-py3.5.kecpkg"
Content-Type: application/octet-stream
SUCCES URI
https://kec2api.ke-chain.com/#scopes/6f7bc9f0-228e-4d3a-9dc0-ec5a75d73e1d/scripts/66c768d3-8c6d-4a0e-b74f-4b3e5fa92dc3
"""
3 changes: 2 additions & 1 deletion kecpkg/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
('python_version', '3.5'),
('venv_dir', 'venv'),
('entrypoint_script', 'script'),
('entrypoint_func', 'main')
('entrypoint_func', 'main'),
('build_dir', 'dist')
])


Expand Down
53 changes: 39 additions & 14 deletions kecpkg/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,29 +102,54 @@ def normalise_name(raw_name):
return re.sub(r"[-_. ]+", "_", raw_name).lower()


def get_package_dir(package_name=None):
def get_package_dir(package_name=None, fail=True):
"""
Check and retrieve the package directory.
:param package_name: (optional) package name
:param fail: (optional, default=True) fail hard with exit when no package dir found
:return: full path name to the package directory
"""
package_dir = package_name and os.path.join(os.getcwd(), package_name) or os.getcwd()

try:
def _inner(d):
from kecpkg.settings import load_settings
# load settings just to test that we are inside a package dir
load_settings(package_dir=package_dir)
return package_dir
except FileNotFoundError:
echo_warning('Cannot find settings in path `{}`...'.format(package_dir))
if os.path.exists(os.path.join(package_dir, 'package_info.json')):
return package_dir
else:
from kecpkg.settings import SETTINGS_FILENAME
echo_failure('This does not seem to be a package in path `{}` - please check that there is a '
'`package_info.json` or a `{}`'.format(package_dir, SETTINGS_FILENAME))
try:
# load settings just to test that we are inside a package dir
load_settings(package_dir=d)
return d
except FileNotFoundError:
if os.path.exists(os.path.join(d, 'package_info.json')):
return d
else:
return None

package_dir = _inner(os.getcwd())
if not package_dir:
package_dir = _inner(os.path.join(os.getcwd(), package_name))
if not package_dir:
package_dir = _inner(package_name)
if not package_dir:
from kecpkg.settings import SETTINGS_FILENAME
echo_failure('This does not seem to be a package in path `{}` - please check that there is a '
'`package_info.json` or a `{}`'.format(package_dir, SETTINGS_FILENAME))
if fail:
sys.exit(1)
else:
return package_dir


def get_package_name():
"""
Provide the name of the package (in current dir)
:param fail: ensure that directory search does not fail in a exit.
:return: package name or None
"""
package_dir = get_package_dir(fail=False)
if package_dir:
return os.path.basename(package_dir)
else:
return None


def get_artifacts_on_disk(root_path, exclude_paths=('venv', 'dist'), verbose=False):
Expand Down

0 comments on commit 9cc638d

Please sign in to comment.