diff --git a/bootstrapvz/common/tools.py b/bootstrapvz/common/tools.py index ada2909..0fceb93 100644 --- a/bootstrapvz/common/tools.py +++ b/bootstrapvz/common/tools.py @@ -133,3 +133,8 @@ def copy_tree(from_path, to_path): for path in files: copy(os.path.join(abs_prefix, path), os.path.join(to_path, prefix, path)) + + +def rel_path(base, path): + import os.path + return os.path.join(os.path.dirname(base), path) diff --git a/bootstrapvz/plugins/admin_user/__init__.py b/bootstrapvz/plugins/admin_user/__init__.py index 4b81c56..0627a96 100644 --- a/bootstrapvz/plugins/admin_user/__init__.py +++ b/bootstrapvz/plugins/admin_user/__init__.py @@ -1,13 +1,8 @@ - - def validate_manifest(data, validator, error): import os.path + schema_path = os.path.normpath(os.path.join(os.path.dirname(__file__), 'manifest-schema.yml')) validator(data, schema_path) - pubkey = data['plugins']['admin_user'].get('pubkey', None) - if pubkey is not None and not os.path.exists(pubkey): - msg = 'Could not find public key at %s' % pubkey - error(msg, ['plugins', 'admin_user', 'pubkey']) def resolve_tasks(taskset, manifest): @@ -24,6 +19,7 @@ def resolve_tasks(taskset, manifest): taskset.add(tasks.AdminUserPassword) if 'pubkey' in manifest.plugins['admin_user']: + taskset.add(tasks.CheckPublicKeyFile) taskset.add(tasks.AdminUserPublicKey) elif manifest.provider['name'] == 'ec2': logging.getLogger(__name__).info("The SSH key will be obtained from EC2") diff --git a/bootstrapvz/plugins/admin_user/manifest-schema.yml b/bootstrapvz/plugins/admin_user/manifest-schema.yml index 4d20396..394b5de 100644 --- a/bootstrapvz/plugins/admin_user/manifest-schema.yml +++ b/bootstrapvz/plugins/admin_user/manifest-schema.yml @@ -11,10 +11,10 @@ properties: properties: username: {type: string} password: {type: string} - pubkey: {$ref: '#/definitions/absolute_path'} + pubkey: {$ref: '#/definitions/path'} required: [username] additionalProperties: false definitions: - absolute_path: - pattern: ^/[^\0]+$ + path: + pattern: ^[^\0]+$ type: string diff --git a/bootstrapvz/plugins/admin_user/tasks.py b/bootstrapvz/plugins/admin_user/tasks.py index 583e0e5..9d7e012 100644 --- a/bootstrapvz/plugins/admin_user/tasks.py +++ b/bootstrapvz/plugins/admin_user/tasks.py @@ -8,6 +8,27 @@ import logging log = logging.getLogger(__name__) +class CheckPublicKeyFile(Task): + description = 'Check that the public key is a valid file' + phase = phases.validation + + @classmethod + def run(cls, info): + from bootstrapvz.common.tools import log_call, rel_path + + pubkey = info.manifest.plugins['admin_user'].get('pubkey', None) + if pubkey is not None: + abs_pubkey = rel_path(info.manifest.path, pubkey) + if not os.path.isfile(abs_pubkey): + msg = 'Could not find public key at %s' % pubkey + info.manifest.validation_error(msg, ['plugins', 'admin_user', 'pubkey']) + + ret, _, stderr = log_call('ssh-keygen -l -f ' + abs_pubkey) + if ret != 0: + msg = 'Invalid public key file at %s' % pubkey + info.manifest.validation_error(msg, ['plugins', 'admin_user', 'pubkey']) + + class AddSudoPackage(Task): description = 'Adding `sudo\' to the image packages' phase = phases.preparation @@ -74,14 +95,21 @@ class AdminUserPublicKey(Task): # Get the stuff we need (username & public key) username = info.manifest.plugins['admin_user']['username'] - with open(info.manifest.plugins['admin_user']['pubkey']) as pubkey_handle: + + from bootstrapvz.common.tools import rel_path + pubkey_path = rel_path(info.manifest.path, + info.manifest.plugins['admin_user']['pubkey']) + + with open(pubkey_path) as pubkey_handle: pubkey = pubkey_handle.read() # paths - ssh_dir_rel = os.path.join('home', username, '.ssh') - auth_keys_rel = os.path.join(ssh_dir_rel, 'authorized_keys') - ssh_dir_abs = os.path.join(info.root, ssh_dir_rel) - auth_keys_abs = os.path.join(info.root, auth_keys_rel) + from os.path import join + ssh_dir_rel = join('home', username, '.ssh') + auth_keys_rel = join(ssh_dir_rel, 'authorized_keys') + ssh_dir_abs = join(info.root, ssh_dir_rel) + auth_keys_abs = join(info.root, auth_keys_rel) + # Create the ssh dir if nobody has created it yet if not os.path.exists(ssh_dir_abs): os.mkdir(ssh_dir_abs, 0700) diff --git a/bootstrapvz/plugins/file_copy/README.rst b/bootstrapvz/plugins/file_copy/README.rst index 8daefa5..166984e 100644 --- a/bootstrapvz/plugins/file_copy/README.rst +++ b/bootstrapvz/plugins/file_copy/README.rst @@ -17,7 +17,7 @@ The ``file_copy`` plugin takes a (non-empty) ``files`` list, and optionally a `` Files (items in the ``files`` list) must be objects with the following properties: - ``src`` and ``dst`` (required) are the source and destination paths. - ``src`` is relative to the current directory, whereas ``dst`` is a path in the VM. + ``src`` is relative to the manifest, whereas ``dst`` is a path in the VM. - ``permissions`` (optional) is a permission string in a format appropriate for ``chmod(1)``. - ``owner`` and ``group`` (optional) are respectively a user and group specification, in a format appropriate for ``chown(1)`` and ``chgrp(1)``. diff --git a/bootstrapvz/plugins/file_copy/__init__.py b/bootstrapvz/plugins/file_copy/__init__.py index 6bdbc60..30fa6d1 100644 --- a/bootstrapvz/plugins/file_copy/__init__.py +++ b/bootstrapvz/plugins/file_copy/__init__.py @@ -7,15 +7,10 @@ def validate_manifest(data, validator, error): schema_path = os.path.normpath(os.path.join(os.path.dirname(__file__), 'manifest-schema.yml')) validator(data, schema_path) - for i, file_entry in enumerate(data['plugins']['file_copy']['files']): - srcfile = file_entry['src'] - if not os.path.exists(srcfile): - msg = 'The source file %s does not exist.' % srcfile - error(msg, ['plugins', 'file_copy', 'files', i]) - def resolve_tasks(taskset, manifest): if ('mkdirs' in manifest.plugins['file_copy']): taskset.add(tasks.MkdirCommand) if ('files' in manifest.plugins['file_copy']): + taskset.add(tasks.ValidateFiles) taskset.add(tasks.FileCopyCommand) diff --git a/bootstrapvz/plugins/file_copy/tasks.py b/bootstrapvz/plugins/file_copy/tasks.py index 179cffd..c353db3 100644 --- a/bootstrapvz/plugins/file_copy/tasks.py +++ b/bootstrapvz/plugins/file_copy/tasks.py @@ -36,6 +36,20 @@ class MkdirCommand(Task): modify_path(info, dir_entry['dir'], dir_entry) +class ValidateFiles(Task): + description = 'Check that the required files exist' + phase = phases.validation + + @classmethod + def run(cls, info): + from bootstrapvz.common.tools import rel_path + + for i, file_entry in enumerate(info.manifest.plugins['file_copy']['files']): + if not os.path.exists(rel_path(info.manifest.path, file_entry['src'])): + msg = 'The source file %s does not exist.' % file_entry['src'] + info.manifest.validation_error(msg, ['plugins', 'file_copy', 'files', i]) + + class FileCopyCommand(Task): description = 'Copying user specified files into the image' phase = phases.user_modification @@ -43,13 +57,21 @@ class FileCopyCommand(Task): @classmethod def run(cls, info): + from bootstrapvz.common.tools import rel_path + for file_entry in info.manifest.plugins['file_copy']['files']: # note that we don't use os.path.join because it can't # handle absolute paths, which 'dst' most likely is. final_destination = os.path.normpath("%s/%s" % (info.root, file_entry['dst'])) - if os.path.isfile(file_entry['src']): - shutil.copy(file_entry['src'], final_destination) + src_path = rel_path(info.manifest.path, file_entry['src']) + if os.path.isfile(src_path): + shutil.copy(src_path, final_destination) else: - shutil.copytree(file_entry['src'], final_destination) + shutil.copytree(src_path, final_destination) - modify_path(info, file_entry['dst'], file_entry) + if os.path.isfile(src_path) and os.path.isdir(final_destination): + dst = os.path.join(final_destination, os.path.basename(src_path)) + else: + dst = final_destination + + modify_path(info, dst, file_entry)