Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
42ba6f5
#1026 groundwork for capacity commands
allmightyspiff Sep 17, 2018
cd3d417
#1026 functions for create-options
allmightyspiff Sep 19, 2018
475b1eb
got capacity create working
allmightyspiff Sep 20, 2018
a53a75a
Merge branch '1026' of github.com:allmightyspiff/softlayer-python int…
allmightyspiff Sep 21, 2018
af4fd92
list and detail support for capacity groups
allmightyspiff Sep 24, 2018
87b51a9
create-guest base files
allmightyspiff Sep 24, 2018
39f9e1b
support for creating guests, some more features for list and detail
allmightyspiff Sep 27, 2018
9f99ed3
#1026 mostly done with the bits that actually do things. still need u…
allmightyspiff Sep 28, 2018
fcd90dd
Merge branch 'master' of github.com:allmightyspiff/softlayer-python i…
allmightyspiff Sep 28, 2018
1046cfe
Merge branch 'master' of github.com:softlayer/softlayer-python into 1026
allmightyspiff Sep 28, 2018
53492ee
#1026 unit tests and fixtures for ReservedCapacityGroup
allmightyspiff Sep 28, 2018
4815cdb
#1026 unit tests
allmightyspiff Oct 1, 2018
ac15931
#1026 pylint fixes
allmightyspiff Oct 1, 2018
0b1f637
Add export/import capabilities to/from IBM Cloud Object Storage
mikewurtz Oct 3, 2018
ec04e85
Fix unit tests
Oct 3, 2018
363266b
Removed unused import
Oct 3, 2018
13a7ac3
Merge pull request #1048 from khuong507/master
allmightyspiff Oct 3, 2018
fbd8034
5.5.3 changelog
allmightyspiff Oct 3, 2018
f5bb75a
Merge pull request #1049 from allmightyspiff/master
allmightyspiff Oct 3, 2018
0ac4de3
Merge remote-tracking branch 'origin/master' into 1026
allmightyspiff Oct 3, 2018
87a8ded
doc updates
allmightyspiff Oct 3, 2018
ecd5d1b
Fix `post_uri` parameter name on docstring
Jorge-Rodriguez Oct 4, 2018
c15db0e
Merge pull request #1050 from Jorge-Rodriguez/master
allmightyspiff Oct 4, 2018
df0f47f
Fix manager and add CLI support
mikewurtz Oct 4, 2018
f4b797d
Fix test
mikewurtz Oct 4, 2018
9d87c90
vs capacity docs
allmightyspiff Oct 4, 2018
b2e6784
Fixed an object mask
allmightyspiff Oct 4, 2018
082c1ea
more docs
allmightyspiff Oct 4, 2018
893ff90
fixed whitespace issue
allmightyspiff Oct 4, 2018
a0da453
Fixed name of ibm-api-key in cli
mikewurtz Oct 5, 2018
192b192
fixed suspend cloud server order.
Oct 5, 2018
3e770a2
Merge branch '1026' of github.com:allmightyspiff/softlayer-python int…
allmightyspiff Oct 8, 2018
d4a72b3
Address comments and add appropriate code
mikewurtz Oct 8, 2018
ad84d58
unit test suspend cloud server
Oct 8, 2018
2385bf2
Add KeyProtect instance in help text
mikewurtz Oct 8, 2018
03d3c8e
#1026 resolving pull request feedback
allmightyspiff Oct 8, 2018
0d22da9
fixed unit tests
allmightyspiff Oct 8, 2018
3a6b7b3
Change defaults to None
mikewurtz Oct 9, 2018
f5da2d6
Unit test suspend cloud server order
Oct 9, 2018
17cae65
Merge pull request #1053 from mikewurtz/icosImageSupport
allmightyspiff Oct 9, 2018
6f58b94
Update to use click 7
felixonmars Oct 9, 2018
3b76899
Fix exit code of edit-permissions test
felixonmars Oct 10, 2018
8056e81
Refactored suspend cloud server order
Oct 10, 2018
d7473db
Refactored suspend cloud server order
Oct 10, 2018
d63f92a
Merge pull request #1055 from felixonmars/new-click
allmightyspiff Oct 10, 2018
4cff83c
some final touches, ended up auto-translating commands so they confor…
allmightyspiff Oct 10, 2018
d989dfd
Refactored suspend cloud server order
Oct 11, 2018
3a50f46
Merge pull request #1054 from FernandoOjeda/fo_suspend_cloud_server
allmightyspiff Oct 11, 2018
e89c8f9
Merge pull request #1051 from allmightyspiff/1026
allmightyspiff Oct 16, 2018
ef4d507
5.6.0 release
allmightyspiff Oct 16, 2018
a929d9c
pinning urllib3 and request since the newest version of urllib3 is in…
allmightyspiff Oct 16, 2018
8a755be
Merge pull request #1058 from allmightyspiff/master
allmightyspiff Oct 17, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
# Change Log


## [5.5.1] - 2018-08-31
## [5.6.0] - 2018-10-16
- Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.3...v5.6.0

+ #1026 Support for [Reserved Capacity](https://console.bluemix.net/docs/vsi/vsi_about_reserved.html#about-reserved-virtual-servers)
* `slcli vs capacity create`
* `slcli vs capacity create-guest`
* `slcli vs capacity create-options`
* `slcli vs capacity detail`
* `slcli vs capacity list`
+ #1050 Fix `post_uri` parameter name on docstring
+ #1039 Fixed suspend cloud server order.
+ #1055 Update to use click 7
+ #1053 Add export/import capabilities to/from IBM Cloud Object Storage to the image manager as well as the slcli.


## [5.5.3] - 2018-08-31
- Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.2...v5.5.3

+ Added `slcli user delete`
+ #1023 Added `slcli order quote` to let users create a quote from the slcli.
+ #1032 Fixed vs upgrades when using flavors.
+ #1034 Added pagination to ticket list commands
+ #1037 Fixed DNS manager to be more flexible and support more zone types.
+ #1044 Pinned Click library version at >=5 < 7

## [5.5.2] - 2018-08-31
- Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.1...v5.5.2

+ #1018 Fixed hardware credentials.
Expand Down
2 changes: 1 addition & 1 deletion SoftLayer/CLI/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def mask(self):
def get_formatter(columns):
"""This function returns a callback to use with click options.

The retuend function parses a comma-separated value and returns a new
The returned function parses a comma-separated value and returns a new
ColumnFormatter.

:param columns: a list of Column instances
Expand Down
2 changes: 1 addition & 1 deletion SoftLayer/CLI/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def cli(env,

@cli.resultcallback()
@environment.pass_env
def output_diagnostics(env, verbose=0, **kwargs):
def output_diagnostics(env, result, verbose=0, **kwargs):
"""Output diagnostic information."""

if verbose > 0:
Expand Down
12 changes: 10 additions & 2 deletions SoftLayer/CLI/image/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@
@click.command()
@click.argument('identifier')
@click.argument('uri')
@click.option('--ibm-api-key',
default=None,
help="The IBM Cloud API Key with access to IBM Cloud Object "
"Storage instance. For help creating this key see "
"https://console.bluemix.net/docs/services/cloud-object-"
"storage/iam/users-serviceids.html#serviceidapikeys")
@environment.pass_env
def cli(env, identifier, uri):
def cli(env, identifier, uri, ibm_api_key):
"""Export an image to object storage.

The URI for an object storage object (.vhd/.iso file) of the format:
swift://<objectStorageAccount>@<cluster>/<container>/<objectPath>
or cos://<clusterName>/<bucketName>/<objectPath> if using IBM Cloud
Object Storage
"""

image_mgr = SoftLayer.ImageManager(env.client)
image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image')
result = image_mgr.export_image_to_uri(image_id, uri)
result = image_mgr.export_image_to_uri(image_id, uri, ibm_api_key)

if not result:
raise exceptions.CLIAbort("Failed to export Image")
42 changes: 39 additions & 3 deletions SoftLayer/CLI/image/import.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,44 @@
default="",
help="The note to be applied to the imported template")
@click.option('--os-code',
default="",
help="The referenceCode of the operating system software"
" description for the imported VHD")
" description for the imported VHD, ISO, or RAW image")
@click.option('--ibm-api-key',
default=None,
help="The IBM Cloud API Key with access to IBM Cloud Object "
"Storage instance and IBM KeyProtect instance. For help "
"creating this key see https://console.bluemix.net/docs/"
"services/cloud-object-storage/iam/users-serviceids.html"
"#serviceidapikeys")
@click.option('--root-key-id',
default=None,
help="ID of the root key in Key Protect")
@click.option('--wrapped-dek',
default=None,
help="Wrapped Data Encryption Key provided by IBM KeyProtect. "
"For more info see https://console.bluemix.net/docs/"
"services/key-protect/wrap-keys.html#wrap-keys")
@click.option('--kp-id',
default=None,
help="ID of the IBM Key Protect Instance")
@click.option('--cloud-init',
is_flag=True,
help="Specifies if image is cloud-init")
@click.option('--byol',
is_flag=True,
help="Specifies if image is bring your own license")
@click.option('--is-encrypted',
is_flag=True,
help="Specifies if image is encrypted")
@environment.pass_env
def cli(env, name, note, os_code, uri):
def cli(env, name, note, os_code, uri, ibm_api_key, root_key_id, wrapped_dek,
kp_id, cloud_init, byol, is_encrypted):
"""Import an image.

The URI for an object storage object (.vhd/.iso file) of the format:
swift://<objectStorageAccount>@<cluster>/<container>/<objectPath>
or cos://<clusterName>/<bucketName>/<objectPath> if using IBM Cloud
Object Storage
"""

image_mgr = SoftLayer.ImageManager(env.client)
Expand All @@ -33,6 +62,13 @@ def cli(env, name, note, os_code, uri):
note=note,
os_code=os_code,
uri=uri,
ibm_api_key=ibm_api_key,
root_key_id=root_key_id,
wrapped_dek=wrapped_dek,
kp_id=kp_id,
cloud_init=cloud_init,
byol=byol,
is_encrypted=is_encrypted
)

if not result:
Expand Down
1 change: 1 addition & 0 deletions SoftLayer/CLI/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
('virtual:reload', 'SoftLayer.CLI.virt.reload:cli'),
('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'),
('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'),
('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'),

('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'),
('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'),
Expand Down
48 changes: 48 additions & 0 deletions SoftLayer/CLI/virt/capacity/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Manages Reserved Capacity."""
# :license: MIT, see LICENSE for more details.

import importlib
import os

import click

CONTEXT = {'help_option_names': ['-h', '--help'],
'max_content_width': 999}


class CapacityCommands(click.MultiCommand):
"""Loads module for capacity related commands.

Will automatically replace _ with - where appropriate.
I'm not sure if this is better or worse than using a long list of manual routes, so I'm trying it here.
CLI/virt/capacity/create_guest.py -> slcli vs capacity create-guest
"""

def __init__(self, **attrs):
click.MultiCommand.__init__(self, **attrs)
self.path = os.path.dirname(__file__)

def list_commands(self, ctx):
"""List all sub-commands."""
commands = []
for filename in os.listdir(self.path):
if filename == '__init__.py':
continue
if filename.endswith('.py'):
commands.append(filename[:-3].replace("_", "-"))
commands.sort()
return commands

def get_command(self, ctx, cmd_name):
"""Get command for click."""
path = "%s.%s" % (__name__, cmd_name)
path = path.replace("-", "_")
module = importlib.import_module(path)
return getattr(module, 'cli')


# Required to get the sub-sub-sub command to work.
@click.group(cls=CapacityCommands, context_settings=CONTEXT)
def cli():
"""Base command for all capacity related concerns"""
pass
51 changes: 51 additions & 0 deletions SoftLayer/CLI/virt/capacity/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Create a Reserved Capacity instance."""

import click


from SoftLayer.CLI import environment
from SoftLayer.CLI import formatting
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager


@click.command(epilog=click.style("""WARNING: Reserved Capacity is on a yearly contract"""
""" and not cancelable until the contract is expired.""", fg='red'))
@click.option('--name', '-n', required=True, prompt=True,
help="Name for your new reserved capacity")
@click.option('--backend_router_id', '-b', required=True, prompt=True, type=int,
help="backendRouterId, create-options has a list of valid ids to use.")
@click.option('--flavor', '-f', required=True, prompt=True,
help="Capacity keyname (C1_2X2_1_YEAR_TERM for example).")
@click.option('--instances', '-i', required=True, prompt=True, type=int,
help="Number of VSI instances this capacity reservation can support.")
@click.option('--test', is_flag=True,
help="Do not actually create the virtual server")
@environment.pass_env
def cli(env, name, backend_router_id, flavor, instances, test=False):
"""Create a Reserved Capacity instance.

*WARNING*: Reserved Capacity is on a yearly contract and not cancelable until the contract is expired.
"""
manager = CapacityManager(env.client)

result = manager.create(
name=name,
backend_router_id=backend_router_id,
flavor=flavor,
instances=instances,
test=test)
if test:
table = formatting.Table(['Name', 'Value'], "Test Order")
container = result['orderContainers'][0]
table.add_row(['Name', container['name']])
table.add_row(['Location', container['locationObject']['longName']])
for price in container['prices']:
table.add_row(['Contract', price['item']['description']])
table.add_row(['Hourly Total', result['postTaxRecurring']])
else:
table = formatting.Table(['Name', 'Value'], "Reciept")
table.add_row(['Order Date', result['orderDate']])
table.add_row(['Order ID', result['orderId']])
table.add_row(['status', result['placedOrder']['status']])
table.add_row(['Hourly Total', result['orderDetails']['postTaxRecurring']])
env.fout(table)
63 changes: 63 additions & 0 deletions SoftLayer/CLI/virt/capacity/create_guest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""List Reserved Capacity"""
# :license: MIT, see LICENSE for more details.

import click

from SoftLayer.CLI import environment
from SoftLayer.CLI import formatting
from SoftLayer.CLI import helpers
from SoftLayer.CLI.virt.create import _parse_create_args as _parse_create_args
from SoftLayer.CLI.virt.create import _update_with_like_args as _update_with_like_args
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager


@click.command()
@click.option('--capacity-id', type=click.INT, help="Reserve capacity Id to provision this guest into.")
@click.option('--primary-disk', type=click.Choice(['25', '100']), default='25', help="Size of the main drive.")
@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN.")
@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN.")
@click.option('--os', '-o', help="OS install code. Tip: you can specify <OS>_LATEST.")
@click.option('--image', help="Image ID. See: 'slcli image list' for reference.")
@click.option('--boot-mode', type=click.STRING,
help="Specify the mode to boot the OS in. Supported modes are HVM and PV.")
@click.option('--postinstall', '-i', help="Post-install script to download.")
@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user.")
@helpers.multi_option('--disk', help="Additional disk sizes.")
@click.option('--private', is_flag=True, help="Forces the VS to only have access the private network.")
@click.option('--like', is_eager=True, callback=_update_with_like_args,
help="Use the configuration from an existing VS.")
@click.option('--network', '-n', help="Network port speed in Mbps.")
@helpers.multi_option('--tag', '-g', help="Tags to add to the instance.")
@click.option('--userdata', '-u', help="User defined metadata string.")
@click.option('--ipv6', is_flag=True, help="Adds an IPv6 address to this guest")
@click.option('--test', is_flag=True,
help="Test order, will return the order container, but not actually order a server.")
@environment.pass_env
def cli(env, **args):
"""Allows for creating a virtual guest in a reserved capacity."""
create_args = _parse_create_args(env.client, args)
if args.get('ipv6'):
create_args['ipv6'] = True
create_args['primary_disk'] = args.get('primary_disk')
manager = CapacityManager(env.client)
capacity_id = args.get('capacity_id')
test = args.get('test')

result = manager.create_guest(capacity_id, test, create_args)

env.fout(_build_receipt(result, test))


def _build_receipt(result, test=False):
title = "OrderId: %s" % (result.get('orderId', 'No order placed'))
table = formatting.Table(['Item Id', 'Description'], title=title)
table.align['Description'] = 'l'

if test:
prices = result['prices']
else:
prices = result['orderDetails']['prices']

for item in prices:
table.add_row([item['id'], item['item']['description']])
return table
45 changes: 45 additions & 0 deletions SoftLayer/CLI/virt/capacity/create_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""List options for creating Reserved Capacity"""
# :license: MIT, see LICENSE for more details.

import click

from SoftLayer.CLI import environment
from SoftLayer.CLI import formatting
from SoftLayer.managers.vs_capacity import CapacityManager as CapacityManager


@click.command()
@environment.pass_env
def cli(env):
"""List options for creating Reserved Capacity"""
manager = CapacityManager(env.client)
items = manager.get_create_options()

items.sort(key=lambda term: int(term['capacity']))
table = formatting.Table(["KeyName", "Description", "Term", "Default Hourly Price Per Instance"],
title="Reserved Capacity Options")
table.align["Hourly Price"] = "l"
table.align["Description"] = "l"
table.align["KeyName"] = "l"
for item in items:
table.add_row([
item['keyName'], item['description'], item['capacity'], get_price(item)
])
env.fout(table)

regions = manager.get_available_routers()
location_table = formatting.Table(['Location', 'POD', 'BackendRouterId'], 'Orderable Locations')
for region in regions:
for location in region['locations']:
for pod in location['location']['pods']:
location_table.add_row([region['keyname'], pod['backendRouterName'], pod['backendRouterId']])
env.fout(location_table)


def get_price(item):
"""Finds the price with the default locationGroupId"""
the_price = "No Default Pricing"
for price in item.get('prices', []):
if not price.get('locationGroupId'):
the_price = "%0.4f" % float(price['hourlyRecurringFee'])
return the_price
Loading