Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions .github/workflows/e2e-test-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
on:
pull_request:
workflow_dispatch:
inputs:
test_path:
description: 'Enter specific test path. E.g. linode_client/test_linode_client.py, models/test_account.py'
required: false
sha:
description: 'The hash value of the commit.'
required: true
pull_request_number:
description: 'The number of the PR.'
required: false

name: PR E2E Tests

jobs:
integration-fork-ubuntu:
runs-on: ubuntu-latest
if:
github.event_name == 'workflow_dispatch' && inputs.sha != ''

steps:
- uses: actions-ecosystem/action-regex-match@v2
id: validate-tests
with:
text: ${{ inputs.test_path }}
regex: '[^a-z0-9-:.\/_]' # Tests validation
flags: gi

# Check out merge commit
- name: Checkout PR
uses: actions/checkout@v3
with:
ref: ${{ inputs.sha }}

- name: Get the hash value of the latest commit from the PR branch
uses: octokit/graphql-action@v2.x
id: commit-hash
if: ${{ inputs.pull_request_number != '' }}
with:
query: |
query PRHeadCommitHash($owner: String!, $repo: String!, $pr_num: Int!) {
repository(owner:$owner, name:$repo) {
pullRequest(number: $pr_num) {
headRef {
target {
... on Commit {
oid
}
}
}
}
}
}
owner: ${{ github.event.repository.owner.login }}
repo: ${{ github.event.repository.name }}
pr_num: ${{ fromJSON(inputs.pull_request_number) }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Update system packages
run: sudo apt-get update -y

- name: Install system deps
run: sudo apt-get install -y build-essential

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- name: Install Python deps
run: pip install -r requirements.txt -r requirements-dev.txt wheel boto3

- name: Install Python SDK
run: make install
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- run: make INTEGRATION_TEST_PATH="${{ inputs.test_path }}" testint
if: ${{ steps.validate-tests.outputs.match == '' }}
env:
LINODE_CLI_TOKEN: ${{ secrets.LINODE_TOKEN }}

- uses: actions/github-script@v6
id: update-check-run
if: ${{ inputs.pull_request_number != '' && fromJson(steps.commit-hash.outputs.data).repository.pullRequest.headRef.target.oid == inputs.sha }}
env:
number: ${{ inputs.pull_request_number }}
job: ${{ github.job }}
conclusion: ${{ job.status }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
const check = checks.check_runs.filter(c => c.name === process.env.job);
const { data: result } = await github.rest.checks.update({
...context.repo,
check_run_id: check[0].id,
status: 'completed',
conclusion: process.env.conclusion
});
return result;
4 changes: 2 additions & 2 deletions .github/workflows/publish-pypi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ jobs:
LINODE_SDK_VERSION: ${{ github.event.release.tag_name }}

- name: Publish the release artifacts to PyPI
uses: pypa/gh-action-pypi-publish@37f50c210e3d2f9450da2cd423303d6a14a6e29f # pin@release/v1
uses: pypa/gh-action-pypi-publish@a56da0b891b3dc519c7ee3284aff1fad93cc8598 # pin@release/v1.8.6
with:
password: ${{ secrets.PYPI_API_TOKEN }}
password: ${{ secrets.PYPI_API_TOKEN }}
21 changes: 18 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
PYTHON ?= python3

INTEGRATION_TEST_PATH :=
TEST_CASE_COMMAND :=
MODEL_COMMAND :=

ifdef TEST_CASE
TEST_CASE_COMMAND = -k $(TEST_CASE)
endif

ifdef TEST_MODEL
MODEL_COMMAND = models/$(TEST_MODEL)
endif

@PHONEY: clean
clean:
mkdir -p dist
Expand All @@ -8,8 +20,7 @@ clean:

@PHONEY: build
build: clean
$(PYTHON) setup.py sdist
$(PYTHON) setup.py bdist_wheel
$(PYTHON) -m build --wheel --sdist


@PHONEY: release
Expand All @@ -18,7 +29,7 @@ release: build


install: clean
python3 setup.py install
python3 -m pip install .


requirements:
Expand All @@ -45,3 +56,7 @@ lint:
autoflake --check linode_api4 test
black --check --verbose linode_api4 test
pylint linode_api4

@PHONEY: testint
testint:
python3 -m pytest test/integration/${INTEGRATION_TEST_PATH}${MODEL_COMMAND} ${TEST_CASE_COMMAND}
33 changes: 31 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Building from Source
To build and install this package:

- Clone this repository
- ``./setup.py install``
- ``python3 -m pip install .``

Usage
=====
Expand Down Expand Up @@ -101,7 +101,7 @@ Contributing
Tests
-----

Tests live in the ``tests`` directory. When invoking tests, make sure you are
Tests live in the ``test`` directory. When invoking tests, make sure you are
in the root directory of this project. To run the full suite across all
supported python versions, use tox_:

Expand Down Expand Up @@ -133,6 +133,35 @@ from the api base url that should be returned, for example::

.. _tox: http://tox.readthedocs.io


Integration Tests
-----------
Integration tests live in the ``test/integration`` directory.

Pre-requisite
^^^^^^^^^^^^^^^^^
Export Linode API token as `LINODE_CLI_TOKEN` before running integration tests::

export LINODE_TOKEN = $(your_token)

Running the tests
^^^^^^^^^^^^^^^^^
Run the tests locally using the make command. Run the entire test suite using command below::

make testint

To run a specific package, use environment variable `INTEGRATION_TEST_PATH` with `testint` command::

make INTEGRATION_TEST_PATH="linode_client" testint

To run a specific model test suite, set the environment variable `TEST_MODEL` using file name in `integration/models`::

make TEST_MODEL="test_account.py" testint

Lastly to run a specific test case use environment variable `TEST_CASE` with `testint` command::

make TEST_CASE=test_get_domain_record testint

Documentation
-------------

Expand Down
104 changes: 104 additions & 0 deletions docs/guides/event_polling.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
Polling for Events
==================

There are often situations where an API request will trigger a
long-running operation (e.g. Instance shutdown) that will run
after the request has been made. These operations are tracked
through `Linode Account Events`_ which reflect the target entity,
progress, and status of these operations.

.. _Linode Account Events: https://www.linode.com/docs/api/account/#events-list

There are often cases where you would like for your application to
halt until these operations have succeeded. The most reliable and
efficient way to achieve this is by using the :py:class:`EventPoller`
object.

Polling on Basic Operations
---------------------------

In order to poll for an operation, we must create an :py:class:`EventPoller`
object *before* the endpoint that triggers the operation has been called.

Assuming a :py:class:`LinodeClient` object has already been created with the name
"client" and an :py:class:`Instance` object has already been created with the name "my_instance",
an :py:class:`EventPoller` can be created using the
:meth:`LinodeClient.polling.event_poller_create(...) <PollingGroup.event_poller_create>`
method::

poller = client.polling.event_poller_create(
"linode", # The type of the target entity
"linode_shutdown", # The action to poll for
entity_id=my_instance.id, # The ID of your Linode Instance
)

Valid values for the `type` and `action` fields can be found in the `Events Response Documentation`_.

.. _Events Response Documentation: https://www.linode.com/docs/api/account/#events-list__responses

From here, we can send the request to trigger the long-running operation::

my_instance.shutdown()

To wait for this operation to finish, we can call the
:meth:`poller.wait_for_next_event_finished(...) <EventPoller.wait_for_next_event_finished>`
method::

poller.wait_for_next_event_finished()

The :py:class:`timeout` (default 240) and :py:class:`interval` (default 5) arguments can optionally be used to configure the timeout
and poll frequency for this operation.

Bringing this together, we get the following::

from linode_api4 import LinodeClient, Instance

# Construct a client
client = LinodeClient("MY_LINODE_TOKEN")

# Fetch an existing Linode Instance
my_instance = client.load(Instance, 12345)

# Create the event poller
poller = client.polling.event_poller_create(
"linode", # The type of the target entity
"linode_shutdown", # The action to poll for
entity_id=my_instance.id, # The ID of your Linode Instance
)

# Shutdown the Instance
my_instance.shutdown()

# Wait until the event has finished
poller.wait_for_next_event_finished()

print("Linode has been successfully shutdown!")

Polling for an Entity to be Free
--------------------------------

In many cases, certain operations cannot be run until any other operations running on a resource have
been completed. To ensure these operation are run reliably and do not encounter conflicts,
you can use the
:meth:`LinodeClient.polling.wait_for_entity_free(...) <PollingGroup.wait_for_entity_free>` method
to wait until a resource has no running or queued operations.

For example::

# Construct a client
client = LinodeClient("MY_LINODE_TOKEN")

# Load an existing instance
my_instance = client.load(Instance, 12345)

# Wait until the Linode is not busy
client.polling.wait_for_entity_free(
"linode",
my_instance.id
)

# Boot the Instance
my_instance.boot()

The :py:class:`timeout` (default 240) and :py:class:`interval` (default 5) arguments can optionally be used to configure the timeout
and poll frequency for this operation.
2 changes: 1 addition & 1 deletion docs/guides/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ If you prefer, you can clone the package from github_ and install it from source

git clone git@github.com:Linode/linode_api4-python
cd linode_api4
python setup.py install
python -m pip install .

Authentication
--------------
Expand Down
4 changes: 3 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ To install from source::

git clone https://github.com/linode/linode_api4-python
cd linode_api4
python setup.py install
python -m pip install .

For more information, see our :doc:`Getting Started<guides/getting_started>`
guide.
Expand All @@ -32,9 +32,11 @@ Table of Contents

guides/getting_started
guides/core_concepts
guides/event_polling
guides/oauth
linode_api4/linode_client
linode_api4/login_client
linode_api4/objects/models
linode_api4/polling
linode_api4/paginated_list
linode_api4/objects/filtering
9 changes: 9 additions & 0 deletions docs/linode_api4/linode_client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ with buckets and objects, use the s3 API directly with a library like `boto3`_.

.. _boto3: https://github.com/boto/boto3

PollingGroup
^^^^^^^^^^^^

Includes methods related to account event polling.

.. autoclass:: linode_api4.linode_client.PollingGroup
:members:
:special-members:

ProfileGroup
^^^^^^^^^^^^

Expand Down
1 change: 0 additions & 1 deletion docs/linode_api4/objects/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,3 @@ Volume Models
:exclude-members: api_endpoint, properties, derived_url_path, id_attribute, parent_id_name
:undoc-members:
:inherited-members:

12 changes: 12 additions & 0 deletions docs/linode_api4/polling.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Event Polling
==========

This project exposes a framework for dynamically polling on long-running Linode Events.

See the :doc:`Event Polling Guide<../guides/event_polling>` for more details.

EventPoller class
-------------------

.. autoclass:: linode_api4.EventPoller
:members:
1 change: 1 addition & 0 deletions linode_api4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from linode_api4.linode_client import LinodeClient
from linode_api4.login_client import LinodeLoginClient, OAuthScopes
from linode_api4.paginated_list import PaginatedList
from linode_api4.polling import EventPoller
Loading