def main(options): try: # import mbed_cloud.update from mbed_cloud.update import UpdateAPI # from mbed_cloud.device_directory import DeviceDirectoryAPI import mbed_cloud.exceptions except: LOG.critical('manifest-tool update commands require installation of the Mbed Cloud SDK:' ' https://github.com/ARMmbed/mbed-cloud-sdk-python') return 1 LOG.debug('Preparing an update on Mbed Cloud') # upload a firmware api = None # dd_api = None try: # If set use api key set in manifest-tool update. if hasattr(options, 'api_key') and options.api_key: tempKey = options.api_key config = {'api_key': tempKey} api = UpdateAPI(config) # Otherwise use API key set in manifest-tool init else: api = UpdateAPI() # dd_api = DeviceDirectoryAPI() except ValueError: LOG.critical('API key is required to connect to the Update Service. It can be added using manifest-tool init -a' ' <api key> or by manually editing .mbed_cloud_config.json') return 1 if not options.payload_name: name = os.path.basename(options.payload.name) + time.strftime('-%Y-%m-%dT%H:%M:%S') LOG.info('Using {} as payload name.'.format(name)) options.payload_name = name if len(options.payload_name) > MAX_NAME_LEN: LOG.critical( 'Payload name is too long. Maximum length is {size}. ("{base}" <- {size}. overflow -> "{overflow}")'.format( size=MAX_NAME_LEN, base=options.payload_name[:MAX_NAME_LEN], overflow=options.payload_name[MAX_NAME_LEN:])) return 1 if not options.manifest_name: name = os.path.basename(options.payload.name) + time.strftime('-%Y-%m-%dT%H:%M:%S-manifest') LOG.info('Using {} as manifest name.'.format(name)) options.manifest_name = name if len(options.manifest_name) > MAX_NAME_LEN: LOG.critical( 'Manifest name is too long. Maximum length is {size}. ("{base}" <- {size}. overflow -> "{overflow}")'.format( size=MAX_NAME_LEN, base=options.manifest_name[:MAX_NAME_LEN], overflow=options.manifest_name[MAX_NAME_LEN:])) return 1 campaign_name = options.payload.name + time.strftime('-%Y-%m-%dT%H:%M:%S-campaign') if len(campaign_name) > MAX_NAME_LEN: LOG.critical( 'Campaign name is too long. Maximum length is {size}. ("{base}" <- {size}. overflow -> "{overflow}")'.format( size=MAX_NAME_LEN, base=campaign_name[:MAX_NAME_LEN], overflow=campaign_name[MAX_NAME_LEN:])) return 1 query_name = options.payload.name + time.strftime('-%Y-%m-%dT%H:%M:%S-filter') if len(query_name) > MAX_NAME_LEN: LOG.critical( 'Filter name is too long. Maximum length is {size}. ("{base}" <- {size}. overflow -> "{overflow}")'.format( size=MAX_NAME_LEN, base=query_name[:MAX_NAME_LEN], overflow=query_name[MAX_NAME_LEN:])) return 1 payload = None manifest = None campaign = None RC = 0 handled = False manifest_file = None tempdirname = tempfile.mkdtemp() try: kwArgs = {} if options.payload_description: kwArgs['description'] = options.payload_description try: payload = api.add_firmware_image( name = options.payload_name, datafile = options.payload.name, **kwArgs) except mbed_cloud.exceptions.CloudApiException as e: # TODO: Produce a better failuer message LOG.critical('Upload of payload failed with:\n{}'.format(e).rstrip()) handled = True LOG.critical('Check API server URL "{}"'.format(api.config["host"])) raise e except MaxRetryError as e: LOG.critical('Upload of payload failed with:\n{}'.format(e)) handled=True LOG.critical('Failed to establish connection to API-GW') LOG.critical('Check API server URL "{}"'.format(api.config["host"])) raise e LOG.info("Created new firmware at {}".format(payload.url)) options.payload.seek(0) # create a manifest create_opts = copy.copy(options) create_opts.uri = payload.url create_opts.payload = options.payload if not (hasattr(create_opts, "output_file") and create_opts.output_file): try: manifest_file = open(os.path.join(tempdirname,'manifest'),'wb') LOG.info("Created temporary manifest file at {}".format(manifest_file.name)) create_opts.output_file = manifest_file except IOError as e: LOG.critical("Failed to create temporary manifest file with:") print(e) LOG.critical("Try using '-o' to output a manifest file at a writable location.") handled = True raise e try: rc = create.main(create_opts) except IOError as e: LOG.critical("Failed to create manifest with:") print(e) handled = True raise e if rc: return rc kwArgs = {} if options.manifest_description: kwArgs['description'] = options.manifest_description manifest_path = create_opts.output_file.name create_opts.output_file.close() # upload a manifest try: manifest = api.add_firmware_manifest( name = options.manifest_name, datafile = manifest_path, **kwArgs) except mbed_cloud.exceptions.CloudApiException as e: # TODO: Produce a better failure message LOG.critical('Upload of manifest failed with:') print(e) LOG.critical("Try using '-o' to output a manifest file at a writable location.") handled = True raise e LOG.info('Created new manifest at {}'.format(manifest.url)) LOG.info('Manifest ID: {}'.format(manifest.id)) try: campaign = api.add_campaign( name = campaign_name, manifest_id = manifest.id, device_filter = {'id': { '$eq': options.device_id }}, ) except mbed_cloud.exceptions.CloudApiException as e: LOG.critical('Campaign creation failed with:') print(e) handled = True raise e LOG.info('Campaign successfully created. Current state: %r' % (campaign.state)) LOG.info('Campaign successfully created. Filter result: %r' % (campaign.device_filter)) LOG.info("Starting the update campign...") # By default a new campaign is created with the 'draft' status. We can manually start it. try: new_campaign = api.start_campaign(campaign) new_campaign = None except mbed_cloud.exceptions.CloudApiException as e: LOG.critical('Starting campaign failed with:') print(e) handled = True raise e LOG.info("Campaign successfully started. Current state: %r. Checking updates.." % (campaign.state)) oldstate = api.get_campaign(campaign.id).state LOG.info("Current state: %r" % (oldstate)) timeout = options.timeout while timeout != 0: c = api.get_campaign(campaign.id) if oldstate != c.state: LOG.info("Current state: %r" % (c.state)) oldstate = c.state if c.state in STOP_STATES: LOG.info("Finished in state: %r" % (c.state)) break time.sleep(1) if timeout > 0: timeout -= 1 if timeout == 0: LOG.critical("Campaign timed out") RC = 1 except KeyboardInterrupt as e: LOG.critical('User Aborted... Cleaning up.') RC = 1 except: if not handled: LOG.critical('Unhandled Exception:') import traceback traceback.print_exc(file=sys.stdout) RC = 1 finally: # cleanup if manifest_file: manifest_file.close() shutil.rmtree(tempdirname) if not options.no_cleanup: LOG.info("** Deleting update campaign and manifest **") try: if campaign and campaign.id: api.delete_campaign(campaign.id) if manifest and manifest.id: api.delete_firmware_manifest(manifest.id) if payload and payload.id: api.delete_firmware_image(payload.id) # dd_api.delete_query(new_query.id) except mbed_cloud.exceptions.CloudApiException as e: LOG.critical('Cleanup of campaign failed with:') print(e) RC = 1 return RC
def update( payload_path: Path, dev_cfg: dict, manifest_version: Type[ManifestAsnCodecBase], priority: int, vendor_data: Path, device_id: str, do_wait: bool, do_start: bool, timeout: int, skip_cleanup: bool, service_config: Path, fw_version: str, sign_image: bool, component: str ): config = None if service_config.is_file(): with service_config.open('rt') as fh: config = yaml.safe_load(fh) api = UpdateAPI(config) manifest_path = None payload_cloud = None manifest_cloud = None campaign_cloud = None try: timestamp = time.strftime('%Y_%m_%d-%H_%M_%S') payload_cloud = _upload_payload( api, payload_name='{timestamp}-{filename}'.format( filename=payload_path.name, timestamp=timestamp), payload_path=payload_path ) manifest_data = create_dev_manifest( dev_cfg=dev_cfg, manifest_version=manifest_version, vendor_data_path=vendor_data, payload_path=payload_path, payload_url=payload_cloud.url, priority=priority, fw_version=fw_version, sign_image=sign_image, component=component ) manifest_name = 'manifest-{timestamp}-{filename}'.format( filename=payload_path.name, timestamp=timestamp) manifest_path = payload_path.parent / manifest_name manifest_path.write_bytes(manifest_data) manifest_cloud = _upload_manifest(api, manifest_name, manifest_path) campaign_name = 'campaign-{timestamp}-{filename}'.format( filename=payload_path.name, timestamp=timestamp) campaign_cloud = _create_campaign( api, campaign_name, manifest_cloud, dev_cfg['vendor-id'], dev_cfg['class-id'], device_id ) if do_start: _start_campaign(api, campaign_cloud) if do_wait: _wait(api, campaign_cloud, timeout) finally: if not skip_cleanup and do_wait: try: logger.info('Cleaning up resources.') if campaign_cloud: logger.info('Deleting campaign %s', campaign_cloud.id) api.delete_campaign(campaign_cloud.id) if manifest_cloud: logger.info('Deleting FW manifest %s', manifest_cloud.id) api.delete_firmware_manifest(manifest_cloud.id) if payload_cloud: logger.info('Deleting FW image %s', payload_cloud.id) api.delete_firmware_image(payload_cloud.id) if manifest_path and manifest_path.is_file(): manifest_path.unlink() except CloudApiException: logger.error('Failed to cleanup resources')