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
1 change: 1 addition & 0 deletions .ansible-lint
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ exclude_paths:
- examples/vm_os_upgrade/main.yml
- .github/workflows
- changelogs
- .tox
16 changes: 16 additions & 0 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
# from https://github.com/ansible-collections/amazon.aws/
name: changelog and linters

# on: [workflow_call] # allow this workflow to be called from other workflows
on:
push:
pull_request:

jobs:
linters:
uses: ansible-network/github_actions/.github/workflows/tox.yml@main
with:
envname: ""
labelname: lint
# 12
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ child-pipeline-gitlab-ci.yml
# developer instance, including credentials
tests/integration/integration_config.yml
tests/integration/inventory
collections

docs/build
docs/source/modules
Expand Down
12 changes: 12 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ sanity: ## Run sanity tests
units: ## Run unit tests
```

Tox based testing was added as we want to use linters from ansible-network/github_actions.
Sample commands :

```
tox list
tox -e ansible-sanity
tox -m lint
tox -e flake8-lint,pylint
tox -e black,flake8 # will reformat code
tox -e ansible2.18-py312-with_constraints
```

If you want to run tests with a single python version (e.g. not with whole test matrix), use:

```
Expand Down
4 changes: 3 additions & 1 deletion plugins/doc_fragments/cloud_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__metaclass__ = type

Expand Down
4 changes: 3 additions & 1 deletion plugins/doc_fragments/cluster_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__metaclass__ = type

Expand Down
4 changes: 3 additions & 1 deletion plugins/doc_fragments/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__metaclass__ = type

Expand Down
4 changes: 3 additions & 1 deletion plugins/doc_fragments/force_reboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__metaclass__ = type

Expand Down
4 changes: 3 additions & 1 deletion plugins/doc_fragments/machine_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__metaclass__ = type

Expand Down
4 changes: 3 additions & 1 deletion plugins/doc_fragments/vm_name.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__metaclass__ = type

Expand Down
15 changes: 11 additions & 4 deletions plugins/inventory/hypercore.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__metaclass__ = type

Expand Down Expand Up @@ -134,13 +136,18 @@
# }
"""

from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
import yaml
import logging
import os

import yaml

from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.plugins.inventory import Cacheable
from ansible.plugins.inventory import Constructable

from ..module_utils import errors
from ..module_utils.client import Client
from ..module_utils.rest_client import RestClient
import os

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)
Expand Down
8 changes: 6 additions & 2 deletions plugins/module_utils/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import annotations
from __future__ import division
from __future__ import print_function

__metaclass__ = type

from ansible.module_utils.basic import env_fallback
from typing import Any

from ansible.module_utils.basic import env_fallback

from ..module_utils.client import AuthMethod

# TODO - env from /etc/environment is loaded
Expand Down
71 changes: 27 additions & 44 deletions plugins/module_utils/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,33 @@
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import annotations
from __future__ import division
from __future__ import print_function

__metaclass__ = type

import enum
import json
import os
import ssl
from typing import Any, Optional, Union
from io import BufferedReader
import enum

from typing import Any
from typing import Optional
from typing import Union

from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.module_utils.six.moves.urllib.error import URLError
from ansible.module_utils.six.moves.urllib.parse import quote
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.urls import Request

from .errors import (
AuthError,
ScaleComputingError,
UnexpectedAPIResponse,
ApiResponseNotJson,
)
from ..module_utils.typed_classes import TypedClusterInstance

from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
from ansible.module_utils.six.moves.urllib.parse import urlencode, quote
from .errors import ApiResponseNotJson
from .errors import AuthError
from .errors import ScaleComputingError
from .errors import UnexpectedAPIResponse

DEFAULT_HEADERS = dict(Accept="application/json")

Expand Down Expand Up @@ -56,15 +59,11 @@ class Response:
# Response(raw_resp) would be simpler.
# How is this used in other projects? Jure?
# Maybe we need/want both.
def __init__(
self, status: int, data: Any, headers: Optional[dict[Any, Any]] = None
):
def __init__(self, status: int, data: Any, headers: Optional[dict[Any, Any]] = None):
self.status = status
self.data = data
# [('h1', 'v1'), ('H2', 'V2')] -> {'h1': 'v1', 'h2': 'V2'}
self.headers = (
dict((k.lower(), v) for k, v in dict(headers).items()) if headers else {}
)
self.headers = dict((k.lower(), v) for k, v in dict(headers).items()) if headers else {}

self._json = None

Expand All @@ -89,8 +88,7 @@ def __init__(
):
if not (host or "").startswith(("https://", "http://")):
raise ScaleComputingError(
"Invalid instance host value: '{0}'. "
"Value must start with 'https://' or 'http://'".format(host)
f"Invalid instance host value: '{host}'. Value must start with 'https://' or 'http://'"
)

self.host = host
Expand Down Expand Up @@ -202,33 +200,22 @@ def _request_no_log(
# Wrong username/password, or expired access token
if e.code == 401:
raise AuthError(
"Failed to authenticate with the instance: {0} {1}".format(
e.code, e.reason
),
f"Failed to authenticate with the instance: {e.code} {e.reason}",
)
# Other HTTP error codes do not necessarily mean errors.
# This is for the caller to decide.
return Response(e.code, e.read(), e.headers)
except URLError as e:
# TODO: Add other errors here; we need to handle them in modules.
# TimeoutError is handled in the rest_client
if (
e.args
and isinstance(e.args, tuple)
and isinstance(e.args[0], ConnectionRefusedError)
):
if e.args and isinstance(e.args, tuple) and isinstance(e.args[0], ConnectionRefusedError):
raise ConnectionRefusedError(e.reason)
elif (
e.args
and isinstance(e.args, tuple)
and isinstance(e.args[0], ConnectionResetError)
):
elif e.args and isinstance(e.args, tuple) and isinstance(e.args[0], ConnectionResetError):
raise ConnectionResetError(e.reason)
elif (
e.args
and isinstance(e.args, tuple)
and type(e.args[0])
in [ssl.SSLEOFError, ssl.SSLZeroReturnError, ssl.SSLSyscallError]
and type(e.args[0]) in [ssl.SSLEOFError, ssl.SSLZeroReturnError, ssl.SSLSyscallError]
):
raise type(e.args[0])(e)
raise ScaleComputingError(e.reason)
Expand All @@ -246,15 +233,13 @@ def request(
) -> Response:
# Make sure we only have one kind of payload
if data is not None and binary_data is not None:
raise AssertionError(
"Cannot have JSON and binary payload in a single request."
)
raise AssertionError("Cannot have JSON and binary payload in a single request.")
escaped_path = quote(path.strip("/"))
if escaped_path:
escaped_path = "/" + escaped_path
url = "{0}{1}".format(self.host, escaped_path)
url = f"{self.host}{escaped_path}"
if query:
url = "{0}?{1}".format(url, urlencode(query))
url = f"{url}?{urlencode(query)}"
headers = dict(headers or DEFAULT_HEADERS, **self.auth_header)
if data is not None:
headers["Content-type"] = "application/json"
Expand All @@ -267,9 +252,7 @@ def request(
)
elif binary_data is not None:
headers["Content-type"] = "application/octet-stream"
return self._request(
method, url, data=binary_data, headers=headers, timeout=timeout
)
return self._request(method, url, data=binary_data, headers=headers, timeout=timeout)
return self._request(method, url, data=data, headers=headers, timeout=timeout)

def get(
Expand Down
30 changes: 13 additions & 17 deletions plugins/module_utils/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
from __future__ import absolute_import
from __future__ import annotations
from __future__ import division
from __future__ import print_function

__metaclass__ = type

from time import sleep
from ..module_utils.utils import PayloadMapper
from ..module_utils.rest_client import RestClient
from ..module_utils.errors import ScaleTimeoutError
from ..module_utils.typed_classes import TypedClusterToAnsible, TypedTaskTag
from typing import Any

from ..module_utils.errors import ScaleTimeoutError
from ..module_utils.rest_client import RestClient
from ..module_utils.typed_classes import TypedClusterToAnsible
from ..module_utils.typed_classes import TypedTaskTag
from ..module_utils.utils import PayloadMapper


class Cluster(PayloadMapper):
def __init__(self, uuid: str, name: str, icos_version: str):
Expand Down Expand Up @@ -61,23 +65,15 @@ def __eq__(self, other: object) -> bool:

@classmethod
def get(cls, rest_client: RestClient, must_exist: bool = True) -> Cluster:
hypercore_dict = rest_client.get_record(
"/rest/v1/Cluster", must_exist=must_exist
)
hypercore_dict = rest_client.get_record("/rest/v1/Cluster", must_exist=must_exist)
cluster = cls.from_hypercore(hypercore_dict) # type: ignore # cluster never None
return cluster

def update_name(
self, rest_client: RestClient, name_new: str, check_mode: bool = False
) -> TypedTaskTag:
return rest_client.update_record(
f"/rest/v1/Cluster/{self.uuid}", dict(clusterName=name_new), check_mode
)
def update_name(self, rest_client: RestClient, name_new: str, check_mode: bool = False) -> TypedTaskTag:
return rest_client.update_record(f"/rest/v1/Cluster/{self.uuid}", dict(clusterName=name_new), check_mode)

@staticmethod
def shutdown(
rest_client: RestClient, force_shutdown: bool = False, check_mode: bool = False
) -> None:
def shutdown(rest_client: RestClient, force_shutdown: bool = False, check_mode: bool = False) -> None:
try:
rest_client.create_record(
"/rest/v1/Cluster/shutdown",
Expand Down
Loading
Loading