feat(build runner): added in context, dockerfile_location

this is a new feature meant to allow people to use any file as
  a dockerfile and any folder as a context directory
This commit is contained in:
Charlton Austin 2017-03-21 17:24:11 -04:00
parent 90b130fe16
commit e6d201e0b0
29 changed files with 531 additions and 111 deletions

View file

@ -1,5 +1,5 @@
""" Create, list, cancel and get status/logs of repository builds. """
import os
from urlparse import urlparse
import logging
@ -51,6 +51,7 @@ def user_view(user):
'is_robot': user.robot,
}
def trigger_view(trigger, can_read=False, can_admin=False, for_build=False):
if trigger and trigger.uuid:
build_trigger = BuildTriggerHandler.get_handler(trigger)
@ -133,6 +134,8 @@ def build_status_view(build_obj):
'display_name': build_obj.display_name,
'status': status or {},
'subdirectory': job_config.get('build_subdir', ''),
'dockerfile_path': job_config.get('build_subdir', ''),
'context': job_config.get('context', ''),
'tags': job_config.get('docker_tags', []),
'manual_user': job_config.get('manual_user', None),
'is_writer': can_write,
@ -244,6 +247,7 @@ class RepositoryBuildList(RepositoryParamResource):
raise InvalidRequest('Invalid Archive URL: Must be http or https')
subdir = request_json['subdirectory'] if 'subdirectory' in request_json else ''
context = request_json['context'] if 'context' in request_json else os.path.dirname(subdir)
tags = request_json.get('docker_tags', ['latest'])
pull_robot_name = request_json.get('pull_robot', None)
@ -291,9 +295,9 @@ class RepositoryBuildList(RepositoryParamResource):
prepared.archive_url = archive_url
prepared.tags = tags
prepared.subdirectory = subdir
prepared.context = context
prepared.is_manual = True
prepared.metadata = {}
try:
build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
except MaximumBuildsQueuedException:

View file

@ -0,0 +1,22 @@
import pytest
from endpoints.api.trigger import is_parent
@pytest.mark.parametrize('context,dockerfile_path,expected', [
("/", "/a/b", True),
("/a", "/a/b", True),
("/a/b", "/a/b", False),
("/a//", "/a/b", True),
("/a", "/a//b/c", True),
("/a//", "a/b", True),
("/a/b", "a/bc/d", False),
("/d", "/a/b", False),
("/a/b", "/a/b.c", False),
("/a/b", "/a/b/b.c", True),
("", "/a/b.c", False),
("/a/b", "", False),
("", "", False),
])
def test_super_user_build_endpoints(context, dockerfile_path, expected):
assert is_parent(context, dockerfile_path) == expected

View file

@ -2,17 +2,21 @@
import json
import logging
from os import path
from urllib import quote
from urlparse import urlunparse
from flask import request, url_for
from app import app
from auth.permissions import (UserAdminPermission, AdministerOrganizationPermission,
ReadRepositoryPermission, AdministerRepositoryPermission)
from buildtrigger.basehandler import BuildTriggerHandler
from buildtrigger.triggerutil import (TriggerDeactivationException,
TriggerActivationException, EmptyRepositoryException,
RepositoryReadException, TriggerStartException)
from data import model
from data.model.build import update_build_trigger
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
log_action, request_error, query_param, parse_args, internal_only,
validate_json_request, api, path_param, abort,
@ -20,12 +24,9 @@ from endpoints.api import (RepositoryParamResource, nickname, resource, require_
from endpoints.exception import NotFound, Unauthorized, InvalidRequest
from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus
from endpoints.building import start_build, MaximumBuildsQueuedException
from data import model
from auth.permissions import (UserAdminPermission, AdministerOrganizationPermission,
ReadRepositoryPermission, AdministerRepositoryPermission)
from util.names import parse_robot_username
from endpoints.exception import NotFound, Unauthorized, InvalidRequest
from util.dockerfileparse import parse_dockerfile
from util.names import parse_robot_username
logger = logging.getLogger(__name__)
@ -131,19 +132,25 @@ class BuildTriggerSubdirs(RepositoryParamResource):
try:
subdirs = handler.list_build_subdirs()
context_map = {}
for file in subdirs:
context_map = handler.get_parent_directory_mappings(file, context_map)
return {
'subdir': ['/' + subdir for subdir in subdirs],
'status': 'success'
'dockerfile_paths': ['/' + subdir for subdir in subdirs],
'contextMap': context_map,
'status': 'success',
}
except EmptyRepositoryException as exc:
return {
'status': 'success',
'subdir': []
'contextMap': {},
'dockerfile_paths': [],
}
except RepositoryReadException as exc:
return {
'status': 'error',
'message': exc.message
'message': exc.message,
}
else:
raise Unauthorized()
@ -235,9 +242,7 @@ class BuildTriggerActivate(RepositoryParamResource):
raise request_error(message=exc.message)
# Save the updated config.
trigger.config = json.dumps(final_config)
trigger.write_token = write_token
trigger.save()
update_build_trigger(trigger, final_config, write_token=write_token)
# Log the trigger setup.
repo = model.repository.get_repository(namespace_name, repo_name)
@ -343,6 +348,15 @@ class BuildTriggerAnalyze(RepositoryParamResource):
'message': 'Could not parse the Dockerfile specified'
}
# Check whether the dockerfile_path is correct
if new_config_dict.get('context'):
if not is_parent(new_config_dict.get('context'), new_config_dict.get('dockerfile_path')):
return {
'status': 'error',
'message': 'Dockerfile, %s, is not child of the context, %s.' %
(new_config_dict.get('context'), new_config_dict.get('dockerfile_path'))
}
# Default to the current namespace.
base_namespace = namespace_name
base_repository = None
@ -399,6 +413,28 @@ class BuildTriggerAnalyze(RepositoryParamResource):
raise NotFound()
def is_parent(context, dockerfile_path):
""" This checks whether the context is a parent of the dockerfile_path"""
if context == "" or dockerfile_path == "":
return False
normalized_context = path.normpath(context)
if normalized_context[len(normalized_context) - 1] != path.sep:
normalized_context += path.sep
if normalized_context[0] != path.sep:
normalized_context = path.sep + normalized_context
normalized_subdir = path.normpath(path.dirname(dockerfile_path))
if normalized_subdir[0] != path.sep:
normalized_subdir = path.sep + normalized_subdir
if normalized_subdir[len(normalized_subdir) - 1] != path.sep:
normalized_subdir += path.sep
return normalized_subdir.startswith(normalized_context)
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/start')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('trigger_uuid', 'The UUID of the build trigger')
@ -418,7 +454,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
'description': '(Custom Only) If specified, the ref/SHA1 used to checkout a git repository.'
},
'refs': {
'type': ['object', 'null'],
'type': ['object', 'null'],
'description': '(SCM Only) If specified, the ref to build.'
}
},
@ -467,6 +503,7 @@ class ActivateBuildTrigger(RepositoryParamResource):
@path_param('trigger_uuid', 'The UUID of the build trigger')
class TriggerBuildList(RepositoryParamResource):
""" Resource to represent builds that were activated from the specified trigger. """
@require_repo_admin
@disallow_for_app_repositories
@parse_args()
@ -483,10 +520,12 @@ class TriggerBuildList(RepositoryParamResource):
FIELD_VALUE_LIMIT = 30
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/fields/<field_name>')
@internal_only
class BuildTriggerFieldValues(RepositoryParamResource):
""" Custom verb to fetch a values list for a particular field name. """
@require_repo_admin
@disallow_for_app_repositories
@nickname('listTriggerFieldValues')
@ -558,13 +597,13 @@ class BuildTriggerSources(RepositoryParamResource):
raise Unauthorized()
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/namespaces')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('trigger_uuid', 'The UUID of the build trigger')
@internal_only
class BuildTriggerSourceNamespaces(RepositoryParamResource):
""" Custom verb to fetch the list of namespaces (orgs, projects, etc) for the trigger config. """
@require_repo_admin
@disallow_for_app_repositories
@nickname('listTriggerBuildSourceNamespaces')