def _datasets(user, application_template): # The interface must: # - show the datasets that the application already has access to, # - show the datasets that the user has access to, # - not duplicate datasets that are both of the above, and # - allow selection or unselection of only the datasets the user has # access to that are also REQUIRES_AUTHORIZATION. # This means the user won't be able to unselect datasets that the # application already has access to, but the users doesn't. This is # deliberate: if they could unselect such datasets, they wouldn't be able # to reverse the change, which may need urgent contact with support to # restore access source_tables_user = source_tables_for_user(user) source_tables_app = source_tables_for_app(application_template) selectable_dataset_ids = set( source_table['dataset']['id'] for source_table in source_tables_user if source_table['dataset']['user_access_type'] == 'REQUIRES_AUTHORIZATION' ) selected_dataset_ids = set( source_table['dataset']['id'] for source_table in source_tables_app ) source_tables_without_select_info = _without_duplicates_preserve_order( source_tables_user + source_tables_app, key=lambda x: (x['dataset']['id'], x['schema'], x['table']), ) source_tables = [ { 'dataset': { 'selectable': source_table['dataset']['id'] in selectable_dataset_ids, 'selected': source_table['dataset']['id'] in selected_dataset_ids, **source_table['dataset'], }, **{key: value for key, value in source_table.items() if key != 'dataset'}, } for source_table in source_tables_without_select_info ] tables_sorted_by_dataset = sorted( [table for table in source_tables], key=lambda x: ( x['dataset']['name'], x['dataset']['id'], x['schema'], x['table'], ), ) return [ (dataset, list(tables)) for dataset, tables in itertools.groupby( tables_sorted_by_dataset, lambda x: x['dataset'] ) ]
def spawn( name, user_id, tag, application_instance_id, spawner_options, ): user = get_user_model().objects.get(pk=user_id) application_instance = ApplicationInstance.objects.get( id=application_instance_id) (source_tables, db_role_schema_suffix, db_user) = ( ( source_tables_for_user(user), db_role_schema_suffix_for_user(user), postgres_user(user.email), ) if application_instance.application_template.application_type == 'TOOL' else ( source_tables_for_app(application_instance.application_template), db_role_schema_suffix_for_app( application_instance.application_template), postgres_user( application_instance.application_template.host_basename), )) credentials = new_private_database_credentials( db_role_schema_suffix, source_tables, db_user, user, valid_for=datetime.timedelta(days=31), ) if application_instance.application_template.application_type == 'TOOL': # For AppStream to access credentials write_credentials_to_bucket(user, credentials) app_schema = f'{USER_SCHEMA_STEM}{db_role_schema_suffix}' get_spawner(name).spawn( user, tag, application_instance, spawner_options, credentials, app_schema, )
def application_api_PUT(request, public_host): # A transaction is unnecessary: the single_running_or_spawning_integrity # key prevents duplicate spawning/running applications at the same # public host try: application_instance = get_api_visible_application_instance_by_public_host( public_host) except ApplicationInstance.DoesNotExist: pass else: return JsonResponse({'message': 'Application instance already exists'}, status=409) try: ( application_template, tag, _, commit_id, ) = application_template_tag_user_commit_from_host(public_host) except ApplicationTemplate.DoesNotExist: return JsonResponse({'message': 'Application template does not exist'}, status=400) app_type = application_template.application_type (source_tables, db_role_schema_suffix, db_user) = (( source_tables_for_user(request.user), db_role_schema_suffix_for_user(request.user), postgres_user(request.user.email), ) if app_type == 'TOOL' else ( source_tables_for_app(application_template), db_role_schema_suffix_for_app(application_template), postgres_user(application_template.host_basename), )) credentials = new_private_database_credentials(db_role_schema_suffix, source_tables, db_user) if app_type == 'TOOL': # For AppStream to access credentials write_credentials_to_bucket(request.user, credentials) try: memory, cpu = request.GET['__memory_cpu'].split('_') except KeyError: memory = None cpu = None spawner_options = json.dumps(application_options(application_template)) try: application_instance = ApplicationInstance.objects.create( owner=request.user, application_template=application_template, spawner=application_template.spawner, spawner_application_template_options=spawner_options, spawner_application_instance_id=json.dumps({}), public_host=public_host, state='SPAWNING', single_running_or_spawning_integrity=public_host, cpu=cpu, memory=memory, commit_id=commit_id, ) except IntegrityError: application_instance = get_api_visible_application_instance_by_public_host( public_host) else: # The database users are stored so when the database users are cleaned up, # we know _not_ to delete any users used by running or spawning apps for creds in credentials: ApplicationInstanceDbUsers.objects.create( application_instance=application_instance, db_id=creds['db_id'], db_username=creds['db_user'], ) app_schema = f'{USER_SCHEMA_STEM}{db_role_schema_suffix}' spawn.delay( application_template.spawner, request.user.email, str(request.user.profile.sso_id), tag, application_instance.id, spawner_options, credentials, app_schema, ) return JsonResponse(api_application_dict(application_instance), status=200)