blob: d819a70ab6d5246441fa93d03d137e12eb0631dd [file] [log] [blame]
Lianhao Lu432bca42018-03-24 22:36:08 +08001# Copyright (c) 2018 Intel Corp. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14#
15
16from collections import namedtuple
17import os
Lianhao Lucd02d1f2018-03-26 13:35:22 +080018import tempfile
Lianhao Lu432bca42018-03-24 22:36:08 +080019
20import udatetime
21
22from vnfsdk_pkgtools.packager import utils
23
24METADATA_KEYS = [ 'vnf_provider_id',
25 'vnf_product_name',
26 'vnf_release_data_time',
27 'vnf_package_version']
28DIGEST_KEYS = [ 'Source', 'Algorithm', 'Hash' ]
29SUPPORTED_HASH_ALGO = ['SHA256', 'SHA512']
30
31class ManifestException(Exception):
32 pass
33
34class Manifest(object):
35 ' Manifest file in CSAR package'
36 def __init__(self, root_path, manifest_path):
37 self.path = manifest_path
38 self.root = root_path
39 self.metadata = {}
40 # digest dict
41 # :key = source
42 # :value = (algorithm, hash)
43 self.digests = {}
44 self.signature = None
45 self.blocks = [ ]
46 self._split_blocks()
47 self._parse_blocks()
48
49 @staticmethod
50 def __split_line(s):
51 remain=s
52 try:
53 (key, value)=s.split(':', 1)
54 value = value.strip()
55 remain = None
56 except ValueError:
57 key = None
58 value = None
59 return (key, value, remain)
60
61 def _split_blocks(self):
62 '''
63 Split manifest file into blocks, each block is seperated by a empty
64 line or a line with only spaces and tabs.
65 '''
66 block_content = [ ]
67 with open(os.path.join(self.root, self.path),'rU') as fp:
68 for line in fp:
69 line = line.strip(' \t\n')
70 if line:
71 block_content.append(line)
72 else:
73 if len(block_content):
74 self.blocks.append(block_content)
75 block_content = []
76 if len(block_content):
77 self.blocks.append(block_content)
78
79 def _parse_blocks(self):
80 for block in self.blocks:
81 (key, value, remain) = self.__split_line(block.pop(0))
82 if key == 'metadata':
83 # metadata block
84 for line in block:
85 (key, value, remain) = self.__split_line(line)
86 if key in METADATA_KEYS:
87 self.metadata[key] = value
88 else:
89 raise ManifestException("Unrecognized metadata %s:" % line)
90 #validate metadata keys
91 missing_keys = set(METADATA_KEYS) - set(self.metadata.keys())
92 if missing_keys:
93 raise ManifestException("Missing metadata keys: %s" % ','.join(missing_keys))
94 # validate vnf_release_data_time
95 try:
96 udatetime.from_string(self.metadata['vnf_release_data_time'])
97 except ValueError:
98 raise ManifestException("Non IETF RFC 3339 vnf_release_data_time: %s"
99 % self.metadata['vnf_release_data_time'])
100 elif key in DIGEST_KEYS:
101 # file digest block
102 desc = {}
103 desc[key] = value
104 for line in block:
105 (key, value, remain) = self.__split_line(line)
106 if key in DIGEST_KEYS:
107 desc[key] = value
108 else:
109 raise ManifestException("Unrecognized file digest line %s:" % line)
110 # validate file digest keys
111 missing_keys = set(DIGEST_KEYS) - set(desc.keys())
112 if missing_keys:
113 raise ManifestException("Missing file digest keys: %s" % ','.join(missing_keys))
114 # validate file digest algo
115 desc['Algorithm'] = desc['Algorithm'].upper()
116 if desc['Algorithm'] not in SUPPORTED_HASH_ALGO:
117 raise ManifestException("Unsupported hash algorithm: %s" % desc['Algorithm'])
118 # validate file digest hash
119 # TODO need to support remote file
120 if "://" not in desc['Source']:
121 hash = utils.cal_file_hash(self.root, desc['Source'], desc['Algorithm'])
122 if hash != desc['Hash']:
123 raise ManifestException("Mismatched hash for file %s" % desc['Source'])
124 # nothing is wrong, let's store this
125 self.digests[desc['Source']] = (desc['Algorithm'], desc['Hash'])
126 elif key:
127 raise ManifestException("Unknown key in line '%s:%s'" % (key, value))
128 else:
129 # TODO signature block
130 pass
131
132 if not self.metadata:
133 raise ManifestException("No metadata")
134
135 def add_file(self, rel_path, algo='SHA256'):
136 '''Add file to the manifest and calculate the digest
137 '''
138 algo = algo.upper()
139 if algo not in SUPPORTED_HASH_ALGO:
140 raise ManifestException("Unsupported hash algorithm: %s" % algo)
141 hash = utils.cal_file_hash(self.root, rel_path, algo)
142 self.digests[rel_path] = (algo, hash)
143
144 def return_as_string(self):
145 '''Return the manifest file content as a string
146 '''
147 ret = ""
148 # metadata
149 ret += "metadata:\n"
150 ret += "vnf_product_name: %s\n" % (self.metadata['vnf_product_name'])
151 ret += "vnf_provider_id: %s\n" % (self.metadata['vnf_provider_id'])
152 ret += "vnf_package_version: %s\n" % (self.metadata['vnf_package_version'])
153 ret += "vnf_release_data_time: %s\n" % (self.metadata['vnf_release_data_time'])
154 # degist
155 for (key, digest) in self.digests.iteritems():
156 ret += "\n"
157 ret += "Source: %s\n" % key
158 ret += "Algorithm: %s\n" % digest[0]
159 ret += "Hash: %s\n" % digest[1]
160 return ret
161
Lianhao Lucd02d1f2018-03-26 13:35:22 +0800162 def update_to_file(self, temporary=False):
Lianhao Lu432bca42018-03-24 22:36:08 +0800163 content = self.return_as_string()
Lianhao Lucd02d1f2018-03-26 13:35:22 +0800164 if temporary:
165 abs_path = tempfile.mktemp()
166 else:
167 abs_path = os.path.abspath(os.path.join(self.root, self.path))
168
169 with open(abs_path, 'w') as fp:
Lianhao Lu432bca42018-03-24 22:36:08 +0800170 fp.write(content)
Lianhao Lucd02d1f2018-03-26 13:35:22 +0800171 return abs_path