| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- """Run the sealed installer in one enterprise-matched container after normal twin initialization."""
- import argparse
- import base64
- import json
- from pathlib import Path
- import re
- import subprocess
- import sys
- import uuid
- sys.dont_write_bytecode = True
- from bootstrap import HERE, Stopped, check_enterprise, need, plain, sealed
- def run(args, runner=subprocess.run, input_bytes=None):
- result = runner(args, input=input_bytes, capture_output=True, timeout=240)
- need(result.returncode == 0, 'HOST_COMMAND_FAILED')
- return result.stdout
- def initialize(checkout, commit, container, apply=False, runner=subprocess.run):
- checkout = plain(checkout)
- need(re.fullmatch('[a-f0-9]{40}', commit), 'PINNED_COMMIT_REQUIRED')
- need(re.fullmatch('[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}', container), 'INVALID_CONTAINER_NAME')
- cfg, files = sealed(checkout)
- actual = run(['git', '-C', str(checkout), 'rev-parse', 'HEAD'], runner).decode().strip()
- need(actual == commit, 'CONFIGURATION_COMMIT_MISMATCH')
- need(not run(['git', '-C', str(checkout), 'status', '--porcelain'], runner).strip(),
- 'CONFIGURATION_CHECKOUT_CHANGED')
- prefix = ['docker', 'exec', '--user', '10000:10000', container]
- need(run([*prefix, 'id', '-u'], runner).strip() == b'10000', 'CONTAINER_USER_MISMATCH')
- current = json.loads(run([*prefix, 'cat', '/opt/data/.fmode/config/enterprise/current.json'], runner))
- check_enterprise(current, cfg)
- # A different enterprise is rejected before docker cp or any installation writes.
- if not apply:
- return {'ok': True, 'planOnly': True, 'companyObjectId': cfg['companyObjectId'],
- 'configurationCommit': commit, 'container': container, 'businessWrites': 0}
- # Transfer only public files over stdin, written by hermes. No root extraction or .git copying.
- copy_code = '''import base64,json,os,pathlib,sys,tempfile
- raw=sys.stdin.buffer.read(16000001)
- assert len(raw)<=16000000
- files=json.loads(raw)
- base=pathlib.Path('/opt/data')
- assert all(not x.is_symlink() for x in (base,*base.parents))
- root=pathlib.Path(tempfile.mkdtemp(prefix='ksf-enterprise-package-',dir=base))
- for name,body in files.items():
- parts=pathlib.PurePosixPath(name).parts
- assert name and name=='/'.join(parts) and not name.startswith('/')
- assert all(p not in ('.','..') for p in parts) and '\\\\' not in name and ':' not in name
- file=root/name
- file.parent.mkdir(parents=True,exist_ok=True,mode=0o700)
- with file.open('xb') as out: out.write(base64.b64decode(body,validate=True))
- file.chmod(0o644)
- print(str(root))
- '''
- wire = json.dumps({n: base64.b64encode((checkout / n).read_bytes()).decode()
- for n in [*files, 'FILES.sha256']}).encode()
- target = run(['docker', 'exec', '--user', '10000:10000', '-i', container,
- 'python3', '-B', '-c', copy_code], runner, wire).decode().strip()
- need(re.fullmatch('/opt/data/ksf-enterprise-package-[a-zA-Z0-9_-]+', target), 'PACKAGE_TRANSFER_PATH_INVALID')
- out = '/opt/data/ksf-enterprise-results/' + uuid.uuid4().hex
- results = []
- for command in ('plan', 'apply', 'verify'):
- value = json.loads(run([*prefix, 'python3', '-B', target + '/bootstrap.py', command,
- '--data-dir', '/opt/data', '--out', out], runner))
- need(value.get('ok') is True, 'CONTAINER_INSTALL_FAILED')
- results.append(value)
- voc_results = []
- for command in ('plan', 'apply', 'verify'):
- value = json.loads(run([*prefix, 'python3', '-B', target + '/voc.py', command,
- '--data-dir', '/opt/data', '--out', out + '/voc'], runner))
- need(value.get('ok') is True, 'CONTAINER_VOC_INSTALL_FAILED')
- voc_results.append(value)
- return {'ok': True, 'container': container, 'companyObjectId': cfg['companyObjectId'],
- 'configurationCommit': commit, 'resultsDirectory': out, 'packageDirectory': target,
- 'installation': results[-1], 'voc': voc_results[-1],
- 'vocModified': voc_results[1].get('vocModified', False), 'authorizationProvisioned': False,
- 'automaticLifecycleHookInstalled': False, 'businessWrites': 0}
- def main():
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--checkout', required=True, type=Path)
- parser.add_argument('--commit', required=True)
- parser.add_argument('--container', required=True)
- parser.add_argument('--apply', action='store_true')
- args = parser.parse_args()
- need(args.checkout.is_absolute(), 'ABSOLUTE_PATH_REQUIRED')
- print(json.dumps(initialize(args.checkout, args.commit, args.container, args.apply),
- ensure_ascii=False, indent=2))
- if __name__ == '__main__':
- try:
- main()
- except (Stopped, OSError, ValueError, KeyError, subprocess.TimeoutExpired) as error:
- print(json.dumps({'ok': False, 'error': {'code': str(error) if isinstance(error, Stopped)
- else 'HOST_BOOTSTRAP_STOPPED'}}), file=sys.stderr)
- sys.exit(1)
|