Marco Ricci commited on 2024-12-20 17:00:48
              Zeige 3 geänderte Dateien mit 94 Einfügungen und 3 Löschungen.
            
When configuring a `vault` service, or the global settings, a new configuration option `--unset FIELD` will unset the specified `FIELD` from the service or global settings prior to and addition to applying the requested settings changes. This way, the user can update all settings on the command-line (except for the "notes") without manually editing and reimporting a configuration export. (It is permissible to only unset settings, without applying any other configuration changes.)
| ... | ... | 
                      @@ -1635,6 +1635,28 @@ DEFAULT_NOTES_MARKER = '# - - - - - >8 - - - - -'  | 
                  
| 1635 | 1635 | 
                        help='overwrite or merge (default) the existing configuration',  | 
                    
| 1636 | 1636 | 
                        cls=CompatibilityOption,  | 
                    
| 1637 | 1637 | 
                        )  | 
                    
| 1638 | 
                        +@click.option(  | 
                    |
| 1639 | 
                        + '--unset',  | 
                    |
| 1640 | 
                        + 'unset_settings',  | 
                    |
| 1641 | 
                        + multiple=True,  | 
                    |
| 1642 | 
                        + type=click.Choice([  | 
                    |
| 1643 | 
                        + 'phrase',  | 
                    |
| 1644 | 
                        + 'key',  | 
                    |
| 1645 | 
                        + 'length',  | 
                    |
| 1646 | 
                        + 'repeat',  | 
                    |
| 1647 | 
                        + 'lower',  | 
                    |
| 1648 | 
                        + 'upper',  | 
                    |
| 1649 | 
                        + 'number',  | 
                    |
| 1650 | 
                        + 'space',  | 
                    |
| 1651 | 
                        + 'dash',  | 
                    |
| 1652 | 
                        + 'symbol',  | 
                    |
| 1653 | 
                        + ]),  | 
                    |
| 1654 | 
                        + help=(  | 
                    |
| 1655 | 
                        + 'with --config, also unsets the given setting; '  | 
                    |
| 1656 | 
                        + 'may be specified multiple times'  | 
                    |
| 1657 | 
                        + ),  | 
                    |
| 1658 | 
                        + cls=CompatibilityOption,  | 
                    |
| 1659 | 
                        +)  | 
                    |
| 1638 | 1660 | 
                        @click.version_option(version=dpp.__version__, prog_name=PROG_NAME)  | 
                    
| 1639 | 1661 | 
                        @standard_logging_options  | 
                    
| 1640 | 1662 | 
                         @click.argument('service', required=False)
                       | 
                    
| ... | ... | 
                      @@ -1662,6 +1684,7 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915  | 
                  
| 1662 | 1684 | 
                        export_settings: TextIO | pathlib.Path | os.PathLike[str] | None = None,  | 
                    
| 1663 | 1685 | 
                        import_settings: TextIO | pathlib.Path | os.PathLike[str] | None = None,  | 
                    
| 1664 | 1686 | 
                        overwrite_config: bool = False,  | 
                    
| 1687 | 
                        + unset_settings: Sequence[str] = (),  | 
                    |
| 1665 | 1688 | 
                        ) -> None:  | 
                    
| 1666 | 1689 | 
                        """Derive a passphrase using the vault(1) derivation scheme.  | 
                    
| 1667 | 1690 | 
                         | 
                    
| ... | ... | 
                      @@ -1758,6 +1781,10 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915  | 
                  
| 1758 | 1781 | 
                        `--merge-existing` (False). Controls whether config saving  | 
                    
| 1759 | 1782 | 
                        and config importing overwrite existing configurations, or  | 
                    
| 1760 | 1783 | 
                        merge them section-wise instead.  | 
                    
| 1784 | 
                        + unset_settings:  | 
                    |
| 1785 | 
                        + Command-line argument `--unset`. If given together with  | 
                    |
| 1786 | 
                        + `--config`, unsets the specified settings (in addition to  | 
                    |
| 1787 | 
                        + any other changes requested).  | 
                    |
| 1761 | 1788 | 
                         | 
                    
| 1762 | 1789 | 
                        """ # noqa: D301  | 
                    
| 1763 | 1790 | 
                        logger = logging.getLogger(PROG_NAME)  | 
                    
| ... | ... | 
                      @@ -2189,13 +2216,20 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915  | 
                  
| 2189 | 2216 | 
                        'Setting a global passphrase is ineffective '  | 
                    
| 2190 | 2217 | 
                        'because a key is also set.'  | 
                    
| 2191 | 2218 | 
                        )  | 
                    
| 2192 | 
                        - if not view.maps[0]:  | 
                    |
| 2219 | 
                        + if not view.maps[0] and not unset_settings:  | 
                    |
| 2193 | 2220 | 
                        settings_type = 'service' if service else 'global'  | 
                    
| 2194 | 2221 | 
                        msg = (  | 
                    
| 2195 | 2222 | 
                                             f'Cannot update {settings_type} settings without '
                       | 
                    
| 2196 | 2223 | 
                        f'actual settings'  | 
                    
| 2197 | 2224 | 
                        )  | 
                    
| 2198 | 2225 | 
                        raise click.UsageError(msg)  | 
                    
| 2226 | 
                        + for setting in unset_settings:  | 
                    |
| 2227 | 
                        + if setting in view.maps[0]:  | 
                    |
| 2228 | 
                        + msg = (  | 
                    |
| 2229 | 
                        +                        f'Attempted to unset and set --{setting} '
                       | 
                    |
| 2230 | 
                        + f'at the same time.'  | 
                    |
| 2231 | 
                        + )  | 
                    |
| 2232 | 
                        + raise click.UsageError(msg)  | 
                    |
| 2199 | 2233 | 
                        subtree: dict[str, Any] = (  | 
                    
| 2200 | 2234 | 
                                         configuration['services'].setdefault(service, {})  # type: ignore[assignment]
                       | 
                    
| 2201 | 2235 | 
                        if service  | 
                    
| ... | ... | 
                      @@ -2203,6 +2237,9 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915  | 
                  
| 2203 | 2237 | 
                        )  | 
                    
| 2204 | 2238 | 
                        if overwrite_config:  | 
                    
| 2205 | 2239 | 
                        subtree.clear()  | 
                    
| 2240 | 
                        + else:  | 
                    |
| 2241 | 
                        + for setting in unset_settings:  | 
                    |
| 2242 | 
                        + subtree.pop(setting, None)  | 
                    |
| 2206 | 2243 | 
                        subtree.update(view)  | 
                    
| 2207 | 2244 | 
                        assert _types.is_vault_config(  | 
                    
| 2208 | 2245 | 
                        configuration  | 
                    
| ... | ... | 
                      @@ -1304,6 +1304,32 @@ contents go here  | 
                  
| 1304 | 1304 | 
                        error=custom_error  | 
                    
| 1305 | 1305 | 
                        ), 'expected error exit and known error message'  | 
                    
| 1306 | 1306 | 
                         | 
                    
| 1307 | 
                        + def test_225f_store_config_fail_unset_and_set_same_settings(  | 
                    |
| 1308 | 
                        + self,  | 
                    |
| 1309 | 
                        + monkeypatch: pytest.MonkeyPatch,  | 
                    |
| 1310 | 
                        + ) -> None:  | 
                    |
| 1311 | 
                        + runner = click.testing.CliRunner(mix_stderr=False)  | 
                    |
| 1312 | 
                        + with tests.isolated_vault_config(  | 
                    |
| 1313 | 
                        + monkeypatch=monkeypatch,  | 
                    |
| 1314 | 
                        + runner=runner,  | 
                    |
| 1315 | 
                        +            vault_config={'global': {'phrase': 'abc'}, 'services': {}},
                       | 
                    |
| 1316 | 
                        + ):  | 
                    |
| 1317 | 
                        + _result = runner.invoke(  | 
                    |
| 1318 | 
                        + cli.derivepassphrase_vault,  | 
                    |
| 1319 | 
                        + [  | 
                    |
| 1320 | 
                        + '--config',  | 
                    |
| 1321 | 
                        + '--unset=length',  | 
                    |
| 1322 | 
                        + '--length=15',  | 
                    |
| 1323 | 
                        + '--',  | 
                    |
| 1324 | 
                        + DUMMY_SERVICE,  | 
                    |
| 1325 | 
                        + ],  | 
                    |
| 1326 | 
                        + catch_exceptions=False,  | 
                    |
| 1327 | 
                        + )  | 
                    |
| 1328 | 
                        + result = tests.ReadableResult.parse(_result)  | 
                    |
| 1329 | 
                        + assert result.error_exit(  | 
                    |
| 1330 | 
                        + error='Attempted to unset and set --length at the same time.'  | 
                    |
| 1331 | 
                        + ), 'expected error exit and known error message'  | 
                    |
| 1332 | 
                        +  | 
                    |
| 1307 | 1333 | 
                        def test_226_no_arguments(self, monkeypatch: pytest.MonkeyPatch) -> None:  | 
                    
| 1308 | 1334 | 
                        runner = click.testing.CliRunner(mix_stderr=False)  | 
                    
| 1309 | 1335 | 
                        with tests.isolated_config(  | 
                    
| ... | ... | 
                      @@ -2752,17 +2778,27 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine):  | 
                  
| 2752 | 2778 | 
                        target=configuration,  | 
                    
| 2753 | 2779 | 
                        config=configuration,  | 
                    
| 2754 | 2780 | 
                        setting=setting.filter(bool),  | 
                    
| 2781 | 
                        + maybe_unset=strategies.sets(  | 
                    |
| 2782 | 
                        + strategies.sampled_from(_valid_properties),  | 
                    |
| 2783 | 
                        + max_size=3,  | 
                    |
| 2784 | 
                        + ),  | 
                    |
| 2755 | 2785 | 
                        overwrite=strategies.booleans(),  | 
                    
| 2756 | 2786 | 
                        )  | 
                    
| 2757 | 2787 | 
                        def set_globals(  | 
                    
| 2758 | 2788 | 
                        self,  | 
                    
| 2759 | 2789 | 
                        config: _types.VaultConfig,  | 
                    
| 2760 | 2790 | 
                        setting: _types.VaultConfigGlobalSettings,  | 
                    
| 2791 | 
                        + maybe_unset: set[str],  | 
                    |
| 2761 | 2792 | 
                        overwrite: bool,  | 
                    
| 2762 | 2793 | 
                        ) -> _types.VaultConfig:  | 
                    
| 2763 | 2794 | 
                        cli._save_config(config)  | 
                    
| 2795 | 
                        +        config_global = config.get('global', {})
                       | 
                    |
| 2796 | 
                        + maybe_unset = set(maybe_unset) - setting.keys()  | 
                    |
| 2764 | 2797 | 
                        if overwrite:  | 
                    
| 2765 | 
                        -            config['global'] = {}
                       | 
                    |
| 2798 | 
                        +            config['global'] = config_global = {}
                       | 
                    |
| 2799 | 
                        + elif maybe_unset:  | 
                    |
| 2800 | 
                        + for key in maybe_unset:  | 
                    |
| 2801 | 
                        + config_global.pop(key, None) # type: ignore[misc]  | 
                    |
| 2766 | 2802 | 
                                 config.setdefault('global', {}).update(setting)
                       | 
                    
| 2767 | 2803 | 
                        assert _types.is_vault_config(config)  | 
                    
| 2768 | 2804 | 
                        # NOTE: This relies on settings_obj containing only the keys  | 
                    
| ... | ... | 
                      @@ -2774,6 +2810,7 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine):  | 
                  
| 2774 | 2810 | 
                        '--config',  | 
                    
| 2775 | 2811 | 
                        '--overwrite-existing' if overwrite else '--merge-existing',  | 
                    
| 2776 | 2812 | 
                        ]  | 
                    
| 2813 | 
                        +            + [f'--unset={key}' for key in maybe_unset]
                       | 
                    |
| 2777 | 2814 | 
                        + [  | 
                    
| 2778 | 2815 | 
                                         f'--{key}={value}'
                       | 
                    
| 2779 | 2816 | 
                        for key, value in setting.items()  | 
                    
| ... | ... | 
                      @@ -2791,6 +2828,10 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine):  | 
                  
| 2791 | 2828 | 
                        config=configuration,  | 
                    
| 2792 | 2829 | 
                        service=strategies.sampled_from(_known_services),  | 
                    
| 2793 | 2830 | 
                        setting=setting.filter(bool),  | 
                    
| 2831 | 
                        + maybe_unset=strategies.sets(  | 
                    |
| 2832 | 
                        + strategies.sampled_from(_valid_properties),  | 
                    |
| 2833 | 
                        + max_size=3,  | 
                    |
| 2834 | 
                        + ),  | 
                    |
| 2794 | 2835 | 
                        overwrite=strategies.booleans(),  | 
                    
| 2795 | 2836 | 
                        )  | 
                    
| 2796 | 2837 | 
                        def set_service(  | 
                    
| ... | ... | 
                      @@ -2798,11 +2839,17 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine):  | 
                  
| 2798 | 2839 | 
                        config: _types.VaultConfig,  | 
                    
| 2799 | 2840 | 
                        service: str,  | 
                    
| 2800 | 2841 | 
                        setting: _types.VaultConfigServicesSettings,  | 
                    
| 2842 | 
                        + maybe_unset: set[str],  | 
                    |
| 2801 | 2843 | 
                        overwrite: bool,  | 
                    
| 2802 | 2844 | 
                        ) -> _types.VaultConfig:  | 
                    
| 2803 | 2845 | 
                        cli._save_config(config)  | 
                    
| 2846 | 
                        +        config_service = config['services'].get(service, {})
                       | 
                    |
| 2847 | 
                        + maybe_unset = set(maybe_unset) - setting.keys()  | 
                    |
| 2804 | 2848 | 
                        if overwrite:  | 
                    
| 2805 | 
                        -            config['services'][service] = {}
                       | 
                    |
| 2849 | 
                        +            config['services'][service] = config_service = {}
                       | 
                    |
| 2850 | 
                        + elif maybe_unset:  | 
                    |
| 2851 | 
                        + for key in maybe_unset:  | 
                    |
| 2852 | 
                        + config_service.pop(key, None) # type: ignore[misc]  | 
                    |
| 2806 | 2853 | 
                                 config['services'].setdefault(service, {}).update(setting)
                       | 
                    
| 2807 | 2854 | 
                        assert _types.is_vault_config(config)  | 
                    
| 2808 | 2855 | 
                        # NOTE: This relies on settings_obj containing only the keys  | 
                    
| ... | ... | 
                      @@ -2814,6 +2861,7 @@ class ConfigManagementStateMachine(stateful.RuleBasedStateMachine):  | 
                  
| 2814 | 2861 | 
                        '--config',  | 
                    
| 2815 | 2862 | 
                        '--overwrite-existing' if overwrite else '--merge-existing',  | 
                    
| 2816 | 2863 | 
                        ]  | 
                    
| 2864 | 
                        +            + [f'--unset={key}' for key in maybe_unset]
                       | 
                    |
| 2817 | 2865 | 
                        + [  | 
                    
| 2818 | 2866 | 
                                         f'--{key}={value}'
                       | 
                    
| 2819 | 2867 | 
                        for key, value in setting.items()  | 
                    
| 2820 | 2868 |