pebble/tools/put_bundle_to_s3.py
2025-01-27 11:38:16 -08:00

253 lines
9.6 KiB
Python

#!/usr/bin/env python
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import boto
import boto.s3
import boto.s3.key
import hashlib
import json
import os.path
from sh import git, head
import sys
import zipfile
class PebbleBundle(object):
MANIFEST_FILENAME = 'manifest.json'
def __init__(self, bundle_path):
bundle_abs_path = os.path.abspath(bundle_path)
if not os.path.exists(bundle_abs_path):
raise "Bundle does not exist: " + bundle_path
self.zip = zipfile.ZipFile(bundle_abs_path)
self.path = bundle_abs_path
self.manifest = None
def get_manifest(self):
if (self.manifest):
return self.manifest
if self.MANIFEST_FILENAME not in self.zip.namelist():
raise "Could not find {}; are you sure this is a PebbleBundle?".format(self.MANIFEST_FILENAME)
self.manifest = json.loads(self.zip.read(self.MANIFEST_FILENAME))
return self.manifest
def is_firmware_bundle(self):
return 'firmware' in self.get_manifest()
def get_firmware_info(self):
if not self.is_firmware_bundle():
return None
return self.get_manifest()['firmware']
S3_PUBLIC_URL = 'https://pebblefw.s3.amazonaws.com/'
S3_BUCKET = 'pebblefw'
LATEST_MANIFEST = 'latest.json'
BUNDLE_DIRECTORY = 'pbz'
LAYOUTS_DIRECTORY = 'layouts'
RECOVERY_MANIFESTS = {
"bigboard" : {
"friendlyVersion" : "v1.4",
"timestamp" : 1354827516,
"sha-256" : "61f59c29b7e1c72c2e4de6848affb56ddd1f4c79512bfb9325aa99a650105d19",
"url" : "https://pebblefw.s3.amazonaws.com/pebble/bigboard/release/pbz/recovery_bigboard_v1.4.pbz",
"notes" : "Bigboard Recovery Firmware"
},
"ev2_4" : {
"friendlyVersion" : "v1.5.2",
"timestamp" : 1356919500,
"sha-256" : "1ca285d65d80b48b90bab85c5f9e54c907414adffa6f1168beec8aac5d6f32a2",
"url" : "https://pebblefw.s3.amazonaws.com/pebble/ev2_4/release/pbz/recovery_ev2_4_v1.5.2.pbz",
"notes" : "Official Recovery Firmware"
},
"bb2" : {
"friendlyVersion" : "v1.5.5",
"timestamp" : 1377981535,
"sha-256" : "620cfcadc8d28240048ffa01eb6984b06774a584349f178564e1548ecc813903",
"url" : "https://pebblefw.s3.amazonaws.com/pebble/bb2/release/pbz/recovery_bb2_v1.5.5.pbz",
"notes" : "Official Recovery Firmware"
},
"v1_5" : {
"friendlyVersion" : "v1.5.5",
"timestamp" : 1377981535,
"sha-256" : "d952ffc4b46d1db965ac8ff0cffca645c1f9c9a8f296cf5da452f698b5bc15ce",
"url" : "https://pebblefw.s3.amazonaws.com/pebble/v1_5/release/pbz/recovery_v1_5_v1.5.5.pbz",
"notes" : "Official Recovery Firmware"
},
"v2_0" : {
"friendlyVersion" : "v1.5.5",
"timestamp" : 1377981535,
"sha-256" : "07fd7c6ef5a1fbf51b8122bca3e72772015b5f210e905f30e853c65ae02ee09b",
"url" : "https://pebblefw.s3.amazonaws.com/pebble/v2_0/release/pbz/recovery_v2_0_v1.5.5.pbz",
"notes" : "Official Recovery Firmware"
},
"snowy_evt2" : {
"friendlyVersion" : "v3.0.2-prf",
"timestamp" : 1430231686,
"sha-256" : "5579978e73db5d57831d4e1b8e3416b66fe94f5f19d3eb3ac49fcd54270935f3",
"url" : "http://pebblefw.s3.amazonaws.com/pebble/snowy_evt2/release-v3/pbz/recovery_snowy_evt2_v3.0.2-prf.pbz",
"notes" : "Official Recovery Firmware"
},
"snowy_dvt" : {
"friendlyVersion" : "v3.0.2-prf",
"timestamp" : 1430231686,
"sha-256" : "3338d94af5b97e94fe0e0dee5eddae7ea9c8f299b64070d1d1a692948c651a02",
"url" : "http://pebblefw.s3.amazonaws.com/pebble/snowy_dvt/release-v3/pbz/recovery_snowy_dvt_v3.0.2-prf.pbz",
"notes" : "Official Recovery Firmware"
},
"snowy_s3" : {
"friendlyVersion" : "v3.0.3-prf",
"timestamp" : 1435167301,
"sha-256" : "0e5bd3d327c5180506b20ef33aed1601037df0030683b27d68c16e4836c4def0",
"url" : "http://pebblefw.s3.amazonaws.com/pebble/snowy_s3/release-v3/pbz/recovery_snowy_s3_v3.0.3-prf.pbz",
"notes" : "Official Recovery Firmware"
},
"spalding" : {
"friendlyVersion" : "v3.2-prf5",
"timestamp" : 1440968436,
"sha-256" : "f6d3616b423d32618a1a3615ae8f8d5526941c7b6311be45e4387045c7eb7602",
"url" : "https://pebblefw.s3.amazonaws.com/pebble/spalding/release-v3/pbz/recovery_s4_pvt_prf5.pbz",
"notes" : "Official Recovery Firmware"
},
"spalding_evt" : {
"friendlyVersion" : "v3.2-prf5",
"sha-256": "f9c2bb857952c034c7122b67f5178a25fcdf9ef8cce3d3bd8c2c8671676958c2",
"url" : "https://pebblefw.s3.amazonaws.com/pebble/spalding/release-v3/pbz/recovery_s4_evt_prf5.pbz",
"timestamp": 1440968436,
"notes" : "Official Recovery Firmware"
}
}
def build_s3_path(*args):
return 'pebble/' + '/'.join(args)
def __git_tag():
return str(git.describe()).strip()
def generate_update_manifest(bundle, bundle_s3_key, layouts_s3_key, notes):
bundle_path = os.path.abspath(bundle.path)
board = bundle.get_firmware_info()['hwrev']
timestamp = bundle.get_firmware_info()['timestamp']
with open(bundle_path, 'rb') as normal_bundle:
bundle_data = normal_bundle.read()
sha256 = hashlib.sha256(bundle_data).hexdigest()
bundle_url = S3_PUBLIC_URL + bundle_s3_key
layouts_url = S3_PUBLIC_URL + layouts_s3_key
manifest = {
'normal' : {
'friendlyVersion' : __git_tag(),
'timestamp' : timestamp,
'sha-256' : sha256,
'url' : bundle_url,
'layouts' : layouts_url,
'notes' : notes
},
'recovery' : RECOVERY_MANIFESTS[board]
}
return json.dumps(manifest)
def push_to_s3(bundle_path, layouts_path, stage, notes, update_latest=False, dry_run=False):
conn = boto.connect_s3()
bundle = PebbleBundle(bundle_path)
bundle_path = bundle.path
bundle_filename = os.path.basename(bundle_path)
git_tag = str(git.describe()).strip()
board = bundle.get_firmware_info()['hwrev']
bundle_key = build_s3_path(board, stage, BUNDLE_DIRECTORY, bundle_filename)
layouts_key = build_s3_path(board, stage, LAYOUTS_DIRECTORY, __git_tag() + '_layouts.json')
manifest_key = build_s3_path(board, stage, __git_tag() + '.json')
with open(notes, 'r') as f:
notes_txt = f.read().strip()
manifest_contents = generate_update_manifest(bundle, bundle_key, layouts_key, notes_txt)
if dry_run:
print manifest_contents
return
fw_bucket = boto.s3.bucket.Bucket(conn, S3_BUCKET)
# Upload the bundle to S3
bundle_obj = boto.s3.key.Key(fw_bucket, bundle_key)
bundle_obj.set_contents_from_filename(bundle_path)
bundle_obj.set_acl('public-read')
print 'PUT ' + bundle_key
bundle_obj = boto.s3.key.Key(fw_bucket, layouts_key)
bundle_obj.set_contents_from_filename(layouts_path)
bundle_obj.set_acl('public-read')
print 'PUT ' + layouts_key
manifest_obj = boto.s3.key.Key(fw_bucket, manifest_key)
manifest_obj.set_contents_from_string(manifest_contents)
manifest_obj.set_acl('public-read')
print 'PUT ' + manifest_key
if update_latest:
latest_key = build_s3_path(board, stage, LATEST_MANIFEST)
latest_obj = boto.s3.key.Key(fw_bucket, latest_key)
# prevent caching of the latest file
headers = {'Cache-Control': 'max-age=0'}
latest_obj.set_contents_from_string(manifest_contents, headers=headers)
latest_obj.set_acl('public-read')
print 'PUT ' + latest_key
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--notes', metavar='NOTES', required=True,
help='path to very brief release notes')
parser.add_argument('--bundle', metavar='BUNDLE_PATH', required=True,
help='path to the bundle')
parser.add_argument('--layouts', metavar='LAYOUTS_PATH', required=True,
help='path to the layouts json')
stage_choices = [
'release', 'test', 'release-v2',
'release-v3', 'beta', 'nightly',
'porksmoothie', 'garlicsoda', 'anchovietea'
]
parser.add_argument('--stage', metavar='STAGE', choices=stage_choices,
required=True, help='the target release stage')
parser.add_argument("--latest", action="store_true",
help="update the release stage's 'latest.json' manifest")
parser.add_argument("-d", "--dry_run", action="store_true", help="don't modify s3")
args = parser.parse_args()
print args
print 'HI'
push_to_s3(bundle_path=args.bundle,
layouts_path=args.layouts,
stage=args.stage,
notes=args.notes,
update_latest=args.latest,
dry_run=args.dry_run)
print 'BYE'
if (__name__ == '__main__'):
main()