1
0
mirror of https://github.com/quay/quay.git synced 2025-07-30 07:43:13 +03:00

billing: Assign SKU to org (PROJQUAY-5363) (#1989)

* add migration for orgrhskus table

* add endpoints for managing and listing skus bound to an org

* create checks in billing flow to look for org-bound skus

* refactor RH marketplace api objects to be more usable in tests

* update cypress test db data and exclude it from pre-commit hook formatting
This commit is contained in:
Marcus Kok
2023-08-25 14:52:54 -04:00
committed by GitHub
parent 5f63b3a7bb
commit e44783fe19
19 changed files with 715 additions and 327 deletions

4
.gitignore vendored
View File

@ -112,3 +112,7 @@ web/yarn-error.log*
web/cypress/videos
web/cypress/screenshots
web/cypress/downloads
# marketplace
local-dev/stack/quay-marketplace-api.crt
local-dev/stack/quay-marketplace-api.key

View File

@ -23,8 +23,11 @@ repos:
entry: web/node_modules/.bin/eslint --fix
language: system
files: ^web/
exclude: ^web/cypress/test/
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
exclude: ^web/cypress/test/quay-db-data.txt
- id: end-of-file-fixer
exclude: ^web/cypress/test/quay-db-data.txt

11
app.py
View File

@ -53,7 +53,7 @@ from util.greenlet_tracing import enable_tracing
from util.ipresolver import IPResolver
from util.label_validator import LabelValidator
from util.log import filter_logs
from util.marketplace import RHMarketplaceAPI, RHUserAPI
from util.marketplace import MarketplaceSubscriptionApi, MarketplaceUserApi
from util.metrics.prometheus import PrometheusPlugin
from util.names import urn_generator
from util.repomirror.api import RepoMirrorAPI
@ -236,12 +236,6 @@ Principal(app, use_sessions=False)
tf = app.config["DB_TRANSACTION_FACTORY"]
rh_user_api = None
rh_marketplace_api = None
if features.ENTITLEMENT_RECONCILIATION or features.RH_MARKETPLACE:
rh_user_api = RHUserAPI(app.config)
rh_marketplace_api = RHMarketplaceAPI(app.config)
model_cache = get_model_cache(app.config)
avatar = Avatar(app)
login_manager = LoginManager(app)
@ -310,6 +304,9 @@ repo_mirror_api = RepoMirrorAPI(
tuf_metadata_api = TUFMetadataAPI(app, app.config)
marketplace_users = MarketplaceUserApi(app)
marketplace_subscriptions = MarketplaceSubscriptionApi(app)
# Check for a key in config. If none found, generate a new signing key for Docker V2 manifests.
_v2_key_path = os.path.join(OVERRIDE_CONFIG_DIRECTORY, DOCKER_V2_SIGNINGKEY_FILENAME)
if os.path.exists(_v2_key_path):

View File

@ -745,6 +745,7 @@ class User(BaseModel):
UserOrganizationQuota,
QuotaLimits,
RedHatSubscriptions,
OrganizationRhSkus,
}
| appr_classes
| v22_classes
@ -1981,13 +1982,29 @@ class ProxyCacheConfig(BaseModel):
class RedHatSubscriptions(BaseModel):
"""
Represents internal Red Hat subscriptions for customers
Represents store for users' RH account numbers
"""
user_id = ForeignKeyField(User, backref="subscription")
user_id = ForeignKeyField(User, backref="account_number")
account_number = IntegerField(unique=True)
class OrganizationRhSkus(BaseModel):
"""
Represents sku to org binding for
RH subscriptions
"""
subscription_id = IntegerField(index=True, unique=True)
user_id = ForeignKeyField(User, backref="org_bound_subscription")
org_id = ForeignKeyField(User, backref="subscription")
indexes = (
(("subscription_id", "org_id"), True),
(("subscription_id", "org_id", "user_id"), True),
)
# Defines a map from full-length index names to the legacy names used in our code
# to meet length restrictions.
LEGACY_INDEX_MAP = {

View File

@ -0,0 +1,54 @@
"""Create table for org to sku relations
Revision ID: b82361fba1cd
Revises: 46980ea2dde5
Create Date: 2023-06-07 14:22:09.384808
"""
# revision identifiers, used by Alembic.
revision = "b82361fba1cd"
down_revision = "8a70b8777089"
from typing import Text
import sqlalchemy as sa
def upgrade(op, tables, tester):
op.create_table(
"organizationrhskus",
sa.Column("id", sa.Integer, nullable=False, autoincrement=True),
sa.Column("subscription_id", sa.Integer, nullable=False),
sa.Column("org_id", sa.Integer, nullable=False),
sa.Column("user_id", sa.Integer, nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("pk_organizationrhskus")),
sa.ForeignKeyConstraint(
["user_id"], ["user.id"], name=op.f("fk_organizationrhskus_userid")
),
sa.ForeignKeyConstraint(["org_id"], ["user.id"], name=op.f("fk_organizationrhskus_orgid")),
)
op.create_index(
"organizationrhskus_subscription_id", "organizationrhskus", ["subscription_id"], unique=True
)
op.create_index(
"organizationrhskus_subscription_id_org_id",
"organizationrhskus",
["subscription_id", "org_id"],
unique=True,
)
op.create_index(
"organizationrhskus_subscription_id_org_id_user_id",
"organizationrhskus",
["subscription_id", "org_id", "user_id"],
unique=True,
)
def downgrade(op, tables, tester):
op.drop_index("organizationrhskus_subscription_id", table_name="organizationrhskus")
op.drop_index("organizationrhskus_subscription_id_org_id", table_name="organizationrhskus")
op.drop_index(
"organizationrhskus_subscription_id_org_id_user_id", table_name="organizationrhskus"
)
op.drop_table("organizationrhskus")

View File

@ -137,6 +137,10 @@ class UnsupportedQuotaSize(DataModelException):
pass
class OrgSubscriptionBindingAlreadyExists(DataModelException):
pass
class TooManyLoginAttemptsException(Exception):
def __init__(self, message, retry_after):
super(TooManyLoginAttemptsException, self).__init__(message)
@ -178,6 +182,7 @@ from data.model import (
notification,
oauth,
organization,
organization_skus,
permission,
proxy_cache,
release,

View File

@ -0,0 +1,56 @@
import logging
import peewee
from data import model
from data.database import OrganizationRhSkus
logger = logging.getLogger(__name__)
def get_org_subscriptions(org_id):
try:
query = OrganizationRhSkus.select().where(OrganizationRhSkus.org_id == org_id)
return query
except OrganizationRhSkus.DoesNotExist:
return None
def bind_subscription_to_org(subscription_id, org_id, user_id):
try:
return OrganizationRhSkus.create(
subscription_id=subscription_id, org_id=org_id, user_id=user_id
)
except model.DataModelException as ex:
logger.error("Problem binding subscription to org %s: %s", org_id, ex)
except peewee.IntegrityError:
raise model.OrgSubscriptionBindingAlreadyExists()
def subscription_bound_to_org(subscription_id):
# lookup row in table matching subscription_id, if there is no row return false, otherwise return true
# this function is used to check if a subscription is bound to an org or
try:
binding = OrganizationRhSkus.get(OrganizationRhSkus.subscription_id == subscription_id)
return True, binding.org_id
except OrganizationRhSkus.DoesNotExist:
return False, None
def remove_subscription_from_org(org_id, subscription_id):
query = OrganizationRhSkus.delete().where(
OrganizationRhSkus.org_id == org_id,
OrganizationRhSkus.subscription_id == subscription_id,
)
query.execute()
def remove_all_owner_subscriptions_from_org(user_id, org_id):
try:
query = OrganizationRhSkus.delete().where(
OrganizationRhSkus.user_id == user_id,
OrganizationRhSkus.org_id == org_id,
)
query.execute()
except model.DataModelException as ex:
raise model.DataModelException(ex)

View File

@ -9,12 +9,13 @@ import stripe
from flask import request
import features
from app import app, billing, rh_marketplace_api, rh_user_api
from app import app, billing, marketplace_subscriptions, marketplace_users
from auth import scopes
from auth.auth_context import get_authenticated_user
from auth.permissions import AdministerOrganizationPermission
from data import model
from data.billing import PLANS, get_plan
from data.billing import PLANS, get_plan, get_plan_using_rh_sku
from data.model import InvalidOrganizationException, organization_skus
from endpoints.api import (
ApiResource,
abort,
@ -47,11 +48,22 @@ def check_internal_api_for_subscription(namespace_user):
Returns subscription from RH marketplace.
None returned if no subscription is found.
"""
user_account_number = rh_user_api.get_account_number(namespace_user)
plans = []
if namespace_user.organization:
query = organization_skus.get_org_subscriptions(namespace_user.id)
org_subscriptions = list(query.dicts()) if query is not None else []
for subscription in org_subscriptions:
subscription_id = subscription["subscription_id"]
sku = marketplace_subscriptions.get_subscription_sku(subscription_id)
plans.append(get_plan_using_rh_sku(sku))
pass
else:
user_account_number = marketplace_users.get_account_number(namespace_user)
if user_account_number:
user_subscriptions = rh_marketplace_api.find_stripe_subscription(user_account_number)
return user_subscriptions
return []
plans = marketplace_subscriptions.get_list_of_subscriptions(
user_account_number, filter_out_org_bindings=True, convert_to_stripe_plans=True
)
return plans
def get_namespace_plan(namespace):
@ -87,8 +99,10 @@ def lookup_allowed_private_repos(namespace):
if features.RH_MARKETPLACE:
namespace_user = model.user.get_namespace_user(namespace)
marketplace_subscriptions = check_internal_api_for_subscription(namespace_user)
for subscription in marketplace_subscriptions:
subscriptions = check_internal_api_for_subscription(namespace_user)
for subscription in subscriptions:
if subscription is not None:
repos_allowed += subscription["privateRepos"]
# Find the number of private repositories used by the namespace and compare it to the
@ -916,3 +930,127 @@ class OrganizationInvoiceField(ApiResource):
return "Okay", 201
abort(403)
@resource("/v1/organization/<orgname>/marketplace")
@path_param("orgname", "The name of the organization")
@show_if(features.BILLING)
class OrganizationRhSku(ApiResource):
"""
Resource for managing an organization's RH SKU
"""
@require_scope(scopes.ORG_ADMIN)
@nickname("listOrgSkus")
def get(self, orgname):
"""
Get sku assigned to org
"""
permission = AdministerOrganizationPermission(orgname)
if permission.can():
organization = model.organization.get_organization(orgname)
query = model.organization_skus.get_org_subscriptions(organization.id)
if query:
subscriptions = list(query.dicts())
for subscription in subscriptions:
subscription["sku"] = marketplace_subscriptions.get_subscription_sku(
subscription["subscription_id"]
)
return subscriptions
else:
return []
abort(401)
@require_scope(scopes.ORG_ADMIN)
@nickname("bindSkuToOrg")
def post(self, orgname):
"""
Assigns a sku to an org
"""
permission = AdministerOrganizationPermission(orgname)
request_data = request.get_json()
subscription_id = request_data["subscription_id"]
if permission.can():
organization = model.organization.get_organization(orgname)
user = get_authenticated_user()
account_number = marketplace_users.get_account_number(user)
subscriptions = marketplace_subscriptions.get_list_of_subscriptions(account_number)
if subscriptions is None:
abort(401, message="no valid subscriptions present")
user_subscription_ids = [int(subscription["id"]) for subscription in subscriptions]
if int(subscription_id) in user_subscription_ids:
try:
model.organization_skus.bind_subscription_to_org(
user_id=user.id, subscription_id=subscription_id, org_id=organization.id
)
return "Okay", 201
except model.OrgSubscriptionBindingAlreadyExists:
abort(400, message="subscription is already bound to an org")
else:
abort(401, message=f"subscription does not belong to {user.username}")
abort(401)
@resource("/v1/organization/<orgname>/marketplace/<subscription_id>")
@path_param("orgname", "The name of the organization")
@path_param("subscription_id", "Marketplace subscription id")
@related_user_resource(UserPlan)
@show_if(features.BILLING)
class OrganizationRhSkuSubscriptionField(ApiResource):
"""
Resource for removing RH skus from an organization
"""
@require_scope(scopes.ORG_ADMIN)
def delete(self, orgname, subscription_id):
"""
Remove sku from an org
"""
permission = AdministerOrganizationPermission(orgname)
if permission.can():
try:
organization = model.organization.get_organization(orgname)
except InvalidOrganizationException:
return ("Organization not valid", 400)
model.organization_skus.remove_subscription_from_org(organization.id, subscription_id)
return ("Deleted", 204)
abort(401)
@resource("/v1/user/marketplace")
@show_if(features.RH_MARKETPLACE)
class UserSkuList(ApiResource):
"""
Resource for listing a user's RH skus
bound to an org
"""
@require_user_admin()
@nickname("getUserMarketplaceSubscriptions")
def get(self):
"""
List the invoices for the current user.
"""
user = get_authenticated_user()
account_number = marketplace_users.get_account_number(user)
if not account_number:
raise NotFound()
user_subscriptions = marketplace_subscriptions.get_list_of_subscriptions(account_number)
for subscription in user_subscriptions:
bound_to_org, organization = organization_skus.subscription_bound_to_org(
subscription["id"]
)
# fill in information for whether a subscription is bound to an org
if bound_to_org:
subscription["assigned_to_org"] = organization.username
else:
subscription["assigned_to_org"] = None
return user_subscriptions

View File

@ -10,7 +10,7 @@ from flask import request
import features
from app import all_queues, app, authentication, avatar
from app import billing as stripe
from app import ip_resolver, namespace_gc_queue, usermanager
from app import ip_resolver, marketplace_subscriptions, namespace_gc_queue, usermanager
from auth import scopes
from auth.auth_context import get_authenticated_user
from auth.permissions import (
@ -21,8 +21,9 @@ from auth.permissions import (
ViewTeamPermission,
)
from data import model
from data.billing import get_plan
from data.billing import get_plan, get_plan_using_rh_sku
from data.database import ProxyCacheConfig
from data.model import organization_skus
from endpoints.api import (
ApiResource,
allow_if_superuser,
@ -42,6 +43,7 @@ from endpoints.api import (
from endpoints.api.user import PrivateRepositories, User
from endpoints.exception import NotFound, Unauthorized
from proxy import Proxy, UpstreamRegistryError
from util.marketplace import MarketplaceSubscriptionApi
from util.names import parse_robot_username
from util.request import get_request_ip
@ -364,15 +366,26 @@ class OrgPrivateRepositories(ApiResource):
organization = model.organization.get_organization(orgname)
private_repos = model.user.get_private_repo_count(organization.username)
data = {"privateAllowed": False}
repos_allowed = 0
if organization.stripe_id:
cus = stripe.Customer.retrieve(organization.stripe_id)
if cus.subscription:
repos_allowed = 0
plan = get_plan(cus.subscription.plan.id)
if plan:
repos_allowed = plan["privateRepos"]
if features.RH_MARKETPLACE:
query = organization_skus.get_org_subscriptions(organization.id)
rh_subscriptions = list(query.dicts()) if query is not None else []
for subscription in rh_subscriptions:
subscription_sku = marketplace_subscriptions.get_subscription_sku(
subscription["subscription_id"]
)
equivalent_stripe_plan = get_plan_using_rh_sku(subscription_sku)
if equivalent_stripe_plan:
repos_allowed += equivalent_stripe_plan["privateRepos"]
data["privateAllowed"] = private_repos < repos_allowed
if AdministerOrganizationPermission(orgname).can():

View File

@ -482,6 +482,9 @@ class TeamMember(ApiResource):
return "", 204
model.team.remove_user_from_team(orgname, teamname, membername, invoking_user)
if features.RH_MARKETPLACE:
org_id = model.organization.get_organization(orgname).id
model.organization_skus.remove_all_owner_subscriptions_from_org(member.id, org_id)
log_action("org_remove_team_member", orgname, {"member": membername, "team": teamname})
return "", 204

View File

@ -1,3 +1,5 @@
# isort reordering imports breaks these tests, so tell it to skip
# isort: skip_file
from typing import List, Optional, Dict, Tuple, Any, Type
from mock import patch
@ -6024,6 +6026,38 @@ SECURITY_TESTS: List[
"devtable",
404,
),
(
OrganizationRhSkuSubscriptionField,
"DELETE",
{"orgname": "buynlarge", "subscription_id": 12345},
None,
None,
401,
),
(
OrganizationRhSku,
"GET",
{"orgname": "buynlarge"},
None,
None,
401,
),
(
OrganizationRhSku,
"POST",
{"orgname": "buynlarge"},
{"subscription_id": 12345},
None,
401,
),
(
UserSkuList,
"GET",
{"orgname": "buynlarge"},
None,
None,
401,
),
]

View File

@ -16,10 +16,10 @@ from app import all_queues, app, authentication, avatar
from app import billing as stripe
from app import (
ip_resolver,
marketplace_subscriptions,
marketplace_users,
namespace_gc_queue,
oauth_login,
rh_marketplace_api,
rh_user_api,
url_scheme_and_hostname,
)
from auth import scopes
@ -638,12 +638,12 @@ class PrivateRepositories(ApiResource):
repos_allowed = plan["privateRepos"]
if features.RH_MARKETPLACE:
# subscriptions in marketplace will get added to private repo count
user_account_number = rh_user_api.get_account_number(user)
user_account_number = marketplace_users.get_account_number(user)
if user_account_number:
marketplace_subscriptions = rh_marketplace_api.find_stripe_subscription(
user_account_number
subscriptions = marketplace_subscriptions.get_list_of_subscriptions(
user_account_number, filter_out_org_bindings=True, convert_to_stripe_plans=True
)
for user_subscription in marketplace_subscriptions:
for user_subscription in subscriptions:
repos_allowed += user_subscription["privateRepos"]
return {"privateCount": private_repos, "privateAllowed": (private_repos < repos_allowed)}

View File

@ -1,3 +1,9 @@
# isort: skip_file
from typing import Dict, Any
import logging
import json
import hashlib
import random
import argparse
import calendar
import hashlib
@ -1339,6 +1345,7 @@ WHITELISTED_EMPTY_MODELS = [
"Image",
"ProxyCacheConfig",
"RedHatSubscriptions",
"OrganizationRhSkus",
"QuotaRegistrySize",
]

View File

@ -37,6 +37,8 @@ from endpoints.api.billing import (
ListPlans,
OrganizationCard,
OrganizationPlan,
OrganizationRhSku,
OrganizationRhSkuSubscriptionField,
UserCard,
UserPlan,
)
@ -5065,5 +5067,62 @@ class TestSuperUserManagement(ApiTestCase):
self.assertEqual(len(json["messages"]), 1)
class TestOrganizationRhSku(ApiTestCase):
def test_bind_sku_to_org(self):
self.login(ADMIN_ACCESS_USER)
self.postResponse(
resource_name=OrganizationRhSku,
params=dict(orgname=ORGANIZATION),
data={"subscription_id": 12345},
expected_code=201,
)
json = self.getJsonResponse(
resource_name=OrganizationRhSku,
params=dict(orgname=ORGANIZATION),
)
self.assertEqual(len(json), 1)
def test_bind_sku_duplicate(self):
user = model.user.get_user(ADMIN_ACCESS_USER)
org = model.organization.get_organization(ORGANIZATION)
model.organization_skus.bind_subscription_to_org(12345, org.id, user.id)
self.login(ADMIN_ACCESS_USER)
self.postResponse(
resource_name=OrganizationRhSku,
params=dict(orgname=ORGANIZATION),
data={"subscription_id": 12345},
expected_code=400,
)
def test_bind_sku_unauthorized(self):
# bind a sku that user does not own
self.login(ADMIN_ACCESS_USER)
self.postResponse(
resource_name=OrganizationRhSku,
params=dict(orgname=ORGANIZATION),
data={"subscription_id": 11111},
expected_code=401,
)
def test_remove_sku_from_org(self):
self.login(ADMIN_ACCESS_USER)
self.postResponse(
resource_name=OrganizationRhSku,
params=dict(orgname=ORGANIZATION),
data={"subscription_id": 12345},
expected_code=201,
)
self.deleteResponse(
resource_name=OrganizationRhSkuSubscriptionField,
params=dict(orgname=ORGANIZATION, subscription_id=12345),
expected_code=204,
)
json = self.getJsonResponse(
resource_name=OrganizationRhSku,
params=dict(orgname=ORGANIZATION),
)
self.assertEqual(len(json), 0)
if __name__ == "__main__":
unittest.main()

View File

@ -112,3 +112,5 @@ class TestConfig(DefaultConfig):
FEATURE_PROXY_CACHE = True
PERMANENTLY_DELETE_TAGS = True
RESET_CHILD_MANIFEST_EXPIRATION = True
FEATURE_RH_MARKETPLACE = True

View File

@ -5,8 +5,8 @@ from datetime import datetime
import requests
from data.billing import RH_SKUS, get_plan, get_plan_using_rh_sku
from data.model import entitlements
from data.billing import RH_SKUS, get_plan_using_rh_sku
from data.model import entitlements, organization_skus
logger = logging.getLogger(__name__)
@ -16,7 +16,7 @@ MARKETPLACE_FILE = "/conf/stack/quay-marketplace-api.crt"
MARKETPLACE_SECRET = "/conf/stack/quay-marketplace-api.key"
class RHUserAPI:
class RedHatUserApi(object):
def __init__(self, app_config):
self.cert = (MARKETPLACE_FILE, MARKETPLACE_SECRET)
self.user_endpoint = app_config.get("ENTITLEMENT_RECONCILIATION_USER_ENDPOINT")
@ -52,7 +52,6 @@ class RHUserAPI:
}
request_url = f"{self.user_endpoint}/v2/findUsers"
r = requests.request(
method="post",
url=request_url,
@ -69,7 +68,7 @@ class RHUserAPI:
return account_number
class RHMarketplaceAPI:
class RedHatSubscriptionApi(object):
def __init__(self, app_config):
self.cert = (MARKETPLACE_FILE, MARKETPLACE_SECRET)
self.marketplace_endpoint = app_config.get(
@ -108,6 +107,7 @@ class RHMarketplaceAPI:
now_ms = time.time() * 1000
# Is subscription still valid?
if now_ms < end_date:
logger.debug("subscription found for %s", str(skuId))
return subscription
return None
@ -165,15 +165,170 @@ class RHMarketplaceAPI:
)
return r.status_code
def find_stripe_subscription(self, account_number):
def get_subscription_sku(self, subscription_id):
"""
Returns the stripe plan/s relating to marketplace subscriptions
for a given account number
Return the sku for a specific subscription
"""
stripe_plans = []
request_url = f"{self.marketplace_endpoint}/subscription/v5/products/subscription_id={subscription_id}"
request_headers = {"Content-Type": "application/json"}
try:
r = requests.request(
method="get",
url=request_url,
cert=self.cert,
verify=True,
timeout=REQUEST_TIMEOUT,
headers=request_headers,
)
info = json.loads(r.content)
SubscriptionSKU = info[0]["sku"]
return SubscriptionSKU
except requests.exceptions.SSLError:
raise requests.exceptions.SSLError
def get_list_of_subscriptions(
self, account_number, filter_out_org_bindings=False, convert_to_stripe_plans=False
):
"""
Returns a list of all active subscriptions a user has
in RH marketplace
"""
subscription_list = []
for sku in RH_SKUS:
user_subscription = self.lookup_subscription(account_number, sku)
if user_subscription is not None:
stripe_plans.append(get_plan_using_rh_sku(sku))
bound_to_org = organization_skus.subscription_bound_to_org(user_subscription["id"])
return stripe_plans
if filter_out_org_bindings and bound_to_org[0]:
continue
if convert_to_stripe_plans:
subscription_list.append(get_plan_using_rh_sku(sku))
else:
# add in sku field for convenience
user_subscription["sku"] = sku
subscription_list.append(user_subscription)
return subscription_list
TEST_USER = {
"account_number": 12345,
"email": "test_user@test.com",
"username": "test_user",
"password": "password",
}
FREE_USER = {
"account_number": 23456,
"email": "free_user@test.com",
"username": "free_user",
"password": "password",
}
DEV_ACCOUNT_NUMBER = 76543
class FakeUserApi(object):
"""
Fake class used for tests
"""
def lookup_customer_id(self, email):
if email == TEST_USER["email"]:
return TEST_USER["account_number"]
if email == FREE_USER["email"]:
return FREE_USER["account_number"]
return None
def get_account_number(self, user):
if user.username == "devtable":
return DEV_ACCOUNT_NUMBER
return self.lookup_customer_id(user.email)
class FakeSubscriptionApi(object):
"""
Fake class used for tests
"""
def __init__(self):
self.subscription_extended = False
self.subscription_created = False
def lookup_subscription(self, customer_id, sku_id):
return None
def create_entitlement(self, customer_id, sku_id):
self.subscription_created = True
def extend_subscription(self, subscription_id, end_date):
self.subscription_extended = True
def get_subscription_sku(self, subscription_id):
if id == 12345:
return "FakeSku"
else:
return None
def get_list_of_subscriptions(
self, account_number, filter_out_org_bindings=False, convert_to_stripe_plans=False
):
if account_number == DEV_ACCOUNT_NUMBER:
return [
{
"id": 12345,
"sku": "FakeSku",
"privateRepos": 0,
}
]
return []
class MarketplaceUserApi(object):
def __init__(self, app=None):
self.app = app
if app is not None:
self.state = self.init_app(app)
else:
self.state = None
def init_app(self, app):
marketplace_enabled = app.config.get("FEATURE_RH_MARKETPLACE", False)
marketplace_user_api = FakeUserApi()
if marketplace_enabled and not app.config.get("TESTING"):
marketplace_user_api = RedHatUserApi(app.config)
app.extensions = getattr(app, "extensions", {})
app.extensions["marketplace_user_api"] = marketplace_user_api
return marketplace_user_api
def __getattr__(self, name):
return getattr(self.state, name, None)
class MarketplaceSubscriptionApi(object):
def __init__(self, app=None):
self.app = app
if app is not None:
self.state = self.init_app(app)
else:
self.state = None
def init_app(self, app):
marketplace_enabled = app.config.get("FEATURE_RH_MARKETPLACE", False)
marketplace_subscription_api = FakeSubscriptionApi()
if marketplace_enabled and not app.config.get("TESTING"):
marketplace_subscription_api = RedHatSubscriptionApi(app.config)
app.extensions = getattr(app, "extensions", {})
app.extensions["marketplace_subscription_api"] = marketplace_subscription_api
return marketplace_subscription_api
def __getattr__(self, name):
return getattr(self.state, name, None)

View File

@ -23,7 +23,6 @@ ALTER TABLE IF EXISTS ONLY public.userprompt DROP CONSTRAINT IF EXISTS fk_userpr
ALTER TABLE IF EXISTS ONLY public.userorganizationquota DROP CONSTRAINT IF EXISTS fk_userorganizationquota_organization;
ALTER TABLE IF EXISTS ONLY public.uploadedblob DROP CONSTRAINT IF EXISTS fk_uploadedblob_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.uploadedblob DROP CONSTRAINT IF EXISTS fk_uploadedblob_blob_id_imagestorage;
ALTER TABLE IF EXISTS ONLY public.torrentinfo DROP CONSTRAINT IF EXISTS fk_torrentinfo_storage_id_imagestorage;
ALTER TABLE IF EXISTS ONLY public.teamsync DROP CONSTRAINT IF EXISTS fk_teamsync_team_id_team;
ALTER TABLE IF EXISTS ONLY public.teamsync DROP CONSTRAINT IF EXISTS fk_teamsync_service_id_loginservice;
ALTER TABLE IF EXISTS ONLY public.teammemberinvite DROP CONSTRAINT IF EXISTS fk_teammemberinvite_user_id_user;
@ -33,20 +32,6 @@ ALTER TABLE IF EXISTS ONLY public.teammember DROP CONSTRAINT IF EXISTS fk_teamme
ALTER TABLE IF EXISTS ONLY public.teammember DROP CONSTRAINT IF EXISTS fk_teammember_team_id_team;
ALTER TABLE IF EXISTS ONLY public.team DROP CONSTRAINT IF EXISTS fk_team_role_id_teamrole;
ALTER TABLE IF EXISTS ONLY public.team DROP CONSTRAINT IF EXISTS fk_team_organization_id_user;
ALTER TABLE IF EXISTS ONLY public.tagtorepositorytag DROP CONSTRAINT IF EXISTS fk_tagtorepositorytag_tag_id_tag;
ALTER TABLE IF EXISTS ONLY public.tagtorepositorytag DROP CONSTRAINT IF EXISTS fk_tagtorepositorytag_repository_tag_id_repositorytag;
ALTER TABLE IF EXISTS ONLY public.tagtorepositorytag DROP CONSTRAINT IF EXISTS fk_tagtorepositorytag_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.tagmanifesttomanifest DROP CONSTRAINT IF EXISTS fk_tagmanifesttomanifest_tag_manifest_id_tagmanifest;
ALTER TABLE IF EXISTS ONLY public.tagmanifesttomanifest DROP CONSTRAINT IF EXISTS fk_tagmanifesttomanifest_manifest_id_manifest;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabelmap DROP CONSTRAINT IF EXISTS fk_tagmanifestlabelmap_tag_manifest_label_id_tagmanifestlabel;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabelmap DROP CONSTRAINT IF EXISTS fk_tagmanifestlabelmap_tag_manifest_id_tagmanifest;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabelmap DROP CONSTRAINT IF EXISTS fk_tagmanifestlabelmap_manifest_label_id_manifestlabel;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabelmap DROP CONSTRAINT IF EXISTS fk_tagmanifestlabelmap_manifest_id_manifest;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabelmap DROP CONSTRAINT IF EXISTS fk_tagmanifestlabelmap_label_id_label;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabel DROP CONSTRAINT IF EXISTS fk_tagmanifestlabel_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabel DROP CONSTRAINT IF EXISTS fk_tagmanifestlabel_label_id_label;
ALTER TABLE IF EXISTS ONLY public.tagmanifestlabel DROP CONSTRAINT IF EXISTS fk_tagmanifestlabel_annotated_id_tagmanifest;
ALTER TABLE IF EXISTS ONLY public.tagmanifest DROP CONSTRAINT IF EXISTS fk_tagmanifest_tag_id_repositorytag;
ALTER TABLE IF EXISTS ONLY public.tag DROP CONSTRAINT IF EXISTS fk_tag_tag_kind_id_tagkind;
ALTER TABLE IF EXISTS ONLY public.tag DROP CONSTRAINT IF EXISTS fk_tag_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.tag DROP CONSTRAINT IF EXISTS fk_tag_manifest_id_manifest;
@ -56,8 +41,6 @@ ALTER TABLE IF EXISTS ONLY public.star DROP CONSTRAINT IF EXISTS fk_star_reposit
ALTER TABLE IF EXISTS ONLY public.servicekey DROP CONSTRAINT IF EXISTS fk_servicekey_approval_id_servicekeyapproval;
ALTER TABLE IF EXISTS ONLY public.robotaccounttoken DROP CONSTRAINT IF EXISTS fk_robotaccounttoken_robot_account_id_user;
ALTER TABLE IF EXISTS ONLY public.robotaccountmetadata DROP CONSTRAINT IF EXISTS fk_robotaccountmetadata_robot_account_id_user;
ALTER TABLE IF EXISTS ONLY public.repositorytag DROP CONSTRAINT IF EXISTS fk_repositorytag_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.repositorytag DROP CONSTRAINT IF EXISTS fk_repositorytag_image_id_image;
ALTER TABLE IF EXISTS ONLY public.repositorysize DROP CONSTRAINT IF EXISTS fk_repositorysize_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.repositorysearchscore DROP CONSTRAINT IF EXISTS fk_repositorysearchscore_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.repositorypermission DROP CONSTRAINT IF EXISTS fk_repositorypermission_user_id_user;
@ -101,6 +84,8 @@ ALTER TABLE IF EXISTS ONLY public.permissionprototype DROP CONSTRAINT IF EXISTS
ALTER TABLE IF EXISTS ONLY public.permissionprototype DROP CONSTRAINT IF EXISTS fk_permissionprototype_delegate_user_id_user;
ALTER TABLE IF EXISTS ONLY public.permissionprototype DROP CONSTRAINT IF EXISTS fk_permissionprototype_delegate_team_id_team;
ALTER TABLE IF EXISTS ONLY public.permissionprototype DROP CONSTRAINT IF EXISTS fk_permissionprototype_activating_user_id_user;
ALTER TABLE IF EXISTS ONLY public.organizationrhskus DROP CONSTRAINT IF EXISTS fk_organizationrhskus_userid;
ALTER TABLE IF EXISTS ONLY public.organizationrhskus DROP CONSTRAINT IF EXISTS fk_organizationrhskus_orgid;
ALTER TABLE IF EXISTS ONLY public.oauthauthorizationcode DROP CONSTRAINT IF EXISTS fk_oauthauthorizationcode_application_id_oauthapplication;
ALTER TABLE IF EXISTS ONLY public.oauthapplication DROP CONSTRAINT IF EXISTS fk_oauthapplication_organization_id_user;
ALTER TABLE IF EXISTS ONLY public.oauthaccesstoken DROP CONSTRAINT IF EXISTS fk_oauthaccesstoken_authorized_user_id_user;
@ -111,9 +96,6 @@ ALTER TABLE IF EXISTS ONLY public.namespacegeorestriction DROP CONSTRAINT IF EXI
ALTER TABLE IF EXISTS ONLY public.messages DROP CONSTRAINT IF EXISTS fk_messages_media_type_id_mediatype;
ALTER TABLE IF EXISTS ONLY public.manifestsecuritystatus DROP CONSTRAINT IF EXISTS fk_manifestsecuritystatus_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.manifestsecuritystatus DROP CONSTRAINT IF EXISTS fk_manifestsecuritystatus_manifest_id_manifest;
ALTER TABLE IF EXISTS ONLY public.manifestlegacyimage DROP CONSTRAINT IF EXISTS fk_manifestlegacyimage_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.manifestlegacyimage DROP CONSTRAINT IF EXISTS fk_manifestlegacyimage_manifest_id_manifest;
ALTER TABLE IF EXISTS ONLY public.manifestlegacyimage DROP CONSTRAINT IF EXISTS fk_manifestlegacyimage_image_id_image;
ALTER TABLE IF EXISTS ONLY public.manifestlabel DROP CONSTRAINT IF EXISTS fk_manifestlabel_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.manifestlabel DROP CONSTRAINT IF EXISTS fk_manifestlabel_manifest_id_manifest;
ALTER TABLE IF EXISTS ONLY public.manifestlabel DROP CONSTRAINT IF EXISTS fk_manifestlabel_label_id_label;
@ -133,14 +115,9 @@ ALTER TABLE IF EXISTS ONLY public.imagestoragesignature DROP CONSTRAINT IF EXIST
ALTER TABLE IF EXISTS ONLY public.imagestoragesignature DROP CONSTRAINT IF EXISTS fk_imagestoragesignature_kind_id_imagestoragesignaturekind;
ALTER TABLE IF EXISTS ONLY public.imagestorageplacement DROP CONSTRAINT IF EXISTS fk_imagestorageplacement_storage_id_imagestorage;
ALTER TABLE IF EXISTS ONLY public.imagestorageplacement DROP CONSTRAINT IF EXISTS fk_imagestorageplacement_location_id_imagestoragelocation;
ALTER TABLE IF EXISTS ONLY public.image DROP CONSTRAINT IF EXISTS fk_image_storage_id_imagestorage;
ALTER TABLE IF EXISTS ONLY public.image DROP CONSTRAINT IF EXISTS fk_image_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.federatedlogin DROP CONSTRAINT IF EXISTS fk_federatedlogin_user_id_user;
ALTER TABLE IF EXISTS ONLY public.federatedlogin DROP CONSTRAINT IF EXISTS fk_federatedlogin_service_id_loginservice;
ALTER TABLE IF EXISTS ONLY public.emailconfirmation DROP CONSTRAINT IF EXISTS fk_emailconfirmation_user_id_user;
ALTER TABLE IF EXISTS ONLY public.derivedstorageforimage DROP CONSTRAINT IF EXISTS fk_derivedstorageforimage_transformation_constraint;
ALTER TABLE IF EXISTS ONLY public.derivedstorageforimage DROP CONSTRAINT IF EXISTS fk_derivedstorageforimage_source_image_id_image;
ALTER TABLE IF EXISTS ONLY public.derivedstorageforimage DROP CONSTRAINT IF EXISTS fk_derivedstorageforimage_derivative_id_imagestorage;
ALTER TABLE IF EXISTS ONLY public.deletedrepository DROP CONSTRAINT IF EXISTS fk_deletedrepository_repository_id_repository;
ALTER TABLE IF EXISTS ONLY public.deletednamespace DROP CONSTRAINT IF EXISTS fk_deletednamespace_namespace_id_user;
ALTER TABLE IF EXISTS ONLY public.blobupload DROP CONSTRAINT IF EXISTS fk_blobupload_repository_id_repository;
@ -325,6 +302,9 @@ DROP INDEX IF EXISTS public.permissionprototype_org_id;
DROP INDEX IF EXISTS public.permissionprototype_delegate_user_id;
DROP INDEX IF EXISTS public.permissionprototype_delegate_team_id;
DROP INDEX IF EXISTS public.permissionprototype_activating_user_id;
DROP INDEX IF EXISTS public.organizationrhskus_subscription_id_org_id_user_id;
DROP INDEX IF EXISTS public.organizationrhskus_subscription_id_org_id;
DROP INDEX IF EXISTS public.organizationrhskus_subscription_id;
DROP INDEX IF EXISTS public.oauthauthorizationcode_code_name;
DROP INDEX IF EXISTS public.oauthauthorizationcode_application_id;
DROP INDEX IF EXISTS public.oauthapplication_organization_id;
@ -538,6 +518,7 @@ ALTER TABLE IF EXISTS ONLY public.quayrelease DROP CONSTRAINT IF EXISTS pk_quayr
ALTER TABLE IF EXISTS ONLY public.quayregion DROP CONSTRAINT IF EXISTS pk_quayregion;
ALTER TABLE IF EXISTS ONLY public.proxycacheconfig DROP CONSTRAINT IF EXISTS pk_proxy_cache_config;
ALTER TABLE IF EXISTS ONLY public.permissionprototype DROP CONSTRAINT IF EXISTS pk_permissionprototype;
ALTER TABLE IF EXISTS ONLY public.organizationrhskus DROP CONSTRAINT IF EXISTS pk_organizationrhskus;
ALTER TABLE IF EXISTS ONLY public.oauthauthorizationcode DROP CONSTRAINT IF EXISTS pk_oauthauthorizationcode;
ALTER TABLE IF EXISTS ONLY public.oauthapplication DROP CONSTRAINT IF EXISTS pk_oauthapplication;
ALTER TABLE IF EXISTS ONLY public.oauthaccesstoken DROP CONSTRAINT IF EXISTS pk_oauthaccesstoken;
@ -640,6 +621,7 @@ ALTER TABLE IF EXISTS public.quayrelease ALTER COLUMN id DROP DEFAULT;
ALTER TABLE IF EXISTS public.quayregion ALTER COLUMN id DROP DEFAULT;
ALTER TABLE IF EXISTS public.proxycacheconfig ALTER COLUMN id DROP DEFAULT;
ALTER TABLE IF EXISTS public.permissionprototype ALTER COLUMN id DROP DEFAULT;
ALTER TABLE IF EXISTS public.organizationrhskus ALTER COLUMN id DROP DEFAULT;
ALTER TABLE IF EXISTS public.oauthauthorizationcode ALTER COLUMN id DROP DEFAULT;
ALTER TABLE IF EXISTS public.oauthapplication ALTER COLUMN id DROP DEFAULT;
ALTER TABLE IF EXISTS public.oauthaccesstoken ALTER COLUMN id DROP DEFAULT;
@ -792,6 +774,8 @@ DROP SEQUENCE IF EXISTS public.proxycacheconfig_id_seq;
DROP TABLE IF EXISTS public.proxycacheconfig;
DROP SEQUENCE IF EXISTS public.permissionprototype_id_seq;
DROP TABLE IF EXISTS public.permissionprototype;
DROP SEQUENCE IF EXISTS public.organizationrhskus_id_seq;
DROP TABLE IF EXISTS public.organizationrhskus;
DROP SEQUENCE IF EXISTS public.oauthauthorizationcode_id_seq;
DROP TABLE IF EXISTS public.oauthauthorizationcode;
DROP SEQUENCE IF EXISTS public.oauthapplication_id_seq;
@ -2786,6 +2770,42 @@ ALTER TABLE public.oauthauthorizationcode_id_seq OWNER TO quay;
ALTER SEQUENCE public.oauthauthorizationcode_id_seq OWNED BY public.oauthauthorizationcode.id;
--
-- Name: organizationrhskus; Type: TABLE; Schema: public; Owner: quay
--
CREATE TABLE public.organizationrhskus (
id integer NOT NULL,
subscription_id integer NOT NULL,
org_id integer NOT NULL,
user_id integer NOT NULL
);
ALTER TABLE public.organizationrhskus OWNER TO quay;
--
-- Name: organizationrhskus_id_seq; Type: SEQUENCE; Schema: public; Owner: quay
--
CREATE SEQUENCE public.organizationrhskus_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER TABLE public.organizationrhskus_id_seq OWNER TO quay;
--
-- Name: organizationrhskus_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: quay
--
ALTER SEQUENCE public.organizationrhskus_id_seq OWNED BY public.organizationrhskus.id;
--
-- Name: permissionprototype; Type: TABLE; Schema: public; Owner: quay
--
@ -5057,6 +5077,13 @@ ALTER TABLE ONLY public.oauthapplication ALTER COLUMN id SET DEFAULT nextval('pu
ALTER TABLE ONLY public.oauthauthorizationcode ALTER COLUMN id SET DEFAULT nextval('public.oauthauthorizationcode_id_seq'::regclass);
--
-- Name: organizationrhskus id; Type: DEFAULT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.organizationrhskus ALTER COLUMN id SET DEFAULT nextval('public.organizationrhskus_id_seq'::regclass);
--
-- Name: permissionprototype id; Type: DEFAULT; Schema: public; Owner: quay
--
@ -5437,7 +5464,7 @@ COPY public.accesstokenkind (id, name) FROM stdin;
--
COPY public.alembic_version (version_num) FROM stdin;
46980ea2dde5
b82361fba1cd
\.
@ -6271,6 +6298,14 @@ COPY public.oauthauthorizationcode (id, application_id, scope, data, code_creden
\.
--
-- Data for Name: organizationrhskus; Type: TABLE DATA; Schema: public; Owner: quay
--
COPY public.organizationrhskus (id, subscription_id, org_id, user_id) FROM stdin;
\.
--
-- Data for Name: permissionprototype; Type: TABLE DATA; Schema: public; Owner: quay
--
@ -6316,8 +6351,8 @@ COPY public.quayservice (id, name) FROM stdin;
--
COPY public.queueitem (id, queue_name, body, available_after, available, processing_expires, retries_remaining, state_id) FROM stdin;
1 namespacegc/2/ {"marker_id": 1, "original_username": "quay"} 2023-06-28 18:17:46.058418 t 2023-06-28 21:12:45.983351 5 f4026e00-acc1-4634-b560-7b9dfe4ea2c0
2 namespacegc/3/ {"marker_id": 2, "original_username": "clair"} 2023-06-28 18:17:51.112431 t 2023-06-28 21:12:51.080949 5 1f7f0b49-2e34-4a00-8414-2559e4bbe63f
1 namespacegc/2/ {"marker_id": 1, "original_username": "quay"} 2023-08-25 15:22:54.808849 t 2023-08-25 18:17:54.783824 5 89f5e206-0b48-4227-8e7f-15e9a549fa2e
\.
@ -7951,6 +7986,13 @@ SELECT pg_catalog.setval('public.oauthapplication_id_seq', 1, false);
SELECT pg_catalog.setval('public.oauthauthorizationcode_id_seq', 1, false);
--
-- Name: organizationrhskus_id_seq; Type: SEQUENCE SET; Schema: public; Owner: quay
--
SELECT pg_catalog.setval('public.organizationrhskus_id_seq', 1, false);
--
-- Name: permissionprototype_id_seq; Type: SEQUENCE SET; Schema: public; Owner: quay
--
@ -8716,6 +8758,14 @@ ALTER TABLE ONLY public.oauthauthorizationcode
ADD CONSTRAINT pk_oauthauthorizationcode PRIMARY KEY (id);
--
-- Name: organizationrhskus pk_organizationrhskus; Type: CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.organizationrhskus
ADD CONSTRAINT pk_organizationrhskus PRIMARY KEY (id);
--
-- Name: permissionprototype pk_permissionprototype; Type: CONSTRAINT; Schema: public; Owner: quay
--
@ -10258,6 +10308,27 @@ CREATE INDEX oauthauthorizationcode_application_id ON public.oauthauthorizationc
CREATE UNIQUE INDEX oauthauthorizationcode_code_name ON public.oauthauthorizationcode USING btree (code_name);
--
-- Name: organizationrhskus_subscription_id; Type: INDEX; Schema: public; Owner: quay
--
CREATE UNIQUE INDEX organizationrhskus_subscription_id ON public.organizationrhskus USING btree (subscription_id);
--
-- Name: organizationrhskus_subscription_id_org_id; Type: INDEX; Schema: public; Owner: quay
--
CREATE UNIQUE INDEX organizationrhskus_subscription_id_org_id ON public.organizationrhskus USING btree (subscription_id, org_id);
--
-- Name: organizationrhskus_subscription_id_org_id_user_id; Type: INDEX; Schema: public; Owner: quay
--
CREATE UNIQUE INDEX organizationrhskus_subscription_id_org_id_user_id ON public.organizationrhskus USING btree (subscription_id, org_id, user_id);
--
-- Name: permissionprototype_activating_user_id; Type: INDEX; Schema: public; Owner: quay
--
@ -11568,30 +11639,6 @@ ALTER TABLE ONLY public.deletedrepository
ADD CONSTRAINT fk_deletedrepository_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: derivedstorageforimage fk_derivedstorageforimage_derivative_id_imagestorage; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.derivedstorageforimage
ADD CONSTRAINT fk_derivedstorageforimage_derivative_id_imagestorage FOREIGN KEY (derivative_id) REFERENCES public.imagestorage(id);
--
-- Name: derivedstorageforimage fk_derivedstorageforimage_source_image_id_image; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.derivedstorageforimage
ADD CONSTRAINT fk_derivedstorageforimage_source_image_id_image FOREIGN KEY (source_image_id) REFERENCES public.image(id);
--
-- Name: derivedstorageforimage fk_derivedstorageforimage_transformation_constraint; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.derivedstorageforimage
ADD CONSTRAINT fk_derivedstorageforimage_transformation_constraint FOREIGN KEY (transformation_id) REFERENCES public.imagestoragetransformation(id);
--
-- Name: emailconfirmation fk_emailconfirmation_user_id_user; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
@ -11616,22 +11663,6 @@ ALTER TABLE ONLY public.federatedlogin
ADD CONSTRAINT fk_federatedlogin_user_id_user FOREIGN KEY (user_id) REFERENCES public."user"(id);
--
-- Name: image fk_image_repository_id_repository; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.image
ADD CONSTRAINT fk_image_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: image fk_image_storage_id_imagestorage; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.image
ADD CONSTRAINT fk_image_storage_id_imagestorage FOREIGN KEY (storage_id) REFERENCES public.imagestorage(id);
--
-- Name: imagestorageplacement fk_imagestorageplacement_location_id_imagestoragelocation; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
@ -11784,30 +11815,6 @@ ALTER TABLE ONLY public.manifestlabel
ADD CONSTRAINT fk_manifestlabel_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: manifestlegacyimage fk_manifestlegacyimage_image_id_image; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.manifestlegacyimage
ADD CONSTRAINT fk_manifestlegacyimage_image_id_image FOREIGN KEY (image_id) REFERENCES public.image(id);
--
-- Name: manifestlegacyimage fk_manifestlegacyimage_manifest_id_manifest; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.manifestlegacyimage
ADD CONSTRAINT fk_manifestlegacyimage_manifest_id_manifest FOREIGN KEY (manifest_id) REFERENCES public.manifest(id);
--
-- Name: manifestlegacyimage fk_manifestlegacyimage_repository_id_repository; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.manifestlegacyimage
ADD CONSTRAINT fk_manifestlegacyimage_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: manifestsecuritystatus fk_manifestsecuritystatus_manifest_id_manifest; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
@ -11888,6 +11895,22 @@ ALTER TABLE ONLY public.oauthauthorizationcode
ADD CONSTRAINT fk_oauthauthorizationcode_application_id_oauthapplication FOREIGN KEY (application_id) REFERENCES public.oauthapplication(id);
--
-- Name: organizationrhskus fk_organizationrhskus_orgid; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.organizationrhskus
ADD CONSTRAINT fk_organizationrhskus_orgid FOREIGN KEY (org_id) REFERENCES public."user"(id);
--
-- Name: organizationrhskus fk_organizationrhskus_userid; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.organizationrhskus
ADD CONSTRAINT fk_organizationrhskus_userid FOREIGN KEY (user_id) REFERENCES public."user"(id);
--
-- Name: permissionprototype fk_permissionprototype_activating_user_id_user; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
@ -12232,22 +12255,6 @@ ALTER TABLE ONLY public.repositorysize
ADD CONSTRAINT fk_repositorysize_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: repositorytag fk_repositorytag_image_id_image; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.repositorytag
ADD CONSTRAINT fk_repositorytag_image_id_image FOREIGN KEY (image_id) REFERENCES public.image(id);
--
-- Name: repositorytag fk_repositorytag_repository_id_repository; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.repositorytag
ADD CONSTRAINT fk_repositorytag_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: robotaccountmetadata fk_robotaccountmetadata_robot_account_id_user; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
@ -12320,118 +12327,6 @@ ALTER TABLE ONLY public.tag
ADD CONSTRAINT fk_tag_tag_kind_id_tagkind FOREIGN KEY (tag_kind_id) REFERENCES public.tagkind(id);
--
-- Name: tagmanifest fk_tagmanifest_tag_id_repositorytag; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifest
ADD CONSTRAINT fk_tagmanifest_tag_id_repositorytag FOREIGN KEY (tag_id) REFERENCES public.repositorytag(id);
--
-- Name: tagmanifestlabel fk_tagmanifestlabel_annotated_id_tagmanifest; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabel
ADD CONSTRAINT fk_tagmanifestlabel_annotated_id_tagmanifest FOREIGN KEY (annotated_id) REFERENCES public.tagmanifest(id);
--
-- Name: tagmanifestlabel fk_tagmanifestlabel_label_id_label; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabel
ADD CONSTRAINT fk_tagmanifestlabel_label_id_label FOREIGN KEY (label_id) REFERENCES public.label(id);
--
-- Name: tagmanifestlabel fk_tagmanifestlabel_repository_id_repository; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabel
ADD CONSTRAINT fk_tagmanifestlabel_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: tagmanifestlabelmap fk_tagmanifestlabelmap_label_id_label; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabelmap
ADD CONSTRAINT fk_tagmanifestlabelmap_label_id_label FOREIGN KEY (label_id) REFERENCES public.label(id);
--
-- Name: tagmanifestlabelmap fk_tagmanifestlabelmap_manifest_id_manifest; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabelmap
ADD CONSTRAINT fk_tagmanifestlabelmap_manifest_id_manifest FOREIGN KEY (manifest_id) REFERENCES public.manifest(id);
--
-- Name: tagmanifestlabelmap fk_tagmanifestlabelmap_manifest_label_id_manifestlabel; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabelmap
ADD CONSTRAINT fk_tagmanifestlabelmap_manifest_label_id_manifestlabel FOREIGN KEY (manifest_label_id) REFERENCES public.manifestlabel(id);
--
-- Name: tagmanifestlabelmap fk_tagmanifestlabelmap_tag_manifest_id_tagmanifest; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabelmap
ADD CONSTRAINT fk_tagmanifestlabelmap_tag_manifest_id_tagmanifest FOREIGN KEY (tag_manifest_id) REFERENCES public.tagmanifest(id);
--
-- Name: tagmanifestlabelmap fk_tagmanifestlabelmap_tag_manifest_label_id_tagmanifestlabel; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifestlabelmap
ADD CONSTRAINT fk_tagmanifestlabelmap_tag_manifest_label_id_tagmanifestlabel FOREIGN KEY (tag_manifest_label_id) REFERENCES public.tagmanifestlabel(id);
--
-- Name: tagmanifesttomanifest fk_tagmanifesttomanifest_manifest_id_manifest; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifesttomanifest
ADD CONSTRAINT fk_tagmanifesttomanifest_manifest_id_manifest FOREIGN KEY (manifest_id) REFERENCES public.manifest(id);
--
-- Name: tagmanifesttomanifest fk_tagmanifesttomanifest_tag_manifest_id_tagmanifest; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagmanifesttomanifest
ADD CONSTRAINT fk_tagmanifesttomanifest_tag_manifest_id_tagmanifest FOREIGN KEY (tag_manifest_id) REFERENCES public.tagmanifest(id);
--
-- Name: tagtorepositorytag fk_tagtorepositorytag_repository_id_repository; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagtorepositorytag
ADD CONSTRAINT fk_tagtorepositorytag_repository_id_repository FOREIGN KEY (repository_id) REFERENCES public.repository(id);
--
-- Name: tagtorepositorytag fk_tagtorepositorytag_repository_tag_id_repositorytag; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagtorepositorytag
ADD CONSTRAINT fk_tagtorepositorytag_repository_tag_id_repositorytag FOREIGN KEY (repository_tag_id) REFERENCES public.repositorytag(id);
--
-- Name: tagtorepositorytag fk_tagtorepositorytag_tag_id_tag; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.tagtorepositorytag
ADD CONSTRAINT fk_tagtorepositorytag_tag_id_tag FOREIGN KEY (tag_id) REFERENCES public.tag(id);
--
-- Name: team fk_team_organization_id_user; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
@ -12504,14 +12399,6 @@ ALTER TABLE ONLY public.teamsync
ADD CONSTRAINT fk_teamsync_team_id_team FOREIGN KEY (team_id) REFERENCES public.team(id);
--
-- Name: torrentinfo fk_torrentinfo_storage_id_imagestorage; Type: FK CONSTRAINT; Schema: public; Owner: quay
--
ALTER TABLE ONLY public.torrentinfo
ADD CONSTRAINT fk_torrentinfo_storage_id_imagestorage FOREIGN KEY (storage_id) REFERENCES public.imagestorage(id);
--
-- Name: uploadedblob fk_uploadedblob_blob_id_imagestorage; Type: FK CONSTRAINT; Schema: public; Owner: quay
--

View File

@ -5,8 +5,7 @@ import time
import features
from app import app
from app import billing as stripe
from app import rh_marketplace_api as internal_marketplace_api
from app import rh_user_api as internal_user_api
from app import marketplace_subscriptions, marketplace_users
from data import model
from data.billing import RH_SKUS, get_plan
from data.model import entitlements
@ -108,7 +107,7 @@ class ReconciliationWorker(Worker):
# try to acquire lock
if skip_lock_for_testing:
self._perform_reconciliation(
user_api=internal_user_api, marketplace_api=internal_marketplace_api
user_api=marketplace_users, marketplace_api=marketplace_subscriptions
)
else:
try:
@ -117,7 +116,7 @@ class ReconciliationWorker(Worker):
lock_ttl=RECONCILIATION_TIMEOUT + LOCK_TIMEOUT_PADDING,
):
self._perform_reconciliation(
user_api=internal_user_api, marketplace_api=internal_marketplace_api
user_api=marketplace_users, marketplace_api=marketplace_subscriptions
)
except LockNotAcquiredException:
logger.debug("Could not acquire global lock for entitlement reconciliation")

View File

@ -1,79 +1,34 @@
import random
import string
from datetime import datetime
from test.fixtures import *
from unittest.mock import MagicMock, patch
from dateutil.relativedelta import relativedelta
from unittest.mock import patch
from data import model
from util.marketplace import FakeSubscriptionApi, FakeUserApi
from workers.reconciliationworker import ReconciliationWorker
TEST_USER = {
"account_number": 12345,
"email": "test_user@test.com",
"username": "test_user",
"password": "password",
}
FREE_USER = {
"account_number": 23456,
"email": "free_user@test.com",
"username": "free_user",
"password": "password",
}
class FakeUserApi:
def lookup_customer_id(self, email):
if email == TEST_USER["email"]:
return TEST_USER["account_number"]
if email == FREE_USER["email"]:
return FREE_USER["account_number"]
return None
class FakeMarketplaceApi:
def __init__(self):
self.subscription_extended = False
self.subscription_created = False
def lookup_subscription(self, customerId, sku_id):
return None
def create_entitlement(self, customerId, skuId):
pass
internal_user_api = FakeUserApi()
internal_marketplace_api = FakeMarketplaceApi()
user_api = FakeUserApi()
marketplace_api = FakeSubscriptionApi()
worker = ReconciliationWorker()
def test_create_for_stripe_user(initialized_db):
test_user = model.user.create_user(
TEST_USER["username"], TEST_USER["password"], TEST_USER["email"]
)
test_user = model.user.create_user("test_user", "password", "test_user@test.com")
test_user.stripe_id = "cus_" + "".join(random.choices(string.ascii_lowercase, k=14))
test_user.save()
with patch.object(internal_marketplace_api, "create_entitlement") as mock:
worker._perform_reconciliation(
user_api=internal_user_api, marketplace_api=internal_marketplace_api
)
with patch.object(marketplace_api, "create_entitlement") as mock:
worker._perform_reconciliation(user_api=user_api, marketplace_api=marketplace_api)
mock.assert_called()
def test_skip_free_user(initialized_db):
free_user = model.user.create_user(
FREE_USER["username"], FREE_USER["password"], FREE_USER["email"]
)
free_user = model.user.create_user("free_user", "password", "free_user@test.com")
free_user.save()
with patch.object(internal_marketplace_api, "create_entitlement") as mock:
worker._perform_reconciliation(
user_api=internal_user_api, marketplace_api=internal_marketplace_api
)
with patch.object(marketplace_api, "create_entitlement") as mock:
worker._perform_reconciliation(user_api=user_api, marketplace_api=marketplace_api)
mock.assert_not_called()