Feature #19: a user can now create a new composition with fine-grained limits (#22)
Signed-off-by: Pedro Escaleira <pedroescaleira@hotmail.com>
diff --git a/src/conductor/conductor.py b/src/conductor/conductor.py
index 783730d..00339cc 100644
--- a/src/conductor/conductor.py
+++ b/src/conductor/conductor.py
@@ -72,7 +72,7 @@
*composition["annotations"]
]
- return { 'name': composition['name'], 'action': { 'exec': { 'kind': 'python:3', 'code':code }, 'annotations': annotations } }
+ return { 'name': composition['name'], 'action': { 'exec': { 'kind': 'python:3', 'code':code }, 'annotations': annotations, 'limits': composition['limits'] } }
def openwhisk(options):
''' return enhanced openwhisk client capable of deploying compositions '''
diff --git a/src/pydeploy/__main__.py b/src/pydeploy/__main__.py
index 548358a..36414df 100644
--- a/src/pydeploy/__main__.py
+++ b/src/pydeploy/__main__.py
@@ -22,86 +22,147 @@
import conductor
import sys
-def keyValue(a):
- parts = a.partition('=')
- if parts[1] != '=':
- raise 'Annotation syntax must be "KEY=VALUE"'
- return { 'key': parts[0], 'value': parts[2] }
-def keyValueFromFile(a):
- parts = a.partition('=')
- if parts[1] != '=':
- raise 'Annotation syntax must be "KEY=FILE"'
+def key_value_arg_verification(a: str):
+ parts = a.partition("=")
+ if parts[1] != "=":
+ raise Exception('Annotation syntax must be "KEY=VALUE"')
+ return parts
- with open(parts[2], encoding='UTF-8') as f:
+
+def annotation_key_value(a: str):
+ parts = key_value_arg_verification(a)
+ return {"key": parts[0], "value": parts[2]}
+
+
+def annotation_key_value_file(a: str):
+ parts = key_value_arg_verification(a)
+ with open(parts[2], encoding="UTF-8") as f:
value = json.load(f)
- return { 'key': parts[0], 'value': value }
+ return {"key": parts[0], "value": value}
def main():
- parser = argparse.ArgumentParser(description='deploy composition', prog='pydeploy', usage='%(prog)s composition composition.json [flags]')
- parser.add_argument('name', metavar='composition', type=str, help='composition name')
- parser.add_argument('file', metavar='composition', type=str, help='composition')
- parser.add_argument('--apihost', action='store', metavar='HOST', help='API HOST')
- parser.add_argument('-i', '--insecure', action='store_true', help='bypass certificate checking')
- parser.add_argument('-u', '--auth', metavar='KEY', help='authorization KEY')
- parser.add_argument('-v', '--version', action='version', version='%(prog)s '+ composer.__version__)
- parser.add_argument('-a', '--annotation', action='append', nargs=1, help='add KEY annotation with VALUE')
- parser.add_argument('-A', '--annotation-file', action='append', nargs=1, help='add KEY annotation with FILE content')
- parser.add_argument('-w', '--overwrite', action='store_true', help='overwrite actions if already defined')
+ parser = argparse.ArgumentParser(
+ description="deploy composition",
+ prog="pydeploy",
+ usage="%(prog)s composition composition.json [flags]",
+ )
+ parser.add_argument(
+ "name", metavar="composition", type=str, help="composition name"
+ )
+ parser.add_argument("file", metavar="composition", type=str, help="composition")
+ parser.add_argument("--apihost", action="store", metavar="HOST", help="API HOST")
+ parser.add_argument(
+ "-i", "--insecure", action="store_true", help="bypass certificate checking"
+ )
+ parser.add_argument("-u", "--auth", metavar="KEY", help="authorization KEY")
+ parser.add_argument(
+ "-v", "--version", action="version", version="%(prog)s " + composer.__version__
+ )
+ parser.add_argument(
+ "-a",
+ "--annotation",
+ action="append",
+ nargs=1,
+ help="add KEY annotation with VALUE",
+ )
+ parser.add_argument(
+ "-A",
+ "--annotation-file",
+ action="append",
+ nargs=1,
+ help="add KEY annotation with FILE content",
+ )
+ parser.add_argument("-l", "--limits", nargs=1, help="define limits for this composition, providing a JSON dictionary")
+ parser.add_argument(
+ "-L",
+ "--limits-file",
+ nargs=1,
+ help="define limits for this composition, providing a JSON file",
+ )
+ parser.add_argument(
+ "-w",
+ "--overwrite",
+ action="store_true",
+ help="overwrite actions if already defined",
+ )
args = parser.parse_args()
try:
filename = args.file
- with open(filename, encoding='UTF-8') as f:
+ with open(filename, encoding="UTF-8") as f:
composition = json.load(f)
- if 'ast' not in composition:
- raise 'Composition must have a field "ast" of type dictionary'
- if 'composition' not in composition:
- raise 'Composition must have a field "composition" of type dictionary'
- if 'version' not in composition:
- raise 'Composition must have a field "composition" of type dictionary'
- if 'actions' in composition:
- if not isinstance(composition['actions'], list):
- raise 'Optional field "actions" must be an array'
+ if "ast" not in composition:
+ raise Exception('Composition must have a field "ast" of type dictionary')
+ if "composition" not in composition:
+ raise Exception(
+ 'Composition must have a field "composition" of type dictionary'
+ )
+ if "version" not in composition:
+ raise Exception(
+ 'Composition must have a field "composition" of type dictionary'
+ )
+ if "actions" in composition:
+ if not isinstance(composition["actions"], list):
+ raise Exception('Optional field "actions" must be an array')
- composition['annotations'] = []
+ composition["annotations"] = []
+ composition["limits"] = {}
if args.annotation is not None:
- composition['annotations'].extend([keyValue(a[0]) for a in args.annotation])
+ composition["annotations"].extend(
+ [annotation_key_value(a[0]) for a in args.annotation]
+ )
if args.annotation_file is not None:
- composition['annotations'].extend([keyValueFromFile(a[0]) for a in args.annotation_file])
+ composition["annotations"].extend(
+ [annotation_key_value_file(a[0]) for a in args.annotation_file]
+ )
+
+ if args.limits is not None:
+ composition["limits"].update(
+ (lambda limits: json.loads(limits))(args.limits[0])
+ )
+
+ if args.limits_file is not None:
+ composition["limits"].update(
+ (lambda name: json.load(open(name, encoding="UTF-8")))(
+ args.limits_file[0]
+ )
+ )
except Exception as err:
+ raise err
print(err)
- sys.exit(422 - 256) # Unprocessable Entity
+ sys.exit(422 - 256) # Unprocessable Entity
-
- options = { 'ignore_certs': args.insecure }
+ options = {"ignore_certs": args.insecure}
if args.apihost is not None:
- options['apihost'] = args.apihost
+ options["apihost"] = args.apihost
if args.auth is not None:
- options['api_key'] = args.auth
+ options["api_key"] = args.auth
try:
- composition['name'] = composer.parse_action_name(args.name)
+ composition["name"] = composer.parse_action_name(args.name)
except Exception as err:
print(err)
- sys.exit(400 - 256) # Bad Request
+ sys.exit(400 - 256) # Bad Request
try:
- actions = conductor.openwhisk(options).compositions.deploy(composition, args.overwrite)
- names = ' '.join([n['name'] for n in actions])
- print('ok: created action'+ ('s' if len(names) > 1 else '') + '' + names)
+ actions = conductor.openwhisk(options).compositions.deploy(
+ composition, args.overwrite
+ )
+ names = " ".join([n["name"] for n in actions])
+ print("ok: created action" + ("s" if len(names) > 1 else "") + "" + names)
except Exception as err:
print(err.error)
sys.exit(500 - 256)
-if __name__ == '__main__':
- main()
+if __name__ == "__main__":
+ main()