OLD | NEW |
---|---|
(Empty) | |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """Cloud Endpoints API for Package Repository service.""" | |
6 | |
7 import endpoints | |
8 | |
9 from protorpc import message_types | |
10 from protorpc import messages | |
11 from protorpc import remote | |
12 | |
13 from components import auth | |
14 from components import utils | |
15 | |
16 from . import acl | |
17 from . import impl | |
18 | |
19 | |
20 # This is used by endpoints indirectly. | |
21 package = 'cipd' | |
22 | |
23 | |
24 class InstanceMetadata(messages.Message): | |
25 """Description of how the package instance was built and registered.""" | |
26 date = messages.StringField(1, required=True) | |
27 hostname = messages.StringField(2, required=True) | |
28 user = messages.StringField(3, required=True) | |
29 | |
30 # Output only fields. | |
31 registered_by = messages.StringField(4, required=False) | |
32 registered_ts = messages.IntegerField(5, required=False) | |
33 | |
34 | |
35 def metadata_from_entity(ent): | |
36 """PackageInstanceMetadata entity -> InstanceMetadata message.""" | |
37 return InstanceMetadata( | |
38 date=ent.date, | |
39 hostname=ent.hostname, | |
40 user=ent.user, | |
41 registered_by=ent.registered_by.to_bytes(), | |
42 registered_ts=utils.datetime_to_timestamp(ent.registered_ts)) | |
43 | |
44 | |
45 def metadata_to_entity(msg): | |
46 """InstanceMetadata message -> PackageInstanceMetadata entity.""" | |
47 return impl.PackageInstanceMetadata( | |
48 date=msg.date, | |
49 hostname=msg.hostname, | |
50 user=msg.user) | |
51 | |
52 | |
53 class Signature(messages.Message): | |
54 """Single signature. Each package instance can have multiple signatures. | |
55 | |
56 See also SignatureBlock struct in infra/tools/cipd/common.go. | |
57 """ | |
58 hash_algo = messages.StringField(1, required=True) | |
59 digest = messages.BytesField(2, required=True) | |
60 signature_algo = messages.StringField(3, required=True) | |
61 signature_key = messages.StringField(4, required=True) | |
62 signature = messages.BytesField(5, required=True) | |
63 | |
64 # Output only fields. | |
65 added_by = messages.StringField(6, required=False) | |
66 added_ts = messages.IntegerField(7, required=False) | |
67 | |
68 | |
69 def signature_from_entity(ent): | |
70 """PackageInstanceSignature entity -> Signature message.""" | |
71 return Signature( | |
72 hash_algo=ent.hash_algo, | |
73 digest=ent.digest, | |
74 signature_algo=ent.signature_algo, | |
75 signature_key=ent.signature_key, | |
76 signature=ent.signature, | |
77 added_by=ent.added_by.to_bytes(), | |
78 added_ts=utils.datetime_to_timestamp(ent.added_ts)) | |
79 | |
80 | |
81 def signature_to_entity(msg): | |
82 """Signature message -> PackageInstanceSignature entity.""" | |
83 return impl.PackageInstanceSignature( | |
84 hash_algo=msg.hash_algo, | |
85 digest=msg.digest, | |
86 signature_algo=msg.signature_algo, | |
87 signature_key=msg.signature_key, | |
88 signature=msg.signature) | |
89 | |
90 | |
91 class RegisterPackageRequest(messages.Message): | |
92 """Request to add a new package instance if it is not yet present. | |
93 | |
94 Instance metadata is recorded only if package instance is not yet present. | |
95 Signatures are appended to the list of signatures (even for existing package). | |
96 | |
97 Callers are expected to execute following protocol: | |
98 1. Attempt to register a package instance by callling registerPackage(msg). | |
nodir
2014/12/30 22:54:00
typo: calllling
Vadim Sh.
2014/12/31 01:27:35
Done.
| |
99 2. On UPLOAD_FIRST response, upload package data and finalize the upload by | |
100 using upload_session_id and upload_url and calling cas.finishUpload. | |
101 3. Once upload is finalized, call registerPackage(msg) again. | |
102 """ | |
103 package_name = messages.StringField(1, required=True) | |
104 instance_id = messages.StringField(2, required=True) | |
105 metadata = messages.MessageField(InstanceMetadata, 3, required=True) | |
106 signatures = messages.MessageField(Signature, 4, repeated=True) | |
107 | |
108 | |
109 class RegisterPackageResponse(messages.Message): | |
110 """Results of registerPackage call. | |
111 | |
112 upload_session_id and upload_url (if present) can be used with CAS service | |
113 (finishUpload call in particular). | |
114 """ | |
115 class Status(messages.Enum): | |
nodir
2014/12/30 22:54:00
Blank line after """
Vadim Sh.
2014/12/31 01:27:35
Done.
| |
116 # Package instance successfully registered. | |
117 REGISTERED = 1 | |
118 # Such package instance already exists. It is not an error. | |
119 ALREADY_REGISTERED = 2 | |
120 # Package data has to be upload to CAS first. | |
121 UPLOAD_FIRST = 3 | |
122 # Some unexpected fatal error happened. | |
123 ERROR = 4 | |
124 | |
125 # Status of this operation, defines what other fields to expect. | |
126 status = messages.EnumField( | |
127 'RegisterPackageResponse.Status', 1, required=True) | |
nodir
2014/12/30 22:54:00
Why not EnumField(Status, ...) ?
Vadim Sh.
2014/12/31 01:27:35
Done. For some reason I though it does like local
| |
128 | |
129 # For REGISTERED or ALREADY_REGISTERED a current metadata of package instance. | |
130 metadata = messages.MessageField(InstanceMetadata, 2, required=False) | |
131 | |
132 # For UPLOAD_FIRST status, a unique identifier of the upload operation. | |
133 upload_session_id = messages.StringField(3, required=False) | |
134 # For UPLOAD_FIRST status, URL to PUT file to via resumable upload protocol. | |
135 upload_url = messages.StringField(4, required=False) | |
136 | |
137 # For ERROR status, a error message. | |
138 error_message = messages.StringField(5, required=False) | |
139 | |
140 | |
141 @auth.endpoints_api( | |
142 name='repo', | |
143 version='v1', | |
144 title='Package Repository API') | |
145 class PackageRepositoryApi(remote.Service): | |
146 """Package Repository API.""" | |
147 | |
148 @auth.endpoints_method( | |
149 RegisterPackageRequest, | |
150 RegisterPackageResponse, | |
151 http_method='POST', | |
152 name='registerPackage') | |
153 @auth.require(lambda: not auth.get_current_identity().is_anonymous) | |
154 def register_package(self, request): | |
155 """Registers a new package instance in the repository.""" | |
156 if not impl.is_valid_package_name(request.package_name): | |
157 raise endpoints.BadRequestException('Invalid package name') | |
158 if not impl.is_valid_instance_id(request.instance_id): | |
159 raise endpoints.BadRequestException('Invalid instance ID') | |
nodir
2014/12/30 22:54:00
Consider returning different error reasons. A prog
Vadim Sh.
2014/12/31 01:27:35
Client will do client side validation. Checks here
| |
160 | |
nodir
2014/12/30 22:54:00
metadata is not validated? To me, either it should
Vadim Sh.
2014/12/31 01:27:35
I was planing to add very few required FYI only fi
nodir
2015/01/02 19:13:03
Acknowledged.
| |
161 caller = auth.get_current_identity() | |
162 if not acl.can_register_package(request.package_name, caller): | |
163 raise auth.AuthorizationError() | |
164 | |
165 service = impl.get_repo_service() | |
166 if service is None: | |
167 raise endpoints.InternalServerErrorException('Service is not configured') | |
168 | |
169 # Metadata proto -> entity. | |
170 metadata = metadata_to_entity(request.metadata) | |
171 metadata.registered_by = caller | |
172 metadata.registered_ts = utils.utcnow() | |
173 | |
174 # Signature list proto -> entity. | |
175 signatures = [] | |
176 for sig in request.signatures: | |
177 ent = signature_to_entity(sig) | |
178 ent.added_by = caller | |
179 ent.added_ts = utils.utcnow() | |
nodir
2014/12/30 22:54:00
call utcnow once per request
Vadim Sh.
2014/12/31 01:27:35
Done.
| |
180 signatures.append(ent) | |
181 | |
182 # Already registered? Just attach any new signatures. | |
183 pkg = service.get_instance(request.package_name, request.instance_id) | |
184 if pkg is not None: | |
185 service.add_signatures( | |
186 request.package_name, request.instance_id, signatures) | |
nodir
2014/12/30 22:54:00
I think you should not add signatures
Vadim Sh.
2014/12/31 01:27:35
Then there's no way to resign a package. I mean it
| |
187 return RegisterPackageResponse( | |
188 status=RegisterPackageResponse.Status.ALREADY_REGISTERED, | |
189 metadata=metadata_from_entity(pkg.metadata)) | |
190 | |
191 # Need to upload to CAS first? Open an upload session. Caller must use | |
192 # CASServiceApi to finish the upload and then call registerPackage again. | |
193 if not service.is_data_uploaded(request.package_name, request.instance_id): | |
194 upload_url, upload_session_id = service.create_upload_session( | |
Vadim Sh.
2014/12/30 02:22:26
It makes 'cas' service and 'repo' service a bit ta
| |
195 request.package_name, request.instance_id, caller) | |
196 return RegisterPackageResponse( | |
197 status=RegisterPackageResponse.Status.UPLOAD_FIRST, | |
198 upload_session_id=upload_session_id, | |
199 upload_url=upload_url) | |
200 | |
201 # Package data is in the store. Make an entity. | |
202 pkg, registered = service.register_instance( | |
203 request.package_name, request.instance_id, metadata, signatures) | |
204 if registered: | |
205 status = RegisterPackageResponse.Status.REGISTERED | |
206 else: # pragma: no cover | |
207 status = RegisterPackageResponse.Status.ALREADY_REGISTERED | |
208 return RegisterPackageResponse( | |
209 status=status, | |
210 metadata=metadata_from_entity(pkg.metadata)) | |
OLD | NEW |