If the signature on the input is bad, but the user chooses to load it anyway, always...
[0publish.git] / 0publish
blob5899cc028c35a05186befbbe8421d83b1aef4736
1 #!/usr/bin/env python
2 from zeroinstall.injector import gpg
3 from zeroinstall import SafeException
4 from optparse import OptionParser
5 import os, sys
6 import signing
7 from logging import info, debug
8 import edit, validator
10 version = '0.6'
12 parser = OptionParser(usage="usage: %prog [options] interface")
13 parser.add_option("--add-version", help="add a new implementation", action='store', metavar='VERSION')
14 parser.add_option("--archive-url", help="add archive at this URL", action='store', metavar='URL')
15 parser.add_option("--archive-file", help="local copy of archive-url", action='store', metavar='FILE')
16 parser.add_option("--archive-extract", help="subdirectory of archive to extract", action='store', metavar='DIR')
17 parser.add_option("-e", "--edit", help="edit with $EDITOR", action='store_true')
18 parser.add_option("-g", "--gpgsign", help="add a GPG signature block", action='store_true')
19 parser.add_option("-k", "--key", help="key to use for signing")
20 parser.add_option("-l", "--local", help="create feed from local interface")
21 parser.add_option("--manifest-algorithm", help="select algorithm for manifests", action='store', metavar='ALG')
22 parser.add_option("--set-interface-uri", help="set interface URI", action='store', metavar='URI')
23 parser.add_option("--set-id", help="set implementation ID", action='store', metavar='DIGEST')
24 parser.add_option("--set-main", help="set main executable", action='store', metavar='EXEC')
25 parser.add_option("--set-arch", help="set architecture", action='store', metavar='ARCH')
26 parser.add_option("--set-released", help="set release date", action='store', metavar='DATE')
27 parser.add_option("--set-stability", help="set stability", action='store', metavar='STABILITY')
28 parser.add_option("--set-version", help="set version number", action='store', metavar='VERSION')
29 parser.add_option("-s", "--stable", help="mark testing version stable", action='store_true')
30 parser.add_option("-x", "--xmlsign", help="add an XML signature block", action='store_true')
31 parser.add_option("-u", "--unsign", help="remove any signature", action='store_true')
32 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
33 parser.add_option("-V", "--version", help="display version information", action='store_true')
35 (options, args) = parser.parse_args()
37 force_save = False
39 if options.version:
40 print "0publish (zero-install) " + version
41 print "Copyright (C) 2005 Thomas Leonard"
42 print "This program comes with ABSOLUTELY NO WARRANTY,"
43 print "to the extent permitted by law."
44 print "You may redistribute copies of this program"
45 print "under the terms of the GNU General Public License."
46 print "For more information about these matters, see the file named COPYING."
47 sys.exit(0)
49 if options.verbose:
50 import logging
51 logger = logging.getLogger()
52 if options.verbose == 1:
53 logger.setLevel(logging.INFO)
54 else:
55 logger.setLevel(logging.DEBUG)
57 if len(args) != 1:
58 parser.print_help()
59 sys.exit(1)
60 interface = args[0]
62 def confirm(q):
63 while True:
64 ans = raw_input(q + " [Y/N] ").lower()
65 if ans in ('y', 'yes'): return True
66 if ans in ('n', 'no'): return False
68 try:
69 # Load or create the starting data...
71 if os.path.exists(interface):
72 contents = file(interface).read()
73 data, sign_fn, key = signing.check_signature(interface)
74 elif options.local:
75 import create
76 if os.path.exists(options.local):
77 data = create.create_from_local(options.local)
78 sign_fn = signing.sign_unsigned
79 key = None
80 force_save = True
81 options.local = False
82 else:
83 raise Exception("File '%s' does not exist." % options.local)
84 else:
85 if confirm("Interface file '%s' does not exist. Create it?" % interface):
86 from create import create
87 data = create(interface)
88 sign_fn = signing.sign_unsigned
89 key = None
90 options.edit = True
91 else:
92 sys.exit(1)
94 debug("Original data: %s", data)
95 info("Original signing method: %s", sign_fn.__name__)
96 info("Original key: %s", key)
98 old_data = data
99 old_sign_fn = sign_fn
100 old_key = key
102 while True:
103 # Validate the input...
104 try:
105 validator.check(data)
106 break
107 except validator.InvalidInterface, ex:
108 print "Invalid interface: " + str(ex)
110 while True:
111 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
112 if ans in ('e', 'edit'):
113 data = edit.edit(data)
114 options.edit = False # Don't edit twice
115 break
116 if ans in ('a', 'abort'): sys.exit(1)
118 # Process it...
119 if options.xmlsign:
120 sign_fn = signing.sign_xml
121 if options.unsign:
122 sign_fn = signing.sign_unsigned
123 if options.gpgsign:
124 sign_fn = signing.sign_plain
125 if options.key:
126 print "Changing key from '%s' to '%s'" % (key, options.key)
127 key = options.key
128 if options.set_interface_uri:
129 import release
130 data = release.set_interface_uri(data, options.set_interface_uri)
131 if options.add_version:
132 import release
133 data = release.add_version(data, options.add_version)
134 if options.set_id or options.set_version or options.set_released or \
135 options.set_stability or options.set_arch or options.set_main:
136 import release
137 data = release.make_release(data, options.set_id,
138 options.set_version, options.set_released, options.set_stability,
139 options.set_main, options.set_arch)
140 if options.stable:
141 import stable
142 data = stable.mark_stable(data)
143 if options.archive_url:
144 import archive
145 data = archive.add_archive(data, options.archive_url, options.archive_file, options.archive_extract, options.manifest_algorithm)
146 elif options.archive_file or options.archive_extract:
147 raise Exception('Must use --archive-uri option')
148 if options.local:
149 import merge
150 data = merge.merge(data, options.local)
151 if options.edit:
152 data = edit.edit(data)
154 while True:
155 # Validate the result...
156 try:
157 validator.check(data)
158 break
159 except validator.InvalidInterface, ex:
160 print "Invalid interface: " + str(ex)
162 while True:
163 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
164 if ans in ('e', 'edit'):
165 data = edit.edit(data)
166 break
167 if ans in ('a', 'abort'): sys.exit(1)
170 if (old_data == data and sign_fn == old_sign_fn and key == old_key) and not force_save:
171 print "Interface unchanged. Not writing."
172 sys.exit(1)
174 # Write it back out
175 if not data.endswith('\n'): data += '\n'
176 sign_fn(interface, data, key)
178 info("Wrote '%S'", interface)
180 if sign_fn != signing.sign_unsigned:
181 # Read it back in to find out what key we signed it with
182 # and ensure that the key has been exported
183 contents = file(interface).read()
184 saved_data, saved_sign_fn, saved_key = signing.check_signature(interface)
185 assert saved_data == data
186 assert saved_sign_fn == sign_fn
187 signing.export_key(os.path.dirname(interface), saved_key)
188 except SafeException, ex:
189 if options.verbose: raise
190 print >>sys.stderr, ex
191 sys.exit(1)