Exemple #1
0
def run_highcpu_build(
    app: App,
    pr_number: int,
    sha: str,
    repo: Repository,
):
    for f in os.listdir("./deploy_files"):
        if f == "cloudbuild.yaml":
            # don't use this one, we don't want any caching
            continue
        shutil.copyfile(f"./deploy_files/{f}", f"./{f}")
    shutil.copyfile("./dockerfiles/buildserver.Dockerfile", "./Dockerfile")
    with open("Dockerfile", "a+") as f:
        f.seek(0)
        contents = f.read()
        contents = contents.replace("$BASE_IMAGE", app.config["build_image"])
        contents = contents.replace("$APP_NAME", app.name)
        contents = contents.replace("$PR_NUMBER", str(pr_number))
        contents = contents.replace("$SHA", sha)
        contents = contents.replace("$REPO_ID", repo.full_name)
        contents = contents.replace("$MASTER_SECRET", get_master_secret())
        f.seek(0)
        f.truncate()
        f.write(contents)
    sh(
        "gcloud",
        "builds",
        "submit",
        "-q",
        "--tag",
        "gcr.io/cs61a-140900/temp-{}".format(
            gen_service_name(app.name, pr_number)),
        # "--machine-type=N1_HIGHCPU_32",
    )
Exemple #2
0
def hash_all(show_progress=False):
    global tracked_files
    files = (
        sh("git",
           "ls-files",
           "--exclude-standard",
           capture_output=True,
           quiet=True).splitlines()  # All tracked files
        + sh(
            "git",
            "ls-files",
            "-o",
            "--exclude-standard",
            capture_output=True,
            quiet=True,
        ).splitlines()  # Untracked but not ignored files
    )
    out = {}
    for file in tqdm(files) if show_progress else files:
        h = get_hash(file)
        if isinstance(file, bytes):
            file = file.decode("ascii")
        out[relpath(file)] = h
    tracked_files = set(out) | set(remote_state)
    return out
Exemple #3
0
def main():
    """Start the Sandbox and IDE servers."""
    sh("nginx")

    sandbox_port = get_open_port()
    sb = subprocess.Popen(
        ["gunicorn", "-b", f":{sandbox_port}", "-w", "4", "sandbox:app", "-t", "3000"],
        env=os.environ,
    )
    proxy(f"sb.{HOSTNAME} *.sb.{HOSTNAME}", sandbox_port, f"sb.{HOSTNAME}")
    proxy(f"*.sb.pr.{HOSTNAME}", sandbox_port, f"sb.pr.{HOSTNAME}")

    ide_port = get_open_port()
    ide = subprocess.Popen(
        ["gunicorn", "-b", f":{ide_port}", "-w", "4", "ide:app", "-t", "3000"],
        env=os.environ,
    )
    proxy(f"ide.{HOSTNAME}", ide_port, f"ide.{HOSTNAME}")
    proxy(f"*.ide.pr.{HOSTNAME}", ide_port, f"ide.pr.{HOSTNAME}")

    with open(f"/etc/nginx/sites-enabled/default", "w") as f:
        f.write(
            DEFAULT_SERVER.format(
                ide_port=ide_port, sb_port=sandbox_port, nginx_port=NGINX_PORT
            )
        )

    sh("nginx", "-s", "reload")

    ide.communicate()  # make sure docker doesn't close this container
def run_service_deploy(app: App, pr_number: int):
    if pr_number != 0:
        return  # do not deploy PR builds to prod!
    for file in os.listdir("."):
        sh(
            "gcloud",
            "compute",
            "scp",
            "--recurse",
            file,
            app.config["service"]["host"] + ":" +
            app.config["service"]["root"],
            "--zone",
            app.config["service"]["zone"],
        )
    sh(
        "gcloud",
        "compute",
        "ssh",
        app.config["service"]["host"],
        "--command=sudo systemctl restart {}".format(
            app.config["service"]["name"]),
        "--zone",
        app.config["service"]["zone"],
    )
Exemple #5
0
def kill():
    username = request.form.get("username")
    pid = get_server_pid(username)

    if pid:
        sh("kill", pid.decode("utf-8")[:-1])
        sh("sleep", "2")  # give the server a couple of seconds to shutdown
    return redirect(session.pop(SK_RETURN_TO, url_for("index")))
def run_static_deploy(app: App, pr_number: int):
    bucket = f"gs://{gen_service_name(app.name, pr_number)}.buckets.cs61a.org"
    prod_bucket = f"gs://{gen_service_name(app.name, 0)}.buckets.cs61a.org"
    try:
        sh("gsutil", "mb", "-b", "on", bucket)
    except CalledProcessError:
        # bucket already exists
        pass
    sh("gsutil", "-m", "rsync", "-dRc", ".", bucket)
Exemple #7
0
def initialize_sandbox(force=False):
    with db_lock("sandboxes", g.username):
        initialized = check_sandbox_initialized(g.username)
        if initialized and not force:
            raise Exception("Sandbox is already initialized")
        elif initialized:
            sh("rm", "-rf", get_working_directory(g.username))
        Path(get_working_directory(g.username)).mkdir(parents=True,
                                                      exist_ok=True)
        os.chdir(get_working_directory(g.username))
        sh("git", "init")
        sh(
            "git",
            "fetch",
            "--depth=1",
            f"https://{get_secret(secret_name='GITHUB_ACCESS_TOKEN')}@github.com/{REPO}",
            "master",
        )
        sh("git", "checkout", "FETCH_HEAD", "-f")
        os.mkdir("published")  # needed for lazy-loading builds
        if is_prod_build():
            add_domain(name="sandbox", domain=f"{g.username}.sb.cs61a.org")
        with connect_db() as db:
            db("UPDATE sandboxes SET initialized=TRUE WHERE username=%s",
               [g.username])
Exemple #8
0
def build_worker(username):
    # Note that we are not necessarily running in an app context
    try:
        while True:
            targets = get_pending_targets(username)
            if not targets:
                break
            target = targets[0]
            src_version = get_src_version(username)
            os.chdir(get_working_directory(username))
            os.chdir("src")
            ok = False
            try:
                sh("make", "-n", "VIRTUAL_ENV=../env", target, env=ENV)
            except CalledProcessError as e:
                if e.returncode == 2:
                    # target does not exist, no need to build
                    update_version(username, target, src_version)
                    ok = True
            if not ok:
                # target exists, time to build!
                try:
                    sh(
                        "make",
                        "VIRTUAL_ENV=../env",
                        target,
                        env={
                            **ENV, "LAZY_LOADING": "true"
                        },
                        capture_output=True,
                        quiet=True,
                    )
                except CalledProcessError as e:
                    log_name = paste_text(
                        data=((e.stdout or b"").decode("utf-8") +
                              (e.stderr or b"").decode("utf-8")))
                    update_version(username, target, src_version, log_name)
                else:
                    update_version(username, target, src_version)
            with connect_db() as db:
                db(
                    "UPDATE builds SET pending=FALSE WHERE username=%s AND target=%s",
                    [username, target],
                )
    except:
        # in the event of failure, cancel all builds and trigger refresh
        increment_manual_version(username)
        clear_pending_builds(username)
        raise
def run_pypi_deploy(app: App, pr_number: int):
    sh("python", "-m", "venv", "env")
    update_setup_py(app, pr_number)
    sh("env/bin/pip", "install", "setuptools")
    sh("env/bin/pip", "install", "wheel")
    sh("env/bin/python", "setup.py", "sdist", "bdist_wheel")
    sh(
        "twine",
        "upload",
        *(f"dist/{file}" for file in os.listdir("dist")),
        env=dict(
            TWINE_USERNAME="******",
            TWINE_PASSWORD=get_secret(secret_name="PYPI_PASSWORD"),
        ),
    )
Exemple #10
0
def create_secret(service):
    if not is_staff("cs61a"):
        return login()
    email = get_user()["email"]
    if not is_admin(course="cs61a", email=email):
        abort(401)

    if service not in list_services():
        abort(404)

    out = reversed([
        entry["timestamp"] + " " + escape(entry["textPayload"])
        for entry in loads(
            sh(
                "gcloud",
                "logging",
                "read",
                f"projects/cs61a-140900/logs/run.googleapis.com AND resource.labels.service_name={service}",
                "--limit",
                "100",
                "--format",
                "json",
                capture_output=True,
            )) if "textPayload" in entry
    ])

    return "<pre>" + "\n".join(map(str, out)) + "</pre>"
Exemple #11
0
def get_pr_subdomains(app: App, pr_number: int) -> List[Hostname]:
    services = json.loads(
        sh(
            "gcloud",
            "run",
            "services",
            "list",
            "--platform",
            "managed",
            "--format",
            "json",
            capture_output=True,
        ))

    def get_hostname(service_name):
        for service in services:
            if service["metadata"]["name"] == service_name:
                return urlparse(service["status"]["address"]["url"]).netloc
        return None

    out = []

    if app.config["deploy_type"] in CLOUD_RUN_DEPLOY_TYPES:
        hostname = get_hostname(gen_service_name(app.name, pr_number))
        assert hostname is not None
        for pr_consumer in app.config["pr_consumers"]:
            out.append(PRHostname(pr_consumer, pr_number, hostname))
    elif app.config["deploy_type"] == "static":
        for consumer in app.config["static_consumers"]:
            hostname = get_hostname(gen_service_name(consumer, pr_number))
            if hostname is None:
                # consumer does not have a PR build, point to master build
                hostname = get_hostname(gen_service_name(consumer, 0))
                assert hostname is not None, "Invalid static resource consumer service"
            out.append(PRHostname(consumer, pr_number, hostname))
        if not app.config["static_consumers"]:
            out.append(
                PRHostname(
                    app.name,
                    pr_number,
                    get_hostname(gen_service_name(STATIC_SERVER, 0)),
                ))
    elif app.config["deploy_type"] == "hosted":
        for pr_consumer in app.config["pr_consumers"]:
            out.append(
                PRHostname(
                    pr_consumer,
                    pr_number,
                    f"{gen_service_name(app.name, pr_number)}.hosted.cs61a.org",
                ))
    elif app.config["deploy_type"] == "pypi":
        out.append(
            PyPIHostname(app.config["package_name"],
                         app.deployed_pypi_version))
    elif app.config["deploy_type"] in NO_PR_BUILD_DEPLOY_TYPES:
        pass
    else:
        assert False, "Unknown deploy type, failed to create PR domains"

    return out
Exemple #12
0
def run_cloud_function_deploy(app: App, pr_number: int):
    if pr_number != 0:
        return
    sh(
        "gcloud",
        "functions",
        "deploy",
        app.name,
        "--runtime",
        "python37",
        "--trigger-http",
        "--entry-point",
        "index",
        "--timeout",
        "500",
    )
Exemple #13
0
def get_server_pid(username):
    try:
        return sh("pgrep",
                  "-f",
                  " ".join(get_server_cmd(username)),
                  capture_output=True)
    except subprocess.CalledProcessError:
        return False
Exemple #14
0
def get_repo_files() -> List[str]:
    return [
        file.decode("ascii") if isinstance(file, bytes) else file
        for file in sh("git",
                       "ls-files",
                       "--exclude-standard",
                       capture_output=True,
                       quiet=True).splitlines()  # All tracked files
        + sh(
            "git",
            "ls-files",
            "-o",
            "--exclude-standard",
            capture_output=True,
            quiet=True,
        ).splitlines()  # Untracked but not ignored files
    ]
    def __init__(self, working_dir: str):
        self.working_dir = working_dir

        self.file_versions = {}
        self.file_cnt = 0

        self.rule_cnt = 0
        self.sh_cnt = 0

        self.rules = []
        self.actions = {}

        self.is_annotating = True

        mkdir(self.BUILD_DIRECTORY)
        sh("git", "init", cwd=self.BUILD_DIRECTORY)
        self._write_file("WORKSPACE")
        self.log(f"STARTING TEST")
Exemple #16
0
def find_target():
    if not hasattr(find_target, "out"):
        remote = sh(
            "git",
            "config",
            "--get",
            "remote.origin.url",
            capture_output=True,
            quiet=True,
        ).decode("utf-8")
        if REPO not in remote:
            raise Exception(
                "You must run this command in the berkeley-cs61a repo directory"
            )
        find_target.out = (sh("git",
                              "rev-parse",
                              "--show-toplevel",
                              capture_output=True,
                              quiet=True).decode("utf-8").strip())
    return find_target.out
Exemple #17
0
def create_pr_subdomain(app, pr_number, pr_host):
    target_domain = f"{pr_number}.{app}.pr.cs61a.org"
    conf_path = f"{pr_confs}/{target_domain}.conf"
    expected_cert_name = f"*.{app}.pr.cs61a.org"

    nginx_config = Server(
        Location(
            "/",
            proxy_pass=f"https://{pr_host}/",
            proxy_read_timeout="1800",
            proxy_connect_timeout="1800",
            proxy_send_timeout="1800",
            send_timeout="1800",
            proxy_set_header={
                "Host": pr_host,
                "X-Forwarded-For-Host": target_domain,
            },
        ),
        server_name=target_domain,
        listen="80",
    )

    if not os.path.exists(conf_path):
        with open(conf_path, "w") as f:
            f.write(str(nginx_config))
        sh("nginx", "-s", "reload")

    cert = proxy_cb.cert_else_false(expected_cert_name, force_exact=True)
    for _ in range(2):
        if cert:
            break
        proxy_cb.run_bot(domains=[expected_cert_name], args=["certonly"])
        cert = proxy_cb.cert_else_false(expected_cert_name, force_exact=True)

    if not cert:
        error = f"Hosted Apps failed to sign a certificate for {expected_cert_name}!"
        post_message(message=error, channel="infra")
        return dict(success=False, reason=error)

    proxy_cb.attach_cert(cert, target_domain)
    return dict(success=True)
Exemple #18
0
def run_61a_website_build():
    env = dict(CLOUD_STORAGE_BUCKET="website-pdf-cache.buckets.cs61a.org", )

    def build(target):
        # need to re-run make for stupid reasons
        out = sh(
            "make",
            "--no-print-directory",
            "-C",
            "src",
            target,
            env=env,
            capture_output=True,
        )
        print(out.decode("utf-8", "replace"))

    build("all")
    sh("cp", "-aT", "published", "released")
    build("unreleased")
    sh("cp", "-aT", "published", "unreleased")
    clean_all_except(["released", "unreleased"])
Exemple #19
0
 def build(target):
     # need to re-run make for stupid reasons
     out = sh(
         "make",
         "--no-print-directory",
         "-C",
         "src",
         target,
         env=env,
         capture_output=True,
     )
     print(out.decode("utf-8", "replace"))
Exemple #20
0
def run_sphinx_build():
    sh("python3", "-m", "venv", "env")
    sh("env/bin/pip", "install", "-r", "requirements.txt")
    sh("env/bin/sphinx-build", "-b", "dirhtml", "..", "_build")
    clean_all_except(["_build"])
    copytree("_build", ".", dirs_exist_ok=True)
    rmtree("_build")
Exemple #21
0
def build_docker_image(app: App, pr_number: int) -> str:
    for f in os.listdir("../../deploy_files"):
        shutil.copyfile(f"../../deploy_files/{f}", f"./{f}")
    service_name = gen_service_name(app.name, pr_number)
    prod_service_name = gen_service_name(app.name, 0)
    with open("cloudbuild.yaml", "a+") as f:
        f.seek(0)
        contents = f.read()
        contents = contents.replace("PROD_SERVICE_NAME", prod_service_name)
        contents = contents.replace("SERVICE_NAME", service_name)
        f.seek(0)
        f.truncate()
        f.write(contents)
    with open("Dockerfile", "a+") as f:
        f.seek(0)
        contents = f.read()
        contents = contents.replace("<APP_MASTER_SECRET>",
                                    gen_master_secret(app, pr_number))
        f.seek(0)
        f.truncate()
        f.write(contents)
    sh("gcloud", "builds", "submit", "-q", "--config", "cloudbuild.yaml")
    return f"gcr.io/{PROJECT_ID}/{service_name}"
Exemple #22
0
def venv(dir, req, reset):
    """Create a virtual environment in DIR.

    DIR is the location of the virtual env
    (minus the `env` folder itself). REQ is
    the location of the requirements file.
    Both of these arguments default to `./`.
    If you want to forcibly recreate an env,
    use the RESET option.
    """
    if not dir.endswith("/"):
        dir = dir + "/"
    if req.endswith("requirements.txt"):
        req = req[:-16]
    if not req.endswith("/"):
        req = req + "/"
    if os.path.exists(f"{dir}{ENV}"):
        if reset:
            shutil.rmtree(f"{dir}{ENV}")
        else:
            print("This environment already exists!")
            return
    sh("python3", "-m", "venv", f"{dir}{ENV}")
    sh(f"{dir}{ENV}/bin/pip3", "install", "-r", f"{req}{REQ}")
Exemple #23
0
 def clone():
     sh("git", "init")
     sh(
         "git",
         "fetch",
         "--depth=1",
         f"https://{get_secret(secret_name='GITHUB_ACCESS_TOKEN')}@github.com{path}",
         sha,
     )
     sh("git", "checkout", "FETCH_HEAD", "-f")
Exemple #24
0
def get_base_hostname(app: str) -> str:
    services = json.loads(
        sh(
            "gcloud",
            "run",
            "services",
            "list",
            "--platform",
            "managed",
            "--format",
            "json",
            capture_output=True,
        ))
    for service in services:
        if service["metadata"]["name"] == gen_service_name(app, 0):
            return urlparse(service["status"]["address"]["url"]).netloc
    raise KeyError
Exemple #25
0
def run_make_command(target):
    os.chdir(get_working_directory(g.username))
    os.chdir("src")

    clear_pending_builds(g.username)

    try:
        yield from sh(
            "make",
            "VIRTUAL_ENV=../env",
            target,
            env=ENV,
            stream_output=True,
            shell=True,
        )

    finally:
        increment_manual_version(g.username)
Exemple #26
0
def list_services():
    return [
        service["metadata"]["name"] for service in loads(
            sh(
                "gcloud",
                "run",
                "services",
                "list",
                "--platform",
                "managed",
                "--region",
                "us-west1",
                "--format",
                "json",
                "-q",
                capture_output=True,
            ))
    ]
Exemple #27
0
def list_services():
    """Returns the list of services from Google Cloud Run necessary to access app logs

    :return: list of services
    """
    return [
        service["metadata"]["name"] for service in loads(
            sh(
                "gcloud",
                "run",
                "services",
                "list",
                "--platform",
                "managed",
                "--region",
                "us-west1",
                "--format",
                "json",
                "-q",
                capture_output=True,
            ))
    ]
    def build(self, rule: Rule):
        if not self.is_annotating:
            self.log("")
        self.log(f"BUILDING rule {rule.name}")
        contents = "\n\n".join(
            rule.to_declaration(self) for rule in self.rules) + "\n"

        with open(self.INPUT_PATH, "w") as f:
            f.write(
                repr({
                    action.shell_id: dict(
                        inputs=[file.path for file in action.inputs],
                        data=action.data,
                    )
                    for action, _ in self.actions.items()
                }))

        self._write_file("BUILD", contents)
        try:
            output = sh(
                "bt",
                f":{rule.name}",
                "-q",
                quiet=True,
                cwd=self.BUILD_DIRECTORY,
                capture_output=True,
            ).decode("utf-8")
        except CalledProcessError as e:
            print(e.stdout.decode("utf-8"))
            raise

        if self.FAILURE in output:
            raise Exception("Unexpected scenario encountered")

        self.log(f"COMPLETED BUILDING rule {rule.name}\n")
        self.is_annotating = True
Exemple #29
0
def gen_service_account(app: App):
    # set up and create service account
    hashstate = HashState()
    permissions = sorted(app.config["permissions"])
    hashstate.record(permissions)
    service_account_name = f"managed-{hashstate.state()}"[:
                                                          30]  # max len of account ID is 30 chars
    service_account_email = (
        f"{service_account_name}@{PROJECT_ID}.iam.gserviceaccount.com")
    existing_accounts = json.loads(
        sh(
            "gcloud",
            "iam",
            "service-accounts",
            "list",
            "--format",
            "json",
            capture_output=True,
        ))
    for account in existing_accounts:
        if account["email"] == service_account_email:
            break
    else:
        # need to create service account
        sh(
            "gcloud",
            "iam",
            "service-accounts",
            "create",
            service_account_name,
            f"--description",
            f'Managed service account with permissions: {" ".join(permissions)}',
            "--display-name",
            "Managed service account - DO NOT EDIT MANUALLY",
        )
        sleep(60)  # it takes a while to create service accounts
        role_lookup = dict(
            # permissions that most apps might need
            storage="roles/storage.admin",
            database="roles/cloudsql.client",
            logging="roles/logging.admin",
            # only buildserver needs these
            iam_admin="roles/resourcemanager.projectIamAdmin",
            cloud_run_admin="roles/run.admin",
            cloud_functions_admin="roles/cloudfunctions.admin",
        )
        for permission in permissions:
            if permission == "rpc":
                pass  # handled later
            else:
                role = role_lookup[permission]
                try:
                    sh(
                        "gcloud",
                        "projects",
                        "add-iam-policy-binding",
                        PROJECT_ID,
                        f"--member",
                        f"serviceAccount:{service_account_email}",
                        f"--role",
                        role,
                    )
                except CalledProcessError:
                    # abort
                    sh(
                        "gcloud",
                        "iam",
                        "service-accounts",
                        "delete",
                        service_account_email,
                    )
                    raise
    return service_account_name
Exemple #30
0
def run_dockerfile_deploy(app: App, pr_number: int):
    image = build_docker_image(app, pr_number)
    service_name = gen_service_name(app.name, pr_number)
    if app.name == "buildserver":
        # we exempt buildserver to avoid breaking CI/CD in case of bugs
        service_account = None
    else:
        service_account = gen_service_account(app)
    sh(
        "gcloud",
        "beta",
        "run",
        "deploy",
        service_name,
        "--image",
        image,
        *(("--service-account", service_account) if service_account else ()),
        "--region",
        "us-west1",
        "--platform",
        "managed",
        "--timeout",
        "45m",
        "--cpu",
        str(app.config["cpus"]),
        "--memory",
        app.config["memory_limit"],
        "--concurrency",
        str(app.config["concurrency"]),
        "--allow-unauthenticated",
        "--add-cloudsql-instances",
        DB_INSTANCE_NAME,
        "--update-env-vars",
        ",".join(f"{key}={val}"
                 for key, val in gen_env_variables(app, pr_number).items()),
        "-q",
    )
    if pr_number == 0:
        domains = json.loads(
            sh(
                "gcloud",
                "beta",
                "run",
                "domain-mappings",
                "list",
                "--platform",
                "managed",
                "--region",
                "us-west1",
                "--format",
                "json",
                capture_output=True,
            ))
        for domain in app.config["first_party_domains"]:
            for domain_config in domains:
                if domain_config["metadata"]["name"] == domain:
                    break
            else:
                sh(
                    "gcloud",
                    "beta",
                    "run",
                    "domain-mappings",
                    "create",
                    "--service",
                    service_name,
                    "--domain",
                    domain,
                    "--platform",
                    "managed",
                    "--region",
                    "us-west1",
                )
        jobs = json.loads(
            sh(
                "gcloud",
                "scheduler",
                "jobs",
                "list",
                "-q",
                "--format=json",
                capture_output=True,
            ))
        for job in jobs:
            name = job["name"].split("/")[-1]
            if name.startswith(f"{app}-"):
                sh("gcloud", "scheduler", "jobs", "delete", name, "-q")

        for job in app.config["tasks"]:
            sh(
                "gcloud",
                "beta",
                "scheduler",
                "jobs",
                "create",
                "http",
                f"{app}-{job['name']}",
                f"--schedule={job['schedule']}",
                f"--uri=https://{app}.cs61a.org/jobs/{job['name']}",
                "--attempt-deadline=1200s",
                "-q",
            )