Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 68D1E200BC4 for ; Sat, 19 Nov 2016 11:40:39 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 67526160B09; Sat, 19 Nov 2016 10:40:39 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 1C73F160AEF for ; Sat, 19 Nov 2016 11:40:37 +0100 (CET) Received: (qmail 42365 invoked by uid 500); 19 Nov 2016 10:40:37 -0000 Mailing-List: contact dev-help@ariatosca.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ariatosca.incubator.apache.org Delivered-To: mailing list dev@ariatosca.incubator.apache.org Received: (qmail 42353 invoked by uid 99); 19 Nov 2016 10:40:37 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd2-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 19 Nov 2016 10:40:37 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd2-us-west.apache.org (ASF Mail Server at spamd2-us-west.apache.org) with ESMTP id D05061A5F0E for ; Sat, 19 Nov 2016 10:40:36 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd2-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -6.218 X-Spam-Level: X-Spam-Status: No, score=-6.218 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-2.999, URIBL_BLOCKED=0.001] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd2-us-west.apache.org [10.40.0.9]) (amavisd-new, port 10024) with ESMTP id d4KB3vGBrbnX for ; Sat, 19 Nov 2016 10:40:32 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id C2A845F1A1 for ; Sat, 19 Nov 2016 10:40:30 +0000 (UTC) Received: (qmail 42314 invoked by uid 99); 19 Nov 2016 10:40:29 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 19 Nov 2016 10:40:29 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id A0216E08F2; Sat, 19 Nov 2016 10:40:29 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: dankilman@apache.org To: dev@ariatosca.incubator.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: incubator-ariatosca git commit: ARIA-23 TBD [Forced Update!] Date: Sat, 19 Nov 2016 10:40:29 +0000 (UTC) archived-at: Sat, 19 Nov 2016 10:40:39 -0000 Repository: incubator-ariatosca Updated Branches: refs/heads/ARIA-23-integrate-csar-packager 8c72e57b0 -> 023b58085 (forced update) ARIA-23 TBD Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/023b5808 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/023b5808 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/023b5808 Branch: refs/heads/ARIA-23-integrate-csar-packager Commit: 023b58085727d1722d41c6234cedaba23919bbfa Parents: 3323819 Author: Dan Kilman Authored: Thu Nov 17 12:43:49 2016 +0200 Committer: Dan Kilman Committed: Sat Nov 19 12:40:22 2016 +0200 ---------------------------------------------------------------------- aria/cli/args_parser.py | 48 ++++++++++ aria/cli/cli.py | 6 ++ aria/cli/commands.py | 89 +++++++++++++++++- aria/cli/csar/__init__.py | 14 +++ aria/cli/csar/constants.py | 27 ++++++ aria/cli/csar/reader.py | 198 ++++++++++++++++++++++++++++++++++++++++ aria/cli/csar/signature.py | 42 +++++++++ aria/cli/csar/writer.py | 40 ++++++++ 8 files changed, 463 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/args_parser.py ---------------------------------------------------------------------- diff --git a/aria/cli/args_parser.py b/aria/cli/args_parser.py index 56fd074..8382fd1 100644 --- a/aria/cli/args_parser.py +++ b/aria/cli/args_parser.py @@ -69,6 +69,9 @@ def config_parser(parser=None): add_execute_parser(sub_parser) add_parse_parser(sub_parser) add_spec_parser(sub_parser) + add_csar_create_parser(sub_parser) + add_csar_open_parser(sub_parser) + add_csar_validate_parser(sub_parser) return parser @@ -199,3 +202,48 @@ def add_spec_parser(spec): '--csv', action='store_true', help='output as CSV') + + +@sub_parser_decorator( + name='csar-create', + help='Create a CSAR file from a TOSCA service template directory', + formatter_class=SmartFormatter) +def add_csar_create_parser(parse): + parse.add_argument( + 'source', + help='Service template directory') + parse.add_argument( + 'entry', + help='Entry definition file relative to service template directory') + parse.add_argument( + '-d', '--destination', + help='Output CSAR zip destination', + required=True) + parse.add_argument( + '-a', '--author', + help='Service template author (injected into CSAR metadata file)', + default='TOSCA') + + +@sub_parser_decorator( + name='csar-open', + help='Extracts a CSAR file to a TOSCA service template directory', + formatter_class=SmartFormatter) +def add_csar_open_parser(parse): + parse.add_argument( + 'source', + help='CSAR file location') + parse.add_argument( + '-d', '--destination', + help='Output directory to extract the CSAR into', + required=True) + + +@sub_parser_decorator( + name='csar-validate', + help='Validates a CSAR file', + formatter_class=SmartFormatter) +def add_csar_validate_parser(parse): + parse.add_argument( + 'source', + help='CSAR file location') http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/cli.py ---------------------------------------------------------------------- diff --git a/aria/cli/cli.py b/aria/cli/cli.py index ad9784c..c5830d5 100644 --- a/aria/cli/cli.py +++ b/aria/cli/cli.py @@ -33,6 +33,9 @@ from .commands import ( ExecuteCommand, ParseCommand, SpecCommand, + CSARCreateCommand, + CSAROpenCommand, + CSARValidateCommand, ) __version__ = '0.1.0' @@ -50,6 +53,9 @@ class AriaCli(LoggerMixin): 'execute': ExecuteCommand.with_logger(base_logger=self.logger), 'parse': ParseCommand.with_logger(base_logger=self.logger), 'spec': SpecCommand.with_logger(base_logger=self.logger), + 'csar-create': CSARCreateCommand.with_logger(base_logger=self.logger), + 'csar-open': CSAROpenCommand.with_logger(base_logger=self.logger), + 'csar-validate': CSARValidateCommand.with_logger(base_logger=self.logger), } def __enter__(self): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/commands.py ---------------------------------------------------------------------- diff --git a/aria/cli/commands.py b/aria/cli/commands.py index 57118a7..600fe12 100644 --- a/aria/cli/commands.py +++ b/aria/cli/commands.py @@ -21,9 +21,12 @@ import json import os import sys import csv +import shutil +import tempfile from glob import glob from importlib import import_module +import jinja2 from yaml import safe_load, YAMLError from .. import (application_model_storage, application_resource_storage) @@ -43,7 +46,7 @@ from ..parser.consumption import ( Inputs, Instance ) -from ..parser.loading import (UriLocation, URI_LOADER_PREFIXES) +from ..parser.loading import (LiteralLocation, UriLocation, URI_LOADER_PREFIXES) from ..utils.application import StorageManager from ..utils.caching import cachedmethod from ..utils.console import (puts, Colored, indent) @@ -61,6 +64,8 @@ from .storage import ( user_space, local_storage, ) +from .csar import writer +from .csar.reader import CSARReader class BaseCommand(LoggerMixin): @@ -388,3 +393,85 @@ class SpecCommand(BaseCommand): with indent(2): for k, v in details.iteritems(): puts('%s: %s' % (Colored.magenta(k), v)) + + +class BaseCSARCommand(BaseCommand): + + @staticmethod + def _parse_text(reader): + context = ConsumptionContext() + context.loading.prefixes += [os.path.join(reader.destination, 'definitions')] + context.presentation.location = LiteralLocation(reader.entry_definitions_yaml) + chain = ConsumerChain(context, (Read, Validate, Model, Instance)) + chain.consume() + if context.validation.dump_issues(): + raise RuntimeError('Validation failed') + return context.modeling.instance + + def _dump_info(self, reader): + template = jinja2.Template(''' +Path: {{r.destination}} +Author: {{r.author}} +Version: {{r.version}} +Metadata file version: {{r.metadata_file_version}} +Entry definitions: {{r.entry_definitions}} +{% for node in nodes %} +Node: {{node.id}} + type: {{node.type_name}} + template: {{node.template_name}} + properties: + {%- for name, prop in node.properties.items() %} + {{name}}: {{prop.value}} + {%- endfor %} + capabilities: + {%- for capability in node.capabilities.values() %} + {{capability.name}}: + {%- for name, prop in capability.properties.items() %} + {{name}}: {{prop.value}} + {%- endfor -%} + {% endfor %} +{% endfor %} +''') + aria_res = self._parse_text(reader=reader) + self.logger.info(template.render(r=reader, + nodes=aria_res.nodes.values())) + + def _read(self, source, destination): + reader = CSARReader(source=source, + destination=destination, + logger=self.logger) + self._dump_info(reader=reader) + + def _validate(self, source): + workdir = tempfile.mkdtemp() + try: + self._read(source=source, destination=workdir) + finally: + shutil.rmtree(workdir, ignore_errors=True) + + +class CSARCreateCommand(BaseCSARCommand): + + def __call__(self, args_namespace, unknown_args): + super(CSARCreateCommand, self).__call__(args_namespace, unknown_args) + writer.write(source=args_namespace.source, + entry=args_namespace.entry, + destination=args_namespace.destination, + author=args_namespace.author, + logger=self.logger) + self._validate(source=args_namespace.destination) + + +class CSAROpenCommand(BaseCSARCommand): + + def __call__(self, args_namespace, unknown_args): + super(CSAROpenCommand, self).__call__(args_namespace, unknown_args) + self._read(source=args_namespace.source, + destination=args_namespace.destination) + + +class CSARValidateCommand(BaseCSARCommand): + + def __call__(self, args_namespace, unknown_args): + super(CSARValidateCommand, self).__call__(args_namespace, unknown_args) + self._validate(source=args_namespace.source) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/csar/__init__.py ---------------------------------------------------------------------- diff --git a/aria/cli/csar/__init__.py b/aria/cli/csar/__init__.py new file mode 100644 index 0000000..ae1e83e --- /dev/null +++ b/aria/cli/csar/__init__.py @@ -0,0 +1,14 @@ +# Licensed to the Apache Software Foundation (ASF) 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. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/csar/constants.py ---------------------------------------------------------------------- diff --git a/aria/cli/csar/constants.py b/aria/cli/csar/constants.py new file mode 100644 index 0000000..4ac0ae2 --- /dev/null +++ b/aria/cli/csar/constants.py @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) 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. + +META_FILE = 'TOSCA-Metadata/TOSCA.meta' + +META_FILE_VERSION_KEY = 'TOSCA-Meta-File-Version' +META_CSAR_VERSION_KEY = 'CSAR-Version' +META_CREATED_BY_KEY = 'Created-By' +META_ENTRY_DEFINITIONS_KEY = 'Entry-Definitions' + +META_TEMPLATE_NAME_KEY = 'template_name' +META_TEMPLATE_AUTHOR_KEY = 'template_author' +META_TEMPLATE_VERSION_KEY = 'template_version' + +META_MIMETYPES_GLOB = 'mimetypes/*.types' http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/csar/reader.py ---------------------------------------------------------------------- diff --git a/aria/cli/csar/reader.py b/aria/cli/csar/reader.py new file mode 100644 index 0000000..656db65 --- /dev/null +++ b/aria/cli/csar/reader.py @@ -0,0 +1,198 @@ +# Licensed to the Apache Software Foundation (ASF) 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 base64 +import os +import mimetypes +import hashlib +import glob +import pprint +import zipfile +import functools + +from ruamel import yaml + +from . import constants + + +class CSARReader(object): + + def __init__(self, source, destination, logger): + self.log = logger + self.source = os.path.normpath(source) + self.destination = destination + self.metadata = {} + self._extract() + self._validate() + + @property + def has_metadata_file(self): + return os.path.isfile(os.path.join(self.destination, constants.META_FILE)) + + @property + def artifacts(self): + return self.metadata.get('artifacts', {}) + + @property + def author(self): + return (self.metadata.get(constants.META_CREATED_BY_KEY) or + self.metadata.get(constants.META_TEMPLATE_AUTHOR_KEY)) + + @property + def version(self): + return (self.metadata.get(constants.META_CSAR_VERSION_KEY) or + self.metadata.get(constants.META_TEMPLATE_VERSION_KEY)) + + @property + def metadata_file_version(self): + return self.metadata.get(constants.META_FILE_VERSION_KEY) + + @property + def template_name(self): + return self.metadata.get(constants.META_TEMPLATE_NAME_KEY) + + @property + def entry_definitions(self): + return self.metadata.get(constants.META_ENTRY_DEFINITIONS_KEY) + + @property + def entry_definitions_yaml(self): + with open(os.path.join(self.destination, self.entry_definitions)) as f: + return yaml.load(f) + + def _extract(self): + if not self.source: + raise RuntimeError('Missing CSAR file') + if not zipfile.is_zipfile(self.source): + raise RuntimeError('CSAR file is not in ZIP format') + self.log.debug('Extracting CSAR contents') + if not os.path.isdir(self.destination): + os.mkdir(self.destination) + with zipfile.ZipFile(self.source) as f: + f.extractall(self.destination) + self.log.debug('CSAR contents successfully extracted') + + def _validate(self): + if self.has_metadata_file: + self.metadata.update(self._validate_metadata_file()) + else: + self.metadata.update(self._validate_metadata_inline()) + self._validate_entry_definitions() + self._validate_artifacts() + + @staticmethod + def _validate_metadata_key(metadata, key, expected=None): + if not metadata.get(key): + raise RuntimeError('Missing metadata "{0}"'.format(key)) + if expected and str(metadata[key]) != expected: + raise RuntimeError('Metadata "{0}" must be {1}'.format(key, expected)) + + def _validate_metadata_file(self): + csar_metafile = os.path.join(self.destination, constants.META_FILE) + self.log.debug('CSAR metadata file: {0}'.format(csar_metafile)) + self.log.debug('Attempting to parse CSAR metadata YAML') + with open(csar_metafile) as f: + metadata = yaml.load(f) + self.log.debug('CSAR metadata:\n{0}'.format(pprint.pformat(metadata))) + validate_key = functools.partial(self._validate_metadata_key, metadata) + validate_key(constants.META_FILE_VERSION_KEY, expected='1.0') + validate_key(constants.META_CSAR_VERSION_KEY, expected='1.1') + validate_key(constants.META_CREATED_BY_KEY) + validate_key(constants.META_ENTRY_DEFINITIONS_KEY) + return metadata + + def _validate_metadata_inline(self): + self.log.debug('Searching for TOSCA template file with metadata') + root_definitions = [] + for ext in ['yaml', 'yml']: + root_definitions.extend(glob.glob('{0}/*.{1}'.format(self.destination, ext))) + if len(root_definitions) != 1: + raise RuntimeError('Exactly 1 YAML file must exist in the CSAR root directory') + root_definition = root_definitions[0] + self.log.debug('Attempting to parse CSAR metadata YAML') + with open(root_definition) as def_file: + definition_data = yaml.load(def_file) + metadata = definition_data.get('metadata') + if not metadata: + raise RuntimeError('Missing metadata section') + validate_key = functools.partial(self._validate_metadata_key, metadata) + validate_key(constants.META_TEMPLATE_VERSION_KEY, expected='1.1') + validate_key(constants.META_TEMPLATE_AUTHOR_KEY) + validate_key(constants.META_TEMPLATE_NAME_KEY) + metadata[constants.META_ENTRY_DEFINITIONS_KEY] = root_definition + return metadata + + def _validate_entry_definitions(self): + self.log.debug('CSAR entry definitions: {0}'.format(self.entry_definitions)) + if not self.has_metadata_file: + self.log.debug('Using inline metadata; skipping...') + return + if not os.path.isfile(os.path.join(self.destination, self.entry_definitions)): + raise RuntimeError('"{0}" points to "{1}", but the file does not exist'.format( + constants.META_ENTRY_DEFINITIONS_KEY, self.entry_definitions)) + + def _validate_artifacts(self): + self.log.debug('Searching for user-defined MIME types') + types = glob.glob(os.path.join(self.destination, constants.META_MIMETYPES_GLOB)) + self.log.debug('Loading {0} user-defined MIME types'.format(len(types))) + mimetypes.init(types or None) + self.log.debug('Checking for artifacts') + if not self.artifacts: + self.log.debug('No artifacts declared') + return + for name, artifact in self.artifacts.iteritems(): + self._validate_artifact(name, artifact) + + def _validate_artifact(self, name, artifact): + self.log.debug('Validating artifact: {0}'.format(name)) + self.log.debug('Checking if artifact file exists') + artifact_path = os.path.join(self.destination, name) + if not os.path.isfile(artifact_path): + raise RuntimeError('Artifact "{0}" declared, but file does not exist'.format(name)) + if 'content-type' not in artifact: + raise RuntimeError('Artifact missing "content-type"') + self.log.debug('Artifact content-type: {0}'.format(artifact['content-type'])) + split_content_type = artifact['content-type'].split('/') + if len(split_content_type) < 2: + raise RuntimeError('Artifact content-type must comply with the "type/subtype" ' + 'structure') + if not split_content_type[-1].startswith('vnd.'): + self.log.warn('Artifact content-type subtype should start with "vnd."') + self.log.debug('Checking content-type against known MIME types') + if artifact['content-type'] not in mimetypes.types_map.values(): + self.log.warn('Could not match artifact content-type with any known MIME type') + self.log.debug('Checking artifact MIME type against content-type') + mime_type = mimetypes.guess_type(artifact_path)[0] + if mime_type is None: + self.log.warn('Could not match artifact to a known MIME type') + if mime_type != artifact['content-type']: + self.log.warn('Artifact content-type does not match the artifacts MIME type') + if 'signature' in artifact: + sig = artifact['signature'] + algorithm = sig.get('algorithm') + digest = sig.get('digest') + if not algorithm: + raise RuntimeError('Artifact signature declared, but no algorithm was found') + if not digest: + raise RuntimeError('Artifact signature declared, but no digest was found') + self.log.debug('Decoding base64-encoded artifact digest') + digest = base64.b64decode(digest).strip() + self.log.debug('Decoded artifact digest: {0}'.format(digest)) + self.log.debug('Calculating {0} digest of artifact {1}'.format(algorithm, name)) + with open(artifact_path, 'rb') as f: + algorithm_digest = hashlib.new(algorithm, f.read()).hexdigest() + self.log.debug('Calculated artifact digest: {0}'.format(algorithm_digest)) + if digest != algorithm_digest: + raise RuntimeError('Artifact digest mismatch') http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/csar/signature.py ---------------------------------------------------------------------- diff --git a/aria/cli/csar/signature.py b/aria/cli/csar/signature.py new file mode 100644 index 0000000..1ba4340 --- /dev/null +++ b/aria/cli/csar/signature.py @@ -0,0 +1,42 @@ +# Licensed to the Apache Software Foundation (ASF) 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 hashlib +import hmac +import os + + +def create_signature(key_data, csar_path, logger): + signature_builder = hmac.new(key_data, digestmod=hashlib.sha384) + with open(csar_path, 'rb') as f: + logger.debug('Using CSAR package at "{0}"'.format(csar_path)) + logger.log.debug('Preparing to calculate CSAR signature') + while True: + block = f.read(4096) + if not block: + break + signature_builder.update(block) + signature = signature_builder.hexdigest() + logger.debug('Calculated CSAR signature as "{0}"'.format(signature)) + return signature + + +def verify_signature(key_data, signature, csar_path, logger): + if os.path.isfile(signature): + with open(signature) as f: + signature = f.read() + signature = signature.strip() + actual_signature = create_signature(key_data, csar_path, logger) + return hmac.compare_digest(signature, actual_signature) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/023b5808/aria/cli/csar/writer.py ---------------------------------------------------------------------- diff --git a/aria/cli/csar/writer.py b/aria/cli/csar/writer.py new file mode 100644 index 0000000..f3c7afa --- /dev/null +++ b/aria/cli/csar/writer.py @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) 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 os +import zipfile + +from ruamel import yaml + +from . import constants + + +def write(source, entry, destination, author, logger): + metadata = { + constants.META_FILE_VERSION_KEY: '1.0', + constants.META_CSAR_VERSION_KEY: '1.1', + constants.META_CREATED_BY_KEY: author, + constants.META_ENTRY_DEFINITIONS_KEY: entry + } + logger.debug('Compressing root directory to ZIP') + with zipfile.ZipFile(destination, 'w', zipfile.ZIP_DEFLATED) as f: + for root, _, files in os.walk(source): + for file in files: + file_full_path = os.path.join(root, file) + file_relative_path = os.path.relpath(file_full_path, source) + logger.debug('Writing to archive: {0}'.format(file_relative_path)) + f.write(file_full_path, file_relative_path) + logger.debug('Writing new metadata file to {0}'.format(constants.META_FILE)) + f.writestr(constants.META_FILE, yaml.dump(metadata, default_flow_style=False))