Esempio n. 1
0
    def last_operation(self, instance_id: str, operation_data: Optional[str],
                       **kwargs) -> LastOperation:
        """
        Further readings `CF Broker API#LastOperation
        <https://docs.cloudfoundry.org/services/api.html#polling>`_

        :param instance_id: Instance id provided by the platform
        :param operation_data: Operation data received from async operation
        :param kwargs: May contain additional information, improves
                       compatibility with upstream versions
        :rtype: LastOperation
        """

        instance = ServiceInstance.query.get(instance_id)

        if not instance:
            raise errors.ErrInstanceDoesNotExist

        if not operation_data:
            raise errors.ErrBadRequest(msg="Missing operation ID")

        operation = instance.operations.filter_by(
            id=int(operation_data)).first()

        if not operation:
            raise errors.ErrBadRequest(
                msg=
                f"Invalid operation id {operation_data} for service {instance_id}"
            )

        return LastOperation(
            state=Operation.States(operation.state),
            description=operation.step_description,
        )
 def validate(self):
     for header in self.header_list:
         if not header:
             raise errors.ErrBadRequest("Headers cannot be empty")
         header_chars = set(header)
         if not header_chars.issubset(HeaderList.ALLOWED_CHARACTERS):
             invalid_characters = header_chars.difference(
                 HeaderList.ALLOWED_CHARACTERS
             )
             raise errors.ErrBadRequest(
                 f"{header} contains these invalid characters: {invalid_characters}"
             )
Esempio n. 3
0
def provision_cdn_instance(instance_id: str, domain_names: list, params: dict):
    instance = CDNServiceInstance(id=instance_id, domain_names=domain_names)
    queue = queue_all_cdn_provision_tasks_for_operation
    instance.cloudfront_origin_hostname = params.get(
        "origin", config.DEFAULT_CLOUDFRONT_ORIGIN)
    validators.Hostname(instance.cloudfront_origin_hostname).validate()
    instance.cloudfront_origin_path = params.get("path", "")
    instance.route53_alias_hosted_zone = config.CLOUDFRONT_HOSTED_ZONE_ID
    forward_cookie_policy, forwarded_cookies = parse_cookie_options(params)
    instance.forward_cookie_policy = forward_cookie_policy
    instance.forwarded_cookies = forwarded_cookies
    forwarded_headers = parse_header_options(params)
    if instance.cloudfront_origin_hostname == config.DEFAULT_CLOUDFRONT_ORIGIN:
        forwarded_headers.append("HOST")
    forwarded_headers = normalize_header_list(forwarded_headers)

    instance.forwarded_headers = forwarded_headers
    instance.error_responses = params.get("error_responses", {})
    validators.ErrorResponseConfig(instance.error_responses).validate()
    if params.get("insecure_origin", False):
        if params.get("origin") is None:
            raise errors.ErrBadRequest(
                "'insecure_origin' cannot be set when using the default origin."
            )
        instance.origin_protocol_policy = CDNServiceInstance.ProtocolPolicy.HTTP.value
    else:
        instance.origin_protocol_policy = CDNServiceInstance.ProtocolPolicy.HTTPS.value
    return instance
 def validate(self):
     if not isinstance(self.input, dict):
         raise errors.ErrBadRequest(
             "error_response should be a dictionary of error code: response path"
         )
     for key, value in self.input.items():
         if key not in ErrorResponseConfig.VALID_ERROR_CODES:
             raise errors.ErrBadRequest("error_response keys must be strings")
         if not isinstance(value, str):
             raise errors.ErrBadRequest("error_response values must be strings")
         if not value:
             raise errors.ErrBadRequest("error_response values must not be empty")
         if not value[0] == "/":
             raise errors.ErrBadRequest(
                 "error_response path must be a path starting with `/`"
             )
Esempio n. 5
0
    def test_update_wraps_bad_request(self):
        self.broker.update.side_effect = errors.ErrBadRequest('BadRequest')

        response = self.client.patch(
            "/v2/service_instances/here-service-instance-id?accepts_incomplete=true",
            data=json.dumps({
                "service_id": "service-guid-here",
                "plan_id": "plan-guid-here",
                "parameters": {
                    "parameter1": 1
                },
                "previous_values": {
                    "plan_id": "old-plan-guid-here",
                    "service_id": "service-guid-here",
                    "organization_id": "org-guid-here",
                    "space_id": "space-guid-here"
                }
            }),
            headers={
                'X-Broker-Api-Version': '2.13',
                'Content-Type': 'application/json',
                'Authorization': self.auth_header
            })

        self.assert400(response)
Esempio n. 6
0
    def provision(self, instance_id: str, details: ProvisionDetails,
                  async_allowed: bool, **kwargs) -> ProvisionedServiceSpec:
        self.logger.info("starting provision request")
        if not async_allowed:
            raise errors.ErrAsyncRequired()

        params = details.parameters or {}

        domain_names = parse_domain_options(params)
        if not domain_names:
            raise errors.ErrBadRequest("'domains' parameter required.")

        self.logger.info("validating CNAMEs")
        validators.CNAME(domain_names).validate()
        self.logger.info("validating unique domains")
        if not config.IGNORE_DUPLICATE_DOMAINS:
            validators.UniqueDomains(domain_names).validate()

        if details.plan_id == CDN_PLAN_ID:
            instance = provision_cdn_instance(instance_id, domain_names,
                                              params)
            queue = queue_all_cdn_provision_tasks_for_operation
        elif details.plan_id == ALB_PLAN_ID:
            instance = ALBServiceInstance(id=instance_id,
                                          domain_names=domain_names)
            queue = queue_all_alb_provision_tasks_for_operation
        elif details.plan_id == MIGRATION_PLAN_ID:
            instance = MigrationServiceInstance(id=instance_id,
                                                domain_names=domain_names)
            db.session.add(instance)
            db.session.commit()
            return ProvisionedServiceSpec(
                state=ProvisionState.SUCCESSFUL_CREATED)
        else:
            raise NotImplementedError()

        self.logger.info("setting origin hostname")
        self.logger.info("creating operation")

        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.PROVISION.value,
            step_description="Queuing tasks",
        )

        db.session.add(instance)
        db.session.add(operation)
        self.logger.info("committing db session")
        db.session.commit()
        self.logger.info("queueing tasks")
        queue(operation.id, cf_logging.FRAMEWORK.context.get_correlation_id())
        self.logger.info("all done. Returning provisioned service spec")

        return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC,
                                      operation=str(operation.id))
Esempio n. 7
0
    def test_deprovisioning_is_called_with_the_right_values(self):
        self.broker.deprovision.side_effect = errors.ErrBadRequest('BadRequest')

        response = self.client.delete(
            "/v2/service_instances/here_instance_id?service_id=service-guid-here&plan_id=plan-guid-here&accepts_incomplete=true",
            headers={
                'X-Broker-Api-Version': '2.13',
                'Authorization': self.auth_header
            })

        self.assert400(response)
    def validate(self):
        instructions = self._instructions(self.domains)

        if instructions:
            msg = [
                "An external domain service already exists for the following domains:"
            ]

            for error in instructions:
                msg.append("  " + error)

            raise errors.ErrBadRequest("\n".join(msg))
Esempio n. 9
0
    def test_unbind_is_called_with_the_right_values(self):
        self.broker.unbind.side_effect = errors.ErrBadRequest('BadRequest')

        query = "service_id=service-guid-here&plan_id=plan-guid-here"
        response = self.client.delete(
            "/v2/service_instances/here_instance_id/service_bindings/here_binding_id?%s" % query,
            headers={
                'X-Broker-Api-Version': '2.13',
                'Authorization': self.auth_header
            })

        self.assert400(response)
    def validate(self):
        instructions = self._instructions(self.domains)

        if instructions:
            msg = [
                "We could not find correct CNAME records for one or more of your domains.",
                "Please ensure the following DNS records are in place and try to provision",
                "this service again:",
            ]

            for error in instructions:
                msg.append("  " + error)

            raise errors.ErrBadRequest("\n".join(msg))
Esempio n. 11
0
 def deprovision(self, instance_id: str, details: DeprovisionDetails,
                 async_allowed: bool, **kwargs) -> DeprovisionServiceSpec:
     instance = self.service_instances.get(instance_id)
     if instance is None:
         raise errors.ErrInstanceDoesNotExist()
     if instance.get('state') == self.CREATED:
         print(self.service_instances[instance_id])
         context_instance = self.service_instances[instance_id]
         project_name = context_instance.get('project_name')
         api_url = context_instance.get(
             'repo_hooks_url')  #api endpoint to delete hook
         hook_id = context_instance.get('repo_hook_id')  #hook id
         jenkins_utils.deprovision_job(project_name, api_url, hook_id)
         del self.service_instances[instance_id]
         return DeprovisionServiceSpec(False)
     elif instance.get('state') in [self.BOUND, self.BINDING]:
         raise errors.ErrBadRequest("Instance Binded,Can't deprovision")
Esempio n. 12
0
    def provision(self, instance_id: str, details: ProvisionDetails,
                  async_allowed: bool, **kwargs) -> ProvisionedServiceSpec:
        self.logger.info("starting provision request")
        if not async_allowed:
            raise errors.ErrAsyncRequired()

        params = details.parameters or {}

        if params.get("domains"):
            domain_names = [
                d.strip().lower() for d in params["domains"].split(",")
            ]
        else:
            raise errors.ErrBadRequest("'domains' parameter required.")

        self.logger.info("validating CNAMEs")
        validators.CNAME(domain_names).validate()
        self.logger.info("validating unique domains")
        validators.UniqueDomains(domain_names).validate()

        instance = ServiceInstance(id=instance_id, domain_names=domain_names)

        self.logger.info("setting origin hostname")
        instance.cloudfront_origin_hostname = params.get(
            "origin", config.DEFAULT_CLOUDFRONT_ORIGIN)
        instance.cloudfront_origin_path = params.get("path", "")
        self.logger.info("creating operation")

        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.PROVISION.value,
        )

        db.session.add(instance)
        db.session.add(operation)
        self.logger.info("committing db session")
        db.session.commit()
        self.logger.info("queueing tasks")
        queue_all_provision_tasks_for_operation(
            operation.id, cf_logging.FRAMEWORK.context.get_correlation_id())
        self.logger.info("all done. Returning provisioned service spec")

        return ProvisionedServiceSpec(state=ProvisionState.IS_ASYNC,
                                      operation=str(operation.id))
Esempio n. 13
0
    def deprovision(
        self,
        instance_id: str,
        details: DeprovisionDetails,
        async_allowed: bool,
        **kwargs,
    ) -> DeprovisionServiceSpec:
        if not async_allowed:
            raise errors.ErrAsyncRequired()
        instance = ServiceInstance.query.get(instance_id)

        if not instance:
            raise errors.ErrInstanceDoesNotExist
        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.DEPROVISION.value,
            step_description="Queuing tasks",
        )

        db.session.add(operation)
        db.session.commit()
        if details.plan_id == CDN_PLAN_ID:
            queue_all_cdn_deprovision_tasks_for_operation(
                operation.id,
                cf_logging.FRAMEWORK.context.get_correlation_id())
        elif details.plan_id == ALB_PLAN_ID:
            queue_all_alb_deprovision_tasks_for_operation(
                operation.id,
                cf_logging.FRAMEWORK.context.get_correlation_id())
        elif details.plan_id == MIGRATION_PLAN_ID:
            for o in instance.operations:
                if o.action == Operation.Actions.UPDATE.value:
                    raise errors.ErrBadRequest(
                        msg="Can't delete migration with update operations")
            queue_all_migration_deprovision_tasks_for_operation(
                operation.id,
                cf_logging.FRAMEWORK.context.get_correlation_id())
        else:
            raise NotImplementedError()

        return DeprovisionServiceSpec(is_async=True,
                                      operation=str(operation.id))
Esempio n. 14
0
    def test_bind_called_with_the_right_values(self):
        self.broker.bind.side_effect = errors.ErrBadRequest('BadRequest')

        response = self.client.put(
            "/v2/service_instances/here-instance_id/service_bindings/here-binding_id",
            data=json.dumps({
                "service_id": "service-guid-here",
                "plan_id": "plan-guid-here",
                "bind_resource": {
                    "app_guid": "app-guid-here",
                    "route": "route-here"
                },
                "parameters": {
                    "parameter1": 1
                }
            }),
            headers={
                'X-Broker-Api-Version': '2.13',
                'Content-Type': 'application/json',
                'Authorization': self.auth_header
            })

        self.assert400(response)
Esempio n. 15
0
    def update(self, instance_id: str, details: UpdateDetails,
               async_allowed: bool, **kwargs) -> UpdateServiceSpec:
        if not async_allowed:
            raise errors.ErrAsyncRequired()

        params = details.parameters or {}

        instance = ServiceInstance.query.get(instance_id)

        if not instance:
            raise errors.ErrBadRequest("Service instance does not exist")

        if instance.deactivated_at:
            raise errors.ErrBadRequest(
                "Cannot update instance because it was already canceled")

        if instance.has_active_operations():
            raise errors.ErrBadRequest(
                "Instance has an active operation in progress")

        domain_names = parse_domain_options(params)
        noop = True
        if domain_names is not None:
            self.logger.info("validating CNAMEs")
            validators.CNAME(domain_names).validate()

            self.logger.info("validating unique domains")
            validators.UniqueDomains(domain_names).validate(instance)
            noop = noop and (sorted(domain_names) == sorted(
                instance.domain_names))
            if instance.instance_type == "cdn_service_instance" and noop:
                instance.new_certificate = instance.current_certificate
            instance.domain_names = domain_names

        if instance.instance_type == "cdn_service_instance":
            # N.B. we're using "param" in params rather than
            # params.get("param") because the OSBAPI spec
            # requires we do not mess with params that were not
            # specified, so unset and set to None have different meanings
            noop = False

            if details.plan_id != CDN_PLAN_ID:
                raise ClientError("Updating service plan is not supported")

            if "origin" in params:
                if params["origin"]:
                    origin_hostname = params["origin"]
                    validators.Hostname(origin_hostname).validate()
                else:
                    origin_hostname = config.DEFAULT_CLOUDFRONT_ORIGIN
                instance.cloudfront_origin_hostname = origin_hostname

            if "path" in params:
                if params["path"]:
                    cloudfront_origin_path = params["path"]
                else:
                    cloudfront_origin_path = ""
                instance.cloudfront_origin_path = cloudfront_origin_path
            if "forward_cookies" in params:
                forward_cookie_policy, forwarded_cookies = parse_cookie_options(
                    params)
                instance.forward_cookie_policy = forward_cookie_policy
                instance.forwarded_cookies = forwarded_cookies

            if "forward_headers" in params:
                forwarded_headers = parse_header_options(params)
            else:
                # .copy() so sqlalchemy recognizes the field has changed
                forwarded_headers = instance.forwarded_headers.copy()
            if instance.cloudfront_origin_hostname == config.DEFAULT_CLOUDFRONT_ORIGIN:
                forwarded_headers.append("HOST")
            forwarded_headers = normalize_header_list(forwarded_headers)
            instance.forwarded_headers = forwarded_headers
            if "insecure_origin" in params:
                origin_protocol_policy = "https-only"
                if params["insecure_origin"]:
                    if (instance.cloudfront_origin_hostname ==
                            config.DEFAULT_CLOUDFRONT_ORIGIN):
                        raise errors.ErrBadRequest(
                            "Cannot use insecure_origin with default origin")
                    origin_protocol_policy = "http-only"
                instance.origin_protocol_policy = origin_protocol_policy
            if "error_responses" in params:
                instance.error_responses = params["error_responses"]
                validators.ErrorResponseConfig(
                    instance.error_responses).validate()

            queue = queue_all_cdn_update_tasks_for_operation
        elif instance.instance_type == "alb_service_instance":
            if details.plan_id != ALB_PLAN_ID:
                raise ClientError("Updating service plan is not supported")
            queue = queue_all_alb_update_tasks_for_operation
        elif instance.instance_type == "migration_service_instance":
            if details.plan_id == CDN_PLAN_ID:
                noop = False
                validate_migration_to_cdn_params(params)
                instance = change_instance_type(instance, CDNServiceInstance,
                                                db.session)
                update_cdn_params_for_migration(instance, params)
                db.session.add(instance.current_certificate)
                queue = queue_all_cdn_broker_migration_tasks_for_operation
            elif details.plan_id == ALB_PLAN_ID:
                noop = False
                validate_migration_to_alb_params(params)
                instance = change_instance_type(instance, ALBServiceInstance,
                                                db.session)
                update_alb_params_for_migration(instance, params)
                db.session.add(instance.current_certificate)
                queue = queue_all_domain_broker_migration_tasks_for_operation
            else:
                raise ClientError(
                    "Updating to this service plan is not supported")
        if noop:
            return UpdateServiceSpec(False)

        operation = Operation(
            state=Operation.States.IN_PROGRESS.value,
            service_instance=instance,
            action=Operation.Actions.UPDATE.value,
            step_description="Queuing tasks",
        )
        db.session.add(operation)
        db.session.add(instance)
        db.session.commit()

        queue(operation.id, cf_logging.FRAMEWORK.context.get_correlation_id())

        return UpdateServiceSpec(True, operation=str(operation.id))
    def validate(self):

        if not re.fullmatch(Hostname.domain_name, self.origin):
            raise errors.ErrBadRequest(f"{self.origin} is not a valid hostname")
        if len(self.origin) > 253:
            raise errors.ErrBadRequest(f"{self.origin} is not a valid hostname")