def parse_action(action, parsed): """ Parse the action to execute. """ if action == 'deploy': deploy(parsed.configuration) elif action == 'list': list_configs() elif action == 'new': new_config(parsed.name) elif action == 'prepare': deploy(parsed.configuration, prepare=True) elif action == 'remove': remove_config(parsed.name) else: util.cprint(m.FUMI_UNKNOWN)
def remove_config(name): """Remove a configuration from the fumi.yml file. Arguments: name (str): Name of the configuration to remove. """ status, content = util.read_yaml(FUMI_YML) if not status: sys.exit(-1) if not content: util.cprint(m.NO_YML, 'red') sys.exit(-1) if name not in content.keys(): util.cprint(m.CONF_NAME_NOT_FOUND % name, 'red') sys.exit(-1) # Remove section del content[name] status = util.write_yaml(FUMI_YML, content) if not status: sys.exit(-1) util.cprint(m.CONF_REMOVED % name, 'green')
def new_config(name): """Create new basic configuration in fumi.yml file. Arguments: name (str): Name for the new configuration. """ status, content = util.read_yaml(FUMI_YML) if not status: sys.exit(-1) if not content: # Starting from scratch content = {} if name in content.keys(): # Do not overwrite configuration util.cprint(m.CONF_EXISTS % name, 'red') sys.exit(-1) content[name] = { 'source-type': '', 'source-path': '', 'predep': [], 'postdep': [], 'host': '', 'user': '', 'use-password': False, 'password': '', 'deploy-path': '', } status = util.write_yaml(FUMI_YML, content) if not status: sys.exit(-1) util.cprint(m.CREATED_BLANK % name)
def build_deployer(config): """Build a Deployer object. Arguments: config (dict): Parsed section of the YAML configuration file. Returns: Boolean indicating result and ``Deployer`` instance or ``None``. """ try: deployer = Deployer(**config) except KeyError as e: # Missing required parameter key = e.args[0] cprint(m.DEP_MISSING_PARAM + '\n' % key, 'red') return False, None # Determine deployment function to use if deployer.source_type == 'local': cprint(m.DEP_LOCAL) deployer.deploy = types.MethodType(deployments.deploy_local, deployer) elif deployer.source_type == 'git': cprint(m.DEP_GIT) deployer.deploy = types.MethodType(deployments.deploy_git, deployer) else: # Unknown deployment type cprint(m.DEP_UNKNOWN % deployer.source_type, 'red') return False, None # Additional method for preparing/testing the deployment deployer.prepare = types.MethodType(deployments.prepare, deployer) return True, deployer
def list_configs(): """List the configurations present in the fumi.yml file.""" status, content = util.read_yaml(FUMI_YML) if not status: sys.exit(-1) if not content: util.cprint(m.NO_YML, 'red') sys.exit(-1) for conf in content.keys(): is_default = content[conf].get('default', False) if is_default: util.cprint(m.LIST_DEFAULT % conf) else: util.cprint('- %s' % conf)
def deploy(deployer): """Git based deployment. Arguments: deployer (``Deployer``): Deployer instance. Returns: Boolean indicating result of the deployment. """ # SSH connection util.cprint('> ' + m.DEP_CONNECTING % (deployer.host, deployer.user), 'cyan') status, ssh = util.connect(deployer) if not status: ssh.close() return False util.cprint(m.DEP_CONNECTED + '\n', 'green') # Predeployment commands status, util.run_commands(ssh, deployer.predep) if not status: ssh.close() return False # Directory structures util.cprint('> ' + m.DEP_CHECK_REMOTE, 'cyan') status = util.check_dirs(ssh, deployer) if not status: ssh.close() return False util.cprint(m.CORRECT + '\n', 'green') # Clone source timestamp = datetime.datetime.utcnow().strftime('%Y%m%d%H%M%S') util.cprint(m.DEP_PREPARE_REV % timestamp, 'white') util.cprint('> ' + m.DEP_GIT_CLONE, 'cyan') rev_path = os.path.join(deployer.deploy_path, 'rev') current_rev = os.path.join(deployer.deploy_path, 'rev', timestamp) clone = 'git clone %s %s' % (deployer.source_path, current_rev) stdin, stdout, stderr = ssh.exec_command(clone) status = stdout.channel.recv_exit_status() if status == 127: util.cprint(m.DEP_MISSING_PARAM) ssh.close() return False # TODO: check git exit codes util.cprint(m.DONE + '\n', 'green') # Link directory status = util.symlink(ssh, deployer, rev_path, timestamp) if not status: util.rollback(ssh, deployer, timestamp, 3) ssh.close() return False # Link shared paths util.symlink_shared(ssh, deployer) # Run post-deployment commands status = util.run_commands(ssh, deployer.postdep, os.path.join(deployer.deploy_path, 'current')) if not status: util.rollback(ssh, deployer, timestamp, 3) ssh.close() return False # Clean revisions if deployer.keep_max: status = util.clean_revisions(ssh, deployer.keep_max, rev_path) util.cprint(m.DEP_COMPLETE, 'green') # Close SSH connection ssh.close() return True
def prepare(deployer): """Test connection and prepare directories. Arguments: deployer (``Deployer``): Deployer instance. Returns: Boolean indicating result of the preparation. """ # SSH connection util.cprint('> ' + m.DEP_CONNECTING % (deployer.host, deployer.user), 'cyan') status, ssh = util.connect(deployer) if not status: ssh.close() return False util.cprint(m.DEP_CONNECTED + '\n', 'green') # Directory structures util.cprint('> ' + m.DEP_CREATEDIR, 'cyan') status = util.create_dirs(ssh, deployer) if not status: ssh.close() return False util.cprint(m.CORRECT + '\n', 'green') util.cprint(m.DEP_PREPARE_COMPLETE, 'green') util.cprint(m.DEP_PREPARE_NOTICE, 'white') # Close SSH connection ssh.close() return True
def deploy(conf_name, prepare=False): """Deploy using given configuration. Arguments: conf_name (str): Name of the configuration to use in the deployment. prepare (bool): Whether or not this is a preparation deployment. A preparation simply checks connection and creates the remote directory tree. """ status, content = util.read_yaml(FUMI_YML) if not status: sys.exit(-1) if not content: util.cprint(m.NO_YML, 'red') sys.exit(-1) if not conf_name: # Find default configuration default = None for k in content.keys(): if content[k].get('default'): # Found default configuration default = k util.cprint(m.USE_DEFAULT_CONF % default) break if not default: # Default not found if len(content.keys()) == 0: util.cprint(m.NO_CONFS, 'red') sys.exit(-1) # Ask for default util.cprint(m.CONFS_FOUND) for k in content.keys(): util.cprint('- %s' % k) default = six.moves.input(m.CONF_SET_DEF) if default in content.keys(): content[default]['default'] = True util.write_yaml(FUMI_YML, content) else: # Welp... util.cprint(m.CONF_NOT_FOUND, 'red') sys.exit(-1) conf_name = default elif conf_name not in content.keys(): util.cprint(m.CONF_NAME_NOT_FOUND % conf_name, 'red') sys.exit(-1) # Build deployer status, deployer = build_deployer(content[conf_name]) if not status: sys.exit(-1) if prepare: # Preparation result = deployer.prepare() else: # Deploy! result = deployer.deploy() if not result: sys.exit(-1)
def deploy(deployer): """Local based deployment. Arguments: deployer (``Deployer``): Deployer instance. Returns: Boolean indicating result of the deployment. """ # SSH connection util.cprint('> ' + m.DEP_CONNECTING % (deployer.host, deployer.user), 'cyan') status, ssh = util.connect(deployer) if not status: ssh.close() return False util.cprint(m.DEP_CONNECTED + '\n', 'green') # Predeployment commands status, util.run_commands(ssh, deployer.predep) if not status: ssh.close() return False # Directory structures util.cprint('> ' + m.DEP_CHECK_REMOTE, 'cyan') status = util.check_dirs(ssh, deployer) if not status: ssh.close() return False util.cprint(m.CORRECT + '\n', 'green') # Compress source to temporary directory timestamp = datetime.datetime.utcnow().strftime('%Y%m%d%H%M%S') util.cprint(m.DEP_PREPARE_REV % timestamp, 'white') compressed_file = timestamp + '.tar.gz' tmp_local = os.path.join('/tmp', compressed_file) if deployer.local_ignore: cnt_list = list( set(os.listdir(deployer.source_path)) ^ set(deployer.local_ignore)) else: cnt_list = os.listdir(deployer.source_path) util.cprint('> ' + m.DEP_LOCAL_COMPRESS % tmp_local, 'cyan') with tarfile.open(tmp_local, "w:gz") as tar: for item in cnt_list: path = os.path.join(deployer.source_path, item) if os.path.exists(path): # Compress tar.add(path, arcname=timestamp + '/' + item) else: # Ignore util.cprint(m.DEP_LOCAL_PATHNOEXIST % path, 'white') util.cprint(m.DONE + '\n', 'green') # Upload compressed source util.cprint('> ' + m.DEP_LOCAL_UPLOAD % compressed_file, 'cyan') try: uload = scp.SCPClient(ssh.get_transport(), buff_size=deployer.buffer_size) except: # Failed to initiate SCP util.cprint(m.DEP_LOCAL_SCPFAIL, 'red') status = util.rollback(ssh, deployer, timestamp, 1) ssh.close() return False uload_tmp = deployer.host_tmp or '/tmp' uload_path = os.path.join(uload_tmp, compressed_file) try: uload.put(tmp_local, uload_path) except scp.SCPException as e: util.cprint(m.DEP_LOCAL_UPLOADERR + '\n' % e, 'red') status = util.rollback(ssh, deployer, timestamp, 3) ssh.close() return False util.cprint(m.DONE + '\n', 'green') # Uncompress source to deploy_path/rev util.cprint('> ' + m.DEP_LOCAL_UNCOMPRESS, 'cyan') rev_path = os.path.join(deployer.deploy_path, 'rev') untar = 'tar -C %s -zxvf %s' % (rev_path, uload_path) stdin, stdout, stderr = ssh.exec_command(untar) status = stdout.channel.recv_exit_status() if status == 127: util.cprint(m.DEP_LOCAL_ERR127 + '\n', 'red') status = util.rollback(ssh, deployer, timestamp, 1) ssh.close() return False elif status == 1: util.cprint(m.DEP_LOCAL_ERR1 + '\n', 'red') util.cprint(*stderr.readlines()) status = util.rollback(ssh, deployer, timestamp, 2) ssh.close() return False elif status == 2: util.cprint(m.DEP_LOCAL_ERR2 + '\n', 'red') util.cprint(*stderr.readlines()) status = util.rollback(ssh, deployer, timestamp, 2) ssh.close() return False util.cprint(m.DONE + '\n', 'green') # Link directory status = util.symlink(ssh, deployer, rev_path, timestamp) if not status: util.rollback(ssh, deployer, timestamp, 3) ssh.close() return False # Link shared paths util.symlink_shared(ssh, deployer) # Run post-deployment commands status = util.run_commands(ssh, deployer.postdep, os.path.join(deployer.deploy_path, 'current')) if not status: util.rollback(ssh, deployer, timestamp, 3) ssh.close() return False # Clean revisions if deployer.keep_max: status = util.clean_revisions(ssh, deployer.keep_max, rev_path) # Cleanup temporary files util.cprint('> ' + m.DEP_LOCAL_CLEAN, 'cyan') util.remove_local(tmp_local) util.remove_remote(ssh, uload_path) util.cprint(m.DONE + '\n', 'green') util.cprint(m.DEP_COMPLETE, 'green') # Close SSH connection ssh.close() return True