Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2052)

Unified Diff: appengine/chrome_infra_packages/cipd/api.py

Issue 816433004: cipd: registerPackage method implementation. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: remove metadata Created 5 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: appengine/chrome_infra_packages/cipd/api.py
diff --git a/appengine/chrome_infra_packages/cipd/api.py b/appengine/chrome_infra_packages/cipd/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..962077e0c298fa0110a1ac42874cd5e0a7fcad8a
--- /dev/null
+++ b/appengine/chrome_infra_packages/cipd/api.py
@@ -0,0 +1,188 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Cloud Endpoints API for Package Repository service."""
+
+import endpoints
+
+from protorpc import message_types
+from protorpc import messages
+from protorpc import remote
+
+from components import auth
+from components import utils
+
+from . import acl
+from . import impl
+
+
+# This is used by endpoints indirectly.
+package = 'cipd'
+
+
+class Signature(messages.Message):
+ """Single signature. Each package instance can have multiple signatures.
+
+ See also SignatureBlock struct in infra/tools/cipd/common.go.
+ """
+ hash_algo = messages.StringField(1, required=True)
+ digest = messages.BytesField(2, required=True)
+ signature_algo = messages.StringField(3, required=True)
+ signature_key = messages.StringField(4, required=True)
+ signature = messages.BytesField(5, required=True)
+
+ # Output only fields.
+ added_by = messages.StringField(6, required=False)
+ added_ts = messages.IntegerField(7, required=False)
+
+
+def signature_from_entity(ent):
+ """PackageInstanceSignature entity -> Signature message."""
+ return Signature(
+ hash_algo=ent.hash_algo,
+ digest=ent.digest,
+ signature_algo=ent.signature_algo,
+ signature_key=ent.signature_key,
+ signature=ent.signature,
+ added_by=ent.added_by.to_bytes(),
+ added_ts=utils.datetime_to_timestamp(ent.added_ts))
+
+
+def signature_to_entity(msg):
+ """Signature message -> PackageInstanceSignature entity."""
+ return impl.PackageInstanceSignature(
+ hash_algo=msg.hash_algo,
+ digest=msg.digest,
+ signature_algo=msg.signature_algo,
+ signature_key=msg.signature_key,
+ signature=msg.signature)
+
+
+class RegisterPackageRequest(messages.Message):
+ """Request to add a new package instance if it is not yet present.
+
+ Callers are expected to execute following protocol:
+ 1. Attempt to register a package instance by calling registerPackage(msg).
+ 2. On UPLOAD_FIRST response, upload package data and finalize the upload by
+ using upload_session_id and upload_url and calling cas.finishUpload.
+ 3. Once upload is finalized, call registerPackage(msg) again.
+ """
+ package_name = messages.StringField(1, required=True)
+ instance_id = messages.StringField(2, required=True)
+ signatures = messages.MessageField(Signature, 3, repeated=True)
+
+
+class RegisterPackageResponse(messages.Message):
+ """Results of registerPackage call.
+
+ upload_session_id and upload_url (if present) can be used with CAS service
+ (finishUpload call in particular).
+ """
+
+ class Status(messages.Enum):
+ # Package instance successfully registered.
+ REGISTERED = 1
+ # Such package instance already exists. It is not an error.
+ ALREADY_REGISTERED = 2
+ # Package data has to be upload to CAS first.
+ UPLOAD_FIRST = 3
+ # Some unexpected fatal error happened.
+ ERROR = 4
+
+ # Status of this operation, defines what other fields to expect.
+ status = messages.EnumField(Status, 1, required=True)
+
+ # For REGISTERED and ALREADY_REGISTERED, who registered the package.
+ registered_by = messages.StringField(2, required=False)
+ # For REGISTERED and ALREADY_REGISTERED, when the package was registered.
+ registered_ts = messages.IntegerField(3, required=False)
+
+ # For UPLOAD_FIRST status, a unique identifier of the upload operation.
+ upload_session_id = messages.StringField(4, required=False)
+ # For UPLOAD_FIRST status, URL to PUT file to via resumable upload protocol.
+ upload_url = messages.StringField(5, required=False)
+
+ # For ERROR status, a error message.
+ error_message = messages.StringField(6, required=False)
+
+
+@auth.endpoints_api(
+ name='repo',
+ version='v1',
+ title='Package Repository API')
+class PackageRepositoryApi(remote.Service):
+ """Package Repository API."""
+
+ @auth.endpoints_method(
+ RegisterPackageRequest,
+ RegisterPackageResponse,
+ http_method='POST',
+ name='registerPackage')
+ @auth.require(lambda: not auth.get_current_identity().is_anonymous)
+ def register_package(self, request):
+ """Registers a new package instance in the repository."""
+ # Forms ERROR response.
+ def error(msg):
+ return RegisterPackageResponse(
+ status=RegisterPackageResponse.Status.ERROR,
+ error_message=msg)
+
+ # Forms REGISTERED or ALREADY_REGISTERED response.
+ def success(pkg, status):
+ return RegisterPackageResponse(
+ status=status,
+ registered_by=pkg.registered_by.to_bytes(),
+ registered_ts=utils.datetime_to_timestamp(pkg.registered_ts))
+
+ package_name = request.package_name
+ if not impl.is_valid_package_name(package_name):
+ return error('Invalid package name')
+
+ instance_id = request.instance_id
+ if not impl.is_valid_instance_id(request.instance_id):
+ return error('Invalid package instance ID')
+
+ caller = auth.get_current_identity()
+ if not acl.can_register_package(package_name, caller):
+ raise auth.AuthorizationError()
+
+ service = impl.get_repo_service()
+ if service is None:
+ raise endpoints.InternalServerErrorException('Service is not configured')
+
+ # Signature list proto -> entity.
+ now = utils.utcnow()
+ signatures = []
+ for sig in request.signatures:
+ ent = signature_to_entity(sig)
+ ent.added_by = caller
+ ent.added_ts = now
+ signatures.append(ent)
+
+ # Already registered? Just attach any new signatures.
+ pkg = service.get_instance(package_name, instance_id)
+ if pkg is not None:
+ service.add_signatures(package_name, instance_id, signatures)
+ return success(pkg, RegisterPackageResponse.Status.ALREADY_REGISTERED)
+
+ # Need to upload to CAS first? Open an upload session. Caller must use
+ # CASServiceApi to finish the upload and then call registerPackage again.
+ if not service.is_instance_file_uploaded(package_name, instance_id):
+ upload_url, upload_session_id = service.create_upload_session(
+ package_name, instance_id, caller)
+ return RegisterPackageResponse(
+ status=RegisterPackageResponse.Status.UPLOAD_FIRST,
+ upload_session_id=upload_session_id,
+ upload_url=upload_url)
+
+ # Package data is in the store. Make an entity.
+ try:
+ pkg = service.register_instance(
+ package_name, instance_id, signatures, caller, now)
+ return success(pkg, RegisterPackageResponse.Status.REGISTERED)
+ except impl.PackageInstanceExistsError: # pragma: no cover
+ # Can happen if package was registered since 'get_instance' call by some
+ # other process. Just add new signatures.
+ pkg = service.add_signatures(package_name, instance_id, signatures)
+ return success(pkg, RegisterPackageResponse.Status.ALREADY_REGISTERED)

Powered by Google App Engine
This is Rietveld 408576698