Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom config file locations via environment variables #1987

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Add support for SketchyBar (via @LarsRefsgaard)
- Add support for nvm (via @Wxh16144)
- Add support for PHPStorm 2023.3 (via @damosse31)
- Add support for changing the config file location via environment variables (via @northisup)

## Mackup 0.8.40

Expand Down
12 changes: 11 additions & 1 deletion doc/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
# Configuration

All the configuration is done in a file named `.mackup.cfg` stored at the
root of your home folder.
root of your home folder. This location can be overridden via environment
variables.

To configure Mackup, create a file named `.mackup.cfg` in your home directory.

```bash
vi ~/.mackup.cfg
```

### Configuration file location

Config files are searched in the following order. If none is found, Mackup will
use the default config location of `~/.mackup.cfg`

- `~/.mackup.cfg`
- `$MACKUP_CONFIG`
- `$XDG_CONFIG_HOME/mackup/mackup.cfg` or `~/.config/mackup/mackup.cfg`

## Storage

You can specify the storage type Mackup will use to store your configuration
Expand Down
65 changes: 56 additions & 9 deletions mackup/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import os
import os.path
from pathlib import Path

from .constants import (
CUSTOM_APPS_DIR,
MACKUP_BACKUP_PATH,
MACKUP_CONFIG_FILE,
ENGINE_DROPBOX,
ENGINE_FS,
ENGINE_GDRIVE,
ENGINE_ICLOUD,
ENGINE_FS,
MACKUP_BACKUP_PATH,
MACKUP_CONFIG_FILE,
)

from .utils import (
error,
get_dropbox_folder_location,
Expand Down Expand Up @@ -140,17 +140,64 @@ def _setup_parser(self, filename=None):
"""
assert isinstance(filename, str) or filename is None

# If we are not overriding the config filename
if not filename:
filename = MACKUP_CONFIG_FILE

parser = configparser.ConfigParser(
allow_no_value=True, inline_comment_prefixes=(";", "#")
)
parser.read(os.path.join(os.path.join(os.environ["HOME"], filename)))
parser.read(self._best_config_path(filename))

return parser

def _best_config_path(self, filename=None):
"""
If no filename is provided, we try to find one in according to the following
order, note that we will always check the original default of `~/.mackup.cfg`
first before checking the other options:

- ~/.mackup.cfg
- $MACKUP_CONFIG
- $XDG_CONFIG_HOME/mackup/mackup.cfg
- ~/.config/mackup/mackup.cfg

if none of these files exist, we create ~/.mackup.cfg

Args:
filename (_type_, optional): _description_. Defaults to None.

Returns:
str: the absolute path to the config file
"""
assert isinstance(filename, str) or filename is None

# If we are not overriding the config filename
if not filename:
default = Path.home() / MACKUP_CONFIG_FILE
search_paths = [
# 1. the default config file is ~/.mackup.cfg
default,
# 2. check for the MACKUP_CONFIG envvar
Path(os.environ.get("MACKUP_CONFIG", "")).expanduser(),
# 3. check for a config file in the XDG_CONFIG_HOME directory
(
Path(os.environ.get("XDG_CONFIG_HOME", "~/.config")).expanduser()
/ "mackup"
/ MACKUP_CONFIG_FILE.lstrip(".")
),
]
filename = next((p for p in search_paths if p.is_file()), default)
else:
filename = Path.home() / filename

try:
# Make sure the config file is in the home directory
filename.relative_to(Path.home())
except ValueError:
error(
f"The config file '{filename}' is not in your home directory. Aborting."
)

# return the absolute path to the config file
return str(filename.absolute())

def _warn_on_old_config(self):
"""Warn the user if an old config format is detected."""
# Is an old section in the config file?
Expand Down
58 changes: 55 additions & 3 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,72 @@
import unittest
import os
import os.path
import unittest
from pathlib import Path

from mackup.config import Config, ConfigError
from mackup.constants import (
ENGINE_DROPBOX,
ENGINE_FS,
ENGINE_GDRIVE,
ENGINE_ICLOUD,
ENGINE_FS,
MACKUP_CONFIG_FILE,
)
from mackup.config import Config, ConfigError


def assert_correct_config_read(testtype):
assert testtype == Config()._parser.get("test", "testtype")


class TestConfig(unittest.TestCase):
def setUp(self):
realpath = os.path.dirname(os.path.realpath(__file__))
os.environ["HOME"] = os.path.join(realpath, "fixtures")

# these may be set on some user's systems
os.environ.pop("XDG_CONFIG_HOME", None)
os.environ.pop("MACKUP_CONFIG", None)

def test_config_envvar(self):
os.environ["MACKUP_CONFIG"] = "~/mackup-envarcheck.cfg"
assert_correct_config_read("test_config_envvar")

def test_config_xdg(self):
os.environ["XDG_CONFIG_HOME"] = "~/xdg-config-home/"
assert_correct_config_read("test_config_xdg")

def test_config_find_correct_default(self):
config_path = Path.home() / MACKUP_CONFIG_FILE

try:
# create a default config file, this must be cleaned up after the test
config_path.write_text(f"[test]\ntesttype = test_config_default")

# nothing else set, should find the default file
assert_correct_config_read("test_config_default")

# set MACKUP_CONFIG, but should still find the default file
os.environ["MACKUP_CONFIG"] = "~/mackup-envarcheck.cfg"
assert_correct_config_read("test_config_default")

# set XDG_CONFIG_HOME, but should still find the default file
os.environ["XDG_CONFIG_HOME"] = "~/xdg-config-home/"
assert_correct_config_read("test_config_default")
except Exception:
raise
finally:
config_path.unlink(missing_ok=True)

assert config_path.exists() is False

def test_config_finds_correct_envvar(self):
# set XDG_CONFIG_HOME, but should still find the default file
os.environ["XDG_CONFIG_HOME"] = "~/xdg-config-home/"
assert_correct_config_read("test_config_xdg")

# set MACKUP_CONFIG, but should still find the default file
os.environ["MACKUP_CONFIG"] = "~/mackup-envarcheck.cfg"
assert_correct_config_read("test_config_envvar")

def test_config_no_config(self):
cfg = Config()

Expand Down