Skip to content

Commit

Permalink
add conf-manager
Browse files Browse the repository at this point in the history
  • Loading branch information
llisplunk committed Apr 30, 2016
1 parent 82877b4 commit 61c4bea
Show file tree
Hide file tree
Showing 11 changed files with 657 additions and 232 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
build
dist
docs/_build
docs/solnlib
docs/index.rst
splunksolutionlib.egg-info
__pycache__
*.pyc
Expand Down
3 changes: 3 additions & 0 deletions examples/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def run_test():
import test_metadata
import test_acl
import test_credentials
import test_conf_manager

print 'check splunk environment...'
test_splunkenv.test_splunkenv()
Expand All @@ -54,6 +55,8 @@ def run_test():
test_acl.test_acl_manager()
print 'test credential manager...'
test_credentials.test_credential_manager()
print 'test conf manager...'
test_conf_manager.test_conf_manager()

if __name__ == '__main__':
teardown_environment()
Expand Down
39 changes: 39 additions & 0 deletions examples/test_conf_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import sys
import os.path as op
import pytest

from splunklib import client

sys.path.insert(0, op.dirname(op.dirname(op.abspath(__file__))))
import solnlib.splunk_rest_client as rest_client
from solnlib import credentials
from solnlib import conf_manager
import context


def test_conf_manager():
session_key = credentials.get_session_key(
context.username, context.password, scheme=context.scheme,
host=context.host, port=context.port)

cfsm = rest_client.SplunkRestClient(
session_key, context.app, owner=context.owner,
scheme=context.scheme, host=context.host, port=context.port).confs
try:
cfsm.get('test')
except client.HTTPError:
cfsm.create('test')

cfm = conf_manager.ConfManager(
'test', session_key, context.app, owner=context.owner,
scheme=context.scheme, host=context.host, port=context.port)
cfm.update('test_stanza', {'k1': 1, 'k2': 2}, ['k1'])
assert cfm.get('test_stanza')['k1'] == 1
assert int(cfm.get('test_stanza')['k2']) == 2
assert len(cfm.get_all()) == 1
cfm.delete('test_stanza')
with pytest.raises(conf_manager.ConfStanzaNotExistException):
cfm.get('test_stanza')
with pytest.raises(conf_manager.ConfStanzaNotExistException):
cfm.delete('test_stanza')
cfm.reload()
272 changes: 272 additions & 0 deletions solnlib/conf_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
# Copyright 2016 Splunk, Inc.
#
# 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.

'''
This modules contains simple interfaces for Splunk config file management.
'''

import json

from splunklib import binding

from solnlib.utils import retry
from solnlib.credentials import CredNotExistException
from solnlib.credentials import CredentialManager
import solnlib.splunk_rest_client as rest_client

__all__ = ['ConfManagerException',
'ConfStanzaNotExistException',
'ConfManager']


class ConfManagerException(Exception):
pass


class ConfStanzaNotExistException(Exception):
pass


class ConfManager(object):
'''Configuration file manager.
:param conf_file: Configuration file.
:type conf_file: ``string``
:param session_key: Splunk access token.
:type session_key: ``string``
:param app: App name of namespace.
:type app: ``string``
:param owner: (optional) Owner of namespace, default is `nobody`.
:type owner: ``string``
:param realm: (optional) Realm of credential, default is None.
:type realm: ``string``
:param scheme: (optional) The access scheme, default is `https`.
:type scheme: ``string``
:param host: (optional) The host name, default is `localhost`.
:type host: ``string``
:param port: (optional) The port number, default is 8089.
:type port: ``integer``
:param context: Other configurations for Splunk rest client.
:type context: ``dict``
:raises ConfManagerException: If `conf_file` does not exist.
Usage::
>>> from solnlib import conf_manager
>>> cfm = conf_manager.ConfManager('test_conf',
session_key,
'Splunk_TA_test')
'''

ENCRYPTED_TOKEN = '******'

reserved_keys = ('userName', 'appName')

def __init__(self, conf_file, session_key, app, owner='nobody',
scheme='https', host='localhost', port=8089, **context):
try:
self._conf_mgr = rest_client.SplunkRestClient(
session_key,
app,
owner=owner,
scheme=scheme,
host=host,
port=port,
**context).confs[conf_file]
except KeyError:
raise ConfManagerException(
'Config file: %s does not exist.' % conf_file)
self._conf_file = conf_file
self._cred_mgr = CredentialManager(
session_key, app, owner=owner, realm=app,
scheme=scheme, host=host, port=port, **context)

def _filter_stanza(self, stanza):
for k in self.reserved_keys:
if k in stanza:
del stanza[k]

return stanza

def _encrypt_stanza(self, stanza_name, stanza, encrypt_keys):
if not encrypt_keys:
return stanza

encrypt_fields = {key: stanza[key] for key in encrypt_keys}
self._cred_mgr.set_password(stanza_name, json.dumps(encrypt_fields))

for key in encrypt_keys:
stanza[key] = self.ENCRYPTED_TOKEN

return stanza

def _decrypt_stanza(self, stanza_name, encrypted_stanza):
encrypted_keys = [key for key in encrypted_stanza if
encrypted_stanza[key] == self.ENCRYPTED_TOKEN]
if encrypted_keys:
encrypted_fields = json.loads(
self._cred_mgr.get_password(stanza_name))
for key in encrypted_keys:
encrypted_stanza[key] = encrypted_fields[key]

return encrypted_stanza

def _delete_stanza_creds(self, stanza_name):
self._cred_mgr.delete_password(stanza_name)

@retry(exceptions=[binding.HTTPError])
def get(self, stanza_name):
'''Get stanza from configuration file.
:param stanza_name: Stanza name.
:type stanza_name: ``string``
:returns: Stanza, like: {
'disabled': '0',
'eai:appName': 'solnlib_demo',
'eai:userName': 'nobody',
'k1': '1',
'k2': '2'}
:rtype: ``dict``
Usage::
>>> from solnlib import conf_manager
>>> cfm = conf_manager.ConfManager('test_conf',
session_key,
'Splunk_TA_test')
>>> cfm.get('test_stanza')
'''

try:
stanza_mgr = self._conf_mgr.list(name=stanza_name)[0]
except binding.HTTPError as e:
if e.status == 404:
raise ConfStanzaNotExistException(
'Stanza: %s does not exist in %s.conf' %
(stanza_name, self._conf_file))
raise

stanza = self._decrypt_stanza(stanza_mgr.name, stanza_mgr.content)
return stanza

@retry(exceptions=[binding.HTTPError])
def get_all(self):
'''Get all stanzas from configuration file.
:returns: All stanzas, like: {'test': {
'disabled': '0',
'eai:appName': 'solnlib_demo',
'eai:userName': 'nobody',
'k1': '1',
'k2': '2'}}
:rtype: ``dict``
:raises ConfStanzaNotExistException: If stanza does not exist.
Usage::
>>> from solnlib import conf_manager
>>> cfm = conf_manager.ConfManager('test_conf',
session_key,
'Splunk_TA_test')
>>> cfm.get_all()
'''

stanza_mgrs = self._conf_mgr.list()
return {stanza_mgr.name: self._decrypt_stanza(
stanza_mgr.name, stanza_mgr.content) for stanza_mgr in stanza_mgrs}

@retry(exceptions=[binding.HTTPError])
def update(self, stanza_name, stanza, encrypt_keys=None):
'''Update stanza with some fields are encrypted.
:param stanza_name: Stanza name.
:type stanza_name: ``string``
:param stanza: Stanza to update, like: {
'k1': 1,
'k2': 2}.
:type stanza: ``dict``
:param encrypt_keys: Fields name to encrypt.
:type encrypt_keys: ``list``
Usage::
>>> from solnlib import conf_manager
>>> cfm = conf_manager.ConfManager('test_conf',
session_key,
'Splunk_TA_test')
>>> cfm.get('test_stanza', {'k1': 1, 'k2': 2}, ['k1'])
'''

stanza = self._filter_stanza(stanza)
encrypted_stanza = self._encrypt_stanza(stanza_name,
stanza,
encrypt_keys)

try:
stanza_mgr = self._conf_mgr.list(name=stanza_name)[0]
except binding.HTTPError as e:
if e.status != 404:
raise

stanza_mgr = self._conf_mgr.create(stanza_name)

stanza_mgr.submit(encrypted_stanza)

@retry(exceptions=[binding.HTTPError])
def delete(self, stanza_name):
'''Delete stanza.
:param stanza_name: Stanza name to delete.
:type stanza_name: ``string``
:raises ConfStanzaNotExistException: If stanza does not exist.
Usage::
>>> from solnlib import conf_manager
>>> cfm = conf_manager.ConfManager('test_conf',
session_key,
'Splunk_TA_test')
>>> cfm.delete('test_stanza')
'''

try:
self._conf_mgr.delete(stanza_name)
except KeyError:
raise ConfStanzaNotExistException(
'Stanza: %s does not exist in %s.conf' %
(stanza_name, self._conf_file))

try:
self._cred_mgr.delete_password(stanza_name)
except CredNotExistException:
pass

@retry(exceptions=[binding.HTTPError])
def reload(self):
'''Reload configuration file.
Usage::
>>> from solnlib import conf_manager
>>> cfm = conf_manager.ConfManager('test_conf',
session_key,
'Splunk_TA_test')
>>> cfm.reload()
'''

self._conf_mgr.get('_reload')
18 changes: 9 additions & 9 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


def mock_splunkhome(monkeypatch):
class _MockPopen(object):
class MockPopen(object):
def __init__(self, args, bufsize=0, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=False, shell=False,
Expand All @@ -37,23 +37,23 @@ def communicate(self, input=None):
return fp.read(), None

monkeypatch.setenv('SPLUNK_HOME', op.join(cur_dir, 'data/mock_splunk/'))
monkeypatch.setattr(subprocess, 'Popen', _MockPopen)
monkeypatch.setattr(subprocess, 'Popen', MockPopen)


def mock_serverinfo(monkeypatch):
_mock_server_info_property = {'server_roles': ['cluster_search_head', 'search_head',
'kv_store', 'shc_captain'],
'version': '6.3.1511.2',
'serverName': 'unittestServer'}
mock_server_info_property = {'server_roles': ['cluster_search_head', 'search_head',
'kv_store', 'shc_captain'],
'version': '6.3.1511.2',
'serverName': 'unittestServer'}

monkeypatch.setattr(client.Service, 'info', _mock_server_info_property)
monkeypatch.setattr(client.Service, 'info', mock_server_info_property)


def mock_gethostname(monkeypatch):
def _mock_gethostname():
def mock_gethostname():
return 'unittestServer'

monkeypatch.setattr(socket, 'gethostname', _mock_gethostname)
monkeypatch.setattr(socket, 'gethostname', mock_gethostname)


def make_response_record(body, status=200):
Expand Down
Loading

0 comments on commit 61c4bea

Please sign in to comment.