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

150 lines
4.9 KiB
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 hashlib
import json
import os
import requests
import libpebble2.util.bundle
S3_BUCKETS = ['pebblefw', 'pebblefw-staging']
STAGES = ['release', 'release-v2', 'release-v3', 'release-v3.7', 'release-v3.8',
'test', 'beta', 'nightly', 'porksmoothie', 'codshake', 'anchovietea']
def _sha256_file(path):
with open(path, 'rb') as f:
data = f.read()
return hashlib.sha256(data).hexdigest()
def _build_s3_path(*args):
return 'pebble/' + '/'.join(args)
def _build_s3_url(bucket, path):
return 'https://{}.s3.amazonaws.com/{}'.format(bucket, path)
def _upload_to_s3(boto_bucket, key, filename=None, content=None, headers=None):
""" Need to set either filename or content """
boto_key = boto.s3.key.Key(boto_bucket, key)
if filename is not None:
print 'PUT %s filename=%s' % (key, filename)
boto_key.set_contents_from_filename(filename, headers=headers)
elif content is not None:
print 'PUT %s string=<%s>' % (key, content)
boto_key.set_contents_from_string(content, headers=headers)
else:
raise Exception('Need to specify either filename or content')
boto_key.set_acl('public-read')
def deploy_bundle(bundle_path, bucket, stage, notes_path, layouts_path=None, dry_run=False):
# Parse the bundle to deploy
bundle = libpebble2.util.bundle.PebbleBundle(bundle_path)
bundle_manifest = bundle.get_manifest()
friendly_version = bundle_manifest['firmware']['versionTag']
# Generate a deployment manifest
deploy_manifest = {
'friendlyVersion': friendly_version,
'timestamp': bundle_manifest['firmware']['timestamp'],
'sha-256': _sha256_file(bundle_path),
}
bundle_filename = os.path.basename(bundle_path)
board = bundle_manifest['firmware']['hwrev']
bundle_key = _build_s3_path(board, stage, 'pbz', bundle_filename)
deploy_manifest['url'] = _build_s3_url(bucket, bundle_key)
if layouts_path is not None:
layouts_key = _build_s3_path(board, stage, 'layouts', friendly_version + '_layouts.json')
deploy_manifest['layouts'] = _build_s3_url(bucket, layouts_key)
with open(notes_path, 'r') as f:
deploy_manifest['notes'] = f.read().strip()
# Fetch the current lastest.json
latest_key = _build_s3_path(board, stage, 'latest.json')
r = requests.get(_build_s3_url(bucket, latest_key))
if r.status_code == 403:
# Starting from scratch
latest_json = {}
else:
latest_json = r.json()
# Update the latest.json with the new deployment
firmware_type = bundle_manifest['firmware']['type']
latest_json[firmware_type] = deploy_manifest
if dry_run:
# We're done!
print "Deploying {} to bucket: {} board: {} stage: {} version: {}".format(
bundle_path, bucket, board, stage, friendly_version)
print '== Before =='
if r.status_code == 403:
print "No latest.json exists"
else:
print json.dumps(r.json(), indent=2)
print '== After =='
print json.dumps(latest_json, indent=2)
return
# Upload the stuff!
boto_conn = boto.connect_s3()
boto_bucket = boto.s3.bucket.Bucket(boto_conn, bucket)
_upload_to_s3(boto_bucket, bundle_key, filename=bundle_path)
if layouts_path is not None:
_upload_to_s3(boto_bucket, layouts_key, filename=layouts_path)
# prevent caching of the latest.json file
headers = {'Cache-Control': 'max-age=0'}
_upload_to_s3(boto_bucket, latest_key, content=json.dumps(latest_json), headers=headers)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('bundle')
parser.add_argument('--bucket', choices=S3_BUCKETS, required=True)
parser.add_argument('--stage', choices=STAGES, required=True)
parser.add_argument('--notes', required=True, help='path to very brief release notes')
parser.add_argument('--layouts', default=None, required=False, help='path to the layouts. json')
parser.add_argument('--dry-run', action='store_true')
parser.add_argument('--debug', action='store_true', help='print some very verbose logs')
args = parser.parse_args()
if args.debug:
import logging
logging.basicConfig(level=logging.DEBUG)
deploy_bundle(args.bundle, args.bucket, args.stage, args.notes, args.layouts, args.dry_run)
if (__name__ == '__main__'):
main()