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

Unified Diff: appengine/swarming/server/task_request.py

Issue 1946253003: swarming: refactor cipd input (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@default-isolate-server
Patch Set: fix import google.protobuf Created 4 years, 7 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/swarming/server/task_request.py
diff --git a/appengine/swarming/server/task_request.py b/appengine/swarming/server/task_request.py
index e57a1519661537ec0c87a83c8e937ad6c94d6a2b..90ec9a55d2107c42259c4fd8c477ccbe32003547 100644
--- a/appengine/swarming/server/task_request.py
+++ b/appengine/swarming/server/task_request.py
@@ -48,7 +48,6 @@ separate entity to clearly declare the boundary for task request deduplication.
import datetime
import hashlib
-import logging
import random
import re
import urlparse
@@ -60,6 +59,12 @@ from components import auth
from components import datastore_utils
from components import pubsub
from components import utils
+
+# Must be after 'components' import, since they add it to sys.path.
+from google import protobuf
+
+from proto import config_pb2
+from server import config
from server import task_pack
import cipd
@@ -94,7 +99,6 @@ _BEGINING_OF_THE_WORLD = datetime.datetime(2010, 1, 1, 0, 0, 0, 0)
# Used for isolated files.
_HASH_CHARS = frozenset('0123456789abcdef')
-NAMESPACE_RE = re.compile(r'^[a-z0-9A-Z\-._]+$')
### Properties validators must come before the models.
@@ -125,7 +129,7 @@ def _validate_hostname(prop, value):
def _validate_namespace(prop, value):
- if not NAMESPACE_RE.match(value):
+ if not config.NAMESPACE_RE.match(value):
raise datastore_errors.BadValueError('malformed %s' % prop._name)
@@ -204,18 +208,34 @@ def _validate_tags(prop, value):
'%s must be key:value form, not %s' % (prop._name, value))
-def _validate_package_name(prop, value):
- """Validates a CIPD package name."""
- if not cipd.is_valid_package_name(value):
+def _validate_cipd_settings(prop, value):
+ """Validates serialized CIPD settings.
+
+ Validates both serialization format and values.
+ """
+ try:
+ settings = config_pb2.CipdSettings()
+ settings.MergeFromString(value) # May raise protobuf.message.DecodeError.
+ config.validate_cipd_settings(settings) # May raise ValueError.
+ except (ValueError, protobuf.message.DecodeError) as ex:
+ raise datastore_errors.BadValueError(
+ '%s must be a valid CipdSettings protobuf message: %s' %
+ (prop._name, ex))
+
+
+def _validate_package_name_template(prop, value):
+ """Validates a CIPD package name template."""
+ if not cipd.is_valid_package_name_template(value):
raise datastore_errors.BadValueError(
- '%s must be a valid CIPD package name "%s"' % (prop._name, value))
+ '%s must be a valid CIPD package name template "%s"' % (
+ prop._name, value))
def _validate_package_version(prop, value):
"""Validates a CIPD package version."""
if not cipd.is_valid_version(value):
raise datastore_errors.BadValueError(
- '%s must be a valid package version "%s"' % (prop._name, value))
+ '%s must be a valid package version "%s"' % (prop._name, value))
### Models.
@@ -256,8 +276,12 @@ class CipdPackage(ndb.Model):
A part of TaskProperties.
"""
+ # Package name template. May use cipd.ALL_PARAMS.
+ # Most users will specify ${platform} parameter.
package_name = ndb.StringProperty(
- indexed=False, validator=_validate_package_name)
+ indexed=False, validator=_validate_package_name_template)
+ # Package version that is valid for all packages matched by package_name.
+ # Most users will specify tags.
version = ndb.StringProperty(
indexed=False, validator=_validate_package_version)
@@ -269,6 +293,39 @@ class CipdPackage(ndb.Model):
raise datastore_errors.BadValueError('CIPD package version is required')
+class CipdInput(ndb.Model):
M-A Ruel 2016/05/11 14:56:45 Why not CipdInputs and cipd_inputs? It's can be mo
nodir 2016/05/12 01:53:22 Because word "input" does not necessarily mean sin
M-A Ruel 2016/05/12 13:49:45 Oh, I had never realized. I wish I knew with input
+ """Specifies which CIPD client and packages to install, from which server.
+
+ A part of TaskProperties.
+ """
+ # Serialized config_pb2.CipdSettings message.
+ # We keep it here to avoid depending on external mutable state.
+ # At the time of writing, it takes 120 bytes.
+ settings = ndb.BlobProperty(validator=_validate_cipd_settings)
+
+ # List of packages to install in $CIPD_PATH prior task execution.
+ packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True)
+
+ def _pre_put_hook(self):
+ if not self.settings:
+ raise datastore_errors.BadValueError(
+ 'cipd_input settings are required; '
+ 'either specify them explicitly or configure defaults on the server.')
+
+ if not self.packages:
+ raise datastore_errors.BadValueError(
+ 'cipd_input cannot have an empty package list')
+
+ package_names = set()
+ for p in self.packages:
+ p._pre_put_hook()
+ if p.package_name in package_names:
+ raise datastore_errors.BadValueError(
+ 'package %s is specified more than once' % p.package_name)
+ package_names.add(p.package_name)
+ self.packages.sort(key=lambda p: p.package_name)
+
+
class TaskProperties(ndb.Model):
"""Defines all the properties of a task to be run on the Swarming
infrastructure.
@@ -310,9 +367,8 @@ class TaskProperties(ndb.Model):
# Only inputs_ref.isolated or command&data can be specified.
inputs_ref = ndb.LocalStructuredProperty(FilesRef)
- # A list of CIPD packages to install $CIPD_PATH and $PATH before task
- # execution.
- packages = ndb.LocalStructuredProperty(CipdPackage, repeated=True)
+ # CIPD packages to install.
+ cipd_input = ndb.LocalStructuredProperty(CipdInput)
# Filter to use to determine the required properties on the bot to run on. For
# example, Windows or hostname. Encoded as json. Optional but highly
@@ -400,22 +456,15 @@ class TaskProperties(ndb.Model):
self.inputs_ref.is_ref = False
self.inputs_ref._pre_put_hook()
- package_names = set()
- for p in self.packages:
- p._pre_put_hook()
- if p.package_name in package_names:
- raise datastore_errors.BadValueError(
- 'package %s is specified more than once' % p.package_name)
- package_names.add(p.package_name)
- self.packages.sort(key=lambda p: p.package_name)
-
- if self.idempotent:
- pinned = lambda p: cipd.is_pinned_version(p.version)
- if self.packages and any(not pinned(p) for p in self.packages):
- raise datastore_errors.BadValueError(
- 'an idempotent task cannot have unpinned packages; '
- 'use instance IDs or tags as package versions')
-
+ if self.cipd_input:
+ self.cipd_input._pre_put_hook()
+ if self.idempotent:
+ pinned = lambda p: cipd.is_pinned_version(p.version)
+ assert self.cipd_input.packages # checked by cipd_input._pre_put_hook
+ if any(not pinned(p) for p in self.cipd_input.packages):
+ raise datastore_errors.BadValueError(
+ 'an idempotent task cannot have unpinned packages; '
+ 'use tags or instance IDs as package versions')
class TaskRequest(ndb.Model):
"""Contains a user request.

Powered by Google App Engine
This is Rietveld 408576698