Skip to content

Commit

Permalink
Merge pull request #3 from SUSE-Enceladus/resolve_customer_service
Browse files Browse the repository at this point in the history
Added resolve_customer lambda handler
  • Loading branch information
schaefi authored Jan 16, 2025
2 parents 06bcaf9 + c72f04a commit 9cea4cc
Show file tree
Hide file tree
Showing 13 changed files with 517 additions and 4 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/ci-code-style.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI-Code-Style

on:
push:
branches:
- "main"
pull_request:

jobs:
unit_tests:
name: Linter checks for SUSE SaaS tools
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]

steps:
- uses: actions/checkout@v3
- name: Python${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
run: |
python -m pip install --upgrade pip
python -m pip install poetry
- name: Run code checks
run: |
make -C aws/resolve_customer check
30 changes: 30 additions & 0 deletions .github/workflows/ci-units-types.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI-Unit-And-Types

on:
push:
branches:
- "main"
pull_request:

jobs:
unit_tests:
name: Unit and Static Type tests for SUSE SaaS tools
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]

steps:
- uses: actions/checkout@v3
- name: Python${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
run: |
python -m pip install --upgrade pip
python -m pip install poetry
- name: Run unit and type tests
run: make -C aws/resolve_customer test
env:
PY_VER: ${{ matrix.python-version }}
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,18 @@ package:
cat package/python-${namespace}-spec-template | sed -e s'@%%VERSION@${version}@' \
> dist/python-${namespace}.spec
cp package/python-${namespace}.changes dist/


setup:
poetry install --all-extras

check: setup
poetry run flake8 --statistics -j auto --count ${namespace}
poetry run flake8 --statistics -j auto --count test/unit

test: setup
poetry run mypy ${namespace}
poetry run bash -c 'pushd test/unit && pytest -n 5 \
--doctest-modules --no-cov-on-fail --cov=${namespace} \
--cov-report=term-missing --cov-fail-under=100 \
--cov-config .coveragerc'
6 changes: 6 additions & 0 deletions aws/resolve_customer/app_resolve_customer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/python3
from resolve_customer.app import lambda_handler as resolve_customer_lambda


def lambda_handler(event, context):
return resolve_customer_lambda(event, context)
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Source: %{name}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildRequires: %{pythons}-%{develsuffix} >= 3.9
BuildRequires: %{pythons}-build
BuildRequires: %{pythons}-pip
BuildRequires: %{pythons}-installer
BuildRequires: %{pythons}-poetry-core >= 1.2.0
BuildRequires: %{pythons}-wheel
Expand All @@ -59,7 +60,7 @@ Requires: %{pythons}-boto3
Requires: %{pythons}-requests
Requires: %{pythons}-setuptools

%description -n python%{python3_pkgversion}-resolve_customer
%description -n %{pythons}-resolve_customer
SaaS tooling for the pubcloud team.

%prep
Expand All @@ -70,8 +71,10 @@ SaaS tooling for the pubcloud team.

%install
%pyproject_install
install -m 755 app_resolve_customer.py %{buildroot}/app_resolve_customer.py

%files -n %{pythons}-resolve_customer
%{_sitelibdir}/resolve_customer*
/app_resolve_customer.py

%changelog
8 changes: 5 additions & 3 deletions aws/resolve_customer/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ packages = [
]

include = [
{ path = "app_resolve_customer.py", format = "sdist" },
{ path = ".bumpversion.cfg", format = "sdist" },
{ path = ".coverage*", format = "sdist" },
{ path = "package", format = "sdist" },
Expand All @@ -37,13 +38,14 @@ classifiers = [
[tool.poetry.urls]
# "Bug Tracker" = ""

[tool.poetry.scripts]
# resolve_customer = "resolve_customer.app:lambda_handler"

[tool.poetry.dependencies]
python = "^3.11"
requests = ">=2.25.0"
setuptools = ">=50"

[tool.poetry.scripts]
# resolve_customer = "resolve_customer.resolve_customer:handle_event"
boto3 = ">=1.12"

[tool.poetry.group.test]
[tool.poetry.group.test.dependencies]
Expand Down
107 changes: 107 additions & 0 deletions aws/resolve_customer/resolve_customer/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright (c) 2025 SUSE LLC. All rights reserved.
#
# This file is part of suse-saas-tools
#
# suse-saas-tools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# mash is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with mash. If not, see <http://www.gnu.org/licenses/>
#
"""
Lambda to retrieve customer information from
AWS Metering/Entitlement Marketplace API's
"""
import json
import base64
from typing import (
Dict, Union, List
)

from resolve_customer.customer import AWSCustomer
from resolve_customer.entitlements import AWSCustomerEntitlement


def lambda_handler(event, context):
"""
Expects event type matching the AWS API Gateway.
Example event body:
{
"x-amzn-marketplace-token": "some"
}
Example return:
{
"statusCode": 200,
"isBase64Encoded": False,
"body": {
"CustomerIdentifier": "id"
"CustomerAWSAccountId": "account_id"
"ProductCode": "product_code"
"Entitlements": [
{
"CustomerIdentifier": "id",
"Dimension": "some",
"ExpirationDate": date,
"ProductCode": "product_code",
"Value": {
"BooleanValue": true|false,
"DoubleValue": number,
"IntegerValue": number,
"StringValue": "str"
}
}
]
}
}
"""
try:
event_body = event['body']
if event.get('isBase64Encoded'):
event_body = json.loads(base64.b64decode(event_body))
return json.dumps(
process_event(event_body.get('x-amzn-marketplace-token'))
)
except Exception as error:
return json.dumps(
error_response(500, f'{type(error).__name__}: {error}')
)


def process_event(
token: str
) -> Dict[str, Union[str, int, Dict[str, Union[str, List]]]]:
customer = AWSCustomer(token)
if customer.error:
return error_response(400, customer.error)
entitlements = AWSCustomerEntitlement(
customer.get_id(), customer.get_product_code()
)
if entitlements.error:
return error_response(400, entitlements.error)
return {
'isBase64Encoded': False,
'statusCode': 200,
'body': {
'CustomerIdentifier': customer.get_id(),
'CustomerAWSAccountId': customer.get_account_id(),
'ProductCode': customer.get_product_code(),
'Entitlements': entitlements.get_entitlements()
}
}


def error_response(status_code: int, message: str):
return {
'isBase64Encoded': False,
'statusCode': status_code,
'body': message
}
54 changes: 54 additions & 0 deletions aws/resolve_customer/resolve_customer/customer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (c) 2025 SUSE LLC. All rights reserved.
#
# This file is part of suse-saas-tools
#
# suse-saas-tools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# mash is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with mash. If not, see <http://www.gnu.org/licenses/>
#
import logging
import boto3

logger = logging.getLogger()


class AWSCustomer:
"""
Get AWS customer ID information from a marketplace token
"""
def __init__(self, token: str):
self.customer = {}
self.error = ''
if token:
try:
marketplace = boto3.client('meteringmarketplace')
self.customer = marketplace.resolve_customer(
RegistrationToken=token
)
except Exception as error:
self.error = f'meteringmarketplace client failed with: {error}'
logger.error(self.error)
else:
self.error = 'no marketplace token provided'
logger.error(self.error)

def get_id(self) -> str:
return self.__get('CustomerIdentifier')

def get_account_id(self) -> str:
return self.__get('CustomerAWSAccountId')

def get_product_code(self) -> str:
return self.__get('ProductCode')

def __get(self, key) -> str:
return self.customer[key] if self.customer else ''
49 changes: 49 additions & 0 deletions aws/resolve_customer/resolve_customer/entitlements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright (c) 2025 SUSE LLC. All rights reserved.
#
# This file is part of suse-saas-tools
#
# suse-saas-tools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# mash is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with mash. If not, see <http://www.gnu.org/licenses/>
#
import logging
import boto3
from typing import List

logger = logging.getLogger()


class AWSCustomerEntitlement:
"""
Get AWS customer entitlements for given customer ID and product code
"""
def __init__(self, customer_id: str, product_code: str):
self.entitlements = {}
self.error = ''
if customer_id and product_code:
try:
marketplace = boto3.client('marketplace-entitlement')
self.entitlements = marketplace.get_entitlements(
{
'ProductCode': product_code,
'Filter': {'CUSTOMER_IDENTIFIER': [customer_id]}
}
)
except Exception as error:
self.error = f'marketplace-entitlement client failed with: {error}'
logger.error(self.error)
else:
self.error = 'no customer_id and/or product_code provided'
logger.error(self.error)

def get_entitlements(self) -> List[dict]:
return self.entitlements.get('Entitlements') or []
7 changes: 7 additions & 0 deletions aws/resolve_customer/test/unit/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[run]
omit =
*/version.py

[report]
omit =
*/version.py
Loading

0 comments on commit 9cea4cc

Please sign in to comment.