Beispiel #1
0
class HTTPXSyncAdapter(SyncRequestAdapter):
    def __init__(self, session=None, **kwargs):
        self.session = session
        from httpx import Client, HTTPError
        if session:
            self.session = session
        else:
            self.session = Client(**kwargs)
        self.error = (HTTPError, InvalidSchemaError)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        pass

    def __del__(self):
        if self.session:
            self.session.close()
Beispiel #2
0
class ConcourseSession:
    def __init__(self, concourse_url: str, token_provider: TokenProvider):
        self.concourse_url = concourse_url
        self._token_provider = token_provider
        self._client = None

    @property
    def is_open(self):
        return self._client is not None

    @property
    def client(self):
        if self._client is None:
            self._client = Client()
            self._update_token(self._client, False)
        return self._client

    def _make_request(self, make_request: Callable[[Client],
                                                   Response]) -> Response:
        client = self.client
        for _ in range(2):
            r = make_request(client)
            if r.status_code == httpx.codes.UNAUTHORIZED:
                logging.warning(
                    f"Got {r.status_code} when trying to access {self.concourse_url}"
                )
                client.headers.update({"Authorization": ""})
                self._update_token(client, True)
            else:
                return r
        raise Exception("Could not connect to concourse server")

    def _build_url(self, *args) -> str:
        parts = [self.concourse_url]
        parts.extend(str(p) for p in args)
        return '/'.join(parts)

    def _build_teams_url(self, *args):
        return self._build_url("api", "v1", "teams", *args)

    def _build_pipeline_url(self, team: str, pipeline: str):
        return self._build_teams_url(team, "pipelines", pipeline, "config")

    def _update_token(self, client: Client, force_new: bool):
        token = self._token_provider.get_token(client, force_new,
                                               self.concourse_url)
        client.headers.update({"Authorization": f"bearer {token}"})

    def close(self):
        if self._client is None:
            return
        try:
            self._client.close()
        except Exception as e:
            logging.error(e)
        finally:
            self._client = None

    def get_pipeline(self, team: str, pipeline: str) -> Tuple[dict, str]:
        r = self._make_request(
            lambda c: c.get(self._build_pipeline_url(team, pipeline)))
        # https://github.com/concourse/concourse/blob/d55be66cc6b10101a8b4ddd9122e437cb10ccf52/go-concourse/concourse/configs.go#L40
        if r.status_code == 404:
            return ({}, "")
        r.raise_for_status()
        pipeline_version = expect(r.headers.get("X-Concourse-Config-Version"),
                                  "BUG: X-Concourse-Config-Version not found")
        pipeline_cfg = r.json()["config"]
        return (pipeline_cfg, pipeline_version)

    def set_pipeline(self, team: str, pipeline: str, config: dict,
                     pipeline_version: str):
        headers = {"X-Concourse-Config-Version": pipeline_version}
        # https://github.com/concourse/concourse/blob/d55be66cc6b10101a8b4ddd9122e437cb10ccf52/go-concourse/concourse/configs.go#L76
        r = self._make_request(
            lambda c: c.put(self._build_pipeline_url(team, pipeline),
                            json=config,
                            headers=headers))
        r.raise_for_status()
Beispiel #3
0
def client():
    client = Client()
    client = CachingClient(client)
    yield client
    client.close()
Beispiel #4
0
class Blocking(Base, BlockingHttp):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self._client = Client(auth=self._basic_auth, timeout=self._timeout)

    def __enter__(self) -> Blocking:
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        self.close()

    def close(self) -> None:
        """Closes sessions
        """

        self._client.close()

    def match(self, match_id: str) -> BlockingMatch:
        """Used to interact with a match.

        Parameters
        ----------
        match_id : str
            Dathost Match ID.

        Returns
        -------
        BlockingMatch
        """

        return BlockingMatch(self, match_id)

    def create_server(
            self,
            settings: ServerSettings) -> Tuple[ServerModel, ServerBlocking]:
        """Creates a new server.

        Parameters
        ----------
        settings: ServerSettings
            Used to configure server.

        Returns
        -------
        ServerModel
            Holds data on server.
        ServerBlocking
            Used to interact with the created server.
        """

        data = cast(
            dict,
            self._post(
                url=SERVER.create,
                read_json=True,
                data=settings.payload,
            ))

        return ServerModel(data), self.server(data["id"])

    def server(self, server_id: str) -> ServerBlocking:
        """Used for interacting with a server.

        Parameters
        ----------
        server_id : str
            Datahost server ID.

        Returns
        -------
        ServerBlocking
            Used to interact with the server.
        """

        return ServerBlocking(self, server_id)

    def servers(
            self) -> Generator[Tuple[ServerModel, ServerBlocking], None, None]:
        """Used to list servers.

        Yields
        -------
        ServerModel
            Holds data on server.
        ServerBlocking
            Used to interact with server.
        """

        for server in cast(dict, self._get(SERVER.list)):
            yield ServerModel(server), self.server(server["id"])

    def account(self) -> AccountModel:
        """Gets account details

        Returns
        -------
        AccountModel
            Holds data on a account.
        """

        return AccountModel(cast(dict, self._get(ACCOUNT.details)))

    def domains(self) -> Generator[str, None, None]:
        """Used to list domains.

        Returns
        -------
        list
            List of domains.
        """

        for domain in cast(list, self._get(CUSTOM_DOMAINS.details)):
            yield domain["name"]
Beispiel #5
0
class Blocking(Base, BlockingHTTP):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self._client = Client(timeout=self._timeout,
                              limits=self._limits,
                              headers={"User-Agent": self._user_agent})

    def close(self) -> None:
        """Closes any underlying TCP sessions.
        """

        self._client.close()

    @authorize_required
    def download_by_name(self,
                         bucket_name: str,
                         file_name: str,
                         settings: DownloadSettings = None) -> bytes:
        """Used to download a file by its name.

        Parameters
        ----------
        bucket_name : str
            Name of bucket.
        file_name : str
            Name of file to download.
        settings : DownloadSettings, optional
            by default None

        Returns
        -------
        bytes

        Notes
        -----
        Use bucket.file.download instead whenever you can.
        """

        if not settings:
            params = None
            headers = None
        else:
            params = settings.parameters
            headers = settings.headers

        return cast(
            bytes,
            self._get(url="{}/{}/{}".format(self._routes.download.file,
                                            bucket_name, file_name),
                      headers=headers,
                      params=params,
                      resp_json=False,
                      include_account=False))

    @authorize_required
    def create_key(self,
                   settings: KeySettings) -> Tuple[KeyModel, BlockingKey]:
        """Used to create a key.

        Parameters
        ----------
        settings: KeySettings
            Used to hold details on a key.

        Returns
        -------
        KeyModel
            Holds details on key.
        BlockingKey
        """

        data = cast(
            dict, self._post(json=settings.payload,
                             url=self._routes.key.create))

        return KeyModel(data), self.key(data["applicationKeyId"])

    @authorize_required
    def keys(
        self,
        limit: int = 100,
        start_key_id: str = None
    ) -> Generator[Tuple[KeyModel, BlockingKey], None, None]:
        """Used to list keys.

        Parameters
        ----------
        limit : int, optional
            Used to limit the listing, by default 100
        start_key_id : str, optional
            Key to start listing from, by default None

        Yields
        -------
        KeyModel
            Holds details on key.
        BlockingKey
        """

        data = cast(
            dict,
            self._post(url=self._routes.key.list,
                       json={
                           "maxKeyCount": limit,
                           "startApplicationKeyId": start_key_id
                       }))

        for key in data["keys"]:
            yield KeyModel(key), self.key(key["applicationKeyId"])

    def key(self, key_id: str) -> BlockingKey:
        """Used to interact with key.

        Parameters
        ----------
        key_id : str
            ID of key.

        Returns
        -------
        BlockingKey
        """

        return BlockingKey(self, key_id)

    @authorize_required
    def create_bucket(
            self,
            settings: BucketSettings) -> Tuple[BucketModel, BlockingBucket]:
        """Used to create a bucket.

        Parameters
        ----------
        settings: BucketSettings
            Holds bucket settings.

        Returns
        -------
        BucketModel
            Holds details on a bucket.
        BlockingBucket
            Used to interact with a bucket.
        """

        data = BucketModel(
            cast(
                dict,
                self._post(
                    url=self._routes.bucket.create,
                    json=settings.payload,
                )))

        return data, self.bucket(data.bucket_id)

    @authorize_required
    def buckets(
        self,
        types: list = ["all"]
    ) -> Generator[Tuple[BucketModel, BlockingBucket], None, None]:
        """Lists buckets.

        Parameters
        ----------
        types : list, optional
            Used to filter bucket types, by default ["all"]

        Yields
        -------
        BucketModel
            Holds details on bucket.
        BlockingBucket
            Used for interacting with bucket.
        """

        data = cast(
            dict,
            self._post(url=self._routes.bucket.list,
                       json={"bucketTypes": types}))

        for bucket in data["buckets"]:
            yield BucketModel(bucket), self.bucket(bucket["bucketId"])

    def bucket(self, bucket_id: str) -> BlockingBucket:
        """Used to interact with a bucket.

        Parameters
        ----------
        bucket_id : str
            ID of Bucket.

        Returns
        -------
        BlockingBucket
        """

        return BlockingBucket(self, bucket_id)

    def __authorize_background(self) -> None:
        """Used to refresh auth tokens every 23.5 hours.
        """

        self._running_task = True
        time.sleep(self._refresh_seconds + randint(0, 1500))
        self._running_task = False

        self.authorize()
        self._check_cache()

    def authorize(self) -> AuthModel:
        """Used to authorize B2 account.

        Returns
        -------
        AuthModel
            Holds data on account auth.
        """

        resp = self._client.get(self._auth_url, auth=self._auth)
        resp.raise_for_status()

        data = AuthModel(resp.json())

        self.account_id = data.account_id

        self._format_routes(data.api_url, data.download_url)

        self._client.headers["Authorization"] = data.authorization_token

        if not self._running_task:
            threading.Thread(target=self.__authorize_background)

        return data
class ZebrunnerAPI(metaclass=Singleton):
    authenticated = False

    def __init__(self, service_url: str = None, access_token: str = None):
        if service_url and access_token:
            self.service_url = service_url.rstrip("/")
            self.access_token = access_token
            self._client = Client()
            self._auth_token = None
            self.authenticated = False

    def _sign_request(self, request: Request) -> Request:
        request.headers["Authorization"] = f"Bearer {self._auth_token}"
        return request

    def auth(self) -> None:
        if not self.access_token or not self.service_url:
            return

        url = self.service_url + "/api/iam/v1/auth/refresh"
        try:
            response = self._client.post(
                url, json={"refreshToken": self.access_token})
        except httpx.RequestError as e:
            logger.warning("Error while sending request to zebrunner.",
                           exc_info=e)
            return

        if response.status_code != 200:
            log_response(response, logging.ERROR)
            return

        self._auth_token = response.json()["authToken"]
        self._client.auth = self._sign_request
        self.authenticated = True

    def start_test_run(self, project_key: str,
                       body: StartTestRunModel) -> Optional[int]:
        url = self.service_url + "/api/reporting/v1/test-runs"

        try:
            response = self._client.post(url,
                                         params={"projectKey": project_key},
                                         json=body.dict(exclude_none=True,
                                                        by_alias=True))
        except httpx.RequestError as e:
            logger.warning("Error while sending request to zebrunner.",
                           exc_info=e)
            return None

        if response.status_code != 200:
            log_response(response, logging.ERROR)
            return None

        return response.json()["id"]

    def start_test(self, test_run_id: int,
                   body: StartTestModel) -> Optional[int]:
        url = self.service_url + f"/api/reporting/v1/test-runs/{test_run_id}/tests"

        try:
            response = self._client.post(url,
                                         json=body.dict(exclude_none=True,
                                                        by_alias=True))
        except httpx.RequestError as e:
            logger.warning("Error while sending request to zebrunner.",
                           exc_info=e)
            return None

        if response.status_code != 200:
            log_response(response, logging.ERROR)
            return None

        return response.json()["id"]

    def finish_test(self, test_run_id: int, test_id: int,
                    body: FinishTestModel) -> None:
        url = self.service_url + f"/api/reporting/v1/test-runs/{test_run_id}/tests/{test_id}"

        try:
            response = self._client.put(url,
                                        json=body.dict(exclude_none=True,
                                                       by_alias=True))
        except httpx.RequestError as e:
            logger.warning("Error while sending request to zebrunner.",
                           exc_info=e)
            return

        if response.status_code != 200:
            log_response(response, logging.ERROR)

    def finish_test_run(self, test_run_id: int) -> None:
        url = self.service_url + f"/api/reporting/v1/test-runs/{test_run_id}"
        try:
            response = self._client.put(
                url,
                json={
                    "endedAt":
                    (datetime.utcnow().replace(tzinfo=timezone.utc) -
                     timedelta(seconds=1)).isoformat()
                },
            )
        except httpx.RequestError as e:
            logger.warning("Error while sending request to zebrunner.",
                           exc_info=e)
            return

        if response.status_code != 200:
            log_response(response, logging.ERROR)
            return

    def send_logs(self, test_run_id: int, logs: List[LogRecordModel]) -> None:
        url = self.service_url + f"/api/reporting/v1/test-runs/{test_run_id}/logs"

        body = [x.dict(exclude_none=True, by_alias=True) for x in logs]
        self._client.post(url, json=body)

    def send_screenshot(self, test_run_id: int, test_id: int,
                        image_path: Union[str, Path]) -> None:
        url = self.service_url + f"/api/reporting/v1/test-runs/{test_run_id}/tests/{test_id}/screenshots"
        with open(image_path, "rb") as image:
            self._client.post(
                url,
                content=image.read(),
                headers={
                    "Content-Type": "image/png",
                    "x-zbr-screenshot-captured-at": round(time.time() * 1000)
                },
            )

    def send_artifact(self,
                      filename: Union[str, Path],
                      test_run_id: int,
                      test_id: Optional[int] = None) -> None:
        if test_id:
            url = f"{self.service_url}/api/reporting/v1/test-runs/{test_run_id}/tests/{test_id}/artifacts"
        else:
            url = f"{self.service_url}/api/reporting/v1/test-runs/{test_run_id}/artifacts"
        with open(filename, "rb") as file:
            self._client.post(url, files={"file": file})

    def send_artifact_references(self,
                                 references: List[ArtifactReferenceModel],
                                 test_run_id: int,
                                 test_id: Optional[int] = None) -> None:
        if test_id:
            url = f"{self.service_url}/api/reporting/v1/test-runs/{test_run_id}/tests/{test_id}/artifact-references"
        else:
            url = f"{self.service_url}/api/reporting/v1/test-runs/{test_run_id}/artifact-references/"
        json_items = [
            item.dict(exclude_none=True, by_alias=True) for item in references
        ]
        self._client.put(url, json={"items": json_items})

    def send_labels(self,
                    labels: List[LabelModel],
                    test_run_id: int,
                    test_id: Optional[int] = None) -> None:
        if test_id:
            url = f"{self.service_url}/api/reporting/v1/test-runs/{test_run_id}/tests/{test_id}/labels"
        else:
            url = f"{self.service_url}/api/reporting/v1/test-runs/{test_run_id}/labels"
        labels_json = [
            label.dict(exclude_none=True, by_alias=True) for label in labels
        ]
        self._client.put(url, json={"items": labels_json})

    def start_test_session(self, test_run_id: int,
                           body: StartTestSessionModel) -> Optional[str]:
        url = self.service_url + f"/api/reporting/v1/test-runs/{test_run_id}/test-sessions"
        response = self._client.post(url,
                                     json=body.dict(exclude_none=True,
                                                    by_alias=True))
        if not response.status_code == 200:
            log_response(response, logging.ERROR)
            return None

        return response.json().get("id")

    def finish_test_session(self, test_run_id: int, zebrunner_id: str,
                            body: FinishTestSessionModel) -> None:
        url = self.service_url + f"/api/reporting/v1/test-runs/{test_run_id}/test-sessions/{zebrunner_id}"
        self._client.put(url, json=body.dict(exclude_none=True, by_alias=True))

    def get_rerun_tests(self, run_context: str) -> RerunDataModel:
        url = self.service_url + "/api/reporting/v1/run-context-exchanges"
        run_context_dict = json.loads(run_context)
        response = self._client.post(url, json=run_context_dict)
        response_data = response.json()
        for test in response_data["tests"]:
            correlation_data = test["correlationData"]
            if correlation_data is not None:
                test["correlationData"] = json.loads(correlation_data)
        return RerunDataModel(**response_data)

    def close(self) -> None:
        self._client.close()