nightly.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import os
  2. import sys
  3. from zipfile import ZipFile, ZIP_DEFLATED
  4. from b2sdk.v2 import InMemoryAccountInfo, B2Api
  5. from datetime import datetime, timezone
  6. import json
  7. UPLOAD_FOLDER = "nightly/"
  8. info = InMemoryAccountInfo()
  9. b2_api = B2Api(info)
  10. application_key_id = os.environ['APPID']
  11. application_key = os.environ['APPKEY']
  12. bucket_name = os.environ['BUCKET']
  13. days_to_keep = os.environ['DAYS_TO_KEEP']
  14. def auth() -> bool:
  15. try:
  16. realm = b2_api.account_info.get_realm()
  17. return True # Already authenticated
  18. except:
  19. pass # Not yet authenticated
  20. err = b2_api.authorize_account("production", application_key_id, application_key)
  21. return err is None
  22. def get_bucket():
  23. if not auth(): sys.exit(1)
  24. return b2_api.get_bucket_by_name(bucket_name)
  25. def remove_prefix(text: str, prefix: str) -> str:
  26. return text[text.startswith(prefix) and len(prefix):]
  27. def create_and_upload_artifact_zip(platform: str, artifact: str) -> int:
  28. now = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
  29. source_archive: str
  30. destination_name = f'odin-{platform}-nightly+{now.strftime("%Y-%m-%d")}'
  31. if platform.startswith("linux") or platform.startswith("macos"):
  32. destination_name += ".tar.gz"
  33. source_archive = artifact
  34. else:
  35. destination_name += ".zip"
  36. source_archive = destination_name
  37. print(f"Creating archive {destination_name} from {artifact} and uploading to {bucket_name}")
  38. with ZipFile(source_archive, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z:
  39. for root, directory, filenames in os.walk(artifact):
  40. for file in filenames:
  41. file_path = os.path.join(root, file)
  42. zip_path = os.path.join("dist", os.path.relpath(file_path, artifact))
  43. z.write(file_path, zip_path)
  44. if not os.path.exists(source_archive):
  45. print(f"Error: archive {source_archive} not found.")
  46. return 1
  47. print("Uploading {} to {}".format(source_archive, UPLOAD_FOLDER + destination_name))
  48. bucket = get_bucket()
  49. res = bucket.upload_local_file(
  50. source_archive, # Local file to upload
  51. "nightly/" + destination_name, # B2 destination path
  52. )
  53. return 0
  54. def prune_artifacts():
  55. print(f"Looking for binaries to delete older than {days_to_keep} days")
  56. bucket = get_bucket()
  57. for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=False):
  58. # Timestamp is in milliseconds
  59. date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0, tz=timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
  60. now = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
  61. delta = now - date
  62. if delta.days > int(days_to_keep):
  63. print("Deleting {}".format(file.file_name))
  64. file.delete()
  65. return 0
  66. def update_nightly_json():
  67. print(f"Updating nightly.json with files {days_to_keep} days or newer")
  68. files_by_date = {}
  69. bucket = get_bucket()
  70. for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=True):
  71. # Timestamp is in milliseconds
  72. date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0).strftime('%Y-%m-%d')
  73. name = remove_prefix(file.file_name, UPLOAD_FOLDER)
  74. sha1 = file.content_sha1
  75. size = file.size
  76. url = bucket.get_download_url(file.file_name)
  77. if date not in files_by_date.keys():
  78. files_by_date[date] = []
  79. files_by_date[date].append({
  80. 'name': name,
  81. 'url': url,
  82. 'sha1': sha1,
  83. 'sizeInBytes': size,
  84. })
  85. now = datetime.now(timezone.utc).isoformat()
  86. nightly = json.dumps({
  87. 'last_updated' : now,
  88. 'files': files_by_date
  89. }, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
  90. res = bucket.upload_bytes(
  91. nightly, # JSON bytes
  92. "nightly.json", # B2 destination path
  93. )
  94. return 0
  95. if __name__ == "__main__":
  96. if len(sys.argv) == 1:
  97. print("Usage: {} <verb> [arguments]".format(sys.argv[0]))
  98. print("\tartifact <platform prefix> <artifact path>\n\t\tCreates and uploads a platform artifact zip.")
  99. print("\tprune\n\t\tDeletes old artifacts from bucket")
  100. print("\tjson\n\t\tUpdate and upload nightly.json")
  101. sys.exit(1)
  102. else:
  103. command = sys.argv[1].lower()
  104. if command == "artifact":
  105. if len(sys.argv) != 4:
  106. print("Usage: {} artifact <platform prefix> <artifact path>".format(sys.argv[0]))
  107. print("Error: Expected artifact command to be given platform prefix and artifact path.\n")
  108. sys.exit(1)
  109. res = create_and_upload_artifact_zip(sys.argv[2], sys.argv[3])
  110. sys.exit(res)
  111. elif command == "prune":
  112. res = prune_artifacts()
  113. sys.exit(res)
  114. elif command == "json":
  115. res = update_nightly_json()
  116. sys.exit(res)