Skip to content

Commit

Permalink
Tidyup a few bits and bobs:
Browse files Browse the repository at this point in the history
- Added configuration profiles, to support multiple environments.
- Added `api_host`, `api_proxy` and `api_user_agent` to config file.
- Added `help` command for those who need more than `-h` and `--help`.
- Changed environment variables to use a `CLOUDSMITH_` prefix.
- Fixed validation for `push` commands that require a distribution.
  • Loading branch information
lskillen committed Dec 3, 2017
1 parent 2f41588 commit 00f1dc3
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 80 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]
Copyright 2017 Cloudsmith Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
126 changes: 116 additions & 10 deletions README.md
Expand Up @@ -14,6 +14,25 @@ The CLI currently supports the following commands:
- `token`: Retrieve your API authentication token/key.
- `whoami`: Retrieve your current authentication status.

# Installation

You can install the latest CLI application from:

- [Official CLI Repository @ Cloudsmith](https://cloudsmith.io/package/ns/cloudsmith/repos/cli/packages/)
- [Official CLI Repository @ PyPi](https://pypi.python.org/pypi/cloudsmith-cli)

The simplest way is to use `pip`, such as:

```
pip install cloudsmith-cli
```

Or you can get the latest pre-release version from Cloudsmith:

```
pip install cloudsmith-cli --extra-index-url=https://dl.cloudsmith.io/public/cloudsmith/cli/python/index/
```

## Configuration

There are two configuration files used by the CLI:
Expand All @@ -31,28 +50,100 @@ By default, the CLI will look for these in the following locations:
Both configuration files use the simple INI format, such as:

```
# Default configuration
[default]
api_key='1234567890abcdef1234567890abcdef'
# Profile-based configuration (not working yet)
[profile:cloudsmith]
api_key='fedcba0987654321fedcba0987654321'
api_key=1234567890abcdef1234567890abcdef
```

### Non-Credentials (config.ini)

TODO
See the [default example](https://raw.githubusercontent.com/cloudsmith-io/cloudsmith-cli/master/config/config.ini) in GitHub:

You can specify the following configuration options:

- `api_host`: The API host to connect to.
- `api_proxy`: The API proxy to connect through.
- `api_user_agent`: The user agent to use for requests.

### Credentials (credentials.ini)

See the [default example](https://raw.githubusercontent.com/cloudsmith-io/cloudsmith-cli/master/config/credentials.ini) in GitHub:

You can specify the following configuration options:

- `api_key`: To specify the authentication key/token for API access.
- `api_key`: The API key for authenticating with the API.

## Examples

TODO: Provide a list of examples for the CLI tool.
**Note:** All of the examples in this section are uploading to the **lskillen** user and the **test** repository. Please replace these with your own user/org and repository names.

### Get your API key/token

You can retrieve your API token using the `cloudsmith token` command:

```
cloudsmith token
Login: you@example.com
Password:
Repeat for confirmation:
```

The resulting output looks something like:

```
Retrieving API token for 'you@example.com' ... OK
Your API token is: 1234567890abcdef1234567890abcdef
```

You can then put this into your `credentials.ini`, use it as an environment variable `API_KEY=your_key_here` or pass it to the CLI using the `-k your_key_here` flag.

### Upload a Debian Package

Assuming you have a package filename **libxml2-2.9.4-2.x86_64.deb**, representing **libxml 2.9.4**, for the **Ubuntu 16.04** distribution (which has a cloudsmith identifier of **ubuntu/xenial**):

```
cloudsmith push deb lskillen/test/ubuntu/xenial libxml2-2.9.4-2.x86_64.deb
```

### Upload a RedHat Package

Assuming you have a package filename **libxml2-2.9.4-2.el5.x86_64.rpm**, representing **libxml 2.9.4**, for the **RedHat Enterprise 5.0** distribution (which has a cloudsmith identifier of **el/5**):

```
cloudsmith push rpm lskillen/test/el/5 libxml2-2.9.4-2.el5.x86_64.rpm
```

### Upload a Python Package

Assuming you have a package filename **boto3-1.4.4.py2.p3-none-any.whl**, representing **boto3 1.4.4**, for **Python 2/3**:

```
cloudsmith push python lskillen/test boto3-1.4.4.py2.p3-none-any.whl
```

### Upload a Ruby Package

Assuming you have a package filename **safe_yaml-1.0.4.gem**, representing **safe_yaml 1.0.4**, for **Ruby 2.3+**:

```
cloudsmith push ruby lskillen/test safe_yaml-1.0.4.gem
```

### Upload a Maven Package

Assuming you have a package filename **validation-api-1.0.0.GA.jar**, representing **validation-api 1.0.0**, for **Maven/Java**:

```
cloudsmith push maven lskillen/test validation-api-1.0.0.GA.jar --pom-file=validation-api-1.0.0.GA.pom
```

### Upload a Raw Package

Assuming you have a package filename **assets.zip**, representing **packaged assets**:

```
cloudsmith push raw assets.zip
```

## Contributing

Expand All @@ -68,6 +159,21 @@ $ bumpversion <major|minor|revision>

A tag will automatically created along with the version bump commit.

## License

Copyright 2017 Cloudsmith Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

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.

## EOF

This quality product was brought to you by [Cloudsmith](https://cloudsmith.io) and the fine folks mentioned in `CONTRIBUTORS.md`.
This quality product was brought to you by [Cloudsmith](https://cloudsmith.io) and the fine folks who have contributed.
18 changes: 18 additions & 0 deletions RELEASE_NOTES.md
@@ -0,0 +1,18 @@
# Release Notes

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

- **Added** configuration profiles, to support multiple environments.
- **Added** `api_host`, `api_proxy` and `api_user_agent` to config file.
- **Added** `help` command for those who need more than `-h` and `--help`.
- **Changed** environment variables to use a `CLOUDSMITH_` prefix.
- **Fixed** validation for `push` commands that require a distribution.

## [0.1.0] - 2017-11-23

- Initial release.
1 change: 1 addition & 0 deletions cloudsmith_cli/cli/commands/__init__.py
Expand Up @@ -5,6 +5,7 @@
from . import check # noqa
from . import delete # noqa
from . import docs # noqa
from . import help # noqa
from . import push # noqa
from . import status # noqa
from . import token # noqa
Expand Down
12 changes: 12 additions & 0 deletions cloudsmith_cli/cli/commands/help.py
@@ -0,0 +1,12 @@
"""Main command/entrypoint."""
from __future__ import absolute_import, print_function, unicode_literals

import click
from . import main


@main.command()
@click.pass_context
def help(ctx):
"""Show this message and exit."""
click.echo(ctx.parent.get_help())
93 changes: 90 additions & 3 deletions cloudsmith_cli/cli/config.py
Expand Up @@ -4,14 +4,18 @@
from click_configfile import ConfigFileReader, Param, SectionSchema
from click_configfile import matches_section
import click
import six
from . import utils


class ConfigSchema(object):
"""Schema for standard configuration."""

@matches_section("default")
class Default(SectionSchema):
pass
api_host = Param(type=str)
api_proxy = Param(type=str)
api_user_agent = Param(type=str)

@matches_section("profile:*")
class Profile(Default):
Expand All @@ -33,8 +37,10 @@ class ConfigReader(ConfigFileReader):

@classmethod
def get_storage_name_for(cls, section_name):
# Config always gets merged
return ''
if not section_name or section_name == 'default':
return 'default'
else:
return section_name


class CredentialsSchema(object):
Expand All @@ -61,3 +67,84 @@ class CredentialsReader(ConfigFileReader):
config_searchpath = [
click.get_app_dir('cloudsmith')
]

@classmethod
def get_storage_name_for(cls, section_name):
if not section_name or section_name == 'default':
return 'default'
else:
return section_name


class Options(object):
DEFAULTS = {
'api_config': None,
'api_key': None,
'api_host': None,
'api_proxy': None,
'api_user_agent': None,
'debug': False,
'output': None,
'verbose': False
}

def __init__(self, *args, **kwargs):
super(Options, self).__init__(*args, **kwargs)
opts = self.DEFAULTS.copy()
opts.update(kwargs)
for k, v in six.iteritems(opts):
setattr(self, k, v)

def load_config_file(self, path, profile=None):
"""Load the standard config file."""
self._load_config(ConfigReader, path, profile=profile)

def load_creds_file(self, path, profile=None):
"""Load the credentials config file."""
self._load_config(CredentialsReader, path, profile=profile)

def _load_config(self, config_cls, path=None, profile=None):
"""Load a configuration file."""
if path:
config_cls.searchpath = [path]

config = config_cls.read_config()

values = config.get('default', {})
self._load_config_from_dict(values)

if profile:
values = config.get('profile:%s' % profile, {})
self._load_config_from_dict(values)

def _load_config_from_dict(self, values):
"""Load configuration from a dictionary."""
for k, v in six.iteritems(values):
if not v:
continue
setattr(self, k, v)

def __setattr__(self, name, value):
if name in self.DEFAULTS:
# Prevent clears if value was set
try:
current_value = getattr(self, name)
if value is None and current_value is not None:
return
except AttributeError:
pass

super(Options, self).__setattr__(name, value)

@property
def api_user_agent(self):
return utils.make_user_agent(prefix=self._user_agent)

@api_user_agent.setter
def api_user_agent(self, value):
self._user_agent = value


def get_or_create_options(ctx):
"""Get or create the options object."""
return ctx.ensure_object(Options)

0 comments on commit 00f1dc3

Please sign in to comment.