def complete_get(ctx, args, incomplete): """ Pull out objects args from Click context and return completions. """ lg.remove() try: cfg = config.get() project = dget(cfg, ["project"]) namespace = ctx.params.get("namespace") or project objects = ctx.params.get("objects") if "/" in incomplete: # We're completing something like `oc get pod/<tab>`. in_rtype = incomplete.split("/")[0] in_rname = incomplete.split("/")[1] names = get_all_resource_names({in_rtype: []}, namespace) return [ in_rtype + "/" + n for n in names if n.startswith(in_rname) and in_rtype + "/" + n not in objects ] if "," in incomplete or [o for o in objects if "," in o]: # This is a NOP like oc return [] slash_mode = False comma_mode = False if objects: try: parsed_objects = parse_get_args(objects) except ParseError: return [] else: slash_mode = all("/" in o for o in objects) comma_mode = all("," in o for o in objects) # First arg after get, autocomplete type # or autocompleting after existing slash-notation arg if not objects or slash_mode: if slash_mode: add_slash = "/" else: add_slash = "" sugg = _suggest_type(incomplete) return [s + add_slash for s in sugg] if not slash_mode and not comma_mode and len(parsed_objects) > 0: # Autocomplete resource names based on the type: oc get pod mypod1 mypod2 names = get_all_resource_names(parsed_objects, namespace) return [ n for n in names if n.startswith(incomplete) and n not in objects ] # Catch all return [] except Exception: # Swallow any exception return []
def complete_containers(ctx, args, incomplete): """ Callback for container name (within a pod and ns) autocompletion :return: List of matching container names or empty list """ lg.remove() cfg = config.get() c_paths = dget(cfg, ["paths"]) c_project = dget(cfg, ["project"]) if ( len(ctx.args) != 1 ): # If there's no pod specified yet, can't autocomplete a container name return [] ns = ctx.params.get("namespace") or c_project pod = ctx.args[0] container_listing = [] for path in c_paths: container_dir = os.path.join(path, "namespaces", ns, "pods", pod) if os.path.isdir(container_dir): containers = os.listdir(container_dir) if containers: container_listing.extend(containers) suggestions = [ c for c in container_listing if incomplete in c and not c.endswith(".yaml") ] # skip .yaml files return suggestions
def cmd(name): cfg = config.get() c_paths = dget(cfg, ["paths"]) c_project = dget(cfg, ["project"]) # print current project if name is None: if c_project is None: print("No project selected") else: print("Using project {}".format(c_project)) # set current project else: if name == c_project: print("Already using project {}".format(c_project)) else: select_project = None for path in c_paths: projs_in_path = locate_project(path, tell="names") if name in projs_in_path: select_project = name break if select_project: print("Now using project {}".format(select_project)) config.save(project=name) else: lg.error("Project {} not found in {} must-gather paths".format( name, len(c_paths)))
def show_mg_info(cfile=None): """ Shows the info of current selected must-gather(s) """ lg.debug("FUNC_INIT: {}".format(locals())) cfg = config.get(cfile=cfile) lg.debug("Loaded config file: {}".format(cfg)) paths = cfg["paths"] project = cfg["project"] out = lg.opt(colors=True).success if "cwd" in cfg and cfg["cwd"]: out("<e>-=[CWD Mode]=-</>") out("") if len(paths) > 1: out("<e>-=[MultiDir Mode]=-</>") out("") out("<e>Selected must-gather paths:</>") i = 1 for path in paths: out(" <e>[{}]</> {}".format(i, _hide_hash(path))) i = i + 1 _show_info(path) out("") out("<e>Current Project:</> {}".format(project)) elif len(paths) == 1: out("<e>Selected must-gather:</> {}".format(_hide_hash(paths[0]))) _show_info(paths[0]) out(" <e>Current Project:</> {}".format(project))
def test_config_save_get(tmpdir): omg_config = tmpdir.join(".omgconfig") config.save(paths=["/test/path"], project="testproject", cfile=omg_config) cfg = config.get(cfile=omg_config) assert cfg["paths"] == ["/test/path"] assert cfg["project"] == "testproject"
def mc_extract(m): cfg = config.get() paths = cfg["paths"] if m: mc_names = list(m) else: mc_names = [] all_mcs = get_all_resources({"mc": mc_names}) # {1: {"mc": [...]}, 2: {"mc": [...] }} for i, mc_res in all_mcs.items(): mg_path = paths[i - 1] emc_path = os.path.join(mg_path, "extracted-machine-configs") # {"mc": [{"res": {}, "yfile_ts": "..."}, ... ]} for _, mc_data in mc_res.items(): if mc_data: try: os.makedirs(emc_path, exist_ok=True) except PermissionError as e: lg.warning(e) continue for mc in mc_data: mc_rd = mc["res"] _write_mc(emc_path, mc_rd)
def _get_all_projects(): cfg = config.get() c_paths = dget(cfg, ["paths"]) if c_paths: projects = [] for path in c_paths: projects.extend(locate_project(path, tell="names")) return projects return []
def get_all_resources(parsed_objects, ns=None): """Get resources from all paths Args: parsed_objects (dict): Parsed object ns (string): Namespace/project Returns: list: for each r_type, list of resources from each path (list of dict of list) For example: [ # resources_from_path1 { 'pod': [ res1, res2 ... ], 'svc': [ res1, ... ], ... }, # resources_from_path2 ... ] """ lg.debug("FUNC_INIT: {}".format(locals())) cfg = config.get() paths = cfg["paths"] # Resources dicts from selected paths # resd_from_paths = [] resd_from_paths = {} i = 0 for path in paths: i += 1 resd = {} for r_type in parsed_objects: r_names = parsed_objects[r_type] try: res = load_res(path, r_type, r_names, ns) except NameSpaceRequired as e: lg.error(e) raise SystemExit(1) except UnkownResourceType: lg.error("Unknow resource type: {}".format(r_type)) raise SystemExit(1) else: resd[r_type] = res # resd_from_paths.append(resd) resd_from_paths[i] = resd return resd_from_paths
def o_raw(resd_from_paths, output): lg.debug("FUNC_INIT: {}".format(locals())) cfg = config.get() paths = cfg["paths"] for i, path_resd in resd_from_paths.items(): all_res = [] for r_type in path_resd: res = path_resd[r_type] if res: all_res.extend(res) if all_res: if len(all_res) == 1: out_res = dget(all_res[0], ["res"]) elif len(all_res) > 1: out_res = { "apiVersion": "v1", "kind": "List", "items": [dget(res, ["res"]) for res in all_res] } else: continue if output == "yaml": print(yaml.dump(out_res)) elif output == "json": print(json.dumps(out_res)) elif output == "name": for res in all_res: group = dget(res, ["rdef", "group"]) singular = dget(res, ["rdef", "singular"]) if group == "core": out_type = singular else: out_type = str(singular) + "." + str(group) name = dget(res, ["res", "metadata", "name"]) print( str(out_type) + "/" + str(name) ) if (len(paths) > 1): lg.opt(colors=True).success("^^^<e>[{}]</>^^^\n".format(i))
def complete_pods(ctx, args, incomplete): """ Callback for pod name (within a ns) autocompletion :return: List of matching pod names or empty list. """ lg.remove() cfg = config.get() c_paths = dget(cfg, ["paths"]) c_project = dget(cfg, ["project"]) ns = ctx.params.get("namespace") or c_project suggestions = [] for path in c_paths: pod_dir = os.path.join(path, "namespaces", ns, "pods") if os.path.isdir(pod_dir): pod_listing = os.listdir(pod_dir) if pod_listing: suggestions.extend([pod for pod in pod_listing if incomplete in pod]) return suggestions
def cmd(objects, output, show_labels): lg.debug("FUNC_INIT: {}".format(locals())) # Parse objects try: parsed_objects = parse_get_args(objects) except ParseError as e: lg.error(e) return 2 lg.debug("parsed_objects: {}".format(parsed_objects)) # Set namespace ns = dget(config.get(), ["project"]) lg.debug("Namespace resolved to: {}".format(ns)) # Collect resources all_res_d = get_all_resources(parsed_objects, ns) # No resource type found e.g: # all_res_d == [{}, {}, ..<paths>.. ] if not any(all_res_d): lg.error("Unkown resource type") return 2 # Resource type found, but no resources found e.g: # [{'pods': []}, {'pods': []}, ..<paths>..] if not any([ bool(all_res_d[i][rtype]) for i in all_res_d for rtype in all_res_d[i] ]): print("No resources found") return 2 # Pass the parsed object to respective output function if output in ["yaml", "json", "name"]: o_raw(all_res_d, output) elif output is None or output == "wide": o_table(all_res_d, ns, output, show_labels) else: lg.error("Unknow output type: {}".format(output))
def o_table(resd_from_paths, ns, output, show_labels): """Handles table output. Both simple (without -o) and wide (-o wide) Args: parsed_objects (dict): Contains parsed dictionary of objects to get. This dict is the one we get from parse.py module. ns (str): Namespace is needed if we are showing output for all-namespaces. output (str): This is passed from click (-o). We need to know if the output is simple (-o not set) or wide (-o wide). show_labels (bool): This is passed from click (--show-labels) """ lg.debug("FUNC_INIT: {}".format(locals())) cfg = config.get() paths = cfg["paths"] tablefmt = getenv("OMG_TABLE_FMT") or "plain" show_type = False if max([len(resd_from_paths[i]) for i in resd_from_paths]) > 1: show_type = True for i, path_resd in resd_from_paths.items(): table_all_res = None for r_type in path_resd: res = path_resd[r_type] if res: res_table = build_table(res, ns, output, show_type, show_labels) if table_all_res: table_all_res += [[""]] + res_table else: table_all_res = res_table if table_all_res: print(tabulate(table_all_res, tablefmt=tablefmt)) if (len(paths) > 1): lg.opt(colors=True).success("^^^<e>[{}]</>^^^\n".format(i))
def cmd(etcdctl_args, output): cfg = config.get() mg_paths = cfg["paths"] if output is None: output = "table" command = "_".join(etcdctl_args) etcd_file = os.path.join( "etcd_info", "{}.json".format(command)) lg.debug("etcd_file: {}".format(etcd_file)) etcd_cmd_paths = {} i = 1 for p in mg_paths: ceph_cmd_path = os.path.join(p, etcd_file) if os.path.isfile(ceph_cmd_path): lg.info("Command output file found: {}".format(ceph_cmd_path)) etcd_cmd_paths[i] = ceph_cmd_path i += 1 if etcd_cmd_paths: for i, cp in etcd_cmd_paths.items(): try: j_data = load_json(cp) table_mod = import_module( "omg.components.etcdctl.output.{}".format(command)) table_mod.etcdctl_out(j_data, output) except Exception as e: lg.warning("Error generating output for {}: {}".format(command, e)) lg.success("\nHere is the raw file ({}):".format(cp)) with open(cp, "r") as cf: print(cf.read()) if len(mg_paths) > 1: lg.opt(colors=True).success("^^^<e>[{}]</>^^^\n".format(i)) else: suggestions = {} i = 1 for p in mg_paths: try: files = os.listdir(os.path.join(p, "etcd_info")) file_match = "_".join(etcdctl_args) sugg = [] sugg.extend([ "omg etcdctl " + f.replace("_", " ").replace(".json", "") for f in files if f.startswith(file_match)]) if sugg: suggestions[i] = sugg except Exception: pass i += 1 if suggestions: lg.success("\nNote: Output of following commands are available:\n") for i, sugg in suggestions.items(): lg.success("\n".join(sugg)) lg.opt(colors=True).success("^^^<e>[{}]</>^^^\n".format(i)) else: lg.error( "Command output not found in any of the" " {} must-gather paths".format(len(mg_paths)) )
def cmd(resource, container, previous): lg.debug("FUNC_INIT: {}".format(locals())) cfg = config.get() paths = dget(cfg, ["paths"]) ns = dget(cfg, ["project"]) if not paths: lg.error("No must-gather selected") raise SystemExit(1) if ns == "_all": lg.error("All Namespaces is not supported with `omg log ...` ") raise SystemExit(1) elif ns is None: lg.error("No namespace/project selected") raise SystemExit(1) if "/" in resource: r_type = resource.split("/")[0] pod = resource.split("/")[1] if r_type not in ["pod", "pods"]: lg.error("Can not print logs of type:", r_type) raise SystemExit(1) else: pod = resource if not ns: lg.error("Namespace/Project not set") raise SystemExit(1) log_files = [] for path in paths: proj_path = os.path.join(path, "namespaces", ns) if not os.path.isdir(proj_path): continue pod_dir = os.path.join(proj_path, "pods", pod) if not os.path.isdir(pod_dir): # lg.warning("Pod directory not found: {}".format(pod_dir)) continue con_dirs = [ c for c in os.listdir(pod_dir) if os.path.isdir(os.path.join(pod_dir, c)) ] if not con_dirs: lg.warning( "No container directory not found in {}".format(pod_dir)) continue elif len(con_dirs) == 1: con_dir = con_dirs[0] if container and container != con_dir: lg.warning("Container directory {} not found in {}".format( container, pod_dir)) continue elif len(con_dirs) > 1: if container is None: lg.error("This pod has more than one containers:" + str(con_dirs) + "\n" "Use -c/--container to specify the container") raise SystemExit(1) elif container not in con_dirs: lg.warning("Container directory {} not found in {}".format( container, pod_dir)) continue else: con_dir = container if previous: log_files.append( os.path.join(pod_dir, con_dir, con_dir, "logs", "previous.log")) else: log_files.append( os.path.join(pod_dir, con_dir, con_dir, "logs", "current.log")) if not log_files: lg.error("No log files found") for logfile in log_files: if not os.path.isfile(logfile): lg.warning("Log file not found: {}".format(logfile)) else: lg.info(logfile) with open(logfile, "r") as lf: print(lf.read()) if len(log_files) > 1: print("") print("~~~") print("")
def cmd(ceph_args, output, com): cfg = config.get() mg_paths = cfg["paths"] if output in ["json", "json-pretty"]: commands_dir = "must_gather_commands_json_output" json_add = "_--format_json-pretty" else: commands_dir = "must_gather_commands" json_add = "" file_name = "{}_{}{}".format(com, "_".join(ceph_args), json_add) if file_name.startswith("ceph_config_show_"): file_name = file_name.replace("ceph_config_show_", "config_") ceph_file = os.path.join("ceph", commands_dir, file_name) lg.debug("ceph_file: {}".format(ceph_file)) ceph_cmd_paths = {} i = 1 for p in mg_paths: ceph_cmd_path = os.path.join(p, ceph_file) if os.path.isfile(ceph_cmd_path): lg.info("Command output file found: {}".format(ceph_cmd_path)) ceph_cmd_paths[i] = ceph_cmd_path i += 1 if ceph_cmd_paths: for i, cp in ceph_cmd_paths.items(): with open(cp, "r") as lf: print(lf.read()) if len(mg_paths) > 1: lg.opt(colors=True).success("^^^<e>[{}]</>^^^\n".format(i)) else: suggestions = {} i = 1 for p in mg_paths: try: files = os.listdir( os.path.join(p, "ceph", "must_gather_commands")) file_match = "{}_{}".format(com, "_".join(ceph_args)) sugg = [] sugg.extend([ "omg " + f.replace("_", " ") for f in files if f.startswith(file_match) ]) sugg.extend([ "omg ceph config show {}".format(f.replace("config_", "")) for f in files if f.startswith("config_") ]) if sugg: suggestions[i] = sugg except Exception as e: lg.debug(e) pass i += 1 if suggestions: lg.success("\nNote: Output of following commands are available:\n") for i, sugg in suggestions.items(): lg.success("\n".join(sugg)) lg.opt(colors=True).success("^^^<e>[{}]</>^^^\n".format(i)) else: lg.error("Command output not found in any of the" " {} must-gather paths".format(len(mg_paths)))
def test_config_get_no_config(caplog): with pytest.raises(SystemExit): config.get("/non/exitent/file") assert "You have not selected a must-gather" in caplog.text