2014-11-26 18:11:28 +00:00
|
|
|
# -*- 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/.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import os.path
|
|
|
|
import json
|
|
|
|
import copy
|
|
|
|
import datetime
|
2014-11-26 18:11:28 +00:00
|
|
|
import sys
|
2014-11-26 18:11:32 +00:00
|
|
|
import urllib2
|
2014-11-26 18:11:28 +00:00
|
|
|
|
|
|
|
from mach.decorators import (
|
|
|
|
CommandArgument,
|
|
|
|
CommandProvider,
|
|
|
|
Command,
|
|
|
|
)
|
|
|
|
|
|
|
|
from taskcluster_graph.commit_parser import parse_commit
|
|
|
|
from taskcluster_graph.slugid import slugid
|
2014-12-11 22:28:32 +00:00
|
|
|
from taskcluster_graph.slugidjar import SlugidJar
|
2014-11-26 18:11:28 +00:00
|
|
|
from taskcluster_graph.from_now import json_time_from_now, current_json_time
|
2014-11-29 23:05:46 +00:00
|
|
|
from taskcluster_graph.templates import Templates
|
2014-11-26 18:11:28 +00:00
|
|
|
|
|
|
|
import taskcluster_graph.build_task
|
|
|
|
|
|
|
|
ROOT = os.path.dirname(os.path.realpath(__file__))
|
2015-02-10 05:54:55 +00:00
|
|
|
GECKO = os.path.realpath(os.path.join(ROOT, '..', '..'))
|
2014-11-26 18:11:28 +00:00
|
|
|
DOCKER_ROOT = os.path.join(ROOT, '..', 'docker')
|
2015-05-05 22:31:28 +00:00
|
|
|
MOZHARNESS_CONFIG = os.path.join(GECKO, 'testing', 'mozharness', 'mozharness.json')
|
2014-11-26 18:11:28 +00:00
|
|
|
|
|
|
|
# XXX: If/when we have the taskcluster queue use construct url instead
|
|
|
|
ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
|
|
|
|
REGISTRY = open(os.path.join(DOCKER_ROOT, 'REGISTRY')).read().strip()
|
|
|
|
|
2014-11-26 18:11:39 +00:00
|
|
|
DEFINE_TASK = 'queue:define-task:aws-provisioner/{}'
|
|
|
|
|
2015-01-21 09:53:37 +00:00
|
|
|
TREEHERDER_ROUTE_PREFIX = 'tc-treeherder-stage'
|
2015-03-06 17:53:00 +00:00
|
|
|
TREEHERDER_ROUTES = {
|
|
|
|
'staging': 'tc-treeherder-stage',
|
|
|
|
'production': 'tc-treeherder'
|
|
|
|
}
|
2015-01-21 09:53:37 +00:00
|
|
|
|
2014-12-22 18:39:42 +00:00
|
|
|
DEFAULT_TRY = 'try: -b do -p all -u all'
|
2014-12-23 18:29:39 +00:00
|
|
|
DEFAULT_JOB_PATH = os.path.join(
|
|
|
|
ROOT, 'tasks', 'branches', 'mozilla-central', 'job_flags.yml'
|
|
|
|
)
|
2014-12-22 18:39:42 +00:00
|
|
|
|
2015-05-05 22:31:28 +00:00
|
|
|
def load_mozharness_info():
|
|
|
|
with open(MOZHARNESS_CONFIG) as content:
|
2015-05-06 00:00:06 +00:00
|
|
|
return json.load(content)
|
2014-11-26 18:11:28 +00:00
|
|
|
|
|
|
|
def docker_image(name):
|
|
|
|
''' Determine the docker tag/revision from an in tree docker file '''
|
|
|
|
repository_path = os.path.join(DOCKER_ROOT, name, 'REPOSITORY')
|
|
|
|
repository = REGISTRY
|
|
|
|
|
|
|
|
version = open(os.path.join(DOCKER_ROOT, name, 'VERSION')).read().strip()
|
|
|
|
|
|
|
|
if os.path.isfile(repository_path):
|
|
|
|
repository = open(repository_path).read().strip()
|
|
|
|
|
|
|
|
return '{}/{}:{}'.format(repository, name, version)
|
|
|
|
|
2014-11-26 18:11:32 +00:00
|
|
|
def get_task(task_id):
|
|
|
|
return json.load(urllib2.urlopen("https://queue.taskcluster.net/v1/task/" + task_id))
|
|
|
|
|
2014-12-22 18:39:42 +00:00
|
|
|
|
2015-02-10 05:54:55 +00:00
|
|
|
def gaia_info():
|
|
|
|
'''
|
|
|
|
Fetch details from in tree gaia.json (which links this version of
|
|
|
|
gecko->gaia) and construct the usual base/head/ref/rev pairing...
|
|
|
|
'''
|
|
|
|
gaia = json.load(open(os.path.join(GECKO, 'b2g', 'config', 'gaia.json')))
|
|
|
|
|
|
|
|
if gaia['git'] is None or \
|
|
|
|
gaia['git']['remote'] == '' or \
|
|
|
|
gaia['git']['git_revision'] == '' or \
|
|
|
|
gaia['git']['branch'] == '':
|
|
|
|
|
|
|
|
# Just use the hg params...
|
|
|
|
return {
|
|
|
|
'gaia_base_repository': 'https://hg.mozilla.org/{}'.format(gaia['repo_path']),
|
|
|
|
'gaia_head_repository': 'https://hg.mozilla.org/{}'.format(gaia['repo_path']),
|
|
|
|
'gaia_ref': gaia['revision'],
|
|
|
|
'gaia_rev': gaia['revision']
|
|
|
|
}
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Use git
|
|
|
|
return {
|
|
|
|
'gaia_base_repository': gaia['git']['remote'],
|
|
|
|
'gaia_head_repository': gaia['git']['remote'],
|
|
|
|
'gaia_rev': gaia['git']['git_revision'],
|
|
|
|
'gaia_ref': gaia['git']['branch'],
|
|
|
|
}
|
|
|
|
|
2015-03-06 17:53:00 +00:00
|
|
|
def decorate_task_treeherder_routes(task, suffix):
|
|
|
|
"""
|
|
|
|
Decorate the given task with treeherder routes.
|
|
|
|
|
|
|
|
Uses task.extra.treeherderEnv if available otherwise defaults to only
|
|
|
|
staging.
|
|
|
|
|
|
|
|
:param dict task: task definition.
|
|
|
|
:param str suffix: The project/revision_hash portion of the route.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if 'extra' not in task:
|
|
|
|
return
|
|
|
|
|
|
|
|
if 'routes' not in task:
|
|
|
|
task['routes'] = []
|
|
|
|
|
|
|
|
treeheder_env = task['extra'].get('treeherderEnv', ['staging'])
|
|
|
|
|
|
|
|
for env in treeheder_env:
|
|
|
|
task['routes'].append('{}.{}'.format(TREEHERDER_ROUTES[env], suffix))
|
|
|
|
|
2014-12-11 22:28:32 +00:00
|
|
|
@CommandProvider
|
|
|
|
class DecisionTask(object):
|
|
|
|
@Command('taskcluster-decision', category="ci",
|
|
|
|
description="Build a decision task")
|
|
|
|
@CommandArgument('--project',
|
|
|
|
required=True,
|
|
|
|
help='Treeherder project name')
|
2015-01-21 09:53:37 +00:00
|
|
|
@CommandArgument('--url',
|
2014-12-22 18:39:42 +00:00
|
|
|
required=True,
|
|
|
|
help='Gecko repository to use as head repository.')
|
2014-12-11 22:28:32 +00:00
|
|
|
@CommandArgument('--revision',
|
|
|
|
required=True,
|
|
|
|
help='Revision for this project')
|
2015-02-06 08:18:42 +00:00
|
|
|
@CommandArgument('--revision-hash',
|
|
|
|
help='Treeherder revision hash')
|
2014-12-11 22:28:32 +00:00
|
|
|
@CommandArgument('--comment',
|
|
|
|
required=True,
|
|
|
|
help='Commit message for this revision')
|
|
|
|
@CommandArgument('--owner',
|
|
|
|
required=True,
|
|
|
|
help='email address of who owns this graph')
|
|
|
|
@CommandArgument('task', help="Path to decision task to run.")
|
|
|
|
def run_task(self, **params):
|
|
|
|
templates = Templates(ROOT)
|
|
|
|
# Template parameters used when expanding the graph
|
2015-02-10 05:54:55 +00:00
|
|
|
parameters = dict(gaia_info().items() + {
|
2014-12-11 22:28:32 +00:00
|
|
|
'source': 'http://todo.com/soon',
|
|
|
|
'project': params['project'],
|
|
|
|
'comment': params['comment'],
|
2015-01-21 09:53:37 +00:00
|
|
|
'url': params['url'],
|
2014-12-11 22:28:32 +00:00
|
|
|
'revision': params['revision'],
|
2015-02-06 08:18:42 +00:00
|
|
|
'revision_hash': params.get('revision_hash', ''),
|
2014-12-11 22:28:32 +00:00
|
|
|
'owner': params['owner'],
|
|
|
|
'as_slugid': SlugidJar(),
|
|
|
|
'from_now': json_time_from_now,
|
|
|
|
'now': datetime.datetime.now().isoformat()
|
2015-02-10 05:54:55 +00:00
|
|
|
}.items())
|
2014-12-11 22:28:32 +00:00
|
|
|
task = templates.load(params['task'], parameters)
|
|
|
|
print(json.dumps(task, indent=4))
|
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
@CommandProvider
|
2014-12-22 18:39:42 +00:00
|
|
|
class Graph(object):
|
|
|
|
@Command('taskcluster-graph', category="ci",
|
|
|
|
description="Create taskcluster task graph")
|
2014-11-29 23:05:46 +00:00
|
|
|
@CommandArgument('--base-repository',
|
2014-12-05 06:33:01 +00:00
|
|
|
default=os.environ.get('GECKO_BASE_REPOSITORY'),
|
2014-11-29 23:05:46 +00:00
|
|
|
help='URL for "base" repository to clone')
|
|
|
|
@CommandArgument('--head-repository',
|
2014-12-05 06:33:01 +00:00
|
|
|
default=os.environ.get('GECKO_HEAD_REPOSITORY'),
|
|
|
|
help='URL for "head" repository to fetch revision from')
|
2014-11-29 23:05:46 +00:00
|
|
|
@CommandArgument('--head-ref',
|
2014-12-05 06:33:01 +00:00
|
|
|
default=os.environ.get('GECKO_HEAD_REF'),
|
2014-11-29 23:05:46 +00:00
|
|
|
help='Reference (this is same as rev usually for hg)')
|
|
|
|
@CommandArgument('--head-rev',
|
2014-12-05 06:33:01 +00:00
|
|
|
default=os.environ.get('GECKO_HEAD_REV'),
|
|
|
|
help='Commit revision to use from head repository')
|
2014-11-26 18:11:28 +00:00
|
|
|
@CommandArgument('--message',
|
2014-12-22 18:39:42 +00:00
|
|
|
help='Commit message to be parsed. Example: "try: -b do -p all -u all"')
|
2015-01-21 09:53:37 +00:00
|
|
|
@CommandArgument('--revision-hash',
|
|
|
|
required=False,
|
|
|
|
help='Treeherder revision hash to attach results to')
|
2014-12-22 18:39:42 +00:00
|
|
|
@CommandArgument('--project',
|
2014-11-26 18:11:28 +00:00
|
|
|
required=True,
|
2014-12-22 18:39:42 +00:00
|
|
|
help='Project to use for creating task graph. Example: --project=try')
|
2015-03-31 08:51:22 +00:00
|
|
|
@CommandArgument('--pushlog-id',
|
|
|
|
dest='pushlog_id',
|
|
|
|
required=False,
|
|
|
|
default=0)
|
2014-11-26 18:11:28 +00:00
|
|
|
@CommandArgument('--owner',
|
2014-11-29 23:05:46 +00:00
|
|
|
required=True,
|
2014-11-26 18:11:28 +00:00
|
|
|
help='email address of who owns this graph')
|
|
|
|
@CommandArgument('--extend-graph',
|
|
|
|
action="store_true", dest="ci", help='Omit create graph arguments')
|
2014-11-29 23:05:46 +00:00
|
|
|
def create_graph(self, **params):
|
2014-12-22 18:39:42 +00:00
|
|
|
project = params['project']
|
|
|
|
message = params.get('message', '') if project == 'try' else DEFAULT_TRY
|
|
|
|
|
|
|
|
# Message would only be blank when not created from decision task
|
|
|
|
if project == 'try' and not message:
|
|
|
|
sys.stderr.write(
|
|
|
|
"Must supply commit message when creating try graph. " \
|
|
|
|
"Example: --message='try: -b do -p all -u all'"
|
|
|
|
)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2014-11-29 23:05:46 +00:00
|
|
|
templates = Templates(ROOT)
|
2014-12-22 18:39:42 +00:00
|
|
|
job_path = os.path.join(ROOT, 'tasks', 'branches', project, 'job_flags.yml')
|
|
|
|
job_path = job_path if os.path.exists(job_path) else DEFAULT_JOB_PATH
|
|
|
|
|
|
|
|
jobs = templates.load(job_path, {})
|
|
|
|
|
|
|
|
job_graph = parse_commit(message, jobs)
|
2015-05-06 00:00:06 +00:00
|
|
|
mozharness = load_mozharness_info()
|
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
# Template parameters used when expanding the graph
|
2015-02-10 05:54:55 +00:00
|
|
|
parameters = dict(gaia_info().items() + {
|
2015-03-31 08:51:22 +00:00
|
|
|
'project': project,
|
|
|
|
'pushlog_id': params.get('pushlog_id', 0),
|
2014-11-26 18:11:28 +00:00
|
|
|
'docker_image': docker_image,
|
2014-11-29 23:05:46 +00:00
|
|
|
'base_repository': params['base_repository'] or \
|
|
|
|
params['head_repository'],
|
|
|
|
'head_repository': params['head_repository'],
|
|
|
|
'head_ref': params['head_ref'] or params['head_rev'],
|
|
|
|
'head_rev': params['head_rev'],
|
|
|
|
'owner': params['owner'],
|
2014-11-26 18:11:28 +00:00
|
|
|
'from_now': json_time_from_now,
|
2014-12-31 12:58:19 +00:00
|
|
|
'now': datetime.datetime.now().isoformat(),
|
2015-05-06 00:00:06 +00:00
|
|
|
'mozharness_repository': mozharness['repo'],
|
|
|
|
'mozharness_rev': mozharness['revision'],
|
|
|
|
'mozharness_ref':mozharness.get('reference', mozharness['revision']),
|
2015-01-21 09:53:37 +00:00
|
|
|
'revision_hash': params['revision_hash']
|
2015-02-10 05:54:55 +00:00
|
|
|
}.items())
|
2014-11-26 18:11:28 +00:00
|
|
|
|
2015-03-06 17:53:00 +00:00
|
|
|
treeherder_route = '{}.{}'.format(
|
2015-01-21 09:53:37 +00:00
|
|
|
params['project'],
|
|
|
|
params.get('revision_hash', '')
|
|
|
|
)
|
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
# Task graph we are generating for taskcluster...
|
|
|
|
graph = {
|
2014-11-26 18:11:39 +00:00
|
|
|
'tasks': [],
|
|
|
|
'scopes': []
|
2014-11-26 18:11:28 +00:00
|
|
|
}
|
|
|
|
|
2015-01-21 09:53:37 +00:00
|
|
|
if params['revision_hash']:
|
2015-03-06 17:53:00 +00:00
|
|
|
for env in TREEHERDER_ROUTES:
|
|
|
|
graph['scopes'].append('queue:route:{}.{}'.format(TREEHERDER_ROUTES[env], treeherder_route))
|
2015-01-21 09:53:37 +00:00
|
|
|
|
2014-12-09 23:03:07 +00:00
|
|
|
graph['metadata'] = {
|
|
|
|
'source': 'http://todo.com/what/goes/here',
|
|
|
|
'owner': params['owner'],
|
|
|
|
# TODO: Add full mach commands to this example?
|
2014-12-22 18:39:42 +00:00
|
|
|
'description': 'Task graph generated via ./mach taskcluster-graph',
|
|
|
|
'name': 'task graph local'
|
2014-12-09 23:03:07 +00:00
|
|
|
}
|
2014-11-26 18:11:28 +00:00
|
|
|
|
|
|
|
for build in job_graph:
|
2014-11-29 23:05:47 +00:00
|
|
|
build_parameters = dict(parameters)
|
2014-11-26 18:11:28 +00:00
|
|
|
build_parameters['build_slugid'] = slugid()
|
2014-11-29 23:05:46 +00:00
|
|
|
build_task = templates.load(build['task'], build_parameters)
|
2014-11-26 18:11:28 +00:00
|
|
|
|
2015-01-21 09:53:37 +00:00
|
|
|
if 'routes' not in build_task['task']:
|
2015-03-06 17:53:00 +00:00
|
|
|
build_task['task']['routes'] = []
|
2015-01-21 09:53:37 +00:00
|
|
|
|
|
|
|
if params['revision_hash']:
|
2015-03-06 17:53:00 +00:00
|
|
|
decorate_task_treeherder_routes(build_task['task'],
|
|
|
|
treeherder_route)
|
2015-01-21 09:53:37 +00:00
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
# Ensure each build graph is valid after construction.
|
|
|
|
taskcluster_graph.build_task.validate(build_task)
|
|
|
|
graph['tasks'].append(build_task)
|
|
|
|
|
|
|
|
tests_url = ARTIFACT_URL.format(
|
|
|
|
build_parameters['build_slugid'],
|
|
|
|
build_task['task']['extra']['locations']['tests']
|
|
|
|
)
|
|
|
|
|
|
|
|
build_url = ARTIFACT_URL.format(
|
|
|
|
build_parameters['build_slugid'],
|
|
|
|
build_task['task']['extra']['locations']['build']
|
|
|
|
)
|
|
|
|
|
2015-03-12 22:43:28 +00:00
|
|
|
# img_url is only necessary for device builds
|
|
|
|
img_url = ARTIFACT_URL.format(
|
|
|
|
build_parameters['build_slugid'],
|
|
|
|
build_task['task']['extra']['locations'].get('img', '')
|
|
|
|
)
|
|
|
|
|
2014-11-26 18:11:39 +00:00
|
|
|
define_task = DEFINE_TASK.format(build_task['task']['workerType'])
|
|
|
|
|
|
|
|
graph['scopes'].append(define_task)
|
|
|
|
graph['scopes'].extend(build_task['task'].get('scopes', []))
|
2015-04-30 19:26:38 +00:00
|
|
|
route_scopes = map(lambda route: 'queue:route:' + route, build_task['task'].get('routes', []))
|
|
|
|
graph['scopes'].extend(route_scopes)
|
2014-11-26 18:11:39 +00:00
|
|
|
|
2015-01-21 09:53:37 +00:00
|
|
|
# Treeherder symbol configuration for the graph required for each
|
|
|
|
# build so tests know which platform they belong to.
|
|
|
|
build_treeherder_config = build_task['task']['extra']['treeherder']
|
|
|
|
|
|
|
|
if 'machine' not in build_treeherder_config:
|
|
|
|
message = '({}), extra.treeherder.machine required for all builds'
|
|
|
|
raise ValueError(message.format(build['task']))
|
|
|
|
|
|
|
|
if 'build' not in build_treeherder_config:
|
|
|
|
build_treeherder_config['build'] = \
|
|
|
|
build_treeherder_config['machine']
|
|
|
|
|
|
|
|
if 'collection' not in build_treeherder_config:
|
|
|
|
build_treeherder_config['collection'] = { 'opt': True }
|
|
|
|
|
|
|
|
if len(build_treeherder_config['collection'].keys()) != 1:
|
|
|
|
message = '({}), extra.treeherder.collection must contain one type'
|
|
|
|
raise ValueError(message.fomrat(build['task']))
|
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
for test in build['dependents']:
|
2014-11-26 18:11:35 +00:00
|
|
|
test = test['allowed_build_tasks'][build['task']]
|
2014-11-26 18:11:28 +00:00
|
|
|
test_parameters = copy.copy(build_parameters)
|
|
|
|
test_parameters['build_url'] = build_url
|
2015-03-12 22:43:28 +00:00
|
|
|
test_parameters['img_url'] = img_url
|
2014-11-26 18:11:28 +00:00
|
|
|
test_parameters['tests_url'] = tests_url
|
|
|
|
|
2015-03-14 02:54:22 +00:00
|
|
|
test_definition = templates.load(test['task'], {})['task']
|
|
|
|
chunk_config = test_definition['extra']['chunks']
|
|
|
|
|
|
|
|
# Allow branch configs to override task level chunking...
|
2014-11-26 18:11:28 +00:00
|
|
|
if 'chunks' in test:
|
2015-03-14 02:54:22 +00:00
|
|
|
chunk_config['total'] = test['chunks']
|
|
|
|
|
|
|
|
test_parameters['total_chunks'] = chunk_config['total']
|
2014-11-26 18:11:28 +00:00
|
|
|
|
2015-03-14 02:54:22 +00:00
|
|
|
for chunk in range(1, chunk_config['total'] + 1):
|
2015-02-10 18:53:02 +00:00
|
|
|
if 'only_chunks' in test and \
|
|
|
|
chunk not in test['only_chunks']:
|
2015-03-14 02:54:22 +00:00
|
|
|
continue
|
2015-02-10 18:53:02 +00:00
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
test_parameters['chunk'] = chunk
|
2014-11-29 23:05:46 +00:00
|
|
|
test_task = templates.load(test['task'], test_parameters)
|
2014-11-26 18:11:28 +00:00
|
|
|
test_task['taskId'] = slugid()
|
|
|
|
|
|
|
|
if 'requires' not in test_task:
|
|
|
|
test_task['requires'] = []
|
|
|
|
|
|
|
|
test_task['requires'].append(test_parameters['build_slugid'])
|
2014-11-26 18:11:39 +00:00
|
|
|
|
2015-01-21 09:53:37 +00:00
|
|
|
if 'treeherder' not in test_task['task']['extra']:
|
|
|
|
test_task['task']['extra']['treeherder'] = {}
|
|
|
|
|
|
|
|
# Copy over any treeherder configuration from the build so
|
|
|
|
# tests show up under the same platform...
|
|
|
|
test_treeherder_config = test_task['task']['extra']['treeherder']
|
|
|
|
|
|
|
|
test_treeherder_config['collection'] = \
|
|
|
|
build_treeherder_config.get('collection', {})
|
|
|
|
|
|
|
|
test_treeherder_config['build'] = \
|
|
|
|
build_treeherder_config.get('build', {})
|
|
|
|
|
|
|
|
test_treeherder_config['machine'] = \
|
|
|
|
build_treeherder_config.get('machine', {})
|
|
|
|
|
|
|
|
if 'routes' not in test_task['task']:
|
|
|
|
test_task['task']['routes'] = []
|
|
|
|
|
|
|
|
if 'scopes' not in test_task['task']:
|
|
|
|
test_task['task']['scopes'] = []
|
|
|
|
|
|
|
|
if params['revision_hash']:
|
2015-03-06 17:53:00 +00:00
|
|
|
decorate_task_treeherder_routes(
|
|
|
|
test_task['task'], treeherder_route)
|
2015-01-21 09:53:37 +00:00
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
graph['tasks'].append(test_task)
|
|
|
|
|
2014-11-26 18:11:39 +00:00
|
|
|
define_task = DEFINE_TASK.format(
|
|
|
|
test_task['task']['workerType']
|
|
|
|
)
|
|
|
|
|
|
|
|
graph['scopes'].append(define_task)
|
|
|
|
graph['scopes'].extend(test_task['task'].get('scopes', []))
|
|
|
|
|
|
|
|
graph['scopes'] = list(set(graph['scopes']))
|
2014-12-09 23:03:07 +00:00
|
|
|
|
|
|
|
# When we are extending the graph remove extra fields...
|
|
|
|
if params['ci'] is True:
|
|
|
|
graph.pop('scopes', None)
|
|
|
|
graph.pop('metadata', None)
|
|
|
|
|
2014-11-26 18:11:28 +00:00
|
|
|
print(json.dumps(graph, indent=4))
|