-
Notifications
You must be signed in to change notification settings - Fork 138
requires root privileges and touches /var during build #966
Changes from all commits
fd66e39
e0dfdb3
65362e6
60f3d89
4e6de1f
8d9f4db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| import json | ||
| import subprocess | ||
| import collections | ||
| from contextlib import contextmanager | ||
| from fnmatch import fnmatch as matches | ||
| import os | ||
| import selinux | ||
|
|
@@ -33,8 +34,6 @@ | |
| ATOMIC_CONFD = os.environ.get('ATOMIC_CONFD', '/etc/atomic.d/') | ||
| ATOMIC_LIBEXEC = os.environ.get('ATOMIC_LIBEXEC', '/usr/libexec/atomic') | ||
| ATOMIC_VAR_LIB = os.environ.get('ATOMIC_VAR_LIB', '/var/lib/atomic') | ||
| if not os.path.exists(ATOMIC_VAR_LIB): | ||
| os.makedirs(ATOMIC_VAR_LIB) | ||
| ATOMIC_INSTALL_JSON = os.environ.get('ATOMIC_INSTALL_JSON', os.path.join(ATOMIC_VAR_LIB, 'install.json')) | ||
|
|
||
| GOMTREE_PATH = "/usr/bin/gomtree" | ||
|
|
@@ -802,102 +801,79 @@ def load_scan_result_file(file_name): | |
| return json.loads(open(os.path.join(file_name), "r").read()) | ||
|
|
||
|
|
||
| def file_lock(func): | ||
| lock_file_name = "{}.lock".format(os.path.join(os.path.dirname(ATOMIC_INSTALL_JSON), "." + os.path.basename(ATOMIC_INSTALL_JSON))) | ||
|
|
||
| # Create the temporary lockfile if it doesn't exist | ||
| if not os.path.exists(lock_file_name): | ||
| open(lock_file_name, 'a').close() | ||
|
|
||
| install_data_file = open(lock_file_name, "r") | ||
|
|
||
| def get_lock(): | ||
| ''' | ||
| Obtains a read-only file lock on the install data | ||
| :return: | ||
| ''' | ||
| time_out = 0 | ||
| f_lock = False | ||
| @contextmanager | ||
| def file_lock(path): | ||
| lock_file_name = "{}.lock".format(path) | ||
| time_out = 0 | ||
| f_lock = False | ||
| with open(lock_file_name, "a") as f: | ||
| while time_out < 10.5: # Ten second attempt to get a lock | ||
| try: | ||
| fcntl.flock(install_data_file, fcntl.LOCK_EX | fcntl.LOCK_NB) | ||
| fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) | ||
| f_lock = True | ||
| break | ||
| except IOError: | ||
| time.sleep(.5) | ||
| time_out += .5 | ||
| if not f_lock: | ||
| raise ValueError("Unable to get file lock for {}".format(ATOMIC_INSTALL_JSON)) | ||
|
|
||
| def release_lock(): | ||
| fcntl.flock(install_data_file, fcntl.LOCK_UN) | ||
|
|
||
| def wrapper(*args, **kwargs): | ||
| get_lock() | ||
| ret = func(*args, **kwargs) | ||
| release_lock() | ||
| return ret | ||
|
|
||
| return wrapper | ||
| raise ValueError("Unable to get file lock for {}".format(lock_file_name)) | ||
|
|
||
| # Call the user code | ||
| yield | ||
| # Now unlock | ||
| fcntl.flock(f, fcntl.LOCK_UN) | ||
|
|
||
| # Records additional data for containers outside of the native storage (docker/ostree) | ||
| class InstallData(object): | ||
| if not os.path.exists(ATOMIC_INSTALL_JSON): | ||
| open(ATOMIC_INSTALL_JSON, 'a').close() | ||
|
|
||
| install_file_handle = open(ATOMIC_INSTALL_JSON, 'r') | ||
|
|
||
| @staticmethod | ||
| def _read_install_data(file_handle): | ||
| try: | ||
| return json.loads(file_handle.read()) | ||
| except ValueError: | ||
| return {} | ||
|
|
||
| @classmethod | ||
| def _write_install_data(cls, new_data): | ||
| install_data = cls._read_install_data(cls.install_file_handle) | ||
| for x in new_data: | ||
| install_data[x] = new_data[x] | ||
| temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) | ||
| json.dump(install_data, temp_file) | ||
| temp_file.close() | ||
| shutil.move(temp_file.name, ATOMIC_INSTALL_JSON) | ||
|
|
||
| @classmethod | ||
| @file_lock | ||
| def read_install_data(cls): | ||
| if os.path.exists(ATOMIC_INSTALL_JSON): | ||
| read_data = cls._read_install_data(cls.install_file_handle) | ||
| return read_data | ||
| return {} | ||
| with file_lock(ATOMIC_INSTALL_JSON): | ||
| try: | ||
| with open(ATOMIC_INSTALL_JSON, 'r') as f: | ||
| # Backwards compatibilty - we previously created an empty file explicitly; | ||
| # see https://github.com/projectatomic/atomic/pull/966 | ||
| if os.fstat(f.fileno()).st_size == 0: | ||
| return {} | ||
| return json.load(f) | ||
| except IOError as e: | ||
| if e.errno == errno.ENOENT: | ||
| return {} | ||
| raise e | ||
|
|
||
| @classmethod | ||
| @file_lock | ||
| def write_install_data(cls, new_data): | ||
| cls._write_install_data(new_data) | ||
| install_data = cls.read_install_data() | ||
| with file_lock(ATOMIC_INSTALL_JSON): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here, we can do the whole read, update, write under lock? |
||
| for x in new_data: | ||
| install_data[x] = new_data[x] | ||
| temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) | ||
| json.dump(install_data, temp_file) | ||
| temp_file.close() | ||
| if not os.path.exists(ATOMIC_VAR_LIB): | ||
| os.makedirs(ATOMIC_VAR_LIB) | ||
| shutil.move(temp_file.name, ATOMIC_INSTALL_JSON) | ||
|
|
||
| @classmethod | ||
| def get_install_name_by_id(cls, iid, install_data=None): | ||
| if not install_data: | ||
| install_data = cls._read_install_data(cls.install_file_handle) | ||
| install_data = cls.read_install_data() | ||
| for installed_image in install_data: | ||
| if install_data[installed_image]['id'] == iid: | ||
| return installed_image | ||
| raise ValueError("Unable to find {} in installed image data ({}). Re-run command with -i to ignore".format(id, ATOMIC_INSTALL_JSON)) | ||
|
|
||
| @classmethod | ||
| @file_lock | ||
| def delete_by_id(cls, iid, ignore=False): | ||
| install_data = cls._read_install_data(cls.install_file_handle) | ||
| install_data = cls.read_install_data() | ||
| try: | ||
| id_key = InstallData.get_install_name_by_id(iid, install_data=install_data) | ||
| except ValueError as e: | ||
| if not ignore: | ||
| raise ValueError(str(e)) | ||
| return | ||
| del install_data[id_key] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, this won't take effect, right? The
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably, but that's not a regression from this patch. I could think of many more things to do to improve this, not least of which is moving it to its own file with documentation, and such...but can we do that kind of thing in a separate PR? I'd really like to get FAHC going again. (And really, do we even really need this InstallData thing? Can't we (for system containers) use the ostree commit metadata?) |
||
| return cls._write_install_data(install_data) | ||
| return cls.write_install_data(install_data) | ||
|
|
||
| @classmethod | ||
| def image_installed(cls, img_object): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we yield the file handle here, so that... (continued in next comment)