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
6 changes: 6 additions & 0 deletions doc/source/sdk/proxies/waf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ Certificate Operations
:members: certificates, create_certificate, get_certificate,
delete_certificate, update_certificate, find_certificate

Domain Operations
^^^^^^^^^^^^^^^^^

.. autoclass:: otcextensions.sdk.waf.v1._proxy.Proxy
:noindex:
:members: domains, create_domain, get_domain, find_domain,
delete_domain, update_domain, find_domain
1 change: 1 addition & 0 deletions doc/source/sdk/resources/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Open Telekom Cloud Resources
Object Block Storage (OBS) <obs/index>
Relational Database Service (RDS) <rds/index>
Virtual Private Cloud (VPC) <vpc/index>
Web Application Firewall (WAF) <waf/index>

Every resource which is used within the proxy methods have own attributes.
Those attributes define the behavior of the resource which can be a cluster
Expand Down
8 changes: 8 additions & 0 deletions doc/source/sdk/resources/waf/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
WAF Resources
=============

.. toctree::
:maxdepth: 1

v1/certificate
v1/domain
13 changes: 13 additions & 0 deletions doc/source/sdk/resources/waf/v1/certificate.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
otcextensions.sdk.waf.v1.certificate
====================================

.. automodule:: otcextensions.sdk.waf.v1.certificate

The WAF Certificate Class
-------------------------

The ``Certificate`` class inherits from
:class:`~otcextensions.sdk.sdk_resource.Resource`.

.. autoclass:: otcextensions.sdk.waf.v1.certificate.Certificate
:members:
13 changes: 13 additions & 0 deletions doc/source/sdk/resources/waf/v1/domain.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
otcextensions.sdk.waf.v1.domain
===============================

.. automodule:: otcextensions.sdk.waf.v1.domain

The WAF Domain Class
--------------------

The ``Domain`` class inherits from
:class:`~otcextensions.sdk.sdk_resource.Resource`.

.. autoclass:: otcextensions.sdk.waf.v1.domain.Domain
:members:
115 changes: 115 additions & 0 deletions otcextensions/sdk/waf/v1/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,118 @@ def find(cls, session, name_or_id, ignore_missing=True, **params):
return None
raise exceptions.ResourceNotFound(
"No %s found for %s" % (cls.__name__, name_or_id))

@classmethod
def list(cls, session, paginated=True, base_path=None,
allow_unknown_params=False, **params):
"""This method is a generator which yields resource objects.

This resource object list generator handles pagination and takes query
params for response filtering.

:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param bool paginated: ``True`` if a GET to this resource returns
a paginated series of responses, or ``False``
if a GET returns only one page of data.
**When paginated is False only one
page of data will be returned regardless
of the API's support of pagination.**
:param str base_path: Base part of the URI for listing resources, if
different from :data:`~openstack.resource.Resource.base_path`.
:param bool allow_unknown_params: ``True`` to accept, but discard
unknown query parameters. This allows getting list of 'filters' and
passing everything known to the server. ``False`` will result in
validation exception when unknown query parameters are passed.
:param dict params: These keyword arguments are passed through the
:meth:`~openstack.resource.QueryParamter._transpose` method
to find if any of them match expected query parameters to be
sent in the *params* argument to
:meth:`~keystoneauth1.adapter.Adapter.get`. They are additionally
checked against the
:data:`~openstack.resource.Resource.base_path` format string
to see if any path fragments need to be filled in by the contents
of this argument.

:return: A generator of :class:`Resource` objects.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_list` is not set to ``True``.
:raises: :exc:`~openstack.exceptions.InvalidResourceQuery` if query
contains invalid params.
"""
if not cls.allow_list:
raise exceptions.MethodNotSupported(cls, "list")

if base_path is None:
base_path = cls.base_path
params = cls._query_mapping._validate(
params, base_path=base_path,
allow_unknown_params=allow_unknown_params)
query_params = cls._query_mapping._transpose(params, cls)
uri = base_path % params

limit = query_params.get('limit', '10')

# Track the total number of resources yielded so we can paginate
# swift objects
total_yielded = 0
page = 0
while uri:
# Copy query_params due to weird mock unittest interactions
response = session.get(
uri,
headers={"Accept": "application/json"},
params=query_params.copy(),
)
exceptions.raise_from_response(response)
data = response.json()

# Discard any existing pagination keys
query_params.pop('marker', None)
query_params.pop('limit', None)

if cls.resources_key:
resources = data[cls.resources_key]
else:
resources = data

if not isinstance(resources, list):
resources = [resources]

marker = None
for raw_resource in resources:
# Do not allow keys called "self" through. Glance chose
# to name a key "self", so we need to pop it out because
# we can't send it through cls.existing and into the
# Resource initializer. "self" is already the first
# argument and is practically a reserved word.
raw_resource.pop("self", None)

value = cls.existing(
connection=session._get_connection(),
**raw_resource)
marker = value.id
yield value
total_yielded += 1

if resources and paginated:
page += 1
uri, next_params = cls._get_next_link(
uri, response, data, marker, limit, total_yielded, page)
query_params.update(next_params)
else:
return

@classmethod
def _get_next_link(cls, uri, response, data, marker, limit, total_yielded,
page):
next_link = None
params = {}
if total_yielded < data['total']:
next_link = uri
params['offset'] = page
params['limit'] = limit
else:
next_link = None
query_params = cls._query_mapping._transpose(params, cls)
return next_link, query_params
86 changes: 86 additions & 0 deletions otcextensions/sdk/waf/v1/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from openstack import proxy

from otcextensions.sdk.waf.v1 import certificate as _cert
from otcextensions.sdk.waf.v1 import domain as _domain


class Proxy(proxy.Proxy):
Expand Down Expand Up @@ -112,3 +113,88 @@ def find_certificate(self, name_or_id, ignore_missing=True, **attrs):
return self._find(_cert.Certificate, name_or_id,
ignore_missing=ignore_missing,
**attrs)

# ======== Domains ========
def domains(self, **query):
"""Retrieve a generator of domains

:param dict query: Optional query parameters to be sent to limit the
resources being returned.
* `limit`: pagination limit
* `offset`: pagination offset
* `name`: domain name (hostname)
* `policy_name`: policy name

:returns: A generator of domain
:class:`~otcextensions.sdk.waf.v1.domain.Domain`
instances
"""
return self._list(_domain.Domain, **query)

def create_domain(self, **attrs):
"""Upload domain from attributes

:param dict attrs: Keyword arguments which will be used to create
a :class:`~otcextensions.sdk.waf.v1.domain.Domain`,
comprised of the properties on the Domain class.
:returns: The results of domain creation
:rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain`
"""
return self._create(_domain.Domain, prepend_key=False, **attrs)

def get_domain(self, domain):
"""Get a domain

:param domain: The value can be the ID of a domain
or a :class:`~otcextensions.sdk.waf.v1.domain.Domain`
instance.
:returns: Domain instance
:rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain`
"""
return self._get(_domain.Domain, domain)

def delete_domain(self, domain, ignore_missing=True):
"""Delete a domain

:param domain: The value can be the ID of a domain
or a :class:`~otcextensions.sdk.waf.v1.domain.Domain`
instance.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
the domain does not exist.
When set to ``True``, no exception will be set when attempting to
delete a nonexistent domain.

:returns: Domain been deleted
:rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain`
"""
return self._delete(_domain.Domain, domain,
ignore_missing=ignore_missing)
Comment thread
gtema marked this conversation as resolved.

def update_domain(self, domain, **attrs):
"""Update domain attributes

:param domain: The id or an instance of
:class:`~otcextensions.sdk.waf.v1.domain.Domain`
:param dict attrs: attributes for update on
:class:`~otcextensions.sdk.waf.v1.domain.Domain`

:rtype: :class:`~otcextensions.sdk.waf.v1.domain.Domain`
"""
return self._update(_domain.Domain, domain, **attrs)

def find_domain(self, name_or_id, ignore_missing=True, **attrs):
"""Find a single domain

:param name_or_id: The name or ID of a domain
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised
when the domain does not exist.
When set to ``True``, no exception will be set when attempting
to delete a nonexistent domain.

:returns: ``None``
"""
return self._find(_domain.Domain, name_or_id,
ignore_missing=ignore_missing,
**attrs)
86 changes: 86 additions & 0 deletions otcextensions/sdk/waf/v1/domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import resource

from otcextensions.sdk.waf.v1 import _base


class ServerEntry(resource.Resource):
#: Properties
#: IP address
address = resource.Body('address')
#: Client protocol
client_protocol = resource.Body('client_protocol')
#: Server protocol
server_protocol = resource.Body('server_protocol')
#: Port number
port = resource.Body('port', type=int)


class Domain(_base.Resource):
"""WAF Domain Resource"""
resources_key = 'items'
base_path = '/waf/instance'

# capabilities
allow_create = True
allow_list = True
allow_fetch = True
allow_delete = True
allow_commit = True

_query_mapping = resource.QueryParameters(
'name', 'hostname', 'policy_name', 'limit', 'offset',
policy_name='policyname',
name='hostname')

#: Properties
Comment thread
gtema marked this conversation as resolved.
#: Specifies whether a domain name is connected to WAF
access_status = resource.Body('access_status', type=int)
#: Certificate ID.
#: This parameter is mandatory when client_protocol is set to HTTPS.
certificate_id = resource.Body('certificate_id')
#: CNAME
cname = resource.Body('cname')
#: domain name
name = resource.Body('hostname', aka='hostname')
#: Specifies the policy ID.
policy_id = resource.Body('policy_id')
#: protocol type of the client.
#: The options are HTTP, HTTPS, and HTTP,HTTPS.
protocol = resource.Body('protocol')
#: WAF mode.
protect_status = resource.Body('protect_status', type=int)
#: Specifies whether a proxy is configured.
proxy = resource.Body('proxy', type=bool)
#: Specifies the origin server information, including the client_protocol,
#: server_protocol, address, and port fields.
server = resource.Body('server', type=list, list_type=ServerEntry)
#: source IP header. This parameter is required only when proxy is set
#: to true.
#: The options are as follows: default, cloudflare, akamai, and custom.
sip_header_name = resource.Body('sip_header_name')
#: Specifies the HTTP request header for identifying the real source IP
#: address. This parameter is required only when proxy is set to true.
#: - If sip_header_name is default, sip_header_list is ["X-Forwarded-For"].
#: - If sip_header_name is cloudflare, sip_header_list is
#: ["CF-Connecting-IP", "X-Forwarded-For"].
#: - If sip_header_name is akamai, sip_header_list is ["True-Client-IP"].
#: - If sip_header_name is custom, you can customize a value.
sip_header_list = resource.Body('sip_header_list', type=list)
#: subdomain name.
#: This parameter is returned only when proxy is set to true.
subdomain = resource.Body('sub_domain')
#: Certificate uploading timestamp
timestamp = resource.Body('timestamp')
Comment thread
gtema marked this conversation as resolved.
#: TXT record. This parameter is returned only when proxy is set to true.
txt_record = resource.Body('txt_code')
9 changes: 5 additions & 4 deletions otcextensions/tests/functional/sdk/waf/v1/test_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ class TestCertificate(TestWaf):
uslYHnizLvYY6FaAdExE1TpM6YrM3b7aYMgv700CDsBCpFncQUx9tujpQxCmMoHZ
rNcviQ==
-----END CERTIFICATE-----"""
CERT_NAME = "SDK-" + uuid.uuid4().hex

def setUp(self):
super(TestCertificate, self).setUp()

self.cert_name = "SDK-" + uuid.uuid4().hex
self.cert = self.client.create_certificate(
key=self._PRIVATE_KEY,
content=self._CERTIFICATE,
name=self.CERT_NAME
name=self.cert_name
)

self.addCleanup(self.conn.waf.delete_certificate, self.cert)
Expand All @@ -99,13 +100,13 @@ def test_update_certificate(self):
cert2 = self.client.create_certificate(
key=self._PRIVATE_KEY,
content=self._CERTIFICATE,
name=self.CERT_NAME + "_2"
name=self.cert_name + "_2"
)

self.addCleanup(self.conn.waf.delete_certificate, cert2)
cert2_cmp = self.client.update_certificate(
cert2,
name=self.CERT_NAME + "_2_cp"
name=self.cert_name + "_2_cp"
)
self.assertEqual(cert2.name, cert2_cmp.name)

Expand Down
Loading