Skip to content

Commit 3d29e26

Browse files
author
Clark Perkins
committed
Finished the bulk of stack things. Left stack access rules off for now since we don't use them
1 parent d598f33 commit 3d29e26

File tree

5 files changed

+96
-98
lines changed

5 files changed

+96
-98
lines changed

stackdio/cli/mixins/stacks.py

Lines changed: 64 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import print_function
22

33
import click
4-
from cmd2 import Cmd
54

65
from stackdio.cli.mixins.blueprints import get_blueprint_id
6+
from stackdio.cli.polling import poll_and_wait
77
from stackdio.cli.utils import print_summary
88
from stackdio.client.exceptions import StackException
99

@@ -141,16 +141,75 @@ def perform_action(obj, stack_title, action):
141141
raise click.UsageError(e.message)
142142

143143

144+
def print_command_output(json_blob):
145+
for host in sorted(json_blob['std_out'], key=lambda x: x['host']):
146+
click.secho('{0}:'.format(host['host']), fg='green')
147+
click.echo(host['output'])
148+
click.echo()
149+
150+
144151
@stacks.command(name='run')
145-
@click.pass_obj
152+
@click.pass_context
146153
@click.argument('stack_title')
154+
@click.argument('host_target')
147155
@click.argument('command')
148-
@click.option('-w', '--wait', is_flag=True, help='Wait for the command to finish running')
149-
def run_command(obj, stack_title, command, wait):
156+
@click.option('-w', '--wait', is_flag=True, default=False,
157+
help='Wait for the command to finish running')
158+
@click.option('-t', '--timeout', type=click.INT, default=120,
159+
help='The amount of time to wait for the command in seconds. '
160+
'Ignored if used without the -w option.')
161+
def run_command(ctx, stack_title, host_target, command, wait, timeout):
150162
"""
151163
Run a command on all hosts in the stack
152164
"""
153-
pass
165+
client = ctx.obj['client']
166+
167+
stack_id = get_stack_id(client, stack_title)
168+
169+
resp = client.run_command(stack_id, host_target, command)
170+
171+
if not wait:
172+
# Grab the parent info name
173+
name = ctx.parent.parent.info_name
174+
175+
click.echo('Command "{0}" running on "{1}" hosts. '
176+
'Check the status by running:'.format(command, host_target))
177+
click.echo()
178+
click.secho(' {0} stacks command-output {1}'.format(name, resp['id']), fg='yellow')
179+
click.echo()
180+
return
181+
182+
@poll_and_wait
183+
def check_status():
184+
command_out = client.get_command(resp['id'])
185+
186+
if command_out['status'] != 'finished':
187+
return False
188+
189+
click.echo()
190+
print_command_output(command_out)
191+
192+
return True
193+
194+
check_status(max_time=timeout)
195+
196+
197+
@stacks.command(name='command-output')
198+
@click.pass_obj
199+
@click.argument('command_id')
200+
def get_command_output(obj, command_id):
201+
"""
202+
Get the status and output of a command
203+
"""
204+
client = obj['client']
205+
206+
resp = client.get_command(command_id)
207+
208+
if resp['status'] != 'finished':
209+
click.secho('Status: {0}'.format(resp['status']), fg='yellow')
210+
return
211+
212+
print_command_output(resp)
154213

155214

156215
def print_logs(client, stack_id):
@@ -215,95 +274,3 @@ def stack_logs(obj, stack_title, log_type, lines):
215274
print_logs(client, stack_id)
216275

217276
raise click.UsageError('Invalid log')
218-
219-
220-
class StackMixin(Cmd):
221-
222-
def _stack_access_rules(self, args):
223-
"""Get access rules for a stack"""
224-
225-
COMMANDS = ["list", "add", "delete"]
226-
227-
if len(args) < 2 or args[0] not in COMMANDS:
228-
print("Usage: stacks access_rules COMMAND STACK_NAME")
229-
print("Where COMMAND is one of: %s" % (", ".join(COMMANDS)))
230-
return
231-
232-
if args[0] == "list":
233-
stack_id = self.stacks.get_stack_id(args[1])
234-
groups = self.stacks.list_access_rules(stack_id)
235-
print("## {0} Access Groups".format(len(groups)))
236-
for group in groups:
237-
print("- Name: {0}".format(group['blueprint_host_definition']['title']))
238-
print(" Description: {0}".format(group['blueprint_host_definition']['description']))
239-
print(" Rules:")
240-
for rule in group['rules']:
241-
print(" {0}".format(rule['protocol']), end='')
242-
if rule['from_port'] == rule['to_port']:
243-
print("port {0} allows".format(rule['from_port']), end='')
244-
else:
245-
print("ports {0}-{1} allow".format(rule['from_port'],
246-
rule['to_port']), end='')
247-
print(rule['rule'])
248-
print('')
249-
return
250-
251-
elif args[0] == "add":
252-
if len(args) < 3:
253-
print("Usage: stacks access_rules add STACK_NAME GROUP_NAME")
254-
return
255-
256-
stack_id = self.stacks.get_stack_id(args[1])
257-
group_id = self.stacks.get_access_rule_id(stack_id, args[2])
258-
259-
protocol = raw_input("Protocol (tcp, udp, or icmp): ")
260-
from_port = raw_input("From port: ")
261-
to_port = raw_input("To port: ")
262-
rule = raw_input("Rule (IP address or group name): ")
263-
264-
data = {
265-
"action": "authorize",
266-
"protocol": protocol,
267-
"from_port": from_port,
268-
"to_port": to_port,
269-
"rule": rule
270-
}
271-
272-
self.stacks.edit_access_rule(group_id, data)
273-
274-
elif args[0] == "delete":
275-
if len(args) < 3:
276-
print("Usage: stacks access_rules delete STACK_NAME GROUP_NAME")
277-
return
278-
279-
stack_id = self.stacks.get_stack_id(args[1])
280-
group_id = self.stacks.get_access_rule_id(stack_id, args[2])
281-
282-
index = 0
283-
284-
rules = self.stacks.list_rules_for_group(group_id)
285-
286-
print('')
287-
for rule in rules:
288-
print("{0}) {1}".format(index, rule['protocol']), end='')
289-
if rule['from_port'] == rule['to_port']:
290-
print("port {0} allows".format(rule['from_port']), end='')
291-
else:
292-
print("ports {0}-{1} allow".format(rule['from_port'], rule['to_port']), end='')
293-
print(rule['rule'])
294-
index += 1
295-
print('')
296-
delete_index = int(raw_input("Enter the index of the rule to delete: "))
297-
298-
data = rules[delete_index]
299-
data['from_port'] = int(data['from_port'])
300-
data['to_port'] = int(data['to_port'])
301-
data['action'] = "revoke"
302-
303-
self.stacks.edit_access_rule(group_id, data)
304-
305-
print('')
306-
307-
args[0] = "list"
308-
309-
self._stack_access_rules(args)

stackdio/cli/polling.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
import time
3+
from functools import wraps
34

45
import click
56

@@ -13,16 +14,17 @@ def poll_and_wait(func):
1314
Execute func in increments of sleep_time for no more than max_time.
1415
Raise TimeoutException if we're not successful in max_time
1516
"""
17+
@wraps(func)
1618
def decorator(args=None, sleep_time=2, max_time=120):
1719
args = args or []
1820

1921
current_time = 0
2022

2123
success = func(*args)
2224
while not success and current_time < max_time:
23-
click.echo('.', nl=False)
2425
current_time += sleep_time
2526
time.sleep(sleep_time)
27+
click.echo('.', nl=False)
2628
success = func(*args)
2729

2830
if not success:

stackdio/cli/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ def print_summary(title, components):
3333
if 'description' in item:
3434
click.echo(' Description: {0}'.format(item['description']))
3535

36+
if 'status' in item:
37+
click.echo(' Status: {0}'.format(item['status']))
38+
3639
if 'status_detail' in item:
3740
click.echo(' Status Detail: {0}'.format(item['status_detail']))
3841

stackdio/client/stack.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ def do_stack_action(self, stack_id, action):
7272

7373
return {'action': action}
7474

75+
@post('stacks/{stack_id}/commands/')
76+
def run_command(self, stack_id, host_target, command):
77+
"""
78+
Run a command on all stacks
79+
"""
80+
return {
81+
'host_target': host_target,
82+
'command': command,
83+
}
84+
85+
@get('stacks/{stack_id}/commands/')
86+
def get_stack_commands(self, stack_id):
87+
"""
88+
Get all commands for a stack
89+
"""
90+
pass
91+
92+
@get('commands/{command_id}/')
93+
def get_command(self, command_id):
94+
"""
95+
Get information about a command
96+
"""
97+
pass
98+
7599
@get('stacks/{stack_id}/history/', paginate=True)
76100
def get_stack_history(self, stack_id):
77101
"""Get stack info"""

stackdio/client/version.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ def _parse_version_string(version_string):
6666

6767
# String trailing info
6868
version_string = re.split("[a-zA-Z]", version_string)[0]
69+
if version_string[-1] == '.':
70+
version_string = version_string[:-1]
6971
version = version_string.split(".")
7072

7173
# Pad length to 3

0 commit comments

Comments
 (0)