Skip to content

Commit

Permalink
Merge pull request #14 from hiddeco/set-command
Browse files Browse the repository at this point in the history
Add `set` command to set the value of dot notation path
  • Loading branch information
squaremo authored Aug 15, 2019
2 parents 1d3cf07 + 20a69cf commit 7fc6ab8
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ clean:
touch $@

test:
pytest --hypothesis-show-statistics
pytest --hypothesis-show-statistics test_*
45 changes: 43 additions & 2 deletions kubeyaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
class NotFound(Exception):
pass

class UnresolvablePath(Exception):
pass

def parse_args():
p = argparse.ArgumentParser()
subparsers = p.add_subparsers()
Expand All @@ -24,17 +27,24 @@ def parse_args():
image.add_argument('--image', required=True)
image.set_defaults(func=update_image)

def note(s):
def keyValuePair(s):
k, v = s.split('=')
return k, v

annotation = subparsers.add_parser('annotate', help='update annotations')
annotation.add_argument('--namespace', required=True)
annotation.add_argument('--kind', required=True)
annotation.add_argument('--name', required=True)
annotation.add_argument('notes', nargs='+', type=note)
annotation.add_argument('notes', nargs='+', type=keyValuePair)
annotation.set_defaults(func=update_annotations)

set = subparsers.add_parser('set', help='update values by their dot notation paths')
set.add_argument('--namespace', required=True)
set.add_argument('--kind', required=True)
set.add_argument('--name', required=True)
set.add_argument('paths', nargs='+', type=keyValuePair)
set.set_defaults(func=set_paths)

return p.parse_args()

def yaml():
Expand Down Expand Up @@ -113,6 +123,35 @@ def ensure(d, *keys):
if not found:
raise NotFound()

def set_paths(spec, docs):
def set_path(d, path, value):
keys = path.split(".")
for k in keys[:-1]:
if k not in d:
return False
d = d[k]
if isinstance(d[keys[-1]], collections.Mapping):
return False
d[keys[-1]] = value
return True

found = False
unresolvable = list()
for doc in docs:
if not found:
for m in manifests(doc):
if match_manifest(spec, m):
for k, v in spec.paths:
if not set_path(m, k, v):
unresolvable.append(k)
found = True
break
yield doc
if len(unresolvable):
raise UnresolvablePath(unresolvable)
if not found:
raise NotFound()

def manifests(doc):
if doc['kind'].endswith('List'):
for m in doc['items']:
Expand Down Expand Up @@ -265,6 +304,8 @@ def main():
apply_to_yaml(functools.partial(args.func, args), sys.stdin, sys.stdout)
except NotFound:
bail("manifest not found")
except UnresolvablePath as e:
bail("unable to resolve path(s):\n" + '\n'.join(e.args[0]))

if __name__ == "__main__":
main()
26 changes: 26 additions & 0 deletions test_kubeyaml_annotate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import kubeyaml
from test_kubeyaml import resource, Spec

def test_update_annotations():
man = resource('Deployment', 'default', 'foo')
man['metadata']['annotations'] = {
'fluxcd.io/automated': 'true',
'fluxcd.io/locked': 'false',
}

automated = ['fluxcd.io/automated', 'false'] # should be altered
unlocked = ['fluxcd.io/locked', ''] # should be removed
new = ['fluxcd.io/tag.foo', 'glob:*'] # should be added

args = Spec.from_resource(man)
args.notes = [automated, unlocked, new]

man1 = None
for out in kubeyaml.update_annotations(args, [man]):
man1 = out

assert man1 is not None
assert man1['metadata']['annotations'] == {
automated[0]: automated[1],
new[0]: new[1],
}
60 changes: 60 additions & 0 deletions test_kubeyaml_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import copy
import kubeyaml
from test_kubeyaml import resource, Spec, check_structure

def test_set_paths():
man = resource('HelmRelease', 'default', 'release')
man['spec'] = {
'chart': {
'repository': 'https://kubernetes-charts.storage.googleapis.com/',
'name': 'sample',
'version': '0.1',
},
'values': {
'image': {
'repository': 'image/sample',
'tag': '0.1',
}
}
}

image, tag = 'other/sample', '0.2'

args = Spec.from_resource(man)
args.paths = [['spec.values.image.tag', tag], ['spec.values.image.repository', image]]

man1 = copy.deepcopy(man)
man2 = None
for out in kubeyaml.set_paths(args, [man]):
man2 = out

assert man2 is not None
assert man2['spec']['values']['image']['repository'] == image
assert man2['spec']['values']['image']['tag'] == tag
check_structure(man1, man2)

def test_set_paths_raise_on_non_plain_values():
man = resource('HelmRelease', 'default', 'release')
man['spec'] = {
'chart': {
'repository': 'https://kubernetes-charts.storage.googleapis.com/',
},
}

args = Spec.from_resource(man)
args.paths = [['spec.chart', 'invalid']]

man1 = copy.deepcopy(man)
man2 = None

try:
for out in kubeyaml.set_paths(args, [man]):
man2 = out
except kubeyaml.UnresolvablePath:
pass
else:
assert False, "UnresolvablePath not raised"

assert man2 is not None
assert man1 == man2
check_structure(man1, man2)

0 comments on commit 7fc6ab8

Please sign in to comment.