/
digest.py
executable file
·145 lines (121 loc) · 4.01 KB
/
digest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/python2
"""
This script takes at least one argument: path to a file with manifest.
It then computes and prints digest of the manifest(s).
"""
from copy import deepcopy
import os
import re
import sys
import json
import hashlib
from collections import OrderedDict
class Manifest(object):
def __init__(self, manifest):
"""
:param manifest: str, json-encoded manifest
"""
self._encoded_manifest = manifest
self._decoded_manifest = None
def prepare_for_digest_computation(self):
"""
strip "signatures" from decoded manifest
:return: decoded manifest (instance of OrderedDict)
"""
decoded_manifest = deepcopy(self.decoded_manifest)
del decoded_manifest["signatures"]
return decoded_manifest
@property
def digest(self):
"""
E.g.
sha256:3bd99e8c083e6bb60ef9c1e7de1a2c34a1fab103b6ee4e9c23f08abd91fb6d53
"""
decoded_manifest = self.prepare_for_digest_computation()
return "sha256:" + hashlib.sha256(self.render(decoded_manifest)).hexdigest()
@property
def decoded_manifest(self):
if self._decoded_manifest is None:
self._decoded_manifest = json.loads(
self._encoded_manifest, object_pairs_hook=OrderedDict, encoding="utf-8")
# # This is left here just to keep track of past progress
# for h in decoded_json["history"]:
# # print json.loads(h["v1Compatibility"], object_pairs_hook=OrderedDict,
# encoding="utf-8")
# i = h["v1Compatibility"]
# i = i.replace(r"\u003c", "<").replace(r"\u003e", ">").replace(r"\u0026", "&")
# h["v1Compatibility"] = i
# # print i
return self._decoded_manifest
def set_tag(self, tag):
"""
:param tag: str
:return: None
"""
self.decoded_manifest["tag"] = tag
def set_name(self, name):
"""
:param name: str
:return: None
"""
self.decoded_manifest["name"] = name
def render(self, decoded_manifest=None):
"""
serialize decoded manifest to json
:returns: str
"""
decoded_manifest = decoded_manifest or self.decoded_manifest
encoded_manifest = json.dumps(
decoded_manifest, indent=3, separators=(',', ': '), ensure_ascii=False)
# # This is left here just to keep track of past progress
# encoded_json = encoded_json.replace("<", r"\\u003c").replace(">", r"\\u003e"
# ).replace("&", r"\\u0026")
return encoded_manifest.encode("utf-8")
def prepare_file_hack(manifest):
"""
This is a hacky implementation which removes 'signatures' using regular expressions,
for more info see:
https://github.com/docker/distribution/issues/1065
:param manifest: str, json-encoded manifest
"""
# 0 start -> signatures
# 1 signatures
# 2 after signatures
states = 0
sig_re = r'(\s+)\"signatures\":\s*\['
stop = None
ls = []
for line in manifest.split("\n"):
if line == stop:
states = 2
if states == 1:
continue
m = re.match(sig_re, line)
if m is not None:
states = 1
ls = ls[:-1]
stop = m.groups()[0] + "]"
continue
else:
ls.append(line)
return "\n".join(ls)
def main():
if len(sys.argv[1:]) <= 0:
print "Provide please at least one path to a file with manifest."
sys.exit(1)
result = []
for fp in sys.argv[1:]:
p = os.path.abspath(os.path.expanduser(fp))
with open(p, "r") as fd:
raw_manifest = fd.read()
m = Manifest(raw_manifest)
digest = m.digest
result.append((fp, digest))
if len(result) == 1:
print result[0][1]
else:
for r in result:
print "{} {}".format(*r)
sys.exit(0)
if __name__ == "__main__":
main()