Beispiel #1
0
 def create(self, python_version, virtualenv_path, project_path, nuke):
     print(snakesay("Creating web app via API"))
     if nuke:
         webapp_url = get_api_endpoint().format(
             username=getpass.getuser(),
             flavor="webapps") + self.domain + "/"
         call_api(webapp_url, "delete")
     post_url = get_api_endpoint().format(username=getpass.getuser(),
                                          flavor="webapps")
     patch_url = post_url + self.domain + "/"
     response = call_api(post_url,
                         "post",
                         data={
                             "domain_name": self.domain,
                             "python_version":
                             PYTHON_VERSIONS[python_version]
                         })
     if not response.ok or response.json().get("status") == "ERROR":
         raise Exception(
             "POST to create webapp via API failed, got {response}:{response_text}"
             .format(response=response, response_text=response.text))
     response = call_api(patch_url,
                         "patch",
                         data={
                             "virtualenv_path": virtualenv_path,
                             "source_directory": project_path
                         })
     if not response.ok:
         raise Exception(
             "PATCH to set virtualenv path and source directory via API failed,"
             "got {response}:{response_text}".format(
                 response=response, response_text=response.text))
Beispiel #2
0
    def add_default_static_files_mappings(self, project_path):
        print(snakesay("Adding static files mappings for /static/ and /media/"))

        url = (
            get_api_endpoint().format(username=getpass.getuser(), flavor="webapps") + self.domain + "/static_files/"
        )
        call_api(url, "post", json=dict(url="/static/", path=str(Path(project_path) / "static")))
        call_api(url, "post", json=dict(url="/media/", path=str(Path(project_path) / "media")))
Beispiel #3
0
    def sanity_checks(self, nuke):
        print(snakesay("Running API sanity checks"))
        token = os.environ.get("API_TOKEN")
        if not token:
            raise SanityException(
                dedent(
                    """
                Could not find your API token.
                You may need to create it on the Accounts page?
                You will also need to close this console and open a new one once you've done that.
                """
                )
            )

        if nuke:
            return

        url = get_api_endpoint().format(username=getpass.getuser(), flavor="webapps") + self.domain + "/"
        response = call_api(url, "get")
        if response.status_code == 200:
            raise SanityException(
                "You already have a webapp for {domain}.\n\nUse the --nuke option if you want to replace it.".format(
                    domain=self.domain
                )
            )
Beispiel #4
0
    def delete_log(self, log_type, index=0):
        if index:
            print(
                snakesay(
                    "Deleting old (archive number {index}) {type} log file for {domain} via API"
                    .format(index=index, type=log_type, domain=self.domain)))
        else:
            print(
                snakesay(
                    "Deleting current {type} log file for {domain} via API".
                    format(type=log_type, domain=self.domain)))

        if index == 1:
            url = get_api_endpoint().format(
                username=getpass.getuser(),
                flavor="files") + "path/var/log/{domain}.{type}.log.1/".format(
                    domain=self.domain, type=log_type)
        elif index > 1:
            url = get_api_endpoint().format(
                username=getpass.getuser(), flavor="files"
            ) + "path/var/log/{domain}.{type}.log.{index}.gz/".format(
                domain=self.domain, type=log_type, index=index)
        else:
            url = get_api_endpoint().format(
                username=getpass.getuser(),
                flavor="files") + "path/var/log/{domain}.{type}.log/".format(
                    domain=self.domain, type=log_type)
        response = call_api(url, "delete")
        if not response.ok:
            raise Exception(
                "DELETE log file via API failed, got {response}:{response_text}"
                .format(response=response, response_text=response.text))
Beispiel #5
0
 def get_log_info(self):
     url = get_api_endpoint().format(
         username=getpass.getuser(),
         flavor="files") + "tree/?path=/var/log/"
     response = call_api(url, "get")
     if not response.ok:
         raise Exception(
             "GET log files info via API failed, got {response}:{response_text}"
             .format(response=response, response_text=response.text))
     file_list = response.json()
     log_types = ["access", "error", "server"]
     logs = {"access": [], "error": [], "server": []}
     log_prefix = "/var/log/{domain}.".format(domain=self.domain)
     for file_name in file_list:
         if type(file_name) == str and file_name.startswith(log_prefix):
             log = file_name[len(log_prefix):].split(".")
             if log[0] in log_types:
                 log_type = log[0]
                 if log[-1] == "log":
                     log_index = 0
                 elif log[-1] == "1":
                     log_index = 1
                 elif log[-1] == "gz":
                     log_index = int(log[-2])
                 else:
                     continue
                 logs[log_type].append(log_index)
     return logs
    def sharing_delete(self, path):
        """Stops sharing file at `path`.

        Returns 204 on successful unshare."""

        url = f"{self.sharing_endpoint}?path={path}"

        result = call_api(url, "DELETE")

        return result.status_code
    def sharing_get(self, path):
        """Checks sharing status for a `path`.

        Returns url with sharing link if file is shared or an empty
        string otherwise."""

        url = f"{self.sharing_endpoint}?path={path}"

        result = call_api(url, "GET")

        return result.json()["url"] if result.ok else ""
Beispiel #8
0
    def get_ssl_info(self):
        url = get_api_endpoint().format(username=getpass.getuser(), flavor="webapps") + self.domain + "/ssl/"
        response = call_api(url, "get")
        if not response.ok:
            raise Exception(
                f"GET SSL details via API failed, got {response}:{response.text}"
            )

        result = response.json()
        result["not_after"] = parse(result["not_after"])
        return result
Beispiel #9
0
    def get_specs(self, task_id):
        """Get task specs by id.

        :param task_id: existing task id
        :returns: dictionary of existing task specs"""

        result = call_api(f"{self.base_url}{task_id}/", "GET")
        if result.status_code == 200:
            return result.json()
        else:
            raise Exception(
                "Could not get task with id {task_id}. Got result {result}: {content}"
                .format(task_id=task_id, result=result, content=result.text))
Beispiel #10
0
 def set_ssl(self, certificate, private_key):
     print(snakesay(f"Setting up SSL for {self.domain} via API"))
     url = get_api_endpoint().format(username=getpass.getuser(), flavor="webapps") + self.domain + "/ssl/"
     response = call_api(url, "post", json={"cert": certificate, "private_key": private_key})
     if not response.ok:
         raise Exception(
             dedent(
                 """
                 POST to set SSL details via API failed, got {response}:{response_text}
                 If you just created an API token, you need to set the API_TOKEN environment variable or start a
                 new console.  Also you need to have setup a `{domain}` PythonAnywhere webapp for this to work.
                 """
             ).format(response=response, response_text=response.text, domain=self.domain)
         )
Beispiel #11
0
    def delete(self, task_id):
        """Deletes scheduled task by id.

        :param task_id: scheduled task to be deleted id number
        :returns: True when API response is 204"""

        result = call_api(f"{self.base_url}{task_id}/", "DELETE")

        if result.status_code == 204:
            return True

        if not result.ok:
            raise Exception(
                f"DELETE via API on task {task_id} failed, got {result}: {result.text}"
            )
    def path_delete(self, path):
        """Deletes the file at specified `path` (if file is a
        directory it will be deleted as well).

        Returns 204 on sucess, raises otherwise."""

        url = f"{self.path_endpoint}{path}"

        result = call_api(url, "DELETE")

        if result.status_code == 204:
            return result.status_code

        raise Exception(
            f"DELETE on {url} failed, got {result}{self._error_msg(result)}")
    def tree_get(self, path):
        """Returns list of absolute paths of regular files and
        subdirectories of a directory at `path`.  Result is limited to
        1000 items.

        Raises if `path` does not point to an existing directory."""

        url = f"{self.tree_endpoint}?path={path}"

        result = call_api(url, "GET")

        if result.ok:
            return result.json()

        raise Exception(
            f"GET to {url} failed, got {result}{self._error_msg(result)}")
    def sharing_post(self, path):
        """Starts sharing a file at `path`.

        Returns a tuple with a status code and sharing link on
        success, raises otherwise.  Status code is 201 on success, 200
        if file has been already shared."""

        url = self.sharing_endpoint

        result = call_api(url, "POST", json={"path": path})

        if result.ok:
            return result.status_code, result.json()["url"]

        raise Exception(
            f"POST to {url} to share '{path}' failed, got {result}{self._error_msg(result)}"
        )
Beispiel #15
0
    def create(self, params):
        """Creates new scheduled task using `params`.

        Params should be: command, enabled (True or False), interval (daily or
        hourly), hour (24h format) and minute.

        :param params: dictionary with required scheduled task specs
        :returns: dictionary with created task specs"""

        result = call_api(self.base_url, "POST", json=params)

        if result.status_code == 201:
            return result.json()

        if not result.ok:
            raise Exception(
                f"POST to set new task via API failed, got {result}: {result.text}"
            )
Beispiel #16
0
    def delete(self, task_id):
        """Deletes scheduled task by id.

        :param task_id: scheduled task to be deleted id number
        :returns: True when API response is 204"""

        result = call_api(
            "{base_url}{task_id}/".format(base_url=self.base_url,
                                          task_id=task_id), "DELETE")

        if result.status_code == 204:
            return True

        if not result.ok:
            raise Exception(
                "DELETE via API on task {task_id} failed, got {result}: "
                "{result_text}".format(task_id=task_id,
                                       result=result,
                                       result_text=result.text))
    def path_get(self, path):
        """Returns dictionary of directory contents when `path` is an
        absolute path to of an existing directory or file contents if
        `path` is an absolute path to an existing file -- both
        available to the PythonAnywhere user.  Raises when `path` is
        invalid or unavailable."""

        url = f"{self.path_endpoint}{path}"

        result = call_api(url, "GET")

        if result.status_code == 200:
            if "application/json" in result.headers.get("content-type", ""):
                return result.json()
            return result.content

        raise Exception(
            f"GET to fetch contents of {url} failed, got {result}{self._error_msg(result)}"
        )
    def path_post(self, dest_path, content):
        """Uploads contents of `content` to `dest_path` which should be
        a valid absolute path of a file available to a PythonAnywhere
        user.  If `dest_path` contains directories which don't exist
        yet, they will be created.

        Returns 200 if existing file on PythonAnywhere has been
        updated with `source` contents, or 201 if file from
        `dest_path` has been created with those contents."""

        url = f"{self.path_endpoint}{dest_path}"

        result = call_api(url, "POST", files={"content": content})

        if result.ok:
            return result.status_code

        raise Exception(
            f"POST to upload contents to {url} failed, got {result}{self._error_msg(result)}"
        )
Beispiel #19
0
 def reload(self):
     print(snakesay(f"Reloading {self.domain} via API"))
     url = get_api_endpoint().format(username=getpass.getuser(), flavor="webapps") + self.domain + "/reload/"
     response = call_api(url, "post")
     if not response.ok:
         if response.status_code == 409 and response.json()["error"] == "cname_error":
             print(
                 snakesay(
                     dedent("""
                         Could not find a CNAME for your website.  If you're using an A record,
                         CloudFlare, or some other way of pointing your domain at PythonAnywhere
                         then that should not be a problem.  If you're not, you should double-check
                         your DNS setup.
                     """)
                 )
             )
             return
         raise Exception(
             f"POST to reload webapp via API failed, got {response}:{response.text}"
         )
Beispiel #20
0
    def update(self, task_id, params):
        """Updates existing task using id and params.

        Params should at least one of: command, enabled, interval, hour,
        minute. To update hourly task don't use 'hour' param. On the other
        hand when changing task's interval from 'hourly' to 'daily' hour is
        required.

        :param task_id: existing task id
        :param params: dictionary of specs to update"""

        result = call_api(
            f"{self.base_url}{task_id}/",
            "PATCH",
            json=params,
        )
        if result.status_code == 200:
            return result.json()
        else:
            raise Exception(
                f"Could not update task {task_id}. Got {result}: {result.text}"
            )
Beispiel #21
0
 def test_raises_on_401(self, api_token, api_responses):
     url = "https://foo.com/"
     api_responses.add(responses.POST, url, status=401, body="nope")
     with pytest.raises(AuthenticationError) as e:
         call_api(url, "post")
     assert str(e.value) == "Authentication error 401 calling API: nope"
Beispiel #22
0
 def test_passes_verify_from_environment(self, api_token, monkeypatch):
     monkeypatch.setenv("PYTHONANYWHERE_INSECURE_API", "true")
     with patch("pythonanywhere.api.base.requests") as mock_requests:
         call_api("url", "post", foo="bar")
     args, kwargs = mock_requests.request.call_args
     assert kwargs["verify"] is False
Beispiel #23
0
 def test_verify_is_true_if_env_not_set(self, api_token):
     with patch("pythonanywhere.api.base.requests") as mock_requests:
         call_api("url", "post", foo="bar")
     args, kwargs = mock_requests.request.call_args
     assert kwargs["verify"] is True
Beispiel #24
0
    def get_list(self):
        """Gets list of existing scheduled tasks.

        :returns: list of existing scheduled tasks specs"""

        return call_api(self.base_url, "GET").json()