fune/taskcluster/taskgraph/parameters.py
Tom Prince f8cc2f6b8f Bug 1490094: [release] Configure staging release resources via dedicated parameter; r=bhearsum
Currently, release resources such as balrog, bouncer and CDN urls are
configured by project. However, all production branches use one value and all
other branches use another. Rather than duplicate lists of projects, add a
helper to identify production release branches.

Differential Revision: https://phabricator.services.mozilla.com/D5583

--HG--
extra : moz-landing-system : lando
2018-09-11 21:09:28 +00:00

223 lines
6.8 KiB
Python

# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
import functools
import json
import time
import yaml
from datetime import datetime
from mozbuild.util import ReadOnlyDict, memoize
from mozversioncontrol import get_repository_object
from . import APP_VERSION_PATH, GECKO, VERSION_PATH
from .util.attributes import RELEASE_PROJECTS
class ParameterMismatch(Exception):
"""Raised when a parameters.yml has extra or missing parameters."""
@memoize
def get_head_ref():
return get_repository_object(GECKO).head_ref
def get_contents(path):
with open(path, "r") as fh:
contents = fh.readline().rstrip()
return contents
get_version = functools.partial(get_contents, VERSION_PATH)
get_app_version = functools.partial(get_contents, APP_VERSION_PATH)
# Please keep this list sorted and in sync with taskcluster/docs/parameters.rst
# Parameters are of the form: {name: default}
PARAMETERS = {
'app_version': get_app_version(),
'base_repository': 'https://hg.mozilla.org/mozilla-unified',
'build_date': lambda: int(time.time()),
'build_number': 1,
'do_not_optimize': [],
'existing_tasks': {},
'filters': ['check_servo', 'target_tasks_method'],
'head_ref': get_head_ref,
'head_repository': 'https://hg.mozilla.org/mozilla-central',
'head_rev': get_head_ref,
'include_nightly': False,
'level': '3',
'message': '',
'moz_build_date': lambda: datetime.now().strftime("%Y%m%d%H%M%S"),
'next_version': None,
'optimize_target_tasks': True,
'owner': 'nobody@mozilla.com',
'project': 'mozilla-central',
'pushdate': lambda: int(time.time()),
'pushlog_id': '0',
'release_enable_emefree': False,
'release_enable_partners': False,
'release_eta': '',
'release_history': {},
'release_partners': None,
'release_partner_config': None,
'release_partner_build_number': 1,
'release_type': '',
'release_product': None,
'target_tasks_method': 'default',
'try_mode': None,
'try_options': None,
'try_task_config': None,
'version': get_version(),
}
COMM_PARAMETERS = {
'comm_base_repository': 'https://hg.mozilla.org/comm-central',
'comm_head_ref': None,
'comm_head_repository': 'https://hg.mozilla.org/comm-central',
'comm_head_rev': None,
}
class Parameters(ReadOnlyDict):
"""An immutable dictionary with nicer KeyError messages on failure"""
def __init__(self, strict=True, **kwargs):
self.strict = strict
if not self.strict:
# apply defaults to missing parameters
for name, default in PARAMETERS.items():
if name not in kwargs:
if callable(default):
default = default()
kwargs[name] = default
if set(kwargs) & set(COMM_PARAMETERS.keys()):
for name, default in COMM_PARAMETERS.items():
if name not in kwargs:
if callable(default):
default = default()
kwargs[name] = default
ReadOnlyDict.__init__(self, **kwargs)
def check(self):
names = set(self)
valid = set(PARAMETERS.keys())
valid_comm = set(COMM_PARAMETERS.keys())
msg = []
missing = valid - names
if missing:
msg.append("missing parameters: " + ", ".join(missing))
extra = names - valid
if extra & set(valid_comm):
# If any comm_* parameters are specified, ensure all of them are specified.
missing = valid_comm - extra
if missing:
msg.append("missing parameters: " + ", ".join(missing))
extra = extra - valid_comm
if extra and self.strict:
msg.append("extra parameters: " + ", ".join(extra))
if msg:
raise ParameterMismatch("; ".join(msg))
def __getitem__(self, k):
if not (k in PARAMETERS.keys() or k in COMM_PARAMETERS.keys()):
raise KeyError("no such parameter {!r}".format(k))
try:
return super(Parameters, self).__getitem__(k)
except KeyError:
raise KeyError("taskgraph parameter {!r} not found".format(k))
def is_try(self):
"""
Determine whether this graph is being built on a try project or for
`mach try fuzzy`.
"""
return 'try' in self['project'] or self['try_mode'] == 'try_select'
def file_url(self, path):
"""
Determine the VCS URL for viewing a file in the tree, suitable for
viewing by a human.
:param basestring path: The path, relative to the root of the repository.
:return basestring: The URL displaying the given path.
"""
if path.startswith('comm/'):
path = path[len('comm/'):]
repo = self['comm_head_repository']
rev = self['comm_head_rev']
else:
repo = self['head_repository']
rev = self['head_rev']
return '{}/file/{}/{}'.format(repo, rev, path)
def release_level(self):
"""
Whether this is a staging release or not.
:return basestring: One of "production" or "staging".
"""
return 'production' if self['project'] in RELEASE_PROJECTS else 'staging'
def load_parameters_file(filename, strict=True, overrides=None):
"""
Load parameters from a path, url, decision task-id or project.
Examples:
task-id=fdtgsD5DQUmAQZEaGMvQ4Q
project=mozilla-central
"""
import urllib
from taskgraph.util.taskcluster import get_artifact_url, find_task_id
if overrides is None:
overrides = {}
if not filename:
return Parameters(strict=strict, **overrides)
try:
# reading parameters from a local parameters.yml file
f = open(filename)
except IOError:
# fetching parameters.yml using task task-id, project or supplied url
task_id = None
if filename.startswith("task-id="):
task_id = filename.split("=")[1]
elif filename.startswith("project="):
index = "gecko.v2.{project}.latest.taskgraph.decision".format(
project=filename.split("=")[1],
)
task_id = find_task_id(index)
if task_id:
filename = get_artifact_url(task_id, 'public/parameters.yml')
f = urllib.urlopen(filename)
if filename.endswith('.yml'):
kwargs = yaml.safe_load(f)
elif filename.endswith('.json'):
kwargs = json.load(f)
else:
raise TypeError("Parameters file `{}` is not JSON or YAML".format(filename))
kwargs.update(overrides)
return Parameters(strict=strict, **kwargs)