Skip to content

Commit 45dc5fc

Browse files
committed
Fixed json promise type
Signed-off-by: Victor Moene <victor.moene@northern.tech>
1 parent 1b37ef9 commit 45dc5fc

2 files changed

Lines changed: 50 additions & 37 deletions

File tree

promise-types/json/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Promise type for manipulating `json` files
55
| Name | Type | Description |
66
|---------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
77
| `object` | `data container` | json object type. It can also be json arrays |
8-
| `array` | `slist`, `rlist`, `ilist`, `data array` | json array type. `slist`, `rlist` and `ilist` will only create string arrays. To create array of other types, use `data array` |
8+
| `array` | `data array` | json array type |
99
| `string` | `string` | json string type |
1010
| `number` | `real`, `int` | json number type |
1111
| `primitive` | `string` | Primitives are values that are either `"true"`, `"false"` or `"null"` in json |
@@ -64,9 +64,9 @@ And the content of `/tmp/oldfile.json` will become:
6464

6565
If the field doesn't exist, it is appended. If it already exists, its data will be overwritten.
6666

67-
### Writing types
67+
### Writing arrays
6868

69-
In order to write compound type such as arrays containg booleans, numbers, etc... One has to use the `data container` type in the policy.
69+
In order to write compound type such as arrays containg booleans, numbers, etc... One has to use the `data` type in the policy.
7070

7171
To see what happens if we use
7272

@@ -84,12 +84,12 @@ bundle agent main
8484
8585
json:
8686
"/tmp/example_1.json:json_data"
87-
array => "$(json_data)";
87+
array => "@(json_data)";
8888
8989
"/tmp/example_2.json:real_list"
90-
array => "$(real_list)";
90+
array => "@(real_list)";
9191
"/tmp/example_2.json:bool_list"
92-
array => "$(bool_list)";
92+
array => "@(bool_list)";
9393
}
9494
```
9595

promise-types/json/json_promise_type.py

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
22
import json
3+
import tempfile
4+
import shutil
35

46
from cfengine import PromiseModule, ValidationError, Result, AttributeObject
57

@@ -48,22 +50,23 @@ def validate_promise(self, promiser, attributes, metadata):
4850
if present_types == 0:
4951
raise ValidationError(
5052
"The promiser '{}' is missing a type attribute. The possible types are {}".format(
51-
promiser, str(self.types)
53+
promiser, ", ".join(["'{}'".format(t) for t in self.types])
5254
)
5355
)
5456
elif len(present_types) > 1:
5557
raise ValidationError(
56-
"The attributes {} cannot be together".format(str(self.types))
58+
"The attributes {} cannot be together".format(
59+
", ".join(["'{}'".format(t) for t in self.types])
60+
)
5761
)
5862

59-
filename, _, _ = promiser.partition(":")
60-
if os.path.exists(filename) and not os.path.isfile(filename):
61-
raise ValidationError(
62-
"'{}' already exists and is not a file".format(filename)
63-
)
63+
filename, colon, field = promiser.partition(":")
6464

65-
if not filename.endswith(".json"):
66-
raise ValidationError("'{}' is not a json file")
65+
if not filename:
66+
raise ValidationError("Invalid syntax: missing file name")
67+
68+
if colon and not field:
69+
raise ValidationError("Invalid syntax: field specified but empty")
6770

6871
model = self.create_attribute_object(attributes)
6972
if (
@@ -77,14 +80,16 @@ def validate_promise(self, promiser, attributes, metadata):
7780

7881
if model.array:
7982
if isinstance(model.array, str):
80-
if not is_json_serializable(model.array):
83+
try:
84+
array = json.loads(model.array)
85+
86+
except:
8187
raise ValidationError(
82-
"'{}' is not a valid list".format(model.array)
88+
"'{}' cannot be serialized to a json array".format(model.array)
8389
)
84-
85-
if not isinstance(json.loads(model.array), list):
90+
if not isinstance(array, list):
8691
raise ValidationError(
87-
"'{}' is not a valid data".format(model.array)
92+
"'{}' is not a valid data array".format(model.array)
8893
)
8994

9095
elif not isinstance(model.array, list):
@@ -106,23 +111,18 @@ def evaluate_promise(self, promiser, attributes, metadata):
106111
model = self.create_attribute_object(attributes)
107112
filename, _, field = promiser.partition(":")
108113

114+
if os.path.exists(filename) and not os.path.isfile(filename):
115+
self.log_error("'{}' already exists and is not a file".format(filename))
116+
return Result.NOT_KEPT
117+
109118
# type conversion
110119

111120
datatype = next(t for t in self.types if t in attributes)
112121

113-
match datatype:
114-
case "object" | "array":
115-
data = (
116-
json.loads(attributes[datatype])
117-
if isinstance(attributes[datatype], str)
118-
else attributes[datatype]
119-
)
120-
case "number":
121-
data = float(model.number) if "." in model.number else int(model.number)
122-
case "primitive":
123-
data = None if model.primitive == "null" else model.primitive == "true"
124-
case _: # strings
125-
data = attributes[datatype]
122+
if isinstance(attributes[datatype], str) and not model.string:
123+
data = json.loads(attributes[datatype])
124+
else:
125+
data = attributes[datatype]
126126

127127
# json manipulation
128128

@@ -133,19 +133,32 @@ def evaluate_promise(self, promiser, attributes, metadata):
133133
content = {}
134134

135135
if field:
136+
if not isinstance(content, dict):
137+
content = {}
138+
self.log_warning(
139+
"Tried to access '{}' in '{}' when the content is not subscriptable. Overwriting the file...".format(
140+
field, filename
141+
)
142+
)
143+
136144
if field in content and content[field] == data:
137-
Result.KEPT
145+
return Result.KEPT
138146
content[field] = data
139147
else:
140148
if content == data:
141-
Result.KEPT
149+
return Result.KEPT
142150
content = data
143151

144-
with open(filename, "w") as f:
152+
fd, tmp = tempfile.mkstemp()
153+
154+
with open(tmp, "w") as f:
145155
json.dump(content, f, indent=4)
146156

157+
shutil.move(tmp, filename)
158+
os.close(fd)
159+
147160
self.log_info("Updated '{}'".format(filename))
148-
Result.REPAIRED
161+
return Result.REPAIRED
149162

150163

151164
if __name__ == "__main__":

0 commit comments

Comments
 (0)