1*4882a593Smuzhiyun""" 2*4882a593SmuzhiyunBitBake 'Fetch' implementation for Amazon AWS S3. 3*4882a593Smuzhiyun 4*4882a593SmuzhiyunClass for fetching files from Amazon S3 using the AWS Command Line Interface. 5*4882a593SmuzhiyunThe aws tool must be correctly installed and configured prior to use. 6*4882a593Smuzhiyun 7*4882a593Smuzhiyun""" 8*4882a593Smuzhiyun 9*4882a593Smuzhiyun# Copyright (C) 2017, Andre McCurdy <armccurdy@gmail.com> 10*4882a593Smuzhiyun# 11*4882a593Smuzhiyun# Based in part on bb.fetch2.wget: 12*4882a593Smuzhiyun# Copyright (C) 2003, 2004 Chris Larson 13*4882a593Smuzhiyun# 14*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 15*4882a593Smuzhiyun# 16*4882a593Smuzhiyun# Based on functions from the base bb module, Copyright 2003 Holger Schurig 17*4882a593Smuzhiyun 18*4882a593Smuzhiyunimport os 19*4882a593Smuzhiyunimport bb 20*4882a593Smuzhiyunimport urllib.request, urllib.parse, urllib.error 21*4882a593Smuzhiyunimport re 22*4882a593Smuzhiyunfrom bb.fetch2 import FetchMethod 23*4882a593Smuzhiyunfrom bb.fetch2 import FetchError 24*4882a593Smuzhiyunfrom bb.fetch2 import runfetchcmd 25*4882a593Smuzhiyun 26*4882a593Smuzhiyundef convertToBytes(value, unit): 27*4882a593Smuzhiyun value = float(value) 28*4882a593Smuzhiyun if (unit == "KiB"): 29*4882a593Smuzhiyun value = value*1024.0; 30*4882a593Smuzhiyun elif (unit == "MiB"): 31*4882a593Smuzhiyun value = value*1024.0*1024.0; 32*4882a593Smuzhiyun elif (unit == "GiB"): 33*4882a593Smuzhiyun value = value*1024.0*1024.0*1024.0; 34*4882a593Smuzhiyun return value 35*4882a593Smuzhiyun 36*4882a593Smuzhiyunclass S3ProgressHandler(bb.progress.LineFilterProgressHandler): 37*4882a593Smuzhiyun """ 38*4882a593Smuzhiyun Extract progress information from s3 cp output, e.g.: 39*4882a593Smuzhiyun Completed 5.1 KiB/8.8 GiB (12.0 MiB/s) with 1 file(s) remaining 40*4882a593Smuzhiyun """ 41*4882a593Smuzhiyun def __init__(self, d): 42*4882a593Smuzhiyun super(S3ProgressHandler, self).__init__(d) 43*4882a593Smuzhiyun # Send an initial progress event so the bar gets shown 44*4882a593Smuzhiyun self._fire_progress(0) 45*4882a593Smuzhiyun 46*4882a593Smuzhiyun def writeline(self, line): 47*4882a593Smuzhiyun percs = re.findall(r'^Completed (\d+.{0,1}\d*) (\w+)\/(\d+.{0,1}\d*) (\w+) (\(.+\)) with\s+', line) 48*4882a593Smuzhiyun if percs: 49*4882a593Smuzhiyun completed = (percs[-1][0]) 50*4882a593Smuzhiyun completedUnit = (percs[-1][1]) 51*4882a593Smuzhiyun total = (percs[-1][2]) 52*4882a593Smuzhiyun totalUnit = (percs[-1][3]) 53*4882a593Smuzhiyun completed = convertToBytes(completed, completedUnit) 54*4882a593Smuzhiyun total = convertToBytes(total, totalUnit) 55*4882a593Smuzhiyun progress = (completed/total)*100.0 56*4882a593Smuzhiyun rate = percs[-1][4] 57*4882a593Smuzhiyun self.update(progress, rate) 58*4882a593Smuzhiyun return False 59*4882a593Smuzhiyun return True 60*4882a593Smuzhiyun 61*4882a593Smuzhiyun 62*4882a593Smuzhiyunclass S3(FetchMethod): 63*4882a593Smuzhiyun """Class to fetch urls via 'aws s3'""" 64*4882a593Smuzhiyun 65*4882a593Smuzhiyun def supports(self, ud, d): 66*4882a593Smuzhiyun """ 67*4882a593Smuzhiyun Check to see if a given url can be fetched with s3. 68*4882a593Smuzhiyun """ 69*4882a593Smuzhiyun return ud.type in ['s3'] 70*4882a593Smuzhiyun 71*4882a593Smuzhiyun def recommends_checksum(self, urldata): 72*4882a593Smuzhiyun return True 73*4882a593Smuzhiyun 74*4882a593Smuzhiyun def urldata_init(self, ud, d): 75*4882a593Smuzhiyun if 'downloadfilename' in ud.parm: 76*4882a593Smuzhiyun ud.basename = ud.parm['downloadfilename'] 77*4882a593Smuzhiyun else: 78*4882a593Smuzhiyun ud.basename = os.path.basename(ud.path) 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun ud.localfile = d.expand(urllib.parse.unquote(ud.basename)) 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun ud.basecmd = d.getVar("FETCHCMD_s3") or "/usr/bin/env aws s3" 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun def download(self, ud, d): 85*4882a593Smuzhiyun """ 86*4882a593Smuzhiyun Fetch urls 87*4882a593Smuzhiyun Assumes localpath was called first 88*4882a593Smuzhiyun """ 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun cmd = '%s cp s3://%s%s %s' % (ud.basecmd, ud.host, ud.path, ud.localpath) 91*4882a593Smuzhiyun bb.fetch2.check_network_access(d, cmd, ud.url) 92*4882a593Smuzhiyun 93*4882a593Smuzhiyun progresshandler = S3ProgressHandler(d) 94*4882a593Smuzhiyun runfetchcmd(cmd, d, False, log=progresshandler) 95*4882a593Smuzhiyun 96*4882a593Smuzhiyun # Additional sanity checks copied from the wget class (although there 97*4882a593Smuzhiyun # are no known issues which mean these are required, treat the aws cli 98*4882a593Smuzhiyun # tool with a little healthy suspicion). 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun if not os.path.exists(ud.localpath): 101*4882a593Smuzhiyun raise FetchError("The aws cp command returned success for s3://%s%s but %s doesn't exist?!" % (ud.host, ud.path, ud.localpath)) 102*4882a593Smuzhiyun 103*4882a593Smuzhiyun if os.path.getsize(ud.localpath) == 0: 104*4882a593Smuzhiyun os.remove(ud.localpath) 105*4882a593Smuzhiyun raise FetchError("The aws cp command for s3://%s%s resulted in a zero size file?! Deleting and failing since this isn't right." % (ud.host, ud.path)) 106*4882a593Smuzhiyun 107*4882a593Smuzhiyun return True 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun def checkstatus(self, fetch, ud, d): 110*4882a593Smuzhiyun """ 111*4882a593Smuzhiyun Check the status of a URL 112*4882a593Smuzhiyun """ 113*4882a593Smuzhiyun 114*4882a593Smuzhiyun cmd = '%s ls s3://%s%s' % (ud.basecmd, ud.host, ud.path) 115*4882a593Smuzhiyun bb.fetch2.check_network_access(d, cmd, ud.url) 116*4882a593Smuzhiyun output = runfetchcmd(cmd, d) 117*4882a593Smuzhiyun 118*4882a593Smuzhiyun # "aws s3 ls s3://mybucket/foo" will exit with success even if the file 119*4882a593Smuzhiyun # is not found, so check output of the command to confirm success. 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun if not output: 122*4882a593Smuzhiyun raise FetchError("The aws ls command for s3://%s%s gave empty output" % (ud.host, ud.path)) 123*4882a593Smuzhiyun 124*4882a593Smuzhiyun return True 125