Skip to content

Commit 56047eb

Browse files
committed
Merge pull request #22 from clarkperkins/feature/change-to-click
Change to click
2 parents 12b47a3 + 23aa8fb commit 56047eb

File tree

21 files changed

+1184
-1330
lines changed

21 files changed

+1184
-1330
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ language: python
22

33
python:
44
- "2.7"
5-
- "3.2"
65
- "3.3"
76
- "3.4"
87

blueprints/.keep

Whitespace-only changes.

bootstrap.yaml

Lines changed: 0 additions & 6 deletions
This file was deleted.

setup.py

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ def test_python_version():
4444
with open('README.md') as f:
4545
LONG_DESCRIPTION = f.read()
4646

47-
CFG_DIR = os.path.expanduser("~/.stackdio-cli")
48-
4947
requirements = [
5048
'Jinja2==2.7.3',
51-
'PyYAML==3.11',
52-
'cmd2==0.6.7',
49+
'PyYAML>=3.10',
50+
'click>=6.0,<7.0',
51+
'click-shell>=0.4',
52+
'colorama>=0.3,<0.4',
5353
'keyring==3.7',
54-
'requests>=2.4.0,<2.6.0',
54+
'requests>=2.4.0',
5555
'simplejson==3.4.0',
5656
]
5757

@@ -61,11 +61,9 @@ def test_python_version():
6161
'pylint<=1.2.0',
6262
]
6363

64-
if __name__ == "__main__":
64+
if __name__ == '__main__':
6565
test_python_version()
6666

67-
# Call the setup method from setuptools that does all the heavy lifting
68-
# of packaging stackdio-client
6967
setup(
7068
name='stackdio',
7169
version=__version__,
@@ -77,14 +75,6 @@ def test_python_version():
7775
license='Apache 2.0',
7876
include_package_data=True,
7977
packages=find_packages(),
80-
data_files=[
81-
(CFG_DIR,
82-
[
83-
'bootstrap.yaml',
84-
]),
85-
(os.path.join(CFG_DIR, "blueprints"),
86-
["blueprints/%s" % f for f in os.listdir("blueprints")]),
87-
],
8878
zip_safe=False,
8979
install_requires=requirements,
9080
dependency_links=[],
@@ -95,25 +85,22 @@ def test_python_version():
9585
'console_scripts': [
9686
'stackdio-cli=stackdio.cli:main',
9787
'blueprint-generator=stackdio.cli.blueprints:main',
88+
'stackdio-config-convert=stackdio.client.config:main',
9889
],
9990
},
10091
classifiers=[
10192
'Development Status :: 4 - Beta',
10293
'Environment :: Web Environment',
103-
'Framework :: Django',
10494
'Intended Audience :: Developers',
10595
'Intended Audience :: Information Technology',
10696
'Intended Audience :: System Administrators',
10797
'License :: OSI Approved :: Apache Software License',
10898
'Programming Language :: Python',
10999
'Programming Language :: Python :: 2',
110-
'Programming Language :: Python :: 2.6',
111100
'Programming Language :: Python :: 2.7',
112101
'Programming Language :: Python :: 3',
113-
'Programming Language :: Python :: 3.2',
114102
'Programming Language :: Python :: 3.3',
115103
'Programming Language :: Python :: 3.4',
116-
'Programming Language :: Python :: 3.5',
117104
'Topic :: System :: Clustering',
118105
'Topic :: System :: Distributed Computing',
119106
]

stackdio/cli/__init__.py

Lines changed: 60 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,213 +1,69 @@
11
#!/usr/bin/env python
22

3-
from __future__ import print_function
4-
5-
import argparse
6-
import json
73
import os
8-
import sys
9-
10-
from cmd2 import Cmd
11-
import keyring
12-
from requests import ConnectionError
13-
14-
15-
from stackdio.client import StackdIO
16-
17-
from stackdio.cli import mixins
18-
19-
20-
class StackdioShell(
21-
Cmd,
22-
mixins.bootstrap.BootstrapMixin,
23-
mixins.stacks.StackMixin,
24-
mixins.formulas.FormulaMixin,
25-
mixins.blueprints.BlueprintMixin):
26-
27-
CFG_DIR = os.path.expanduser(os.getenv('STACKDIO_CFG_DIR',"~/.stackdio-cli/"))
28-
CFG_FILE = os.path.join(CFG_DIR, os.getenv('STACKDIO_CFG_FILE',"config.json"))
29-
BOOTSTRAP_FILE = os.path.join(CFG_DIR, "bootstrap.yaml")
30-
KEYRING_SERVICE = "stackdio_cli"
31-
PROMPT = "\n{username} @ {url}\n> "
32-
HELP_CMDS = [
33-
"account_summary",
34-
"stacks", "blueprints", "formulas",
35-
"initial_setup", "bootstrap",
36-
"help", "exit", "quit",
37-
]
38-
39-
Cmd.intro = """
40-
######################################################################
41-
s t a c k d . i o
42-
######################################################################
43-
"""
44-
45-
def __init__(self):
46-
Cmd.__init__(self)
47-
mixins.bootstrap.BootstrapMixin.__init__(self)
48-
self._load_config()
49-
if 'url' in self.config and self.config['url']:
50-
self._init_stacks()
51-
self._validate_auth()
52-
53-
def preloop(self):
54-
self._setprompt()
55-
56-
def precmd(self, line):
57-
self._setprompt()
58-
return line
59-
60-
def postloop(self):
61-
print("\nGoodbye!")
62-
63-
def get_names(self):
64-
if self.validated:
65-
return ["do_%s" % c for c in self.HELP_CMDS]
66-
else:
67-
return ["do_initial_setup", "do_help"]
68-
69-
def _init_stacks(self):
70-
"""Instantiate a StackdIO object"""
71-
self.stacks = StackdIO(
72-
base_url=self.config["url"],
73-
auth=(
74-
self.config["username"],
75-
keyring.get_password(self.KEYRING_SERVICE, self.config.get("username") or "")
76-
),
77-
verify=self.config.get('verify', True)
78-
)
79-
80-
def _load_config(self):
81-
"""Attempt to load config file, otherwise fallback to DEFAULT_CONFIG"""
82-
83-
try:
84-
self.config = json.loads(open(self.CFG_FILE).read())
85-
self.config['blueprint_dir'] = os.path.expanduser(self.config.get('blueprint_dir', ''))
86-
87-
except ValueError:
88-
print(self.colorize(
89-
"What happened?! The config file is not valid JSON. A "
90-
"re-install is likely the easiest fix.", "red"))
91-
raise
92-
except IOError:
93-
self.config = {
94-
'url': None,
95-
'username': None,
96-
}
97-
print(self.colorize(
98-
"It seems like this is your first time using the CLI. Please run "
99-
"'initial_setup' to configure.", "green"))
100-
# print(self.colorize(
101-
# "What happened?! Unable to find a config file. A re-install "
102-
# "is likely the easiest fix.", "red"))
103-
# raise
104-
105-
self.has_public_key = None
106-
107-
def _validate_auth(self):
108-
"""Verify that we can connect successfully to the api"""
109-
110-
# If there is no config, just force the user to go through initial setup
111-
if self.config['url'] is None:
112-
return
113-
114-
try:
115-
self.stacks.get_root()
116-
status_code = 200
117-
self.validated = (200 <= status_code <= 299)
118-
except ConnectionError:
119-
print(self.colorize(
120-
"Unable to connect to {0}".format(self.config["url"]),
121-
"red"))
122-
raise
123-
124-
if self.validated:
125-
print(self.colorize(
126-
"Config loaded and validated", "blue"))
127-
self.has_public_key = self.stacks.get_public_key()
128-
else:
129-
print(self.colorize(
130-
"ERROR: Unable to validate config", "red"))
131-
self.has_public_key = None
132-
133-
def _setprompt(self):
134-
135-
Cmd.prompt = self.colorize(
136-
self.PROMPT.format(**self.config),
137-
"blue")
138-
139-
if not self.validated and self.config['url'] is not None:
140-
print(self.colorize("""
141-
##
142-
## Unable to validate connection - one of several possibilities exist:
143-
## If this is the first time you've fired this up, you need to run
144-
## 'initial_setup' to configure your account details. If you've already
145-
## done that, there could be a network connection issue anywhere between
146-
## your computer and your stackd.io instance,
147-
## or your password may be incorrect, or ... etc.
148-
##
149-
""",
150-
"green"))
151-
152-
if self.validated and not self.has_public_key:
153-
print(self.colorize(
154-
"## Your account is missing the public key, run 'bootstrap' to fix",
155-
"red"))
156-
157-
def _print_summary(self, title, components):
158-
num_components = len(components)
159-
print("## {0} {1}{2}".format(
160-
num_components,
161-
title,
162-
"s" if num_components == 0 or num_components > 1 else ""))
163-
164-
for item in components:
165-
print("- Title: {0}\n Description: {1}".format(
166-
item.get("title"), item.get("description")))
167-
168-
if "status_detail" in item:
169-
print(" Status Detail: {0}\n".format(
170-
item.get("status_detail")))
171-
else:
172-
print("")
173-
174-
def do_account_summary(self, args=None):
175-
"""Get a summary of your account."""
176-
sys.stdout.write("Polling {0} ... ".format(self.config["url"]))
177-
sys.stdout.flush()
178-
179-
public_key = self.stacks.get_public_key()
180-
formulas = self.stacks.list_formulas()
181-
blueprints = self.stacks.list_blueprints()
182-
stacks = self.stacks.list_stacks()
183-
184-
sys.stdout.write("done\n")
185-
186-
print("## Username: {0}".format(self.config["username"]))
187-
print("## Public Key:\n{0}".format(public_key))
188-
189-
self._print_summary("Formula", formulas)
190-
self._print_summary("Blueprint", blueprints)
191-
self._print_summary("Stack", stacks)
1924

5+
import click
6+
import click_shell
1937

194-
def main():
8+
from stackdio.cli.mixins import blueprints, formulas, stacks
9+
from stackdio.cli.utils import pass_client
10+
from stackdio.client import StackdioClient
11+
from stackdio.client.config import CFG_FILE
12+
from stackdio.client.version import __version__
13+
14+
15+
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
16+
17+
HIST_FILE = os.path.join(os.path.expanduser('~'), '.stackdio-cli', 'history')
18+
19+
20+
@click_shell.shell(context_settings=CONTEXT_SETTINGS, prompt='stackdio > ',
21+
intro='stackdio-cli, v{0}'.format(__version__), hist_file=HIST_FILE)
22+
@click.version_option(__version__, '-v', '--version')
23+
@click.option('-c', '--config-file', help='The config file to use.',
24+
type=click.Path(dir_okay=False, file_okay=True), default=CFG_FILE,
25+
envvar='STACKDIO_CLI_CONFIG_FILE')
26+
@click.pass_context
27+
def stackdio(ctx, config_file):
28+
# Create a client instance
29+
client = StackdioClient(cfg_file=config_file)
30+
31+
# Throw an error if we're not configured already
32+
if ctx.invoked_subcommand not in ('configure', None) and not client.usable():
33+
raise click.UsageError('It looks like you haven\'t used this CLI before. Please run '
34+
'`stackdio-cli configure`')
35+
36+
# Put the client in the obj so other commands can pick it up
37+
ctx.obj = client
19538

196-
parser = argparse.ArgumentParser(
197-
description="Invoke the stackdio cli")
198-
parser.add_argument("--debug", action="store_true", help="Enable debugging output")
199-
args = parser.parse_args()
200-
201-
# an ugly hack to work around the fact that cmd2 is using optparse to parse
202-
# arguments for the commands; not sure what the "right" fix is, but as long
203-
# as we assume that we don't want any of our arguments to get passed into
204-
# the cmdloop this seems ok
205-
sys.argv = sys.argv[0:1]
206-
207-
shell = StackdioShell()
208-
if args.debug:
209-
shell.debug = True
210-
shell.cmdloop()
39+
40+
@stackdio.command(name='configure')
41+
@pass_client
42+
def configure(client):
43+
"""
44+
Configure the client
45+
"""
46+
client.config.prompt_for_config()
47+
48+
49+
@stackdio.command(name='server-version')
50+
@pass_client
51+
def server_version(client):
52+
"""
53+
Print the version of the server
54+
"""
55+
click.echo('stackdio-server, version {0}'.format(client.get_version()))
56+
57+
58+
# Add all our other commands
59+
stackdio.add_command(blueprints.blueprints)
60+
stackdio.add_command(stacks.stacks)
61+
stackdio.add_command(formulas.formulas)
62+
63+
64+
def main():
65+
# Just run our CLI tool
66+
stackdio()
21167

21268

21369
if __name__ == '__main__':

0 commit comments

Comments
 (0)