def get(self, namespace_name, repository_name, uuid): """ Get information for the specified notification. """ found = model.get_repo_notification(uuid) if not found: raise NotFound() return found.to_dict()
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 post(self, namespace_name, repository_name, manifestref): """ Adds a new label into the tag manifest. """ label_data = request.get_json() # Check for any reserved prefixes. if label_validator.has_reserved_prefix(label_data["key"]): abort(400, message="Label has a reserved prefix") repo_ref = registry_model.lookup_repository(namespace_name, repository_name) if repo_ref is None: raise NotFound() manifest = registry_model.lookup_manifest_by_digest(repo_ref, manifestref) if manifest is None: raise NotFound() label = None try: label = registry_model.create_manifest_label( manifest, label_data["key"], label_data["value"], "api", label_data["media_type"] ) except InvalidLabelKeyException: message = ( "Label is of an invalid format or missing please " + "use %s format for labels" % VALID_LABEL_KEY_REGEX ) abort(400, message=message) except InvalidMediaTypeException: message = ( "Media type is invalid please use a valid media type: text/plain, application/json" ) abort(400, message=message) if label is None: raise NotFound() metadata = { "id": label.uuid, "key": label.key, "value": label.value, "manifest_digest": manifestref, "media_type": label.media_type_name, "namespace": namespace_name, "repo": repository_name, } log_action("manifest_label_add", namespace_name, metadata, repo_name=repository_name) resp = {"label": _label_dict(label)} repo_string = "%s/%s" % (namespace_name, repository_name) headers = { "Location": api.url_for( ManageRepositoryManifestLabel, repository=repo_string, manifestref=manifestref, labelid=label.uuid, ), } return resp, 201, headers
def put(self, namespace_name, repository_name): """ Allow users to modifying the repository's mirroring configuration. """ values = request.get_json() repo = model.repository.get_repository(namespace_name, repository_name) if not repo: raise NotFound() mirror = model.repo_mirror.get_mirror(repo) if not mirror: raise NotFound() if 'is_enabled' in values: if values['is_enabled'] == True: if model.repo_mirror.enable_mirror(repo): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='is_enabled', to=True) if values['is_enabled'] == False: if model.repo_mirror.disable_mirror(repo): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='is_enabled', to=False) if 'external_reference' in values: if values['external_reference'] == '': return { 'detail': 'Empty string is an invalid repository location.' }, 400 if model.repo_mirror.change_remote(repo, values['external_reference']): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='external_reference', to=values['external_reference']) if 'robot_username' in values: robot_username = values['robot_username'] robot = self._setup_robot_for_mirroring(namespace_name, repository_name, robot_username) if model.repo_mirror.set_mirroring_robot(repo, robot): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='robot_username', to=robot_username) if 'sync_start_date' in values: try: sync_start_date = self._string_to_dt(values['sync_start_date']) except ValueError as e: return { 'detail': 'Incorrect DateTime format for sync_start_date.' }, 400 if model.repo_mirror.change_sync_start_date(repo, sync_start_date): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='sync_start_date', to=sync_start_date) if 'sync_interval' in values: if model.repo_mirror.change_sync_interval(repo, values['sync_interval']): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='sync_interval', to=values['sync_interval']) if 'external_registry_username' in values and 'external_registry_password' in values: username = values['external_registry_username'] password = values['external_registry_password'] if username is None and password is not None: return { 'detail': 'Unable to delete username while setting a password.' }, 400 if model.repo_mirror.change_credentials(repo, username, password): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='external_registry_username', to=username) if password is None: track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='external_registry_password', to=None) else: track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='external_registry_password', to="********") elif 'external_registry_username' in values: username = values['external_registry_username'] if model.repo_mirror.change_username(repo, username): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='external_registry_username', to=username) # Do not allow specifying a password without setting a username if 'external_registry_password' in values and 'external_registry_username' not in values: return { 'detail': 'Unable to set a new password without also specifying a username.' }, 400 if 'external_registry_config' in values: external_registry_config = values.get('external_registry_config', {}) if 'verify_tls' in external_registry_config: updates = { 'verify_tls': external_registry_config['verify_tls'] } if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='verify_tls', to=external_registry_config['verify_tls']) if 'proxy' in external_registry_config: proxy_values = external_registry_config.get('proxy', {}) if 'http_proxy' in proxy_values: updates = { 'proxy': { 'http_proxy': proxy_values['http_proxy'] } } if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='http_proxy', to=proxy_values['http_proxy']) if 'https_proxy' in proxy_values: updates = { 'proxy': { 'https_proxy': proxy_values['https_proxy'] } } if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='https_proxy', to=proxy_values['https_proxy']) if 'no_proxy' in proxy_values: updates = {'proxy': {'no_proxy': proxy_values['no_proxy']}} if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log('repo_mirror_config_changed', wrap_repository(repo), changed='no_proxy', to=proxy_values['no_proxy']) return '', 201
def put(self, namespace_name, repository_name): """ Allow users to modifying the repository's mirroring configuration. """ values = request.get_json() repo = model.repository.get_repository(namespace_name, repository_name) if not repo: raise NotFound() mirror = model.repo_mirror.get_mirror(repo) if not mirror: raise NotFound() if "is_enabled" in values: if values["is_enabled"] == True: if model.repo_mirror.enable_mirror(repo): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="is_enabled", to=True, ) if values["is_enabled"] == False: if model.repo_mirror.disable_mirror(repo): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="is_enabled", to=False, ) if "external_reference" in values: if values["external_reference"] == "": return { "detail": "Empty string is an invalid repository location." }, 400 if model.repo_mirror.change_remote(repo, values["external_reference"]): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="external_reference", to=values["external_reference"], ) if "robot_username" in values: robot_username = values["robot_username"] robot = self._setup_robot_for_mirroring(namespace_name, repository_name, robot_username) if model.repo_mirror.set_mirroring_robot(repo, robot): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="robot_username", to=robot_username, ) if "sync_start_date" in values: try: sync_start_date = self._string_to_dt(values["sync_start_date"]) except ValueError as e: return { "detail": "Incorrect DateTime format for sync_start_date." }, 400 if model.repo_mirror.change_sync_start_date(repo, sync_start_date): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="sync_start_date", to=sync_start_date, ) if "sync_interval" in values: if model.repo_mirror.change_sync_interval(repo, values["sync_interval"]): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="sync_interval", to=values["sync_interval"], ) if "external_registry_username" in values and "external_registry_password" in values: username = values["external_registry_username"] password = values["external_registry_password"] if username is None and password is not None: return { "detail": "Unable to delete username while setting a password." }, 400 if model.repo_mirror.change_credentials(repo, username, password): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="external_registry_username", to=username, ) if password is None: track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="external_registry_password", to=None, ) else: track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="external_registry_password", to="********", ) elif "external_registry_username" in values: username = values["external_registry_username"] if model.repo_mirror.change_username(repo, username): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="external_registry_username", to=username, ) # Do not allow specifying a password without setting a username if "external_registry_password" in values and "external_registry_username" not in values: return ( { "detail": "Unable to set a new password without also specifying a username." }, 400, ) if "external_registry_config" in values: external_registry_config = values.get("external_registry_config", {}) if "verify_tls" in external_registry_config: updates = { "verify_tls": external_registry_config["verify_tls"] } if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="verify_tls", to=external_registry_config["verify_tls"], ) if "proxy" in external_registry_config: proxy_values = external_registry_config.get("proxy", {}) if "http_proxy" in proxy_values: updates = { "proxy": { "http_proxy": proxy_values["http_proxy"] } } if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="http_proxy", to=proxy_values["http_proxy"], ) if "https_proxy" in proxy_values: updates = { "proxy": { "https_proxy": proxy_values["https_proxy"] } } if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="https_proxy", to=proxy_values["https_proxy"], ) if "no_proxy" in proxy_values: updates = {"proxy": {"no_proxy": proxy_values["no_proxy"]}} if model.repo_mirror.change_external_registry_config( repo, updates): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="no_proxy", to=proxy_values["no_proxy"], ) if "root_rule" in values: if values["root_rule"]["rule_kind"] != "tag_glob_csv": raise ValidationError( 'validation failed: rule_kind must be "tag_glob_csv"') if model.repo_mirror.change_rule( repo, RepoMirrorRuleType.TAG_GLOB_CSV, values["root_rule"]["rule_value"]): track_and_log( "repo_mirror_config_changed", wrap_repository(repo), changed="mirror_rule", to=values["root_rule"]["rule_value"], ) return "", 201
def get(self, uuid): note = model.notification.lookup_notification(get_authenticated_user(), uuid) if not note: raise NotFound() return notification_view(note)
def get_quota(namespace_name, quota_id): quota = model.namespacequota.get_namespace_quota(namespace_name, quota_id) if quota is None: raise NotFound() return quota
def subscribe(user, plan, token, require_business_plan): if not features.BILLING: return plan_found = None for plan_obj in PLANS: if plan_obj["stripeId"] == plan: plan_found = plan_obj if not plan_found or plan_found["deprecated"]: logger.warning("Plan not found or deprecated: %s", plan) raise NotFound() if require_business_plan and not plan_found[ "bus_features"] and not plan_found["price"] == 0: logger.warning("Business attempting to subscribe to personal plan: %s", user.username) raise request_error(message="No matching plan found") private_repos = model.get_private_repo_count(user.username) # This is the default response response_json = { "plan": plan, "usedPrivateRepos": private_repos, } status_code = 200 if not user.stripe_id: # Check if a non-paying user is trying to subscribe to a free plan if not plan_found["price"] == 0: # They want a real paying plan, create the customer and plan # simultaneously card = token try: cus = billing.Customer.create(email=user.email, plan=plan, card=card) user.stripe_id = cus.id user.save() check_repository_usage(user, plan_found) log_action("account_change_plan", user.username, {"plan": plan}) except stripe.error.CardError as e: return carderror_response(e) except stripe.error.APIConnectionError as e: return connection_response(e) response_json = subscription_view(cus.subscription, private_repos) status_code = 201 else: # Change the plan try: cus = billing.Customer.retrieve(user.stripe_id) except stripe.error.APIConnectionError as e: return connection_response(e) if plan_found["price"] == 0: if cus.subscription is not None: # We only have to cancel the subscription if they actually have one try: cus.subscription.delete() except stripe.error.APIConnectionError as e: return connection_response(e) check_repository_usage(user, plan_found) log_action("account_change_plan", user.username, {"plan": plan}) else: # User may have been a previous customer who is resubscribing if token: cus.card = token cus.plan = plan try: cus.save() except stripe.error.CardError as e: return carderror_response(e) except stripe.error.APIConnectionError as e: return connection_response(e) response_json = subscription_view(cus.subscription, private_repos) check_repository_usage(user, plan_found) log_action("account_change_plan", user.username, {"plan": plan}) return response_json, status_code
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 get(self, error_type): """ Get a detailed description of the error """ if error_type in ERROR_DESCRIPTION.keys(): return error_view(error_type) raise NotFound()
def get_trigger(trigger_uuid): try: trigger = model.build.get_build_trigger(trigger_uuid) except model.InvalidBuildTriggerException: raise NotFound() return trigger
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=str(exc)) # 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 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).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