From 8a78a1696891731097d55f219d53bb3d9cbf003c Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 23 Oct 2014 10:17:23 -0500 Subject: [PATCH 001/151] Adding .spec file for python-softlayer --- python-softlayer.spec | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 python-softlayer.spec diff --git a/python-softlayer.spec b/python-softlayer.spec new file mode 100644 index 000000000..b93faf289 --- /dev/null +++ b/python-softlayer.spec @@ -0,0 +1,47 @@ +# sitelib for noarch packages, sitearch for others (remove the unneeded one) +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} +%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} + +Name: python-softlayer +Version: 773ab17 +Release: 1%{?dist} +Summary: softlayer python interface + +License: Softlayer +URL: https://github.com/softlayer/softlayer-python +Source0: python-softlayer-773ab17.tar.gz + +#BuildArch: +BuildRequires: python-devel +Requires: python-requests, python-docopt = 0.6.1, python-prettytable >= 0.7.0 +Requires: python-importlib, python-six >= 1.6.1 + +%description + + +%prep +%setup -q + + +%build +# Remove CFLAGS=... for noarch packages (unneeded) +CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build + + +%install +rm -rf $RPM_BUILD_ROOT +%{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT + + +%files +%doc +/usr/bin/sl +# For noarch packages: sitelib +%{python_sitelib}/* +# For arch-specific packages: sitearch +#%{python_sitearch}/* + + +%changelog +* Wed Mar 19 2014 Andy Bakun 773ab17-1 +- initial packaging From bff00651c28344d034fcec92e1f9fcad3a8098b2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 23 Oct 2014 12:00:04 -0500 Subject: [PATCH 002/151] added github url to the spec file --- python-softlayer.spec | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/python-softlayer.spec b/python-softlayer.spec index b93faf289..e3f78b802 100644 --- a/python-softlayer.spec +++ b/python-softlayer.spec @@ -1,18 +1,20 @@ # sitelib for noarch packages, sitearch for others (remove the unneeded one) %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} %{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} +%global commit master +%global shortcommit %(c=%{commit}; echo ${c:0:7}) -Name: python-softlayer -Version: 773ab17 +Name: softlayer-python +Version: %{commit} Release: 1%{?dist} Summary: softlayer python interface License: Softlayer URL: https://github.com/softlayer/softlayer-python -Source0: python-softlayer-773ab17.tar.gz +Source: https://github.com/softlayer/softlayer-python/archive/%{commit}/softlayer-python-%{commit}.tar.gz #BuildArch: -BuildRequires: python-devel +BuildRequires: python-devel, python-setuptools Requires: python-requests, python-docopt = 0.6.1, python-prettytable >= 0.7.0 Requires: python-importlib, python-six >= 1.6.1 @@ -43,5 +45,9 @@ rm -rf $RPM_BUILD_ROOT %changelog +* Thu Oct 23 2014 Christopher Gallo - master-2 +- Changed Source to a proper github url, added python-setuptool build + requirement + * Wed Mar 19 2014 Andy Bakun 773ab17-1 - initial packaging From 585c8ca9b8ec20918ded43380db4b69ab4034cb6 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 23 Oct 2014 12:59:32 -0500 Subject: [PATCH 003/151] renamed the spec file --- python-softlayer.spec => softlayer-python.spec | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python-softlayer.spec => softlayer-python.spec (100%) diff --git a/python-softlayer.spec b/softlayer-python.spec similarity index 100% rename from python-softlayer.spec rename to softlayer-python.spec From 3eafa595e7615200a2e5a3b0dd0bdfebcbe7f499 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 7 Oct 2014 08:46:19 -0500 Subject: [PATCH 004/151] Conversion to click This includes a mess of changes: * stricter option parsing (ex: global options are specified directly after sl) * no prefix-based option matching that doc-opt did * entrypoint-based loading * Several CLI cleanups: ** Remove help command ** Renames dns subcommands to include zone- and record- ** Renames --hourly and --billing for `sl vs create` and `sl hardware create` to --billing=hourly or --billing=monthly. ** Many other subtle changes --- SoftLayer/CLI/cdn/__init__.py | 2 + SoftLayer/CLI/cdn/detail.py | 32 + SoftLayer/CLI/cdn/list.py | 43 + SoftLayer/CLI/cdn/load.py | 18 + SoftLayer/CLI/cdn/origin_add.py | 24 + SoftLayer/CLI/cdn/origin_list.py | 28 + SoftLayer/CLI/cdn/origin_remove.py | 18 + SoftLayer/CLI/cdn/purge.py | 18 + SoftLayer/CLI/config/__init__.py | 36 + SoftLayer/CLI/config/setup.py | 142 ++ SoftLayer/CLI/config/show.py | 16 + SoftLayer/CLI/core.py | 425 +++--- SoftLayer/CLI/dns/__init__.py | 2 + SoftLayer/CLI/dns/record_add.py | 27 + SoftLayer/CLI/dns/record_edit.py | 28 + SoftLayer/CLI/dns/record_list.py | 49 + SoftLayer/CLI/dns/record_remove.py | 23 + SoftLayer/CLI/dns/zone_create.py | 17 + SoftLayer/CLI/dns/zone_delete.py | 25 + SoftLayer/CLI/dns/zone_import.py | 117 ++ SoftLayer/CLI/dns/zone_list.py | 30 + SoftLayer/CLI/dns/zone_print.py | 19 + SoftLayer/CLI/environment.py | 148 ++- SoftLayer/CLI/exceptions.py | 22 +- SoftLayer/CLI/firewall/__init__.py | 18 + SoftLayer/CLI/firewall/add.py | 54 + SoftLayer/CLI/firewall/cancel.py | 31 + SoftLayer/CLI/firewall/detail.py | 49 + SoftLayer/CLI/firewall/edit.py | 180 +++ SoftLayer/CLI/firewall/list.py | 85 ++ SoftLayer/CLI/globalip/__init__.py | 2 + SoftLayer/CLI/globalip/assign.py | 21 + SoftLayer/CLI/globalip/cancel.py | 26 + SoftLayer/CLI/globalip/create.py | 42 + SoftLayer/CLI/globalip/list.py | 50 + SoftLayer/CLI/globalip/unassign.py | 20 + SoftLayer/CLI/helpers.py | 10 - SoftLayer/CLI/image/__init__.py | 11 + SoftLayer/CLI/image/delete.py | 20 + SoftLayer/CLI/image/detail.py | 59 + SoftLayer/CLI/image/edit.py | 31 + SoftLayer/CLI/image/list.py | 55 + SoftLayer/CLI/iscsi/__init__.py | 1 + SoftLayer/CLI/iscsi/cancel.py | 30 + SoftLayer/CLI/iscsi/create.py | 23 + SoftLayer/CLI/iscsi/detail.py | 54 + SoftLayer/CLI/iscsi/list.py | 40 + SoftLayer/CLI/loadbal/__init__.py | 12 + SoftLayer/CLI/loadbal/cancel.py | 29 + SoftLayer/CLI/loadbal/create.py | 25 + SoftLayer/CLI/loadbal/create_options.py | 35 + SoftLayer/CLI/loadbal/detail.py | 85 ++ SoftLayer/CLI/loadbal/group_add.py | 41 + SoftLayer/CLI/loadbal/group_delete.py | 28 + SoftLayer/CLI/loadbal/group_edit.py | 41 + SoftLayer/CLI/loadbal/group_reset.py | 21 + SoftLayer/CLI/loadbal/health_checks.py | 26 + SoftLayer/CLI/loadbal/list.py | 49 + SoftLayer/CLI/loadbal/routing_methods.py | 25 + SoftLayer/CLI/loadbal/routing_types.py | 24 + SoftLayer/CLI/loadbal/service_add.py | 52 + SoftLayer/CLI/loadbal/service_delete.py | 28 + SoftLayer/CLI/loadbal/service_edit.py | 49 + SoftLayer/CLI/loadbal/service_toggle.py | 28 + SoftLayer/CLI/metadata.py | 67 + SoftLayer/CLI/modules/__init__.py | 15 - SoftLayer/CLI/modules/cdn.py | 188 --- SoftLayer/CLI/modules/config.py | 199 --- SoftLayer/CLI/modules/dns.py | 369 ------ SoftLayer/CLI/modules/filters.py | 32 - SoftLayer/CLI/modules/firewall.py | 424 ------ SoftLayer/CLI/modules/globalip.py | 170 --- SoftLayer/CLI/modules/help.py | 31 - SoftLayer/CLI/modules/image.py | 175 --- SoftLayer/CLI/modules/iscsi.py | 189 --- SoftLayer/CLI/modules/loadbal.py | 591 --------- SoftLayer/CLI/modules/messaging.py | 522 -------- SoftLayer/CLI/modules/metadata.py | 243 ---- SoftLayer/CLI/modules/nas.py | 50 - SoftLayer/CLI/modules/rwhois.py | 98 -- SoftLayer/CLI/modules/server.py | 1107 ---------------- SoftLayer/CLI/modules/snapshot.py | 152 --- SoftLayer/CLI/modules/sshkey.py | 164 --- SoftLayer/CLI/modules/ssl.py | 171 --- SoftLayer/CLI/modules/subnet.py | 283 ---- SoftLayer/CLI/modules/summary.py | 43 - SoftLayer/CLI/modules/ticket.py | 277 ---- SoftLayer/CLI/modules/vlan.py | 141 -- SoftLayer/CLI/modules/vs.py | 1161 ----------------- SoftLayer/CLI/mq/__init__.py | 57 + SoftLayer/CLI/mq/accounts_list.py | 28 + SoftLayer/CLI/mq/endpoints_list.py | 27 + SoftLayer/CLI/mq/ping.py | 25 + SoftLayer/CLI/mq/queue_add.py | 46 + SoftLayer/CLI/mq/queue_detail.py | 26 + SoftLayer/CLI/mq/queue_edit.py | 46 + SoftLayer/CLI/mq/queue_list.py | 34 + SoftLayer/CLI/mq/queue_pop.py | 41 + SoftLayer/CLI/mq/queue_push.py | 32 + SoftLayer/CLI/mq/queue_remove.py | 30 + SoftLayer/CLI/mq/topic_add.py | 47 + SoftLayer/CLI/mq/topic_detail.py | 30 + SoftLayer/CLI/mq/topic_list.py | 29 + SoftLayer/CLI/mq/topic_push.py | 34 + SoftLayer/CLI/mq/topic_remove.py | 25 + SoftLayer/CLI/mq/topic_subscribe.py | 46 + SoftLayer/CLI/mq/topic_unsubscribe.py | 25 + SoftLayer/CLI/nas/__init__.py | 1 + SoftLayer/CLI/nas/list.py | 39 + SoftLayer/CLI/routes.py | 182 +++ SoftLayer/CLI/rwhois/__init__.py | 1 + SoftLayer/CLI/rwhois/edit.py | 55 + SoftLayer/CLI/rwhois/show.py | 34 + SoftLayer/CLI/server/__init__.py | 209 +++ SoftLayer/CLI/server/cancel.py | 38 + SoftLayer/CLI/server/cancel_reasons.py | 25 + SoftLayer/CLI/server/create.py | 295 +++++ SoftLayer/CLI/server/create_options.py | 106 ++ SoftLayer/CLI/server/detail.py | 111 ++ SoftLayer/CLI/server/edit.py | 43 + SoftLayer/CLI/server/list.py | 78 ++ SoftLayer/CLI/server/list_chassis.py | 26 + SoftLayer/CLI/server/nic_edit.py | 24 + SoftLayer/CLI/server/power.py | 79 ++ SoftLayer/CLI/server/reload.py | 37 + SoftLayer/CLI/server/rescue.py | 28 + SoftLayer/CLI/snapshot/__init__.py | 1 + SoftLayer/CLI/snapshot/cancel.py | 22 + SoftLayer/CLI/snapshot/create.py | 21 + SoftLayer/CLI/snapshot/create_space.py | 23 + SoftLayer/CLI/snapshot/list.py | 37 + SoftLayer/CLI/snapshot/restore_volume.py | 23 + SoftLayer/CLI/sshkey/__init__.py | 1 + SoftLayer/CLI/sshkey/add.py | 32 + SoftLayer/CLI/sshkey/edit.py | 25 + SoftLayer/CLI/sshkey/list.py | 34 + SoftLayer/CLI/sshkey/print.py | 36 + SoftLayer/CLI/sshkey/remove.py | 24 + SoftLayer/CLI/ssl/__init__.py | 1 + SoftLayer/CLI/ssl/add.py | 42 + SoftLayer/CLI/ssl/download.py | 34 + SoftLayer/CLI/ssl/edit.py | 39 + SoftLayer/CLI/ssl/list.py | 41 + SoftLayer/CLI/ssl/remove.py | 21 + SoftLayer/CLI/subnet/__init__.py | 1 + SoftLayer/CLI/subnet/cancel.py | 26 + SoftLayer/CLI/subnet/create.py | 64 + SoftLayer/CLI/subnet/detail.py | 71 + SoftLayer/CLI/subnet/list.py | 67 + SoftLayer/CLI/subnet/lookup.py | 60 + SoftLayer/CLI/summary.py | 45 + SoftLayer/CLI/template.py | 29 +- SoftLayer/CLI/ticket/__init__.py | 43 + SoftLayer/CLI/ticket/create.py | 29 + SoftLayer/CLI/ticket/detail.py | 22 + SoftLayer/CLI/ticket/list.py | 39 + SoftLayer/CLI/ticket/subjects.py | 22 + SoftLayer/CLI/ticket/summary.py | 33 + SoftLayer/CLI/ticket/update.py | 26 + SoftLayer/CLI/virt/__init__.py | 2 + SoftLayer/CLI/virt/cancel.py | 25 + SoftLayer/CLI/virt/capture.py | 37 + SoftLayer/CLI/virt/create.py | 282 ++++ SoftLayer/CLI/virt/create_options.py | 112 ++ SoftLayer/CLI/virt/detail.py | 112 ++ SoftLayer/CLI/virt/dns.py | 103 ++ SoftLayer/CLI/virt/edit.py | 51 + SoftLayer/CLI/virt/list.py | 75 ++ SoftLayer/CLI/virt/network.py | 27 + SoftLayer/CLI/virt/power.py | 85 ++ SoftLayer/CLI/virt/ready.py | 25 + SoftLayer/CLI/virt/reload.py | 34 + SoftLayer/CLI/virt/upgrade.py | 53 + SoftLayer/CLI/vlan/__init__.py | 1 + SoftLayer/CLI/vlan/detail.py | 86 ++ SoftLayer/CLI/vlan/list.py | 55 + SoftLayer/managers/cci.py | 5 +- SoftLayer/managers/cdn.py | 19 +- SoftLayer/managers/dns.py | 49 +- SoftLayer/managers/firewall.py | 28 +- SoftLayer/managers/hardware.py | 59 +- SoftLayer/managers/image.py | 19 +- SoftLayer/managers/iscsi.py | 33 +- SoftLayer/managers/load_balancer.py | 45 +- SoftLayer/managers/messaging.py | 70 +- SoftLayer/managers/metadata.py | 15 +- SoftLayer/managers/network.py | 47 +- SoftLayer/managers/ordering.py | 27 +- SoftLayer/managers/sshkey.py | 15 +- SoftLayer/managers/ssl.py | 13 +- SoftLayer/managers/ticket.py | 15 +- SoftLayer/managers/vs.py | 73 +- SoftLayer/testing/__init__.py | 26 +- SoftLayer/testing/fixture_client.py | 49 +- SoftLayer/testing/fixtures/Account.py | 35 +- SoftLayer/tests/CLI/core_tests.py | 314 ++--- SoftLayer/tests/CLI/environment_tests.py | 41 +- SoftLayer/tests/CLI/helper_tests.py | 87 +- SoftLayer/tests/CLI/modules/cdn_tests.py | 66 +- SoftLayer/tests/CLI/modules/config_tests.py | 99 +- SoftLayer/tests/CLI/modules/dns_tests.py | 172 +-- SoftLayer/tests/CLI/modules/firewall_tests.py | 34 +- SoftLayer/tests/CLI/modules/globalip_tests.py | 51 +- SoftLayer/tests/CLI/modules/help_tests.py | 23 - SoftLayer/tests/CLI/modules/import_tests.py | 20 - SoftLayer/tests/CLI/modules/nas_tests.py | 17 +- SoftLayer/tests/CLI/modules/rwhois_tests.py | 89 +- SoftLayer/tests/CLI/modules/server_tests.py | 1000 ++++++-------- SoftLayer/tests/CLI/modules/sshkey_tests.py | 100 +- SoftLayer/tests/CLI/modules/summary_tests.py | 33 +- SoftLayer/tests/CLI/modules/vs_tests.py | 142 +- SoftLayer/tests/managers/cci_tests.py | 5 +- SoftLayer/tests/managers/loadbal_tests.py | 5 - SoftLayer/tests/managers/network_tests.py | 15 +- SoftLayer/tests/transport_tests.py | 2 +- docs/api/client.rst | 2 +- docs/cli.rst | 165 ++- docs/cli/vs.rst | 205 +-- docs/dev/cli.rst | 239 +--- docs/dev/index.rst | 14 +- docs/index.rst | 8 +- setup.cfg | 4 +- setup.py | 22 +- tools/requirements.txt | 4 +- tools/test-requirements.txt | 3 +- tox.ini | 4 +- 226 files changed, 8327 insertions(+), 9219 deletions(-) create mode 100644 SoftLayer/CLI/cdn/__init__.py create mode 100644 SoftLayer/CLI/cdn/detail.py create mode 100644 SoftLayer/CLI/cdn/list.py create mode 100644 SoftLayer/CLI/cdn/load.py create mode 100644 SoftLayer/CLI/cdn/origin_add.py create mode 100644 SoftLayer/CLI/cdn/origin_list.py create mode 100644 SoftLayer/CLI/cdn/origin_remove.py create mode 100644 SoftLayer/CLI/cdn/purge.py create mode 100644 SoftLayer/CLI/config/__init__.py create mode 100644 SoftLayer/CLI/config/setup.py create mode 100644 SoftLayer/CLI/config/show.py create mode 100644 SoftLayer/CLI/dns/__init__.py create mode 100644 SoftLayer/CLI/dns/record_add.py create mode 100644 SoftLayer/CLI/dns/record_edit.py create mode 100644 SoftLayer/CLI/dns/record_list.py create mode 100644 SoftLayer/CLI/dns/record_remove.py create mode 100644 SoftLayer/CLI/dns/zone_create.py create mode 100644 SoftLayer/CLI/dns/zone_delete.py create mode 100644 SoftLayer/CLI/dns/zone_import.py create mode 100644 SoftLayer/CLI/dns/zone_list.py create mode 100644 SoftLayer/CLI/dns/zone_print.py create mode 100644 SoftLayer/CLI/firewall/__init__.py create mode 100644 SoftLayer/CLI/firewall/add.py create mode 100644 SoftLayer/CLI/firewall/cancel.py create mode 100644 SoftLayer/CLI/firewall/detail.py create mode 100644 SoftLayer/CLI/firewall/edit.py create mode 100644 SoftLayer/CLI/firewall/list.py create mode 100644 SoftLayer/CLI/globalip/__init__.py create mode 100644 SoftLayer/CLI/globalip/assign.py create mode 100644 SoftLayer/CLI/globalip/cancel.py create mode 100644 SoftLayer/CLI/globalip/create.py create mode 100644 SoftLayer/CLI/globalip/list.py create mode 100644 SoftLayer/CLI/globalip/unassign.py create mode 100644 SoftLayer/CLI/image/__init__.py create mode 100644 SoftLayer/CLI/image/delete.py create mode 100644 SoftLayer/CLI/image/detail.py create mode 100644 SoftLayer/CLI/image/edit.py create mode 100644 SoftLayer/CLI/image/list.py create mode 100644 SoftLayer/CLI/iscsi/__init__.py create mode 100644 SoftLayer/CLI/iscsi/cancel.py create mode 100644 SoftLayer/CLI/iscsi/create.py create mode 100644 SoftLayer/CLI/iscsi/detail.py create mode 100644 SoftLayer/CLI/iscsi/list.py create mode 100644 SoftLayer/CLI/loadbal/__init__.py create mode 100644 SoftLayer/CLI/loadbal/cancel.py create mode 100644 SoftLayer/CLI/loadbal/create.py create mode 100644 SoftLayer/CLI/loadbal/create_options.py create mode 100644 SoftLayer/CLI/loadbal/detail.py create mode 100644 SoftLayer/CLI/loadbal/group_add.py create mode 100644 SoftLayer/CLI/loadbal/group_delete.py create mode 100644 SoftLayer/CLI/loadbal/group_edit.py create mode 100644 SoftLayer/CLI/loadbal/group_reset.py create mode 100644 SoftLayer/CLI/loadbal/health_checks.py create mode 100644 SoftLayer/CLI/loadbal/list.py create mode 100644 SoftLayer/CLI/loadbal/routing_methods.py create mode 100644 SoftLayer/CLI/loadbal/routing_types.py create mode 100644 SoftLayer/CLI/loadbal/service_add.py create mode 100644 SoftLayer/CLI/loadbal/service_delete.py create mode 100644 SoftLayer/CLI/loadbal/service_edit.py create mode 100644 SoftLayer/CLI/loadbal/service_toggle.py create mode 100644 SoftLayer/CLI/metadata.py delete mode 100644 SoftLayer/CLI/modules/__init__.py delete mode 100644 SoftLayer/CLI/modules/cdn.py delete mode 100644 SoftLayer/CLI/modules/config.py delete mode 100755 SoftLayer/CLI/modules/dns.py delete mode 100644 SoftLayer/CLI/modules/filters.py delete mode 100755 SoftLayer/CLI/modules/firewall.py delete mode 100644 SoftLayer/CLI/modules/globalip.py delete mode 100644 SoftLayer/CLI/modules/help.py delete mode 100644 SoftLayer/CLI/modules/image.py delete mode 100644 SoftLayer/CLI/modules/iscsi.py delete mode 100755 SoftLayer/CLI/modules/loadbal.py delete mode 100644 SoftLayer/CLI/modules/messaging.py delete mode 100644 SoftLayer/CLI/modules/metadata.py delete mode 100644 SoftLayer/CLI/modules/nas.py delete mode 100644 SoftLayer/CLI/modules/rwhois.py delete mode 100644 SoftLayer/CLI/modules/server.py delete mode 100644 SoftLayer/CLI/modules/snapshot.py delete mode 100644 SoftLayer/CLI/modules/sshkey.py delete mode 100755 SoftLayer/CLI/modules/ssl.py delete mode 100644 SoftLayer/CLI/modules/subnet.py delete mode 100644 SoftLayer/CLI/modules/summary.py delete mode 100644 SoftLayer/CLI/modules/ticket.py delete mode 100644 SoftLayer/CLI/modules/vlan.py delete mode 100755 SoftLayer/CLI/modules/vs.py create mode 100644 SoftLayer/CLI/mq/__init__.py create mode 100644 SoftLayer/CLI/mq/accounts_list.py create mode 100644 SoftLayer/CLI/mq/endpoints_list.py create mode 100644 SoftLayer/CLI/mq/ping.py create mode 100644 SoftLayer/CLI/mq/queue_add.py create mode 100644 SoftLayer/CLI/mq/queue_detail.py create mode 100644 SoftLayer/CLI/mq/queue_edit.py create mode 100644 SoftLayer/CLI/mq/queue_list.py create mode 100644 SoftLayer/CLI/mq/queue_pop.py create mode 100644 SoftLayer/CLI/mq/queue_push.py create mode 100644 SoftLayer/CLI/mq/queue_remove.py create mode 100644 SoftLayer/CLI/mq/topic_add.py create mode 100644 SoftLayer/CLI/mq/topic_detail.py create mode 100644 SoftLayer/CLI/mq/topic_list.py create mode 100644 SoftLayer/CLI/mq/topic_push.py create mode 100644 SoftLayer/CLI/mq/topic_remove.py create mode 100644 SoftLayer/CLI/mq/topic_subscribe.py create mode 100644 SoftLayer/CLI/mq/topic_unsubscribe.py create mode 100644 SoftLayer/CLI/nas/__init__.py create mode 100644 SoftLayer/CLI/nas/list.py create mode 100644 SoftLayer/CLI/routes.py create mode 100644 SoftLayer/CLI/rwhois/__init__.py create mode 100644 SoftLayer/CLI/rwhois/edit.py create mode 100644 SoftLayer/CLI/rwhois/show.py create mode 100644 SoftLayer/CLI/server/__init__.py create mode 100644 SoftLayer/CLI/server/cancel.py create mode 100644 SoftLayer/CLI/server/cancel_reasons.py create mode 100644 SoftLayer/CLI/server/create.py create mode 100644 SoftLayer/CLI/server/create_options.py create mode 100644 SoftLayer/CLI/server/detail.py create mode 100644 SoftLayer/CLI/server/edit.py create mode 100644 SoftLayer/CLI/server/list.py create mode 100644 SoftLayer/CLI/server/list_chassis.py create mode 100644 SoftLayer/CLI/server/nic_edit.py create mode 100644 SoftLayer/CLI/server/power.py create mode 100644 SoftLayer/CLI/server/reload.py create mode 100644 SoftLayer/CLI/server/rescue.py create mode 100644 SoftLayer/CLI/snapshot/__init__.py create mode 100644 SoftLayer/CLI/snapshot/cancel.py create mode 100644 SoftLayer/CLI/snapshot/create.py create mode 100644 SoftLayer/CLI/snapshot/create_space.py create mode 100644 SoftLayer/CLI/snapshot/list.py create mode 100644 SoftLayer/CLI/snapshot/restore_volume.py create mode 100644 SoftLayer/CLI/sshkey/__init__.py create mode 100644 SoftLayer/CLI/sshkey/add.py create mode 100644 SoftLayer/CLI/sshkey/edit.py create mode 100644 SoftLayer/CLI/sshkey/list.py create mode 100644 SoftLayer/CLI/sshkey/print.py create mode 100644 SoftLayer/CLI/sshkey/remove.py create mode 100644 SoftLayer/CLI/ssl/__init__.py create mode 100644 SoftLayer/CLI/ssl/add.py create mode 100644 SoftLayer/CLI/ssl/download.py create mode 100644 SoftLayer/CLI/ssl/edit.py create mode 100644 SoftLayer/CLI/ssl/list.py create mode 100644 SoftLayer/CLI/ssl/remove.py create mode 100644 SoftLayer/CLI/subnet/__init__.py create mode 100644 SoftLayer/CLI/subnet/cancel.py create mode 100644 SoftLayer/CLI/subnet/create.py create mode 100644 SoftLayer/CLI/subnet/detail.py create mode 100644 SoftLayer/CLI/subnet/list.py create mode 100644 SoftLayer/CLI/subnet/lookup.py create mode 100644 SoftLayer/CLI/summary.py create mode 100644 SoftLayer/CLI/ticket/__init__.py create mode 100644 SoftLayer/CLI/ticket/create.py create mode 100644 SoftLayer/CLI/ticket/detail.py create mode 100644 SoftLayer/CLI/ticket/list.py create mode 100644 SoftLayer/CLI/ticket/subjects.py create mode 100644 SoftLayer/CLI/ticket/summary.py create mode 100644 SoftLayer/CLI/ticket/update.py create mode 100644 SoftLayer/CLI/virt/__init__.py create mode 100644 SoftLayer/CLI/virt/cancel.py create mode 100644 SoftLayer/CLI/virt/capture.py create mode 100644 SoftLayer/CLI/virt/create.py create mode 100644 SoftLayer/CLI/virt/create_options.py create mode 100644 SoftLayer/CLI/virt/detail.py create mode 100644 SoftLayer/CLI/virt/dns.py create mode 100644 SoftLayer/CLI/virt/edit.py create mode 100644 SoftLayer/CLI/virt/list.py create mode 100644 SoftLayer/CLI/virt/network.py create mode 100644 SoftLayer/CLI/virt/power.py create mode 100644 SoftLayer/CLI/virt/ready.py create mode 100644 SoftLayer/CLI/virt/reload.py create mode 100644 SoftLayer/CLI/virt/upgrade.py create mode 100644 SoftLayer/CLI/vlan/__init__.py create mode 100644 SoftLayer/CLI/vlan/detail.py create mode 100644 SoftLayer/CLI/vlan/list.py delete mode 100644 SoftLayer/tests/CLI/modules/help_tests.py delete mode 100644 SoftLayer/tests/CLI/modules/import_tests.py diff --git a/SoftLayer/CLI/cdn/__init__.py b/SoftLayer/CLI/cdn/__init__.py new file mode 100644 index 000000000..678a299ea --- /dev/null +++ b/SoftLayer/CLI/cdn/__init__.py @@ -0,0 +1,2 @@ +"""Content Delivery Network.""" +# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/cdn/detail.py b/SoftLayer/CLI/cdn/detail.py new file mode 100644 index 000000000..64ccc0cbb --- /dev/null +++ b/SoftLayer/CLI/cdn/detail.py @@ -0,0 +1,32 @@ +"""Detail a CDN Account.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('account_id') +@environment.pass_env +def cli(env, account_id): + """Detail a CDN Account.""" + + manager = SoftLayer.CDNManager(env.client) + account = manager.get_account(account_id) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + + table.add_row(['id', account['id']]) + table.add_row(['account_name', account['cdnAccountName']]) + table.add_row(['type', account['cdnSolutionName']]) + table.add_row(['status', account['status']['name']]) + table.add_row(['created', account['createDate']]) + table.add_row(['notes', + account.get('cdnAccountNote', formatting.blank())]) + + return table diff --git a/SoftLayer/CLI/cdn/list.py b/SoftLayer/CLI/cdn/list.py new file mode 100644 index 000000000..9cf1220bf --- /dev/null +++ b/SoftLayer/CLI/cdn/list.py @@ -0,0 +1,43 @@ +"""List CDN Accounts.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.option('--sortby', + help='Column to sort by', + type=click.Choice(['id', + 'datacenter', + 'host', + 'cores', + 'memory', + 'primary_ip', + 'backend_ip'])) +@environment.pass_env +def cli(env, sortby): + """List all CDN accounts.""" + + manager = SoftLayer.CDNManager(env.client) + accounts = manager.list_accounts() + + table = formatting.Table(['id', + 'account_name', + 'type', + 'created', + 'notes']) + for account in accounts: + table.add_row([ + account['id'], + account['cdnAccountName'], + account['cdnSolutionName'], + account['createDate'], + account.get('cdnAccountNote', formatting.blank()) + ]) + + table.sortby = sortby + return table diff --git a/SoftLayer/CLI/cdn/load.py b/SoftLayer/CLI/cdn/load.py new file mode 100644 index 000000000..5dc53ca2b --- /dev/null +++ b/SoftLayer/CLI/cdn/load.py @@ -0,0 +1,18 @@ +"""Cache one or more files on all edge nodes.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('account_id') +@click.argument('content_url', nargs=-1) +@environment.pass_env +def cli(env, account_id, content_url): + """Cache one or more files on all edge nodes.""" + + manager = SoftLayer.CDNManager(env.client) + manager.load_content(account_id, content_url) diff --git a/SoftLayer/CLI/cdn/origin_add.py b/SoftLayer/CLI/cdn/origin_add.py new file mode 100644 index 000000000..c8f5635b5 --- /dev/null +++ b/SoftLayer/CLI/cdn/origin_add.py @@ -0,0 +1,24 @@ +"""Create an origin pull mapping.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('account_id') +@click.argument('content_url') +@click.option('--type', + help='The media type for this mapping (http, flash, wm, ...)', + default='http') +@click.option('--cname', + help='An optional CNAME to attach to the mapping') +@environment.pass_env +def cli(env, account_id, content_url, type, cname): + """Create an origin pull mapping.""" + + manager = SoftLayer.CDNManager(env.client) + manager.add_origin(account_id, type, content_url, cname) diff --git a/SoftLayer/CLI/cdn/origin_list.py b/SoftLayer/CLI/cdn/origin_list.py new file mode 100644 index 000000000..3a7b7c841 --- /dev/null +++ b/SoftLayer/CLI/cdn/origin_list.py @@ -0,0 +1,28 @@ +"""List origin pull mappings.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('account_id') +@environment.pass_env +def cli(env, account_id): + """List origin pull mappings.""" + + manager = SoftLayer.CDNManager(env.client) + origins = manager.get_origins(account_id) + + table = formatting.Table(['id', 'media_type', 'cname', 'origin_url']) + + for origin in origins: + table.add_row([origin['id'], + origin['mediaType'], + origin.get('cname', formatting.blank()), + origin['originUrl']]) + + return table diff --git a/SoftLayer/CLI/cdn/origin_remove.py b/SoftLayer/CLI/cdn/origin_remove.py new file mode 100644 index 000000000..055a93bb2 --- /dev/null +++ b/SoftLayer/CLI/cdn/origin_remove.py @@ -0,0 +1,18 @@ +"""Remove an origin pull mapping.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('account_id') +@click.argument('origin_id') +@environment.pass_env +def cli(env, account_id, origin_id): + """Remove an origin pull mapping.""" + + manager = SoftLayer.CDNManager(env.client) + manager.remove_origin(account_id, origin_id) diff --git a/SoftLayer/CLI/cdn/purge.py b/SoftLayer/CLI/cdn/purge.py new file mode 100644 index 000000000..09d0810ae --- /dev/null +++ b/SoftLayer/CLI/cdn/purge.py @@ -0,0 +1,18 @@ +"""Purge cached files from all edge nodes.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('account_id') +@click.argument('content_url', nargs=-1) +@environment.pass_env +def cli(env, account_id, content_url): + """Purge cached files from all edge nodes.""" + + manager = SoftLayer.CDNManager(env.client) + manager.purge_content(account_id, content_url) diff --git a/SoftLayer/CLI/config/__init__.py b/SoftLayer/CLI/config/__init__.py new file mode 100644 index 000000000..8a66b089a --- /dev/null +++ b/SoftLayer/CLI/config/__init__.py @@ -0,0 +1,36 @@ +"""CLI configuration.""" +# :license: MIT, see LICENSE for more details. + +from SoftLayer.CLI import formatting + + +def get_settings_from_client(client): + """Pull out settings from a SoftLayer.Client instance. + + :param client: SoftLayer.Client instance + """ + settings = { + 'username': '', + 'api_key': '', + 'timeout': client.timeout or '', + 'endpoint_url': client.endpoint_url, + } + try: + settings['username'] = client.auth.username + settings['api_key'] = client.auth.api_key + except AttributeError: + pass + + return settings + + +def config_table(settings): + """Returns a config table.""" + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + table.add_row(['Username', settings['username'] or 'not set']) + table.add_row(['API Key', settings['api_key'] or 'not set']) + table.add_row(['Endpoint URL', settings['endpoint_url'] or 'not set']) + table.add_row(['Timeout', settings['timeout'] or 'not set']) + return table diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py new file mode 100644 index 000000000..5b8fa7306 --- /dev/null +++ b/SoftLayer/CLI/config/setup.py @@ -0,0 +1,142 @@ +"""Setup CLI configuration.""" +# :license: MIT, see LICENSE for more details. +import os.path + +import SoftLayer +from SoftLayer import auth +from SoftLayer.CLI import config +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer import utils + +import click + + +def get_api_key(client, username, secret, endpoint_url=None): + """Attempts API-Key and password auth to get an API key. + + This will also generate an API key if one doesn't exist + """ + + client.endpoint_url = endpoint_url + client.auth = None + # Try to use a client with username/api key + if len(secret) == 64: + try: + client.auth = auth.BasicAuthentication(username, secret) + client['Account'].getCurrentUser() + return secret + except SoftLayer.SoftLayerAPIError as ex: + if 'invalid api token' not in ex.faultString.lower(): + raise + else: + # Try to use a client with username/password + client.authenticate_with_password(username, secret) + + user_record = client['Account'].getCurrentUser( + mask='id, apiAuthenticationKeys') + api_keys = user_record['apiAuthenticationKeys'] + if len(api_keys) == 0: + return client['User_Customer'].addApiAuthenticationKey( + id=user_record['id']) + return api_keys[0]['authenticationKey'] + + +@click.command() +@environment.pass_env +def cli(env): + """Edit configuration.""" + + username, secret, endpoint_url, timeout = get_user_input(env) + + api_key = get_api_key(env.client, username, secret, + endpoint_url=endpoint_url) + + path = '~/.softlayer' + if env.config_file: + path = env.config_file + config_path = os.path.expanduser(path) + + env.out(env.fmt(config.config_table({'username': username, + 'api_key': api_key, + 'endpoint_url': endpoint_url, + 'timeout': timeout}))) + + if not formatting.confirm('Are you sure you want to write settings ' + 'to "%s"?' % config_path, default=True): + raise exceptions.CLIAbort('Aborted.') + + # Persist the config file. Read the target config file in before + # setting the values to avoid clobbering settings + parsed_config = utils.configparser.RawConfigParser() + parsed_config.read(config_path) + try: + parsed_config.add_section('softlayer') + except utils.configparser.DuplicateSectionError: + pass + + parsed_config.set('softlayer', 'username', username) + parsed_config.set('softlayer', 'api_key', api_key) + parsed_config.set('softlayer', 'endpoint_url', endpoint_url) + + config_fd = os.fdopen(os.open(config_path, + (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), + 0o600), + 'w') + try: + parsed_config.write(config_fd) + finally: + config_fd.close() + + return "Configuration Updated Successfully" + + +def get_user_input(env): + """Ask for username, secret (api_key or password) and endpoint_url.""" + + defaults = config.get_settings_from_client(env.client.real_client) + timeout = defaults['timeout'] + + # Ask for username + for _ in range(3): + username = (env.input('Username [%s]: ' % defaults['username']) + or defaults['username']) + if username: + break + else: + raise exceptions.CLIAbort('Aborted after 3 attempts') + + # Ask for 'secret' which can be api_key or their password + for _ in range(3): + secret = (env.getpass('API Key or Password [%s]: ' + % defaults['api_key']) + or defaults['api_key']) + if secret: + break + else: + raise exceptions.CLIAbort('Aborted after 3 attempts') + + # Ask for which endpoint they want to use + for _ in range(3): + endpoint_type = env.input( + 'Endpoint (public|private|custom): ') + endpoint_type = endpoint_type.lower() + if not endpoint_type: + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT + break + if endpoint_type == 'public': + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT + break + elif endpoint_type == 'private': + endpoint_url = SoftLayer.API_PRIVATE_ENDPOINT + break + elif endpoint_type == 'custom': + endpoint_url = env.input( + 'Endpoint URL [%s]: ' % defaults['endpoint_url'] + ) or defaults['endpoint_url'] + break + else: + raise exceptions.CLIAbort('Aborted after 3 attempts') + + return username, secret, endpoint_url, timeout diff --git a/SoftLayer/CLI/config/show.py b/SoftLayer/CLI/config/show.py new file mode 100644 index 000000000..a3f7119af --- /dev/null +++ b/SoftLayer/CLI/config/show.py @@ -0,0 +1,16 @@ +"""Show current CLI configuration.""" +# :license: MIT, see LICENSE for more details. + +from SoftLayer.CLI import config +from SoftLayer.CLI import environment + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """Show current configuration.""" + + settings = config.get_settings_from_client(env.client.real_client) + return config.config_table(settings) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index c47b24a54..2f4842e54 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -1,262 +1,233 @@ """ -usage: sl [...] - sl help - sl help - sl [-h | --help] - -SoftLayer Command-line Client {version} - -The available modules are: - -Compute: - image Manages compute and flex images - metadata Get details about this machine. Also available with 'my' and 'meta' - server Bare metal servers - sshkey Manage SSH keys on your account - vs Virtual Servers (formerly CCIs) - -Networking: - cdn Content Delivery Network service management - dns Domain Name System - firewall Firewall rule and security management - loadbal Load Balancer management - globalip Global IP address management - messaging Message Queue Service - rwhois RWhoIs operations - ssl Manages SSL - subnet Subnet ordering and management - vlan Manage VLANs on your account - -Storage: - iscsi View iSCSI details - nas View NAS details - snapshot iSCSI snapshots - -General: - config View and edit configuration for this tool - ticket Manage account tickets - summary Display an overall summary of your account - help Show help - -See 'sl help ' for more information on a specific module. - -To use most commands your SoftLayer username and api_key need to be configured. -The easiest way to do that is to use: 'sl config setup' -""" -# :license: MIT, see LICENSE for more details. - -# pylint: disable=W0703 + SoftLayer.core + ~~~~~~~~~~~~~~ + Core for the SoftLayer CLI + :license: MIT, see LICENSE for more details. +""" +from __future__ import print_function import logging import sys - -import docopt +import time +import types import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer import consts +import click +# pylint: disable=too-many-public-methods, broad-except, unused-argument +# pylint: disable=redefined-builtin, super-init-not-called DEBUG_LOGGING_MAP = { - '0': logging.CRITICAL, - '1': logging.WARNING, - '2': logging.INFO, - '3': logging.DEBUG + 0: logging.CRITICAL, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG } -VALID_FORMATS = ['raw', 'table', 'json'] +VALID_FORMATS = ['table', 'raw', 'json'] +DEFAULT_FORMAT = 'raw' +if sys.stdout.isatty(): + DEFAULT_FORMAT = 'table' + + +class CommandLoader(click.MultiCommand): + """Loads commands for click.""" + def __init__(self, module=None, **attrs): + click.MultiCommand.__init__(self, **attrs) + self.module = module + + def list_commands(self, ctx): + """Get module for click.""" + env = ctx.ensure_object(environment.Environment) + return env.command_list(self.module) + + def get_command(self, ctx, name): + """Get command for click.""" + env = ctx.ensure_object(environment.Environment) + command = env.get_command(self.module, name) + return command + +class ModuleLoader(click.MultiCommand): + """Loads module for click.""" -def _append_common_options(arg_doc): - """Append common options to the doc string""" - default_format = 'raw' - if sys.stdout.isatty(): - default_format = 'table' + def list_commands(self, ctx): + """Get module for click.""" + env = ctx.ensure_object(environment.Environment) + return sorted(env.module_list()) - arg_doc += """ -Standard Options: - --format=ARG Output format. [Options: table, raw] [Default: %s] - -C FILE --config=FILE Config file location. [Default: ~/.softlayer] - --debug=LEVEL Specifies the debug noise level - 1=warn, 2=info, 3=debug - --timings Time each API call and display after results - --proxy=PROTO:PROXY_URL HTTP[s] proxy to be use to make API calls - -h --help Show this screen -""" % default_format - return arg_doc + def get_command(self, ctx, name): + """Get command for click.""" + env = ctx.ensure_object(environment.Environment) + # Do alias lookup + module_name = env.get_module_name(name) -class CommandParser(object): - """Helper class to parse commands. + module = env.get_module(module_name) + if isinstance(module, types.ModuleType): + return CommandLoader(module=module_name, help=module.__doc__) + else: + return module + + +class CliClient(SoftLayer.Client): + """A wrapped SoftLayer.Client that adds CLI-specific functionality. + + At the moment, it has a slightly different accounting for API call timings + but will also allow for 2FA and other similar functionality. - :param env: Environment instance """ - def __init__(self, env): - self.env = env - - def get_main_help(self): - """Get main help text.""" - main_doc = __doc__.format(version=SoftLayer.__version__) - return _append_common_options(main_doc).strip() - - def get_module_help(self, module_name): - """Get help text for a module.""" - module = self.env.load_module(module_name) - arg_doc = module.__doc__ - return _append_common_options(arg_doc).strip() - - def get_command_help(self, module_name, command_name): - """Get help text for a specific command.""" - command = self.env.get_command(module_name, command_name) - arg_doc = command.__doc__ - - if 'confirm' in command.options: - arg_doc += """ -Prompt Options: - -y, --really Confirm all prompt actions -""" - return _append_common_options(arg_doc).strip() - - def parse_main_args(self, args): - """Parse root arguments.""" - main_help = self.get_main_help() - arguments = docopt.docopt( - main_help, - version=consts.VERSION, - argv=args, - options_first=True) - arguments[''] = self.env.get_module_name(arguments['']) - return arguments - - def parse_module_args(self, module_name, args): - """Parse module arguments.""" - arg_doc = self.get_module_help(module_name) - arguments = docopt.docopt( - arg_doc, - version=consts.VERSION, - argv=[module_name] + args, - options_first=True) - return arguments - - def parse_command_args(self, module_name, command_name, args): - """Parse command arguments.""" - command = self.env.get_command(module_name, command_name) - arg_doc = self.get_command_help(module_name, command_name) - arguments = docopt.docopt(arg_doc, - version=consts.VERSION, - argv=[module_name] + args) - return command, arguments - - def parse(self, args): - """Parse entire tree of arguments.""" - # handle `sl ...` - main_args = self.parse_main_args(args) - module_name = main_args[''] - - # handle `sl ...` - module_args = self.parse_module_args(module_name, main_args['']) - - # get the command argument - command_name = module_args.get('') - - # handle `sl ...` - return self.parse_command_args( - module_name, - command_name, - main_args['']) - - -def main(args=sys.argv[1:], env=environment.Environment()): - """Entry point for the command-line client.""" - # Parse Top-Level Arguments + def __init__(self, client, *args, **kwargs): + self.real_client = client + self.last_calls = [] + # NOTE(kmcdonald): I really don't like this pattern. + + def call(self, service, method, *args, **kwargs): + """See Client.call for documentation.""" + start_time = time.time() + + result = self.real_client.call(service, method, *args, **kwargs) + + end_time = time.time() + diff = end_time - start_time + self.last_calls.append((service, method, start_time, diff)) + return result + + +@click.group(help="SoftLayer Command-line Client", + epilog="""To use most commands your SoftLayer +username and api_key need to be configured. The easiest way to do that is to +use: 'sl config setup'""", + cls=ModuleLoader) +@click.pass_context +@click.option('--format', + default=DEFAULT_FORMAT, + help="Output format", + type=click.Choice(VALID_FORMATS)) +@click.option('--config', '-C', + required=False, + default=click.get_app_dir('softlayer', + force_posix=True), + help="Config file location", + type=click.Path(resolve_path=True)) +@click.option('--debug', + required=False, + default='0', + help="Sets the debug noise level", + type=click.Choice(sorted([str(key) for key + in DEBUG_LOGGING_MAP.keys()]))) +@click.option('--verbose', '-v', + help="Sets the debug noise level", + type=click.IntRange(0, 3, clamp=True), + count=True) +@click.option('--timings', + required=False, + is_flag=True, + help="Time each API call and display after results") +@click.option('--proxy', + required=False, + help="HTTP[S] proxy to be use to make API calls") +@click.option('--really', '-y', + is_flag=True, + required=False, + help="Confirm all prompt actions") +@click.option('--fixtures', + is_flag=True, + required=False, + help="Use fixtures instead of actually making API calls") +@click.version_option(version=SoftLayer.__version__, + prog_name="SoftLayer Command-line Client") +def cli(ctx, + format='table', + config=None, + debug=0, + verbose=0, + proxy=None, + really=False, + fixtures=False, + **kwargs): + """Main click CLI entry-point.""" + + # Set logging level + debug_int = int(debug) + if debug_int: + verbose = debug_int + + if verbose: + logger = logging.getLogger() + logger.addHandler(logging.StreamHandler()) + logger.setLevel(DEBUG_LOGGING_MAP.get(verbose, logging.DEBUG)) + + # Populate environement with client and set it as the context object + env = ctx.ensure_object(environment.Environment) + env.skip_confirmations = really + env.config_file = config + env.format = format + if env.client is None: + # Environment can be passed in explicitly. This is used for testing + if fixtures: + from SoftLayer import testing + real_client = testing.FixtureClient() + else: + # Create SL Client + real_client = SoftLayer.Client(proxy=proxy, config_file=config) + + client = CliClient(real_client) + env.client = client + + +@cli.resultcallback() +@click.pass_context +def output_result(ctx, result, timings=False, **kwargs): + """Outputs the results returned by the CLI and also outputs timings.""" + + env = ctx.ensure_object(environment.Environment) + output = env.fmt(result) + if output: + env.out(output) + + if timings: + timing_table = formatting.Table(['service', 'method', 'time']) + + for service, call, _, duration in env.client.last_calls: + timing_table.add_row([service, call, duration]) + + env.err(env.fmt(timing_table)) + + +def main(): + """Main program. Catches several common errors and displays them nicely.""" exit_status = 0 - resolver = CommandParser(env) try: - command, command_args = resolver.parse(args) - - # Set logging level - debug_level = command_args.get('--debug') - if debug_level: - logger = logging.getLogger() - handler = logging.StreamHandler() - logger.addHandler(handler) - logger.setLevel(DEBUG_LOGGING_MAP.get(debug_level, logging.DEBUG)) - - kwargs = { - 'proxy': command_args.get('--proxy'), - 'config_file': command_args.get('--config') - } - if command_args.get('--timings'): - client = SoftLayer.TimedClient(**kwargs) - else: - client = SoftLayer.Client(**kwargs) - - # Do the thing - runnable = command(client=client, env=env) - data = runnable.execute(command_args) - if data: - out_format = command_args.get('--format', 'table') - if out_format not in VALID_FORMATS: - raise exceptions.ArgumentError('Invalid format "%s"' - % out_format) - output = formatting.format_output(data, fmt=out_format) - if output: - env.out(output) - - if command_args.get('--timings'): - out_format = command_args.get('--format', 'table') - api_calls = client.get_last_calls() - timing_table = formatting.KeyValueTable(['call', 'time']) - - for call, _, duration in api_calls: - timing_table.add_row([call, duration]) - - env.err(formatting.format_output(timing_table, fmt=out_format)) - - except exceptions.InvalidCommand as ex: - env.err(resolver.get_module_help(ex.module_name)) - if ex.command_name: - env.err('') - env.err(str(ex)) - exit_status = 1 - except exceptions.InvalidModule as ex: - env.err(resolver.get_main_help()) - if ex.module_name: - env.err('') - env.err(str(ex)) - exit_status = 1 - except docopt.DocoptExit as ex: - env.err(ex.usage) - env.err( - '\nUnknown argument(s), use -h or --help for available options') - exit_status = 127 - except KeyboardInterrupt: - env.out('') - exit_status = 1 - except exceptions.CLIAbort as ex: - env.err(str(ex.message)) - exit_status = ex.code - except SystemExit as ex: - exit_status = ex.code + cli.main() except SoftLayer.SoftLayerAPIError as ex: if 'invalid api token' in ex.faultString.lower(): - env.out("Authentication Failed: To update your credentials, use " - "'sl config setup'") + print("Authentication Failed: To update your credentials," + " use 'sl config setup'") + exit_status = 1 else: - env.err(str(ex)) + print(str(ex)) exit_status = 1 except SoftLayer.SoftLayerError as ex: - env.err(str(ex)) + print(str(ex)) exit_status = 1 + except exceptions.CLIAbort as ex: + print(str(ex.message)) + exit_status = ex.code except Exception: import traceback - env.err("An unexpected error has occured:") - env.err(traceback.format_exc()) - env.err("Feel free to report this error as it is likely a bug:") - env.err(" https://github.com/softlayer/softlayer-python/issues") + print("An unexpected error has occured:") + print(str(traceback.format_exc())) + print("Feel free to report this error as it is likely a bug:") + print(" https://github.com/softlayer/softlayer-python/issues") exit_status = 1 sys.exit(exit_status) + + +if __name__ == '__main__': + main() diff --git a/SoftLayer/CLI/dns/__init__.py b/SoftLayer/CLI/dns/__init__.py new file mode 100644 index 000000000..85833186f --- /dev/null +++ b/SoftLayer/CLI/dns/__init__.py @@ -0,0 +1,2 @@ +"""Domain Name System.""" +# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/dns/record_add.py b/SoftLayer/CLI/dns/record_add.py new file mode 100644 index 000000000..856c4be53 --- /dev/null +++ b/SoftLayer/CLI/dns/record_add.py @@ -0,0 +1,27 @@ +"""Add resource record.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('zone') +@click.argument('record') +@click.argument('type') +@click.argument('data') +@click.option('--ttl', + type=click.INT, + default=7200, + help='TTL value in seconds, such as 86400') +@environment.pass_env +def cli(env, zone, record, type, data, ttl): + """Add resource record.""" + + manager = SoftLayer.DNSManager(env.client) + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + manager.create_record(zone_id, record, type, data, ttl=ttl) diff --git a/SoftLayer/CLI/dns/record_edit.py b/SoftLayer/CLI/dns/record_edit.py new file mode 100644 index 000000000..f73e4c89f --- /dev/null +++ b/SoftLayer/CLI/dns/record_edit.py @@ -0,0 +1,28 @@ +"""Update DNS record.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('record_id') +@click.option('--record', help='Host record, such as www') +@click.option('--data', help='Record data, such as an IP address') +@click.option('--ttl', + type=click.INT, + help='TTL value in seconds, such as 86400') +@click.option('--type', help='Record type, such as A or CNAME') +@environment.pass_env +def cli(env, record_id, record, data, ttl, type): + """Update DNS record.""" + manager = SoftLayer.DNSManager(env.client) + result = manager.get_record(record_id) + result['host'] = record or result['record'] + result['ttl'] = ttl or result['ttl'] + result['type'] = type or result['type'] + result['data'] = data or result['data'] + manager.edit_record(result) diff --git a/SoftLayer/CLI/dns/record_list.py b/SoftLayer/CLI/dns/record_list.py new file mode 100644 index 000000000..bf66b9f59 --- /dev/null +++ b/SoftLayer/CLI/dns/record_list.py @@ -0,0 +1,49 @@ +"""List all records in a zone.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click +# pylint: disable=redefined-builtin + + +@click.command() +@click.argument('zone') +@click.option('--data', help='Record data, such as an IP address') +@click.option('--record', help='Host record, such as www') +@click.option('--ttl', + type=click.INT, + help='TTL value in seconds, such as 86400') +@click.option('--type', help='Record type, such as A or CNAME') +@environment.pass_env +def cli(env, zone, data, record, ttl, type): + """List all records in a zone.""" + + manager = SoftLayer.DNSManager(env.client) + table = formatting.Table(['id', 'record', 'type', 'ttl', 'value']) + + table.align['ttl'] = 'l' + table.align['record'] = 'r' + table.align['value'] = 'l' + + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + + records = manager.get_records(zone_id, + record_type=type, + host=record, + ttl=ttl, + data=data) + + for record in records: + table.add_row([ + record['id'], + record['host'], + record['type'].upper(), + record['ttl'], + record['data'] + ]) + + return table diff --git a/SoftLayer/CLI/dns/record_remove.py b/SoftLayer/CLI/dns/record_remove.py new file mode 100644 index 000000000..3ef81c90b --- /dev/null +++ b/SoftLayer/CLI/dns/record_remove.py @@ -0,0 +1,23 @@ +"""Remove resource record.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('record_id') +@environment.pass_env +def cli(env, record_id): + """Add resource record.""" + + manager = SoftLayer.DNSManager(env.client) + + if env.skip_confirmations or formatting.no_going_back('yes'): + manager.delete_record(record_id) + else: + raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/dns/zone_create.py b/SoftLayer/CLI/dns/zone_create.py new file mode 100644 index 000000000..21b9e9289 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_create.py @@ -0,0 +1,17 @@ +"""Create a zone.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.argument('zone') +@environment.pass_env +def cli(env, zone): + """Create a zone.""" + + manager = SoftLayer.DNSManager(env.client) + manager.create_zone(zone) diff --git a/SoftLayer/CLI/dns/zone_delete.py b/SoftLayer/CLI/dns/zone_delete.py new file mode 100644 index 000000000..8608043ef --- /dev/null +++ b/SoftLayer/CLI/dns/zone_delete.py @@ -0,0 +1,25 @@ +"""Delete zone.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('zone') +@environment.pass_env +def cli(env, zone): + """Delete zone.""" + + manager = SoftLayer.DNSManager(env.client) + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + + if env.skip_confirmations or formatting.no_going_back(zone): + manager.delete_zone(zone_id) + else: + raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/dns/zone_import.py b/SoftLayer/CLI/dns/zone_import.py new file mode 100644 index 000000000..505dab7d0 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_import.py @@ -0,0 +1,117 @@ +"""Import zone based off a BIND zone file.""" +# :license: MIT, see LICENSE for more details. +import re + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + +import click + +RECORD_REGEX = re.compile(r"""^((?P([\w-]+(\.)?)*|\@)?\s+ + (?P\d+)?\s+ + (?P\w+)?)?\s+ + (?P\w+)\s+ + (?P.*)""", re.X) +RECORD_FMT = "type={type}, record={record}, data={data}, ttl={ttl}" + + +@click.command() +@click.argument('zonefile', + type=click.Path(exists=True, readable=True, resolve_path=True)) +@click.option('--dry-run', is_flag=True, help="Don't actually create records") +@environment.pass_env +def cli(env, zonefile, dry_run): + """Import zone based off a BIND zone file.""" + + manager = SoftLayer.DNSManager(env.client) + with open(zonefile) as zone_f: + zone_contents = zone_f.read() + + zone, records, bad_lines = parse_zone_details(zone_contents) + + env.out("Parsed: zone=%s" % zone) + for record in records: + env.out("Parsed: %s" % RECORD_FMT.format(**record)) + + for line in bad_lines: + env.out("Unparsed: %s" % line) + + if dry_run: + return + + # Find zone id or create the zone if it doesn't exist + try: + zone_id = helpers.resolve_id(manager.resolve_ids, zone, + name='zone') + except exceptions.CLIAbort: + zone_id = manager.create_zone(zone)['id'] + env.out(click.style("Created: %s" % zone, fg='green')) + + # Attempt to create each record + for record in records: + try: + manager.create_record(zone_id, + record['record'], + record['type'], + record['data'], + record['ttl']) + + env.out(click.style("Created: %s" % RECORD_FMT.format(**record), + fg='green')) + except SoftLayer.SoftLayerAPIError as ex: + env.out(click.style("Failed: %s" % RECORD_FMT.format(**record), + fg='red')) + env.out(click.style(str(ex), fg='red')) + + env.out(click.style("Finished", fg='green')) + + +def parse_zone_details(zone_contents): + """Parses a zone file into python data-structures.""" + records = [] + bad_lines = [] + zone_lines = [line.strip() for line in zone_contents.split('\n')] + + zone_search = re.search(r'^\$ORIGIN (?P.*)\.', zone_lines[0]) + zone = zone_search.group('zone') + + for line in zone_lines[1:]: + record_search = re.search(RECORD_REGEX, line) + if record_search is None: + bad_lines.append(line) + continue + + name = record_search.group('domain') + # The API requires we send a host, although bind allows a blank + # entry. @ is the same thing as blank + if name is None: + name = "@" + + ttl = record_search.group('ttl') + # we don't do anything with the class + # domain_class = domainSearch.group('class') + record_type = record_search.group('type').upper() + data = record_search.group('data') + + # the dns class doesn't support weighted MX records yet, so we chomp + # that part out. + if record_type == "MX": + record_search = re.search(r'(?P\d+)\s+(?P.*)', data) + data = record_search.group('data') + + # This will skip the SOA record bit. And any domain that gets + # parsed oddly. + if record_type == 'IN': + bad_lines.append(line) + continue + + records.append({ + 'record': name, + 'type': record_type, + 'data': data, + 'ttl': ttl, + }) + + return zone, records, bad_lines diff --git a/SoftLayer/CLI/dns/zone_list.py b/SoftLayer/CLI/dns/zone_list.py new file mode 100644 index 000000000..8ad628af8 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_list.py @@ -0,0 +1,30 @@ +"""List all zones.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List all zones.""" + + manager = SoftLayer.DNSManager(env.client) + zones = manager.list_zones() + table = formatting.Table(['id', 'zone', 'serial', 'updated']) + table.align['serial'] = 'c' + table.align['updated'] = 'c' + + for zone in zones: + table.add_row([ + zone['id'], + zone['name'], + zone['serial'], + zone['updateDate'], + ]) + + return table diff --git a/SoftLayer/CLI/dns/zone_print.py b/SoftLayer/CLI/dns/zone_print.py new file mode 100644 index 000000000..fcb2ab0c2 --- /dev/null +++ b/SoftLayer/CLI/dns/zone_print.py @@ -0,0 +1,19 @@ +"""Print zone in BIND format.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('zone') +@environment.pass_env +def cli(env, zone): + """Print zone in BIND format.""" + + manager = SoftLayer.DNSManager(env.client) + zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') + return manager.dump_zone(zone_id) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index 58a024c4c..58031e80d 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -7,22 +7,22 @@ """ import getpass import importlib -import inspect -import os -import os.path -import sys from SoftLayer.CLI import exceptions -from SoftLayer.CLI import modules +from SoftLayer.CLI import formatting +from SoftLayer.CLI import routes from SoftLayer import utils -# pylint: disable=R0201 +import click +import pkg_resources + +# pylint: disable=too-many-instance-attributes, invalid-name class Environment(object): """Provides access to the current CLI environment.""" def __init__(self): - # {'module_name': {'action': 'actionClass'}} + # {'module_name': {'action': plugin_loader}} self.plugins = {} self.aliases = { 'meta': 'metadata', @@ -35,58 +35,90 @@ def __init__(self): 'virtual': 'vs', 'lb': 'loadbal', } - self.stdout = sys.stdout - self.stderr = sys.stderr + self.client = None + self.format = 'table' + self.skip_confirmations = False + self._modules_loaded = False + self.config_file = None + + def command_list(self, module_name): + """Command listing.""" + + self._load_modules() + # Filter commands registered as None. These are the bases. + return sorted([m for m in self.plugins[module_name].keys() + if m is not None]) + + def module_list(self): + """Returns the list of modules in SoftLayer.CLI.modules.""" + self._load_modules() + return sorted(list(self.plugins.keys())) def get_command(self, module_name, command_name): """Based on the loaded modules, return a command.""" + self._load_modules() actions = self.plugins.get(module_name) or {} + if command_name in actions: - return actions[command_name] - if None in actions: - return actions[None] + return actions[command_name].load() + raise exceptions.InvalidCommand(module_name, command_name) + def get_module(self, module_name): + """Returns the module.""" + self._load_modules() + return self.get_command(module_name, None) + def get_module_name(self, module_name): """Returns the actual module name. Uses the alias mapping.""" if module_name in self.aliases: return self.aliases[module_name] return module_name - def load_module(self, module_name): # pragma: no cover - """Loads module by name.""" - try: - module = importlib.import_module('SoftLayer.CLI.modules.%s' - % module_name) - for _, obj in inspect.getmembers(module): - if inspect.isclass(obj) and issubclass(obj, CLIRunnable): - self.add_plugin(obj) - return module - except ImportError: - raise exceptions.InvalidModule(module_name) - - def add_plugin(self, cls): - """Add a CLIRunnable as a plugin to the environment.""" - command = cls.__module__.split('.')[-1] - if command not in self.plugins: - self.plugins[command] = {} - self.plugins[command][cls.action] = cls - - def plugin_list(self): - """Returns the list of modules in SoftLayer.CLI.modules.""" - return modules.get_module_list() + def _load_modules(self): + """Loads all modules.""" + if self._modules_loaded is True: + return + + self._load_modules_from_python() + self._load_modules_from_entry_points() + + self._modules_loaded = True + + def _load_modules_from_python(self): + """Load modules from the native python source.""" + for name, modpath in routes.ALL_ROUTES: + module, subcommand = _parse_name(name) + if module not in self.plugins: + self.plugins[module] = {} + + if ':' in modpath: + path, attr = modpath.split(':', 1) + else: + path, attr = modpath, None + self.plugins[module][subcommand] = ModuleLoader(path, attr=attr) + + def _load_modules_from_entry_points(self): + """Load modules from the entry_points (slower).""" + for obj in pkg_resources.iter_entry_points(group='softlayer.cli', + name=None): + + module, subcommand = _parse_name(obj.name) + if module not in self.plugins: + self.plugins[module] = {} + self.plugins[module][subcommand] = obj def out(self, output, newline=True): """Outputs a string to the console (stdout).""" - self.stdout.write(output) - if newline: - self.stdout.write(os.linesep) + click.echo(output, nl=newline) def err(self, output, newline=True): """Outputs an error string to the console (stderr).""" - self.stderr.write(output) - if newline: - self.stderr.write(os.linesep) + click.echo(output, nl=newline, err=True) + + def fmt(self, output): + """Format output based on current the environment format.""" + return formatting.format_output(output, fmt=self.format) def input(self, prompt): """Provide a command prompt.""" @@ -96,28 +128,30 @@ def getpass(self, prompt): """Provide a password prompt.""" return getpass.getpass(prompt) - def exit(self, code=0): - """Exit.""" - sys.exit(code) +class ModuleLoader(object): + """Module loader that acts a little like an EntryPoint object.""" -class CLIRunnable(object): - """This represents a descrete command or action in the CLI. + def __init__(self, import_path, attr=None): + self.import_path = import_path + self.attr = attr - CLIRunnable is intended to be subclassed. + def load(self): + """load and return the module/attribute.""" + module = importlib.import_module(self.import_path) + if self.attr: + return getattr(module, self.attr) + return module - """ - options = [] # set by subclass - action = 'not set' # set by subclass - def __init__(self, client=None, env=None): - self.client = client - self.env = env +def _parse_name(name): + """Parse command name and path from the given name.""" + if ':' in name: + module, subcommand = name.split(':', 1) + else: + module, subcommand = name, None - def execute(self, args): - """Execute the command. + return module, subcommand - This is intended to be overridden in a subclass. - """ - pass +pass_env = click.make_pass_decorator(Environment, ensure=True) diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index b1c725ef4..ea93462a4 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -15,6 +15,12 @@ def __init__(self, code=0, *args): super(CLIHalt, self).__init__(*args) self.code = code + def __str__(self): + return "" % (self.code, + getattr(self, 'message')) + + __repr__ = __str__ + class CLIAbort(CLIHalt): """Halt the execution of the command. Gives an exit code of 2.""" @@ -35,13 +41,9 @@ class InvalidCommand(SoftLayer.SoftLayerError): def __init__(self, module_name, command_name, *args): self.module_name = module_name self.command_name = command_name - error = 'Invalid command: "%s".' % self.command_name - SoftLayer.SoftLayerError.__init__(self, error, *args) - - -class InvalidModule(SoftLayer.SoftLayerError): - """Raised when trying to use a module that does not exist.""" - def __init__(self, module_name, *args): - self.module_name = module_name - error = 'Invalid module: "%s".' % self.module_name - SoftLayer.SoftLayerError.__init__(self, error, *args) + cmd_str = module_name + if command_name is not None: + cmd_str = '%s %s' % (module_name, command_name) + SoftLayer.SoftLayerError.__init__(self, + 'Invalid command: "%s"' % cmd_str, + *args) diff --git a/SoftLayer/CLI/firewall/__init__.py b/SoftLayer/CLI/firewall/__init__.py new file mode 100644 index 000000000..70b92df9f --- /dev/null +++ b/SoftLayer/CLI/firewall/__init__.py @@ -0,0 +1,18 @@ +"""Firewalls.""" +# :license: MIT, see LICENSE for more details. + +from SoftLayer.CLI import exceptions + + +def parse_id(input_id): + """Helper package to retrieve the actual IDs. + + :param input_id: the ID provided by the user + :returns: A list of valid IDs + """ + key_value = input_id.split(':') + + if len(key_value) != 2: + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) + return key_value[0], int(key_value[1]) diff --git a/SoftLayer/CLI/firewall/add.py b/SoftLayer/CLI/firewall/add.py new file mode 100644 index 000000000..5a6a7d07a --- /dev/null +++ b/SoftLayer/CLI/firewall/add.py @@ -0,0 +1,54 @@ +"""Create new firewall.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('target') +@click.option('--firewall-type', + type=click.Choice(['vs', 'vlan', 'server']), + help='Firewall type', + required=True) +@click.option('--high-availability', '--ha', + is_flag=True, + help='High available firewall option') +@environment.pass_env +def cli(env, target, firewall_type, high_availability): + """Create new firewall.""" + + mgr = SoftLayer.FirewallManager(env.client) + + if not env.skip_confirmations: + if firewall_type == 'vlan': + pkg = mgr.get_dedicated_package(ha_enabled=high_availability) + elif firewall_type == 'vs': + pkg = mgr.get_standard_package(target, is_cci=True) + elif firewall_type == 'server': + pkg = mgr.get_standard_package(target, is_cci=False) + + if not pkg: + return "Unable to add firewall - Is network public enabled?" + + env.out("******************") + env.out("Product: %s" % pkg[0]['description']) + env.out("Price: $%s monthly" % pkg[0]['prices'][0]['recurringFee']) + env.out("******************") + + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') + + if firewall_type == 'vlan': + mgr.add_vlan_firewall(target, ha_enabled=high_availability) + elif firewall_type == 'vs': + mgr.add_standard_firewall(target, is_cci=True) + elif firewall_type == 'server': + mgr.add_standard_firewall(target, is_cci=False) + + return "Firewall is being created!" diff --git a/SoftLayer/CLI/firewall/cancel.py b/SoftLayer/CLI/firewall/cancel.py new file mode 100644 index 000000000..c15670eb8 --- /dev/null +++ b/SoftLayer/CLI/firewall/cancel.py @@ -0,0 +1,31 @@ +"""List firewalls.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import firewall +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """List firewalls.""" + + mgr = SoftLayer.FirewallManager(env.client) + firewall_type, firewall_id = firewall.parse_id(identifier) + + if any([env.skip_confirmations, + formatting.confirm("This action will cancel a firewall from your" + "account. Continue?")]): + if firewall_type in ['cci', 'server']: + mgr.cancel_firewall(firewall_id, dedicated=False) + elif firewall_type == 'vlan': + mgr.cancel_firewall(firewall_id, dedicated=True) + return 'Firewall with id %s is being cancelled!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py new file mode 100644 index 000000000..4ba53dc62 --- /dev/null +++ b/SoftLayer/CLI/firewall/detail.py @@ -0,0 +1,49 @@ +"""Detail firewall.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import firewall +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Detail firewall.""" + + mgr = SoftLayer.FirewallManager(env.client) + + firewall_type, firewall_id = firewall.parse_id(identifier) + if firewall_type == 'vlan': + rules = mgr.get_dedicated_fwl_rules(firewall_id) + else: + rules = mgr.get_standard_fwl_rules(firewall_id) + + return get_rules_table(rules) + + +def get_rules_table(rules): + """Helper to format the rules into a table. + + :param list rules: A list containing the rules of the firewall + :returns: a formatted table of the firewall rules + """ + table = formatting.Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', + 'dest', 'dest_mask']) + table.sortby = '#' + for rule in rules: + table.add_row([ + rule['orderValue'], + rule['action'], + rule['protocol'], + rule['sourceIpAddress'], + rule['sourceIpSubnetMask'], + '%s:%s-%s' % (rule['destinationIpAddress'], + rule['destinationPortRangeStart'], + rule['destinationPortRangeEnd']), + rule['destinationIpSubnetMask']]) + return table diff --git a/SoftLayer/CLI/firewall/edit.py b/SoftLayer/CLI/firewall/edit.py new file mode 100644 index 000000000..0ea61e330 --- /dev/null +++ b/SoftLayer/CLI/firewall/edit.py @@ -0,0 +1,180 @@ +"""List firewalls.""" +# :license: MIT, see LICENSE for more details. + +import os +import subprocess +import tempfile + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import firewall +from SoftLayer.CLI import formatting + +import click + +DELIMITER = "=========================================\n" + + +def parse_rules(content=None): + """Helper to parse the input from the user into a list of rules. + + :param string content: the content of the editor + :returns: a list of rules + """ + rules = content.split(DELIMITER) + parsed_rules = list() + order = 1 + for rule in rules: + if rule.strip() == '': + continue + parsed_rule = {} + lines = rule.split("\n") + parsed_rule['orderValue'] = order + order += 1 + for line in lines: + if line.strip() == '': + continue + key_value = line.strip().split(':') + key = key_value[0].strip() + value = key_value[1].strip() + if key == 'action': + parsed_rule['action'] = value + elif key == 'protocol': + parsed_rule['protocol'] = value + elif key == 'source_ip_address': + parsed_rule['sourceIpAddress'] = value + elif key == 'source_ip_subnet_mask': + parsed_rule['sourceIpSubnetMask'] = value + elif key == 'destination_ip_address': + parsed_rule['destinationIpAddress'] = value + elif key == 'destination_ip_subnet_mask': + parsed_rule['destinationIpSubnetMask'] = value + elif key == 'destination_port_range_start': + parsed_rule['destinationPortRangeStart'] = int(value) + elif key == 'destination_port_range_end': + parsed_rule['destinationPortRangeEnd'] = int(value) + elif key == 'version': + parsed_rule['version'] = int(value) + parsed_rules.append(parsed_rule) + return parsed_rules + + +def open_editor(rules=None, content=None): + """Helper to open an editor for editing the firewall rules. + + This method takes two parameters, if content is provided, + that means that submitting the rules failed and we are allowing + the user to re-edit what they provided. If content is not provided, the + rules retrieved from the firewall will be displayed to the user. + + :param list rules: A list containing the rules of the firewall + :param string content: the content that the user provided in the editor + :returns: a formatted string that get be pushed into the editor + """ + + # Let's get the default EDITOR of the environment, + # use nano if none is specified + editor = os.environ.get('EDITOR', 'nano') + + with tempfile.NamedTemporaryFile(suffix=".tmp") as tfile: + + if content: + # if content is provided, just display it as is + tfile.write(content) + tfile.flush() + subprocess.call([editor, tfile.name]) + tfile.seek(0) + data = tfile.read() + return data + + if not rules: + # if the firewall has no rules, provide a template + tfile.write(DELIMITER) + tfile.write(get_formatted_rule()) + else: + # if the firewall has rules, display those to the user + for rule in rules: + tfile.write(DELIMITER) + tfile.write(get_formatted_rule(rule)) + tfile.write(DELIMITER) + tfile.flush() + subprocess.call([editor, tfile.name]) + tfile.seek(0) + data = tfile.read() + return data + + return + + +def get_formatted_rule(rule=None): + """Helper to format the rule into a user friendly format. + + :param dict rule: A dict containing one rule of the firewall + :returns: a formatted string that get be pushed into the editor + """ + rule = rule or {} + return ('action: %s\n' + 'protocol: %s\n' + 'source_ip_address: %s\n' + 'source_ip_subnet_mask: %s\n' + 'destination_ip_address: %s\n' + 'destination_ip_subnet_mask: %s\n' + 'destination_port_range_start: %s\n' + 'destination_port_range_end: %s\n' + 'version: %s\n' + % (rule.get('action', 'permit'), + rule.get('protocol', 'tcp'), + rule.get('sourceIpAddress', 'any'), + rule.get('sourceIpSubnetMask', '255.255.255.255'), + rule.get('destinationIpAddress', 'any'), + rule.get('destinationIpSubnetMask', '255.255.255.255'), + rule.get('destinationPortRangeStart', 1), + rule.get('destinationPortRangeEnd', 1), + rule.get('version', 4))) + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Edit firewall rules.""" + + mgr = SoftLayer.FirewallManager(env.client) + + firewall_type, firewall_id = firewall.parse_id(identifier) + if firewall_type == 'vlan': + orig_rules = mgr.get_dedicated_fwl_rules(firewall_id) + else: + orig_rules = mgr.get_standard_fwl_rules(firewall_id) + # open an editor for the user to enter their rules + edited_rules = open_editor(rules=orig_rules) + env.out(edited_rules) + if formatting.confirm("Would you like to submit the rules. " + "Continue?"): + while True: + try: + rules = parse_rules(edited_rules) + if firewall_type == 'vlan': + rules = mgr.edit_dedicated_fwl_rules(firewall_id, + rules) + else: + rules = mgr.edit_standard_fwl_rules(firewall_id, + rules) + break + except (SoftLayer.SoftLayerError, ValueError) as error: + env.out("Unexpected error({%s})" % (error)) + if formatting.confirm("Would you like to continue editing " + "the rules. Continue?"): + edited_rules = open_editor(content=edited_rules) + env.out(edited_rules) + if formatting.confirm("Would you like to submit the " + "rules. Continue?"): + continue + else: + raise exceptions.CLIAbort('Aborted.') + else: + raise exceptions.CLIAbort('Aborted.') + return 'Firewall updated!' + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/firewall/list.py b/SoftLayer/CLI/firewall/list.py new file mode 100644 index 000000000..9d0a17a8f --- /dev/null +++ b/SoftLayer/CLI/firewall/list.py @@ -0,0 +1,85 @@ +"""List firewalls.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List firewalls.""" + + mgr = SoftLayer.FirewallManager(env.client) + table = formatting.Table(['firewall id', + 'type', + 'features', + 'server/vlan id']) + fwvlans = mgr.get_firewalls() + dedicated_firewalls = [firewall for firewall in fwvlans + if firewall['dedicatedFirewallFlag']] + + for vlan in dedicated_firewalls: + features = [] + if vlan['highAvailabilityFirewallFlag']: + features.append('HA') + + if features: + feature_list = formatting.listing(features, separator=',') + else: + feature_list = formatting.blank() + + table.add_row([ + 'vlan:%s' % vlan['networkVlanFirewall']['id'], + 'VLAN - dedicated', + feature_list, + vlan['id'] + ]) + + shared_vlan = [firewall for firewall in fwvlans + if not firewall['dedicatedFirewallFlag']] + for vlan in shared_vlan: + vs_firewalls = [guest + for guest in vlan['firewallGuestNetworkComponents'] + if has_firewall_component(guest)] + + for firewall in vs_firewalls: + table.add_row([ + 'cci:%s' % firewall['id'], + 'CCI - standard', + '-', + firewall['guestNetworkComponent']['guest']['id'] + ]) + + server_firewalls = [server + for server in vlan['firewallNetworkComponents'] + if has_firewall_component(server)] + + for firewall in server_firewalls: + table.add_row([ + 'server:%s' % firewall['id'], + 'Server - standard', + '-', + utils.lookup(firewall, + 'networkComponent', + 'downlinkComponent', + 'hardwareId') + ]) + + return table + + +def has_firewall_component(server): + """Helper to determine whether or not a server has a firewall. + + :param dict server: A dictionary representing a server + :returns: True if the Server has a firewall. + """ + if server['status'] != 'no_edit': + return True + + return False diff --git a/SoftLayer/CLI/globalip/__init__.py b/SoftLayer/CLI/globalip/__init__.py new file mode 100644 index 000000000..68fb2f949 --- /dev/null +++ b/SoftLayer/CLI/globalip/__init__.py @@ -0,0 +1,2 @@ +"""Global IP addresses.""" +# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/globalip/assign.py b/SoftLayer/CLI/globalip/assign.py new file mode 100644 index 000000000..ba13739af --- /dev/null +++ b/SoftLayer/CLI/globalip/assign.py @@ -0,0 +1,21 @@ +"""Assigns the global IP to a target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@click.argument('target') +@environment.pass_env +def cli(env, identifier, target): + """Assigns the global IP to a target.""" + + mgr = SoftLayer.NetworkManager(env.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, + name='global ip') + mgr.assign_global_ip(global_ip_id, target) diff --git a/SoftLayer/CLI/globalip/cancel.py b/SoftLayer/CLI/globalip/cancel.py new file mode 100644 index 000000000..066088223 --- /dev/null +++ b/SoftLayer/CLI/globalip/cancel.py @@ -0,0 +1,26 @@ +"""Cancel global IP.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel global IP.""" + + mgr = SoftLayer.NetworkManager(env.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, + name='global ip') + + if env.skip_confirmations or formatting.no_going_back(global_ip_id): + mgr.cancel_global_ip(global_ip_id) + else: + raise exceptions.CLIAbort('Aborted') diff --git a/SoftLayer/CLI/globalip/create.py b/SoftLayer/CLI/globalip/create.py new file mode 100644 index 000000000..4d0e62ffc --- /dev/null +++ b/SoftLayer/CLI/globalip/create.py @@ -0,0 +1,42 @@ +"""Creates a global IP.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.option('--ipv6', '--v6', is_flag=True, help='Order a IPv6 IP') +@click.option('--test', help='test order') +@environment.pass_env +def cli(env, ipv6, test): + """Creates a global IP.""" + + mgr = SoftLayer.NetworkManager(env.client) + + version = 4 + if ipv6: + version = 6 + if not test and not env.skip_confirmations: + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Cancelling order.') + result = mgr.add_global_ip(version=version, test_order=test) + + table = formatting.Table(['item', 'cost']) + table.align['Item'] = 'r' + table.align['cost'] = 'r' + + total = 0.0 + for price in result['orderDetails']['prices']: + total += float(price.get('recurringFee', 0.0)) + rate = "%.2f" % float(price['recurringFee']) + + table.add_row([price['item']['description'], rate]) + + table.add_row(['Total monthly cost', "%.2f" % total]) + return table diff --git a/SoftLayer/CLI/globalip/list.py b/SoftLayer/CLI/globalip/list.py new file mode 100644 index 000000000..d1a86bc08 --- /dev/null +++ b/SoftLayer/CLI/globalip/list.py @@ -0,0 +1,50 @@ +"""List all global IPs.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.option('--ip-version', + help='Display only IPv4', + type=click.Choice(['v4', 'v6'])) +@environment.pass_env +def cli(env, ip_version): + """List all global IPs.""" + + mgr = SoftLayer.NetworkManager(env.client) + + table = formatting.Table(['id', 'ip', 'assigned', 'target']) + + version = None + if ip_version == 'v4': + version = 4 + elif ip_version == 'v6': + version = 6 + + ips = mgr.list_global_ips(version=version) + + for ip_address in ips: + assigned = 'No' + target = 'None' + if ip_address.get('destinationIpAddress'): + dest = ip_address['destinationIpAddress'] + assigned = 'Yes' + target = dest['ipAddress'] + virtual_guest = dest.get('virtualGuest') + if virtual_guest: + target += (' (%s)' + % virtual_guest['fullyQualifiedDomainName']) + elif ip_address['destinationIpAddress'].get('hardware'): + target += (' (%s)' + % dest['hardware']['fullyQualifiedDomainName']) + + table.add_row([ip_address['id'], + ip_address['ipAddress']['ipAddress'], + assigned, + target]) + return table diff --git a/SoftLayer/CLI/globalip/unassign.py b/SoftLayer/CLI/globalip/unassign.py new file mode 100644 index 000000000..e7c0d9f7e --- /dev/null +++ b/SoftLayer/CLI/globalip/unassign.py @@ -0,0 +1,20 @@ +"""Unassigns a global IP from a target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Unassigns a global IP from a target.""" + + mgr = SoftLayer.NetworkManager(env.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, + name='global ip') + mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index 60d2852e9..135e36052 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -30,13 +30,3 @@ def resolve_id(resolver, identifier, name='object'): (name, identifier, ', '.join([str(_id) for _id in ids]))) return ids[0] - - -def sanitize_args(args): - """ sanitize input (remove = sign from argument values) - :returns args back - """ - for key, value in args.items(): - if isinstance(value, str) and value.startswith('='): - args[key] = value[1:] - return args diff --git a/SoftLayer/CLI/image/__init__.py b/SoftLayer/CLI/image/__init__.py new file mode 100644 index 000000000..a186c27d3 --- /dev/null +++ b/SoftLayer/CLI/image/__init__.py @@ -0,0 +1,11 @@ +"""Compute images.""" +# :license: MIT, see LICENSE for more details. +from SoftLayer.CLI import formatting + + +MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' + 'imageType') +DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' + 'note,createDate,status') +PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') +PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') diff --git a/SoftLayer/CLI/image/delete.py b/SoftLayer/CLI/image/delete.py new file mode 100644 index 000000000..6cfc903b7 --- /dev/null +++ b/SoftLayer/CLI/image/delete.py @@ -0,0 +1,20 @@ +"""Delete an image.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Delete an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') + + image_mgr.delete_image(image_id) diff --git a/SoftLayer/CLI/image/detail.py b/SoftLayer/CLI/image/detail.py new file mode 100644 index 000000000..c716c2299 --- /dev/null +++ b/SoftLayer/CLI/image/detail.py @@ -0,0 +1,59 @@ +"""Get details for an image.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.CLI import image as image_mod +from SoftLayer import utils + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get details for an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') + + image = image_mgr.get_image(image_id, mask=image_mod.DETAIL_MASK) + disk_space = 0 + datacenters = [] + for child in image.get('children'): + disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) + if child.get('datacenter'): + datacenters.append(utils.lookup(child, 'datacenter', 'name')) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + + table.add_row(['id', image['id']]) + table.add_row(['global_identifier', + image.get('globalIdentifier', formatting.blank())]) + table.add_row(['name', image['name'].strip()]) + table.add_row(['status', formatting.FormattedItem( + utils.lookup(image, 'status', 'keyname'), + utils.lookup(image, 'status', 'name'), + )]) + table.add_row(['account', image.get('accountId', formatting.blank())]) + table.add_row(['visibility', + image_mod.PUBLIC_TYPE if image['publicFlag'] + else image_mod.PRIVATE_TYPE]) + table.add_row(['type', + formatting.FormattedItem( + utils.lookup(image, 'imageType', 'keyName'), + utils.lookup(image, 'imageType', 'name'), + )]) + table.add_row(['flex', image.get('flexImageFlag')]) + table.add_row(['note', image.get('note')]) + table.add_row(['created', image.get('createDate')]) + table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) + table.add_row(['datacenters', formatting.listing(sorted(datacenters), + separator=',')]) + + return table diff --git a/SoftLayer/CLI/image/edit.py b/SoftLayer/CLI/image/edit.py new file mode 100644 index 000000000..4f4d68892 --- /dev/null +++ b/SoftLayer/CLI/image/edit.py @@ -0,0 +1,31 @@ +"""Edit details of an image.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--name', help="Name of the image") +@click.option('--note', help="Additional note for the image") +@click.option('--tag', help="Tags for the image") +@environment.pass_env +def cli(env, identifier, name, note, tag): + """Edit details of an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + data = {} + if name: + data['name'] = name + if note: + data['note'] = note + if tag: + data['tag'] = tag + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') + if not image_mgr.edit(image_id, **data): + raise exceptions.CLIAbort("Failed to Edit Image") diff --git a/SoftLayer/CLI/image/list.py b/SoftLayer/CLI/image/list.py new file mode 100644 index 000000000..91901e97c --- /dev/null +++ b/SoftLayer/CLI/image/list.py @@ -0,0 +1,55 @@ +"""List images.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import image as image_mod +from SoftLayer import utils + +import click + + +@click.command() +@click.option('--public/--private', + is_flag=True, + default=None, + help='Display only public or private images') +@environment.pass_env +def cli(env, public): + """List images.""" + + image_mgr = SoftLayer.ImageManager(env.client) + + images = [] + if public in [False, None]: + for image in image_mgr.list_private_images(mask=image_mod.MASK): + images.append(image) + + if public in [True, None]: + for image in image_mgr.list_public_images(mask=image_mod.MASK): + images.append(image) + + table = formatting.Table(['id', + 'account', + 'name', + 'type', + 'visibility', + 'global_identifier']) + + images = [image for image in images if image['parentId'] == ''] + for image in images: + + table.add_row([ + image['id'], + image.get('accountId', formatting.blank()), + image['name'].strip(), + formatting.FormattedItem( + utils.lookup(image, 'imageType', 'keyName'), + utils.lookup(image, 'imageType', 'name')), + image_mod.PUBLIC_TYPE if image['publicFlag'] + else image_mod.PRIVATE_TYPE, + image.get('globalIdentifier', formatting.blank()), + ]) + + return table diff --git a/SoftLayer/CLI/iscsi/__init__.py b/SoftLayer/CLI/iscsi/__init__.py new file mode 100644 index 000000000..2b379049a --- /dev/null +++ b/SoftLayer/CLI/iscsi/__init__.py @@ -0,0 +1 @@ +"""iSCSI storage.""" diff --git a/SoftLayer/CLI/iscsi/cancel.py b/SoftLayer/CLI/iscsi/cancel.py new file mode 100644 index 000000000..ea0e1cd7d --- /dev/null +++ b/SoftLayer/CLI/iscsi/cancel.py @@ -0,0 +1,30 @@ +"""Cancel an existing iSCSI account.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--reason', help="An optional reason for cancellation") +@click.option('--immediate', + is_flag=True, + help="Cancels the iSCSI immediately instead of on the billing " + "anniversary") +@environment.pass_env +def cli(env, identifier, reason, immediate): + """Cancel an existing iSCSI account.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') + + if env.skip_confirmations or formatting.no_going_back(iscsi_id): + iscsi_mgr.cancel_iscsi(iscsi_id, reason, immediate) + else: + raise exceptions.CLIAbort('Aborted') diff --git a/SoftLayer/CLI/iscsi/create.py b/SoftLayer/CLI/iscsi/create.py new file mode 100644 index 000000000..0a8a480d4 --- /dev/null +++ b/SoftLayer/CLI/iscsi/create.py @@ -0,0 +1,23 @@ +"""Creates an iSCSI target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment + +import click + + +@click.command() +@click.option('--size', + type=click.INT, + required=True, + help="Size of the iSCSI volume to create (in gibibytes)") +@click.option('--datacenter', + required=True, + help="Datacenter shortname (sng01, dal05, ...)") +@environment.pass_env +def cli(env, size, datacenter): + """Creates an iSCSI target.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + iscsi_mgr.create_iscsi(size=size, location=datacenter) diff --git a/SoftLayer/CLI/iscsi/detail.py b/SoftLayer/CLI/iscsi/detail.py new file mode 100644 index 000000000..b920b41c5 --- /dev/null +++ b/SoftLayer/CLI/iscsi/detail.py @@ -0,0 +1,54 @@ +"""Get details for an iSCSI target.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--password', + is_flag=True, + help="Show credentials to access the iSCSI target") +@environment.pass_env +def cli(env, identifier, password): + """Get details for an iSCSI target.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, identifier, 'iSCSI') + result = iscsi_mgr.get_iscsi(iscsi_id) + result = utils.NestedDict(result) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + + table.add_row(['id', result['id']]) + table.add_row(['serviceResourceName', result['serviceResourceName']]) + table.add_row(['createDate', result['createDate']]) + table.add_row(['nasType', result['nasType']]) + table.add_row(['capacityGb', result['capacityGb']]) + + if result['snapshotCapacityGb']: + table.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) + + table.add_row(['mountableFlag', result['mountableFlag']]) + table.add_row(['serviceResourceBackendIpAddress', + result['serviceResourceBackendIpAddress']]) + table.add_row(['price', result['billingItem']['recurringFee']]) + table.add_row(['BillingItemId', result['billingItem']['id']]) + if result.get('notes'): + table.add_row(['notes', result['notes']]) + + if password: + pass_table = formatting.Table(['username', 'password']) + pass_table.add_row([result['username'], result['password']]) + table.add_row(['users', pass_table]) + + return table diff --git a/SoftLayer/CLI/iscsi/list.py b/SoftLayer/CLI/iscsi/list.py new file mode 100644 index 000000000..60923ce3f --- /dev/null +++ b/SoftLayer/CLI/iscsi/list.py @@ -0,0 +1,40 @@ +"""List iSCSI targets.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List iSCSI targets.""" + + iscsi_mgr = SoftLayer.ISCSIManager(env.client) + iscsi_list = iscsi_mgr.list_iscsi() + iscsi_list = [utils.NestedDict(n) for n in iscsi_list] + table = formatting.Table([ + 'id', + 'datacenter', + 'size', + 'username', + 'password', + 'server' + ]) + for iscsi in iscsi_list: + table.add_row([ + iscsi['id'], + iscsi['serviceResource']['datacenter'].get('name', + formatting.blank()), + formatting.FormattedItem(iscsi.get('capacityGb', + formatting.blank()), + "%dGB" % iscsi.get('capacityGb', 0)), + iscsi.get('username', formatting.blank()), + iscsi.get('password', formatting.blank()), + iscsi.get('serviceResourceBackendIpAddress', + formatting.blank())]) + return table diff --git a/SoftLayer/CLI/loadbal/__init__.py b/SoftLayer/CLI/loadbal/__init__.py new file mode 100644 index 000000000..8f7becb62 --- /dev/null +++ b/SoftLayer/CLI/loadbal/__init__.py @@ -0,0 +1,12 @@ +"""Load balancers.""" + +from SoftLayer.CLI import exceptions + + +def parse_id(input_id): + """Parse the load balancer kind and actual id from the "kind:id" form.""" + parts = input_id.split(':') + if len(parts) != 2: + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form "kind:id"' % input_id) + return parts[0], int(parts[1]) diff --git a/SoftLayer/CLI/loadbal/cancel.py b/SoftLayer/CLI/loadbal/cancel.py new file mode 100644 index 000000000..6ba2fa0d7 --- /dev/null +++ b/SoftLayer/CLI/loadbal/cancel.py @@ -0,0 +1,29 @@ +"""Cancel an existing load balancer.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel an existing load balancer.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, loadbal_id = loadbal.parse_id(identifier) + + if any([env.skip_confirmations, + formatting.confirm("This action will cancel a load balancer. " + "Continue?")]): + mgr.cancel_lb(loadbal_id) + return 'Load Balancer with id %s is being cancelled!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/loadbal/create.py b/SoftLayer/CLI/loadbal/create.py new file mode 100644 index 000000000..e253d62c7 --- /dev/null +++ b/SoftLayer/CLI/loadbal/create.py @@ -0,0 +1,25 @@ +"""Adds a load balancer given the id returned from create-options.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@click.argument('billing-id') +@click.option('--datacenter', '-d', + help='Datacenter shortname (sng01, dal05, ...)') +@environment.pass_env +def cli(env, billing_id, datacenter): + """Adds a load balancer given the id returned from create-options.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') + mgr.add_local_lb(billing_id, datacenter=datacenter) + return "Load balancer is being created!" diff --git a/SoftLayer/CLI/loadbal/create_options.py b/SoftLayer/CLI/loadbal/create_options.py new file mode 100644 index 000000000..386256fb9 --- /dev/null +++ b/SoftLayer/CLI/loadbal/create_options.py @@ -0,0 +1,35 @@ +"""Show load balancer options.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """Reset connections on a certain service group.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + table = formatting.Table(['price_id', 'capacity', 'description', 'price']) + + table.sortby = 'price' + table.align['price'] = 'r' + table.align['capacity'] = 'r' + table.align['id'] = 'r' + + packages = mgr.get_lb_pkgs() + + for package in packages: + table.add_row([ + package['prices'][0]['id'], + package.get('capacity'), + package['description'], + '%.2f' % float(package['prices'][0]['recurringFee']) + ]) + + return table diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py new file mode 100644 index 000000000..9e3a817ec --- /dev/null +++ b/SoftLayer/CLI/loadbal/detail.py @@ -0,0 +1,85 @@ +"""Get Load balancer details.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get Load balancer details.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, loadbal_id = loadbal.parse_id(identifier) + + load_balancer = mgr.get_local_lb(loadbal_id) + + table = formatting.KeyValueTable(['Name', 'Value']) + table.align['Name'] = 'l' + table.align['Value'] = 'l' + table.add_row(['General properties', '----------']) + table.add_row([' ID', 'local:%s' % load_balancer['id']]) + table.add_row([' IP Address', load_balancer['ipAddress']['ipAddress']]) + name = load_balancer['loadBalancerHardware'][0]['datacenter']['name'] + table.add_row([' Datacenter', name]) + table.add_row([' Connections limit', load_balancer['connectionLimit']]) + table.add_row([' Dedicated', load_balancer['dedicatedFlag']]) + table.add_row([' HA', load_balancer['highAvailabilityFlag']]) + table.add_row([' SSL Enabled', load_balancer['sslEnabledFlag']]) + table.add_row([' SSL Active', load_balancer['sslActiveFlag']]) + index0 = 1 + for virtual_server in load_balancer['virtualServers']: + table.add_row(['Service group %s' % index0, + '**************']) + index0 += 1 + table2 = formatting.Table(['Service group ID', + 'Port', + 'Allocation', + 'Routing type', + 'Routing Method']) + + for group in virtual_server['serviceGroups']: + table2.add_row([ + '%s:%s' % (load_balancer['id'], virtual_server['id']), + virtual_server['port'], + '%s %%' % virtual_server['allocation'], + '%s:%s' % (group['routingTypeId'], + group['routingType']['name']), + '%s:%s' % (group['routingMethodId'], + group['routingMethod']['name']) + ]) + + table.add_row([' Group Properties', table2]) + + table3 = formatting.Table(['Service_ID', + 'IP Address', + 'Port', + 'Health Check', + 'Weight', + 'Enabled', + 'Status']) + service_exist = False + for service in group['services']: + service_exist = True + health_check = service['healthChecks'][0] + table3.add_row([ + '%s:%s' % (load_balancer['id'], service['id']), + service['ipAddress']['ipAddress'], + service['port'], + '%s:%s' % (health_check['healthCheckTypeId'], + health_check['type']['name']), + service['groupReferences'][0]['weight'], + service['enabled'], + service['status'] + ]) + if service_exist: + table.add_row([' Services', table3]) + else: + table.add_row([' Services', 'None']) + return table diff --git a/SoftLayer/CLI/loadbal/group_add.py b/SoftLayer/CLI/loadbal/group_add.py new file mode 100644 index 000000000..76456f2b3 --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_add.py @@ -0,0 +1,41 @@ +"""Adds a new load_balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--allocation', + required=True, + type=click.INT, + help="The allocated percent of connections") +@click.option('--port', + required=True, + help="The port number", + type=click.INT) +@click.option('--routing-type', + required=True, + help="The port routing type") +@click.option('--routing-method', + required=True, + help="The routing method") +@environment.pass_env +def cli(env, identifier, allocation, port, routing_type, routing_method): + """Adds a new load_balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, loadbal_id = loadbal.parse_id(identifier) + + mgr.add_service_group(loadbal_id, + allocation=allocation, + port=port, + routing_type=routing_type, + routing_method=routing_method) + + return 'Load balancer service group is being added!' diff --git a/SoftLayer/CLI/loadbal/group_delete.py b/SoftLayer/CLI/loadbal/group_delete.py new file mode 100644 index 000000000..075dbe899 --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_delete.py @@ -0,0 +1,28 @@ +"""Deletes an existing load balancer service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Deletes an existing load balancer service group.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + _, group_id = loadbal.parse_id(identifier) + + if env.skip_confirmations or formatting.confirm("This action will cancel " + "a service group. " + "Continue?"): + mgr.delete_service_group(group_id) + return 'Service group %s is being deleted!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/loadbal/group_edit.py b/SoftLayer/CLI/loadbal/group_edit.py new file mode 100644 index 000000000..6de786284 --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_edit.py @@ -0,0 +1,41 @@ +"""Edit an existing load balancer service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--allocation', + type=click.INT, + help="Change the allocated percent of connections") +@click.option('--port', + help="Change the port number", + type=click.INT) +@click.option('--routing-type', + help="Change the port routing type") +@click.option('--routing-method', + help="Change the routing method") +@environment.pass_env +def cli(env, identifier, allocation, port, routing_type, routing_method): + """Edit an existing load balancer service group.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, group_id = loadbal.parse_id(identifier) + + # check if any input is provided + if not any([allocation, port, routing_type, routing_method]): + return 'At least one property is required to be changed!' + + mgr.edit_service_group(loadbal_id, + group_id, + allocation=allocation, + port=port, + routing_type=routing_type, + routing_method=routing_method) + + return 'Load balancer service group %s is being updated!' % identifier diff --git a/SoftLayer/CLI/loadbal/group_reset.py b/SoftLayer/CLI/loadbal/group_reset.py new file mode 100644 index 000000000..78b227d4e --- /dev/null +++ b/SoftLayer/CLI/loadbal/group_reset.py @@ -0,0 +1,21 @@ +"""Reset connections on a certain service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Reset connections on a certain service group.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, group_id = loadbal.parse_id(identifier) + + mgr.reset_service_group(loadbal_id, group_id) + return 'Load balancer service group connections are being reset!' diff --git a/SoftLayer/CLI/loadbal/health_checks.py b/SoftLayer/CLI/loadbal/health_checks.py new file mode 100644 index 000000000..7f73e0b3a --- /dev/null +++ b/SoftLayer/CLI/loadbal/health_checks.py @@ -0,0 +1,26 @@ +"""List health check types.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List health check types.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + hc_types = mgr.get_hc_types() + table = formatting.KeyValueTable(['ID', 'Name']) + table.align['ID'] = 'l' + table.align['Name'] = 'l' + table.sortby = 'ID' + for hc_type in hc_types: + table.add_row([hc_type['id'], hc_type['name']]) + + return table diff --git a/SoftLayer/CLI/loadbal/list.py b/SoftLayer/CLI/loadbal/list.py new file mode 100644 index 000000000..a462ef9b1 --- /dev/null +++ b/SoftLayer/CLI/loadbal/list.py @@ -0,0 +1,49 @@ +"""List active load balancers.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List active load balancers.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + load_balancers = mgr.get_local_lbs() + + table = formatting.Table(['ID', + 'VIP Address', + 'Location', + 'SSL Offload', + 'Connections/second', + 'Type']) + + table.align['Connections/second'] = 'r' + + for load_balancer in load_balancers: + ssl_support = 'Not Supported' + if load_balancer['sslEnabledFlag']: + if load_balancer['sslActiveFlag']: + ssl_support = 'On' + else: + ssl_support = 'Off' + lb_type = 'Standard' + if load_balancer['dedicatedFlag']: + lb_type = 'Dedicated' + elif load_balancer['highAvailabilityFlag']: + lb_type = 'HA' + table.add_row([ + 'local:%s' % load_balancer['id'], + load_balancer['ipAddress']['ipAddress'], + load_balancer['loadBalancerHardware'][0]['datacenter']['name'], + ssl_support, + load_balancer['connectionLimit'], + lb_type + ]) + + return table diff --git a/SoftLayer/CLI/loadbal/routing_methods.py b/SoftLayer/CLI/loadbal/routing_methods.py new file mode 100644 index 000000000..4549e5ece --- /dev/null +++ b/SoftLayer/CLI/loadbal/routing_methods.py @@ -0,0 +1,25 @@ +"""List routing methods.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List routing types.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + routing_methods = mgr.get_routing_methods() + table = formatting.KeyValueTable(['ID', 'Name']) + table.align['ID'] = 'l' + table.align['Name'] = 'l' + table.sortby = 'ID' + for routing_method in routing_methods: + table.add_row([routing_method['id'], routing_method['name']]) + + return table diff --git a/SoftLayer/CLI/loadbal/routing_types.py b/SoftLayer/CLI/loadbal/routing_types.py new file mode 100644 index 000000000..b3b76060c --- /dev/null +++ b/SoftLayer/CLI/loadbal/routing_types.py @@ -0,0 +1,24 @@ +"""List routing types.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +import click + + +@click.command() +@environment.pass_env +def cli(env): + """List routing types.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + routing_methods = mgr.get_routing_methods() + table = formatting.KeyValueTable(['ID', 'Name']) + table.align['ID'] = 'l' + table.align['Name'] = 'l' + table.sortby = 'ID' + for routing_method in routing_methods: + table.add_row([routing_method['id'], routing_method['name']]) + return table diff --git a/SoftLayer/CLI/loadbal/service_add.py b/SoftLayer/CLI/loadbal/service_add.py new file mode 100644 index 000000000..5a9fa982a --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_add.py @@ -0,0 +1,52 @@ +"""Adds a new load balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--enabled / --disabled', + required=True, + help="Create the service as enable or disabled") +@click.option('--port', + required=True, + help="The port number for the service", + type=click.INT) +@click.option('--weight', + required=True, + type=click.INT, + help="The weight of the service") +@click.option('--healthcheck-type', + required=True, + help="The health check type") +@click.option('--ip-address', '--ip', + required=True, + help="The IP of the service") +@environment.pass_env +def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): + """Adds a new load balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, group_id = loadbal.parse_id(identifier) + + # check if the IP is valid + ip_address_id = None + if ip_address: + ip_service = env.client['Network_Subnet_IpAddress'] + ip_record = ip_service.getByIpAddress(ip_address) + ip_address_id = ip_record['id'] + + mgr.add_service(loadbal_id, + group_id, + ip_address_id=ip_address_id, + enabled=enabled, + port=port, + weight=weight, + hc_type=healthcheck_type) + return 'Load balancer service is being added!' diff --git a/SoftLayer/CLI/loadbal/service_delete.py b/SoftLayer/CLI/loadbal/service_delete.py new file mode 100644 index 000000000..8b4d43f7b --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_delete.py @@ -0,0 +1,28 @@ +"""Deletes an existing load balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Deletes an existing load balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + _, service_id = loadbal.parse_id(identifier) + + if env.skip_confirmations or formatting.confirm("This action will cancel " + "a service from your load " + "balancer. Continue?"): + mgr.delete_service(service_id) + return 'Load balancer service %s is being cancelled!' % service_id + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/loadbal/service_edit.py b/SoftLayer/CLI/loadbal/service_edit.py new file mode 100644 index 000000000..0ae024edc --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_edit.py @@ -0,0 +1,49 @@ +"""Edit the properties of a service group.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@click.option('--enabled / --disabled', + default=None, + help="Enable or disable the service") +@click.option('--port', + help="Change the port number for the service", type=click.INT) +@click.option('--weight', + type=click.INT, + help="Change the weight of the service") +@click.option('--healthcheck-type', help="Change the health check type") +@click.option('--ip-address', '--ip', help="Change the IP of the service") +@environment.pass_env +def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): + """Edit the properties of a service group.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + + loadbal_id, service_id = loadbal.parse_id(identifier) + + # check if any input is provided + if not any([ip_address, enabled, weight, port, healthcheck_type]): + return 'At least one property is required to be changed!' + + # check if the IP is valid + ip_address_id = None + if ip_address: + ip_service = env.client['Network_Subnet_IpAddress'] + ip_record = ip_service.getByIpAddress(ip_address) + ip_address_id = ip_record['id'] + + mgr.edit_service(loadbal_id, + service_id, + ip_address_id=ip_address_id, + enabled=enabled, + port=port, + weight=weight, + hc_type=healthcheck_type) + return 'Load balancer service %s is being modified!' % identifier diff --git a/SoftLayer/CLI/loadbal/service_toggle.py b/SoftLayer/CLI/loadbal/service_toggle.py new file mode 100644 index 000000000..7f4090e75 --- /dev/null +++ b/SoftLayer/CLI/loadbal/service_toggle.py @@ -0,0 +1,28 @@ +"""Toggle the status of an existing load balancer service.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import loadbal + +import click + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Toggle the status of an existing load balancer service.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + _, service_id = loadbal.parse_id(identifier) + + if env.skip_confirmations or formatting.confirm("This action will toggle " + "the status on the " + "service. Continue?"): + mgr.toggle_service_status(service_id) + return 'Load balancer service %s status updated!' % identifier + else: + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/metadata.py b/SoftLayer/CLI/metadata.py new file mode 100644 index 000000000..150ea3c6e --- /dev/null +++ b/SoftLayer/CLI/metadata.py @@ -0,0 +1,67 @@ +"""Find details about this machine.""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting + +import click + +META_MAPPING = { + 'backend_ip': 'primary_backend_ip', + 'ip': 'primary_ip', +} + + +@click.command(epilog="These commands only work on devices on the backend " + "SoftLayer network. This allows for self-discovery for " + "newly provisioned resources.") +@click.argument('prop', type=click.Choice(['backend_ip', + 'backend_mac', + 'datacenter', + 'datacenter_id', + 'fqdn', + 'frontend_mac', + 'id', + 'ip', + 'network', + 'provision_state', + 'tags', + 'user_data'])) +def cli(prop): + """Find details about this machine.""" + + try: + if prop == 'network': + return get_network() + + meta_prop = META_MAPPING.get(prop) or prop + return SoftLayer.MetadataManager().get(meta_prop) + except SoftLayer.TransportError: + raise exceptions.CLIAbort( + 'Cannot connect to the backend service address. Make sure ' + 'this command is being ran from a device on the backend ' + 'network.') + + +def get_network(): + """Returns a list of tables with public and private network details.""" + meta = SoftLayer.MetadataManager() + network_tables = [] + for network_func in [meta.public_network, meta.private_network]: + network = network_func() + + table = formatting.KeyValueTable(['name', 'value']) + table.align['Name'] = 'r' + table.align['Value'] = 'l' + table.add_row(['mac addresses', + formatting.listing(network['mac_addresses'], + separator=',')]) + table.add_row(['router', network['router']]) + table.add_row(['vlans', + formatting.listing(network['vlans'], separator=',')]) + table.add_row(['vlan ids', + formatting.listing(network['vlan_ids'], separator=',')]) + network_tables.append(table) + + return network_tables diff --git a/SoftLayer/CLI/modules/__init__.py b/SoftLayer/CLI/modules/__init__.py deleted file mode 100644 index eaad02f96..000000000 --- a/SoftLayer/CLI/modules/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" - SoftLayer.CLI.modules - ~~~~~~~~~~~~~~~~~~~~~ - Contains all plugable modules for the CLI interface - - :license: MIT, see LICENSE for more details. -""" - -import pkgutil - - -def get_module_list(): - """Returns each module under SoftLayer.CLI.modules.""" - actions = [action[1] for action in pkgutil.iter_modules(__path__)] - return actions diff --git a/SoftLayer/CLI/modules/cdn.py b/SoftLayer/CLI/modules/cdn.py deleted file mode 100644 index 11f0e8b47..000000000 --- a/SoftLayer/CLI/modules/cdn.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -usage: sl cdn [] [...] [options] - -Manage CDN accounts and configuration - -The available commands are: - detail Show details for a CDN account - list List CDN accounts - load Cache one or more files on all edge nodes - origin-add Add an origin pull mapping - origin-list Show origin pull mappings on a CDN account - origin-remove Remove an origin pull mapping - purge Purge one or more cached files from all edge nodes -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -class ListAccounts(environment.CLIRunnable): - """ -usage: sl cdn list [options] - -List all CDN accounts - -Options: - --sortby=SORTBY Sort by this value. [Default: id] - [Options: id, account_name, type, created, notes] -""" - action = 'list' - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - accounts = manager.list_accounts() - - table = formatting.Table(['id', - 'account_name', - 'type', - 'created', - 'notes']) - for account in accounts: - table.add_row([ - account['id'], - account['cdnAccountName'], - account['cdnSolutionName'], - account['createDate'], - account.get('cdnAccountNote', formatting.blank()) - ]) - - table.sortby = args['--sortby'] - return table - - -class DetailAccount(environment.CLIRunnable): - """ -usage: sl cdn detail [options] - -Show CDN account details -""" - action = 'detail' - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - account = manager.get_account(args.get('')) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - table.add_row(['id', account['id']]) - table.add_row(['account_name', account['cdnAccountName']]) - table.add_row(['type', account['cdnSolutionName']]) - table.add_row(['status', account['status']['name']]) - table.add_row(['created', account['createDate']]) - table.add_row(['notes', - account.get('cdnAccountNote', formatting.blank())]) - - return table - - -class LoadContent(environment.CLIRunnable): - """ -usage: sl cdn load ... [options] - -Cache one or more files on all edge nodes - -Required: - account The CDN account ID to cache content in - content_url The CDN URL(s) or CDN CNAME-based URL(s) for the content - you wish to cache (can be repeated) -""" - action = 'load' - required_params = ['account', 'content_url'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - manager.load_content(args.get(''), args.get('')) - - -class PurgeContent(environment.CLIRunnable): - """ -usage: sl cdn purge ... [options] - -Purge one or more cached files from all edge nodes - -Required: - account The CDN account ID to purge content from - content_url The CDN URL(s) or CDN CNAME-based URL(s) for the content - you wish to cache (can be repeated) -""" - action = 'purge' - required_params = ['account', 'content_url'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - manager.purge_content(args.get(''), - args.get('')) - - -class ListOrigins(environment.CLIRunnable): - """ -usage: sl cdn origin-list [options] - -List origin pull mappings associated with a CDN account. -""" - action = 'origin-list' - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - origins = manager.get_origins(args.get('')) - - table = formatting.Table(['id', 'media_type', 'cname', 'origin_url']) - - for origin in origins: - table.add_row([origin['id'], - origin['mediaType'], - origin.get('cname', formatting.blank()), - origin['originUrl']]) - - return table - - -class AddOrigin(environment.CLIRunnable): - """ -usage: sl cdn origin-add [options] - -Create an origin pull mapping on a CDN account - -Required: - account The CDN account ID to create a mapping on - url A full URL where content should be pulled from by - CDN edge nodes - -Options: - --type=TYPE The media type for this mapping (http, flash, wm, ...) - (default: http) - --cname=CNAME An optional CNAME to attach to the mapping -""" - action = 'origin-add' - required_params = ['account', 'url'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - media_type = args.get('--type') or 'http' - - manager.add_origin(args.get(''), media_type, - args.get(''), args.get('--cname', None)) - - -class RemoveOrigin(environment.CLIRunnable): - """ -usage: sl cdn origin-remove [options] - -Remove an origin pull mapping from a CDN account - -Required: - account The CDN account ID to remove a mapping from - origin_id The origin mapping ID to remove -""" - action = 'origin-remove' - required_params = ['account', 'origin_id'] - - def execute(self, args): - manager = SoftLayer.CDNManager(self.client) - manager.remove_origin(args.get(''), - args.get('')) diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py deleted file mode 100644 index 3f84e1052..000000000 --- a/SoftLayer/CLI/modules/config.py +++ /dev/null @@ -1,199 +0,0 @@ -""" -usage: sl config [] [...] [options] - -View and edit configuration - -The available commands are: - setup Setup configuration - show Show current configuration -""" -# :license: MIT, see LICENSE for more details. - -import os.path - -import SoftLayer -from SoftLayer import auth -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer import utils - - -def get_settings_from_client(client): - """ Pull out settings from a SoftLayer.Client instance. - - :param client: SoftLayer.Client instance - """ - settings = { - 'username': '', - 'api_key': '', - 'timeout': client.timeout or '', - 'endpoint_url': client.endpoint_url, - } - try: - settings['username'] = client.auth.username - settings['api_key'] = client.auth.api_key - except AttributeError: - pass - - return settings - - -def config_table(settings): - """ Returns a config table """ - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - table.add_row(['Username', settings['username'] or 'not set']) - table.add_row(['API Key', settings['api_key'] or 'not set']) - table.add_row(['Endpoint URL', settings['endpoint_url'] or 'not set']) - table.add_row(['Timeout', settings['timeout'] or 'not set']) - return table - - -def get_api_key(client, username, secret, endpoint_url=None): - """ Attempts API-Key and password auth to get an API key - - This will also generate an API key if one doesn't exist - """ - - client.endpoint_url = endpoint_url - client.auth = None - # Try to use a client with username/api key - if len(secret) == 64: - try: - client.auth = auth.BasicAuthentication(username, secret) - client['Account'].getCurrentUser() - return secret - except SoftLayer.SoftLayerAPIError as ex: - if 'invalid api token' not in ex.faultString.lower(): - raise - else: - # Try to use a client with username/password - client.authenticate_with_password(username, secret) - - user_record = client['Account'].getCurrentUser( - mask='id, apiAuthenticationKeys') - api_keys = user_record['apiAuthenticationKeys'] - if len(api_keys) == 0: - return client['User_Customer'].addApiAuthenticationKey( - id=user_record['id']) - return api_keys[0]['authenticationKey'] - - -class Setup(environment.CLIRunnable): - """ -usage: sl config setup [options] - -Setup configuration -""" - action = 'setup' - - def execute(self, args): - username, secret, endpoint_url, timeout = self.get_user_input() - - api_key = get_api_key(self.client, username, secret, - endpoint_url=endpoint_url) - - path = '~/.softlayer' - if args.get('--config'): - path = args.get('--config') - config_path = os.path.expanduser(path) - - self.env.out( - formatting.format_output(config_table({ - 'username': username, - 'api_key': api_key, - 'endpoint_url': endpoint_url, - 'timeout': timeout}))) - - if not formatting.confirm('Are you sure you want to write settings ' - 'to "%s"?' % config_path, default=True): - raise exceptions.CLIAbort('Aborted.') - - # Persist the config file. Read the target config file in before - # setting the values to avoid clobbering settings - config = utils.configparser.RawConfigParser() - config.read(config_path) - try: - config.add_section('softlayer') - except utils.configparser.DuplicateSectionError: - pass - - config.set('softlayer', 'username', username) - config.set('softlayer', 'api_key', api_key) - config.set('softlayer', 'endpoint_url', endpoint_url) - - config_file = os.fdopen(os.open(config_path, - (os.O_WRONLY | os.O_CREAT), - 0o600), - 'w') - try: - config.write(config_file) - finally: - config_file.close() - - return "Configuration Updated Successfully" - - def get_user_input(self): - """ Ask for username, secret (api_key or password) and endpoint_url """ - - defaults = get_settings_from_client(self.client) - timeout = defaults['timeout'] - - # Ask for username - for _ in range(3): - username = (self.env.input('Username [%s]: ' - % defaults['username']) - or defaults['username']) - if username: - break - else: - raise exceptions.CLIAbort('Aborted after 3 attempts') - - # Ask for 'secret' which can be api_key or their password - for _ in range(3): - secret = (self.env.getpass('API Key or Password [%s]: ' - % defaults['api_key']) - or defaults['api_key']) - if secret: - break - else: - raise exceptions.CLIAbort('Aborted after 3 attempts') - - # Ask for which endpoint they want to use - for _ in range(3): - endpoint_type = self.env.input( - 'Endpoint (public|private|custom): ') - endpoint_type = endpoint_type.lower() - if not endpoint_type: - endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT - break - if endpoint_type == 'public': - endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT - break - elif endpoint_type == 'private': - endpoint_url = SoftLayer.API_PRIVATE_ENDPOINT - break - elif endpoint_type == 'custom': - endpoint_url = self.env.input( - 'Endpoint URL [%s]: ' % defaults['endpoint_url'] - ) or defaults['endpoint_url'] - break - else: - raise exceptions.CLIAbort('Aborted after 3 attempts') - - return username, secret, endpoint_url, timeout - - -class Show(environment.CLIRunnable): - """ -usage: sl config show [options] - -Show current configuration -""" - action = 'show' - - def execute(self, args): - settings = get_settings_from_client(self.client) - return config_table(settings) diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py deleted file mode 100755 index 90a995b87..000000000 --- a/SoftLayer/CLI/modules/dns.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -usage: sl dns [] [...] [options] - -Manage DNS - -The available zone commands are: - create Create zone - delete Delete zone - list List zones or a zone's records - print Print zone in BIND format - -The available record commands are: - add Add resource record - edit Update resource records (bulk/single) - remove Remove resource records -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - -import re - -RECORD_REGEX = re.compile(r"""^((?P([\w-]+(\.)?)*|\@)?\s+ - (?P\d+)?\s+ - (?P\w+)?)?\s+ - (?P\w+)\s+ - (?P.*)""", re.X) - - -class DumpZone(environment.CLIRunnable): - """ -usage: sl dns print [options] - -print zone in BIND format - -Arguments: - Zone name (softlayer.com) -""" - action = "print" - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - return manager.dump_zone(zone_id) - - -class CreateZone(environment.CLIRunnable): - """ -usage: sl dns create [options] - -Create a zone - -Arguments: - Zone name (softlayer.com) -""" - action = 'create' - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - manager.create_zone(args['']) - - -class DeleteZone(environment.CLIRunnable): - """ -usage: sl dns delete [options] - -Delete zone - -Arguments: - Zone name (softlayer.com) -""" - action = 'delete' - options = ['confirm'] - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - if args['--really'] or formatting.no_going_back(args['']): - manager.delete_zone(zone_id) - else: - raise exceptions.CLIAbort("Aborted.") - - -class ImportZone(environment.CLIRunnable): - """ -usage: sl dns import [options] - -Creates a new zone based off a nicely BIND formatted file - -Arguments: - Path to the bind zone file you want to import -Options: - --dry-run Don't actually do anything. This will show you what is parsed - - """ - action = 'import' - - def execute(self, args): - - dry_run = args.get('--dry-run') - - manager = SoftLayer.DNSManager(self.client) - with open(args['']) as zone_file: - zone_contents = zone_file.read() - - zone, records, bad_lines = parse_zone_details(zone_contents) - - self.env.out("Parsed: zone=%s" % zone) - for record in records: - self.env.out("Parsed: %s" % record) - for line in bad_lines: - self.env.out("Unparsed: %s" % line) - - if dry_run: - return - - # Find zone id or create the zone if it doesn't exist - try: - zone_id = helpers.resolve_id(manager.resolve_ids, zone, - name='zone') - except exceptions.CLIAbort: - zone_id = manager.create_zone(zone)['id'] - self.env.out("\033[92mCREATED ZONE: %s\033[0m" % zone) - - # Attempt to create each record - for record in records: - try: - manager.create_record(zone_id, - record['record'], - record['record_type'], - record['data'], - record['ttl']) - self.env.out("\033[92mCreated: Host: %s\033[0m" % record) - except SoftLayer.SoftLayerAPIError as ex: - self.env.out("\033[91mFAILED: %s" % record) - self.env.out("%s \033[0m" % ex) - - return "Finished" - - -def parse_zone_details(zone_contents): - """Parses a zone file into python data-structures""" - records = [] - bad_lines = [] - zone_lines = [line.strip() for line in zone_contents.split('\n')] - - zone_search = re.search(r'^\$ORIGIN (?P.*)\.', zone_lines[0]) - zone = zone_search.group('zone') - - for line in zone_lines[1:]: - record_search = re.search(RECORD_REGEX, line) - if record_search is None: - bad_lines.append(line) - continue - - name = record_search.group('domain') - # The API requires we send a host, although bind allows a blank - # entry. @ is the same thing as blank - if name is None: - name = "@" - - ttl = record_search.group('ttl') - # we don't do anything with the class - # domain_class = domainSearch.group('class') - record_type = record_search.group('type').upper() - data = record_search.group('data') - - # the dns class doesn't support weighted MX records yet, so we chomp - # that part out. - if record_type == "MX": - record_search = re.search(r'(?P\d+)\s+(?P.*)', data) - data = record_search.group('data') - - # This will skip the SOA record bit. And any domain that gets - # parsed oddly. - if record_type == 'IN': - bad_lines.append(line) - continue - - records.append({ - 'record': name, - 'record_type': record_type, - 'data': data, - 'ttl': ttl, - }) - - return zone, records, bad_lines - - -class ListZones(environment.CLIRunnable): - """ -usage: sl dns list [] [options] - -List zones and optionally, records - -Filters: - --data=DATA Record data, such as an IP address - --record=HOST Host record, such as www - --ttl=TTL TTL value in seconds, such as 86400 - --type=TYPE Record type, such as A or CNAME -""" - action = 'list' - - def execute(self, args): - if args['']: - return self.list_zone(args) - - return self.list_all_zones() - - def list_zone(self, args): - """ list records for a particular zone """ - manager = SoftLayer.DNSManager(self.client) - table = formatting.Table(['id', 'record', 'type', 'ttl', 'value']) - - table.align['ttl'] = 'l' - table.align['record'] = 'r' - table.align['value'] = 'l' - - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - records = manager.get_records( - zone_id, - record_type=args.get('--type'), - host=args.get('--record'), - ttl=args.get('--ttl'), - data=args.get('--data'), - ) - - for record in records: - table.add_row([ - record['id'], - record['host'], - record['type'].upper(), - record['ttl'], - record['data'] - ]) - - return table - - def list_all_zones(self): - """ List all zones """ - manager = SoftLayer.DNSManager(self.client) - zones = manager.list_zones() - table = formatting.Table(['id', 'zone', 'serial', 'updated']) - table.align['serial'] = 'c' - table.align['updated'] = 'c' - - for zone in zones: - table.add_row([ - zone['id'], - zone['name'], - zone['serial'], - zone['updateDate'], - ]) - - return table - - -class AddRecord(environment.CLIRunnable): - """ -usage: sl dns add [--ttl=TTL] [options] - -Add resource record - -Arguments: - Zone name (softlayer.com) - Resource record (www) - Record type. [Options: A, AAAA, - CNAME, MX, NS, PTR, SPF, SRV, TXT] - Record data. NOTE: only minor validation is done - -Options: - --ttl=TTL Time to live -""" - action = 'add' - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - manager.create_record( - zone_id, - args[''], - args[''], - args[''], - ttl=args['--ttl'] or 7200) - - -class EditRecord(environment.CLIRunnable): - """ -usage: sl dns edit [--data=DATA] [--ttl=TTL] [--id=ID] - [options] - -Update resource records (bulk/single) - -Arguments: - Zone name (softlayer.com) - Resource record (www) - -Options: - --data=DATA - --id=ID Modify only the given ID - --ttl=TTL Time to live -""" - action = 'edit' - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - results = manager.get_records( - zone_id, - host=args['']) - - for result in results: - if args['--id'] and str(result['id']) != args['--id']: - continue - result['data'] = args['--data'] or result['data'] - result['ttl'] = args['--ttl'] or result['ttl'] - manager.edit_record(result) - - -class RecordRemove(environment.CLIRunnable): - """ -usage: sl dns remove [--id=ID] [options] - -Remove resource records - -Arguments: - Zone name (softlayer.com) - Resource record (www) - -Options: - --id=ID Remove only the given ID -""" - action = 'remove' - options = ['confirm'] - - def execute(self, args): - manager = SoftLayer.DNSManager(self.client) - zone_id = helpers.resolve_id(manager.resolve_ids, args[''], - name='zone') - - if args['--id']: - records = [{'id': args['--id']}] - else: - records = manager.get_records( - zone_id, - host=args['']) - - if args['--really'] or formatting.no_going_back('yes'): - table = formatting.Table(['record']) - for result in records: - manager.delete_record(result['id']) - table.add_row([result['id']]) - - return table - raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/modules/filters.py b/SoftLayer/CLI/modules/filters.py deleted file mode 100644 index ae8073a6d..000000000 --- a/SoftLayer/CLI/modules/filters.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -usage: sl help filters - -Filters are used to limit the amount of results. Some commands will accept a -filter operation for certain fields. Filters can be applied across multiple -fields in most cases. - -Available Operations: - Case Insensitive - 'value' Exact value match - 'value*' Begins with value - '*value' Ends with value - '*value*' Contains value - - Case Sensitive - '~ value' Exact value match - '> value' Greater than value - '< value' Less than value - '>= value' Greater than or equal to value - '<= value' Less than or equal to value - -Examples: - sl server list --datacenter=dal05 - sl server list --hostname='prod*' - sl vs list --network=100 --cpu=2 - sl vs list --network='< 100' --cpu=2 - sl vs list --memory='>= 2048' - -Note: Comparison operators (>, <, >=, <=) can be used with integers, floats, - and strings. -""" -# :license: MIT, see LICENSE for more details. diff --git a/SoftLayer/CLI/modules/firewall.py b/SoftLayer/CLI/modules/firewall.py deleted file mode 100755 index 489cf829a..000000000 --- a/SoftLayer/CLI/modules/firewall.py +++ /dev/null @@ -1,424 +0,0 @@ -""" -usage: sl firewall [] [...] [options] - -Firewall rule and security management - -The available commands are: - add Add a new firewall - cancel Cancel an existing firewall - detail Provide details about a particular firewall - edit Edit the rules of a particular firewall - list List active firewalls - both dedicated and shared - -""" -# :license: MIT, see LICENSE for more details. - -from __future__ import print_function -import os -import subprocess -import tempfile - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - -DELIMITER = "=========================================\n" - - -def get_ids(input_id): - """ Helper package to retrieve the actual IDs - :param input_id: the ID provided by the user - :returns: A list of valid IDs - """ - key_value = input_id.split(':') - - if len(key_value) != 2: - raise exceptions.CLIAbort( - 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) - return key_value - - -def print_package_info(package): - """ Helper package to print the firewall price. - - :param dict package: A dictionary representing the firewall package - """ - print("******************") - print("Product: %s" % package[0]['description']) - print("Price: %s$ monthly" % package[0]['prices'][0]['recurringFee']) - print("******************") - return - - -def has_firewall_component(server): - """ Helper to determine whether or not a server has a firewall. - - :param dict server: A dictionary representing a server - :returns: True if the Server has a firewall. - """ - if server['status'] != 'no_edit': - return True - - return False - - -def get_rules_table(rules): - """ Helper to format the rules into a table - - :param list rules: A list containing the rules of the firewall - :returns: a formatted table of the firewall rules - """ - table = formatting.Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', - 'dest', 'dest_mask']) - table.sortby = '#' - for rule in rules: - table.add_row([ - rule['orderValue'], - rule['action'], - rule['protocol'], - rule['sourceIpAddress'], - rule['sourceIpSubnetMask'], - '%s:%s-%s' % (rule['destinationIpAddress'], - rule['destinationPortRangeStart'], - rule['destinationPortRangeEnd']), - rule['destinationIpSubnetMask']]) - return table - - -def get_formatted_rule(rule=None): - """ Helper to format the rule into a user friendly format - for editing purposes - - :param dict rule: A dict containing one rule of the firewall - :returns: a formatted string that get be pushed into the editor - """ - rule = rule or {} - return ('action: %s\n' - 'protocol: %s\n' - 'source_ip_address: %s\n' - 'source_ip_subnet_mask: %s\n' - 'destination_ip_address: %s\n' - 'destination_ip_subnet_mask: %s\n' - 'destination_port_range_start: %s\n' - 'destination_port_range_end: %s\n' - 'version: %s\n' - % (rule.get('action', 'permit'), - rule.get('protocol', 'tcp'), - rule.get('sourceIpAddress', 'any'), - rule.get('sourceIpSubnetMask', '255.255.255.255'), - rule.get('destinationIpAddress', 'any'), - rule.get('destinationIpSubnetMask', '255.255.255.255'), - rule.get('destinationPortRangeStart', 1), - rule.get('destinationPortRangeEnd', 1), - rule.get('version', 4))) - - -def open_editor(rules=None, content=None): - """ Helper to open an editor for editing the firewall rules - This method takes two parameters, if content is provided, - that means that submitting the rules failed and we are allowing - the user to re-edit what they provided. - If content is not provided, the rules retrieved from the firewall - will be displayed to the user. - - :param list rules: A list containing the rules of the firewall - :param string content: the content that the user provided in the editor - :returns: a formatted string that get be pushed into the editor - """ - - # Let's get the default EDITOR of the environment, - # use nano if none is specified - editor = os.environ.get('EDITOR', 'nano') - - with tempfile.NamedTemporaryFile(suffix=".tmp") as tfile: - - if content: - # if content is provided, just display it as is - tfile.write(content) - tfile.flush() - subprocess.call([editor, tfile.name]) - tfile.seek(0) - data = tfile.read() - return data - - if not rules: - # if the firewall has no rules, provide a template - tfile.write(DELIMITER) - tfile.write(get_formatted_rule()) - else: - # if the firewall has rules, display those to the user - for rule in rules: - tfile.write(DELIMITER) - tfile.write(get_formatted_rule(rule)) - tfile.write(DELIMITER) - tfile.flush() - subprocess.call([editor, tfile.name]) - tfile.seek(0) - data = tfile.read() - return data - - return - - -def parse_rules(content=None): - """ Helper to parse the input from the user into a list of rules. - - :param string content: the content of the editor - :returns: a list of rules - """ - rules = content.split(DELIMITER) - parsed_rules = list() - order = 1 - for rule in rules: - if rule.strip() == '': - continue - parsed_rule = {} - lines = rule.split("\n") - parsed_rule['orderValue'] = order - order += 1 - for line in lines: - if line.strip() == '': - continue - key_value = line.strip().split(':') - key = key_value[0].strip() - value = key_value[1].strip() - if key == 'action': - parsed_rule['action'] = value - elif key == 'protocol': - parsed_rule['protocol'] = value - elif key == 'source_ip_address': - parsed_rule['sourceIpAddress'] = value - elif key == 'source_ip_subnet_mask': - parsed_rule['sourceIpSubnetMask'] = value - elif key == 'destination_ip_address': - parsed_rule['destinationIpAddress'] = value - elif key == 'destination_ip_subnet_mask': - parsed_rule['destinationIpSubnetMask'] = value - elif key == 'destination_port_range_start': - parsed_rule['destinationPortRangeStart'] = int(value) - elif key == 'destination_port_range_end': - parsed_rule['destinationPortRangeEnd'] = int(value) - elif key == 'version': - parsed_rule['version'] = int(value) - parsed_rules.append(parsed_rule) - return parsed_rules - - -class FWList(environment.CLIRunnable): - """ -usage: sl firewall list [options] - -List active firewalls -""" - action = 'list' - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - table = formatting.Table(['firewall id', - 'type', - 'features', - 'server/vlan id']) - fwvlans = mgr.get_firewalls() - dedicated_firewalls = [firewall for firewall in fwvlans - if firewall['dedicatedFirewallFlag']] - - for vlan in dedicated_firewalls: - features = [] - if vlan['highAvailabilityFirewallFlag']: - features.append('HA') - - if features: - feature_list = formatting.listing(features, separator=',') - else: - feature_list = formatting.blank() - - table.add_row([ - 'vlan:%s' % vlan['networkVlanFirewall']['id'], - 'VLAN - dedicated', - feature_list, - vlan['id'] - ]) - - shared_vlan = [firewall for firewall in fwvlans - if not firewall['dedicatedFirewallFlag']] - for vlan in shared_vlan: - vs_firewalls = [guest - for guest in vlan['firewallGuestNetworkComponents'] - if has_firewall_component(guest)] - - for firewall in vs_firewalls: - table.add_row([ - 'cci:%s' % firewall['id'], - 'CCI - standard', - '-', - firewall['guestNetworkComponent']['guest']['id'] - ]) - - server_firewalls = [server - for server in vlan['firewallNetworkComponents'] - if has_firewall_component(server)] - - for firewall in server_firewalls: - table.add_row([ - 'server:%s' % firewall['id'], - 'Server - standard', - '-', - utils.lookup(firewall, - 'networkComponent', - 'downlinkComponent', - 'hardwareId') - ]) - - return table - - -class FWCancel(environment.CLIRunnable): - """ -usage: sl firewall cancel [options] - -Cancels a firewall - -Options: - --really Whether to skip the confirmation prompt - -""" - action = 'cancel' - options = ['really'] - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = args.get('') - key_value = get_ids(input_id) - firewall_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "firewall from your account." - " Continue?"): - if key_value[0] in ['cci', 'server']: - mgr.cancel_firewall(firewall_id, dedicated=False) - elif key_value[0] == 'vlan': - mgr.cancel_firewall(firewall_id, dedicated=True) - return 'Firewall with id %s is being cancelled!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class FWAdd(environment.CLIRunnable): - """ -usage: sl firewall add (--cci | --vlan | --server) [options] - -Adds a firewall of type either standard (cci or server) or dedicated(vlan) -Options: - --cci creates a standard firewall for a CCI - --vlan creates a dedicated firewall for a VLAN - --server creates a standard firewall for a server - --ha whether HA will be on or off - only for dedicated - --really whether to skip the confirmation prompt -""" - action = 'add' - options = ['really', 'ha'] - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = helpers.resolve_id( - mgr.resolve_ids, args.get(''), 'firewall') - ha_support = args.get('--ha', False) - if not args['--really']: - if args['--vlan']: - pkg = mgr.get_dedicated_package(ha_enabled=ha_support) - elif args['--cci']: - pkg = mgr.get_standard_package(input_id) - elif args['--server']: - pkg = mgr.get_standard_package(input_id, is_cci=False) - - if not pkg: - return "Unable to add firewall - Is network public enabled?" - print_package_info(pkg) - - if not formatting.confirm("This action will incur charges on your " - "account. Continue?"): - raise exceptions.CLIAbort('Aborted.') - - if args['--vlan']: - mgr.add_vlan_firewall(input_id, ha_enabled=ha_support) - elif args['--cci']: - mgr.add_standard_firewall(input_id, is_cci=True) - elif args['--server']: - mgr.add_standard_firewall(input_id, is_cci=False) - - return "Firewall is being created!" - - -class FWDetails(environment.CLIRunnable): - """ -usage: sl firewall detail [options] - -Get firewall details -""" - action = 'detail' - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - if key_value[0] == 'vlan': - rules = mgr.get_dedicated_fwl_rules(key_value[1]) - else: - rules = mgr.get_standard_fwl_rules(key_value[1]) - - return get_rules_table(rules) - - -class FWEdit(environment.CLIRunnable): - """ -usage: sl firewall edit [options] - -Edit the rules for a firewall -""" - action = 'edit' - - def execute(self, args): - mgr = SoftLayer.FirewallManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - firewall_id = int(key_value[1]) - if key_value[0] == 'vlan': - orig_rules = mgr.get_dedicated_fwl_rules(firewall_id) - else: - orig_rules = mgr.get_standard_fwl_rules(firewall_id) - # open an editor for the user to enter their rules - edited_rules = open_editor(rules=orig_rules) - print(edited_rules) - if formatting.confirm("Would you like to submit the rules. " - "Continue?"): - while True: - try: - rules = parse_rules(edited_rules) - if key_value[0] == 'vlan': - rules = mgr.edit_dedicated_fwl_rules(firewall_id, - rules) - else: - rules = mgr.edit_standard_fwl_rules(firewall_id, - rules) - break - except (SoftLayer.SoftLayerError, ValueError) as error: - print("Unexpected error({%s})" % (error)) - if formatting.confirm("Would you like to continue editing " - "the rules. Continue?"): - edited_rules = open_editor(content=edited_rules) - print(edited_rules) - if formatting.confirm("Would you like to submit the " - "rules. Continue?"): - continue - else: - raise exceptions.CLIAbort('Aborted.') - else: - raise exceptions.CLIAbort('Aborted.') - return 'Firewall updated!' - else: - raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/modules/globalip.py b/SoftLayer/CLI/modules/globalip.py deleted file mode 100644 index 6f87c2a10..000000000 --- a/SoftLayer/CLI/modules/globalip.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -usage: sl globalip [] [...] [options] - -Orders or configures global IP addresses - -The available commands are: - assign Assign a target to a global IP address - cancel Cancels a global IP - create Orders a new global IP address - list Display a list of global IP addresses - unassign Unassigns a global IP -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - - -class GlobalIpAssign(environment.CLIRunnable): - """ -usage: sl globalip assign [options] - -Assigns a global IP to a target. - -Required: - The ID or address of the global IP - The IP address to assign to the global IP -""" - action = 'assign' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') - mgr.assign_global_ip(global_ip_id, args['']) - - -class GlobalIpCancel(environment.CLIRunnable): - """ -usage: sl globalip cancel [options] - -Cancel a subnet -""" - - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') - - if args['--really'] or formatting.no_going_back(global_ip_id): - mgr.cancel_global_ip(global_ip_id) - else: - raise exceptions.CLIAbort('Aborted') - - -class GlobalIpCreate(environment.CLIRunnable): - """ -usage: - sl globalip create [options] - -Add a new global IP address to your account. - -Options: - --v6 Orders IPv6 - --test Do not order the IP; just get a quote -""" - action = 'create' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - - version = 4 - if args.get('--v6'): - version = 6 - if not args.get('--test') and not args['--really']: - if not formatting.confirm("This action will incur charges on your " - "account. Continue?"): - raise exceptions.CLIAbort('Cancelling order.') - result = mgr.add_global_ip(version=version, - test_order=args.get('--test')) - - table = formatting.Table(['Item', 'cost']) - table.align['Item'] = 'r' - table.align['cost'] = 'r' - - total = 0.0 - for price in result['orderDetails']['prices']: - total += float(price.get('recurringFee', 0.0)) - rate = "%.2f" % float(price['recurringFee']) - - table.add_row([price['item']['description'], rate]) - - table.add_row(['Total monthly cost', "%.2f" % total]) - return table - - -class GlobalIpList(environment.CLIRunnable): - """ -usage: sl globalip list [options] - -Displays a list of global IPs - -Filters: - --v4 Display only IPV4 - --v6 Display only IPV6 -""" - action = 'list' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - - table = formatting.Table(['id', 'ip', 'assigned', 'target']) - table.sortby = args.get('--sortby') or 'id' - - version = 0 - if args.get('--v4'): - version = 4 - elif args.get('--v6'): - version = 6 - - ips = mgr.list_global_ips(version=version) - - for ip_address in ips: - assigned = 'No' - target = 'None' - if ip_address.get('destinationIpAddress'): - dest = ip_address['destinationIpAddress'] - assigned = 'Yes' - target = dest['ipAddress'] - virtual_guest = dest.get('virtualGuest') - if virtual_guest: - target += (' (%s)' - % virtual_guest['fullyQualifiedDomainName']) - elif ip_address['destinationIpAddress'].get('hardware'): - target += (' (%s)' - % dest['hardware']['fullyQualifiedDomainName']) - - table.add_row([ip_address['id'], - ip_address['ipAddress']['ipAddress'], - assigned, - target]) - return table - - -class GlobalIpUnassign(environment.CLIRunnable): - """ -usage: sl globalip unassign [options] - -Unassigns a global IP from a target. - -Required: - The ID or address of the global IP -""" - action = 'unassign' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') - mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/CLI/modules/help.py b/SoftLayer/CLI/modules/help.py deleted file mode 100644 index 0866687e1..000000000 --- a/SoftLayer/CLI/modules/help.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -usage: sl help [options] - sl help [options] - sl help [options] - -View help on a module or command. -""" -# :license: MIT, see LICENSE for more details. -# Missing docstrings ignored due to __doc__ = __doc__ magic -# pylint: disable=C0111 - -from SoftLayer.CLI import core -from SoftLayer.CLI import environment - - -class Show(environment.CLIRunnable): - # Use the same documentation as the module - __doc__ = __doc__ - action = None - - def execute(self, args): - parser = core.CommandParser(self.env) - if not any([args[''], args['']]): - return parser.get_module_help('help') - - self.env.load_module(args['']) - - if args['']: - return parser.get_command_help(args[''], args['']) - elif args['']: - return parser.get_module_help(args['']) diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py deleted file mode 100644 index 5075eed60..000000000 --- a/SoftLayer/CLI/modules/image.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -usage: sl image [] [...] [options] - -Manage compute images - -The available commands are: - delete Delete an image - detail Output details about an image - list List images - edit Edit an image -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - -MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' - 'imageType') -DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' - 'note,createDate,status') -PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') -PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') - - -class ListImages(environment.CLIRunnable): - """ -usage: sl image list [--public | --private] [options] - -List images - -Options: - --private Display only private images - --public Display only public images -""" - action = 'list' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - - neither = not any([args['--private'], args['--public']]) - - images = [] - if args['--private'] or neither: - for image in image_mgr.list_private_images(mask=MASK): - images.append(image) - - if args['--public'] or neither: - for image in image_mgr.list_public_images(mask=MASK): - images.append(image) - - table = formatting.Table(['id', - 'account', - 'name', - 'type', - 'visibility', - 'global_identifier']) - - images = [image for image in images if image['parentId'] == ''] - for image in images: - - table.add_row([ - image['id'], - image.get('accountId', formatting.blank()), - image['name'].strip(), - formatting.FormattedItem( - utils.lookup(image, 'imageType', 'keyName'), - utils.lookup(image, 'imageType', 'name')), - PUBLIC_TYPE if image['publicFlag'] else PRIVATE_TYPE, - image.get('globalIdentifier', formatting.blank()), - ]) - - return table - - -class DetailImage(environment.CLIRunnable): - """ -usage: sl image detail [options] - -Get details for an image -""" - action = 'detail' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - image_id = helpers.resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') - - image = image_mgr.get_image(image_id, mask=DETAIL_MASK) - disk_space = 0 - datacenters = [] - for child in image.get('children'): - disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) - if child.get('datacenter'): - datacenters.append(utils.lookup(child, 'datacenter', 'name')) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - table.add_row(['id', image['id']]) - table.add_row(['global_identifier', - image.get('globalIdentifier', formatting.blank())]) - table.add_row(['name', image['name'].strip()]) - table.add_row(['status', formatting.FormattedItem( - utils.lookup(image, 'status', 'keyname'), - utils.lookup(image, 'status', 'name'), - )]) - table.add_row(['account', image.get('accountId', formatting.blank())]) - table.add_row(['visibility', - PUBLIC_TYPE if image['publicFlag'] else PRIVATE_TYPE]) - table.add_row(['type', - formatting.FormattedItem( - utils.lookup(image, 'imageType', 'keyName'), - utils.lookup(image, 'imageType', 'name'), - )]) - table.add_row(['flex', image.get('flexImageFlag')]) - table.add_row(['note', image.get('note')]) - table.add_row(['created', image.get('createDate')]) - table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) - table.add_row(['datacenters', formatting.listing(sorted(datacenters), - separator=',')]) - - return table - - -class DeleteImage(environment.CLIRunnable): - """ -usage: sl image delete [options] - -Get details for an image -""" - action = 'delete' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - image_id = helpers.resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') - - image_mgr.delete_image(image_id) - - -class EditImage(environment.CLIRunnable): - """ -usage: sl image edit [--tag=Tag...] [options] - -Edit Details for an image - -Options: - --name=Name Name of the Image - --note=Note Note of the Image - --tag=TAG... Tags of the Image. Can be specified multiple times. - -Note: Image to be edited must be private -""" - action = 'edit' - - def execute(self, args): - image_mgr = SoftLayer.ImageManager(self.client) - data = {} - if args.get('--name'): - data['name'] = args.get('--name') - if args.get('--note'): - data['note'] = args.get('--note') - if args.get('--tag'): - data['tag'] = args.get('--tag') - image_id = helpers.resolve_id(image_mgr.resolve_ids, - args.get(''), 'image') - if not image_mgr.edit(image_id, **data): - raise exceptions.CLIAbort("Failed to Edit Image") diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py deleted file mode 100644 index a0d5bbd41..000000000 --- a/SoftLayer/CLI/modules/iscsi.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -usage: sl iscsi [] [...] [options] - -Manage, order, delete iSCSI targets - -The available commands are: - cancel Cancel an existing iSCSI target - create Order and create an iSCSI target - detail Output details about an iSCSI - list List iSCSI targets on the account - -For several commands, will be asked for. This will be the id -for iSCSI target. -""" -# from SoftLayer.CLI import (CLIRunnable, Table, no_going_back, FormattedItem) -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - - -class ListISCSIs(environment.CLIRunnable): - - """ -usage: sl iscsi list [options] - -List iSCSI targets -""" - action = 'list' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_list = iscsi_mgr.list_iscsi() - iscsi_list = [utils.NestedDict(n) for n in iscsi_list] - table = formatting.Table([ - 'id', - 'datacenter', - 'size', - 'username', - 'password', - 'server' - ]) - for iscsi in iscsi_list: - table.add_row([ - iscsi['id'], - iscsi['serviceResource']['datacenter'].get('name', - formatting.blank()), - formatting.FormattedItem( - iscsi.get('capacityGb', formatting.blank()), - "%dGB" % iscsi.get('capacityGb', 0)), - iscsi.get('username', formatting.blank()), - iscsi.get('password', formatting.blank()), - iscsi.get('serviceResourceBackendIpAddress', - formatting.blank())]) - return table - - -class CreateISCSI(environment.CLIRunnable): - - """ -usage: sl iscsi create [options] - -Orders and creates an iSCSI target. - -Examples: - sl iscsi create --size=1 --datacenter=dal05 - sl iscsi create --size 1 -d dal05 - sl iscsi create -s 1 -d dal05 - -Required: - -s, --size=SIZE Size of the iSCSI volume to create - -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) -""" - action = 'create' - options = ['confirm'] - required_params = ['--size', '--datacenter'] - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - self._validate_create_args(args) - size, location = self._parse_create_args(args) - iscsi_mgr.create_iscsi(size=size, location=location) - - def _parse_create_args(self, args): - """ Converts CLI arguments to arguments that can be passed into - ISCSIManager.create_iscsi. - :param dict args: CLI arguments - """ - size = args['--size'] - location = args['--datacenter'] - return int(size), str(location) - - def _validate_create_args(self, args): - """ Raises an ArgumentError if the given arguments are not valid """ - invalid_args = [k for k in self.required_params if args.get(k) is None] - if invalid_args: - raise exceptions.ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - - -class CancelISCSI(environment.CLIRunnable): - - """ -usage: sl iscsi cancel [options] - -Cancel existing iSCSI - -Examples: - sl iscsi cancel 12345 - sl iscsi cancel 12345 --immediate - sl iscsi cancel 12345 --immediate --reason='no longer needed' - -options : - --immediate Cancels the iSCSI immediately (instead of on the billing - anniversary) - --reason=REASON An optional reason for cancellation. -""" - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_id = helpers.resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - - immediate = args.get('--immediate', False) - - reason = args.get('--reason') - if args['--really'] or formatting.no_going_back(iscsi_id): - iscsi_mgr.cancel_iscsi(iscsi_id, reason, immediate) - else: - raise exceptions.CLIAbort('Aborted') - - -class ISCSIDetails(environment.CLIRunnable): - - """ -usage: sl iscsi detail [--password] [options] - -Get details for an iSCSI - -Examples: - sl iscsi detail 12345 - sl iscsi detail 12345 --password - -Options: - --password Show credentials to access the iSCSI target -""" - action = 'detail' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - iscsi_id = helpers.resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - result = iscsi_mgr.get_iscsi(iscsi_id) - result = utils.NestedDict(result) - - table.add_row(['id', result['id']]) - table.add_row(['serviceResourceName', result['serviceResourceName']]) - table.add_row(['createDate', result['createDate']]) - table.add_row(['nasType', result['nasType']]) - table.add_row(['capacityGb', result['capacityGb']]) - if result['snapshotCapacityGb']: - table.add_row(['snapshotCapacityGb', result['snapshotCapacityGb']]) - table.add_row(['mountableFlag', result['mountableFlag']]) - table.add_row( - ['serviceResourceBackendIpAddress', - result['serviceResourceBackendIpAddress']]) - table.add_row(['price', result['billingItem']['recurringFee']]) - table.add_row(['BillingItemId', result['billingItem']['id']]) - if result.get('notes'): - table.add_row(['notes', result['notes']]) - - if args.get('--password'): - pass_table = formatting.Table(['username', 'password']) - pass_table.add_row([result['username'], result['password']]) - table.add_row(['users', pass_table]) - - return table diff --git a/SoftLayer/CLI/modules/loadbal.py b/SoftLayer/CLI/modules/loadbal.py deleted file mode 100755 index cd1e46c00..000000000 --- a/SoftLayer/CLI/modules/loadbal.py +++ /dev/null @@ -1,591 +0,0 @@ -""" -usage: sl loadbal [] [...] [options] - -Local LoadBalancer management - -The available commands are: - cancel Cancel an existing load balancer - create Create a new load balancer - create-options Lists the different packages for load balancers - detail Provide details about a particular load balancer - group-add Add a new service group in the load balancer - group-delete Delete a service group from the load balancer - group-edit Edit the properties of a service group - group-reset Resets all the connections on a service group - health-checks List the different health check values - list List active load balancers - routing-methods List supported routing methods - routing-types List supported routing types - service-add Add a service to an existing service group - service-delete Delete an existing service - service-edit Edit an existing service - service-toggle Toggle the status of the service -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - - -def get_ids(input_id): - """ Helper package to retrieve the actual IDs - - :param input_id: the ID provided by the user - :returns: A list of valid IDs - """ - key_value = input_id.split(':') - if len(key_value) != 2: - raise exceptions.CLIAbort( - 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) - return key_value - - -def get_local_lbs_table(load_balancers): - """ Helper package to format the local load balancers into a table. - - :param dict load_balancers: A dictionary representing the load_balancers - :returns: A table containing the local load balancers - """ - table = formatting.Table(['ID', - 'VIP Address', - 'Location', - 'SSL Offload', - 'Connections/second', - 'Type']) - - table.align['Connections/second'] = 'r' - - for load_balancer in load_balancers: - ssl_support = 'Not Supported' - if load_balancer['sslEnabledFlag']: - if load_balancer['sslActiveFlag']: - ssl_support = 'On' - else: - ssl_support = 'Off' - lb_type = 'Standard' - if load_balancer['dedicatedFlag']: - lb_type = 'Dedicated' - elif load_balancer['highAvailabilityFlag']: - lb_type = 'HA' - table.add_row([ - 'local:%s' % load_balancer['id'], - load_balancer['ipAddress']['ipAddress'], - load_balancer['loadBalancerHardware'][0]['datacenter']['name'], - ssl_support, - load_balancer['connectionLimit'], - lb_type - ]) - return table - - -def get_local_lb_table(load_balancer): - """ Helper package to format the local loadbal details into a table. - - :param dict load_balancer: A dictionary representing the loadbal - :returns: A table containing the local loadbal details - """ - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'l' - table.align['Value'] = 'l' - table.add_row(['General properties', '----------']) - table.add_row([' ID', 'local:%s' % load_balancer['id']]) - table.add_row([' IP Address', load_balancer['ipAddress']['ipAddress']]) - name = load_balancer['loadBalancerHardware'][0]['datacenter']['name'] - table.add_row([' Datacenter', name]) - table.add_row([' Connections limit', load_balancer['connectionLimit']]) - table.add_row([' Dedicated', load_balancer['dedicatedFlag']]) - table.add_row([' HA', load_balancer['highAvailabilityFlag']]) - table.add_row([' SSL Enabled', load_balancer['sslEnabledFlag']]) - table.add_row([' SSL Active', load_balancer['sslActiveFlag']]) - index0 = 1 - for virtual_server in load_balancer['virtualServers']: - table.add_row(['Service group %s' % index0, - '**************']) - index0 += 1 - table2 = formatting.Table(['Service group ID', - 'Port', - 'Allocation', - 'Routing type', - 'Routing Method']) - - for group in virtual_server['serviceGroups']: - table2.add_row([ - '%s:%s' % (load_balancer['id'], virtual_server['id']), - virtual_server['port'], - '%s %%' % virtual_server['allocation'], - '%s:%s' % (group['routingTypeId'], - group['routingType']['name']), - '%s:%s' % (group['routingMethodId'], - group['routingMethod']['name']) - ]) - - table.add_row([' Group Properties', table2]) - - table3 = formatting.Table(['Service_ID', - 'IP Address', - 'Port', - 'Health Check', - 'Weight', - 'Enabled', - 'Status']) - service_exist = False - for service in group['services']: - service_exist = True - health_check = service['healthChecks'][0] - table3.add_row([ - '%s:%s' % (load_balancer['id'], service['id']), - service['ipAddress']['ipAddress'], - service['port'], - '%s:%s' % (health_check['healthCheckTypeId'], - health_check['type']['name']), - service['groupReferences'][0]['weight'], - service['enabled'], - service['status'] - ]) - if service_exist: - table.add_row([' Services', table3]) - else: - table.add_row([' Services', 'None']) - return table - - -class LoadBalancerList(environment.CLIRunnable): - """ -usage: sl loadbal list [options] - -List active load balancers - -""" - action = 'list' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - load_balancers = mgr.get_local_lbs() - return get_local_lbs_table(load_balancers) - - -class LoadBalancerHealthChecks(environment.CLIRunnable): - """ -usage: sl loadbal health-checks [options] - -List load balancer service health check types that can be used -""" - action = 'health-checks' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - hc_types = mgr.get_hc_types() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for hc_type in hc_types: - table.add_row([hc_type['id'], hc_type['name']]) - return table - - -class LoadBalancerRoutingMethods(environment.CLIRunnable): - """ -usage: sl loadbal routing-methods [options] - -List load balancers routing methods that can be used -""" - action = 'routing-methods' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - routing_methods = mgr.get_routing_methods() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for routing_method in routing_methods: - table.add_row([routing_method['id'], routing_method['name']]) - return table - - -class LoadBalancerRoutingTypes(environment.CLIRunnable): - """ -usage: sl loadbal routing-types [options] - -List load balancers routing types that can be used -""" - action = 'routing-types' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - routing_types = mgr.get_routing_types() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for routing_type in routing_types: - table.add_row([routing_type['id'], routing_type['name']]) - return table - - -class LoadBalancerDetails(environment.CLIRunnable): - """ -usage: sl loadbal detail [options] - -Get Load balancer details - -""" - action = 'detail' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[1]) - - load_balancer = mgr.get_local_lb(loadbal_id) - return get_local_lb_table(load_balancer) - - -class LoadBalancerCancel(environment.CLIRunnable): - """ -usage: sl loadbal cancel [options] - -Cancels an existing load_balancer - -""" - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "load balancer. Continue?"): - mgr.cancel_lb(loadbal_id) - return 'Load Balancer with id %s is being cancelled!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceDelete(environment.CLIRunnable): - """ -usage: sl loadbal service-delete [options] - -Deletes an existing load_balancer service - -""" - action = 'service-delete' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - service_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "service from your load " - "balancer. Continue?"): - mgr.delete_service(service_id) - return 'Load balancer service %s is being cancelled!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceToggle(environment.CLIRunnable): - """ -usage: sl loadbal service-toggle [options] - -Toggle the status of an existing load_balancer service - -""" - action = 'service-toggle' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - service_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will toggle " - "the status on the service. " - "Continue?"): - mgr.toggle_service_status(service_id) - return 'Load balancer service %s status updated!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceEdit(environment.CLIRunnable): - """ -usage: sl loadbal service-edit [options] - -Enable an existing load_balancer service -Options: ---enabled=ENABLED Set to 1 to enable the service, or 0 to disable ---port=PORT Change the value of the port ---weight=WEIGHT Change the weight of the service ---hc_type=HCTYPE Change the health check type ---ip=IP Change the IP of the service - -""" - action = 'service-edit' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - service_id = int(key_value[1]) - - # check if any input is provided - if not (args['--ip'] or args['--enabled'] or args['--weight'] - or args['--port'] or args['--hc_type']): - return 'At least one property is required to be changed!' - - # check if the IP is valid - ip_address_id = None - if args['--ip']: - ip_address = mgr.get_ip_address(args['--ip']) - if not ip_address: - return 'Provided IP address is not valid!' - else: - ip_address_id = ip_address['id'] - - mgr.edit_service(loadbal_id, - service_id, - ip_address_id=ip_address_id, - enabled=args.get('--enabled'), - port=args.get('--port'), - weight=args.get('--weight'), - hc_type=args.get('--hc_type')) - return 'Load balancer service %s is being modified!' % input_id - - -class LoadBalancerServiceAdd(environment.CLIRunnable): - """ -usage: sl loadbal service-add --ip=IP --port=PORT \ ---weight=WEIGHT --hc_type=HCTYPE --enabled=ENABLED [options] - -Adds a new load_balancer service -Required: ---enabled=ENABLED Set to 1 to enable the service, 0 to disable [default: 1]. ---port=PORT Set to the desired port value [default: 80]. ---weight=WEIGHT Set to the desired weight value [default: 1]. ---hc_type=HCTYPE Set to the desired health check value [default: 21]. ---ip=IP Set to the desired IP value. - -""" - action = 'service-add' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - group_id = int(key_value[1]) - - # check if the IP is valid - ip_address = None - if args['--ip']: - ip_address = mgr.get_ip_address(args['--ip']) - if not ip_address: - return 'Provided IP address is not valid!' - - mgr.add_service(loadbal_id, - group_id, - ip_address_id=ip_address['id'], - enabled=args.get('--enabled'), - port=args.get('--port'), - weight=args.get('--weight'), - hc_type=args.get('--hc_type')) - return 'Load balancer service is being added!' - - -class LoadBalancerServiceGroupDelete(environment.CLIRunnable): - """ -usage: sl loadbal group-delete [options] - -Deletes an existing load_balancer service group - -""" - action = 'group-delete' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - group_id = int(key_value[1]) - - if args['--really'] or formatting.confirm("This action will cancel a " - "service group. Continue?"): - mgr.delete_service_group(group_id) - return 'Service group %s is being deleted!' % input_id - else: - raise exceptions.CLIAbort('Aborted.') - - -class LoadBalancerServiceGroupEdit(environment.CLIRunnable): - """ -usage: sl loadbal group-edit [options] - -Edits an existing load_balancer service group -Options: ---allocation=PERC Change the allocated % of connections ---port=PORT Change the port ---routing_type=TYPE Change the port routing type ---routing_method=METHOD Change the routing method - -""" - action = 'group-edit' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - group_id = int(key_value[1]) - - # check if any input is provided - if not (args['--allocation'] or args['--port'] - or args['--routing_type'] or args['--routing_method']): - return 'At least one property is required to be changed!' - - routing_type = args.get('--routing_type') - routing_method = args.get('--routing_method') - - mgr.edit_service_group(loadbal_id, - group_id, - allocation=args.get('--allocation'), - port=args.get('--port'), - routing_type=routing_type, - routing_method=routing_method) - - return 'Load balancer service group %s is being updated!' % input_id - - -class LoadBalancerServiceGroupReset(environment.CLIRunnable): - """ -usage: sl loadbal group-reset [options] - -Resets the connections on a certain service group - -""" - action = 'group-reset' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - - key_value = get_ids(input_id) - loadbal_id = int(key_value[0]) - group_id = int(key_value[1]) - - mgr.reset_service_group(loadbal_id, group_id) - return 'Load balancer service group connections are being reset!' - - -class LoadBalancerServiceGroupAdd(environment.CLIRunnable): - """ -usage: sl loadbal group-add --allocation=PERC --port=PORT \ ---routing_type=TYPE --routing_method=METHOD [options] - -Adds a new load_balancer service -Required: ---allocation=PERC The % of connections that will be allocated ---port=PORT The virtual port number for the group ---routing_type=TYPE The routing type for the group ---routing_method=METHOD The routing method for the group - -""" - action = 'group-add' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = args.get('') - key_value = get_ids(input_id) - - loadbal_id = int(key_value[1]) - - mgr.add_service_group(loadbal_id, - allocation=int(args.get('--allocation')), - port=int(args.get('--port')), - routing_type=int(args.get('--routing_type')), - routing_method=int(args.get('--routing_method'))) - - return 'Load balancer service group is being added!' - - -class LoadBalancerCreate(environment.CLIRunnable): - """ -usage: sl loadbal create (--datacenter=DC) [options] - -Adds a load_balancer given the billing id returned from create-options - -Options: - -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) - Note: Omitting this value defaults to the first - available datacenter -""" - action = 'create' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - input_id = helpers.resolve_id( - mgr.resolve_ids, args.get(''), 'load_balancer') - if not formatting.confirm("This action will incur charges on your " - "account. Continue?"): - raise exceptions.CLIAbort('Aborted.') - mgr.add_local_lb(input_id, datacenter=args['--datacenter']) - return "Load balancer is being created!" - - -class CreateOptionsLoadBalancer(environment.CLIRunnable): - """ -usage: sl loadbal create-options - -Output available options when adding a new load balancer - -""" - action = 'create-options' - - def execute(self, args): - mgr = SoftLayer.LoadBalancerManager(self.client) - - table = formatting.Table(['id', 'capacity', 'description', 'price']) - - table.sortby = 'price' - table.align['price'] = 'r' - table.align['capacity'] = 'r' - table.align['id'] = 'r' - - packages = mgr.get_lb_pkgs() - - for package in packages: - table.add_row([ - package['prices'][0]['id'], - package.get('capacity'), - package['description'], - format(float(package['prices'][0]['recurringFee']), '.2f') - ]) - - return table diff --git a/SoftLayer/CLI/modules/messaging.py b/SoftLayer/CLI/modules/messaging.py deleted file mode 100644 index 0c39a8ad0..000000000 --- a/SoftLayer/CLI/modules/messaging.py +++ /dev/null @@ -1,522 +0,0 @@ -""" -usage: sl messaging [] [...] [options] - -Manage the SoftLayer Message Queue service. For most commands, a queue account -is required. Use 'sl messaging accounts-list' to list current accounts - -The available commands are: - accounts-list List all queue accounts - endpoints-list List all service endpoints - ping Ping the service - - queue-add Create a new queue - queue-detail Prints the details of a queue - queue-edit Modifies an existing queue - queue-list Lists out all queues on an account - queue-pop Pop a message from a queue - queue-push Pushes a message into a queue - queue-remove Delete a queue - - topic-add Creates a new topic - topic-detail Prints the details of a topic - topic-list Lists out all topics on an account - topic-push Pushes a notification to a topic - topic-remove Deletes a topic - topic-subscribe Adds a subscription on a topic - topic-unsubscribe Remove a subscription on a topic - -""" -# :license: MIT, see LICENSE for more details. -# Missing docstrings ignored due to __doc__ = __doc__ magic -# pylint: disable=C0111 -import sys - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting - - -COMMON_MESSAGING_ARGS = """Service Options: - --datacenter=NAME Datacenter, E.G.: dal05 - --network=TYPE Network type, [Options: public, private] -""" - - -class ListAccounts(environment.CLIRunnable): - """ -usage: sl messaging accounts-list [options] - -List SoftLayer Message Queue Accounts - -""" - action = 'accounts-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - accounts = manager.list_accounts() - - table = formatting.Table([ - 'id', 'name', 'status' - ]) - for account in accounts: - if not account['nodes']: - continue - - table.add_row([ - account['nodes'][0]['accountName'], - account['name'], - account['status']['name'], - ]) - - return table - - -class ListEndpoints(environment.CLIRunnable): - """ -usage: sl messaging endpoints-list [options] - -List SoftLayer Message Queue Endpoints - -""" - action = 'endpoints-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - regions = manager.get_endpoints() - - table = formatting.Table([ - 'name', 'public', 'private' - ]) - for region, endpoints in regions.items(): - table.add_row([ - region, - endpoints.get('public') or formatting.blank(), - endpoints.get('private') or formatting.blank(), - ]) - - return table - - -class Ping(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging ping [options] - -Ping the SoftLayer Message Queue service - -""" + COMMON_MESSAGING_ARGS - action = 'ping' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - okay = manager.ping( - datacenter=args['--datacenter'], network=args['--network']) - if okay: - return 'OK' - else: - exceptions.CLIAbort('Ping failed') - - -def queue_table(queue): - """ Returns a table with details about a queue """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['name', queue['name']]) - table.add_row(['message_count', queue['message_count']]) - table.add_row(['visible_message_count', queue['visible_message_count']]) - table.add_row(['tags', formatting.listing(queue['tags'] or [])]) - table.add_row(['expiration', queue['expiration']]) - table.add_row(['visibility_interval', queue['visibility_interval']]) - return table - - -def message_table(message): - """ Returns a table with details about a message """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['id', message['id']]) - table.add_row(['initial_entry_time', message['initial_entry_time']]) - table.add_row(['visibility_delay', message['visibility_delay']]) - table.add_row(['visibility_interval', message['visibility_interval']]) - table.add_row(['fields', message['fields']]) - return [table, message['body']] - - -def topic_table(topic): - """ Returns a table with details about a topic """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['name', topic['name']]) - table.add_row(['tags', formatting.listing(topic['tags'] or [])]) - return table - - -def subscription_table(sub): - """ Returns a table with details about a subscription """ - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['id', sub['id']]) - table.add_row(['endpoint_type', sub['endpoint_type']]) - for key, val in sub['endpoint'].items(): - table.add_row([key, val]) - return table - - -class QueueList(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-list [options] - -List all queues on an account - -""" + COMMON_MESSAGING_ARGS - action = 'queue-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - queues = mq_client.get_queues()['items'] - - table = formatting.Table([ - 'name', 'message_count', 'visible_message_count' - ]) - for queue in queues: - table.add_row([ - queue['name'], - queue['message_count'], - queue['visible_message_count'], - ]) - return table - - -class QueueDetail(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-detail [options] - -Detail a queue - -""" + COMMON_MESSAGING_ARGS - action = 'queue-detail' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - queue = mq_client.get_queue(args['']) - return queue_table(queue) - - -class QueueCreate(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-add [options] - -Create a queue - -Options: - --visibility_interval=SECONDS Time in seconds that messages will re-appear - after being popped - --expiration=SECONDS Time in seconds that messages will live - --tags=TAGS Comma-separated list of tags - -""" + COMMON_MESSAGING_ARGS - action = 'queue-add' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - queue = mq_client.create_queue( - args[''], - visibility_interval=int(args.get('--visibility_interval') or 30), - expiration=int(args.get('--expiration') or 604800), - tags=tags, - ) - return queue_table(queue) - - -class QueueModify(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-edit [options] - -Modify a queue - -Options: - --visibility_interval=SECONDS Time in seconds that messages will re-appear - after being popped - --expiration=SECONDS Time in seconds that messages will live - --tags=TAGS Comma-separated list of tags - -""" + COMMON_MESSAGING_ARGS - action = 'queue-edit' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - queue = mq_client.create_queue( - args[''], - visibility_interval=int(args.get('--visibility_interval') or 30), - expiration=int(args.get('--expiration') or 604800), - tags=tags, - ) - return queue_table(queue) - - -class QueueDelete(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-remove [] - [options] - -Delete a queue or a queued message - -Options: - --force Flag to force the deletion of the queue even when there are messages - -""" + COMMON_MESSAGING_ARGS - action = 'queue-remove' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - if args['']: - mq_client.delete_message(args[''], - args['']) - else: - mq_client.delete_queue(args[''], args.get('--force')) - - -class QueuePush(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-push ( | -) - [options] - -Push a message into a queue - -Options: - --force Flag to force the deletion of the queue even when there are messages - -""" + COMMON_MESSAGING_ARGS - action = 'queue-push' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - body = '' - if args[''] == '-': - body = sys.stdin.read() - else: - body = args[''] - return message_table( - mq_client.push_queue_message(args[''], body)) - - -class QueuePop(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging queue-pop [options] - -Pops a message from a queue - -Options: - --count=NUM Count of messages to pop - --delete-after Remove popped messages from the queue - -""" + COMMON_MESSAGING_ARGS - action = 'queue-pop' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - messages = mq_client.pop_messages( - args[''], - args.get('--count') or 1) - formatted_messages = [] - for message in messages['items']: - formatted_messages.append(message_table(message)) - - if args.get('--delete-after'): - for message in messages['items']: - mq_client.delete_message( - args[''], - message['id']) - return formatted_messages - - -class TopicList(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-list [options] - -List all topics on an account - -""" + COMMON_MESSAGING_ARGS - action = 'topic-list' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - topics = mq_client.get_topics()['items'] - - table = formatting.Table(['name']) - for topic in topics: - table.add_row([topic['name']]) - return table - - -class TopicDetail(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-detail [options] - -Detail a topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-detail' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - topic = mq_client.get_topic(args['']) - subscriptions = mq_client.get_subscriptions(args['']) - tables = [] - for sub in subscriptions['items']: - tables.append(subscription_table(sub)) - return [topic_table(topic), tables] - - -class TopicCreate(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-add [options] - -Create a new topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-add' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - topic = mq_client.create_topic( - args[''], - visibility_interval=int( - args.get('--visibility_interval') or 30), - expiration=int(args.get('--expiration') or 604800), - tags=tags, - ) - return topic_table(topic) - - -class TopicDelete(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-remove [options] - -Delete a topic or subscription - -Options: - --force Flag to force the deletion of the topic even when there are - subscriptions -""" + COMMON_MESSAGING_ARGS - action = 'topic-remove' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - mq_client.delete_topic(args[''], args.get('--force')) - - -class TopicSubscribe(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-subscribe [options] - -Create a subscription on a topic - -Options: - --type=TYPE Type of endpoint, [Options: http, queue] - --queue-name=NAME Queue name. Required if --type is queue - --http-method=METHOD HTTP Method to use if --type is http - --http-url=URL HTTP/HTTPS URL to use. Required if --type is http - --http-body=BODY HTTP Body template to use if --type is http - -""" + COMMON_MESSAGING_ARGS - action = 'topic-subscribe' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - if args['--type'] == 'queue': - subscription = mq_client.create_subscription( - args[''], - 'queue', - queue_name=args['--queue-name'], - ) - elif args['--type'] == 'http': - subscription = mq_client.create_subscription( - args[''], - 'http', - method=args['--http-method'] or 'GET', - url=args['--http-url'], - body=args['--http-body'] - ) - else: - raise exceptions.ArgumentError( - '--type should be either queue or http.') - return subscription_table(subscription) - - -class TopicUnsubscribe(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-unsubscribe - [options] - -Remove a subscription on a topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-unsubscribe' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - mq_client.delete_subscription( - args[''], - args['']) - - -class TopicPush(environment.CLIRunnable): - __doc__ = """ -usage: sl messaging topic-push ( | -) - [options] - -Push a message into a topic - -""" + COMMON_MESSAGING_ARGS - action = 'topic-push' - - def execute(self, args): - manager = SoftLayer.MessagingManager(self.client) - mq_client = manager.get_connection(args['']) - - # the message body comes from the positional argument or stdin - body = '' - if args[''] == '-': - body = sys.stdin.read() - else: - body = args[''] - return message_table( - mq_client.push_topic_message(args[''], body)) diff --git a/SoftLayer/CLI/modules/metadata.py b/SoftLayer/CLI/modules/metadata.py deleted file mode 100644 index 4bf89d9b4..000000000 --- a/SoftLayer/CLI/modules/metadata.py +++ /dev/null @@ -1,243 +0,0 @@ -""" -usage: sl metadata [] [...] [options] - -Find details about this machine. These commands only work on devices on the -backend SoftLayer network. This allows for self-discovery for newly provisioned -resources. - -The available commands are: - backend_ip Primary backend ip address - backend_mac Backend mac addresses - datacenter Datacenter name - datacenter_id Datacenter id - fqdn Fully qualified domain name - frontend_mac Frontend mac addresses - hostname Hostname - id Id - ip Primary ip address - network Details about either the public or private network - provision_state Provision state - tags Tags - user_data User-defined data -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting - - -class MetaRunnable(environment.CLIRunnable): - """ A CLIRunnable that raises a nice error on connection issues because - the metadata service is only accessable on a SoftLayer device - """ - def execute(self, args): - try: - return self._execute(args) - except SoftLayer.TransportError: - raise exceptions.CLIAbort( - 'Cannot connect to the backend service address. Make sure ' - 'this command is being ran from a device on the backend ' - 'network.') - - def _execute(self, _): - """ To be overridden exactly like the execute() method """ - pass - - -class BackendMacAddresses(MetaRunnable): - """ -usage: sl metadata backend_mac [options] - -List backend mac addresses -""" - action = 'backend_mac' - - def _execute(self, _): - backend_macs = SoftLayer.MetadataManager().get('backend_mac') - return formatting.listing(backend_macs, separator=',') - - -class Datacenter(MetaRunnable): - """ -usage: sl metadata datacenter [options] - -Get datacenter name -""" - action = 'datacenter' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('datacenter') - - -class DatacenterId(MetaRunnable): - """ -usage: sl metadata datacenter_id [options] - -Get datacenter id -""" - action = 'datacenter_id' - - def _execute(self, _): - return str(SoftLayer.MetadataManager().get('datacenter_id')) - - -class FrontendMacAddresses(MetaRunnable): - """ -usage: sl metadata frontend_mac [options] - -List frontend mac addresses -""" - action = 'frontend_mac' - - def _execute(self, _): - frontend_macs = SoftLayer.MetadataManager().get('frontend_mac') - return formatting.listing(frontend_macs, separator=',') - - -class FullyQualifiedDomainName(MetaRunnable): - """ -usage: sl metadata fqdn [options] - -Get fully qualified domain name -""" - action = 'fqdn' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('fqdn') - - -class Hostname(MetaRunnable): - """ -usage: sl metadata hostname [options] - -Get hostname -""" - action = 'hostname' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('hostname') - - -class Id(MetaRunnable): - """ -usage: sl metadata id - -Get id -""" - action = 'id' - - def _execute(self, _): - return str(SoftLayer.MetadataManager().get('id')) - - -class PrimaryBackendIpAddress(MetaRunnable): - """ -usage: sl metadata backend_ip [options] - -Get primary backend ip address -""" - action = 'backend_ip' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('primary_backend_ip') - - -class PrimaryIpAddress(MetaRunnable): - """ -usage: sl metadata ip [options] - -Get primary ip address -""" - action = 'ip' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('primary_ip') - - -class ProvisionState(MetaRunnable): - """ -usage: sl metadata provision_state [options] - -Get provision state -""" - action = 'provision_state' - - def _execute(self, _): - return SoftLayer.MetadataManager().get('provision_state') - - -class Tags(MetaRunnable): - """ -usage: sl metadata tags [options] - -List tags -""" - action = 'tags' - - def _execute(self, _): - return formatting.listing(SoftLayer.MetadataManager().get('tags'), - separator=',') - - -class UserMetadata(MetaRunnable): - """ -usage: sl metadata user_data [options] - -Get user-defined data -""" - action = 'user_data' - - def _execute(self, _): - """ Returns user metadata """ - userdata = SoftLayer.MetadataManager().get('user_data') - if userdata: - return userdata - else: - raise exceptions.CLIAbort("No user metadata.") - - -class Network(MetaRunnable): - """ -usage: sl metadata network ( | ) [options] - -Get details about the public or private network -""" - action = 'network' - - def _execute(self, args): - meta = SoftLayer.MetadataManager() - if args['']: - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - network = meta.public_network() - table.add_row([ - 'mac addresses', - formatting.listing(network['mac_addresses'], separator=',')]) - table.add_row([ - 'router', network['router']]) - table.add_row([ - 'vlans', formatting.listing(network['vlans'], separator=',')]) - table.add_row([ - 'vlan ids', - formatting.listing(network['vlan_ids'], separator=',')]) - return table - - if args['']: - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - network = meta.private_network() - table.add_row([ - 'mac addresses', - formatting.listing(network['mac_addresses'], separator=',')]) - table.add_row([ - 'router', network['router']]) - table.add_row([ - 'vlans', formatting.listing(network['vlans'], separator=',')]) - table.add_row([ - 'vlan ids', - formatting.listing(network['vlan_ids'], separator=',')]) - return table diff --git a/SoftLayer/CLI/modules/nas.py b/SoftLayer/CLI/modules/nas.py deleted file mode 100644 index 860e7bf09..000000000 --- a/SoftLayer/CLI/modules/nas.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -usage: sl nas [] [...] [options] - -Manage NAS accounts - -The available commands are: - list List NAS accounts -""" -# :license: MIT, see LICENSE for more details. - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - - -class ListNAS(environment.CLIRunnable): - """ -usage: sl nas list [options] - -List NAS accounts - -Options: -""" - action = 'list' - - def execute(self, args): - account = self.client['Account'] - - nas_accounts = account.getNasNetworkStorage( - mask='eventCount,serviceResource[datacenter.name]') - - table = formatting.Table(['id', 'datacenter', 'size', 'username', - 'password', 'server']) - - for nas_account in nas_accounts: - table.add_row([ - nas_account['id'], - utils.lookup(nas_account, - 'serviceResource', - 'datacenter', - 'name') or formatting.blank(), - formatting.FormattedItem( - nas_account.get('capacityGb', formatting.blank()), - "%dGB" % nas_account.get('capacityGb', 0)), - nas_account.get('username', formatting.blank()), - nas_account.get('password', formatting.blank()), - nas_account.get('serviceResourceBackendIpAddress', - formatting.blank())]) - - return table diff --git a/SoftLayer/CLI/modules/rwhois.py b/SoftLayer/CLI/modules/rwhois.py deleted file mode 100644 index c036488a0..000000000 --- a/SoftLayer/CLI/modules/rwhois.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -usage: sl rwhois [] [...] [options] - -Manage the RWhoIs information on the account. - -The available commands are: - edit Edit the RWhois data on the account - show Show the RWhois data on the account -""" -# :license: MIT, see LICENSE for more details. - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting - - -class RWhoisEdit(environment.CLIRunnable): - """ -usage: sl rwhois edit [options] - -Updates the RWhois information on your account. Only the fields you -specify will be changed. To clear a value, specify an empty string like: "" - -Options: - --abuse=EMAIL Set the abuse email - --address1=ADDR Update the address 1 field - --address2=ADDR Update the address 2 field - --city=CITY Set the city information - --company=NAME Set the company name - --country=COUNTRY Set the country information. Use the two-letter - abbreviation. - --firstname=NAME Update the first name field - --lastname=NAME Update the last name field - --postal=CODE Set the postal code field - --private Flags the address as a private residence. - --public Flags the address as a public residence. - --state=STATE Set the state information. Use the two-letter - abbreviation. -""" - action = 'edit' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - - update = { - 'abuse_email': args.get('--abuse'), - 'address1': args.get('--address1'), - 'address2': args.get('--address2'), - 'company_name': args.get('--company'), - 'city': args.get('--city'), - 'country': args.get('--country'), - 'first_name': args.get('--firstname'), - 'last_name': args.get('--lastname'), - 'postal_code': args.get('--postal'), - 'state': args.get('--state') - } - - if args.get('--private'): - update['private_residence'] = False - elif args.get('--public'): - update['private_residence'] = True - - check = [x for x in update.values() if x is not None] - if not check: - raise exceptions.CLIAbort( - "You must specify at least one field to update.") - - mgr.edit_rwhois(**update) # pylint: disable=W0142 - - -class RWhoisShow(environment.CLIRunnable): - """ -usage: sl rwhois show [options] - -Display the RWhois information for your account. -""" - action = 'show' - - def execute(self, args): - mgr = SoftLayer.NetworkManager(self.client) - result = mgr.get_rwhois() - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - table.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) - table.add_row(['Company', result['companyName']]) - table.add_row(['Abuse Email', result['abuseEmail']]) - table.add_row(['Address 1', result['address1']]) - if result.get('address2'): - table.add_row(['Address 2', result['address2']]) - table.add_row(['City', result['city']]) - table.add_row(['State', result.get('state', '-')]) - table.add_row(['Postal Code', result.get('postalCode', '-')]) - table.add_row(['Country', result['country']]) - - return table diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py deleted file mode 100644 index e49891991..000000000 --- a/SoftLayer/CLI/modules/server.py +++ /dev/null @@ -1,1107 +0,0 @@ -""" -usage: sl server [] [...] [options] - sl server [-h | --help] - -Manage hardware servers - -The available commands are: - cancel Cancel a dedicated server. - cancel-reasons Provides the list of possible cancellation reasons - create Create a new dedicated server - create-options Display a list of creation options for a specific chassis - detail Retrieve hardware details - list List hardware devices - list-chassis Provide a list of all chassis available for ordering - nic-edit Edit NIC settings - power-cycle Issues power cycle to server - power-off Powers off a running server - power-on Boots up a server - reboot Reboots a running server - reload Perform an OS reload - -For several commands, will be asked for. This can be the id, -hostname or the ip address for a piece of hardware. -""" -# :license: MIT, see LICENSE for more details. -import os -import re - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer.CLI import template -from SoftLayer import utils - - -class ListServers(environment.CLIRunnable): - """ -usage: sl server list [options] - -List hardware servers on the account - -Examples: - sl server list --datacenter=dal05 - sl server list --network=100 --domain=example.com - sl server list --tags=production,db - -Options: - --sortby=ARG Column to sort by. options: id, datacenter, host, cores, - memory, primary_ip, backend_ip - -Filters: - -c, --cpu=CPU Number of CPU cores - -D, --domain=DOMAIN Domain portion of the FQDN. example: example.com - -d, --datacenter=DC Datacenter shortname (sng01, dal05, ...) - -H, --hostname=HOST Host portion of the FQDN. example: server - -m, --memory=MEMORY Memory in gigabytes - -n, --network=MBPS Network port speed in Mbps - --tags=ARG Only show instances that have one of these tags. - Comma-separated. (production,db) - -For more on filters see 'sl help filters' -""" - action = 'list' - - def execute(self, args): - manager = SoftLayer.HardwareManager(self.client) - - tags = None - if args.get('--tags'): - tags = [tag.strip() for tag in args.get('--tags').split(',')] - - servers = manager.list_hardware( - hostname=args.get('--hostname'), - domain=args.get('--domain'), - cpus=args.get('--cpu'), - memory=args.get('--memory'), - datacenter=args.get('--datacenter'), - nic_speed=args.get('--network'), - tags=tags) - - table = formatting.Table([ - 'id', - 'datacenter', - 'host', - 'cores', - 'memory', - 'primary_ip', - 'backend_ip', - 'active_transaction', - 'owner' - ]) - table.sortby = args.get('--sortby') or 'host' - - for server in servers: - server = utils.NestedDict(server) - - table.add_row([ - server['id'], - server['datacenter']['name'] or formatting.blank(), - server['fullyQualifiedDomainName'], - server['processorPhysicalCoreAmount'], - formatting.gb(server['memoryCapacity'] or 0), - server['primaryIpAddress'] or formatting.blank(), - server['primaryBackendIpAddress'] or formatting.blank(), - formatting.active_txn(server), - utils.lookup( - server, 'billingItem', 'orderItem', 'order', 'userRecord', - 'username') or formatting.blank(), - ]) - - return table - - -class ServerDetails(environment.CLIRunnable): - """ -usage: sl server detail [--passwords] [--price] [options] - -Get details for a hardware device - -Options: - --passwords Show passwords (check over your shoulder!) - --price Show associated prices -""" - action = 'detail' - - def execute(self, args): - hardware = SoftLayer.HardwareManager(self.client) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - hardware_id = helpers.resolve_id( - hardware.resolve_ids, args.get(''), 'hardware') - result = hardware.get_hardware(hardware_id) - result = utils.NestedDict(result) - - table.add_row(['id', result['id']]) - table.add_row(['hostname', result['fullyQualifiedDomainName']]) - table.add_row(['status', result['hardwareStatus']['status']]) - table.add_row(['datacenter', - result['datacenter']['name'] or formatting.blank()]) - table.add_row(['cores', result['processorPhysicalCoreAmount']]) - table.add_row(['memory', - formatting.gb(result['memoryCapacity'])]) - table.add_row(['public_ip', - result['primaryIpAddress'] or formatting.blank()]) - table.add_row(['private_ip', - result['primaryBackendIpAddress'] - or formatting.blank()]) - table.add_row(['ipmi_ip', - result['networkManagementIpAddress'] - or formatting.blank()]) - table.add_row([ - 'os', - formatting.FormattedItem( - result['operatingSystem']['softwareLicense'] - ['softwareDescription']['referenceCode'] or formatting.blank(), - result['operatingSystem']['softwareLicense'] - ['softwareDescription']['name'] or formatting.blank() - )]) - - table.add_row( - ['created', result['provisionDate'] or formatting.blank()]) - - table.add_row(['owner', formatting.FormattedItem( - utils.lookup(result, 'billingItem', 'orderItem', - 'order', 'userRecord', - 'username') or formatting.blank() - )]) - - vlan_table = formatting.Table(['type', 'number', 'id']) - - for vlan in result['networkVlans']: - vlan_table.add_row([ - vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) - table.add_row(['vlans', vlan_table]) - - if result.get('notes'): - table.add_row(['notes', result['notes']]) - - if args.get('--price'): - table.add_row(['price rate', - result['billingItem']['recurringFee']]) - - if args.get('--passwords'): - user_strs = [] - for item in result['operatingSystem']['passwords']: - user_strs.append( - "%s %s" % (item['username'], item['password'])) - table.add_row(['users', formatting.listing(user_strs)]) - - tag_row = [] - for tag in result['tagReferences']: - tag_row.append(tag['tag']['name']) - - if tag_row: - table.add_row(['tags', formatting.listing(tag_row, separator=',')]) - - # Test to see if this actually has a primary (public) ip address - try: - if not result['privateNetworkOnlyFlag']: - ptr_domains = (self.client['Hardware_Server'] - .getReverseDomainRecords(id=hardware_id)) - - for ptr_domain in ptr_domains: - for ptr in ptr_domain['resourceRecords']: - table.add_row(['ptr', ptr['data']]) - except SoftLayer.SoftLayerAPIError: - pass - return table - - -class ServerReload(environment.CLIRunnable): - """ -usage: sl server reload [--key=KEY...] [options] - -Reload the OS on a hardware server based on its current configuration - -Optional: - -i, --postinstall=URI Post-install script to download - (Only HTTPS executes, HTTP leaves file in /root) - -k, --key=KEY SSH keys to add to the root user. Can be specified - multiple times -""" - - action = 'reload' - options = ['confirm'] - - def execute(self, args): - hardware = SoftLayer.HardwareManager(self.client) - hardware_id = helpers.resolve_id( - hardware.resolve_ids, args.get(''), 'hardware') - keys = [] - if args.get('--key'): - for key in args.get('--key'): - resolver = SoftLayer.SshKeyManager(self.client).resolve_ids - key_id = helpers.resolve_id(resolver, key, 'SshKey') - keys.append(key_id) - if args['--really'] or formatting.no_going_back(hardware_id): - hardware.reload(hardware_id, args['--postinstall'], keys) - else: - raise exceptions.CLIAbort('Aborted') - - -class CancelServer(environment.CLIRunnable): - """ -usage: sl server cancel [options] - -Cancel a dedicated server - -Options: - --immediate Cancels the server immediately (instead of on the billing - anniversary) - --comment=COMMENT An optional comment to add to the cancellation ticket - --reason=REASON An optional cancellation reason. See cancel-reasons for a - list of available options -""" - - action = 'cancel' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id( - mgr.resolve_ids, args.get(''), 'hardware') - - comment = args.get('--comment') - - if not comment and not args['--really']: - comment = self.env.input("(Optional) Add a cancellation comment:") - - reason = args.get('--reason') - immediate = args.get('--immediate') - - if args['--really'] or formatting.no_going_back(hw_id): - mgr.cancel_hardware(hw_id, reason, comment, immediate) - else: - raise exceptions.CLIAbort('Aborted') - - -class ServerCancelReasons(environment.CLIRunnable): - """ -usage: sl server cancel-reasons - -Display a list of cancellation reasons -""" - - action = 'cancel-reasons' - - def execute(self, args): - table = formatting.Table(['Code', 'Reason']) - table.align['Code'] = 'r' - table.align['Reason'] = 'l' - - mgr = SoftLayer.HardwareManager(self.client) - - for code, reason in mgr.get_cancellation_reasons().items(): - table.add_row([code, reason]) - - return table - - -class ServerPowerOff(environment.CLIRunnable): - """ -usage: sl server power-off [options] - -Power off an active server -""" - action = 'power-off' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - if args['--really'] or formatting.confirm('This will power off the ' - 'server with id %s ' - 'Continue?' % hw_id): - self.client['Hardware_Server'].powerOff(id=hw_id) - else: - raise exceptions.CLIAbort('Aborted.') - - -class ServerReboot(environment.CLIRunnable): - """ -usage: sl server reboot [--hard | --soft] [options] - -Reboot an active server - -Optional: - --hard Perform an abrupt reboot - --soft Perform a graceful reboot -""" - action = 'reboot' - options = ['confirm'] - - def execute(self, args): - hardware_server = self.client['Hardware_Server'] - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - if args['--really'] or formatting.confirm('This will power off the ' - 'server with id %s. ' - 'Continue?' % hw_id): - if args['--hard']: - hardware_server.rebootHard(id=hw_id) - elif args['--soft']: - hardware_server.rebootSoft(id=hw_id) - else: - hardware_server.rebootDefault(id=hw_id) - else: - raise exceptions.CLIAbort('Aborted.') - - -class ServerPowerOn(environment.CLIRunnable): - """ -usage: sl server power-on [options] - -Power on a server -""" - action = 'power-on' - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - self.client['Hardware_Server'].powerOn(id=hw_id) - - -class ServerPowerCycle(environment.CLIRunnable): - """ -usage: sl server power-cycle [options] - -Issues power cycle to server via the power strip -""" - action = 'power-cycle' - options = ['confirm'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - - if args['--really'] or formatting.confirm('This will power off the ' - 'server with id %s. ' - 'Continue?' % hw_id): - self.client['Hardware_Server'].powerCycle(id=hw_id) - else: - raise exceptions.CLIAbort('Aborted.') - - -class NicEditServer(environment.CLIRunnable): - """ -usage: sl server nic-edit (public | private) --speed=SPEED - [options] - -Manage NIC settings - -Options: - --speed=SPEED Port speed. 0 disables the port. - [Options: 0, 10, 100, 1000, 10000] -""" - action = 'nic-edit' - - def execute(self, args): - public = args['public'] - - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - - mgr.change_port_speed(hw_id, public, args['--speed']) - - -class ListChassisServer(environment.CLIRunnable): - """ -usage: sl server list-chassis [options] - -Display a list of chassis available for ordering dedicated servers. -""" - action = 'list-chassis' - - def execute(self, args): - table = formatting.Table(['Code', 'Chassis']) - table.align['Code'] = 'r' - table.align['Chassis'] = 'l' - - mgr = SoftLayer.HardwareManager(self.client) - chassis = mgr.get_available_dedicated_server_packages() - - for chassis in chassis: - table.add_row([chassis[0], chassis[1]]) - - return table - - -class ServerRescue(environment.CLIRunnable): - """ -usage: sl server rescue [options] - -Reboot server into a rescue image - - -""" - action = 'rescue' - options = ['confirm'] - - def execute(self, args): - server = SoftLayer.HardwareManager(self.client) - server_id = helpers.resolve_id(server.resolve_ids, - args.get(''), - 'hardware') - - if args['--really'] or formatting.confirm( - "This action will reboot this server. " - "Continue?"): - - server.rescue(server_id) - else: - raise exceptions.CLIAbort('Aborted') - - -class ServerCreateOptions(environment.CLIRunnable): - """ -usage: sl server create-options [options] - -Output available available options when creating a dedicated server with the -specified chassis. - -Options: - --all Show all options. default if no other option provided - --controller Show disk controller options - --cpu Show CPU options - --datacenter Show datacenter options - --disk Show disk options - --memory Show memory size options - --nic Show NIC speed options - --os Show operating system options -""" - - action = 'create-options' - options = ['datacenter', 'cpu', 'memory', 'os', 'disk', 'nic', - 'controller'] - - def execute(self, args): - mgr = SoftLayer.HardwareManager(self.client) - - table = formatting.KeyValueTable(['Name', 'Value']) - table.align['Name'] = 'r' - table.align['Value'] = 'l' - - chassis_id = args.get('') - - found = False - for chassis in mgr.get_available_dedicated_server_packages(): - if chassis_id == str(chassis[0]): - found = True - break - - if not found: - raise exceptions.CLIAbort('Invalid chassis specified.') - - ds_options = mgr.get_dedicated_server_create_options(chassis_id) - - show_all = True - for opt_name in self.options: - if args.get("--" + opt_name): - show_all = False - break - - if args['--all']: - show_all = True - - # Determine if this is a "Bare Metal Instance" or regular server - bmc = False - if chassis_id == str(mgr.get_bare_metal_package_id()): - bmc = True - - if args['--datacenter'] or show_all: - results = self.get_create_options(ds_options, 'datacenter')[0] - - table.add_row([results[0], formatting.listing(sorted(results[1]))]) - - if (args['--cpu'] or show_all) and not bmc: - results = self.get_create_options(ds_options, 'cpu') - - cpu_table = formatting.Table(['ID', 'Description']) - cpu_table.align['ID'] = 'r' - cpu_table.align['Description'] = 'l' - - for result in sorted(results, key=lambda x: x[1]): - cpu_table.add_row([result[1], result[0]]) - table.add_row(['cpu', cpu_table]) - - if (args['--memory'] or show_all) and not bmc: - results = self.get_create_options(ds_options, 'memory')[0] - - table.add_row([results[0], formatting.listing( - item[0] for item in sorted(results[1]))]) - - if bmc and (show_all or args['--memory'] or args['--cpu']): - results = self.get_create_options(ds_options, 'server_core') - memory_cpu_table = formatting.Table(['memory', 'cpu']) - for result in results: - memory_cpu_table.add_row([ - result[0], - formatting.listing( - [item[0] for item in sorted( - result[1], key=lambda x: int(x[0]) - )])]) - table.add_row(['memory/cpu', memory_cpu_table]) - - if args['--os'] or show_all: - results = self.get_create_options(ds_options, 'os') - - for result in results: - table.add_row([ - result[0], - formatting.listing( - [item[0] for item in sorted(result[1])], - separator=os.linesep - )]) - - if args['--disk'] or show_all: - results = self.get_create_options(ds_options, 'disk')[0] - - table.add_row([ - results[0], - formatting.listing( - [item[0] for item in sorted(results[1])], - separator=os.linesep - )]) - - if args['--nic'] or show_all: - results = self.get_create_options(ds_options, 'nic') - - for result in results: - table.add_row([result[0], formatting.listing( - item[0] for item in sorted(result[1],))]) - - if (args['--controller'] or show_all) and not bmc: - results = self.get_create_options(ds_options, 'disk_controller')[0] - - table.add_row([results[0], formatting.listing( - item[0] for item in sorted(results[1],))]) - - return table - - def get_create_options(self, ds_options, section, pretty=True): - """ This method can be used to parse the bare metal instance creation - options into different sections. This can be useful for data validation - as well as printing the options on a help screen. - - :param dict ds_options: The instance options to parse. Must come from - the .get_bare_metal_create_options() function - in the HardwareManager. - :param string section: The section to parse out. - :param bool pretty: If true, it will return the results in a 'pretty' - format that's easier to print. - """ - return_value = None - - if 'datacenter' == section: - datacenters = [loc['keyname'] - for loc in ds_options['locations']] - return_value = [('datacenter', datacenters)] - elif 'cpu' == section and 'server' in ds_options['categories']: - results = [] - - for item in ds_options['categories']['server']['items']: - results.append(( - item['description'], - item['price_id'] - )) - - return_value = results - elif 'memory' == section and 'ram' in ds_options['categories']: - ram = [] - for option in ds_options['categories']['ram']['items']: - ram.append((int(option['capacity']), option['price_id'])) - - return_value = [('memory', ram)] - elif ('server_core' == section - and 'server_core' in ds_options['categories']): - mem_options = {} - cpu_regex = re.compile(r'(\d+) x ') - memory_regex = re.compile(r' - (\d+) GB Ram', re.I) - - for item in ds_options['categories']['server_core']['items']: - cpu = cpu_regex.search(item['description']).group(1) - memory = memory_regex.search(item['description']).group(1) - - if cpu and memory: - if memory not in mem_options: - mem_options[memory] = [] - - mem_options[memory].append((cpu, item['price_id'])) - - results = [] - for memory in sorted(mem_options.keys(), key=int): - key = memory - - if pretty: - key = memory - - results.append((key, mem_options[memory])) - - return_value = results - elif 'os' == section: - os_regex = re.compile(r'(^[A-Za-z\s\/\-]+) ([\d\.]+)') - bit_regex = re.compile(r' \((\d+)\s*bit') - extra_regex = re.compile(r' - (.+)\(') - - os_list = {} - flat_list = [] - - # Loop through the operating systems and get their OS codes - for opsys in ds_options['categories']['os']['items']: - if 'Windows Server' in opsys['description']: - os_code = self._generate_windows_code(opsys['description']) - else: - os_results = os_regex.search(opsys['description']) - - # Skip this operating system if it's not parsable - if os_results is None: - continue - - name = os_results.group(1) - version = os_results.group(2) - bits = bit_regex.search(opsys['description']) - extra_info = extra_regex.search(opsys['description']) - - if bits: - bits = bits.group(1) - if extra_info: - extra_info = extra_info.group(1) - - os_code = self._generate_os_code(name, version, bits, - extra_info) - - name = os_code.split('_')[0] - - if name not in os_list: - os_list[name] = [] - - os_list[name].append((os_code, opsys['price_id'])) - flat_list.append((os_code, opsys['price_id'])) - - if pretty: - results = [] - for opsys in sorted(os_list.keys()): - results.append(('os (%s)' % opsys, os_list[opsys])) - - return_value = results - else: - return_value = [('os', flat_list)] - - elif 'disk' == section: - disks = [] - type_regex = re.compile(r'^[\d\.]+[GT]B\s+(.+)$') - for disk in ds_options['categories']['disk0']['items']: - disk_type = 'SATA' - disk_type = type_regex.match(disk['description']).group(1) - - disk_type = disk_type.replace('RPM', '').strip() - disk_type = disk_type.replace(' ', '_').upper() - disk_type = str(int(disk['capacity'])) + '_' + disk_type - disks.append((disk_type, disk['price_id'], disk['id'])) - - return_value = [('disk', disks)] - elif 'nic' == section: - single = [] - dual = [] - - for item in ds_options['categories']['port_speed']['items']: - if 'dual' in item['description'].lower(): - dual.append((str(int(item['capacity'])) + '_DUAL', - item['price_id'])) - else: - single.append((str(int(item['capacity'])), - item['price_id'])) - - return_value = [('single nic', single), ('dual nic', dual)] - elif 'disk_controller' == section: - options = [] - for item in ds_options['categories']['disk_controller']['items']: - text = item['description'].replace(' ', '') - - if 'Non-RAID' == text: - text = 'None' - - options.append((text, item['price_id'])) - - return_value = [('disk_controllers', options)] - - return return_value - - def _generate_os_code(self, name, version, bits, extra_info): - """ Encapsulates the code for generating the operating system code. """ - name = name.replace(' Linux', '') - name = name.replace('Enterprise', '') - name = name.replace('GNU/Linux', '') - - os_code = name.strip().replace(' ', '_').upper() - - if os_code.startswith('RED_HAT'): - os_code = 'REDHAT' - - if 'UBUNTU' in os_code: - version = re.sub(r'\.\d+', '', version) - - os_code += '_' + version.replace('.0', '') - - if bits: - os_code += '_' + bits - - if extra_info: - garbage = ['Install', '(32 bit)', '(64 bit)'] - - for obj in garbage: - extra_info = extra_info.replace(obj, '') - - os_code += '_' + extra_info.strip().replace(' ', '_').upper() - - return os_code - - def _generate_windows_code(self, description): - """ Separates the code for generating the Windows OS code - since it's significantly different from the rest. - """ - version_check = re.search(r'Windows Server (\d+)', description) - version = version_check.group(1) - - os_code = 'WIN_' + version - - if 'Datacenter' in description: - os_code += '-DC' - elif 'Enterprise' in description: - os_code += '-ENT' - else: - os_code += '-STD' - - if 'ith R2' in description: - os_code += '-R2' - elif 'ith Hyper-V' in description: - os_code += '-HYPERV' - - bit_check = re.search(r'\((\d+)\s*bit', description) - if bit_check: - os_code += '_' + bit_check.group(1) - - return os_code - - -class CreateServer(environment.CLIRunnable): - """ -usage: sl server create [--disk=SIZE...] [--key=KEY...] [options] - -Order/create a dedicated server. See 'sl server list-chassis' and -'sl server create-options' for valid options. - -Required: - -H --hostname=HOST Host portion of the FQDN. example: server - -D --domain=DOMAIN Domain portion of the FQDN. example: example.com - --chassis=CHASSIS The chassis to use for the new server - -c --cpu=CPU CPU model - -o OS, --os=OS OS install code. - -m --memory=MEMORY Memory in gigabytes. example: 4 - --billing=BILLING Billing rate. Options are "monthly" (default) or - "hourly". The hourly rate is only available on the - "Bare Metal Instance" chassis. - -Optional: - -d, --datacenter=DC Datacenter name - Note: Omitting this value defaults to the first - available datacenter - -n, --network=MBPS Network port speed in Mbps - --disk=SIZE... Disks. Can be specified multiple times - --controller=RAID The RAID configuration for the server. - Defaults to None. - -i, --postinstall=URI Post-install script to download - -k KEY, --key=KEY SSH keys to assign to the root user. Can be specified - multiple times. - --test Do not create the server, just get a quote - --vlan_public=VLAN The ID of the public VLAN on which you want the - hardware placed - --vlan_private=VLAN The ID of the private VLAN on which you want the - hardware placed - -t, --template=FILE A template file that defaults the command-line - options using the long name in INI format - --export=FILE Exports options to a template file -""" - action = 'create' - options = ['confirm'] - required_params = ['--hostname', '--domain', '--chassis', '--cpu', - '--memory', '--os'] - - def execute(self, args): - template.update_with_template_args(args, list_args=['--disk', '--key']) - mgr = SoftLayer.HardwareManager(self.client) - self._validate_args(args) - - ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) - - order = self._process_args(args, ds_options) - - # Do not create hardware server with --test or --export - do_create = not (args['--export'] or args['--test']) - - output = None - if args.get('--test'): - result = mgr.verify_order(**order) - - table = formatting.Table(['Item', 'cost']) - table.align['Item'] = 'r' - table.align['cost'] = 'r' - - total = 0.0 - for price in result['prices']: - total += float(price.get('recurringFee', 0.0)) - rate = "%.2f" % float(price['recurringFee']) - - table.add_row([price['item']['description'], rate]) - - table.add_row(['Total monthly cost', "%.2f" % total]) - output = [] - output.append(table) - output.append(formatting.FormattedItem( - '', - ' -- ! Prices reflected here are retail and do not ' - 'take account level discounts and are not guaranteed.')) - - if args['--export']: - export_file = args.pop('--export') - template.export_to_template(export_file, args, - exclude=['--wait', '--test']) - return 'Successfully exported options to a template file.' - - if do_create: - if args['--really'] or formatting.confirm( - "This action will incur charges on your account. " - "Continue?"): - result = mgr.place_order(**order) - - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['id', result['orderId']]) - table.add_row(['created', result['orderDate']]) - output = table - else: - raise exceptions.CLIAbort('Aborting dedicated server order.') - - return output - - def _process_args(self, args, ds_options): - """ - Helper method to centralize argument processing without convoluting - code flow of the main execute method. - """ - mgr = SoftLayer.HardwareManager(self.client) - - order = { - 'hostname': args['--hostname'], - 'domain': args['--domain'], - 'bare_metal': False, - 'package_id': args['--chassis'], - } - - # Determine if this is a "Bare Metal Instance" or regular server - bmc = False - if args['--chassis'] == str(mgr.get_bare_metal_package_id()): - bmc = True - - # Convert the OS code back into a price ID - os_price = self._get_price_id_from_options(ds_options, 'os', - args['--os']) - - if os_price: - order['os'] = os_price - else: - raise exceptions.CLIAbort('Invalid operating system specified.') - - order['location'] = args['--datacenter'] or 'FIRST_AVAILABLE' - - if bmc: - order['server'] = self._get_cpu_and_memory_price_ids( - ds_options, args['--cpu'], args['--memory']) - order['bare_metal'] = True - - if args['--billing'] == 'hourly': - order['hourly'] = True - else: - order['server'] = args['--cpu'] - order['ram'] = self._get_price_id_from_options( - ds_options, 'memory', int(args['--memory'])) - - # Set the disk sizes - disk_prices = [] - disk_number = 0 - for disk in args.get('--disk'): - disk_price = self._get_disk_price(ds_options, disk, disk_number) - disk_number += 1 - if disk_price: - disk_prices.append(disk_price) - - if not disk_prices: - disk_prices.append(self._get_default_value(ds_options, 'disk0')) - - order['disks'] = disk_prices - - # Set the disk controller price - if not bmc: - if args.get('--controller'): - dc_price = self._get_price_id_from_options( - ds_options, 'disk_controller', args.get('--controller')) - else: - dc_price = self._get_price_id_from_options(ds_options, - 'disk_controller', - 'None') - - order['disk_controller'] = dc_price - - # Set the port speed - port_speed = args.get('--network') or '100' - - nic_price = self._get_price_id_from_options(ds_options, 'nic', - port_speed) - - if nic_price: - order['port_speed'] = nic_price - else: - raise exceptions.CLIAbort('Invalid NIC speed specified.') - - if args.get('--postinstall'): - order['post_uri'] = args.get('--postinstall') - - # Get the SSH keys - if args.get('--key'): - keys = [] - for key in args.get('--key'): - resolver = SoftLayer.SshKeyManager(self.client).resolve_ids - key_id = helpers.resolve_id(resolver, key, 'SshKey') - keys.append(key_id) - order['ssh_keys'] = keys - - if args.get('--vlan_public'): - order['public_vlan'] = args['--vlan_public'] - - if args.get('--vlan_private'): - order['private_vlan'] = args['--vlan_private'] - - return order - - def _validate_args(self, args): - """ Raises an ArgumentError if the given arguments are not valid """ - invalid_args = [k for k in self.required_params if args.get(k) is None] - if invalid_args: - raise exceptions.ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - - def _get_default_value(self, ds_options, option): - """ Returns a 'free' price id given an option """ - if option not in ds_options['categories']: - return - - for item in ds_options['categories'][option]['items']: - if not any([ - float(item.get('setupFee', 0)), - float(item.get('recurringFee', 0)), - float(item.get('hourlyRecurringFee', 0)), - float(item.get('oneTimeFee', 0)), - float(item.get('laborFee', 0)), - ]): - return item['price_id'] - - def _get_disk_price(self, ds_options, value, number): - """ Returns a price id that matches a given disk config """ - if not number: - return self._get_price_id_from_options(ds_options, 'disk', value) - # This will get the item ID for the matching identifier string, which - # we can then use to get the price ID for our specific disk - item_id = self._get_price_id_from_options(ds_options, 'disk', - value, True) - key = 'disk' + str(number) - if key in ds_options['categories']: - for item in ds_options['categories'][key]['items']: - if item['id'] == item_id: - return item['price_id'] - - def _get_cpu_and_memory_price_ids(self, ds_options, cpu_value, - memory_value): - """ - Returns a price id for a cpu/memory pair in pre-configured servers - (formerly known as BMC). - """ - ds_obj = ServerCreateOptions() - for memory, options in ds_obj.get_create_options(ds_options, - 'server_core', - False): - if memory == memory_value: - for cpu_size, price_id in options: - if cpu_size == cpu_value: - return price_id - - def _get_price_id_from_options(self, ds_options, option, value, - item_id=False): - """ Returns a price_id for a given option and value """ - ds_obj = ServerCreateOptions() - - for _, options in ds_obj.get_create_options(ds_options, option, False): - for item_options in options: - if item_options[0] == value: - if not item_id: - return item_options[1] - return item_options[2] - - -class EditServer(environment.CLIRunnable): - """ -usage: sl server edit [options] - -Edit hardware details - -Options: - -D --domain=DOMAIN Domain portion of the FQDN example: example.com - -F --userfile=FILE Read userdata from file - -H --hostname=HOST Host portion of the FQDN. example: server - -u --userdata=DATA User defined metadata string -""" - action = 'edit' - - def execute(self, args): - data = {} - - if args['--userdata'] and args['--userfile']: - raise exceptions.ArgumentError( - '[-u | --userdata] not allowed with [-F | --userfile]') - if args['--userfile']: - if not os.path.exists(args['--userfile']): - raise exceptions.ArgumentError( - 'File does not exist [-u | --userfile] = %s' - % args['--userfile']) - - if args.get('--userdata'): - data['userdata'] = args['--userdata'] - elif args.get('--userfile'): - with open(args['--userfile'], 'r') as userfile: - data['userdata'] = userfile.read() - - data['hostname'] = args.get('--hostname') - data['domain'] = args.get('--domain') - - mgr = SoftLayer.HardwareManager(self.client) - hw_id = helpers.resolve_id(mgr.resolve_ids, - args.get(''), - 'hardware') - if not mgr.edit(hw_id, **data): - raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/CLI/modules/snapshot.py b/SoftLayer/CLI/modules/snapshot.py deleted file mode 100644 index a7d95b3b5..000000000 --- a/SoftLayer/CLI/modules/snapshot.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -usage: sl snapshot [] [...] [options] - -Manage, order, delete iSCSI snapshots - -The available commands are: - cancel Cancel an iSCSI snapshot - create Create a snapshot of given iSCSI volume - create-space Orders space for storing snapshots - list List snpshots of given iSCSI - restore-volume Restores volume from existing snapshot - -For several commands will be asked for.This can be the id -of iSCSI volume or iSCSI snapshot. -""" -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers -from SoftLayer import utils - - -class CreateSnapshot(environment.CLIRunnable): - - """ -usage: sl snapshot create [options] - -Create a snapshot of the iSCSI volume. - -Examples: - sl snapshot create 123456 --note='Backup' - sl snapshot create 123456 - -Options: - --notes=NOTE An optional snapshot's note - -""" - action = 'create' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - notes = args.get('--notes') - iscsi_mgr.create_snapshot(iscsi_id, notes) - - -class CreateSnapshotSpace(environment.CLIRunnable): - - """ -usage: sl snapshot create-space [options] - -Orders snapshot space for given iSCSI. - -Examples: - sl snapshot create-space 123456 --capacity=20 - -Required : - --capacity=CAPACITY Size of snapshot space to create -""" - - action = 'create-space' - required_params = ['--capacity'] - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - invalid_args = [k for k in self.required_params if args.get(k) is None] - if invalid_args: - raise exceptions.ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - capacity = args.get('--capacity') - iscsi_mgr.create_snapshot_space(iscsi_id, capacity) - - -class CancelSnapshot(environment.CLIRunnable): - - """ -usage: sl snapshot cancel [options] - -Cancel/Delete iSCSI snapshot. - -""" - action = 'cancel' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') - iscsi_mgr.delete_snapshot(snapshot_id) - - -class RestoreVolumeFromSnapshot(environment.CLIRunnable): - - """ -usage: sl snapshot restore-volume - -restores volume from existing snapshot. - -""" - action = 'restore-volume' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - volume_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') - iscsi_mgr.restore_from_snapshot(volume_id, snapshot_id) - - -class ListSnapshots(environment.CLIRunnable): - - """ -usage: sl snapshot list - -List iSCSI Snapshots -""" - action = 'list' - - def execute(self, args): - iscsi_mgr = SoftLayer.ISCSIManager(self.client) - iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') - iscsi = self.client['Network_Storage_Iscsi'] - snapshots = iscsi.getPartnerships( - mask='volumeId,partnerVolumeId,createDate,type', id=iscsi_id) - snapshots = [utils.NestedDict(n) for n in snapshots] - - table = formatting.Table([ - 'id', - 'createDate', - 'name', - 'description', - ]) - - for snapshot in snapshots: - table.add_row([ - snapshot['partnerVolumeId'], - snapshot['createDate'], - snapshot['type']['name'], - snapshot['type']['description'], - ]) - return table diff --git a/SoftLayer/CLI/modules/sshkey.py b/SoftLayer/CLI/modules/sshkey.py deleted file mode 100644 index 9649f355f..000000000 --- a/SoftLayer/CLI/modules/sshkey.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -usage: sl sshkey [] [...] [options] - -Manage SSH keys - -The available commands are: - add Add a new SSH key to your account - remove Removes an SSH key - edit Edits information about the SSH key - list Display a list of SSH keys on your account - print Prints out an SSH key -""" -# :license: MIT, see LICENSE for more details. - -from os import path - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers - - -class AddSshKey(environment.CLIRunnable): - """ -usage: sl sshkey add