Skip to content

Commit d38eab0

Browse files
authored
Merge pull request #115 from olehermanse/json
2 parents c008180 + 43825b5 commit d38eab0

15 files changed

Lines changed: 235 additions & 115 deletions

File tree

cfbs.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,10 @@
152152
"library-for-promise-types-in-python": {
153153
"description": "Library enabling promise types implemented in python.",
154154
"subdirectory": "libraries/python",
155-
"steps": ["copy cfengine.py modules/promises/"]
155+
"steps": [
156+
"copy cfengine_module_library.py modules/promises/cfengine_module_library.py",
157+
"copy cfengine_module_library.py modules/promises/cfengine.py"
158+
]
156159
},
157160
"maintainers-in-motd": {
158161
"description": "Add maintainer and purpose information from CMDB to /etc/motd",

examples/git-using-lib/git_using_lib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from cfengine import PromiseModule, ValidationError, Result
2+
from cfengine_module_library import PromiseModule, ValidationError, Result
33

44

55
class GitPromiseTypeModule(PromiseModule):

examples/gpg/gpg.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
import json
4545
from subprocess import Popen, PIPE
4646
import sys
47-
from cfengine import PromiseModule, ValidationError, Result
47+
from cfengine_module_library import PromiseModule, ValidationError, Result
48+
4849

4950
class GpgKeysPromiseTypeModule(PromiseModule):
5051
def __init__(self):

examples/rss/rss.py

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,59 @@
11
import requests, html, re, os, random
22
import xml.etree.ElementTree as ET
3-
from cfengine import PromiseModule, ValidationError, Result
3+
from cfengine_module_library import PromiseModule, ValidationError, Result
44

55

66
class RssPromiseTypeModule(PromiseModule):
77
def __init__(self):
88
super().__init__("rss_promise_module", "0.0.3")
99

10-
1110
def validate_promise(self, promiser, attributes, metadata):
1211
# check promiser type
1312
if type(promiser) is not str:
1413
raise ValidationError("invalid type for promiser: expected string")
1514

1615
# check that promiser is a valid file path
1716
if not self._is_unix_file(promiser) and not self._is_win_file(promiser):
18-
raise ValidationError(f"invalid value '{promiser}' for promiser: must be a filepath")
17+
raise ValidationError(
18+
f"invalid value '{promiser}' for promiser: must be a filepath"
19+
)
1920

2021
# check that required attribute feed is present
2122
if "feed" not in attributes:
2223
raise ValidationError("Missing required attribute feed")
2324

2425
# check that attribute feed has a valid type
25-
feed = attributes['feed']
26+
feed = attributes["feed"]
2627
if type(feed) is not str:
2728
raise ValidationError("Invalid type for attribute feed: expected string")
2829

2930
# check that attribute feed is a valid file path or url
30-
if not (self._is_unix_file(feed) or self._is_win_file(feed) or self._is_url(feed)):
31-
raise ValidationError(f"Invalid value '{feed}' for attribute feed: must be a file path or url")
31+
if not (
32+
self._is_unix_file(feed) or self._is_win_file(feed) or self._is_url(feed)
33+
):
34+
raise ValidationError(
35+
f"Invalid value '{feed}' for attribute feed: must be a file path or url"
36+
)
3237

3338
# additional checks if optional attribute select is present
3439
if "select" in attributes:
35-
select = attributes['select']
40+
select = attributes["select"]
3641

3742
# check that attribute select has a valid type
3843
if type(select) is not str:
39-
raise ValidationError(f"Invalid type for attribute select: expected string")
44+
raise ValidationError(
45+
f"Invalid type for attribute select: expected string"
46+
)
4047

4148
# check that attribute select has a valid value
42-
if select != 'newest' and select != 'oldest' and select != 'random':
43-
raise ValidationError(f"Invalid value '{select}' for attribute select: must be newest, oldest or random")
44-
49+
if select != "newest" and select != "oldest" and select != "random":
50+
raise ValidationError(
51+
f"Invalid value '{select}' for attribute select: must be newest, oldest or random"
52+
)
4553

4654
def evaluate_promise(self, promiser, attributes, metadata):
4755
# get attriute feed
48-
feed = attributes['feed']
56+
feed = attributes["feed"]
4957

5058
# fetch resource
5159
resource = self._get_resource(feed)
@@ -65,81 +73,83 @@ def evaluate_promise(self, promiser, attributes, metadata):
6573

6674
return result
6775

68-
6976
def _get_resource(self, path):
7077
if self._is_url(path):
7178
# fetch from url
7279
self.log_verbose(f"Fetching feed from url '{path}'")
7380
response = requests.get(path)
7481
if response.ok:
7582
return response.content
76-
self.log_error(f"Failed to fetch feed from url '{path}'': status code '{response.status_code}'")
83+
self.log_error(
84+
f"Failed to fetch feed from url '{path}'': status code '{response.status_code}'"
85+
)
7786
return None
7887

7988
# fetch from file
8089
try:
8190
self.log_verbose(f"Reading feed from file '{path}'")
82-
with open(path, 'r', encoding='utf-8') as f:
91+
with open(path, "r", encoding="utf-8") as f:
8392
resource = f.read()
8493
return resource
8594
except Exception as e:
8695
self.log_error(f"Failed to open file '{path}' for reading: {e}")
8796
return None
8897

89-
9098
def _get_items(self, res, path):
9199
# extract descriptions in /channel/item
92100
try:
93101
self.log_verbose(f"Parsing feed '{path}'")
94102
items = []
95103
root = ET.fromstring(res)
96-
for item in root.findall('./channel/item'):
104+
for item in root.findall("./channel/item"):
97105
for child in item:
98-
if child.tag == 'description':
106+
if child.tag == "description":
99107
items.append(child.text)
100108
return items
101109
except Exception as e:
102110
self.log_error(f"Failed to parse feed '{path}': {e}")
103111
return None
104112

105-
106113
def _pick_item(self, items, attributes):
107114
# Pick newest item as default
108115
item = items[0]
109116

110117
# Select item from feed
111118
if "select" in attributes:
112-
select = attributes['select']
113-
if select == 'random':
119+
select = attributes["select"]
120+
if select == "random":
114121
self.log_verbose("Selecting random item from feed")
115122
item = random.choice(items)
116-
elif select == 'oldest':
123+
elif select == "oldest":
117124
self.log_verbose("Selecting oldest item from feed")
118-
item = items[- 1]
125+
item = items[-1]
119126
else:
120127
self.log_verbose("Selecting newest item from feed")
121128
else:
122129
self.log_verbose("Selecting newest item as default")
123130
return item
124131

125-
126132
def _write_promiser(self, item, promiser):
127133
file_exist = os.path.isfile(promiser)
128134

129135
if file_exist:
130136
try:
131-
with open(promiser, 'r', encoding='utf-8') as f:
137+
with open(promiser, "r", encoding="utf-8") as f:
132138
if f.read() == item:
133-
self.log_verbose(f"File '{promiser}' exists and is up to date, no changes needed")
139+
self.log_verbose(
140+
f"File '{promiser}' exists and is up to date, no changes needed"
141+
)
134142
return Result.KEPT
135143
except Exception as e:
136144
self.log_error(f"Failed to open file '{promiser}' for reading: {e}")
137145
return Result.NOT_KEPT
138146

139147
try:
140-
with open(promiser, 'w', encoding='utf-8') as f:
148+
with open(promiser, "w", encoding="utf-8") as f:
141149
if file_exist:
142-
self.log_info(f"File '{promiser}' exists but contents differ, updating content")
150+
self.log_info(
151+
f"File '{promiser}' exists but contents differ, updating content"
152+
)
143153
else:
144154
self.log_info(f"File '{promiser}' does not exist, creating file")
145155
f.write(item)
@@ -148,17 +158,20 @@ def _write_promiser(self, item, promiser):
148158
self.log_error(f"Failed to open file '{promiser}' for writing: {e}")
149159
return Result.NOT_KEPT
150160

151-
152161
def _is_win_file(self, path):
153162
return re.search(r"^[a-zA-Z]:\\[\\\S|*\S]?.*$", path) != None
154163

155-
156164
def _is_unix_file(self, path):
157165
return re.search(r"^(/[^/ ]*)+/?$", path) != None
158166

159-
160167
def _is_url(self, path):
161-
return re.search(r"^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+", path) != None
168+
return (
169+
re.search(
170+
r"^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+",
171+
path,
172+
)
173+
!= None
174+
)
162175

163176

164177
if __name__ == "__main__":

examples/site-up/site_up.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import ssl
33
import urllib.request
44
import urllib.error
5-
from cfengine import PromiseModule, ValidationError, Result
5+
from cfengine_module_library import PromiseModule, ValidationError, Result
66

77

88
class SiteUpPromiseTypeModule(PromiseModule):

libraries/python/cfengine.py renamed to libraries/python/cfengine_module_library.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
"""
2+
CFEngine module library
3+
4+
This library can be used to implement CFEngine modules in python.
5+
Currently, this is for implementing custom promise types,
6+
but it might be expanded to other types of modules in the future,
7+
for example custom functions.
8+
"""
9+
110
import sys
211
import json
312
import traceback
@@ -49,8 +58,9 @@ def _should_send_log(level_set, msg_level):
4958
# for auditing/changelog and all modules are required to send info: messages
5059
# for all REPAIRED promises. A similar logic applies to errors and warnings,
5160
# IOW, anything at or above the info level.
52-
return ((_LOG_LEVELS[msg_level] <= _LOG_LEVELS["info"]) or
53-
(_LOG_LEVELS[msg_level] <= _LOG_LEVELS[level_set]))
61+
return (_LOG_LEVELS[msg_level] <= _LOG_LEVELS["info"]) or (
62+
_LOG_LEVELS[msg_level] <= _LOG_LEVELS[level_set]
63+
)
5464

5565

5666
def _cfengine_type(typing):
@@ -71,12 +81,14 @@ class AttributeObject(object):
7181
def __init__(self, d):
7282
for key, value in d.items():
7383
setattr(self, key, value)
84+
7485
def __repr__(self):
7586
return "{}({})".format(
7687
self.__class__.__qualname__,
77-
", ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items())
88+
", ".join("{}={!r}".format(k, v) for k, v in self.__dict__.items()),
7889
)
7990

91+
8092
class ValidationError(Exception):
8193
def __init__(self, message):
8294
self.message = message
@@ -380,6 +392,8 @@ def _handle_evaluate(self, promiser, attributes, request):
380392
try:
381393
results = self.evaluate_promise(promiser, attributes, metadata)
382394

395+
assert results is not None # Most likely someone forgot to return something
396+
383397
# evaluate_promise should return either a result or a (result, result_classes) pair
384398
if type(results) == str:
385399
self._result = results
@@ -389,7 +403,9 @@ def _handle_evaluate(self, promiser, attributes, request):
389403
self._result_classes = results[1]
390404
except Exception as e:
391405
self.log_critical(
392-
"{error_type}: {error}".format(error_type=type(e).__name__, error=e)
406+
"{error_type}: {error} (Bug in python promise type module, run with --debug for traceback)".format(
407+
error_type=type(e).__name__, error=e
408+
)
393409
)
394410
self._add_traceback_to_response()
395411
self._result = Result.ERROR

promise-types/ansible/ansible_promise.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Dict, Tuple, List
44

5-
from cfengine import PromiseModule, ValidationError, Result
5+
from cfengine_module_library import PromiseModule, ValidationError, Result
66

77
try:
88
from ansible import context
@@ -73,7 +73,7 @@ def v2_playbook_on_stats(self, stats):
7373
class AnsiblePromiseTypeModule(PromiseModule):
7474
def __init__(self, **kwargs):
7575
super(AnsiblePromiseTypeModule, self).__init__(
76-
"ansible_promise_module", "0.2.2", **kwargs
76+
"ansible_promise_module", "0.0.0", **kwargs
7777
)
7878

7979
def must_be_absolute(v):

promise-types/git/git.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
from typing import Dict, List, Optional
66

7-
from cfengine import PromiseModule, ValidationError, Result
7+
from cfengine_module_library import PromiseModule, ValidationError, Result
88

99

1010
class GitPromiseTypeModule(PromiseModule):
1111
def __init__(self, **kwargs):
1212
super(GitPromiseTypeModule, self).__init__(
13-
"git_promise_module", "0.2.5", **kwargs
13+
"git_promise_module", "0.0.0", **kwargs
1414
)
1515

1616
def destination_must_be_absolute(v):
@@ -161,7 +161,12 @@ def evaluate_promise(self, promiser: str, attributes: Dict, metadata: Dict):
161161
# checkout the branch, if different from the current one
162162
output = self._git(
163163
model,
164-
[model.executable, "rev-parse", "--abbrev-ref", "HEAD".format()],
164+
[
165+
model.executable,
166+
"rev-parse",
167+
"--abbrev-ref",
168+
"HEAD".format(),
169+
],
165170
cwd=model.destination,
166171
)
167172
detached = False
@@ -256,7 +261,7 @@ def _git_envvars(self, model: object):
256261
env["GIT_SSH_COMMAND"] = model.ssh_executable
257262
if model.ssh_options:
258263
env["GIT_SSH_COMMAND"] += " " + model.ssh_options
259-
if not 'HOME' in env:
264+
if not "HOME" in env:
260265
# git should have a HOME env var to retrieve .gitconfig, .git-credentials, etc
261266
env["HOME"] = str(Path.home())
262267
return env

promise-types/groups/groups.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import re
22
import json
33
from subprocess import Popen, PIPE
4-
from cfengine import PromiseModule, ValidationError, Result
4+
from cfengine_module_library import PromiseModule, ValidationError, Result
55

66

77
class GroupsPromiseTypeModule(PromiseModule):
88
def __init__(self):
9-
super().__init__("groups_promise_module", "0.2.4")
9+
super().__init__("groups_promise_module", "0.0.0")
1010
self._name_regex = re.compile(r"^[a-z_][a-z0-9_-]*[$]?$")
1111
self._name_maxlen = 32
1212

0 commit comments

Comments
 (0)