def get_info(host, *, users=None, connection=None): log.debug("Getting info about '{}'".format(host)) user, host = connection.ssh_user, connection.ssh_host data = OrderedDict() data["ssh_user"] = user data["ssh_host"] = host data["ssh"] = "{}@{}".format(user, host) data["whoami"] = ssh_cmd(connection, "whoami") data["uname"] = ssh_cmd(connection, "uname") data["arch"] = ssh_cmd(connection, "uname -m") data["os_release"] = os_release(ssh_cmd(connection, "cat /etc/os-release")) data["agent_location"] = ssh_cmd(connection, "which cf-agent") data["policy_server"] = ssh_cmd(connection, "cat /var/cfengine/policy_server.dat") agent_version = ssh_cmd(connection, "cf-agent --version") if agent_version: # 'CFEngine Core 3.12.1 \n CFEngine Enterprise 3.12.1' # ^ split and use this part for version number agent_version = agent_version.split()[2] data["agent_version"] = agent_version data["bin"] = {} for bin in ["dpkg", "rpm", "yum", "apt", "pkg"]: path = ssh_cmd(connection, "which {}".format(bin)) if path: data["bin"][bin] = path log.debug("JSON data from host info: \n" + pretty(data)) return data
def validate_args(args): if args.version is True: # --version with no second argument print_version_info() exit_success() if args.version and args.command not in ["install", "packages"]: user_error("Cannot specify version number in '{}' command".format( args.command)) if "hosts" in args and args.hosts: log.debug("validate_args, hosts in args, args.hosts='{}'".format( args.hosts)) args.hosts = resolve_hosts(args.hosts) if "clients" in args and args.clients: args.clients = resolve_hosts(args.clients) if "bootstrap" in args and args.bootstrap: args.bootstrap = [ strip_user(host_info) for host_info in resolve_hosts(args.bootstrap, private_ips=True) ] if "hub" in args and args.hub: args.hub = resolve_hosts(args.hub) if not args.command: user_error("Invalid or missing command") args.command = args.command.strip() validate_command(args.command, args)
def connect(host, users=None): log.debug("Connecting to '{}'".format(host)) if "@" in host: parts = host.split("@") assert len(parts) == 2 host = parts[1] if not users: users = [parts[0]] if not users: users = [ "Administrator", "ubuntu", "ec2-user", "centos", "vagrant", "root" ] # Similar to ssh, try own username first, # some systems will lock us out if we have too many failed attempts. if whoami() not in users: users = [whoami()] + users for user in users: try: log.debug("Attempting ssh: {}@{}".format(user, host)) c = fabric.Connection(host=host, user=user) c.ssh_user = user c.ssh_host = host c.run("whoami", hide=True) return c except AuthenticationException: continue sys.exit("Could not ssh into '{}'".format(host))
def agent_run(host, *, connection=None): print("Triggering an agent run on: '{}'".format(host)) command = "/var/cfengine/bin/cf-agent -Kf update.cf" output = ssh_sudo(connection, command) log.debug(output) command = "/var/cfengine/bin/cf-agent -K" output = ssh_sudo(connection, command) log.debug(output)
def ssh_sudo(connection, cmd): try: log.debug("Running(sudo) over SSH: '{}'".format(cmd)) result = connection.sudo(cmd, hide=True) output = result.stdout.strip() log.debug("'{}' -> '{}'".format(cmd, output)) return output except UnexpectedExit: return None
def agent_run(host): print("Triggering an agent run on: '{}'".format(host)) with remote.connect(host) as connection: command = "/var/cfengine/bin/cf-agent -Kf update.cf" output = remote.ssh_sudo(connection, command) log.debug(output) command = "/var/cfengine/bin/cf-agent -K" output = remote.ssh_sudo(connection, command) log.debug(output)
def disable_password_dialog(host): print("Disabling password change on hub: '{}'".format(host)) api = "https://{}/api/user/admin".format(host) d = json.dumps({"password": "******"}) creds = "admin:admin" c = "curl -X POST -k {} -u {} -H 'Content-Type: application/json' -d '{}'".format( api, creds, d) log.debug(c) os.system(c)
def install(hubs, clients, *, bootstrap=None, package=None, hub_package=None, client_package=None, version=None, demo=False, call_collect=False, edition=None): assert hubs or clients assert not (hubs and clients and package) # These assertions are checked in main.py if not hub_package: hub_package = package if not client_package: client_package = package if bootstrap: if type(bootstrap) is str: bootstrap = [bootstrap] save_file(os.path.join(cf_remote_dir(), "policy_server.dat"), "\n".join(bootstrap + [""])) errors = 0 if hubs: if type(hubs) is str: hubs = [hubs] for index, hub in enumerate(hubs): log.debug("Installing {} hub package on '{}'".format(edition, hub)) errors += install_host( hub, hub=True, package=hub_package, bootstrap=bootstrap[index % len(bootstrap)] if bootstrap else None, version=version, demo=demo, call_collect=call_collect, edition=edition) for index, host in enumerate(clients or []): log.debug("Installing {} client package on '{}'".format(edition, host)) errors += install_host( host, hub=False, package=client_package, bootstrap=bootstrap[index % len(bootstrap)] if bootstrap else None, version=version, demo=demo, edition=edition) if demo and hubs: for hub in hubs: print( "Your demo hub is ready: https://{}/ (Username: admin, Password: password)" .format(strip_user(hub))) return errors
def info(hosts, users=None): assert hosts log.debug("hosts='{}'".format(hosts)) errors = 0 for host in hosts: data = get_info(host, users=users) if data: print_info(data) else: errors += 1 return errors
def agent_run(data, *, connection=None): host = data["ssh_host"] agent = data["agent"] print("Triggering an agent run on: '{}'".format(host)) command = "{} -Kf update.cf".format(agent) ssh_func = ssh_cmd if data["os"] == "windows" else ssh_sudo output = ssh_func(connection, command) log.debug(output) command = "{} -K".format(agent) output = ssh_func(connection, command) log.debug(output)
def get_json(url): r = requests.get(url) assert r.status_code >= 200 and r.status_code < 300 data = r.json() filename = os.path.basename(url) dir = cf_remote_dir("json") path = os.path.join(dir, filename) log.debug("Saving '{}' to '{}'".format(url, path)) write_json(path, data) return data
def find(self, tags, extension=None): if not self.extended_data: self.init_download() log.debug("Looking for tags: {}".format(tags)) artifacts = self.artifacts if extension: artifacts = [a for a in self.artifacts if a.extension == extension] for tag in tags or []: tag = canonify(tag) artifacts = [a for a in artifacts if tag in a.tags] # Have to force evaluation using list comprehension, # since we are overwriting artifacts return artifacts
def install( hubs, clients, *, bootstrap=None, package=None, hub_package=None, client_package=None, version=None, demo=False, call_collect=False): assert hubs or clients assert not (hubs and clients and package) # These assertions are checked in main.py if not hub_package: hub_package = package if not client_package: client_package = package if bootstrap: if type(bootstrap) is str: bootstrap = [bootstrap] save_file(os.path.join(cf_remote_dir(), "policy_server.dat"), "\n".join(bootstrap + [""])) if hubs: if type(hubs) is str: hubs = [hubs] for index, hub in enumerate(hubs): log.debug("Installing hub package on '{}'".format(hub)) install_host( hub, hub=True, package=hub_package, bootstrap=bootstrap[index % len(bootstrap)] if bootstrap else None, version=version, demo=demo, call_collect=call_collect) for index, host in enumerate(clients or []): log.debug("Installing client package on '{}'".format(host)) install_host( host, hub=False, package=client_package, bootstrap=bootstrap[index % len(bootstrap)] if bootstrap else None, version=version, demo=demo) if demo and hubs: for hub in hubs: print( "Your demo hub is ready: https://{}/ (Username: admin, Password: password)". format(strip_user(hub)))
def os_release(inp): if not inp: log.debug("Cannot parse os-release file (empty)") return None d = OrderedDict() for line in inp.splitlines(): line = line.strip() if "=" not in line: continue key, sep, value = line.partition("=") assert "=" not in key if len(value) > 1 and value[0] == value[-1] and value[0] in ["'", '"']: value = value[1:-1] d[key] = value return d
def get_cloud_hosts(name, private_ips=False): if not os.path.exists(paths.CLOUD_STATE_FPATH): return [] state = read_json(paths.CLOUD_STATE_FPATH) group_name = None hosts = [] if name.startswith("@") and name in state: # @some_group given and exists group_name = name elif ("@" + name) in state: # group_name given and @group_name exists group_name = "@" + name if group_name is not None: for name, info in state[group_name].items(): if name == "meta": continue log.debug("found name '{}' in state, info='{}'".format(name, info)) hosts.append(info) else: if name in state: # host_name given and exists at the top level hosts.append(state[name]) else: for group_name in [ key for key in state.keys() if key.startswith("@") ]: if name in state[group_name]: hosts.append(state[group_name][name]) ret = [] for host in hosts: if private_ips: key = "private_ips" else: key = "public_ips" ips = host.get(key, []) if len(ips) > 0: if host.get("user"): ret.append('{}@{}'.format(host.get("user"), ips[0])) else: ret.append(ips[0]) else: ret.append(None) return ret
def get_info(host, *, users=None, connection=None): log.debug("Getting info about '{}'".format(host)) user, host = connection.ssh_user, connection.ssh_host data = OrderedDict() data["ssh_user"] = user data["ssh_host"] = host data["ssh"] = "{}@{}".format(user, host) data["whoami"] = ssh_cmd(connection, "whoami") systeminfo = ssh_cmd(connection, "systeminfo") if systeminfo: data["os"] = "windows" data["systeminfo"] = parse_systeminfo(systeminfo) data["package_tags"] = ["x86_64", "msi"] data["arch"] = "x86_64" agent = r'& "C:\Program Files\Cfengine\bin\cf-agent.exe"' data["agent"] = agent data["agent_version"] = parse_version( ssh_cmd(connection, '{} -V'.format(agent))) else: data["os"] = "unix" data["uname"] = ssh_cmd(connection, "uname") data["arch"] = ssh_cmd(connection, "uname -m") data["os_release"] = os_release( ssh_cmd(connection, "cat /etc/os-release")) data["agent_location"] = ssh_cmd(connection, "which cf-agent") data["policy_server"] = ssh_cmd(connection, "cat /var/cfengine/policy_server.dat") agent = r'/var/cfengine/bin/cf-agent' data["agent"] = agent data["agent_version"] = parse_version( ssh_cmd(connection, "{} --version".format(agent))) data["bin"] = {} for bin in ["dpkg", "rpm", "yum", "apt", "pkg"]: path = ssh_cmd(connection, "which {}".format(bin)) if path: data["bin"][bin] = path log.debug("JSON data from host info: \n" + pretty(data)) return data
def connect(host, users=None): log.debug("Connecting to '{}'".format(host)) if "@" in host: parts = host.split("@") assert len(parts) == 2 host = parts[1] if not users: users = [parts[0]] if not users: users = ["ubuntu", "ec2-user", "centos", "vagrant", "root"] for user in users: try: c = fabric.Connection(host=host, user=user) c.ssh_user = user c.ssh_host = host c.run("whoami", hide=True) return c except AuthenticationException: continue sys.exit("Could not ssh into '{}'".format(host))
def install(hub, clients, *, bootstrap=None, package=None, hub_package=None, client_package=None, version=None, demo=False): assert hub or clients assert not (hub and clients and package) # These assertions are checked in main.py if not hub_package: hub_package = package if not client_package: client_package = package if bootstrap: save_file(os.path.join(cf_remote_dir(), "policy_server.dat"), bootstrap + "\n") if hub: log.debug("Installing hub package on '{}'".format(hub)) install_host(hub, hub=True, package=hub_package, bootstrap=bootstrap, version=version, demo=demo) for host in (clients or []): log.debug("Installing client package on '{}'".format(host)) install_host(host, hub=False, package=client_package, bootstrap=bootstrap, version=version, demo=demo) if demo and hub: print( "Your demo hub is ready: https://{}/ (Username: admin, Password: password)" .format(hub))
def download_package(url, path=None): if not path: filename = os.path.basename(url) directory = cf_remote_packages_dir() mkdir(directory) path = os.path.join(directory, filename) # Use "ab" to prevent truncation of the file in case it is already being # downloaded by a different thread. with open(path, "ab") as f: # Get an exclusive lock. If the file size is != 0 then it's already # downloaded, otherwise we download. fcntl.flock(f.fileno(), fcntl.LOCK_EX) st = os.stat(path) if st.st_size != 0: log.debug("Package '{}' already downloaded".format(path)) else: print("Downloading package: '{}'".format(path)) f.write(urllib.request.urlopen(url).read()) f.flush() fcntl.flock(f.fileno(), fcntl.LOCK_UN) return path
def resolve_hosts(string, single=False, private_ips=False): log.debug("resolving hosts from '{}'".format(string)) if is_file_string(string): names = expand_list_from_file(string) else: names = string.split(",") ret = [] for name in names: if is_in_cloud_state(name): hosts = get_cloud_hosts(name, private_ips) ret.extend(hosts) log.debug("found in cloud, adding '{}'".format(hosts)) else: ret.append(name) if single: if len(ret) != 1: user_error("'{}' must contain exactly 1 hostname or IP".format(string)) return ret[0] else: return ret
def ssh_sudo(connection, cmd, errors=False): assert connection try: log.debug("Running(sudo) over SSH: '{}'".format(cmd)) result = connection.run('sudo bash -c "{}"'.format(cmd.replace('"', r'\"')), hide=True) output = result.stdout.strip("\n") log.debug("'{}' -> '{}'".format(cmd, output)) return output except UnexpectedExit as e: msg = "Sudo command unexpectedly exited: '{}'".format(cmd) if errors: print(e) log.error(msg) else: log.debug(str(e)) log.debug(msg) return None
def ssh_cmd(connection, cmd, errors=False): assert connection try: log.debug("Running over SSH: '{}'".format(cmd)) result = connection.run(cmd, hide=True) output = result.stdout.strip("\n") log.debug("'{}' -> '{}'".format(cmd, output)) return output except UnexpectedExit as e: msg = "Non-sudo command unexpectedly exited: '{}'".format(cmd) if errors: print(e) log.error(msg) else: log.debug(str(e)) log.debug(msg) return None
def ssh_cmd(connection, cmd, errors=False): assert connection try: log.debug("Running over SSH: '{}'".format(cmd)) result = connection.run(cmd, hide=True) output = result.stdout.replace("\r\n", "\n").strip("\n") log.debug("'{}' -> '{}'".format(cmd, output)) return output except UnexpectedExit as e: msg = "Non-sudo command unexpectedly exited: '{}'".format(cmd) if errors: print(e) log.error(msg) else: log.debug(str(e)) log.debug(msg) return None
def deploy(hubs, masterfiles): if masterfiles.startswith(("http://", "https://")): urls = [masterfiles] paths = _download_urls(urls) assert len(paths) == 1 masterfiles = paths[0] log.debug(f"Deploying downloaded: {masterfiles}") else: masterfiles = os.path.abspath(os.path.expanduser(masterfiles)) log.debug(f"Deploy path expanded to: {masterfiles}") masterfiles = masterfiles.rstrip("/") if os.path.isfile(masterfiles): return deploy_tarball(hubs, masterfiles) if not os.path.isdir(masterfiles): log.error(f"'{masterfiles}' must be a directory") return 1 directory = masterfiles if not directory.endswith("/masterfiles"): log.error( "The masterfiles directory to deploy must be called 'masterfiles'") return 1 if os.path.isfile(f"{directory}/autogen.sh"): os.system(f"bash -c 'cd {directory} && ./autogen.sh 1>/dev/null 2>&1'") if not os.path.isfile(f"{directory}/promises.cf"): log.error( f"The autogen.sh script did not produce promises.cf in '{directory}'" ) return 1 elif os.path.isfile(f"{directory}/configure"): os.system(f"bash -c 'cd {directory} && ./configure 1>/dev/null 2>&1'") if not os.path.isfile(f"{directory}/promises.cf"): log.error( f"The configure script did not produce promises.cf in '{directory}'" ) return 1 else: log.debug( "No autogen.sh / configure found, assuming ready to install directory" ) if not os.path.isfile(f"{directory}/promises.cf"): log.error(f"No promises.cf in '{directory}'") return 1 assert (not cf_remote_dir().endswith("/")) tarball = cf_remote_dir() + "/masterfiles.tgz" above = directory[0:-len("/masterfiles")] os.system(f"rm -rf {tarball}") os.system(f"tar -czf {tarball} -C {above} masterfiles") return deploy_tarball(hubs, tarball)
def ssh_sudo(connection, cmd, errors=False): assert connection try: log.debug(f"Running(sudo) over SSH: '{cmd}'") escaped = cmd.replace('"', r"\"") sudo_cmd = f'sudo bash -c "{escaped}"' result = connection.run(sudo_cmd, hide=True, pty=True) output = result.stdout.strip("\n") log.debug(f"'{cmd}' -> '{output}'") return output except UnexpectedExit as e: msg = f"Sudo command unexpectedly exited: '{cmd}'" if errors: print(e) log.error(msg) else: log.debug(str(e)) log.debug(msg) return None
def filter_artifacts(artifacts, tags, extension): if extension: artifacts = [a for a in artifacts if a.extension == extension] log.debug("Looking for tags: {}".format(tags)) log.debug("In artifacts: {}".format(artifacts)) for tag in tags or []: tag = canonify(tag) new_artifacts = [a for a in artifacts if tag in a.tags] # Have to force evaluation using list comprehension, # since we are overwriting artifacts if len(new_artifacts) > 0: artifacts = new_artifacts log.debug("Found artifacts: {}".format(artifacts)) return artifacts
def connect(host, users=None): log.debug(f"Connecting to '{host}'") log.debug(f"users= '{users}'") if "@" in host: parts = host.split("@") assert len(parts) == 2 host = parts[1] if not users: users = [parts[0]] if not users: users = [ "Administrator", "admin", "ubuntu", "ec2-user", "centos", "vagrant", "root", ] # Similar to ssh, try own username first, # some systems will lock us out if we have too many failed attempts. if whoami() not in users: users = [whoami()] + users for user in users: try: log.debug(f"Attempting ssh: {user}@{host}") connect_kwargs = {} key = os.getenv("CF_REMOTE_SSH_KEY") if key: connect_kwargs["key_filename"] = os.path.expanduser(key) c = fabric.Connection(host=host, user=user, connect_kwargs=connect_kwargs) c.ssh_user = user c.ssh_host = host c.run("whoami", hide=True) return c except AuthenticationException: continue sys.exit(f"Could not ssh into '{host}'")
def mkdir(path): if not os.path.exists(path): log.info("Creating directory: '{}'".format(path)) os.makedirs(path) else: log.debug("Directory already exists: '{}'".format(path))
def get_info(host, *, users=None, connection=None): log.debug("Getting info about '{}'".format(host)) user, host = connection.ssh_user, connection.ssh_host data = OrderedDict() data["ssh_user"] = user data["ssh_host"] = host data["ssh"] = "{}@{}".format(user, host) data["whoami"] = ssh_cmd(connection, "whoami") systeminfo = ssh_cmd(connection, "systeminfo") if systeminfo: data["os"] = "windows" data["systeminfo"] = parse_systeminfo(systeminfo) data["package_tags"] = ["x86_64", "msi"] data["arch"] = "x86_64" agent = r"& 'C:\Program Files\Cfengine\bin\cf-agent.exe'" data["agent"] = agent version_cmd = powershell('{} -V'.format(agent)) data["agent_version"] = parse_version(ssh_cmd(connection, version_cmd)) else: data["os"] = "unix" data["uname"] = ssh_cmd(connection, "uname") data["arch"] = ssh_cmd(connection, "uname -m") data["os_release"] = os_release( ssh_cmd(connection, "cat /etc/os-release")) tags = [] if data["os_release"]: distro = data["os_release"]["ID"] major = data["os_release"]["VERSION_ID"].split(".")[0] platform_tag = distro + major # Add tags with version number first, to filter by them first: tags.append(platform_tag) # Example: ubuntu16 if distro == "centos": tags.append("el" + major) # Then add more generic tags (lower priority): tags.append(distro) # Example: ubuntu if distro == "centos": tags.append("rhel") tags.append("el") else: redhat_release = ssh_cmd(connection, "cat /etc/redhat-release") if redhat_release: # Examples: # CentOS release 6.10 (Final) # Red Hat Enterprise Linux release 8.0 (Ootpa) before, after = redhat_release.split(" release ") distro = "rhel" if before.lower().startswith("centos"): distro = "centos" major = after.split(".")[0] tags.append(distro + major) tags.append("el" + major) if "rhel" not in tags: tags.append("rhel" + major) tags.append(distro) if "rhel" not in tags: tags.append("rhel") tags.append("el") data["package_tags"] = tags data["agent_location"] = ssh_cmd(connection, "which cf-agent") data["policy_server"] = ssh_cmd(connection, "cat /var/cfengine/policy_server.dat") agent = r'/var/cfengine/bin/cf-agent' data["agent"] = agent data["agent_version"] = parse_version( ssh_cmd(connection, "{} --version".format(agent))) data["bin"] = {} for bin in ["dpkg", "rpm", "yum", "apt", "pkg"]: path = ssh_cmd(connection, "which {}".format(bin)) if path: data["bin"][bin] = path log.debug("JSON data from host info: \n" + pretty(data)) return data
def info(hosts, users=None): assert hosts log.debug("hosts='{}'".format(hosts)) for host in hosts: data = get_info(host, users=users) print_info(data)
def install(hubs, clients, *, bootstrap=None, package=None, hub_package=None, client_package=None, version=None, demo=False, call_collect=False, edition=None, remote_download=False, trust_keys=None): assert hubs or clients assert not (hubs and clients and package) assert (trust_keys is None) or hasattr(trust_keys, "__iter__") # These assertions are checked/ensured in main.py # If there are URLs in any of the package strings and remote_download is FALSE, download and replace with path: packages = (package, hub_package, client_package) if remote_download: package, hub_package, client_package = _verify_package_urls(packages) else: package, hub_package, client_package = _download_urls(packages) # If any of these are folders, transform them to lists of the files inside those folders: package = _maybe_packages_in_folder(package) hub_package = _maybe_packages_in_folder(hub_package) client_package = _maybe_packages_in_folder(client_package) # If --hub-package or --client-pacakge are not specified, use --package argument: if not hub_package: hub_package = package if not client_package: client_package = package if bootstrap: if type(bootstrap) is str: bootstrap = [bootstrap] save_file(os.path.join(cf_remote_dir(), "policy_server.dat"), "\n".join(bootstrap + [""])) hub_jobs = [] if hubs: show_host_info = (len(hubs) == 1) if type(hubs) is str: hubs = [hubs] for index, hub in enumerate(hubs): log.debug("Installing {} hub package on '{}'".format(edition, hub)) hub_jobs.append( HostInstaller( hub, hub=True, packages=hub_package, bootstrap=bootstrap[index % len(bootstrap)] if bootstrap else None, version=version, demo=demo, call_collect=call_collect, edition=edition, show_info=show_host_info, remote_download=remote_download, trust_keys=trust_keys)) errors = 0 if hub_jobs: with Pool(len(hub_jobs)) as hubs_install_pool: hubs_install_pool.map(lambda job: job.run(), hub_jobs) errors = sum(job.errors for job in hub_jobs) if errors > 0: s = "s" if errors > 1 else "" log.error( f"{errors} error{s} encountered while installing hub packages, aborting..." ) return errors client_jobs = [] show_host_info = (clients and (len(clients) == 1)) for index, host in enumerate(clients or []): log.debug("Installing {} client package on '{}'".format(edition, host)) client_jobs.append( HostInstaller( host, hub=False, packages=client_package, bootstrap=bootstrap[index % len(bootstrap)] if bootstrap else None, version=version, demo=demo, edition=edition, show_info=show_host_info, remote_download=remote_download, trust_keys=trust_keys)) if client_jobs: with Pool(len(client_jobs)) as clients_install_pool: clients_install_pool.map(lambda job: job.run(), client_jobs) errors += sum(job.errors for job in client_jobs) if demo and hubs: for hub in hubs: print( "Your demo hub is ready: https://{}/ (Username: admin, Password: password)" .format(strip_user(hub))) if errors > 0: s = "s" if errors > 1 else "" log.error( f"{errors} error{s} encountered while installing client packages") return errors