def app_publish( dev_blockchain_id, app_domain, app_method_list, app_index_uris, app_index_file, app_driver_hints=[], data_privkey=None, proxy=None, wallet_keys=None, config_path=CONFIG_PATH ): """ Instantiate an application. * replicate the (opaque) app index file to "index.html" to each URL in app_uris * replicate the list of URIs and the list of methods to ".blockstack" via each of the client's storage drivers. This succeeds even if the app already exists (in which case, it will be overwritten). This method is idempotent, so it can be retried on failure. data_privkey should be the publisher's private key (i.e. their data key) name should be the blockchain ID that points to data_pubkey Return {'status': True, 'config_fq_data_id': config's fully-qualified data ID, 'index_fq_data_id': index file's fully-qualified data ID} on success Return {'error': ...} on error """ proxy = get_default_proxy() if proxy is None else proxy # replicate configuration data (method list and app URIs) app_cfg = { 'blockchain_id': dev_blockchain_id, 'app_domain': app_domain, 'index_uris': app_index_uris, 'api_methods': app_method_list, 'driver_hints': app_driver_hints, } jsonschema.validate(app_cfg, APP_CONFIG_SCHEMA) config_data_id = '{}/.blockstack'.format(app_domain) res = data.put_mutable(config_data_id, app_cfg, blockchain_id=dev_blockchain_id, data_privkey=data_privkey, wallet_keys=wallet_keys, config_path=config_path, fully_qualified_data_id=True) if 'error' in res: log.error('Failed to replicate application configuration {}: {}'.format(config_data_id, res['error'])) return {'error': 'Failed to replicate application config'} # what drivers to use for the index file? urls = user_db.urls_from_uris(app_index_uris) driver_names = [] for url in urls: drivers = storage.get_drivers_for_url(url) driver_names += [d.__name__ for d in drivers] driver_names = list(set(driver_names)) index_data_id = "{}/index.html".format(app_domain) # replicate app index file (at least one must succeed) # NOTE: the publisher is free to use alternative URIs that are not supported; they'll just be ignored. res = data.put_mutable( index_data_id, app_index_file, blockchain_id=dev_blockchain_id, data_privkey=data_privkey, storage_drivers=driver_names, wallet_keys=wallet_keys, config_path=config_path, fully_qualified_data_id=True) if 'error' in res: log.error("Failed to replicate application index file to {}: {}".format(",".join(urls), res['error'])) return {'error': 'Failed to replicate index file'} return {'status': True, 'config_fq_data_id': config_data_id, 'index_fq_data_id': index_data_id}
def app_unpublish( blockchain_id, app_domain, force=False, data_privkey=None, app_config=None, wallet_keys=None, proxy=None, config_path=CONFIG_PATH ): """ Unpublish an application Deletes its config and index. Does NOT delete its resources. Does NOT delete user data. if force is True, then we will try to delete the app state even if we can't load the app config WARNING: force can be dangerous, since it can delete data via drivers that were never meant for this app. Use with caution! Return {'status': True, 'app_config': ..., 'retry': ...} on success. If retry is True, then retry this method with the given app_config Return {'error': ...} on error """ proxy = get_default_proxy() if proxy is None else proxy # find out where to delete from data_pubkey = None if data_privkey is not None: data_pubkey = get_pubkey_hex(str(data_privkey)) if app_config is None: app_config = app_get_config(blockchain_id, app_domain, data_pubkey=data_pubkey, proxy=proxy, config_path=CONFIG_PATH ) if 'error' in app_config: if not force: log.error("Failed to load app config for {}'s {}".format(blockchain_id, app_domain)) return {'error': 'Failed to load app config'} else: # keep going app_config = None log.warning("Failed to load app config, but proceeding at caller request") config_data_id = storage.make_fq_data_id(app_domain, '.blockstack') index_data_id = storage.make_fq_data_id(app_domain, 'index.html') storage_drivers = None if app_config is not None: # only use the ones we have to urls = user_db.urls_from_uris(app_config['index_uris']) driver_names = [] for url in urls: drivers = storage.get_drivers_for_url(url) driver_names += [d.__name__ for d in drivers] storage_drivers = list(set(driver_names)) ret = {} # delete the index index_tombstone = storage.make_data_tombstone(index_data_id) signed_index_tombstone = storage.sign_data_tombstone(index_data_id, data_privkey) res = data.delete_mutable(index_data_id, [signed_index_tombstone], proxy=proxy, storage_drivers=storage_drivers, blockchain_id=blockchain_id, is_fq_data_id=True, config_path=config_path) if 'error' in res: log.warning("Failed to delete index file {}".format(index_data_id)) ret['app_config'] = app_config ret['retry'] = True # delete the config config_tombstone = storage.make_data_tombstone(config_data_id) signed_config_tombstone = storage.sign_data_tombstone(config_data_id, data_privkey) res = data.delete_mutable(config_data_id, [signed_config_tombstone], proxy=proxy, blockchain_id=blockchain_id, is_fq_data_id=True, config_path=config_path) if 'error' in res: log.warning("Failed to delete config file {}".format(config_data_id)) if not ret.has_key('app_config'): ret['app_config'] = app_config ret['retry'] = True ret['status'] = True return ret
def app_publish( dev_blockchain_id, app_domain, app_method_list, app_index_uris, app_index_file, app_driver_hints=[], data_privkey=None, proxy=None, wallet_keys=None, config_path=CONFIG_PATH ): """ Instantiate an application. * replicate the (opaque) app index file to "index.html" to each URL in app_uris * replicate the list of URIs and the list of methods to ".blockstack" via each of the client's storage drivers. This succeeds even if the app already exists (in which case, it will be overwritten). This method is idempotent, so it can be retried on failure. data_privkey should be the publisher's private key (i.e. their data key) name should be the blockchain ID that points to data_pubkey Return {'status': True, 'config_fq_data_id': config's fully-qualified data ID, 'index_fq_data_id': index file's fully-qualified data ID} on success Return {'error': ...} on error """ if data_privkey is None: assert wallet_keys, 'Missing both data private key and wallet keys' data_privkey = wallet_keys.get('data_privkey') assert data_privkey, "Wallet does not have a data private key" proxy = get_default_proxy() if proxy is None else proxy # replicate configuration data (method list and app URIs) app_cfg = { 'blockchain_id': dev_blockchain_id, 'app_domain': app_domain, 'index_uris': app_index_uris, 'api_methods': app_method_list, 'driver_hints': app_driver_hints, } jsonschema.validate(app_cfg, APP_CONFIG_SCHEMA) data_pubkey = get_pubkey_hex(data_privkey) config_data_id = storage.make_fq_data_id(app_domain, '.blockstack') app_cfg_blob = data.make_mutable_data_info(config_data_id, app_cfg, is_fq_data_id=True) app_cfg_str = data.data_blob_serialize(app_cfg_blob) app_cfg_sig = data.data_blob_sign( app_cfg_str, data_privkey ) res = data.put_mutable(config_data_id, app_cfg_str, data_pubkey, app_cfg_sig, app_cfg_blob['version'], blockchain_id=dev_blockchain_id, config_path=config_path) if 'error' in res: log.error('Failed to replicate application configuration {}: {}'.format(config_data_id, res['error'])) return {'error': 'Failed to replicate application config'} # what drivers to use for the index file? urls = user_db.urls_from_uris(app_index_uris) driver_names = [] for url in urls: drivers = storage.get_drivers_for_url(url) driver_names += [d.__name__ for d in drivers] driver_names = list(set(driver_names)) index_data_id = storage.make_fq_data_id(app_domain, 'index.html') # replicate app index file (at least one must succeed) # NOTE: the publisher is free to use alternative URIs that are not supported; they'll just be ignored. app_index_blob = data.make_mutable_data_info(index_data_id, app_index_file, is_fq_data_id=True) app_index_blob_str = data.data_blob_serialize(app_index_blob) app_index_sig = data.data_blob_sign(app_index_blob_str, data_privkey) res = data.put_mutable( index_data_id, app_index_blob_str, data_pubkey, app_index_sig, app_index_blob['version'], blockchain_id=dev_blockchain_id, config_path=config_path, storage_drivers=app_driver_hints ) if 'error' in res: log.error("Failed to replicate application index file to {}: {}".format(",".join(urls), res['error'])) return {'error': 'Failed to replicate index file'} return {'status': True, 'config_fq_data_id': config_data_id, 'index_fq_data_id': index_data_id}
def app_unpublish(blockchain_id, app_domain, force=False, data_privkey=None, app_config=None, wallet_keys=None, proxy=None, config_path=CONFIG_PATH): """ Unpublish an application Deletes its config and index. Does NOT delete its resources. Does NOT delete user data. if force is True, then we will try to delete the app state even if we can't load the app config WARNING: force can be dangerous, since it can delete data via drivers that were never meant for this app. Use with caution! Return {'status': True, 'app_config': ..., 'retry': ...} on success. If retry is True, then retry this method with the given app_config Return {'error': ...} on error """ proxy = get_default_proxy() if proxy is None else proxy # find out where to delete from data_pubkey = None if data_privkey is not None: data_pubkey = get_pubkey_hex(str(data_privkey)) if app_config is None: app_config = app_get_config(blockchain_id, app_domain, data_pubkey=data_pubkey, proxy=proxy, config_path=CONFIG_PATH) if 'error' in app_config: if not force: log.error("Failed to load app config for {}'s {}".format( blockchain_id, app_domain)) return {'error': 'Failed to load app config'} else: # keep going app_config = None log.warning( "Failed to load app config, but proceeding at caller request" ) config_data_id = storage.make_fq_data_id(app_domain, '.blockstack') index_data_id = storage.make_fq_data_id(app_domain, 'index.html') storage_drivers = None if app_config is not None: # only use the ones we have to urls = user_db.urls_from_uris(app_config['index_uris']) driver_names = [] for url in urls: drivers = storage.get_drivers_for_url(url) driver_names += [d.__name__ for d in drivers] storage_drivers = list(set(driver_names)) ret = {} # delete the index index_tombstone = storage.make_data_tombstone(index_data_id) signed_index_tombstone = storage.sign_data_tombstone( index_data_id, data_privkey) res = data.delete_mutable(index_data_id, [signed_index_tombstone], proxy=proxy, storage_drivers=storage_drivers, blockchain_id=blockchain_id, is_fq_data_id=True, config_path=config_path) if 'error' in res: log.warning("Failed to delete index file {}".format(index_data_id)) ret['app_config'] = app_config ret['retry'] = True # delete the config config_tombstone = storage.make_data_tombstone(config_data_id) signed_config_tombstone = storage.sign_data_tombstone( config_data_id, data_privkey) res = data.delete_mutable(config_data_id, [signed_config_tombstone], proxy=proxy, blockchain_id=blockchain_id, is_fq_data_id=True, config_path=config_path) if 'error' in res: log.warning("Failed to delete config file {}".format(config_data_id)) if not ret.has_key('app_config'): ret['app_config'] = app_config ret['retry'] = True ret['status'] = True return ret