def post(self, namespace_name, repo_name, trigger_uuid): """ Manually start a build from the specified trigger. """ trigger = get_trigger(trigger_uuid) if not trigger.enabled: raise InvalidRequest("Trigger is not enabled.") handler = BuildTriggerHandler.get_handler(trigger) if not handler.is_active(): raise InvalidRequest("Trigger is not active.") try: repo = model.repository.get_repository(namespace_name, repo_name) pull_robot_name = model.build.get_pull_robot_name(trigger) run_parameters = request.get_json() prepared = handler.manual_start(run_parameters=run_parameters) build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name) except TriggerException as tse: raise InvalidRequest(tse.message) except MaximumBuildsQueuedException: abort(429, message="Maximum queued build rate exceeded.") except BuildTriggerDisabledException: abort(400, message="Build trigger is disabled") resp = build_status_view(build_request) repo_string = "%s/%s" % (namespace_name, repo_name) headers = { "Location": api.url_for( RepositoryBuildStatus, repository=repo_string, build_uuid=build_request.uuid ), } return resp, 201, headers
def post(self): """ Create a new repository. """ owner = get_authenticated_user() req = request.get_json() if owner is None and "namespace" not in "req": raise InvalidRequest( "Must provide a namespace or must be logged in.") namespace_name = req[ "namespace"] if "namespace" in req else owner.username permission = CreateRepositoryPermission(namespace_name) if permission.can(): repository_name = req["repository"] visibility = req["visibility"] if model.repo_exists(namespace_name, repository_name): raise request_error(message="Repository already exists") visibility = req["visibility"] if visibility == "private": check_allowed_private_repos(namespace_name) # Verify that the repository name is valid. if not REPOSITORY_NAME_REGEX.match(repository_name): raise InvalidRequest("Invalid repository name") kind = req.get("repo_kind", "image") or "image" created = model.create_repo( namespace_name, repository_name, owner, req["description"], visibility=visibility, repo_kind=kind, ) if created is None: raise InvalidRequest("Could not create repository") log_action( "create_repo", namespace_name, { "repo": repository_name, "namespace": namespace_name }, repo_name=repository_name, ) return { "namespace": namespace_name, "name": repository_name, "kind": kind, }, 201 raise Unauthorized()
def wrapped(self, *args, **kwargs): schema = self.schemas[schema_name] try: json_data = request.get_json() if json_data is None: if not optional: raise InvalidRequest("Missing JSON body") else: validate(json_data, schema) return func(self, *args, **kwargs) except ValidationError as ex: raise InvalidRequest(str(ex))
def post(self, namespace, repository, tag): """ Restores a repository tag back to a previous image in the repository. """ repo_ref = registry_model.lookup_repository(namespace, repository) if repo_ref is None: raise NotFound() # Restore the tag back to the previous image. image_id = request.get_json().get("image", None) manifest_digest = request.get_json().get("manifest_digest", None) if image_id is None and manifest_digest is None: raise InvalidRequest("Missing manifest_digest") # Data for logging the reversion/restoration. username = get_authenticated_user().username log_data = { "username": username, "repo": repository, "tag": tag, "image": image_id, "manifest_digest": manifest_digest, } manifest_or_legacy_image = None if manifest_digest is not None: manifest_or_legacy_image = registry_model.lookup_manifest_by_digest( repo_ref, manifest_digest, allow_dead=True, require_available=True) elif image_id is not None: manifest_or_legacy_image = registry_model.get_legacy_image( repo_ref, image_id) if manifest_or_legacy_image is None: raise NotFound() if not registry_model.retarget_tag( repo_ref, tag, manifest_or_legacy_image, storage, docker_v2_signing_key, is_reversion=True, ): raise InvalidRequest("Could not restore tag") log_action("revert_tag", namespace, log_data, repo_name=repository) return {}
def post(self): """Create a new repository.""" owner = get_authenticated_user() req = request.get_json() if owner is None and 'namespace' not in 'req': raise InvalidRequest( 'Must provide a namespace or must be logged in.') namespace_name = req[ 'namespace'] if 'namespace' in req else owner.username permission = CreateRepositoryPermission(namespace_name) if permission.can(): repository_name = req['repository'] visibility = req['visibility'] if model.repo_exists(namespace_name, repository_name): raise request_error(message='Repository already exists') visibility = req['visibility'] if visibility == 'private': check_allowed_private_repos(namespace_name) # Verify that the repository name is valid. if not REPOSITORY_NAME_REGEX.match(repository_name): raise InvalidRequest('Invalid repository name') kind = req.get('repo_kind', 'image') or 'image' model.create_repo(namespace_name, repository_name, owner, req['description'], visibility=visibility, repo_kind=kind) log_action('create_repo', namespace_name, { 'repo': repository_name, 'namespace': namespace_name }, repo_name=repository_name) return { 'namespace': namespace_name, 'name': repository_name, 'kind': kind, }, 201 raise Unauthorized()
def put(self, namespace_name, repo_name, trigger_uuid): """ Updates the specified build trigger. """ trigger = get_trigger(trigger_uuid) handler = BuildTriggerHandler.get_handler(trigger) if not handler.is_active(): raise InvalidRequest("Cannot update an unactivated trigger") enable = request.get_json()["enabled"] model.build.toggle_build_trigger(trigger, enable) log_action( "toggle_repo_trigger", namespace_name, { "repo": repo_name, "trigger_id": trigger_uuid, "service": trigger.service.name, "enabled": enable, }, repo=model.repository.get_repository(namespace_name, repo_name), ) return trigger_view(trigger)
def get(self, page_token, parsed_args): """ Fetch the list of repositories visible to the current user under a variety of situations. """ # Ensure that the user requests either filtered by a namespace, only starred repositories, # or public repositories. This ensures that the user is not requesting *all* visible repos, # which can cause a surge in DB CPU usage. if not parsed_args['namespace'] and not parsed_args[ 'starred'] and not parsed_args['public']: raise InvalidRequest( 'namespace, starred or public are required for this API call') user = get_authenticated_user() username = user.username if user else None last_modified = parsed_args['last_modified'] popularity = parsed_args['popularity'] if parsed_args['starred'] and not username: # No repositories should be returned, as there is no user. abort(400) repos, next_page_token = model.get_repo_list( parsed_args['starred'], user, parsed_args['repo_kind'], parsed_args['namespace'], username, parsed_args['public'], page_token, last_modified, popularity) return { 'repositories': [repo.to_dict() for repo in repos] }, next_page_token
def request_error(exception=None, **kwargs): data = kwargs.copy() message = "Request error." if exception: message = str(exception) message = data.pop("message", message) raise InvalidRequest(message, data)
def post(self, namespace_name, repo_name, trigger_uuid): """ List the build sources for the trigger configuration thus far. """ namespace = request.get_json().get("namespace") if namespace is None: raise InvalidRequest() trigger = get_trigger(trigger_uuid) user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): handler = BuildTriggerHandler.get_handler(trigger) try: return {"sources": handler.list_build_sources_for_namespace(namespace)} except TriggerException as rre: raise InvalidRequest(rre.message) else: raise Unauthorized()
def wrapper(self, *args, **kwargs): # Team syncing can only be enabled if we have a federated service. if features.TEAM_SYNCING and authentication.federated_service: orgname = kwargs["orgname"] teamname = kwargs["teamname"] if model.team.get_team_sync_information(orgname, teamname): if not except_robots or not parse_robot_username( kwargs.get("membername", "")): raise InvalidRequest( "Cannot call this method on an auth-synced team") return func(self, *args, **kwargs)
def delete(self, namespace, repository, build_uuid): """ Cancels a repository build. """ try: build = model.build.get_repository_build(build_uuid) except model.build.InvalidRepositoryBuildException: raise NotFound() if build.repository.name != repository or build.repository.namespace_user.username != namespace: raise NotFound() if model.build.cancel_repository_build(build, dockerfile_build_queue): return 'Okay', 201 else: raise InvalidRequest('Build is currently running or has finished')
def put(self, namespace_name, repo_name, trigger_uuid): """ Updates the specified build trigger. """ trigger = get_trigger(trigger_uuid) handler = BuildTriggerHandler.get_handler(trigger) if not handler.is_active(): raise InvalidRequest('Cannot update an unactivated trigger') enable = request.get_json()['enabled'] model.build.toggle_build_trigger(trigger, enable) log_action('toggle_repo_trigger', namespace_name, {'repo': repo_name, 'trigger_id': trigger_uuid, 'service': trigger.service.name, 'enabled': enable}, repo=model.repository.get_repository(namespace_name, repo_name)) return trigger_view(trigger)
def get(self, namespace_name, repo_name, trigger_uuid): """ List the build sources for the trigger configuration thus far. """ trigger = get_trigger(trigger_uuid) user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): handler = BuildTriggerHandler.get_handler(trigger) try: return {"namespaces": handler.list_build_source_namespaces()} except TriggerException as rre: raise InvalidRequest(str(rre)) from rre else: raise Unauthorized()
def post(self, service_id): """ Generates the auth URL and CSRF token explicitly for OIDC/OAuth-associated login. """ login_service = oauth_login.get_service(service_id) if login_service is None: raise InvalidRequest() csrf_token = generate_csrf_token(OAUTH_CSRF_TOKEN_NAME) kind = request.get_json()['kind'] redirect_suffix = '' if kind == 'login' else '/' + kind try: login_scopes = login_service.get_login_scopes() auth_url = login_service.get_auth_url(url_scheme_and_hostname, redirect_suffix, csrf_token, login_scopes) return {'auth_url': auth_url} except DiscoveryFailureException as dfe: logger.exception('Could not discovery OAuth endpoint information') raise DownstreamIssue(dfe.message)
def post(self, orgname, teamname): if _syncing_setup_allowed(orgname): try: team = model.team.get_organization_team(orgname, teamname) except model.InvalidTeamException: raise NotFound() config = request.get_json() # Ensure that the specified config points to a valid group. status, err = authentication.check_group_lookup_args(config) if not status: raise InvalidRequest("Could not sync to group: %s" % err) # Set the team's syncing config. model.team.set_team_syncing(team, authentication.federated_service, config) return team_view(orgname, team) raise Unauthorized()
def post(self, namespace, repository): """ Request that a repository be built and pushed from the specified input. """ logger.debug("User requested repository initialization.") request_json = request.get_json() dockerfile_id = request_json.get("file_id", None) archive_url = request_json.get("archive_url", None) if not dockerfile_id and not archive_url: raise InvalidRequest("file_id or archive_url required") if archive_url: archive_match = None try: archive_match = urlparse(archive_url) except ValueError: pass if not archive_match: raise InvalidRequest( "Invalid Archive URL: Must be a valid URI") scheme = archive_match.scheme if scheme != "http" and scheme != "https": raise InvalidRequest( "Invalid Archive URL: Must be http or https") context, subdir = self.get_dockerfile_context(request_json) tags = request_json.get("docker_tags", ["latest"]) pull_robot_name = request_json.get("pull_robot", None) # Verify the security behind the pull robot. if pull_robot_name: result = parse_robot_username(pull_robot_name) if result: try: model.user.lookup_robot(pull_robot_name) except model.InvalidRobotException: raise NotFound() # Make sure the user has administer permissions for the robot's namespace. (robot_namespace, _) = result if not AdministerOrganizationPermission(robot_namespace).can(): raise Unauthorized() else: raise Unauthorized() # Check if the dockerfile resource has already been used. If so, then it # can only be reused if the user has access to the repository in which the # dockerfile was previously built. if dockerfile_id: associated_repository = model.build.get_repository_for_resource( dockerfile_id) if associated_repository: if not ModifyRepositoryPermission( associated_repository.namespace_user.username, associated_repository.name): raise Unauthorized() # Start the build. repo = model.repository.get_repository(namespace, repository) if repo is None: raise NotFound() try: build_name = (user_files.get_file_checksum(dockerfile_id) if dockerfile_id else hashlib.sha224( archive_url.encode("ascii")).hexdigest()[0:7]) except IOError: raise InvalidRequest("File %s could not be found or is invalid" % dockerfile_id) prepared = PreparedBuild() prepared.build_name = build_name prepared.dockerfile_id = dockerfile_id 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: abort(429, message="Maximum queued build rate exceeded.") except BuildTriggerDisabledException: abort(400, message="Build trigger is disabled") resp = build_status_view(build_request) repo_string = "%s/%s" % (namespace, repository) headers = { "Location": api.url_for(RepositoryBuildStatus, repository=repo_string, build_uuid=build_request.uuid), } return resp, 201, headers
def put(self, namespace, repository, tag): """ Change which image a tag points to or create a new tag. """ if not TAG_REGEX.match(tag): abort(400, TAG_ERROR) repo_ref = registry_model.lookup_repository(namespace, repository) if repo_ref is None: raise NotFound() if "expiration" in request.get_json(): tag_ref = registry_model.get_repo_tag(repo_ref, tag) if tag_ref is None: raise NotFound() expiration = request.get_json().get("expiration") expiration_date = None if expiration is not None: try: expiration_date = datetime.utcfromtimestamp( float(expiration)) except ValueError: abort(400) if expiration_date <= datetime.now(): abort(400) existing_end_ts, ok = registry_model.change_repository_tag_expiration( tag_ref, expiration_date) if ok: if not (existing_end_ts is None and expiration_date is None): log_action( "change_tag_expiration", namespace, { "username": get_authenticated_user().username, "repo": repository, "tag": tag, "namespace": namespace, "expiration_date": expiration_date, "old_expiration_date": existing_end_ts, }, repo_name=repository, ) else: raise InvalidRequest( "Could not update tag expiration; Tag has probably changed" ) if "image" in request.get_json( ) or "manifest_digest" in request.get_json(): existing_tag = registry_model.get_repo_tag( repo_ref, tag, include_legacy_image=True) manifest_or_image = None image_id = None manifest_digest = None if "manifest_digest" in request.get_json(): manifest_digest = request.get_json()["manifest_digest"] manifest_or_image = registry_model.lookup_manifest_by_digest( repo_ref, manifest_digest, require_available=True) else: image_id = request.get_json()["image"] manifest_or_image = registry_model.get_legacy_image( repo_ref, image_id) if manifest_or_image is None: raise NotFound() existing_manifest = ( registry_model.get_manifest_for_tag(existing_tag) if existing_tag else None) existing_manifest_digest = existing_manifest.digest if existing_manifest else None if not registry_model.retarget_tag(repo_ref, tag, manifest_or_image, storage, docker_v2_signing_key): raise InvalidRequest("Could not move tag") username = get_authenticated_user().username log_action( "move_tag" if existing_tag else "create_tag", namespace, { "username": username, "repo": repository, "tag": tag, "namespace": namespace, "image": image_id, "manifest_digest": manifest_digest, "original_manifest_digest": existing_manifest_digest, }, repo_name=repository, ) return "Updated", 201
def post(self, namespace_name, repo_name, trigger_uuid): """ Activate the specified build trigger. """ trigger = get_trigger(trigger_uuid) handler = BuildTriggerHandler.get_handler(trigger) if handler.is_active(): raise InvalidRequest('Trigger config is not sufficient for activation.') user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): # Update the pull robot (if any). pull_robot_name = request.get_json().get('pull_robot', None) if pull_robot_name: try: pull_robot = model.user.lookup_robot(pull_robot_name) except model.InvalidRobotException: raise NotFound() # Make sure the user has administer permissions for the robot's namespace. (robot_namespace, _) = parse_robot_username(pull_robot_name) if not AdministerOrganizationPermission(robot_namespace).can(): raise Unauthorized() # Make sure the namespace matches that of the trigger. if robot_namespace != namespace_name: raise Unauthorized() # Set the pull robot. trigger.pull_robot = pull_robot # Update the config. new_config_dict = request.get_json()['config'] write_token_name = 'Build Trigger: %s' % trigger.service.name write_token = model.token.create_delegate_token(namespace_name, repo_name, write_token_name, 'write') try: path = url_for('webhooks.build_trigger_webhook', trigger_uuid=trigger.uuid) authed_url = _prepare_webhook_url(app.config['PREFERRED_URL_SCHEME'], '$token', write_token.get_code(), app.config['SERVER_HOSTNAME'], path) handler = BuildTriggerHandler.get_handler(trigger, new_config_dict) final_config, private_config = handler.activate(authed_url) if 'private_key' in private_config: trigger.secure_private_key = DecryptedValue(private_config['private_key']) # TODO(remove-unenc): Remove legacy field. if ActiveDataMigration.has_flag(ERTMigrationFlags.WRITE_OLD_FIELDS): trigger.private_key = private_config['private_key'] except TriggerException as exc: write_token.delete_instance() raise request_error(message=exc.message) # Save the updated config. update_build_trigger(trigger, final_config, write_token=write_token) # Log the trigger setup. repo = model.repository.get_repository(namespace_name, repo_name) log_action('setup_repo_trigger', namespace_name, {'repo': repo_name, 'namespace': namespace_name, 'trigger_id': trigger.uuid, 'service': trigger.service.name, 'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None, 'config': final_config}, repo=repo) return trigger_view(trigger, can_admin=True) else: raise Unauthorized()
def put(self, namespace, repository, tag): """ Change which image a tag points to or create a new tag.""" if not TAG_REGEX.match(tag): abort(400, TAG_ERROR) repo_ref = registry_model.lookup_repository(namespace, repository) if repo_ref is None: raise NotFound() if 'expiration' in request.get_json(): tag_ref = registry_model.get_repo_tag(repo_ref, tag) if tag_ref is None: raise NotFound() expiration = request.get_json().get('expiration') expiration_date = None if expiration is not None: try: expiration_date = datetime.utcfromtimestamp( float(expiration)) except ValueError: abort(400) if expiration_date <= datetime.now(): abort(400) existing_end_ts, ok = registry_model.change_repository_tag_expiration( tag_ref, expiration_date) if ok: if not (existing_end_ts is None and expiration_date is None): log_action( 'change_tag_expiration', namespace, { 'username': get_authenticated_user().username, 'repo': repository, 'tag': tag, 'namespace': namespace, 'expiration_date': expiration_date, 'old_expiration_date': existing_end_ts }, repo_name=repository) else: raise InvalidRequest( 'Could not update tag expiration; Tag has probably changed' ) if 'image' in request.get_json( ) or 'manifest_digest' in request.get_json(): existing_tag = registry_model.get_repo_tag( repo_ref, tag, include_legacy_image=True) manifest_or_image = None image_id = None manifest_digest = None if 'image' in request.get_json(): image_id = request.get_json()['image'] manifest_or_image = registry_model.get_legacy_image( repo_ref, image_id) else: manifest_digest = request.get_json()['manifest_digest'] manifest_or_image = registry_model.lookup_manifest_by_digest( repo_ref, manifest_digest, require_available=True) if manifest_or_image is None: raise NotFound() # TODO: Remove this check once fully on V22 existing_manifest_digest = None if existing_tag: existing_manifest = registry_model.get_manifest_for_tag( existing_tag) existing_manifest_digest = existing_manifest.digest if existing_manifest else None if not registry_model.retarget_tag(repo_ref, tag, manifest_or_image, storage, docker_v2_signing_key): raise InvalidRequest('Could not move tag') username = get_authenticated_user().username log_action( 'move_tag' if existing_tag else 'create_tag', namespace, { 'username': username, 'repo': repository, 'tag': tag, 'namespace': namespace, 'image': image_id, 'manifest_digest': manifest_digest, 'original_image': (existing_tag.legacy_image.docker_image_id if existing_tag and existing_tag.legacy_image_if_present else None), 'original_manifest_digest': existing_manifest_digest, }, repo_name=repository) return 'Updated', 201
def wrapped(self, *args, **kwargs): if request.is_json and len(request.get_data()) > max_size: raise InvalidRequest() return func(self, *args, **kwargs)
def post(self, namespace_name, repo_name, trigger_uuid): """ Activate the specified build trigger. """ trigger = get_trigger(trigger_uuid) handler = BuildTriggerHandler.get_handler(trigger) if handler.is_active(): raise InvalidRequest("Trigger config is not sufficient for activation.") user_permission = UserAdminPermission(trigger.connected_user.username) if user_permission.can(): # Update the pull robot (if any). pull_robot_name = request.get_json().get("pull_robot", None) if pull_robot_name: try: pull_robot = model.user.lookup_robot(pull_robot_name) except model.InvalidRobotException: raise NotFound() # Make sure the user has administer permissions for the robot's namespace. (robot_namespace, _) = parse_robot_username(pull_robot_name) if not AdministerOrganizationPermission(robot_namespace).can(): raise Unauthorized() # Make sure the namespace matches that of the trigger. if robot_namespace != namespace_name: raise Unauthorized() # Set the pull robot. trigger.pull_robot = pull_robot # Update the config. new_config_dict = request.get_json()["config"] write_token_name = "Build Trigger: %s" % trigger.service.name write_token = model.token.create_delegate_token( namespace_name, repo_name, write_token_name, "write" ) try: path = url_for("webhooks.build_trigger_webhook", trigger_uuid=trigger.uuid) authed_url = _prepare_webhook_url( app.config["PREFERRED_URL_SCHEME"], "$token", write_token.get_code(), app.config["SERVER_HOSTNAME"], path, ) handler = BuildTriggerHandler.get_handler(trigger, new_config_dict) final_config, private_config = handler.activate(authed_url) if "private_key" in private_config: trigger.secure_private_key = DecryptedValue(private_config["private_key"]) except TriggerException as exc: write_token.delete_instance() raise request_error(message=exc.message) # Save the updated config. update_build_trigger(trigger, final_config, write_token=write_token) # Log the trigger setup. repo = model.repository.get_repository(namespace_name, repo_name) log_action( "setup_repo_trigger", namespace_name, { "repo": repo_name, "namespace": namespace_name, "trigger_id": trigger.uuid, "service": trigger.service.name, "pull_robot": trigger.pull_robot.username if trigger.pull_robot else None, "config": final_config, }, repo=repo, ) return trigger_view(trigger, can_admin=True) else: raise Unauthorized()