Skip to content

Commit 938f820

Browse files
authored
feat(config): added global config manager (#533)
1 parent ff9a67c commit 938f820

File tree

12 files changed

+191
-277
lines changed

12 files changed

+191
-277
lines changed

conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def instance_path(renku_path, monkeypatch):
4545
@pytest.fixture()
4646
def runner(monkeypatch):
4747
"""Create a runner on isolated filesystem."""
48-
from renku.cli._config import RENKU_HOME
48+
from renku.api.config import RENKU_HOME
4949
monkeypatch.setenv('RENKU_CONFIG', RENKU_HOME)
5050
return CliRunner()
5151

@@ -75,7 +75,7 @@ def generate(args=('update', ), cwd=None, **streams):
7575
@pytest.fixture()
7676
def isolated_runner(monkeypatch):
7777
"""Create a runner on isolated filesystem."""
78-
from renku.cli._config import RENKU_HOME
78+
from renku.api.config import RENKU_HOME
7979
monkeypatch.setenv('RENKU_CONFIG', RENKU_HOME)
8080
runner_ = CliRunner()
8181
with runner_.isolated_filesystem():

renku/api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright 2017-2018 - Swiss Data Science Center (SDSC)
3+
# Copyright 2017-2019 - Swiss Data Science Center (SDSC)
44
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
55
# Eidgenössische Technische Hochschule Zürich (ETHZ).
66
#

renku/api/client.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,16 @@
1919

2020
import attr
2121

22+
from .config import ConfigManagerMixin
2223
from .datasets import DatasetsApiMixin
2324
from .repository import PathMixin, RepositoryApiMixin
2425
from .storage import StorageApiMixin
2526

2627

2728
@attr.s
2829
class LocalClient(
29-
PathMixin,
30-
StorageApiMixin,
31-
RepositoryApiMixin,
32-
DatasetsApiMixin,
30+
PathMixin, StorageApiMixin, RepositoryApiMixin, DatasetsApiMixin,
31+
ConfigManagerMixin
3332
):
3433
"""A low-level client for communicating with a local Renku repository.
3534

renku/api/config.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2019 - Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Client for handling a configuration."""
19+
import configparser
20+
import fcntl
21+
import os
22+
from pathlib import Path
23+
24+
import attr
25+
import click
26+
27+
APP_NAME = 'Renku'
28+
"""Application name for storing configuration."""
29+
30+
RENKU_HOME = '.renku'
31+
"""Project directory name."""
32+
33+
34+
def print_app_config_path(ctx, param, value):
35+
"""Print application config path."""
36+
if not value or ctx.resilient_parsing:
37+
return
38+
click.echo(ConfigManagerMixin().config_path)
39+
ctx.exit()
40+
41+
42+
def default_config_dir():
43+
"""Return default config directory."""
44+
return click.get_app_dir(APP_NAME, force_posix=True)
45+
46+
47+
@attr.s
48+
class ConfigManagerMixin:
49+
"""Client for handling global configuration."""
50+
51+
config_dir = attr.ib(default=default_config_dir(), converter=str)
52+
config_name = attr.ib(default='renku.ini', converter=str)
53+
54+
_lock = attr.ib(default=None)
55+
56+
def __enter__(self):
57+
"""Acquire a lock file."""
58+
lock_name = '{0}/{1}.lock'.format(self.config_dir, self.config_name)
59+
locked_file_descriptor = open(lock_name, 'w+')
60+
fcntl.lockf(locked_file_descriptor, fcntl.LOCK_EX)
61+
self._lock = locked_file_descriptor
62+
63+
def __exit__(self, type, value, traceback):
64+
"""Release lock file."""
65+
self._lock.close()
66+
67+
@property
68+
def config_path(self):
69+
"""Renku config path."""
70+
config = Path(self.config_dir)
71+
if not config.exists():
72+
config.mkdir()
73+
74+
return config / Path(self.config_name)
75+
76+
def load_config(self):
77+
"""Loads global configuration object."""
78+
config = configparser.ConfigParser()
79+
config.read(str(self.config_path))
80+
return config
81+
82+
def store_config(self, config):
83+
"""Persists global configuration object."""
84+
os.umask(0)
85+
fd = os.open(str(self.config_path), os.O_CREAT | os.O_WRONLY, 0o600)
86+
with open(fd, 'w') as file:
87+
config.write(file)
88+
89+
def get_value(self, section, key):
90+
"""Get value from specified section and key."""
91+
config = self.load_config()
92+
return config.get(section, key, fallback=None)
93+
94+
def set_value(self, section, key, value):
95+
"""Set value to specified section and key."""
96+
config = self.load_config()
97+
if section in config:
98+
config[section][key] = value
99+
else:
100+
config[section] = {key: value}
101+
102+
self.store_config(config)
103+
return config
104+
105+
106+
def get_config(client, write_op, is_global):
107+
"""Get configuration object."""
108+
if is_global:
109+
return client
110+
111+
if write_op:
112+
return client.repo.config_writer()
113+
return client.repo.config_reader()

renku/cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@
8282
import yaml
8383

8484
from ..api.client import LocalClient
85+
from ..api.config import RENKU_HOME, default_config_dir, print_app_config_path
8586
from ..api.repository import default_path
86-
from ._config import RENKU_HOME, default_config_dir, print_app_config_path
8787
from ._exc import IssueFromTraceback
8888
from ._options import install_completion, option_use_external_storage
8989
from ._version import check_version, print_version

renku/cli/_config.py

Lines changed: 0 additions & 160 deletions
This file was deleted.

renku/cli/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def dump(self, app_name):
127127

128128
def _check_version():
129129
"""Check renku version."""
130-
from ._config import APP_NAME
130+
from ..api.config import APP_NAME
131131

132132
if VersionCache.load(APP_NAME).is_fresh:
133133
return

renku/cli/config.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@
3838
https://registry.gitlab.com/demo/demo
3939
4040
"""
41+
import configparser
4142

4243
import click
44+
from click import BadParameter
45+
46+
from renku.api.config import get_config
4347

4448
from ._client import pass_local_client
4549

@@ -55,14 +59,24 @@ def _split_section_and_key(key):
5559
@click.command()
5660
@click.argument('key', required=True)
5761
@click.argument('value', required=False, default=None)
62+
@click.option(
63+
'--global',
64+
'is_global',
65+
is_flag=True,
66+
help='Store to global configuration.'
67+
)
5868
@pass_local_client
59-
def config(client, key, value):
60-
"""Get and set Renku repository and global options."""
61-
if value is None:
62-
cfg = client.repo.config_reader()
63-
click.echo(cfg.get_value(*_split_section_and_key(key)))
64-
else:
65-
with client.repo.config_writer() as cfg:
69+
def config(client, key, value, is_global):
70+
"""Manage configuration options."""
71+
write_op = value is not None
72+
config_ = get_config(client, write_op, is_global)
73+
if write_op:
74+
with config_:
6675
section, config_key = _split_section_and_key(key)
67-
cfg.set_value(section, config_key, value)
76+
config_.set_value(section, config_key, value)
6877
click.echo(value)
78+
else:
79+
try:
80+
click.echo(config_.get_value(*_split_section_and_key(key)))
81+
except configparser.NoSectionError:
82+
raise BadParameter('Requested configuration not found')

0 commit comments

Comments
 (0)