Skip to content
37 changes: 37 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
..
Copyright 2017 - Swiss Data Science Center (SDSC)
A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
Eidgenössische Technische Hochschule Zürich (ETHZ).

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.

Renga Command Line
==================

.. automodule:: renga.cli


``renga login``
---------------

.. automodule:: renga.cli.login

``renga init``
--------------

.. automodule:: renga.cli.init

``renga add``
-------------

.. automodule:: renga.cli.add
4 changes: 2 additions & 2 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ There are several ways to instantiate a client used for communication with
the Renga platform.

1. The easiest way is by calling the function :py:func:`~renga.client.from_env`
when running in environment created by Renga platform itself.
2. The client can be created from local configuration file by calling
when running in an environment created by the Renga platform itself.
2. The client can be created from a local configuration file by calling
:py:func:`~renga.cli._client.from_config`.
3. Lastly, it can also be configured manually by
instantiating a :py:class:`~renga.client.RengaClient` class.
Expand Down
28 changes: 28 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@ You can access files from a bucket:
For more details and examples have a look at :doc:`the reference
<client>`.

Use the Renga command line
--------------------------

Interaction with the platform can also take place via the command-line
interface (CLI).

First, you need to authenticate with an existing instance of the Renga
platform. The example shows a case when you have the platform running on
``localhost``.

.. code-block:: console

$ renga login http://localhost
Username: demo
Password: ****
Access token has been stored in: ...

Following the above example you can create a first bucket and upload a file.

.. code-block:: console

$ export BUCKET_ID=$(renga io buckets create first-bucket)
$ echo "hello world" | renga io buckets $BUCKET_ID upload --name greeting.txt
9876

For more information about using `renga`, refer to the :doc:`Renga command
line <cli>` instructions.

.. toctree::
:hidden:
Expand All @@ -62,6 +89,7 @@ For more details and examples have a look at :doc:`the reference
buckets
contexts
api
cli
contributing
changes
license
Expand Down
7 changes: 7 additions & 0 deletions renga/api/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ def storage_authorize(self, resource_id=None, request_type=None):
'request_type': request_type})
return resp.json()

def storage_file_metadata_replace(self, resource_id, data):
"""Replace resource metadata."""
return self.put(
self._url('api/storage/file/{0}', resource_id),
json=data,
).json()

def storage_io_write(self, data):
"""Write data to the file.

Expand Down
77 changes: 72 additions & 5 deletions renga/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,71 @@
# 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.
"""CLI for the Renga platform."""
r"""The base command for interacting with the Renga platform.

``renga`` (base command)
------------------------

To list the available commands, either run ``renga`` with no parameters or
execute ``renga help``:

.. code-block:: console

$ renga help
Usage: renga [OPTIONS] COMMAND [ARGS]...

Check common Renga commands used in various situations.

Options:
--version Print version number.
--config FILENAME Location of client config files.
--config-path Print application config path.
--no-project Run command outside project context.
-h, --help Show this message and exit.

Commands:
# [...]

Configuration files
~~~~~~~~~~~~~~~~~~~

Depending on your system, you may find the configuration files used by Renga
command line in a different folder. By default, the following rules are used:

MacOS:
``~/Library/Application Support/Renga``
Unix:
``~/.config/renga``
Windows:
``C:\Users\<user>\AppData\Roaming\Renga``

If in doubt where to look for the configuration file, you can display its path
by running ``renga --config-path``.

You can specify a different location via the ``RENGA_CONFIG`` environment
variable or the ``--config`` command line option. If both are specified, then
the ``--config`` option value is used. For example:

.. code-block:: console

$ renga --config ~/renga/config/ login

instructs Renga to store the configuration files in your ``~/renga/config/``
directory when running the ``login`` command.
"""

import click
from click_plugins import with_plugins
from pkg_resources import iter_entry_points

from ._config import print_app_config_path, with_config
from ._config import config_load, default_config_dir, print_app_config_path
from ._version import print_version


@with_plugins(iter_entry_points('renga.cli'))
@click.group(context_settings={
'auto_envvar_prefix': 'RENGA',
'help_option_names': ['-h', '--help'],
})
@click.option(
'--version',
Expand All @@ -36,6 +88,14 @@
expose_value=False,
is_eager=True,
help=print_version.__doc__)
@click.option(
'--config',
envvar='RENGA_CONFIG',
default=default_config_dir,
type=click.Path(),
callback=config_load,
expose_value=False,
help='Location of client config files.')
@click.option(
'--config-path',
is_flag=True,
Expand All @@ -46,9 +106,16 @@
@click.option(
'--no-project',
is_flag=True,
default=False)
@with_config
default=False,
help='Run command outside project context.')
@click.pass_context
def cli(ctx, config, no_project):
def cli(ctx, no_project):
"""Check common Renga commands used in various situations."""
ctx.obj['no_project'] = no_project


@cli.command()
@click.pass_context
def help(ctx):
"""Show help message and exit."""
click.echo(ctx.parent.get_help())
2 changes: 1 addition & 1 deletion renga/cli/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@


def from_config(config=None, endpoint=None):
"""Create new client for endpoint in the config.
"""Create a new client for endpoint in the config.

Use ``renga`` command-line interface to manage multiple
configurations.
Expand Down
44 changes: 28 additions & 16 deletions renga/cli/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,48 @@
lambda dumper, data: dumper.represent_str(str(data)))


def default_config_dir():
"""Return default config directory."""
return click.get_app_dir(APP_NAME)


def config_path(path=None):
"""Return config path."""
if path is None:
path = os.environ.get('RENGA_CONFIG', click.get_app_dir(APP_NAME))
try:
os.makedirs(path)
except OSError as e: # pragma: no cover
if e.errno != errno.EEXIST:
raise
path = default_config_dir()
try:
os.makedirs(path)
except OSError as e: # pragma: no cover
if e.errno != errno.EEXIST:
raise
return os.path.join(path, 'config.yml')


def read_config(path=None):
def read_config(path):
"""Read Renga configuration."""
try:
with open(config_path(path=path), 'r') as configfile:
with open(config_path(path), 'r') as configfile:
return yaml.load(configfile) or {}
except FileNotFoundError:
return {}


def write_config(config, path=None):
def write_config(config, path):
"""Write Renga configuration."""
with open(config_path(path=path), 'w+') as configfile:
with open(config_path(path), 'w+') as configfile:
yaml.dump(config, configfile, default_flow_style=False)


def config_load(ctx, param, value):
"""Print application config path."""
if ctx.obj is None:
ctx.obj = {}

ctx.obj['config_path'] = value
ctx.obj['config'] = read_config(value)
return value


def with_config(f):
"""Add config to function."""
# keep it.
Expand All @@ -87,10 +102,7 @@ def new_func(ctx, *args, **kwargs):
if ctx.obj is None:
ctx.obj = {}

if 'config' in ctx.obj:
config = ctx.obj['config']
else:
config = ctx.obj['config'] = read_config()
config = ctx.obj['config']

project_enabled = not ctx.obj.get('no_project', False)
project_config_path = get_project_config_path()
Expand All @@ -104,7 +116,7 @@ def new_func(ctx, *args, **kwargs):
if not project_config_path:
raise RuntimeError('Invalid config update')
write_config(project_config, path=project_config_path)
write_config(config)
write_config(config, path=ctx.obj['config_path'])
if project_config is not None:
config['project'] = project_config
return result
Expand All @@ -116,7 +128,7 @@ def print_app_config_path(ctx, param, value):
"""Print application config path."""
if not value or ctx.resilient_parsing:
return
click.echo(config_path())
click.echo(config_path(os.environ.get('RENGA_CONFIG')))
ctx.exit()


Expand Down
16 changes: 16 additions & 0 deletions renga/cli/_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ def default_endpoint_from_config(config, option=None):
option=option)


def password_prompt(ctx, param, value):
"""Prompt for password if ``--password-stdin`` is not used."""
if ctx.resilient_parsing:
return

if not value:
if 'password_stdin' in ctx.params:
with click.open_file('-') as fp:
value = fp.read().strip('\n')
else:
value = click.prompt('Password', hide_input=True)

click.echo(value)
return value


def default_endpoint(ctx, param, value):
"""Return default endpoint if specified."""
if ctx.resilient_parsing:
Expand Down
Loading