Skip to content

Commit

Permalink
feat(config): added global config manager (#533)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsam committed May 27, 2019
1 parent ff9a67c commit 938f820
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 277 deletions.
4 changes: 2 additions & 2 deletions conftest.py
Expand Up @@ -45,7 +45,7 @@ def instance_path(renku_path, monkeypatch):
@pytest.fixture()
def runner(monkeypatch):
"""Create a runner on isolated filesystem."""
from renku.cli._config import RENKU_HOME
from renku.api.config import RENKU_HOME
monkeypatch.setenv('RENKU_CONFIG', RENKU_HOME)
return CliRunner()

Expand Down Expand Up @@ -75,7 +75,7 @@ def generate(args=('update', ), cwd=None, **streams):
@pytest.fixture()
def isolated_runner(monkeypatch):
"""Create a runner on isolated filesystem."""
from renku.cli._config import RENKU_HOME
from renku.api.config import RENKU_HOME
monkeypatch.setenv('RENKU_CONFIG', RENKU_HOME)
runner_ = CliRunner()
with runner_.isolated_filesystem():
Expand Down
2 changes: 1 addition & 1 deletion renku/api/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright 2017-2018 - Swiss Data Science Center (SDSC)
# Copyright 2017-2019 - Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
Expand Down
7 changes: 3 additions & 4 deletions renku/api/client.py
Expand Up @@ -19,17 +19,16 @@

import attr

from .config import ConfigManagerMixin
from .datasets import DatasetsApiMixin
from .repository import PathMixin, RepositoryApiMixin
from .storage import StorageApiMixin


@attr.s
class LocalClient(
PathMixin,
StorageApiMixin,
RepositoryApiMixin,
DatasetsApiMixin,
PathMixin, StorageApiMixin, RepositoryApiMixin, DatasetsApiMixin,
ConfigManagerMixin
):
"""A low-level client for communicating with a local Renku repository.
Expand Down
113 changes: 113 additions & 0 deletions renku/api/config.py
@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
#
# Copyright 2019 - Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Client for handling a configuration."""
import configparser
import fcntl
import os
from pathlib import Path

import attr
import click

APP_NAME = 'Renku'
"""Application name for storing configuration."""

RENKU_HOME = '.renku'
"""Project directory name."""


def print_app_config_path(ctx, param, value):
"""Print application config path."""
if not value or ctx.resilient_parsing:
return
click.echo(ConfigManagerMixin().config_path)
ctx.exit()


def default_config_dir():
"""Return default config directory."""
return click.get_app_dir(APP_NAME, force_posix=True)


@attr.s
class ConfigManagerMixin:
"""Client for handling global configuration."""

config_dir = attr.ib(default=default_config_dir(), converter=str)
config_name = attr.ib(default='renku.ini', converter=str)

_lock = attr.ib(default=None)

def __enter__(self):
"""Acquire a lock file."""
lock_name = '{0}/{1}.lock'.format(self.config_dir, self.config_name)
locked_file_descriptor = open(lock_name, 'w+')
fcntl.lockf(locked_file_descriptor, fcntl.LOCK_EX)
self._lock = locked_file_descriptor

def __exit__(self, type, value, traceback):
"""Release lock file."""
self._lock.close()

@property
def config_path(self):
"""Renku config path."""
config = Path(self.config_dir)
if not config.exists():
config.mkdir()

return config / Path(self.config_name)

def load_config(self):
"""Loads global configuration object."""
config = configparser.ConfigParser()
config.read(str(self.config_path))
return config

def store_config(self, config):
"""Persists global configuration object."""
os.umask(0)
fd = os.open(str(self.config_path), os.O_CREAT | os.O_WRONLY, 0o600)
with open(fd, 'w') as file:
config.write(file)

def get_value(self, section, key):
"""Get value from specified section and key."""
config = self.load_config()
return config.get(section, key, fallback=None)

def set_value(self, section, key, value):
"""Set value to specified section and key."""
config = self.load_config()
if section in config:
config[section][key] = value
else:
config[section] = {key: value}

self.store_config(config)
return config


def get_config(client, write_op, is_global):
"""Get configuration object."""
if is_global:
return client

if write_op:
return client.repo.config_writer()
return client.repo.config_reader()
2 changes: 1 addition & 1 deletion renku/cli/__init__.py
Expand Up @@ -82,8 +82,8 @@
import yaml

from ..api.client import LocalClient
from ..api.config import RENKU_HOME, default_config_dir, print_app_config_path
from ..api.repository import default_path
from ._config import RENKU_HOME, default_config_dir, print_app_config_path
from ._exc import IssueFromTraceback
from ._options import install_completion, option_use_external_storage
from ._version import check_version, print_version
Expand Down
160 changes: 0 additions & 160 deletions renku/cli/_config.py

This file was deleted.

2 changes: 1 addition & 1 deletion renku/cli/_version.py
Expand Up @@ -127,7 +127,7 @@ def dump(self, app_name):

def _check_version():
"""Check renku version."""
from ._config import APP_NAME
from ..api.config import APP_NAME

if VersionCache.load(APP_NAME).is_fresh:
return
Expand Down
30 changes: 22 additions & 8 deletions renku/cli/config.py
Expand Up @@ -38,8 +38,12 @@
https://registry.gitlab.com/demo/demo
"""
import configparser

import click
from click import BadParameter

from renku.api.config import get_config

from ._client import pass_local_client

Expand All @@ -55,14 +59,24 @@ def _split_section_and_key(key):
@click.command()
@click.argument('key', required=True)
@click.argument('value', required=False, default=None)
@click.option(
'--global',
'is_global',
is_flag=True,
help='Store to global configuration.'
)
@pass_local_client
def config(client, key, value):
"""Get and set Renku repository and global options."""
if value is None:
cfg = client.repo.config_reader()
click.echo(cfg.get_value(*_split_section_and_key(key)))
else:
with client.repo.config_writer() as cfg:
def config(client, key, value, is_global):
"""Manage configuration options."""
write_op = value is not None
config_ = get_config(client, write_op, is_global)
if write_op:
with config_:
section, config_key = _split_section_and_key(key)
cfg.set_value(section, config_key, value)
config_.set_value(section, config_key, value)
click.echo(value)
else:
try:
click.echo(config_.get_value(*_split_section_and_key(key)))
except configparser.NoSectionError:
raise BadParameter('Requested configuration not found')

0 comments on commit 938f820

Please sign in to comment.