Skip to content
This repository has been archived by the owner on Sep 15, 2020. It is now read-only.

Make custom filters available in Mistral; Disable Decryption-by-default in st2kv function #30

Merged
merged 37 commits into from Jul 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b28c758
Added test function to test install workflow
Jul 7, 2017
d7502fe
Implmented redirect functions for all filters
Jul 12, 2017
c59c81f
Copied filter code into st2mistral from st2 repo
Jul 12, 2017
222b0b2
Removed old references
Jul 12, 2017
b50b414
Restored original import order
Jul 12, 2017
bd060a3
Added filter unit tests
Jul 12, 2017
2188602
Removed fat from test base
Jul 12, 2017
f6a51f2
Moved use_none to its own file, added test
Jul 12, 2017
01c1570
PEP8 fixes
Jul 12, 2017
7eabead
Undo addition of oslo.log and oslo.config
Jul 12, 2017
996cc7a
Rum unit tests in circle
Jul 13, 2017
806df5a
Removed decrypt_kv filter; added optional parameter to disable decryp…
Jul 13, 2017
ed8c56e
Removed crypto unit test
Jul 13, 2017
e9145d1
Removed crypto references from unit test base
Jul 13, 2017
cffcbd8
Alphabetized filter dict in test
Jul 14, 2017
31ddc75
Removed extra underscore in reference to use_none
Jul 27, 2017
b020e95
Added unicode specifiers for params with default values in regex filters
Jul 27, 2017
efda5a5
Added to_complex unit test
Jul 27, 2017
b6fb220
Renamed test base to be more generic
Jul 27, 2017
8220a6b
Added infra for YAQL tests
Jul 27, 2017
6d0beec
Added unit test for to_complex in yaql
Jul 27, 2017
a1c7a2b
Moving some things around, added test for time filter
Jul 27, 2017
9ef0a4e
Condensing YAQL unit tests into a single file for now
Jul 27, 2017
4894d44
Added tests for data, json escape, and regex filters
Jul 27, 2017
cb87499
PoC for handling filter vs function usage in Jinja
Jul 28, 2017
3af478a
Rollback previous commit (going to fix upstream instead)
Jul 29, 2017
be47054
Moved functions out of 'filters' dir into 'functions'
Jul 29, 2017
f49a9ff
Alphabetizing some things
Jul 29, 2017
5291379
More filters -> functions renaming
Jul 29, 2017
256a8cd
Fixed reference to get_functions
Jul 29, 2017
3efc840
Removed old filters dir
Jul 29, 2017
a164d6c
to_complex improvements
Jul 29, 2017
739fa88
Added context to functions; revamped testing accordingly
Jul 29, 2017
5dd1696
Style-related corrections
Jul 30, 2017
c12ff85
Reverted import order for config
Jul 31, 2017
97614c8
Finish YAQL unit tests
Jul 31, 2017
8a82648
Reorganized unit tests to follow the OpenStack layout
Jul 31, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions circle.yml
Expand Up @@ -7,3 +7,4 @@ dependencies:
test:
override:
- tox -e pep8
- tox -e py27
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -3,3 +3,4 @@
# process, which may cause wedges in the gate later.

retrying<1.4,>=1.3
semver==2.7.2
19 changes: 19 additions & 0 deletions setup.cfg
Expand Up @@ -10,7 +10,26 @@ mistral.actions =
st2.action = st2mistral.actions.stackstorm:St2Action

mistral.expression.functions =
json_escape = st2mistral.functions.json_escape:json_escape
regex_match = st2mistral.functions.regex:regex_match
regex_replace = st2mistral.functions.regex:regex_replace
regex_search = st2mistral.functions.regex:regex_search
regex_substring = st2mistral.functions.regex:regex_substring
st2kv = st2mistral.functions.stackstorm:st2kv_
to_complex = st2mistral.functions.data:to_complex
to_human_time_from_seconds = st2mistral.functions.time:to_human_time_from_seconds
to_json_string = st2mistral.functions.data:to_json_string
to_yaml_string = st2mistral.functions.data:to_yaml_string
use_none = st2mistral.functions.use_none:use_none
version_compare = st2mistral.functions.version:version_compare
version_bump_major = st2mistral.functions.version:version_bump_major
version_bump_minor = st2mistral.functions.version:version_bump_minor
version_bump_patch = st2mistral.functions.version:version_bump_patch
version_equal = st2mistral.functions.version:version_equal
version_less_than = st2mistral.functions.version:version_less_than
version_match = st2mistral.functions.version:version_match
version_more_than = st2mistral.functions.version:version_more_than
version_strip_patch = st2mistral.functions.version:version_strip_patch

mistral.auth =
st2 = st2mistral.auth.server:St2AuthHandler
Expand Down
38 changes: 38 additions & 0 deletions st2mistral/functions/data.py
@@ -0,0 +1,38 @@
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

import json
import yaml

__all__ = [
'to_complex',
'to_json_string',
'to_yaml_string'
]


def to_complex(context, value):
return json.dumps(value)


def to_json_string(context, value, indent=4, sort_keys=False,
separators=(',', ':')):
return json.dumps(value, indent=indent, separators=separators,
sort_keys=sort_keys)


def to_yaml_string(context, value, indent=4, allow_unicode=True):
return yaml.safe_dump(value, indent=indent, allow_unicode=allow_unicode,
default_flow_style=False)
36 changes: 36 additions & 0 deletions st2mistral/functions/json_escape.py
@@ -0,0 +1,36 @@
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

import json

__all__ = [
'json_escape'
]


def json_escape(context, value):
"""Adds escape sequences to problematic characters in the string

This function simply passes the value to json.dumps
as a convenient way of escaping characters in it

However, before returning, we want to strip the double
quotes at the ends of the string, since we're not looking
for a valid JSON value at the end, just conveniently using
this function to do the escaping. The value could be any
arbitrary value
"""

return json.dumps(value).strip('"')
58 changes: 58 additions & 0 deletions st2mistral/functions/regex.py
@@ -0,0 +1,58 @@
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

import re
import six

__all__ = [
'regex_match',
'regex_replace',
'regex_search'
]


def _get_regex_flags(ignorecase=False):
return re.I if ignorecase else 0


def regex_match(context, value, pattern=u'', ignorecase=False):
if not isinstance(value, six.string_types):
value = str(value)
flags = _get_regex_flags(ignorecase)
return bool(re.match(pattern, value, flags))


def regex_replace(context, value=u'', pattern=u'', replacement=u'',
ignorecase=False):
if not isinstance(value, six.string_types):
value = str(value)
flags = _get_regex_flags(ignorecase)
regex = re.compile(pattern, flags)
return regex.sub(replacement, value)


def regex_search(context, value, pattern=u'', ignorecase=False):
if not isinstance(value, six.string_types):
value = str(value)
flags = _get_regex_flags(ignorecase)
return bool(re.search(pattern, value, flags))


def regex_substring(context, value, pattern=u'', result_index=0,
ignorecase=False):
if not isinstance(value, six.string_types):
value = str(value)
flags = _get_regex_flags(ignorecase)
return re.findall(pattern, value, flags)[result_index]
14 changes: 11 additions & 3 deletions st2mistral/functions/stackstorm.py
Expand Up @@ -24,11 +24,19 @@
from mistral import exceptions as exc
from st2mistral.utils import http


LOG = logging.getLogger(__name__)


def st2kv_(context, key):
def st2kv_(context, key, decrypt=False):
"""Retrieve value for provided key in StackStorm datastore

:param key: User to whom key belongs.
:type key: ``str``
:param decrypt: Request decrypted version of the value (defaults to False)
:type decrypt: ``bool``

:rtype: ``dict``
"""

# Get StackStorm auth token from the action context.
token = None
Expand All @@ -51,7 +59,7 @@ def st2kv_(context, key):
key_id = key

endpoint = st2_ctx['api_url'] + '/keys/' + key_id
params = {'decrypt': True, 'scope': scope}
params = {'decrypt': decrypt, 'scope': scope}

LOG.info(
'Sending HTTP request for custom YAQL function st2kv '
Expand Down
85 changes: 85 additions & 0 deletions st2mistral/functions/time.py
@@ -0,0 +1,85 @@
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

import datetime

__all__ = [
'to_human_time_from_seconds'
]


def to_human_time_from_seconds(context, seconds):
"""Given a time value in seconds, this returns a fuzzy version like 3m5s.

:param time_seconds: Time specified in seconds.
:type time_seconds: ``int`` or ``long`` or ``float``

:rtype: ``str``
"""
assert (isinstance(seconds, int) or isinstance(seconds, long) or
isinstance(seconds, float))

return _get_human_time(seconds)


def _get_human_time(seconds):
"""Takes number of seconds as input and returns a string of form '1h3m5s'.

:param seconds: Number of seconds.
:type seconds: ``int`` or ``long`` or ``float``

:rtype: ``str``
"""

if seconds is None:
return None

if seconds == 0:
return '0s'

if seconds < 1:
return '%s\u03BCs' % seconds # Microseconds

if isinstance(seconds, float):
seconds = long(round(seconds)) # Let's lose microseconds.

timedelta = datetime.timedelta(seconds=seconds)
offset_date = datetime.datetime(1, 1, 1) + timedelta

years = offset_date.year - 1
days = offset_date.day - 1
hours = offset_date.hour
mins = offset_date.minute
secs = offset_date.second

time_parts = [years, days, hours, mins, secs]

first_non_zero_pos = next((i for i, x in enumerate(time_parts) if x), None)

if first_non_zero_pos is None:
return '0s'
else:
time_parts = time_parts[first_non_zero_pos:]

if len(time_parts) == 1:
return '%ss' % tuple(time_parts)
elif len(time_parts) == 2:
return '%sm%ss' % tuple(time_parts)
elif len(time_parts) == 3:
return '%sh%sm%ss' % tuple(time_parts)
elif len(time_parts) == 4:
return '%sd%sh%sm%ss' % tuple(time_parts)
elif len(time_parts) == 5:
return '%sy%sd%sh%sm%ss' % tuple(time_parts)
28 changes: 28 additions & 0 deletions st2mistral/functions/use_none.py
@@ -0,0 +1,28 @@
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

__all__ = [
'use_none'
]


# Magic string to which None type is serialized when using use_none function
NONE_MAGIC_VALUE = '%*****__%NONE%__*****%'


def use_none(context, value):
if value is None:
return NONE_MAGIC_VALUE
return value
62 changes: 62 additions & 0 deletions st2mistral/functions/version.py
@@ -0,0 +1,62 @@
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

import semver

__all__ = [
'version_compare',
'version_more_than',
'version_less_than',
'version_equal',
'version_match',
'version_bump_major',
'version_bump_minor'
]


def version_compare(context, value, pattern):
return semver.compare(value, pattern)


def version_more_than(context, value, pattern):
return semver.compare(value, pattern) == 1


def version_less_than(context, value, pattern):
return semver.compare(value, pattern) == -1


def version_equal(context, value, pattern):
return semver.compare(value, pattern) == 0


def version_match(context, value, pattern):
return semver.match(value, pattern)


def version_bump_major(context, value):
return semver.bump_major(value)


def version_bump_minor(context, value):
return semver.bump_minor(value)


def version_bump_patch(context, value):
return semver.bump_patch(value)


def version_strip_patch(context, value):
return "{major}.{minor}".format(**semver.parse(value))
Empty file.