diff --git a/.gitignore b/.gitignore index 411709398..438f1d703 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ docs/_build/* build/* dist/* *.egg-info +.cache + diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 07b9dd068..000000000 --- a/CHANGELOG +++ /dev/null @@ -1,303 +0,0 @@ -5.2.0 - - * Significant additions to `slcli file` and `slcli block` commands. You can now authorize hosts, revoke access. You can also create, delete, restore, disable, enable snapshots. These features need to be battle-tested so report any issues that you see. - - * Adds logic to `SoftLayer.create_client_from_env` that detects if a REST endpoint_url was given in order to use the REST transport automatically. This means that you can also configure REST endpoints for `slcli`. The default still uses XML-RPC endpoint, but from a small amount of testing shows that the REST transport is significantly faster. - - * Adds `--network-space` to `slcli subnet list` in order to filter subnets based on network space. The two main options are PUBLIC and PRIVATE. For example, to list all public subnets, you can run: `slcli subnet list --network-space=PUBLIC` - - * Fixes a UnicodeEncodeError when piping slcli output with unicode characters. This was mostly reported with `slcli image list` but could also happen with many other calls. - - * Adds a new, non-default column, "created_by" that shows who ordered the volume for `slcli file volume-list` and `slcli block volume-list`. - - * Fixed a bug where os_version was not displaying correctly in `slcli virtual detail` or `slcli virtual detail` - - * Adds a new `slcli report bandwidth` command that will print a report of all bandwidth pools and virtual/hardware servers that your user has access to. - - * Adds an "IN" syntax to the `slcli call-api` command. For example, to find VSIs that are in either the dal05 or sng01 datacenter you can run this command: `slcli call-api Account getVirtualGuests -f 'virtualGuests.datacenter.name IN dal05,sng01'` - -5.1.0 - - * Added block storage functionality. You can order, list, detail, cancel volumes. You can list and delete snapshots. You can also list ACLs for volumes. - - * Added functionality to attach/detach devices to tickets - - * Virtual list now lists users and passwords for all known software - - * Fixes bug with `vlan detail` CLI command - -5.0.1 - - * Adds missing depdendency that was previously pulled in by prompt_toolkit - - * Fix a bug by updating the CDN manager to use the new purge method - - * Fixes bug that occured when iscsi listings with resources have no datacenter - -5.0.0 - - * Adds a shell (accessable with `slcli shell`) which provides autocomplete for slcli commands and options - - * Move modifying nic speed to `slcli virtual edit` and `slcli hardware edit` instead of having its own command - - * How filters work with `slcli call-api` has changed significantly. Instead of accepting JSON, it now accepts an easier-to-use format. See `slcli call-api -h` for examples - - * Adds manager for object storage - - * 'virtual' and 'hardware' are preferred over 'vs' and 'server' in the CLI - - * Improved REST transport support - - * Many bug fixes - -4.1.1 - - * Fixes to work with Click v5 - - * Re-adds `--no-public` option to only provision private interfaces with servers via `slcli server create` - - * Removes non-functional `--vlan-public` and `--vlan-private` from `slcli server create` - - * VSManager.wait_for_ready will now behave as it is documented to behave. - -4.1.0 - - * Adds a shell which provides a shell interface for `slcli`. This is available by using `slcli shell` - - * `slcli vs create` and `slcli server create` will now prompt for missing required options - - * Fixes `slcli firewall add` command - - * Handles case where `slcli vs detail` and `slcli server detail` was causing an error when trying to display the creator - - * Fixes VSManager.verify_create_instance() with tags (and, in turn, `slcli vs create --test` with tags) - - * Fixes `vs resume` command - - * Updates hardware ordering to deal with location-specific prices - - * Fixes several description errors in the CLI - - * Running `vs edit` without a tag option will no longer remove all tags - - * Adds editing of hardware tags - -4.0.4 - - * Fixes bug with pulling the userData property for the virtual server detail - - * Fixes a class of bugs invloving unicode from the API - -4.0.3 - - * Fixes bug with `slcli vs ready` command - - * Fixes bug with `slcli loadbal service-add` command - - * Fixes bug with `slcli vlan list` with vlans that don't have a datacenter - - * Improves validation of virtual server and hardware create commands - -4.0.2 - - * Fixes a bug that breaks user confirmation prompts - - * Fixes general issue with sorting on certain row types in the CLI - - * Fixes image capture for Windows guests - - -4.0.1 - - * Fixes bug in `sl setup` command not properly defaulting to current values. - - * Fixes bug where turning off compression headers would still send compression headers. - - * Reverts to using ids over global identifiers for `sl vs list` and `sl server list`. - - -4.0.0 - - * Because there are many changes between version 3 and version 4, it is strongly recommend to pin the version of the SoftLayer python bindings as soon as you can in order to prevent unintentional breakage when upgrading. To keep yourself on version 3, you can use this directive: softlayer>=3,<4. That can be used with pip (pip install softlayer>=3,<4), requirements in your setup.py and/or in your requirements.txt file. - - * CLI: The command is renamed from `sl` to `slcli` to avoid package conflicts. - - * CLI: Global options now need to be specified right after the `slcli` command. For example, you would now use `slcli --format=raw list` over `slcli vs list --format=raw`. This is a change for the following options: - * --format - * -c or --config - * --debug - * --proxy - * -y or --really - * --version - - * API: The hardware manager has a significant update to how place_order() works. It will now only support the fast server provisioning package which has presets for options like CPU, Memory and disk. - - * API: The client transport is now pluggable. If you want to add extra logging or accounting, you can now subclass or wrap softlayer.transports.XmlRpcTransport in order to do so. A good example of that is done with softlayer.transports.TimingTransport. - - * API: Removed deprecated SoftLayer.CCIManager. - - * API: Adds virtual server rescue command to SoftLayer.VSManager - - * API+CLI: Adds ability to import virtual images from a given URI. The API only supports importing from a swift account using 'swift://'. For more details, see http://developer.softlayer.com/reference/services/SoftLayer_Virtual_Guest_Block_Device_Template_Group/createFromExternalSource. - - * CLI: A `--fixtures` global flag was added to pull from fixture data instead of the API. This is useful for discovery, demonstration and testing purposes. - - * CLI: A `--verbose` or `-v` flag was added to eventually replace `--debug`. To make a command more verbose, simply add more `-v` flags. For example `sl -vvv vs list` will be the most verbose and show everything down to request/response tracing. - - * CLI: Significant changes were done to the CLI argument parsing. Docopt was dropped in favor of click. Therefore, some subtle differences which aren't documented here may exist. - - * CLI: Credentials can now be requested using `sl vs credentials `, `sl hardware credentials ` and `sl nas credentials ` for virtual servers, hardware servers and NAS accounts respectively. - - * CLI: Adds virtual server rescue command, `sl vs rescue ` - - -3.3.0 - - * CLI+API: Load balancer support - - * CLI: More detail added to the `sl image detail` and `sl image list` commands - - * CLI: Adds command to import DNS entries from BIND zone files - - * CLI+API: Adds support for booting into rescue images for virtual servers and hardware - - * API: Adds ability to order virtual and hardare servers from a quote to the ordering manager - - * CLI: Fixes bug with `sl server list-chassis` and `sl server list-chassis` - - * API: Restructure of the way custom authentication can be plugged in the API client - - * Several other bug fixes - -3.2.0 - - * CLI+API: Added firewall manager and CLI module - - * CLI+API: Added iscsi manager and CLI module - - * API: Added ability to create multiple virtual servers at once to VSManager - - * API: Added OrderingManager. Remove hard-coded price IDs - - * Fixed several small bugs - -3.1.0 - - * CLI+API: Added CDN manager and CLI module - - * CLI+API: Added ticket manager and CLI module - - * CLI+API: Added image manager and improves image CLI module - - * CLI+API: Added the ability to specify a proxy URL for API bindings and the CLI - - * API: six is now used to provide support for Python 2 and Python 3 with the same source - - * CLI+API: Added ability to resize a virtual machine - - * CLI+API: Implemented product name changes in accordance with SoftLayer's new product names. Existing managers should continue to work as before. Minor CLI changes were necessary. - - * CLI+API: Added firewall manager and CLI module - - * CLI+API: Added load balancer manager and CLI module - - * Many bug fixes and minor suggested improvements - - -3.0.2 - - * CLI+API: Simplified object mask reformatting and added support for more complex masks. - - * CLI: Fixed the sl bmc create --network argument. - - * CLI+API: Improved output of the message queue feature and fixed some minor bugs. - - * CLI: Fixed an error when using --test and ordering a non-private subnet. - - * API: Fix to prevent double counting results in summary_by_datacenter(). - - * CLI+API: Added IPMI IP address to hardware details. - - * CLI: Added support for ordering multiple disks when creating a CCI. - - * API: Added flag to disable compression on HTTP requests. - - * CLI: Added CIDR information to subnet displays. - -3.0.1 - - * CLI: Fixed an error message about pricing information that appeared when ordering a new private subnet. - - * CLI+API: Added ability to specify SSH keys when reloading CCIs and servers. - -3.0.0 - - * Many bug fixes and consistency improvements - - * API: Removes old API client interfaces which have been deprecated in the v2. See link for more details: https://softlayer-api-python-client.readthedocs.org/en/latest/api/client/#backwards-compatibility - - * CLI+API: Improved dedicated server ordering. Adds power management for hardware servers: power-on, power-off, power-cycle, reboot - - * CLI+API: Adds a networking manager and adds several network-related CLI modules. This includes the ability to: - - * list, create, cancel and assign global IPs - - * list, create, cancel and detail subnets. Also has the ability to lookup details about an IP address with 'sl subnet lookup' - - * list, detail VLANs - - * show and edit RWhois data - - * CLI+API: Adds SoftLayer Message Queue Service bindings (as a manager) and a CLI counterpart. With this you can interact with existing message queue accounts - - * CLI+API: Ability to manage SSH Keys with a manager and a CLI module - - * CLI+API: Adds the ability to create CCIs with the following options: metadata, post-install script, SSH key - - * CLI: Adds templating for creating CCIs and hardware nodes which can be used to create more CCIs and hardware with the same settings - - * CLI+API: Adds the ability to create hardware servers with a default SSH key - - * CLI: Adds a --debug option to print out debugging information. --debug=3 is the highest log level which prints full HTTP request/responses including the body - - * CLI: The commands in the main help are now organized into categories - - -2.3.0 - - * Several bug fixes and improvements - - * Removed Python 2.5 support. Some stuff MIGHT work with 2.5 but it is no longer tested - - * API: Refactored managers into their own module to not clutter the top level - - * CLI+API: Added much more hardware support: Filters for hardware listing, dedicated server/bare metal cloud ordering, hardware cancellation - - * CLI+API: Added DNS Zone filtering (server side) - - * CLI+API: Added Post Install script support for CCIs and hardware - - * CLI: Added Message queue functionality - - * CLI: Added --debug option to CLI commands - - * API: Added more logging - - * API: Added token-based auth so you can use the API bindings with your username/password if you want. (It's still highly recommended to use your API key instead of your password) - - -2.2.0 - - * Consistency changes/bug fixes - - * Added sphinx documentation. See it here: https://softlayer-api-python-client.readthedocs.org - - * CCI: Adds Support for Additional Disks - - * CCI: Adds a way to block until transactions are done on a CCI - - * CLI: For most CCI commands, you can specify id, hostname, private ip or public ip as - - * CLI: Adds the ability to filter list results for CCIs - - * API: for large result sets, requests can now be chunked into smaller batches on the server side. Using service.iter_call('getObjects', ...) or service.getObjects(..., iter=True) will return a generator regardless of the results returned. offset and limit can be passed in like normal. An additional named parameter of 'chunk' is used to limit the number of items coming back in a single request, defaults to 100 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..4a899f129 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,273 @@ +# Change Log + +## [Unreleased] + - Changes: https://github.com/softlayer/softlayer-python/compare/v5.2.1...HEAD + +### Added + +### Changed + +## [5.2.1] - 2016-10-4 + - Changes: https://github.com/softlayer/softlayer-python/compare/v5.2.0...v5.2.1 + +### Added + - CLI: Adds a new 'jsonraw' format option that will print JSON without whitespace. This is useful for integrating the CLI with other tooling. + +### Changed + - API: Fixes JSON loading while using the REST transport with Python 3 + - CLI+API: Metadata disks are now excluded when capturing "all" block devices with `slcli virtual capture --all` + - CLI: Fixes a bug where dns zone importing was not importing wildcard records + +## [5.2.0] - 2016-08-25 + - Changes: https://github.com/softlayer/softlayer-python/compare/v5.1.0...v5.2.0 + +### Added + - CLI+API: Significant additions to `slcli file` and `slcli block` commands. You can now authorize hosts, revoke access. You can also create, delete, restore, disable, enable snapshots. These features need to be battle-tested so report any issues that you see. + - CLI+API: Adds logic to `SoftLayer.create_client_from_env` that detects if a REST endpoint_url was given in order to use the REST transport automatically. This means that you can also configure REST endpoints for `slcli`. The default still uses XML-RPC endpoint, but from a small amount of testing shows that the REST transport is significantly faster. + - CLI: Adds `--network-space` to `slcli subnet list` in order to filter subnets based on network space. The two main options are PUBLIC and PRIVATE. For example, to list all public subnets, you can run: `slcli subnet list --network-space=PUBLIC` + - CLI: Adds a new, non-default column, "created_by" that shows who ordered the volume for `slcli file volume-list` and `slcli block volume-list`. + - CLI: Adds a new `slcli report bandwidth` command that will print a report of all bandwidth pools and virtual/hardware servers that your user has access to. + - CLI: Adds an "IN" syntax to the `slcli call-api` command. For example, to find VSIs that are in either the dal05 or sng01 datacenter you can run this command: `slcli call-api Account getVirtualGuests -f 'virtualGuests.datacenter.name IN dal05,sng01'` + +### Changed + - CLI: Fixes a UnicodeEncodeError when piping slcli output with unicode characters. This was mostly reported with `slcli image list` but could also happen with many other calls. + - CLI: Fixed a bug where os_version was not displaying correctly in `slcli virtual detail` or `slcli virtual detail` + +## [5.1.0] - 2016-05-12 + - Changes: https://github.com/softlayer/softlayer-python/compare/v5.0.1...v5.1.0 + +### Added + - CLI+API: Added block storage functionality. You can order, list, detail, cancel volumes. You can list and delete snapshots. You can also list ACLs for volumes. + - Added functionality to attach/detach devices to tickets + - CLI: Virtual list now lists users and passwords for all known software + +### Changed + - CLI: Fixes bug with `vlan detail` CLI command + +## [5.0.1] - 2016-03-30 + - https://github.com/softlayer/softlayer-python/compare/v5.0.0...v5.0.1 + +### Changed + - CLI: Adds missing dependency that was previously pulled in by prompt_toolkit + - API: Fix a bug by updating the CDN manager to use the new purge method + - CLI: Fixes bug that occured when iscsi listings with resources have no datacenter + +## [5.0.0] - 2016-03-18 + - Changes: https://github.com/softlayer/softlayer-python/compare/v4.1.1...v5.0.0 + +### Added + - CLI: Adds a shell (accessable with `slcli shell`) which provides autocomplete for slcli commands and options + - CLI: How filters work with `slcli call-api` has changed significantly. Instead of accepting JSON, it now accepts an easier-to-use format. See `slcli call-api -h` for examples + - API: Adds manager for object storage + - API: Improved REST transport support + +### Changed + - CLI: Move modifying nic speed to `slcli virtual edit` and `slcli hardware edit` instead of having its own command + - CLI: 'virtual' and 'hardware' are preferred over 'vs' and 'server' in the CLI + - CLI+API: Many unmentioned bug fixes + +## [4.1.1] - 2015-08-17 + - Changes: https://github.com/softlayer/softlayer-python/compare/v4.1.0...v4.1.1 + +### Added + - CLI: Re-adds `--no-public` option to only provision private interfaces with servers via `slcli server create` + +### Changed + - CLI: Fixes to work with Click v5 + - Removes non-functional `--vlan-public` and `--vlan-private` from `slcli server create` + - VSManager.wait_for_ready will now behave as it is documented to behave. + +## [4.1.0] - 2015-08-17 + - Changes: https://github.com/softlayer/softlayer-python/compare/v4.0.4...v4.1.0 + +### Added + - CLI: Adds a shell which provides a shell interface for `slcli`. This is available by using `slcli shell` + - CLI: `slcli vs create` and `slcli server create` will now prompt for missing required options + - CLI+API: Adds editing of hardware tags + +### Changed + - CLI: Fixes `slcli firewall add` command + - CLI: Handles case where `slcli vs detail` and `slcli server detail` was causing an error when trying to display the creator + - API: Fixes VSManager.verify_create_instance() with tags (and, in turn, `slcli vs create --test` with tags) + - CLI: Fixes `vs resume` command + - API+CLI: Updates hardware ordering to deal with location-specific prices + - CLI: Fixes several description errors in the CLI + - CLI: Running `vs edit` without a tag option will no longer remove all tags + +## [4.0.4] - 2015-06-30 + - Changes: https://github.com/softlayer/softlayer-python/compare/v4.0.3...v4.0.4 + +### Changed + - CLI: Fixes bug with pulling the userData property for the virtual server detail + - CLI: Fixes a class of bugs invloving unicode from the API + +## [4.0.3] - 2015-06-15 + - Changes: https://github.com/softlayer/softlayer-python/compare/v4.0.2...v4.0.3 + +### Changed + - CLI: Fixes bug with `slcli vs ready` command + - CLI: Fixes bug with `slcli loadbal service-add` command + - CLI: Fixes bug with `slcli vlan list` with vlans that don't have a datacenter + - CLI: Improves validation of virtual server and hardware create commands + +## [4.0.2] - 2015-05-04 + - Changes https://github.com/softlayer/softlayer-python/compare/v4.0.1...v4.0.2 + +### Changed + - CLI: Fixes a bug that breaks user confirmation prompts + - CLI: Fixes general issue with sorting on certain row types in the CLI + - API: Fixes image capture for Windows guests + +## [4.0.1] - 2015-04-28 + - Changes: https://github.com/softlayer/softlayer-python/compare/v4.0.0...v4.0.1 + +### Changed + - CLI: Fixes bug in `sl setup` command not properly defaulting to current values. + - API: Fixes bug where turning off compression headers would still send compression headers. + - CLI: Reverts to using ids over global identifiers for `sl vs list` and `sl server list`. + +## [4.0.0] - 2015-04-21 + - Changes: https://github.com/softlayer/softlayer-python/compare/v3.3.0...v4.0.0 + - Because there are many changes between version 3 and version 4, it is strongly recommend to pin the version of the SoftLayer python bindings as soon as you can in order to prevent unintentional breakage when upgrading. To keep yourself on version 3, you can use this directive: softlayer>=3,<4. That can be used with pip (pip install softlayer>=3,<4), requirements in your setup.py and/or in your requirements.txt file. + +### Added + - API: The client transport is now pluggable. If you want to add extra logging or accounting, you can now subclass or wrap softlayer.transports.XmlRpcTransport in order to do so. A good example of that is done with softlayer.transports.TimingTransport. + - API+CLI: Adds ability to import virtual images from a given URI. The API only supports importing from a swift account using 'swift://'. For more details, see http://developer.softlayer.com/reference/services/SoftLayer_Virtual_Guest_Block_Device_Template_Group/createFromExternalSource. + - CLI: A `--fixtures` global flag was added to pull from fixture data instead of the API. This is useful for discovery, demonstration and testing purposes. + - CLI: A `--verbose` or `-v` flag was added to eventually replace `--debug`. To make a command more verbose, simply add more `-v` flags. For example `sl -vvv vs list` will be the most verbose and show everything down to request/response tracing. + - CLI: Credentials can now be requested using `sl vs credentials `, `sl hardware credentials ` and `sl nas credentials ` for virtual servers, hardware servers and NAS accounts respectively. + - CLI: Adds virtual server rescue command, `sl vs rescue ` + +### Changed + - CLI: The command is renamed from `sl` to `slcli` to avoid package conflicts. + - CLI: Global options now need to be specified right after the `slcli` command. For example, you would now use `slcli --format=raw list` over `slcli vs list --format=raw`. This is a change for the following options: + - --format + - -c or --config + - --debug + - --proxy + - -y or --really + - --version + - API: The hardware manager has a significant update to how place_order() works. It will now only support the fast server provisioning package which has presets for options like CPU, Memory and disk. + - API: Removed deprecated SoftLayer.CCIManager. + - API: Adds virtual server rescue command to SoftLayer.VSManager + - CLI: Significant changes were done to the CLI argument parsing. Docopt was dropped in favor of click. Therefore, some subtle differences which aren't documented here may exist. + +## [3.3.0] - 2014-10-23 + - Changes: https://github.com/softlayer/softlayer-python/compare/v3.2.0...v3.3.0 + +### Added + - CLI+API: Load balancer support + - CLI: More detail added to the `sl image detail` and `sl image list` commands + - CLI: Adds command to import DNS entries from BIND zone files + - CLI+API: Adds support for booting into rescue images for virtual servers and hardware + - API: Adds ability to order virtual and hardare servers from a quote to the ordering manager + +### Changed + - CLI: Fixes bug with `sl server list-chassis` and `sl server list-chassis` + - API: Restructure of the way custom authentication can be plugged in the API client + - Several other bug fixes + +## [3.2.0] - 2014-07-09 + - Changes: https://github.com/softlayer/softlayer-python/compare/v3.1.0...v3.2.0 + +### Added + - CLI+API: Added firewall manager and CLI module + - CLI+API: Added iscsi manager and CLI module + - API: Added ability to create multiple virtual servers at once to VSManager + - API: Added OrderingManager. Remove hard-coded price IDs + +### Changed + - Fixed several small bugs + +## [3.1.0] - 2014-04-24 + - Changes: https://github.com/softlayer/softlayer-python/compare/v3.0.2...v3.1.0 + +### Added + - CLI+API: Added CDN manager and CLI module + - CLI+API: Added ticket manager and CLI module + - CLI+API: Added image manager and improves image CLI module + - CLI+API: Added the ability to specify a proxy URL for API bindings and the CLI + - CLI+API: Added ability to resize a virtual machine + - CLI+API: Added firewall manager and CLI module + - CLI+API: Added load balancer manager and CLI module + +### Changed + - API: six is now used to provide support for Python 2 and Python 3 with the same source + - CLI+API: Implemented product name changes in accordance with SoftLayer's new product names. Existing managers should continue to work as before. Minor CLI changes were necessary. + - Many bug fixes and minor suggested improvements + +## [3.0.2] - 2013-12-9 + - Changes: https://github.com/softlayer/softlayer-python/compare/v3.0.1...v3.0.2 + +### Added + - CLI+API: Simplified object mask reformatting and added support for more complex masks. + - CLI+API: Added IPMI IP address to hardware details. + - CLI: Added support for ordering multiple disks when creating a CCI. + - API: Added flag to disable compression on HTTP requests. + - CLI: Added CIDR information to subnet displays. + +### Changed + - CLI: Fixed the sl bmc create --network argument. + - CLI+API: Improved output of the message queue feature and fixed some minor bugs. + - CLI: Fixed an error when using --test and ordering a non-private subnet. + - API: Fix to prevent double counting results in summary_by_datacenter(). + +### [3.0.1] - 2013-10-11 + - Changes: https://github.com/softlayer/softlayer-python/compare/v3.0.0...v3.0.1 + +### Added + - CLI+API: Added ability to specify SSH keys when reloading CCIs and servers. + +### Changed + - CLI: Fixed an error message about pricing information that appeared when ordering a new private subnet. + +## [3.0.0] - 2013-09-19 + - Changes: https://github.com/softlayer/softlayer-python/compare/v2.3.0...v3.0.0 + +### Added + - CLI+API: Adds SoftLayer Message Queue Service bindings (as a manager) and a CLI counterpart. With this you can interact with existing message queue accounts + - CLI+API: Adds the ability to create CCIs with the following options: metadata, post-install script, SSH key + - CLI+API: Improved dedicated server ordering. Adds power management for hardware servers: power-on, power-off, power-cycle, reboot + - CLI+API: Adds a networking manager and adds several network-related CLI modules. This includes the ability to: + - list, create, cancel and assign global IPs + - list, create, cancel and detail subnets. Also has the ability to lookup details about an IP address with 'sl subnet lookup' + - list, detail VLANs + - show and edit RWhois data + - CLI+API: Ability to manage SSH Keys with a manager and a CLI module + - CLI: Adds a --debug option to print out debugging information. --debug=3 is the highest log level which prints full HTTP request/responses including the body + - CLI+API: Adds the ability to create hardware servers with a default SSH key + - CLI: Adds templating for creating CCIs and hardware nodes which can be used to create more CCIs and hardware with the same settings + +### Changed + - Many bug fixes and consistency improvements + - API: Removes old API client interfaces which have been deprecated in the v2. See link for more details: https://softlayer-api-python-client.readthedocs.org/en/latest/api/client/#backwards-compatibility + - CLI: The commands in the main help are now organized into categories + +## [2.3.0] - 2013-07-19 + - Changes: https://github.com/softlayer/softlayer-python/compare/v2.2.0...v2.3.0 + +### Added + - CLI+API: Added much more hardware support: Filters for hardware listing, dedicated server/bare metal cloud ordering, hardware cancellation + - CLI+API: Added DNS Zone filtering (server side) + - CLI+API: Added Post Install script support for CCIs and hardware + - CLI: Added Message queue functionality + - CLI: Added --debug option to CLI commands + - API: Added more logging + - API: Added token-based auth so you can use the API bindings with your username/password if you want. (It's still highly recommended to use your API key instead of your password) + +### Changed + - Several bug fixes and improvements + - Removed Python 2.5 support. Some stuff MIGHT work with 2.5 but it is no longer tested + - API: Refactored managers into their own module to not clutter the top level + +## [2.2.0] - 2013-04-11 +### Added + - Added sphinx documentation. See it here: https://softlayer-api-python-client.readthedocs.org + - CCI: Adds Support for Additional Disks + - CCI: Adds a way to block until transactions are done on a CCI + - CLI: For most CCI commands, you can specify id, hostname, private ip or public ip as + - CLI: Adds the ability to filter list results for CCIs + - API: for large result sets, requests can now be chunked into smaller batches on the server side. Using service.iter_call('getObjects', ...) or service.getObjects(..., iter=True) will return a generator regardless of the results returned. offset and limit can be passed in like normal. An additional named parameter of 'chunk' is used to limit the number of items coming back in a single request, defaults to 100 + +### Changed + - Consistency changes/bug fixes diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 6197a85fb..c0971279a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -21,6 +21,7 @@ Ryan Hanson Scott Thompson Sergio Carlos Shane Poage +Shravan Kumar Raghu simplydave SoftLayer suppandi diff --git a/ISSUE_TEMPLATE b/ISSUE_TEMPLATE index 97df85338..bad173543 100644 --- a/ISSUE_TEMPLATE +++ b/ISSUE_TEMPLATE @@ -1,3 +1,5 @@ +**Please triple-check to make sure that you have properly masked out user credentials like usernames, passwords and API keys before submitting your issue** + ### Expected Behavior ### Actual Behavior diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 322309f2b..2a79264c2 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -46,7 +46,8 @@ def create_client_from_env(username=None, config_file=None, proxy=None, user_agent=None, - transport=None): + transport=None, + verify=True): """Creates a SoftLayer API client using your environment. Settings are loaded via keyword arguments, environemtal variables and @@ -68,6 +69,8 @@ def create_client_from_env(username=None, calls if you wish to bypass the packages built in User Agent string :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) + :param bool verify: decide to verify the server's SSL/TLS cert. DO NOT SET + TO FALSE WITHOUT UNDERSTANDING THE IMPLICATIONS. Usage: @@ -83,6 +86,7 @@ def create_client_from_env(username=None, endpoint_url=endpoint_url, timeout=timeout, proxy=proxy, + verify=verify, config_file=config_file) if transport is None: @@ -94,6 +98,7 @@ def create_client_from_env(username=None, proxy=settings.get('proxy'), timeout=settings.get('timeout'), user_agent=user_agent, + verify=verify, ) else: # Default the transport to use XMLRPC @@ -102,6 +107,7 @@ def create_client_from_env(username=None, proxy=settings.get('proxy'), timeout=settings.get('timeout'), user_agent=user_agent, + verify=verify, ) # If we have enough information to make an auth driver, let's do it @@ -240,7 +246,8 @@ def call(self, service, method, *args, **kwargs): request.filter = kwargs.get('filter') request.limit = kwargs.get('limit') request.offset = kwargs.get('offset') - request.verify = kwargs.get('verify') + if kwargs.get('verify') is not None: + request.verify = kwargs.get('verify') if self.auth: extra_headers = self.auth.get_headers() diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index aa384309d..02ef1e0f4 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -31,7 +31,7 @@ 3: logging.DEBUG } -VALID_FORMATS = ['table', 'raw', 'json'] +VALID_FORMATS = ['table', 'raw', 'json', 'jsonraw'] DEFAULT_FORMAT = 'raw' if sys.stdout.isatty(): DEFAULT_FORMAT = 'table' diff --git a/SoftLayer/CLI/dns/zone_import.py b/SoftLayer/CLI/dns/zone_import.py index 6e1259de0..7b47cdb12 100644 --- a/SoftLayer/CLI/dns/zone_import.py +++ b/SoftLayer/CLI/dns/zone_import.py @@ -9,7 +9,7 @@ from SoftLayer.CLI import exceptions from SoftLayer.CLI import helpers -RECORD_REGEX = re.compile(r"""^((?P([\w-]+(\.)?)*|\@)?\s+ +RECORD_REGEX = re.compile(r"""^((?P(([\w-]+|\*)(\.)?)*|\@)?\s+ (?P\d+)?\s+ (?P\w+)?)?\s+ (?P\w+)\s+ diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index b73ecc0de..25cdb956a 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -55,6 +55,12 @@ def cli(env, volume_id): file_volume['serviceResourceBackendIpAddress'], ]) + if file_volume['fileNetworkMountAddress']: + table.add_row([ + 'Mount Address', + file_volume['fileNetworkMountAddress'], + ]) + if file_volume['snapshotCapacityGb']: table.add_row([ 'Snapshot Capacity (GB)', diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index a3266362e..40399538c 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -27,6 +27,8 @@ mask="serviceResourceBackendIpAddress"), column_helper.Column('active_transactions', ('activeTransactionCount',), mask="activeTransactionCount"), + column_helper.Column('mount_addr', ('fileNetworkMountAddress',), + mask="fileNetworkMountAddress",), column_helper.Column( 'created_by', ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), @@ -40,7 +42,8 @@ 'capacity_gb', 'bytes_used', 'ip_addr', - 'active_transactions' + 'active_transactions', + 'mount_addr' ] diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 15cb8df81..59917b007 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -28,7 +28,7 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 :param string fmt (optional): One of: table, raw, json, python """ if isinstance(data, utils.string_types): - if fmt == 'json': + if fmt in ('json', 'jsonraw'): return json.dumps(data) return data @@ -46,6 +46,9 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 format_output(data, fmt='python'), indent=4, cls=CLIJSONEncoder) + elif fmt == 'jsonraw': + return json.dumps(format_output(data, fmt='python'), + CLIJSONEncoder) elif fmt == 'python': return data.to_python() diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 2b072a0a0..b71c20874 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.2.0' +VERSION = 'v5.2.1' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 88f0eb605..561ff3926 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -479,6 +479,7 @@ 'password': 'pass', 'serviceResourceBackendIpAddress': '127.0.0.1', 'storageType': {'keyName': 'ENDURANCE_STORAGE'}, + 'fileNetworkMountAddress': '127.0.0.1:/TEST', }] getActiveQuotes = [{ diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 52a178e3a..43012eb7a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -31,6 +31,7 @@ }], 'serviceResource': {'datacenter': {'id': 449500, 'name': 'dal05'}}, 'serviceResourceBackendIpAddress': '10.1.2.3', + 'fileNetworkMountAddress': '127.0.0.1:/TEST', 'serviceResourceName': 'Storage Type 01 Aggregate staaspar0101_pc01', 'username': 'username', 'storageType': {'keyName': 'ENDURANCE_STORAGE'}, diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 104406681..966e8a405 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -33,10 +33,13 @@ "primaryNetworkComponent": {"speed": 10, "maxSpeed": 100}, 'hourlyBillingFlag': False, 'createDate': '2013-08-01 15:23:45', - 'blockDevices': [{"device": 0, 'mountType': 'Disk', "uuid": 1}, - {"device": 1, 'mountType': 'Disk'}, - {"device": 2, 'mountType': 'CD'}, - {"device": 3, 'mountType': 'Disk', "uuid": 3}], + 'blockDevices': [{'device': 0, 'mountType': 'Disk', "uuid": 1}, + {'device': 1, 'mountType': 'Disk', + 'diskImage': {'type': {'keyName': 'SWAP'}}}, + {'device': 2, 'mountType': 'CD'}, + {'device': 3, 'mountType': 'Disk', 'uuid': 3}, + {'device': 4, 'mountType': 'Disk', 'uuid': 4, + 'diskImage': {'metadataFlag': True}}], 'notes': 'notes', 'networkVlans': [{'networkSpace': 'PUBLIC', 'vlanNumber': 23, diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index ae1792064..717d4edd5 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -35,7 +35,8 @@ def list_file_volumes(self, datacenter=None, username=None, 'bytesUsed', 'serviceResource.datacenter[name]', 'serviceResourceBackendIpAddress', - 'activeTransactionCount' + 'activeTransactionCount', + 'fileNetworkMountAddress' ] kwargs['mask'] = ','.join(items) @@ -81,6 +82,7 @@ def get_file_volume_details(self, volume_id, **kwargs): 'storageType.keyName', 'serviceResource.datacenter[name]', 'serviceResourceBackendIpAddress', + 'fileNetworkMountAddress', 'storageTierLevel', 'iops', 'lunId', diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 3a89eb8c0..9d0741c05 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -721,19 +721,42 @@ def capture(self, instance_id, name, additional_disks=False, notes=None): notes = "Some notes about this image" result = mgr.capture(instance_id=12345, name=name, notes=notes) """ - vsi = self.get_instance(instance_id) - disk_filter = lambda x: x['device'] == '0' - # Skip disk 1 (swap partition) and CD mounts - if additional_disks: - disk_filter = lambda x: (str(x['device']) != '1' and - x['mountType'] != 'CD') + vsi = self.client.call( + 'Virtual_Guest', + 'getObject', + id=instance_id, + mask="""id, + blockDevices[id,device,mountType, + diskImage[id,metadataFlag,type[keyName]]]""") - disks = [block_device for block_device in vsi['blockDevices'] - if disk_filter(block_device)] + disks_to_capture = [] + for block_device in vsi['blockDevices']: + + # We never want metadata disks + if utils.lookup(block_device, 'diskImage', 'metadataFlag'): + continue + + # We never want swap devices + type_name = utils.lookup(block_device, + 'diskImage', + 'type', + 'keyName') + if type_name == 'SWAP': + continue + + # We never want CD images + if block_device['mountType'] == 'CD': + continue + + # Only use the first block device if we don't want additional disks + if not additional_disks and str(block_device['device']) != '0': + continue + + disks_to_capture.append(block_device) return self.guest.createArchiveTransaction( - name, disks, notes, id=instance_id) + name, disks_to_capture, notes, id=instance_id) def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True): diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 8e143818d..b3239fc14 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -64,7 +64,7 @@ def __init__(self): self.transport_headers = {} #: Boolean specifying if the server certificate should be verified. - self.verify = True + self.verify = None #: Client certificate file path. self.cert = None @@ -103,13 +103,15 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, - user_agent=None): + user_agent=None, + verify=True): self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') self.timeout = timeout or None self.proxy = proxy self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify def __call__(self, request): """Makes a SoftLayer API call against the XML-RPC endpoint. @@ -145,6 +147,12 @@ def __call__(self, request): payload = utils.xmlrpc_client.dumps(tuple(largs), methodname=request.method, allow_none=True) + + # Prefer the request setting, if it's not None + verify = request.verify + if verify is None: + verify = self.verify + LOGGER.debug("=== REQUEST ===") LOGGER.info('POST %s', url) LOGGER.debug(request.transport_headers) @@ -155,7 +163,7 @@ def __call__(self, request): data=payload, headers=request.transport_headers, timeout=self.timeout, - verify=request.verify, + verify=verify, cert=request.cert, proxies=_proxies_dict(self.proxy)) LOGGER.debug("=== RESPONSE ===") @@ -202,13 +210,15 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, - user_agent=None): + user_agent=None, + verify=True): self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT_REST).rstrip('/') self.timeout = timeout or None self.proxy = proxy self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify def __call__(self, request): """Makes a SoftLayer API call against the REST endpoint. @@ -269,6 +279,11 @@ def __call__(self, request): url = '%s.%s' % ('/'.join(url_parts), 'json') + # Prefer the request setting, if it's not None + verify = request.verify + if verify is None: + verify = self.verify + LOGGER.debug("=== REQUEST ===") LOGGER.info(url) LOGGER.debug(request.transport_headers) @@ -280,14 +295,14 @@ def __call__(self, request): params=params, data=raw_body, timeout=self.timeout, - verify=request.verify, + verify=verify, cert=request.cert, proxies=_proxies_dict(self.proxy)) LOGGER.debug("=== RESPONSE ===") LOGGER.debug(resp.headers) - LOGGER.debug(resp.content) + LOGGER.debug(resp.text) resp.raise_for_status() - result = json.loads(resp.content) + result = json.loads(resp.text) if isinstance(result, list): return SoftLayerListResult( @@ -295,9 +310,9 @@ def __call__(self, request): else: return result except requests.HTTPError as ex: - content = json.loads(ex.response.content) + message = json.loads(ex.response.text)['error'] raise exceptions.SoftLayerAPIError(ex.response.status_code, - content['error']) + message) except requests.RequestException as ex: raise exceptions.TransportError(0, str(ex)) diff --git a/docs/conf.py b/docs/conf.py index bc9b52bdb..cfd15e4c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,9 +55,9 @@ # built documents. # # The short X.Y version. -version = '5.2.0' +version = '5.2.1' # The full version, including alpha/beta/rc tags. -release = '5.2.0' +release = '5.2.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.cfg b/setup.cfg index 20520b557..ba4e6f120 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -[pytest] +[tool:pytest] python_files = *_tests.py [wheel] diff --git a/setup.py b/setup.py index c6b246a32..554069b4c 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ from __future__ import print_function -import sys -from codecs import open +import codecs import os from setuptools import setup, find_packages @@ -8,14 +7,14 @@ DESCRIPTION = "A library for SoftLayer's API" if os.path.exists('README.rst'): - with open('README.rst', 'r', 'utf-8') as readme_file: + with codecs.open('README.rst', 'r', 'utf-8') as readme_file: LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION setup( name='SoftLayer', - version='5.2.0', + version='5.2.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index fd0d31823..d5aa291d4 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -251,8 +251,7 @@ class ResolveIdTests(testing.TestCase): def test_resolve_id_one(self): resolver = lambda r: [12345] - id = helpers.resolve_id(resolver, 'test') - self.assertEqual(id, 12345) + self.assertEqual(helpers.resolve_id(resolver, 'test'), 12345) def test_resolve_id_none(self): resolver = lambda r: [] @@ -306,6 +305,21 @@ def test_format_output_json(self): ret = formatting.format_output('test', 'json') self.assertEqual('"test"', ret) + def test_format_output_jsonraw(self): + t = formatting.Table(['nothing']) + t.align['nothing'] = 'c' + t.add_row(['testdata']) + t.add_row([formatting.blank()]) + t.sortby = 'nothing' + ret = formatting.format_output(t, 'jsonraw') + # This uses json.dumps due to slight changes in the output between + # py3.3 and py3.4 + expected = json.dumps([{'nothing': 'testdata'}, {'nothing': None}]) + self.assertEqual(expected, ret) + + ret = formatting.format_output('test', 'json') + self.assertEqual('"test"', ret) + def test_format_output_json_keyvaluetable(self): t = formatting.KeyValueTable(['key', 'value']) t.add_row(['nothing', formatting.blank()]) @@ -315,6 +329,21 @@ def test_format_output_json_keyvaluetable(self): "nothing": null }''', ret) + def test_format_output_jsonraw_keyvaluetable(self): + t = formatting.KeyValueTable(['key', 'value']) + t.add_row(['nothing', formatting.blank()]) + t.sortby = 'nothing' + ret = formatting.format_output(t, 'jsonraw') + self.assertEqual('''{"nothing": null}''', ret) + + def test_format_output_json_string(self): + ret = formatting.format_output("test", 'json') + self.assertEqual('"test"', ret) + + def test_format_output_jsonraw_string(self): + ret = formatting.format_output("test", 'jsonraw') + self.assertEqual('"test"', ret) + def test_format_output_formatted_item(self): item = formatting.FormattedItem('test', 'test_formatted') ret = formatting.format_output(item, 'table') @@ -380,6 +409,16 @@ def test_format_output_unicode(self): t = formatting.format_output(item, 'raw') self.assertEqual('raw ☃', t) + def test_format_output_table_invalid_sort(self): + t = formatting.Table(['nothing']) + t.align['nothing'] = 'c' + t.add_row(['testdata']) + t.sortby = 'DOES NOT EXIST' + self.assertRaises( + exceptions.CLIHalt, + formatting.format_output, t, 'table', + ) + class TestTemplateArgs(testing.TestCase): diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index a6ce0d767..b907d200c 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -13,67 +13,73 @@ import pytest -class BuildFilterTests(testing.TestCase): +def test_filter_empty(): + assert call_api._build_filters([]) == {} - def test_empty(self): - assert call_api._build_filters([]) == {} - def test_basic(self): - result = call_api._build_filters(['property=value']) - assert result == {'property': {'operation': '_= value'}} +def test_filter_basic(): + result = call_api._build_filters(['property=value']) + assert result == {'property': {'operation': '_= value'}} - def test_nested(self): - result = call_api._build_filters(['nested.property=value']) - assert result == {'nested': {'property': {'operation': '_= value'}}} - def test_multi(self): - result = call_api._build_filters(['prop1=value1', 'prop2=prop2']) - assert result == { - 'prop1': {'operation': '_= value1'}, - 'prop2': {'operation': '_= prop2'}, - } +def test_filter_nested(): + result = call_api._build_filters(['nested.property=value']) + assert result == {'nested': {'property': {'operation': '_= value'}}} - def test_in(self): - result = call_api._build_filters(['prop IN value1,value2']) - assert result == { - 'prop': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['value1', 'value2']}], - } - } - def test_in_multi(self): - result = call_api._build_filters([ - 'prop_a IN a_val1,a_val2', - 'prop_b IN b_val1,b_val2', - ]) - assert result == { - 'prop_a': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['a_val1', 'a_val2']}], - }, - 'prop_b': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['b_val1', 'b_val2']}], - }, - } +def test_filter_multi(): + result = call_api._build_filters(['prop1=value1', 'prop2=prop2']) + assert result == { + 'prop1': {'operation': '_= value1'}, + 'prop2': {'operation': '_= prop2'}, + } + - def test_in_with_whitespace(self): - result = call_api._build_filters(['prop IN value1 , value2 ']) - assert result == { - 'prop': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['value1', 'value2']}], - } +def test_filter_in(): + result = call_api._build_filters(['prop IN value1,value2']) + assert result == { + 'prop': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['value1', 'value2']}], } + } + + +def test_filter_in_multi(): + result = call_api._build_filters([ + 'prop_a IN a_val1,a_val2', + 'prop_b IN b_val1,b_val2', + ]) + assert result == { + 'prop_a': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['a_val1', 'a_val2']}], + }, + 'prop_b': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['b_val1', 'b_val2']}], + }, + } + + +def test_filter_in_with_whitespace(): + result = call_api._build_filters(['prop IN value1 , value2 ']) + assert result == { + 'prop': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['value1', 'value2']}], + } + } + + +def test_filter_invalid_operation(): + with pytest.raises(exceptions.CLIAbort): + call_api._build_filters(['prop N/A value1']) - def test_invalid_operation(self): - with pytest.raises(exceptions.CLIAbort): - call_api._build_filters(['prop N/A value1']) - def test_only_whitespace(self): - with pytest.raises(exceptions.CLIAbort): - call_api._build_filters([' ']) +def test_filter_only_whitespace(): + with pytest.raises(exceptions.CLIAbort): + call_api._build_filters([' ']) class CallCliTests(testing.TestCase): diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index a6f0feafd..eebf4ea2b 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -53,11 +53,11 @@ def set_up(self): @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_setup(self, input, getpass, confirm_mock): + def test_setup(self, mocked_input, getpass, confirm_mock): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = True getpass.return_value = 'A' * 64 - input.side_effect = ['user', 'public', 0] + mocked_input.side_effect = ['user', 'public', 0] result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) @@ -76,11 +76,11 @@ def test_setup(self, input, getpass, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_setup_cancel(self, input, getpass, confirm_mock): + def test_setup_cancel(self, mocked_input, getpass, confirm_mock): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = False getpass.return_value = 'A' * 64 - input.side_effect = ['user', 'public', 0] + mocked_input.side_effect = ['user', 'public', 0] result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) @@ -90,9 +90,9 @@ def test_setup_cancel(self, input, getpass, confirm_mock): @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_private(self, input, getpass): + def test_get_user_input_private(self, mocked_input, getpass): getpass.return_value = 'A' * 64 - input.side_effect = ['user', 'private', 0] + mocked_input.side_effect = ['user', 'private', 0] username, secret, endpoint_url, timeout = ( config.get_user_input(self.env)) @@ -100,12 +100,13 @@ def test_get_user_input_private(self, input, getpass): self.assertEqual(username, 'user') self.assertEqual(secret, 'A' * 64) self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT) + self.assertEqual(timeout, 0) @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_custom(self, input, getpass): + def test_get_user_input_custom(self, mocked_input, getpass): getpass.return_value = 'A' * 64 - input.side_effect = ['user', 'custom', 'custom-endpoint', 0] + mocked_input.side_effect = ['user', 'custom', 'custom-endpoint', 0] _, _, endpoint_url, _ = config.get_user_input(self.env) @@ -113,9 +114,9 @@ def test_get_user_input_custom(self, input, getpass): @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_default(self, input, getpass): + def test_get_user_input_default(self, mocked_input, getpass): self.env.getpass.return_value = 'A' * 64 - self.env.input.side_effect = ['user', 'public', 0] + mocked_input.side_effect = ['user', 'public', 0] _, _, endpoint_url, _ = config.get_user_input(self.env) diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index d537872d7..836da74a9 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -115,6 +115,8 @@ def test_parse_zone_file(self): dev.realtest.com IN TXT "This is just a test of the txt record" IN AAAA 2001:db8:10::1 spf IN TXT "v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a" +*.testing 86400 IN A 127.0.0.2 +* 86400 IN A 127.0.0.3 """ expected = [{'data': 'ns1.softlayer.com.', @@ -148,7 +150,15 @@ def test_parse_zone_file(self): {'data': '"v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a"', 'record': 'spf', 'type': 'TXT', - 'ttl': None}] + 'ttl': None}, + {'data': '127.0.0.2', + 'record': '*.testing', + 'type': 'A', + 'ttl': '86400'}, + {'data': '127.0.0.3', + 'record': '*', + 'type': 'A', + 'ttl': '86400'}] zone, records, bad_lines = zone_import.parse_zone_details(zone_file) self.assertEqual(zone, 'realtest.com') self.assertEqual(records, expected) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 3cbce5266..3bd43015e 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -86,7 +86,8 @@ def test_volume_list(self): 'ip_addr': '127.0.0.1', 'storage_type': 'ENDURANCE', 'username': 'user', - 'active_transactions': None + 'active_transactions': None, + 'mount_addr': '127.0.0.1:/TEST' }], json.loads(result.output)) @@ -122,6 +123,7 @@ def test_volume_detail(self): 'Used Space': '0B', 'Endurance Tier': '2 IOPS per GB', 'IOPs': 1000, + 'Mount Address': '127.0.0.1:/TEST', 'Snapshot Capacity (GB)': '10', 'Snapshot Used (Bytes)': 1024, 'Capacity (GB)': '20GB', diff --git a/tests/api_tests.py b/tests/api_tests.py index ff5fce608..db42ee350 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -16,7 +16,8 @@ class Inititialization(testing.TestCase): def test_init(self): client = SoftLayer.Client(username='doesnotexist', api_key='issurelywrong', - timeout=10) + timeout=10, + endpoint_url='http://example.com/v3/xmlrpc/') self.assertIsInstance(client.auth, SoftLayer.BasicAuthentication) self.assertEqual(client.auth.username, 'doesnotexist') @@ -95,7 +96,7 @@ def test_simple_call(self): offset=None, ) - def test_verify(self): + def test_verify_request_false(self): client = SoftLayer.BaseClient(transport=self.mocks) mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') mock.return_value = {"test": "result"} @@ -105,6 +106,26 @@ def test_verify(self): self.assertEqual(resp, {"test": "result"}) self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=False) + def test_verify_request_true(self): + client = SoftLayer.BaseClient(transport=self.mocks) + mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') + mock.return_value = {"test": "result"} + + resp = client.call('SERVICE', 'METHOD', verify=True) + + self.assertEqual(resp, {"test": "result"}) + self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=True) + + def test_verify_request_not_specified(self): + client = SoftLayer.BaseClient(transport=self.mocks) + mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') + mock.return_value = {"test": "result"} + + resp = client.call('SERVICE', 'METHOD') + + self.assertEqual(resp, {"test": "result"}) + self.assert_called_with('SoftLayer_SERVICE', 'METHOD', verify=None) + @mock.patch('SoftLayer.API.BaseClient.iter_call') def test_iterate(self, _iter_call): self.client['SERVICE'].METHOD(iter=True) diff --git a/tests/config_tests.py b/tests/config_tests.py index 3ef805e9f..ad913be57 100644 --- a/tests/config_tests.py +++ b/tests/config_tests.py @@ -86,9 +86,10 @@ def test_no_section(self, config_parser): self.assertIsNone(result) - @mock.patch('six.moves.configparser.RawConfigParser') - def test_config_file(self, config_parser): - config.get_client_settings_config_file(config_file='path/to/config') - config_parser().read.assert_called_with([mock.ANY, - mock.ANY, - 'path/to/config']) + +@mock.patch('six.moves.configparser.RawConfigParser') +def test_config_file(config_parser): + config.get_client_settings_config_file(config_file='path/to/config') + config_parser().read.assert_called_with([mock.ANY, + mock.ANY, + 'path/to/config']) diff --git a/tests/managers/vs_tests.py b/tests/managers/vs_tests.py index a0cecb205..fb1a2c2aa 100644 --- a/tests/managers/vs_tests.py +++ b/tests/managers/vs_tests.py @@ -615,7 +615,7 @@ def test_captures(self): expected = fixtures.SoftLayer_Virtual_Guest.createArchiveTransaction self.assertEqual(result, expected) - args = ('a', [], None) + args = ('a', [{'device': 0, 'uuid': 1, 'mountType': 'Disk'}], None) self.assert_called_with('SoftLayer_Virtual_Guest', 'createArchiveTransaction', args=args, diff --git a/tests/transport_tests.py b/tests/transport_tests.py index e3d827e1f..690882f69 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -8,6 +8,7 @@ import warnings import mock +import pytest import requests import six @@ -17,26 +18,31 @@ from SoftLayer import transports +def get_xmlrpc_response(): + response = requests.Response() + list_body = six.b(''' + + + + + + + + +''') + response.raw = io.BytesIO(list_body) + response.headers['SoftLayer-Total-Items'] = 10 + response.status_code = 200 + return response + + class TestXmlRpcAPICall(testing.TestCase): def set_up(self): self.transport = transports.XmlRpcTransport( endpoint_url='http://something.com', ) - self.response = requests.Response() - list_body = six.b(''' - - - - - - - - -''') - self.response.raw = io.BytesIO(list_body) - self.response.headers['SoftLayer-Total-Items'] = 10 - self.response.status_code = 200 + self.response = get_xmlrpc_response() @mock.patch('requests.request') def test_call(self, request): @@ -121,7 +127,7 @@ def test_identifier(self, request): req.identifier = 1234 self.transport(req) - args, kwargs = request.call_args + _, kwargs = request.call_args self.assertIn( """ id @@ -251,6 +257,55 @@ def test_request_exception(self, request): self.assertRaises(SoftLayer.TransportError, self.transport, req) +@mock.patch('requests.request') +@pytest.mark.parametrize( + "transport_verify,request_verify,expected", + [ + (True, True, True), + (True, False, False), + (True, None, True), + + (False, True, True), + (False, False, False), + (False, None, False), + + (None, True, True), + (None, False, False), + (None, None, True), + ] +) +def test_verify(request, + transport_verify, + request_verify, + expected): + request.return_value = get_xmlrpc_response() + + transport = transports.XmlRpcTransport( + endpoint_url='http://something.com', + ) + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + if request_verify is not None: + req.verify = request_verify + + if transport_verify is not None: + transport.verify = transport_verify + + transport(req) + + request.assert_called_with('POST', + 'http://something.com/SoftLayer_Service', + data=mock.ANY, + headers=mock.ANY, + cert=mock.ANY, + proxies=mock.ANY, + timeout=mock.ANY, + verify=expected) + + class TestRestAPICall(testing.TestCase): def set_up(self): @@ -261,6 +316,7 @@ def set_up(self): @mock.patch('requests.request') def test_basic(self, request): request().content = '[]' + request().text = '[]' request().headers = requests.structures.CaseInsensitiveDict({ 'SoftLayer-Total-Items': '10', }) @@ -290,7 +346,7 @@ def test_error(self, request): e = requests.HTTPError('error') e.response = mock.MagicMock() e.response.status_code = 404 - e.response.content = '''{ + e.response.text = '''{ "error": "description", "code": "Error Code" }''' @@ -315,7 +371,7 @@ def test_proxy_without_protocol(self): @mock.patch('requests.request') def test_valid_proxy(self, request): - request().content = '{}' + request().text = '{}' self.transport.proxy = 'http://localhost:3128' req = transports.Request() @@ -323,6 +379,7 @@ def test_valid_proxy(self, request): req.method = 'Resource' self.transport(req) + request.assert_called_with( 'GET', 'http://something.com/SoftLayer_Service/Resource.json', proxies={'https': 'http://localhost:3128', @@ -337,7 +394,7 @@ def test_valid_proxy(self, request): @mock.patch('requests.request') def test_with_id(self, request): - request().content = '{}' + request().text = '{}' req = transports.Request() req.service = 'SoftLayer_Service' @@ -361,7 +418,7 @@ def test_with_id(self, request): @mock.patch('requests.request') def test_with_args(self, request): - request().content = '{}' + request().text = '{}' req = transports.Request() req.service = 'SoftLayer_Service' @@ -385,7 +442,7 @@ def test_with_args(self, request): @mock.patch('requests.request') def test_with_filter(self, request): - request().content = '{}' + request().text = '{}' req = transports.Request() req.service = 'SoftLayer_Service' @@ -410,7 +467,7 @@ def test_with_filter(self, request): @mock.patch('requests.request') def test_with_mask(self, request): - request().content = '{}' + request().text = '{}' req = transports.Request() req.service = 'SoftLayer_Service'