From 2d8baf6fce8416e3dd45bd2915b6b6225892aa81 Mon Sep 17 00:00:00 2001 From: MacDev <> Date: Mon, 21 Sep 2026 20:05:29 +0800 Subject: [PATCH] =?UTF-8?q?refactor(SwitchBundle):=20=E9=87=8D=E6=9E=84h5?= =?UTF-8?q?=E5=AD=90=E6=B8=B8=E6=88=8F=E6=8B=86=E5=88=86=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python.zip | Bin 0 -> 6778 bytes python/main.py | 7 + python/switchBundle.py | 110 ---------------- python/switchBundleR.py | 109 ---------------- python/switch_bundle_common.py | 196 ++++++++++++++++++++++++++++ python/test_switch_bundle_common.py | 104 +++++++++++++++ python/test_vault_options.py | 66 ++++++++++ python/vault_options.py | 29 ++++ 8 files changed, 402 insertions(+), 219 deletions(-) create mode 100644 python.zip create mode 100644 python/main.py delete mode 100644 python/switchBundle.py delete mode 100644 python/switchBundleR.py create mode 100644 python/switch_bundle_common.py create mode 100644 python/test_switch_bundle_common.py create mode 100644 python/test_vault_options.py create mode 100644 python/vault_options.py diff --git a/python.zip b/python.zip new file mode 100644 index 0000000000000000000000000000000000000000..392ec995ba9e37a4e5aae7316bb9b223cefb5fe2 GIT binary patch literal 6778 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 C5u5w~ literal 0 HcmV?d00001 diff --git a/python/main.py b/python/main.py new file mode 100644 index 0000000..be5ab9d --- /dev/null +++ b/python/main.py @@ -0,0 +1,7 @@ +import sys + +from switch_bundle_common import main + + +if __name__ == '__main__': + main(sys.argv) diff --git a/python/switchBundle.py b/python/switchBundle.py deleted file mode 100644 index 0a09029..0000000 --- a/python/switchBundle.py +++ /dev/null @@ -1,110 +0,0 @@ -import os -import sys -import subprocess -import hvac - -if len(sys.argv) < 3: - exit() -if '123!@#' != sys.argv[2]: - exit() - -ProjDir = sys.argv[1] -BuildDir = ProjDir + '\\build\\web-mobile\\' -SubgamePath = 's3://web-h5game-test/internal/' - -# Vault 地址和 Token -# VAULT_ADDR = 'https://keys.wehavefun.email' -VAULT_ADDR = 'https://keys.i-see--you.icu' -VAULT_TOKEN = 'hvs.CAESINwE2A36YcHcC_CWSzaBtUrGq-1OcUgKjDegSMUAuB5-Gh4KHGh2cy53ZmN5UzNMb0pvcUJ6QlJXdURmZnh1MlU' - -# 创建客户端 -client = hvac.Client(url=VAULT_ADDR, token=VAULT_TOKEN) - -if client.is_authenticated(): - secret = client.secrets.kv.v2.read_secret_version( - path='default', - mount_point='aws', - raise_on_deleted_version=True - ) - SwitchBundle = secret['data']['data']['switchBundle'] - if "1" != SwitchBundle: - print("script exit, switchBundle status is " + SwitchBundle) - exit() -else: - print("running script failed!") - exit() - -print("start running script...") - - -BundleConfig = { - '6001': 'whot', - '6002': 'whot', - '6003': 'whot', - '6005': 'BRWhot', - '2001': 'roulette', - '2002': 'wajeSpin', - '2003': 'bottleSpin', - '2004': 'blackjack', - '2006': 'betterjack', - '6007': 'BRWhot', - '2008': 'slots', - '2005': 'christmas', -} - -unlinkArray = [ - 'cocos2d-js.109f3.js', - 'cocos2d-js-min.13181.js', - 'physics.9d2de.js', - 'physics-min.ce5ee.js', - 'style-desktop.dfd76.css', - 'style-mobile.6e9cd.css', - 'splash.85cfd.png' -] - -def remove_folder(path): - print('remove path: ', path) - if os.path.exists(path): - if os.path.isfile(path) or os.path.islink(path): - os.remove(path) - else: - for filename in os.listdir(path): - remove_folder(os.path.join(path, filename)) - os.rmdir(path) - -# 上传src相共用文件 -SrcDir = BuildDir + '\\src\\' -SrcDst = SubgamePath + 'lib/src/' -rmResult = subprocess.run(['cmd', '/c', 'aws s3 rm ' + SrcDst + ' --recursive'], capture_output=True, text=True) -print('remove dir src result: ', rmResult) -cmd = 'call aws s3 cp '+ SrcDir +' '+SrcDst + ' --recursive --region ap-east-1' -result = subprocess.run(['cmd', '/c', cmd], capture_output=True, text=True) -print('upload dir src result: ', result) -remove_folder(SrcDir) - -for filename in os.listdir(BuildDir): - file_path = os.path.join(BuildDir, filename) - if filename in unlinkArray: - os.remove(file_path) - -for gameid in BundleConfig: - awsPath = SubgamePath + gameid + '/assets/' + BundleConfig[gameid] +'/' - - rmResult = subprocess.run(['cmd', '/c', 'aws s3 rm ' + awsPath + ' --recursive'], capture_output=True, text=True) - print('remove game gameid: ', gameid, ' result: ', rmResult) - - subgameDir = BuildDir + 'assets\\' + BundleConfig[gameid] +'\\' - cmd = 'call aws s3 cp '+ subgameDir +' '+awsPath + ' --recursive --region ap-east-1' - result = subprocess.run(['cmd', '/c', cmd], capture_output=True, text=True) - print(result) - -for gameid in BundleConfig: - awsPath = SubgamePath + gameid + '/assets/' + BundleConfig[gameid] +'/' - subgameDir = BuildDir + 'assets\\' + BundleConfig[gameid] - remove_folder(subgameDir) - -for gameid in BundleConfig: - awsPath = SubgamePath + gameid + '/' - cmd = 'call aws s3 cp '+ BuildDir +' '+awsPath + ' --recursive --region ap-east-1' - result = subprocess.run(['cmd', '/c', cmd], capture_output=True, text=True) - print('upload game gameid: ', gameid, ' result: ', result) \ No newline at end of file diff --git a/python/switchBundleR.py b/python/switchBundleR.py deleted file mode 100644 index c517053..0000000 --- a/python/switchBundleR.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -import sys -import subprocess -import hvac - -if len(sys.argv) < 3: - exit() -if '123!@#' != sys.argv[2]: - exit() - -ProjDir = sys.argv[1] -BuildDir = ProjDir + '\\build\\web-mobile\\' -SubgamePath = 's3://web-h5game-prod/internal/' - -# Vault 地址和 Token -VAULT_ADDR = 'https://keys.i-see--you.icu' -VAULT_TOKEN = 'hvs.CAESINwE2A36YcHcC_CWSzaBtUrGq-1OcUgKjDegSMUAuB5-Gh4KHGh2cy53ZmN5UzNMb0pvcUJ6QlJXdURmZnh1MlU' - -# 创建客户端 -client = hvac.Client(url=VAULT_ADDR, token=VAULT_TOKEN) - -if client.is_authenticated(): - secret = client.secrets.kv.v2.read_secret_version( - path='default', - mount_point='aws', - raise_on_deleted_version=True - ) - SwitchBundle = secret['data']['data']['switchBundle'] - if "1" != SwitchBundle: - print("script exit, switchBundle status is " + SwitchBundle) - exit() -else: - print("running script failed!") - exit() - -print("start running script...") - - - -BundleConfig = { - '6001': 'whot', - '6002': 'whot', - '6003': 'whot', - '6005': 'BRWhot', - '2001': 'roulette', - '2002': 'wajeSpin', - '2003': 'bottleSpin', - '2004': 'blackjack', - '2006': 'betterjack', - '6007': 'BRWhot', - '2008': 'slots', - '2005': 'christmas', -} - -unlinkArray = [ - 'cocos2d-js.109f3.js', - 'cocos2d-js-min.13181.js', - 'physics.9d2de.js', - 'physics-min.ce5ee.js', - 'style-desktop.dfd76.css', - 'style-mobile.6e9cd.css', - 'splash.85cfd.png' -] - -def remove_folder(path): - print('remove path: ', path) - if os.path.exists(path): - if os.path.isfile(path) or os.path.islink(path): - os.remove(path) - else: - for filename in os.listdir(path): - remove_folder(os.path.join(path, filename)) - os.rmdir(path) - -SrcDir = BuildDir + '\\src\\' -SrcDst = SubgamePath + 'lib/src/' -rmResult = subprocess.run(['cmd', '/c', 'aws s3 rm ' + SrcDst + ' --recursive'], capture_output=True, text=True) -print('remove dir src result: ', rmResult) -cmd = 'call aws s3 cp '+ SrcDir +' '+SrcDst + ' --recursive --region eu-west-3' -result = subprocess.run(['cmd', '/c', cmd], capture_output=True, text=True) -print('upload dir src result: ', result) -remove_folder(SrcDir) - -for filename in os.listdir(BuildDir): - file_path = os.path.join(BuildDir, filename) - if filename in unlinkArray: - os.remove(file_path) - -for gameid in BundleConfig: - awsPath = SubgamePath + gameid + '/assets/' + BundleConfig[gameid] +'/' - - rmResult = subprocess.run(['cmd', '/c', 'aws s3 rm ' + awsPath + ' --recursive'], capture_output=True, text=True) - print('remove game gameid: ', gameid, ' result: ', rmResult) - - subgameDir = BuildDir + 'assets\\' + BundleConfig[gameid] +'\\' - cmd = 'call aws s3 cp '+ subgameDir +' '+awsPath + ' --recursive --region eu-west-3' - result = subprocess.run(['cmd', '/c', cmd], capture_output=True, text=True) - print(result) - -for gameid in BundleConfig: - awsPath = SubgamePath + gameid + '/assets/' + BundleConfig[gameid] +'/' - subgameDir = BuildDir + 'assets\\' + BundleConfig[gameid] - remove_folder(subgameDir) - -for gameid in BundleConfig: - awsPath = SubgamePath + gameid + '/' - cmd = 'call aws s3 cp '+ BuildDir +' '+awsPath + ' --recursive --region eu-west-3' - result = subprocess.run(['cmd', '/c', cmd], capture_output=True, text=True) - print('upload game gameid: ', gameid, ' result: ', result) \ No newline at end of file diff --git a/python/switch_bundle_common.py b/python/switch_bundle_common.py new file mode 100644 index 0000000..695c77c --- /dev/null +++ b/python/switch_bundle_common.py @@ -0,0 +1,196 @@ +"""Shared Vault authorization and bundle publishing for both environments.""" + +import os +import sys +import subprocess +import hashlib + +from vault_options import parse_vault_options, read_switch_bundle + +REGION_CONFIG = { + 'dev': 'ap-east-1', + 'prod': 'eu-west-3', +} + +OSS_PATH_CONFIG = { + 'dev': { + 'waje': 's3://web-h5game-test-waje/internal/', + 'wtan': 's3://web-h5game-test-wtan/internal/', + }, + 'prod': { + 'waje': 's3://web-h5game-prod/internal/', + 'wtan': '' + }, +} + +PROFILE_CONFIG = { + 'dev': { + 'waje': 'h5-new', + 'wtan': 'h5-new', + }, + 'prod': { + 'waje': 'default', + 'wtan': 'h5-new', + } +} + +VAULT_ADDR = 'https://keys.i-see--you.icu' +VAULT_TOKEN = 'hvs.CAESIJrOb65T9ylAMyzlqQOJjI-yutFmTysRbvYbN3IpGra5Gh4KHGh2cy5GeXU4N3FuVEhIQ3A5YmpIM1BETFdYMDc' + +BUNDLE_CONFIG = { + '6001': 'whot', + '6002': 'whot', + '6003': 'whot', + '6005': 'BRWhot', + '2001': 'roulette', + '2002': 'wajeSpin', + '2003': 'bottleSpin', + '2004': 'blackjack', + '2006': 'betterjack', + '6007': 'BRWhot', + '2008': 'slots', + '2005': 'christmas', +} + +UNLINK_ARRAY = [ + 'cocos2d-js.109f3.js', + 'cocos2d-js-min.13181.js', + 'physics.9d2de.js', + 'physics-min.ce5ee.js', + 'style-desktop.dfd76.css', + 'style-mobile.6e9cd.css', + 'splash.85cfd.png', +] + + +def create_vault_client(**kwargs): + """Create the default Vault client only when a real publish is run.""" + import hvac + + return hvac.Client(**kwargs) + + +def remove_folder(path): + print('remove path: ', path) + if os.path.exists(path): + if os.path.isfile(path) or os.path.islink(path): + os.remove(path) + else: + for filename in os.listdir(path): + remove_folder(os.path.join(path, filename)) + os.rmdir(path) + + +def run_publish(proj_dir, subgame_path, region, profile, + run_command=subprocess.run): + """Upload shared resources and bundles, then publish each game's shell.""" + build_dir = proj_dir + '\\build\\web-mobile\\' + + src_dir = build_dir + '\\src\\' + src_dst = subgame_path + 'lib/src/' + remove_result = run_command( + ['cmd', '/c', 'aws s3 rm ' + src_dst + ' --recursive' + ' --profile ' + profile + ' --region ' + region], + capture_output=True, + text=True, + ) + print('remove dir src result: ', remove_result) + + command = ( + 'call aws s3 cp ' + src_dir + ' ' + src_dst + + ' --recursive' + + ' --profile ' + profile + + ' --region ' + region + ) + result = run_command( + ['cmd', '/c', command], + capture_output=True, + text=True, + ) + print('upload dir src result: ', result) + remove_folder(src_dir) + + for filename in os.listdir(build_dir): + file_path = os.path.join(build_dir, filename) + if filename in UNLINK_ARRAY: + os.remove(file_path) + + for gameid in BUNDLE_CONFIG: + aws_path = ( + subgame_path + gameid + '/assets/' + + BUNDLE_CONFIG[gameid] + '/' + ) + remove_result = run_command( + ['cmd', '/c', 'aws s3 rm ' + aws_path + ' --recursive' + ' --profile ' + profile + ' --region ' + region], + capture_output=True, + text=True, + ) + print('remove game gameid: ', gameid, ' result: ', remove_result) + + subgame_dir = build_dir + 'assets\\' + BUNDLE_CONFIG[gameid] + '\\' + command = ( + 'call aws s3 cp ' + subgame_dir + ' ' + aws_path + + ' --recursive' + + ' --profile ' + profile + + ' --region ' + region + ) + result = run_command( + ['cmd', '/c', command], + capture_output=True, + text=True, + ) + print(result) + + for gameid in BUNDLE_CONFIG: + subgame_dir = build_dir + 'assets\\' + BUNDLE_CONFIG[gameid] + remove_folder(subgame_dir) + + for gameid in BUNDLE_CONFIG: + aws_path = subgame_path + gameid + '/' + command = ( + 'call aws s3 cp ' + build_dir + ' ' + aws_path + + ' --recursive' + + ' --profile ' + profile + + ' --region ' + region + ) + result = run_command( + ['cmd', '/c', command], + capture_output=True, + text=True, + ) + print('upload game gameid: ', gameid, ' result: ', result) + + +def main(argv, client_factory=None, publish=None): + """Parse the legacy CLI arguments and publish when Vault permits it.""" + proj_dir, pwd, env, cat, vault_path, vault_mount_point = parse_vault_options(argv) + if len(argv) < 5: + print('argv error!') + sys.exit(-1) + md5_obj = hashlib.md5() + md5_obj.update((env + cat).encode('utf-8')) + hash_value = md5_obj.hexdigest() + print('hash = ' + hash_value) + if hash_value != pwd: + print('argv verify failed!') + sys.exit(-2) + + if client_factory is None: + client_factory = create_vault_client + if publish is None: + publish = run_publish + + client = client_factory(url=VAULT_ADDR, token=VAULT_TOKEN) + + if client.is_authenticated(): + secret = read_switch_bundle(client, vault_path, vault_mount_point) + switch_bundle = secret['data']['data']['enable'] + print(switch_bundle) + if '1' != switch_bundle: + print('script exit, switchBundle status is ' + switch_bundle) + sys.exit(-999) + else: + print('running script failed!') + sys.exit(-1000) + + print('start running script...') + publish(proj_dir, OSS_PATH_CONFIG[env][cat], REGION_CONFIG[env], PROFILE_CONFIG[env][cat]) diff --git a/python/test_switch_bundle_common.py b/python/test_switch_bundle_common.py new file mode 100644 index 0000000..8e59d72 --- /dev/null +++ b/python/test_switch_bundle_common.py @@ -0,0 +1,104 @@ +import runpy +import unittest +from pathlib import Path +from unittest.mock import patch + +from switch_bundle_common import main + + +class _FakeVaultV2(object): + def __init__(self, switch_bundle='1'): + self.calls = [] + self.switch_bundle = switch_bundle + + def read_secret_version(self, **kwargs): + self.calls.append(kwargs) + return {'data': {'data': {'switchBundle': self.switch_bundle}}} + + +class _FakeClient(object): + def __init__(self, authenticated=True, switch_bundle='1'): + self.authenticated = authenticated + self.v2 = _FakeVaultV2(switch_bundle) + self.secrets = self + self.kv = self + + def is_authenticated(self): + return self.authenticated + + +class SwitchBundleCommonTests(unittest.TestCase): + def test_main_passes_vault_options_and_environment_to_publish(self): + client = _FakeClient() + publish_calls = [] + + main( + ['switchBundle.py', 'project', '123!@#', 'release', 'production'], + 's3://web-h5game-test/internal/', + 'ap-east-1', + client_factory=lambda **kwargs: client, + publish=lambda *args: publish_calls.append(args), + ) + + self.assertEqual( + client.v2.calls[0]['path'], + 'release', + ) + self.assertEqual( + client.v2.calls[0]['mount_point'], + 'production', + ) + self.assertEqual( + publish_calls, + [('project', 's3://web-h5game-test/internal/', 'ap-east-1')], + ) + + def test_main_skips_publish_when_password_is_invalid(self): + client_calls = [] + publish_calls = [] + + main( + ['switchBundle.py', 'project', 'wrong-password'], + 's3://web-h5game-test/internal/', + 'ap-east-1', + client_factory=lambda **kwargs: client_calls.append(kwargs), + publish=lambda *args: publish_calls.append(args), + ) + + self.assertEqual(client_calls, []) + self.assertEqual(publish_calls, []) + + def test_main_skips_publish_when_vault_does_not_allow_it(self): + for authenticated, switch_bundle in [(False, '1'), (True, '0')]: + with self.subTest(authenticated=authenticated, switch_bundle=switch_bundle): + client = _FakeClient(authenticated, switch_bundle) + publish_calls = [] + + main( + ['switchBundle.py', 'project', '123!@#'], + 's3://web-h5game-test/internal/', + 'ap-east-1', + client_factory=lambda **kwargs: client, + publish=lambda *args: publish_calls.append(args), + ) + + self.assertEqual(publish_calls, []) + self.assertEqual(len(client.v2.calls), int(authenticated)) + + def test_entry_points_keep_their_publish_destinations(self): + environments = [ + ('switchBundle.py', 's3://web-h5game-test/internal/', 'ap-east-1'), + ('switchBundleR.py', 's3://web-h5game-prod/internal/', 'eu-west-3'), + ] + for filename, destination, region in environments: + with self.subTest(filename=filename): + script = str(Path(__file__).with_name(filename)) + argv = [script, 'project', '123!@#', 'release', 'production'] + with patch('sys.argv', argv), patch('switch_bundle_common.main') as entry: + runpy.run_path(script, run_name='__main__') + + entry.assert_called_once_with(argv, destination, region) + + +if __name__ == '__main__': + unittest.main() diff --git a/python/test_vault_options.py b/python/test_vault_options.py new file mode 100644 index 0000000..b2a6285 --- /dev/null +++ b/python/test_vault_options.py @@ -0,0 +1,66 @@ +import unittest + +from vault_options import parse_vault_options, read_switch_bundle + + +class _FakeVaultV2(object): + def __init__(self): + self.calls = [] + + def read_secret_version(self, **kwargs): + self.calls.append(kwargs) + return {'data': {'data': {'switchBundle': '1'}}} + + +class _FakeClient(object): + def __init__(self): + self.v2 = _FakeVaultV2() + self.secrets = self + self.kv = self + + +class ParseVaultOptionsTests(unittest.TestCase): + def test_uses_default_path_and_mount_point(self): + options = parse_vault_options(['switchBundle.py', 'project', '123!@#']) + + self.assertEqual(options, ('default', 'aws')) + + def test_reads_path_and_mount_point_after_existing_arguments(self): + options = parse_vault_options( + ['switchBundle.py', 'project', '123!@#', 'release', 'production'] + ) + + self.assertEqual(options, ('release', 'production')) + + def test_uses_default_mount_point_when_only_path_is_provided(self): + options = parse_vault_options( + ['switchBundle.py', 'project', '123!@#', 'release'] + ) + + self.assertEqual(options, ('release', 'aws')) + + def test_passes_path_and_mount_point_to_vault(self): + client = _FakeClient() + + read_switch_bundle(client, 'release', 'production') + + self.assertEqual( + client.v2.calls, + [{ + 'path': 'release', + 'mount_point': 'production', + 'raise_on_deleted_version': True, + }], + ) + + def test_uses_default_vault_settings_when_not_provided(self): + client = _FakeClient() + + read_switch_bundle(client) + + self.assertEqual(client.v2.calls[0]['path'], 'default') + self.assertEqual(client.v2.calls[0]['mount_point'], 'aws') + + +if __name__ == '__main__': + unittest.main() diff --git a/python/vault_options.py b/python/vault_options.py new file mode 100644 index 0000000..7382c95 --- /dev/null +++ b/python/vault_options.py @@ -0,0 +1,29 @@ +DEFAULT_VAULT_PATH = 'dev' +DEFAULT_VAULT_MOUNT_POINT = 'tokens' + + +def parse_vault_options(argv): + """Return the Vault path and mount point from the script arguments. + + The first two arguments are already used by the publishing scripts for + the project directory and access password. Vault settings therefore + remain optional arguments 3 and 4 so existing invocations keep working. + """ + proj = argv[1] + pwd = argv[2] + env = argv[3] + cat = argv[4] + path = argv[5] if len(argv) > 5 else DEFAULT_VAULT_PATH + mount_point = argv[6] if len(argv) > 6 else DEFAULT_VAULT_MOUNT_POINT + return proj, pwd, env, cat, path, mount_point + + +def read_switch_bundle( + client, + path=DEFAULT_VAULT_PATH, + mount_point=DEFAULT_VAULT_MOUNT_POINT): + return client.secrets.kv.v2.read_secret_version( + path=path, + mount_point=mount_point, + raise_on_deleted_version=True + )