def test_compile_config_dot_square(self, fname_param_config): """Folder location. This is tricky because it depends on whether or not the user specified a config file and whether or not he also specified the "--folder" flag. """ _, param, _ = fname_param_config param.configfile = None assert Filepath(".square.yaml").exists() # User specified "--no-config": use `.square.yaml` if it exists. param.no_config = False cfg, err = main.compile_config(param) assert not err and cfg.kubecontext == "dot-square" # User did not specify "--no-config" and`.square.yaml` exists too. This must # still use the default configuration. param.no_config = True cfg, err = main.compile_config(param) assert not err and cfg.kubecontext is None # User did not specify "--no-config": use `.square.yaml` if it exists. Filepath(".square.yaml").rename(".square.yaml.bak") assert not Filepath(".square.yaml").exists() param.no_config = False cfg, err = main.compile_config(param) assert not err and cfg.kubecontext is None
def test_compile_config_kinds_clear_existing(self, fname_param_config, tmp_path): """Empty list on command line must clear the option.""" _, param, _ = fname_param_config # Use the test configuration for this test (it has non-zero labels). param.configfile = str(TEST_CONFIG_FNAME) # User did not provide `--labels` or `--namespace` option. param.labels = None param.namespaces = None # Convert the parsed command line into a `Config` structure. cfg, err = main.compile_config(param) assert not err # The defaults must have taken over because the user did not specify # new labels etc. assert cfg.selectors.labels == ["app=square"] assert cfg.selectors.namespaces == ["default", "kube-system"] assert cfg.groupby == GroupBy(label="app", order=["ns", "label", "kind"]) # Pretend the user specified and empty `--labels`, `--groupby` and # `--namespaces`. This must clear the respective entries. param.labels = [] param.groupby = [] param.namespaces = [] cfg, err = main.compile_config(param) assert not err # This time, the user supplied arguments must have cleared the # respective fields. assert cfg.selectors.labels == [] assert cfg.selectors.namespaces == [] assert cfg.groupby == GroupBy()
def test_compile_config_default_folder(self, fname_param_config): """Folder location. This is tricky because it depends on whether or not the user specified a config file and whether or not he also specified the "--folder" flag. """ _, param, _ = fname_param_config # Config file and no "--folder": config file entry wins. param.configfile = str(TEST_CONFIG_FNAME) param.folder = None cfg, err = main.compile_config(param) assert not err assert cfg.folder == TEST_CONFIG_FNAME.parent.absolute() / "some/path" # Config file and "--folder some/where": `--folder` wins. param.configfile = str(TEST_CONFIG_FNAME) param.folder = "some/where" cfg, err = main.compile_config(param) assert not err and cfg.folder == Filepath("some/where") # No config file and no "--folder": manifest folder must be CWD. param.configfile = None param.folder = None cfg, err = main.compile_config(param) assert not err and cfg.folder == Filepath.cwd() / "foo" / "bar" # No config file and "--folder some/where": must point to `some/where`. param.configfile = None param.folder = "some/where" cfg, err = main.compile_config(param) assert not err and cfg.folder == Filepath("some/where")
def test_parse_commandline_get_grouping(self, tmp_path): """GET supports file hierarchy options.""" kubeconfig = tmp_path / "kubeconfig.yaml" kubeconfig.write_text("") base_cmd = ("square.py", "get", "all", "--kubeconfig", str(tmp_path / "kubeconfig.yaml")) # ---------------------------------------------------------------------- # Default file system hierarchy. # ---------------------------------------------------------------------- with mock.patch("sys.argv", base_cmd): param = main.parse_commandline_args() assert param.groupby is None # ---------------------------------------------------------------------- # User defined file system hierarchy. # ---------------------------------------------------------------------- cmd = ("--groupby", "ns", "kind") with mock.patch("sys.argv", base_cmd + cmd): param = main.parse_commandline_args() assert param.groupby == ["ns", "kind"] cfg, err = main.compile_config(param) assert not err assert cfg.groupby == GroupBy(label="", order=["ns", "kind"]) # ---------------------------------------------------------------------- # Include a label into the hierarchy and use "ns" twice. # ---------------------------------------------------------------------- cmd = ("--groupby", "ns", "label=foo", "ns") with mock.patch("sys.argv", base_cmd + cmd): param = main.parse_commandline_args() assert param.groupby == ["ns", "label=foo", "ns"] cfg, err = main.compile_config(param) assert not err assert cfg.groupby == GroupBy(label="foo", order=["ns", "label", "ns"]) # ---------------------------------------------------------------------- # The label resource, unlike "ns" or "kind", can only be specified # at most once. # ---------------------------------------------------------------------- cmd = ("--groupby", "ns", "label=foo", "label=bar") with mock.patch("sys.argv", base_cmd + cmd): param = main.parse_commandline_args() assert param.groupby == ["ns", "label=foo", "label=bar"] expected = Config( folder=Filepath(""), kubeconfig=Filepath(""), kubecontext=None, selectors=Selectors(set(), [], []), groupby=GroupBy("", []), priorities=[], ) assert main.compile_config(param) == (expected, True)
def test_compile_config_kinds(self, fname_param_config): """Parse resource kinds.""" # Specify `Service` twice. _, param, ref_config = fname_param_config param.kinds = ["Service", "Deploy", "Service"] cfg, err = main.compile_config(param) assert not err assert cfg.selectors.kinds == {"Service", "Deploy"} # An empty resource list must use the defaults. param.kinds = None cfg, err = main.compile_config(param) assert not err assert cfg.selectors.kinds == ref_config.selectors.kinds
def test_compile_hierarchy_ok(self, fname_param_config): """Parse the `--groupby` argument.""" _, param, _ = fname_param_config err_resp = Config( folder=Filepath(""), kubeconfig=Filepath(""), kubecontext=None, selectors=Selectors(set(), [], []), groupby=GroupBy("", []), priorities=[], ), True # ---------------------------------------------------------------------- # Default hierarchy. # ---------------------------------------------------------------------- for cmd in ["apply", "get", "plan"]: param.parser = cmd ret, err = main.compile_config(param) assert not err assert ret.groupby == GroupBy(label="app", order=["ns", "label", "kind"]) del cmd, ret, err # ---------------------------------------------------------------------- # User defined hierarchy with a valid label. # ---------------------------------------------------------------------- param.parser = "get" param.groupby = ("ns", "kind", "label=app", "ns") ret, err = main.compile_config(param) assert not err assert ret.groupby == GroupBy(label="app", order=["ns", "kind", "label", "ns"]) # ---------------------------------------------------------------------- # User defined hierarchy with invalid labels. # ---------------------------------------------------------------------- param.parser = "get" invalid_labels = ["label", "label=", "label=foo=bar"] for label in invalid_labels: param.groupby = ("ns", "kind", label, "ns") assert main.compile_config(param) == err_resp # ---------------------------------------------------------------------- # User defined hierarchy with invalid resource types. # ---------------------------------------------------------------------- param.parser = "get" param.groupby = ("ns", "unknown") assert main.compile_config(param) == err_resp
def test_compile_config_missing_k8s_credentials(self, fname_param_config): """Gracefully abort if kubeconfig does not exist""" _, param, _ = fname_param_config param.kubeconfig += "does-not-exist" assert main.compile_config(param) == ( Config( folder=Filepath(""), kubeconfig=Filepath(""), kubecontext=None, selectors=Selectors(set(), [], []), groupby=GroupBy("", []), priorities=[], ), True)
def test_compile_config_basic(self, fname_param_config): """Verify that our config and command line args fixtures match.""" _, param, ref_config = fname_param_config # Convert the parsed command line `param` to a `Config` structure. out, err = main.compile_config(param) assert not err # The parsed `Config` must match our `ref_config` fixture except for # the folder and kubeconfig because the fixture explicitly overrode # those to point to a temporary location. out.folder, out.kubeconfig = ref_config.folder, ref_config.kubeconfig assert out == ref_config
def test_compile_config_missing_config_file(self, fname_param_config): """Abort if the config file is missing or invalid.""" _, param, _ = fname_param_config param.configfile = Filepath("/does/not/exist.yaml") _, err = main.compile_config(param) assert err
def test_compile_config_kubeconfig(self, fname_param_config, tmp_path): """Which kubeconfig to use. The tricky part here is when to use the KUBECONFIG env var. With Square, it will *only* try to use KUBECONFIG if neither `--config` nor `--kubeconfig` was specified. """ # Unpack the fixture: path to valid ".square.yaml", command line # parameters and an already parsed `Config`. fname_config, param, ref_config = fname_param_config ref_data = yaml.safe_load(fname_config.read_text()) cwd = fname_config.parent fname_config_dotsquare = fname_config fname_config_custom = cwd / "customconfig.yaml" fname_kubeconfig_dotsquare = cwd / "kubeconfig-dotsquare" fname_kubeconfig_custom = cwd / "kubeconfig-custom" fname_kubeconfig_commandline = cwd / "kubeconfig-commandline" fname_kubeconfig_envvar = cwd / "kubeconfig-envvar" def reset(): """Reset the files in the temporary folder.""" data = copy.deepcopy(ref_data) # Write ".square". data["kubeconfig"] = str(fname_kubeconfig_dotsquare) fname_config_dotsquare.write_text(yaml.dump(data)) # Write "customconfig.yaml" data["kubeconfig"] = str(fname_kubeconfig_custom) fname_config_custom.write_text(yaml.dump(data)) # Create empty files for all Kubeconfigs. fname_kubeconfig_dotsquare.write_text("") fname_kubeconfig_custom.write_text("") fname_kubeconfig_commandline.write_text("") fname_kubeconfig_envvar.write_text("") del data # Create dummy kubeconfig for when we want to simulate `--kubeconfig`. kubeconfig_file = tmp_path / "kubeconfig-commandline" kubeconfig_file.write_text("") for envvar in [True, False]: new_env = os.environ.copy() if envvar: new_env["KUBECONFIG"] = str(fname_kubeconfig_envvar) else: new_env.pop("KUBECONFIG", None) with mock.patch.dict("os.environ", values=new_env, clear=True): reset() # .square: true, custom: true, --kubeconf: true -> --kubeconf param.configfile = str(fname_config_custom) param.kubeconfig = str(fname_kubeconfig_commandline) cfg, err = main.compile_config(param) assert not err and cfg.kubeconfig == fname_kubeconfig_commandline # .square: true, custom: true, --kubeconf: false -> custom param.configfile = str(fname_config_custom) param.kubeconfig = None cfg, err = main.compile_config(param) assert not err and cfg.kubeconfig == fname_kubeconfig_custom # .square: true, custom: false, --kubeconf: true -> --kubeconf param.configfile = None param.kubeconfig = str(fname_kubeconfig_commandline) cfg, err = main.compile_config(param) assert not err and cfg.kubeconfig == fname_kubeconfig_commandline # .square: true, custom: false, --kubeconf: false -> dotsquare param.configfile = None param.kubeconfig = None cfg, err = main.compile_config(param) assert not err and cfg.kubeconfig == fname_kubeconfig_dotsquare # ------------------------------------------------------------------ reset() fname_config_dotsquare.unlink() assert not fname_config_dotsquare.exists() # .square: false, custom: true, --kubeconf: true -> --kubeconf param.configfile = str(fname_config_custom) param.kubeconfig = str(fname_kubeconfig_commandline) cfg, err = main.compile_config(param) assert not err and cfg.kubeconfig == fname_kubeconfig_commandline # .square: false, custom: true, --kubeconf: false -> custom param.configfile = str(fname_config_custom) param.kubeconfig = None cfg, err = main.compile_config(param) assert not err and cfg.kubeconfig == fname_kubeconfig_custom # .square: false, custom: false, --kubeconf: true -> --kubeconf param.configfile = None param.kubeconfig = str(fname_kubeconfig_commandline) cfg, err = main.compile_config(param) assert not err and cfg.kubeconfig == fname_kubeconfig_commandline # .square: false, custom: false, --kubeconf: false -> env var. param.configfile = None param.kubeconfig = None cfg, err = main.compile_config(param) if envvar: assert not err and cfg.kubeconfig == Filepath(os.getenv("KUBECONFIG")) else: assert err
def test_compile_config_kinds_merge_file(self, config, tmp_path): """Merge configuration from file and command line.""" # Dummy file. kubeconfig_override = tmp_path / "kubeconfig" kubeconfig_override.write_text("") # --------------------------------------------------------------------- # Override nothing on the command line except for `kubeconfig` because # it must point to a valid file. # --------------------------------------------------------------------- param = types.SimpleNamespace( configfile=Filepath("tests/support/config.yaml"), # Must override this and point it to a dummy file or # `compile_config` will complain it does not exist. kubeconfig=str(kubeconfig_override), # User did not specify anything else. kubecontext=None, folder=None, groupby=None, kinds=None, labels=None, namespaces=None, priorities=None, ) # Translate command line arguments into `Config`. cfg, err = main.compile_config(param) assert not err assert cfg.folder == Filepath("tests/support").absolute() / "some/path" assert cfg.kubeconfig == kubeconfig_override assert cfg.kubecontext is None assert cfg.priorities == list(DEFAULT_PRIORITIES) assert cfg.selectors == Selectors( kinds=set(DEFAULT_PRIORITIES), namespaces=["default", "kube-system"], labels=["app=square"], ) assert cfg.groupby == GroupBy(label="app", order=["ns", "label", "kind"]) assert set(cfg.filters.keys()) == { "_common_", "ConfigMap", "Deployment", "HorizontalPodAutoscaler", "Service" } # --------------------------------------------------------------------- # Override everything on the command line. # --------------------------------------------------------------------- param = types.SimpleNamespace( folder="folder-override", kinds=["Deployment", "Namespace"], labels=["app=square", "foo=bar"], namespaces=["default", "kube-system"], kubeconfig=str(kubeconfig_override), kubecontext="kubecontext-override", groupby=["kind", "label=foo", "ns"], priorities=["Namespace", "Deployment"], configfile=Filepath("tests/support/config.yaml"), ) # Translate command line arguments into `Config`. cfg, err = main.compile_config(param) assert not err assert cfg.folder == Filepath(param.folder) assert cfg.kubeconfig == kubeconfig_override assert cfg.kubecontext == "kubecontext-override" assert cfg.priorities == ["Namespace", "Deployment"] assert cfg.selectors == Selectors( kinds={"Namespace", "Deployment"}, namespaces=["default", "kube-system"], labels=["app=square", "foo=bar"], ) assert cfg.groupby == GroupBy(label="foo", order=["kind", "label", "ns"]) assert set(cfg.filters.keys()) == { "_common_", "ConfigMap", "Deployment", "HorizontalPodAutoscaler", "Service" }