Beispiel #1
0
    def test_get_list_of_logs(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="files") +
                        "tree/?path=/var/log/")
        api_responses.add(
            responses.GET,
            expected_url,
            status=200,
            body=json.dumps([
                "/var/log/blah",
                "/var/log/mydomain.com.access.log",
                "/var/log/mydomain.com.access.log.1",
                "/var/log/mydomain.com.access.log.2.gz",
                "/var/log/mydomain.com.error.log",
                "/var/log/mydomain.com.error.log.1",
                "/var/log/mydomain.com.error.log.2.gz",
                "/var/log/mydomain.com.server.log",
                "/var/log/mydomain.com.server.log.1",
                "/var/log/mydomain.com.server.log.2.gz",
            ]),
        )

        logs = Webapp("mydomain.com").get_log_info()

        post = api_responses.calls[0]
        assert post.request.url == expected_url
        assert post.request.headers["Authorization"] == f"Token {api_token}"
        assert logs == {
            "access": [0, 1, 2],
            "error": [0, 1, 2],
            "server": [0, 1, 2]
        }
Beispiel #2
0
    def test_returns_json_from_server_having_parsed_expiry_with_timezone_offset_and_separators(
            self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/ssl/")
        api_responses.add(
            responses.GET,
            expected_url,
            status=200,
            body=json.dumps({
                "not_after":
                "2018-08-24T17:16:23+00:00",
                "issuer_name":
                "PythonAnywhere test CA",
                "subject_name":
                "www.mycoolsite.com",
                "subject_alternate_names":
                ["www.mycoolsite.com", "mycoolsite.com"],
            }),
        )

        assert Webapp("mydomain.com").get_ssl_info() == {
            "not_after": datetime(2018, 8, 24, 17, 16, 23, tzinfo=tzutc()),
            "issuer_name": "PythonAnywhere test CA",
            "subject_name": "www.mycoolsite.com",
            "subject_alternate_names":
            ["www.mycoolsite.com", "mycoolsite.com"],
        }

        get = api_responses.calls[0]
        assert get.request.method == "GET"
        assert get.request.url == expected_url
        assert get.request.headers["Authorization"] == f"Token {api_token}"
Beispiel #3
0
def main(domain_name, certificate_file, private_key_file, suppress_reload):
    if not os.path.exists(certificate_file):
        print(f"Could not find certificate file {certificate_file}")
        sys.exit(1)
    with open(certificate_file, "r") as f:
        certificate = f.read()

    if not os.path.exists(private_key_file):
        print(f"Could not find private key file {private_key_file}")
        sys.exit(1)
    with open(private_key_file, "r") as f:
        private_key = f.read()

    webapp = Webapp(domain_name)
    webapp.set_ssl(certificate, private_key)
    if not suppress_reload:
        webapp.reload()

    ssl_details = webapp.get_ssl_info()
    print(snakesay(
        "That's all set up now :-)\n"
        "Your new certificate will expire on {expiry:%d %B %Y},\n"
        "so shortly before then you should renew it\n"
        "and install the new certificate.".format(
            expiry=ssl_details["not_after"]
        )
    ))
Beispiel #4
0
    def test_does_two_posts_to_static_files_endpoint(self, api_token,
                                                     api_responses):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/static_files/")
        api_responses.add(responses.POST, expected_url, status=201)
        api_responses.add(responses.POST, expected_url, status=201)

        Webapp("mydomain.com").add_default_static_files_mappings(
            "/project/path")

        post1 = api_responses.calls[0]
        assert post1.request.url == expected_url
        assert post1.request.headers["content-type"] == "application/json"
        assert post1.request.headers["Authorization"] == f"Token {api_token}"
        assert json.loads(post1.request.body.decode("utf8")) == {
            "url": "/static/",
            "path": "/project/path/static",
        }
        post2 = api_responses.calls[1]
        assert post2.request.url == expected_url
        assert post2.request.headers["content-type"] == "application/json"
        assert post2.request.headers["Authorization"] == f"Token {api_token}"
        assert json.loads(post2.request.body.decode("utf8")) == {
            "url": "/media/",
            "path": "/project/path/media",
        }
Beispiel #5
0
    def test_does_patch_to_update_virtualenv_path_and_source_directory(
            self, api_responses, api_token):
        expected_post_url = get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps")
        expected_patch_url = (get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps") + "mydomain.com/")
        api_responses.add(
            responses.POST,
            expected_post_url,
            status=201,
            body=json.dumps({"status": "OK"}),
        )
        api_responses.add(responses.PATCH, expected_patch_url, status=200)

        Webapp("mydomain.com").create("3.7",
                                      "/virtualenv/path",
                                      "/project/path",
                                      nuke=False)

        patch = api_responses.calls[1]
        assert patch.request.url == expected_patch_url
        assert patch.request.body == urlencode({
            "virtualenv_path":
            "/virtualenv/path",
            "source_directory":
            "/project/path"
        })
        assert patch.request.headers["Authorization"] == f"Token {api_token}"
Beispiel #6
0
    def test_raises_if_patch_does_not_20x(self, api_responses, api_token):
        expected_post_url = get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps")
        expected_patch_url = (get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps") + "mydomain.com/")
        api_responses.add(
            responses.POST,
            expected_post_url,
            status=201,
            body=json.dumps({"status": "OK"}),
        )
        api_responses.add(
            responses.PATCH,
            expected_patch_url,
            status=400,
            json={"message": "an error"},
        )

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").create("3.7",
                                          "/virtualenv/path",
                                          "/project/path",
                                          nuke=False)

        assert (
            "PATCH to set virtualenv path and source directory via API failed"
            in str(e.value))
        assert "an error" in str(e.value)
Beispiel #7
0
    def test_does_post_to_create_webapp(self, api_responses, api_token):
        expected_post_url = get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps")
        expected_patch_url = (get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps") + "mydomain.com/")
        api_responses.add(
            responses.POST,
            expected_post_url,
            status=201,
            body=json.dumps({"status": "OK"}),
        )
        api_responses.add(responses.PATCH, expected_patch_url, status=200)

        Webapp("mydomain.com").create("3.7",
                                      "/virtualenv/path",
                                      "/project/path",
                                      nuke=False)

        post = api_responses.calls[0]
        assert post.request.url == expected_post_url
        assert post.request.body == urlencode({
            "domain_name":
            "mydomain.com",
            "python_version":
            PYTHON_VERSIONS["3.7"]
        })
        assert post.request.headers["Authorization"] == f"Token {api_token}"
Beispiel #8
0
 def __init__(self, domain, python_version):
     self.domain = domain
     self.python_version = python_version
     self.project_path = Path(f'~/{domain}').expanduser()
     self.virtualenv = Virtualenv(self.domain, self.python_version)
     self.wsgi_file_path = Path(
         f"/var/www/{domain.replace('.', '_')}_wsgi.py")
     self.webapp = Webapp(domain)
Beispiel #9
0
    def test_nuke_option_overrides_all_but_token_check(self, api_token,
                                                       api_responses,
                                                       fake_home,
                                                       virtualenvs_folder):
        webapp = Webapp(self.domain)
        (fake_home / self.domain).mkdir()
        (virtualenvs_folder / self.domain).mkdir()

        webapp.sanity_checks(nuke=True)  # should not raise
Beispiel #10
0
def reload(domain_name: str = typer.Option(
    "your-username.pythonanywhere.com",
    "-d",
    "--domain",
    help="Domain name, eg www.mydomain.com",
)):
    domain_name = ensure_domain(domain_name)
    webapp = Webapp(domain_name)
    webapp.reload()
    typer.echo(snakesay(f"{domain_name} has been reloaded"))
Beispiel #11
0
    def test_raises_if_get_does_not_return_200(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/ssl/")
        api_responses.add(responses.GET, expected_url, status=404, body="nope")

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").get_ssl_info()

        assert "GET SSL details via API failed, got" in str(e.value)
        assert "nope" in str(e.value)
Beispiel #12
0
    def test_raises_if_get_does_not_20x(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="files") +
                        "tree/?path=/var/log/")
        api_responses.add(responses.GET, expected_url, status=404, body="nope")

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").get_log_info()

        assert "GET log files info via API failed" in str(e.value)
        assert "nope" in str(e.value)
Beispiel #13
0
    def test_delete_old_access_log(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="files") +
                        "path/var/log/mydomain.com.access.log.1/")
        api_responses.add(responses.DELETE, expected_url, status=200)

        Webapp("mydomain.com").delete_log(log_type="access", index=1)

        post = api_responses.calls[0]
        assert post.request.url == expected_url
        assert post.request.body is None
        assert post.request.headers["Authorization"] == f"Token {api_token}"
Beispiel #14
0
    def test_does_post_to_reload_url(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/reload/")
        api_responses.add(responses.POST, expected_url, status=200)

        Webapp("mydomain.com").reload()

        post = api_responses.calls[0]
        assert post.request.url == expected_url
        assert post.request.body is None
        assert post.request.headers["Authorization"] == f"Token {api_token}"
Beispiel #15
0
    def test_raises_if_post_does_not_20x(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/ssl/")
        api_responses.add(responses.POST,
                          expected_url,
                          status=404,
                          body="nope")

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").set_ssl("foo", "bar")

        assert "POST to set SSL details via API failed" in str(e.value)
        assert "nope" in str(e.value)
Beispiel #16
0
def install_ssl(
    domain_name: str = typer.Argument(
        ...,
        help="Domain name, eg www.mydomain.com",
    ),
    certificate_file: Path = typer.Argument(
        ...,
        exists=True,
        file_okay=True,
        readable=True,
        resolve_path=True,
        help=
        "The name of the file containing the combined certificate in PEM format (normally a number of blocks, "
        'each one starting "BEGIN CERTIFICATE" and ending "END CERTIFICATE")',
    ),
    private_key_file: Path = typer.Argument(
        ...,
        exists=True,
        file_okay=True,
        readable=True,
        resolve_path=True,
        help=
        "The name of the file containing the private key in PEM format (a file with one block, "
        'starting with something like "BEGIN PRIVATE KEY" and ending with something like "END PRIVATE KEY")',
    ),
    suppress_reload: bool = typer.Option(
        False,
        help=
        "The website will need to be reloaded in order to activate the new certificate/key combination "
        "-- this happens by default, use this option to suppress it.",
    ),
):
    with open(certificate_file, "r") as f:
        certificate = f.read()

    with open(private_key_file, "r") as f:
        private_key = f.read()

    webapp = Webapp(domain_name)
    webapp.set_ssl(certificate, private_key)
    if not suppress_reload:
        webapp.reload()

    ssl_details = webapp.get_ssl_info()
    typer.echo(
        snakesay("That's all set up now :-)\n"
                 f"Your new certificate for {domain_name} will expire\n"
                 f"on {ssl_details['not_after'].date().isoformat()},\n"
                 "so shortly before then you should renew it\n"
                 "and install the new certificate."))
Beispiel #17
0
    def test_raises_if_delete_does_not_20x(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="files") +
                        "path/var/log/mydomain.com.access.log/")
        api_responses.add(responses.DELETE,
                          expected_url,
                          status=404,
                          body="nope")

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").delete_log(log_type="access")

        assert "DELETE log file via API failed" in str(e.value)
        assert "nope" in str(e.value)
Beispiel #18
0
    def test_raises_if_post_does_not_20x_that_is_not_a_cname_error(
            self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/reload/")
        api_responses.add(responses.POST,
                          expected_url,
                          status=404,
                          body="nope")

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").reload()

        assert "POST to reload webapp via API failed" in str(e.value)
        assert "nope" in str(e.value)
def main(domain_name, suppress_reload):
    homedir = expanduser("~")
    possible_paths = (
        os.path.join(homedir, 'letsencrypt', domain_name),
        os.path.join(homedir, 'letsencrypt', 'certs', domain_name),
    )
    done = False
    for path in possible_paths:
        certificate_file = os.path.join(path, 'fullchain.pem')
        private_key_file = os.path.join(path, 'privkey.pem')
        if os.path.exists(certificate_file) and os.path.exists(private_key_file):
            with open(certificate_file, "r") as f:
                certificate = f.read()
            with open(private_key_file, "r") as f:
                private_key = f.read()

            webapp = Webapp(domain_name)
            webapp.set_ssl(certificate, private_key)
            if not suppress_reload:
                webapp.reload()

            ssl_details = webapp.get_ssl_info()
            print(
                snakesay(
                    "This method of handling Let's Encrypt certs\n"
                    "**************IS DEPRECATED.**************\n\n"
                    "You can now have a Let's Encrypt certificate managed by PythonAnywhere.\n"
                    "We handle all the details of getting it, installing it,\n"
                    "and managing renewals for you. So you don't need to do\n"
                    "any of the stuff below any more.\n\n"
                    "Anyway, All is set up for now. \n"
                    "Your new certificate will expire on {expiry:%d %B %Y},\n"
                    "so shortly before then you should switch to the new system\n"
                    "(see https://help.pythonanywhere.com/pages/HTTPSSetup/)\n"
                    "".format(
                        expiry=ssl_details["not_after"]
                    )
                )
            )

            done = True
            break

    if not done:
        print("Could not find certificate or key files (looked in {possible_paths})".format(
            possible_paths=possible_paths
        ))
        sys.exit(2)
Beispiel #20
0
    def test_raises_if_post_does_not_20x(self, api_responses, api_token):
        expected_post_url = get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps")
        api_responses.add(responses.POST,
                          expected_post_url,
                          status=500,
                          body="an error")

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").create("3.7",
                                          "/virtualenv/path",
                                          "/project/path",
                                          nuke=False)

        assert "POST to create webapp via API failed" in str(e.value)
        assert "an error" in str(e.value)
Beispiel #21
0
    def test_does_post_to_ssl_url(self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/ssl/")
        api_responses.add(responses.POST, expected_url, status=200)
        certificate = "certificate data"
        private_key = "private key data"

        Webapp("mydomain.com").set_ssl(certificate, private_key)

        post = api_responses.calls[0]
        assert post.request.url == expected_url
        assert json.loads(post.request.body.decode("utf8")) == {
            "private_key": "private key data",
            "cert": "certificate data",
        }
        assert post.request.headers["Authorization"] == f"Token {api_token}"
Beispiel #22
0
def main(domain, log_type, log_index):
    webapp = Webapp(ensure_domain(domain))
    log_types = ["access", "error", "server"]
    logs = webapp.get_log_info()
    if log_type == "all" and log_index == "all":
        for key in log_types:
            for log in logs[key]:
                webapp.delete_log(key, log)
    elif log_type == "all":
        for key in log_types:
            webapp.delete_log(key, int(log_index))
    elif log_index == "all":
        for i in logs[log_type]:
            webapp.delete_log(log_type, int(i))
    else:
        webapp.delete_log(log_type, int(log_index))
    print(snakesay('All Done!'))
Beispiel #23
0
    def test_ignores_404_from_delete_call_when_nuking(self, api_responses,
                                                      api_token):
        post_url = get_api_endpoint().format(username=getpass.getuser(),
                                             flavor="webapps")
        webapp_url = (get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps") + "mydomain.com/")
        api_responses.add(responses.DELETE, webapp_url, status=404)
        api_responses.add(responses.POST,
                          post_url,
                          status=201,
                          body=json.dumps({"status": "OK"}))
        api_responses.add(responses.PATCH, webapp_url, status=200)

        Webapp("mydomain.com").create("3.7",
                                      "/virtualenv/path",
                                      "/project/path",
                                      nuke=True)
Beispiel #24
0
    def test_does_not_raise_if_post_responds_with_a_cname_error(
            self, api_responses, api_token):
        expected_url = (get_api_endpoint().format(username=getpass.getuser(),
                                                  flavor="webapps") +
                        "mydomain.com/reload/")
        api_responses.add(
            responses.POST,
            expected_url,
            status=409,
            json={
                "status": "error",
                "error": "cname_error"
            },
        )

        ## Should not raise
        Webapp("mydomain.com").reload()
Beispiel #25
0
    def test_raises_if_webapp_already_exists(self, api_token, api_responses):
        webapp = Webapp(self.domain)
        api_responses.add(
            responses.GET,
            self.expected_url,
            status=200,
            body=json.dumps({
                "id": 1,
                "domain_name": self.domain
            }),
        )

        with pytest.raises(SanityException) as e:
            webapp.sanity_checks(nuke=False)

        assert "You already have a webapp for " + self.domain in str(e.value)
        assert "nuke" in str(e.value)
Beispiel #26
0
    def test_does_delete_first_for_nuke_call(self, api_responses, api_token):
        post_url = get_api_endpoint().format(username=getpass.getuser(),
                                             flavor="webapps")
        webapp_url = (get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps") + "mydomain.com/")
        api_responses.add(responses.DELETE, webapp_url, status=200)
        api_responses.add(responses.POST,
                          post_url,
                          status=201,
                          body=json.dumps({"status": "OK"}))
        api_responses.add(responses.PATCH, webapp_url, status=200)

        Webapp("mydomain.com").create("3.7",
                                      "/virtualenv/path",
                                      "/project/path",
                                      nuke=True)

        delete = api_responses.calls[0]
        assert delete.request.method == "DELETE"
        assert delete.request.url == webapp_url
        assert delete.request.headers["Authorization"] == f"Token {api_token}"
Beispiel #27
0
    def test_raises_if_post_returns_a_200_with_status_error(
            self, api_responses, api_token):
        expected_post_url = get_api_endpoint().format(
            username=getpass.getuser(), flavor="webapps")
        api_responses.add(
            responses.POST,
            expected_post_url,
            status=200,
            body=json.dumps({
                "status": "ERROR",
                "error_type": "bad",
                "error_message": "bad things happened",
            }),
        )

        with pytest.raises(Exception) as e:
            Webapp("mydomain.com").create("3.7",
                                          "/virtualenv/path",
                                          "/project/path",
                                          nuke=False)

        assert "POST to create webapp via API failed" in str(e.value)
        assert "bad things happened" in str(e.value)
Beispiel #28
0
def delete_logs(
    domain_name: str = typer.Option(
        "your-username.pythonanywhere.com",
        "-d",
        "--domain",
        help="Domain name, eg www.mydomain.com",
    ),
    log_type: LogType = typer.Option(
        LogType.all,
        "-t",
        "--log-type",
    ),
    log_index: str = typer.Option(
        "all",
        "-i",
        "--log-index",
        callback=index_callback,
        help=
        "0 for current log, 1-9 for one of archive logs or all for all of them",
    ),
):
    webapp = Webapp(ensure_domain(domain_name))
    log_types = ["access", "error", "server"]
    logs = webapp.get_log_info()
    if log_type == "all" and log_index == "all":
        for key in log_types:
            for log in logs[key]:
                webapp.delete_log(key, log)
    elif log_type == "all":
        for key in log_types:
            webapp.delete_log(key, int(log_index))
    elif log_index == "all":
        for i in logs[log_type]:
            webapp.delete_log(log_type, int(i))
    else:
        webapp.delete_log(log_type, int(log_index))
    typer.echo(snakesay("All done!"))
Beispiel #29
0
def main(domain_name):
    webapp = Webapp(domain_name)
    webapp.reload()
    print(snakesay(f"{domain_name} has been reloaded"))
Beispiel #30
0
 def test_does_not_raise_if_no_webapp(self, api_token, api_responses):
     webapp = Webapp(self.domain)
     api_responses.add(responses.GET, self.expected_url, status=404)
     webapp.sanity_checks(nuke=False)  # should not raise