Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added resolve_customer lambda handler #3

Merged
merged 2 commits into from
Jan 16, 2025
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
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,11 +40,13 @@ 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
BuildRequires: %{pythons}-requests
BuildRequires: %{pythons}-setuptools
BuildRequires: python-rpm-macros

%description
SaaS tooling for the pubcloud team.
Expand All @@ -53,12 +55,12 @@ SaaS tooling for the pubcloud team.
%package -n %{pythons}-resolve_customer
Summary: resolve_customer - AWS ResolveCustomer
Group: %{pygroup}
Requires: python >= 3.11
Requires: python3 >= 3.11
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 @@ -69,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
schaefi marked this conversation as resolved.
Show resolved Hide resolved
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()
schaefi marked this conversation as resolved.
Show resolved Hide resolved
}
}


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')
schaefi marked this conversation as resolved.
Show resolved Hide resolved
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')
schaefi marked this conversation as resolved.
Show resolved Hide resolved
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
Loading