diff --git a/.gitignore b/.gitignore index 5e820a5..193319c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ Session.vim *~ # auto-generated tag files tags + +# virtual environments +venv +.venv diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..83ee5ab --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog +All notable changes to this project will be documented in this file. + +Changelog was added with version 2.0.0. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.0.0] - 2021-05-05 + +Python 2 is no longer supported. This is a maintenance release without +that many added features. It was mostly done to make future development easier. + +### Added +- Storage upload accepts filenames in strings and PathLike or BinaryIO variables. +- Code style is now guarded by Black, flake8, isort etc. +- Improved documentation and its examples, especially regarding server creation and storage uploads. + +### Changed +- Huge amount of fixups in project tests, style and imports by [akx](https://github.com/akx). Thank you! :heart: +- Zone default from storage creation has been removed, making zone a required variable with `create_storage()`. +- Passwords for server user are not created by default if SSH keys are provided. +- Tests and deployments moved fully from CircleCI to GitHub Actions. +- Fixed storage upload not reading the file into memory before uploading. +- Moved to fully using setup.cfg instead of requirements.txt. + +### Removed +- Python 2 support. diff --git a/README.md b/README.md index 08779f1..f1cafe2 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ [![PyPI version](https://badge.fury.io/py/upcloud-api.svg)](https://badge.fury.io/py/upcloud-api) [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/UpCloudLtd/upcloud-python-api/blob/master/LICENSE) -OOP-based api client for [UpCloud's API](https://developers.upcloud.com/1.3/). Features most of the API's functionality and some convenience functions that combine several API endpoints and logic. +OOP-based API client for [UpCloud's API](https://developers.upcloud.com/1.3/). Includes most of the API +functionality and some convenience functions that combine several API endpoints and logic. -Please test all of your use cases thoroughly before actual production use. Using a separate UpCloud account for testing / developing the client is recommended. +Please test all of your use cases thoroughly before actual production use. Using a separate UpCloud account for +testing / developing the client is recommended. ## Installation @@ -15,13 +17,13 @@ Please test all of your use cases thoroughly before actual production use. Using pip install upcloud-api ``` -Alternatively, if you want the newest master or a devel branch - clone the project and run: +Alternatively, if you want the newest (possibly not yet released) stuff, clone the project and run: ``` bash python setup.py install ``` -### Supported Python in API v2.0.0 +### Supported Python versions in API v2.0.0 - Python 3.6 - Python 3.7 @@ -29,20 +31,20 @@ python setup.py install - Python 3.9 - PyPy3 -**We don't recommend using Python 2:** +**Python 2 has been deprecated** -- Python 2.7 is supported in API < v2.0.0 +- Python 2.7 is supported in older API versions (< v2.0.0), still available in [PyPI](https://pypi.org/project/upcloud-api/1.0.1/). ## Changelog -- See the [Releases page](https://github.com/UpCloudLtd/upcloud-python-api/releases) +- Changelog is available [in its own file](CHANGELOG.md), starting from version 2.0.0. ## Usage -Note that the API finishes the request before the server is shutdown. Poll the server details to monitor server status. -You must take this into account in your automations. +More usage examples are available under [docs/]. If there's a specific thing you're interested in, +but are not able to get working, please [contact UpCloud support](https://upcloud.com/contact/). -### Defining and creating Servers +### Defining and creating servers ```python @@ -61,22 +63,21 @@ login_user = login_user_block( cluster = { 'web1': Server( - core_number=1, # CPU cores - memory_amount=1024, # RAM in MB + plan='2xCPU-4GB', hostname='web1.example.com', zone='uk-lon1', # All available zones with ids can be retrieved by using manager.get_zones() storage_devices=[ - # OS: 01000000-0000-4000-8000-000030200200, all available os templates can be retrieved by calling manager.get_templates() + # OS: template storage UUID, all available os templates can be retrieved by calling manager.get_templates() # Note: the storage os template uuid:s will change when OS is updated. So check that the UUID is correct # default tier: maxIOPS, the 100k IOPS storage backend Storage(os='01000000-0000-4000-8000-000030200200', size=10), - # secondary storage, hdd for reduced cost + # secondary storage, hdd for reduced speed & cost Storage(size=100, tier='hdd') ], login_user=login_user # user and ssh-keys ), 'web2': Server( - core_number=1, + plan='2xCPU-4GB', memory_amount=1024, hostname='web2.example.com', zone='uk-lon1', @@ -87,7 +88,9 @@ cluster = { login_user=login_user ), 'db': Server( - plan='2xCPU-4GB', # use a preconfigured plan, instead of custom + # use custom resources, instead of a plan + core_number=12, # CPU cores + memory_amount=49152, # RAM in MB hostname='db.example.com', zone='uk-lon1', storage_devices=[ @@ -97,8 +100,7 @@ cluster = { login_user=login_user ), 'lb': Server( - core_number=2, - memory_amount=1024, + plan='2xCPU-4GB', hostname='balancer.example.com', zone='uk-lon1', storage_devices=[ @@ -109,7 +111,7 @@ cluster = { } for server in cluster: - manager.create_server(cluster[server]) # automatically populates the Server objects with data from API + manager.create_server(cluster[server]) # creates all server objects defined in cluster ``` @@ -156,20 +158,19 @@ server.start() ``` -### Clone a server +### Clone a new server from existing storage Cloning is done by giving existing storage uuid to storage_devices. Note that size of the storage -must be defined and must be at least same size than storage being cloned. +must be defined and must be at least the same size as the storage being cloned. ```python clone = Server( - core_number=1, - memory_amount=1024, + plan='2xCPU-4GB', hostname='cloned.server', zone='fi-hel1', storage_devices=[ Storage( - uuid='012bea57-0f70-4194-82d0-b3d25f4a018b', + uuid='012bea57-0f70-4154-84d0-b3d25f4a018b', size=50 # size must be defined and it has to be at least same size than storage being cloned ), ] @@ -190,14 +191,14 @@ server.to_dict() ``` -### GET resources +### Get resources ```python servers = manager.get_servers() -server1 = manager.get_server(UUID) # e.g servers[0].uuid +server1 = manager.get_server(uuid) # e.g servers[0].uuid storages = manager.get_storages() -storage1 = manager.get_storage(UUID) # e.g server1.storage_devices[0].uuid +storage1 = manager.get_storage(uuid) # e.g server1.storage_devices[0].uuid ip_addrs = manager.get_ips() ip_addr = manager.get_ip(address) # e.g server1.ip_addresses[0].address @@ -209,39 +210,36 @@ Set up environment and install dependencies: ``` bash # run at project root, python3 and virtualenv must be installed -virtualenv ENV -source ENV/bin/activate -pip install -r requirements-dev.txt +virtualenv venv +source venv/bin/activate ``` -Install the package in editable mode, as mentioned in -[https://docs.pytest.org/en/stable/goodpractices.html](https://docs.pytest.org/en/stable/goodpractices.html) +Install the package in editable mode. -```python +```bash # run at project root pip install -e . ``` -Tests located in `project_root/test/` directory. Run with: +Tests are located under `test/`. Run with: -```python +```bash py.test test/ ``` To test against all supported python versions, run: -```python +```bash tox ``` -To check for possible vulnerabilities in python packages, run: - -```python -safety check -``` -The project also supplies a small test suite to test against the live API at `test/live_test.py`. This suite is NOT run with `py.test` as it will permanently remove all resources related to an account. It should only be run with a throwaway dev-only account when preparing for a new release. It is not shipped with PyPI releases. See source code on how to run the live tests. +The project also supplies a small test suite to test against the live API at `test/live_test.py`. +This suite is NOT run with `py.test` as it will permanently remove all resources related to an account. +It should only be run with a throwaway dev-only account when preparing for a new release. It is not shipped with +PyPI releases. See source code on how to run the live test. ## Bugs, Issues, Problems, Ideas -Feel free to open a new issue : ) +Please report issues and features requests through +[the issues page](https://github.com/UpCloudLtd/upcloud-python-api/issues). diff --git a/docs/CloudManager.md b/docs/CloudManager.md index c8756cc..45e64fa 100644 --- a/docs/CloudManager.md +++ b/docs/CloudManager.md @@ -10,7 +10,7 @@ Default timeout is 10. ```python # create manager and form a token -manager = CloudManager("api-username", "password", timeout=15) # default timeout is 10 +manager = CloudManager("api-username", "password") ``` @@ -18,7 +18,6 @@ manager = CloudManager("api-username", "password", timeout=15) # default timeout ```python -# test token manager.authenticate() # alias: get_account() manager.get_account() @@ -58,6 +57,6 @@ List the possible server CPU-ram configurations. ```python -manager.get_server_sizes +manager.get_server_sizes() ``` diff --git a/docs/Firewall.md b/docs/Firewall.md index 38077a0..3b1c222 100644 --- a/docs/Firewall.md +++ b/docs/Firewall.md @@ -1,11 +1,4 @@ -The code examples use the following: -```python -import upcloud_api -from upcloud_api import FirewallRule - -manager = upcloud_api.CloudManager("username", "password") -``` # About diff --git a/docs/IP-address.md b/docs/IP-address.md index 49a5f09..3e0c814 100644 --- a/docs/IP-address.md +++ b/docs/IP-address.md @@ -17,7 +17,7 @@ The only updateable attribute is the `ptr_record`. ## List / Get -CloudManager returns IP-address objects. +CloudManager returns IPAddress objects. ```python @@ -28,7 +28,7 @@ manager.get_ip("185.20.31.125") ## Create -The new IP-address must be attached to a server and has a random address. +A new IPAddress must be attached to a server and has a random address. ```python @@ -51,7 +51,7 @@ server.add_ip("IPv6") ## Update -At the moment only the ptr_record (reverse DNS) of an IP can be changed. +At the moment only the ptr_record (reverse DNS) of an IP address can be changed. ```python diff --git a/docs/Server.md b/docs/Server.md index cd014ae..52f67da 100644 --- a/docs/Server.md +++ b/docs/Server.md @@ -1,11 +1,4 @@ -The examples use the following: -```python -import upcloud_api -from upcloud_api import Server -from upcloud_api import Storage -manager = upcloud_api.CloudManager("username", "password") -``` # Start / Stop / Restart @@ -54,7 +47,7 @@ server = Server( hostname = "web1.example.com", zone = 'uk-lon1', storage_devices = [ - Storage(os = "01000000-0000-4000-8000-000030060200", size=10), + Storage(os = "01000000-0000-4000-8000-000030200200", size=10), Storage(size=10, tier="hdd") ]) @@ -62,8 +55,7 @@ manager.create_server( server ) ``` -Currently available Storage operating systems are the following UpCloud public templates: -Valid Operating Systems cam be retrieved with 'manager.get_templates()'. More information on this method can be found in storage_mixin documentation. +Currently available operating system templates can be retrieved with 'manager.get_templates()'. More information on this method can be found in storage_mixin documentation. Please refer to the [API documentation](https://www.upcloud.com/static/downloads/upcloud-apidoc-1.1.1.pdf) for the allowed Server attributes. @@ -124,7 +116,7 @@ server.remove_ip(IP) ## Destroy -Destroys the Server instance and its IP-addresses. However, does not destroy the Storages. +Destroys the Server instance and its IP addresses. However, it does not destroy the Storages. ```python diff --git a/docs/Storage.md b/docs/Storage.md index c174514..f1bb9d2 100644 --- a/docs/Storage.md +++ b/docs/Storage.md @@ -1,29 +1,26 @@ -The code examples use the following: -```python -import upcloud_api -from upcloud_api import Storage - -manager = upcloud_api.CloudManager("username", "password") -``` # About -Storages are entirely separate from Servers and can be attached/detached from them. Storages can be created, updated and destroyed separately from servers. They can be loaded as CDROMs or disks and they can be cloned to create a new Storage that is a 1:1 clone of another one. +Storages are entirely separate from Servers and can be attached/detached from them. +Storages can be created, updated and destroyed separately from servers. They can be loaded as CDROMs or disks, +and they can be cloned into new storage devices. ### Tiers -UpCloud offers MaxIOPS (Extremely fast 100k IOPS Storage) and HDD storages. Older disks may still be on our SSD but can be moved to MaxIOPS by cloning. +UpCloud offers MaxIOPS (Extremely fast 100k IOPS Storage) and HDD storages. ``` Tiers: - "maxiops", "hdd", ( "ssd" ) + "maxiops", "hdd", ``` ### Templates -Public templates such as the 01000000-0000-4000-8000-000030060200 can be cloned by anyone to get a pre-installed server image that is immediately ready to go. A user can also create private templates for themselves out of any storage. Storages can be cloned from templates during server creation. +Public templates such as the 01000000-0000-4000-8000-000030200200 can be cloned by anyone to get a pre-installed +server image that is immediately ready to go. A user can also create private templates for themselves out of +any storage. Storages can be cloned from templates during server creation. ## List / Get @@ -45,7 +42,7 @@ Storages list filters: ## Create -Storage can be created with the CloudManager's `.create_storage(size=10, tier="maxiops", title="Storage disk", zone="fi-hel1")` +Storage can be created with the CloudManager's `create_storage()` function. ```python @@ -64,7 +61,8 @@ storage2 = manager.create_storage(zone='de-fra1', size=100) ## Update -Only the size and title of a storage can be updated. Please note that size can not be reduced and that OS level actions are required to account for the increased size. +Only the size and title of a storage can be updated. Please note that size can not be reduced and that +OS level actions are required to account for the increased size. ```python @@ -152,7 +150,8 @@ storage_clone = storage.clone(title='title of storage clone', zone='fi-hel1', ti ## Cancel clone operation Cancels a running cloning operation and deletes the incomplete copy using StorageManager. -Needs to be called from the cloned storage (object returned by clone operation) and not the storage that is being cloned. +Needs to be called from the cloned storage (object returned by clone operation) +and not the storage that is being cloned. ```python @@ -187,8 +186,8 @@ storage_backup.restore_backup() ## Templatize storage -Creates an exact copy of an existing storage resource which can be used as a template for creating new servers using StorageManager. -Method requires title to be passed. +Creates an exact copy of an existing storage resource which can be used as a template +for creating new servers using StorageManager. Method requires title to be passed. ```python diff --git a/docs/host-mixin.md b/docs/host-mixin.md index 31c8c9a..1e476d5 100644 --- a/docs/host-mixin.md +++ b/docs/host-mixin.md @@ -1,4 +1,7 @@ ## About + +Server hosts are only available for private cloud users. To get access, [contact UpCloud sales](https://upcloud.com/contact/). + ```python class HostManager(): """ diff --git a/docs/index.md b/docs/index.md index 4e45d0e..5e66f13 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,22 +1,41 @@ -# UpCloud-python-api Documentation +# UpCloud's Python API Documentation -This is the documentation for the newest version of the client. For older versions, -please download the correct source from [here](https://github.com/UpCloudLtd/upcloud-python-api/releases) and use `mkdocs` to build the documentation. +This is the documentation for the newest version of the library. For older versions, +please download the correct source from [releases](https://github.com/UpCloudLtd/upcloud-python-api/releases) +and use `mkdocs` to build the documentation. -Reading this documentation and using UpCloud-python-api requires that you are familiar with [UpCloud's API v1.2 documentation](https://www.upcloud.com/api/). Additionally, the documentation also assumes that the user refers to the source code, which is well commented and documented with Python's docstrings, for more detailed information. +This documentation includes many code examples for administrating resources on top of UpCloud. +In some cases it can help to be familiar with [UpCloud's API v1.3 documentation](https://www.upcloud.com/api/). +The code itself also has commentary & examples and is structured similarly to this documentation. -If you haven't used UpCloud's API before, please see [Getting Started With UpCloud’s API](https://www.upcloud.com/support/getting-started-with-upclouds-api/) +If you haven't used UpCloud's API before, please see +[Getting Started With UpCloud’s API](https://www.upcloud.com/support/getting-started-with-upclouds-api/). -The documentation is divided into two parts. Usage describes the basic CRUD functionality for the object representations of Server, Storage and IP-address. The CloudManager describes the API for performing direct API calls. +The documentation is divided into two parts. Usage describes the basic CRUD functionality for the object +representations of different UpCloud resources (servers, storages, networks etc). The CloudManager describes +the API for performing direct API calls. +Python package `upcloud_api` must be installed before any code examples can be tried. -The code examples use the following: +```bash +pip3 install upcloud-api +``` + +NOTE: Support for Python 2.7 ended with version 1.0.1. If you need to use it, a supporting version can be +installed with `pip install upcloud-api==1.0.1`. + +Many code examples assume that CloudManager object has already been initialized. +In addition to CloudManager, some resources might be needed to be imported. +Full example of imports is below. ```python import upcloud_api from upcloud_api import Storage from upcloud_api import Server -from upcloud_api import ZONE +from upcloud_api import FirewallRule +from upcloud_api import Network +from upcloud_api import IPAddress +from upcloud_api import ObjectStorage manager = upcloud_api.CloudManager("username", "password") ``` diff --git a/docs/network-mixin.md b/docs/network-mixin.md index 2d2c546..ab5fe0f 100644 --- a/docs/network-mixin.md +++ b/docs/network-mixin.md @@ -5,7 +5,7 @@ class NetworkManager(): Functions for managing networks. Intended to be used as a mixin for CloudManager. """ ``` -`NetworkManager` is a mixed into `CloudManager` and the following methods are available by +`NetworkManager` is a mixed into `CloudManager` and the following methods are available through it. ```python manager = CloudManager("api-username", "password") diff --git a/docs/object_storage-mixin.md b/docs/object_storage-mixin.md index 3a5395a..87c3153 100644 --- a/docs/object_storage-mixin.md +++ b/docs/object_storage-mixin.md @@ -5,7 +5,7 @@ class ObjectStorageManager(): Functions for managing Object Storages. Intended to be used as a mixin for CloudManager. """ ``` -`ObjectStorageManager` is a mixed into `CloudManager` and the following methods are available by +`ObjectStorageManager` is a mixed into `CloudManager` and the following methods are available through it. ```python manager = CloudManager("api-username", "password") diff --git a/setup.cfg b/setup.cfg index b677db8..037abb2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = upcloud_api description = UpCloud API Client long_description = file: README.md long_description_content_type = text/markdown -opriginal_author = Elias Nygren +original_author = Elias Nygren maintainer = UpCloud maintainer_email = hello@upcloud.com url = https://github.com/UpCloudLtd/upcloud-python-api diff --git a/test/test_server_creation.py b/test/test_server_creation.py index d783a54..597436b 100644 --- a/test/test_server_creation.py +++ b/test/test_server_creation.py @@ -124,7 +124,7 @@ def test_server_prepare_post_body_optional_attributes(self): assert body['server']['password_delivery'] == 'email' assert body['server']['login_user'] == { 'username': 'upclouduser', - 'create_password': 'yes', + 'create_password': 'no', 'ssh_keys': {'ssh_key': ['this-is-a-SSH-key']}, } assert body['server']['avoid_host'] == '12345678' diff --git a/upcloud_api/__init__.py b/upcloud_api/__init__.py index 908e690..c3ddfc5 100644 --- a/upcloud_api/__init__.py +++ b/upcloud_api/__init__.py @@ -2,13 +2,13 @@ Python Interface to UpCloud's API. """ -__version__ = '1.0.1' -__author__ = 'Elias Nygren' -__author_email__ = 'elias.nygren@upcloud.com' +__version__ = '2.0.0' +__author__ = 'Developers from UpCloud & elsewhere' +__author_email__ = 'hello@upcloud.com' __maintainer__ = 'UpCloud' __maintainer_email__ = 'hello@upcloud.com' __license__ = 'MIT' -__copyright__ = 'Copyright (c) 2015 UpCloud' +__copyright__ = 'Copyright (c) 2015- UpCloud' from upcloud_api.cloud_manager import CloudManager from upcloud_api.errors import UpCloudAPIError, UpCloudClientError diff --git a/upcloud_api/cloud_manager/server_mixin.py b/upcloud_api/cloud_manager/server_mixin.py index f24887b..0d2019a 100644 --- a/upcloud_api/cloud_manager/server_mixin.py +++ b/upcloud_api/cloud_manager/server_mixin.py @@ -91,7 +91,7 @@ def create_server(self, server: Server) -> Server: hostname = "my.example.1", zone = "uk-lon1", storage_devices = [ - Storage(os = "01000000-0000-4000-8000-000030060200", size=10, tier=maxiops, title='The OS drive'), + Storage(os = "01000000-0000-4000-8000-000030200200", size=10, tier=maxiops, title='Example OS disk'), Storage(size=10), Storage() title = "My Example Server" @@ -103,10 +103,11 @@ def create_server(self, server: Server) -> Server: - title defaults to hostname + " OS disk" and hostname + " storage disk id" (id is a running starting from 1) - tier defaults to maxiops - - valid operating systems are: - "CentOS 6.10", "CentOS 7.6" - "Ubuntu 12.04", "01000000-0000-4000-8000-000030060200" - "Windows 2012", "Windows 2016" + - valid operating systems are for example: + * CentOS 8: 01000000-0000-4000-8000-000050010400 + * Debian 10: 01000000-0000-4000-8000-000020050100 + * Ubuntu 20.04: 01000000-0000-4000-8000-000030200200 + * Windows 2019: 01000000-0000-4000-8000-000010070300 """ if isinstance(server, Server): body = server.prepare_post_body() diff --git a/upcloud_api/server.py b/upcloud_api/server.py index 8fd61ea..e93a09f 100644 --- a/upcloud_api/server.py +++ b/upcloud_api/server.py @@ -10,14 +10,14 @@ from upcloud_api import CloudManager -def login_user_block(username, ssh_keys, create_password=True): +def login_user_block(username, ssh_keys, create_password=False): """ Helper function for creating Server.login_user blocks. (see: https://www.upcloud.com/api/8-servers/#create-server) """ block = { - 'create_password': 'yes' if create_password is True else 'no', + 'create_password': 'yes' if create_password else 'no', 'ssh_keys': {'ssh_key': ssh_keys}, }