2017-03-10 15:36:30 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
import logging
|
2017-11-30 15:30:41 +01:00
|
|
|
from urllib.parse import urljoin
|
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
from flask import current_app
|
|
|
|
from flask_script import Manager
|
2017-11-30 15:30:41 +01:00
|
|
|
import requests
|
2017-03-10 15:36:30 +01:00
|
|
|
|
|
|
|
from pillar.cli import manager
|
2017-07-11 14:27:33 +02:00
|
|
|
from pillar.api import service
|
2017-03-10 15:36:30 +01:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
manager_cloud = Manager(
|
|
|
|
current_app, usage="Blender Cloud scripts")
|
|
|
|
|
|
|
|
|
2017-07-11 14:27:33 +02:00
|
|
|
@manager_cloud.command
|
|
|
|
def create_groups():
|
|
|
|
"""Creates the admin/demo/subscriber groups."""
|
|
|
|
|
|
|
|
import pprint
|
|
|
|
|
|
|
|
group_ids = {}
|
|
|
|
groups_coll = current_app.db('groups')
|
|
|
|
|
|
|
|
for group_name in ['admin', 'demo', 'subscriber']:
|
|
|
|
if groups_coll.find({'name': group_name}).count():
|
|
|
|
log.info('Group %s already exists, skipping', group_name)
|
|
|
|
continue
|
|
|
|
result = groups_coll.insert_one({'name': group_name})
|
|
|
|
group_ids[group_name] = result.inserted_id
|
|
|
|
|
|
|
|
service.fetch_role_to_group_id_map()
|
|
|
|
log.info('Created groups:\n%s', pprint.pformat(group_ids))
|
|
|
|
|
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
@manager_cloud.command
|
|
|
|
def reconcile_subscribers():
|
|
|
|
"""For every user, check their subscription status with the store."""
|
2017-07-11 14:29:02 +02:00
|
|
|
|
2017-07-11 14:57:57 +02:00
|
|
|
import threading
|
|
|
|
import concurrent.futures
|
2017-11-30 15:30:41 +01:00
|
|
|
|
|
|
|
from pillar.auth import UserClass
|
2017-12-05 11:56:33 +01:00
|
|
|
from pillar.api import blender_id
|
2017-11-30 15:30:41 +01:00
|
|
|
from pillar.api.blender_cloud.subscription import do_update_subscription
|
|
|
|
|
|
|
|
sessions = threading.local()
|
2017-03-10 15:36:30 +01:00
|
|
|
|
2017-07-11 14:29:02 +02:00
|
|
|
service.fetch_role_to_group_id_map()
|
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
users_coll = current_app.data.driver.db['users']
|
2017-11-30 15:30:41 +01:00
|
|
|
found = users_coll.find({'auth.provider': 'blender-id'})
|
|
|
|
count_users = found.count()
|
|
|
|
count_skipped = count_processed = 0
|
|
|
|
log.info('Processing %i users', count_users)
|
2017-07-11 14:40:46 +02:00
|
|
|
|
2017-07-11 14:57:57 +02:00
|
|
|
lock = threading.Lock()
|
2017-07-11 14:29:02 +02:00
|
|
|
|
2017-07-11 14:57:57 +02:00
|
|
|
real_current_app = current_app._get_current_object()
|
2017-07-11 14:29:02 +02:00
|
|
|
|
2017-11-30 15:30:41 +01:00
|
|
|
api_token = real_current_app.config['BLENDER_ID_USER_INFO_TOKEN']
|
|
|
|
api_url = real_current_app.config['BLENDER_ID_USER_INFO_API']
|
|
|
|
|
2017-07-11 14:57:57 +02:00
|
|
|
def do_user(idx, user):
|
2017-11-30 15:30:41 +01:00
|
|
|
nonlocal count_skipped, count_processed
|
2017-07-11 14:29:02 +02:00
|
|
|
|
2017-11-30 15:30:41 +01:00
|
|
|
log.info('Processing %i/%i %s', idx + 1, count_users, user['email'])
|
|
|
|
|
|
|
|
# Get the Requests session for this thread.
|
|
|
|
try:
|
|
|
|
sess = sessions.session
|
|
|
|
except AttributeError:
|
|
|
|
sess = sessions.session = requests.Session()
|
2017-07-11 14:57:57 +02:00
|
|
|
|
2017-11-30 15:30:41 +01:00
|
|
|
# Get the info from Blender ID
|
2017-12-05 11:56:33 +01:00
|
|
|
bid_user_id = blender_id.get_user_blenderid(user)
|
|
|
|
if not bid_user_id:
|
2017-11-30 15:30:41 +01:00
|
|
|
with lock:
|
|
|
|
count_skipped += 1
|
|
|
|
return
|
2017-07-11 14:57:57 +02:00
|
|
|
|
2017-11-30 15:30:41 +01:00
|
|
|
url = urljoin(api_url, bid_user_id)
|
|
|
|
resp = sess.get(url, headers={'Authorization': f'Bearer {api_token}'})
|
2017-07-11 14:57:57 +02:00
|
|
|
|
2017-11-30 15:30:41 +01:00
|
|
|
if resp.status_code == 404:
|
|
|
|
log.info('User %s with Blender ID %s not found, skipping', user['email'], bid_user_id)
|
2017-07-11 14:57:57 +02:00
|
|
|
with lock:
|
2017-11-30 15:30:41 +01:00
|
|
|
count_skipped += 1
|
|
|
|
return
|
|
|
|
|
|
|
|
if resp.status_code != 200:
|
|
|
|
log.error('Unable to reach Blender ID (code %d), aborting', resp.status_code)
|
|
|
|
with lock:
|
|
|
|
count_skipped += 1
|
|
|
|
return
|
|
|
|
|
|
|
|
bid_user = resp.json()
|
|
|
|
if not bid_user:
|
|
|
|
log.error('Unable to parse response for user %s, aborting', user['email'])
|
|
|
|
with lock:
|
|
|
|
count_skipped += 1
|
|
|
|
return
|
|
|
|
|
|
|
|
# Actually update the user, and do it thread-safe just to be sure.
|
|
|
|
with real_current_app.app_context():
|
|
|
|
local_user = UserClass.construct('', user)
|
|
|
|
with lock:
|
|
|
|
do_update_subscription(local_user, bid_user)
|
|
|
|
count_processed += 1
|
2017-07-11 14:57:57 +02:00
|
|
|
|
|
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
|
|
|
future_to_user = {executor.submit(do_user, idx, user): user
|
|
|
|
for idx, user in enumerate(found)}
|
|
|
|
for future in concurrent.futures.as_completed(future_to_user):
|
|
|
|
user = future_to_user[future]
|
|
|
|
try:
|
|
|
|
future.result()
|
|
|
|
except Exception as ex:
|
|
|
|
log.exception('Error updating user %s', user)
|
2017-03-10 15:36:30 +01:00
|
|
|
|
2017-11-30 15:30:41 +01:00
|
|
|
log.info('Done reconciling %d subscribers', count_users)
|
|
|
|
log.info(' processed: %d', count_processed)
|
|
|
|
log.info(' skipped : %d', count_skipped)
|
2017-03-10 15:36:30 +01:00
|
|
|
|
2017-07-11 14:29:02 +02:00
|
|
|
|
2017-03-10 15:36:30 +01:00
|
|
|
manager.add_command("cloud", manager_cloud)
|