Skip to content

Commit

Permalink
Merge pull request #4 from getsentry/iw/apply-on-update
Browse files Browse the repository at this point in the history
Adding implementation for apply_on_update
  • Loading branch information
IanWoodard committed Dec 5, 2023
2 parents b08dc79 + 300bb14 commit ea1d00d
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 7 deletions.
37 changes: 34 additions & 3 deletions lib/sh.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
from __future__ import annotations

import contextlib
import os
from os import getenv
from typing import TYPE_CHECKING
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import MutableMapping
from typing import Union

US_ASCII = "US-ASCII" # the least-ambiguous encoding
JSONPrimitive = Union[str, int, float, bool, None]
JSONObject = Dict[str, JSONPrimitive]
JSONArray = List[JSONPrimitive]
JSONValue = Union[JSONPrimitive, JSONArray, JSONObject]

if TYPE_CHECKING:
# strict encapsulation: limit run-time access to just one function each
Expand Down Expand Up @@ -37,11 +46,25 @@ def xtrace(cmd: Command) -> None:
info((PS4, quote(cmd)))


def cd(dirname: str) -> None:
def cd(
dirname: str,
env: MutableMapping[str, str] = os.environ,
direnv: bool = True,
) -> None:
from os import chdir

xtrace(("cd", dirname))
chdir(dirname)
if direnv:
direnv_json: JSONValue = json(("direnv", "export", "json"))
if not isinstance(direnv_json, dict):
raise TypeError(f"expected dict, got {type(direnv_json)}")
for key, value in direnv_json.items():
if value is None:
env.pop(key, None)
else:
assert isinstance(value, str), value
env[key] = value


def quote(cmd: Command) -> str:
Expand All @@ -54,8 +77,16 @@ def stdout(cmd: Command) -> str:
return _wait(_popen(cmd, capture_output=True)).stdout.rstrip("\n")


def json(cmd: Command, encoding: str = US_ASCII) -> Iterable[object]:
"""Return a parsing of newline-delimited json on a subprocess' stdout."""
def json(cmd: Command) -> JSONValue:
"""Parse the (singular) json on a subprocess' stdout."""
import json

result: JSONValue = json.loads(stdout(cmd))
return result


def jq(cmd: Command, encoding: str = US_ASCII) -> Iterable[object]:
"""Yield the objects from newline-delimited json on a subprocess' stdout."""
import json

process = _popen(cmd, encoding=encoding, capture_output=True)
Expand Down
30 changes: 28 additions & 2 deletions manual_tests/behaviors/apply_on_update.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
#!/usr/bin/env py.test
from __future__ import annotations

from lib.functions import now
from manual_tests.lib import gh
from manual_tests.lib import gha
from manual_tests.lib import slice
from manual_tests.lib import tacos_demo
from manual_tests.lib import tf

TEST_NAME = __name__

Branch = int


def test() -> None:
# TODO: anything
pass
tacos_demo.clone()
tacos_demo_pr = tacos_demo.new_pr(TEST_NAME, slice.random())
try:
since = now()
gha.assert_eventual_success("terraform_lock", since)

gh.approve_pr(tacos_demo_pr.url)
gh.assert_pr_is_approved(tacos_demo_pr.url)

# the taco-apply label causes the plan to become clean:
assert not tf.plan_clean()
since = now()
gh.add_label(tacos_demo_pr.url, ":taco::apply")
gha.assert_eventual_success("terraform_apply", since)
assert tf.plan_clean()
finally:
gh.close_pr(tacos_demo_pr.branch)
37 changes: 36 additions & 1 deletion manual_tests/lib/gh.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

def assert_matching_comment(comment: str, since: datetime) -> None:
# Fetch the comments on the PR
comments = sh.json(
comments = sh.jq(
(
"gh",
"pr",
Expand All @@ -41,6 +41,30 @@ def assert_matching_comment(comment: str, since: datetime) -> None:
raise AssertionError(f"No matching comment: {comment}\n{comments}")


def assert_pr_has_label(pr_url: URL, label: str) -> None:
sh.banner("asserting PR has label:")
labels = sh.stdout(
(
"gh",
"pr",
"view",
"--json",
"labels",
"--jq",
".labels.[] | .name",
pr_url,
)
)

assert label in labels, (label, labels)


def assert_pr_is_approved(pr_url: URL) -> None:
sh.banner("asserting PR is approved:")
# TODO: actually check that the PR is approved (once we add a second service account)
assert_pr_has_label(pr_url, ":taco::approve")


def open_pr(branch: Branch) -> str:
return sh.stdout(("gh", "pr", "create", "--fill-first", "--head", branch))

Expand All @@ -66,3 +90,14 @@ def close_pr(pr_url: URL) -> None:
pr_url,
)
)


def approve_pr(pr_url: URL) -> None:
sh.banner("approving PR:")
# TODO: find a way to approve with a separate service account
add_label(pr_url, ":taco::approve")


def add_label(pr_url: URL, label: str) -> None:
sh.banner(f"adding label {label} to PR:")
sh.run(("gh", "pr", "edit", "--add-label", label, pr_url))
2 changes: 1 addition & 1 deletion manual_tests/lib/gha.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

def get_checks() -> Iterable[object]:
"""get the most recent run of the named check"""
return sh.json(
return sh.jq(
(
"gh",
"pr",
Expand Down
12 changes: 12 additions & 0 deletions manual_tests/lib/tf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import annotations

from lib import sh


def plan_clean() -> bool:
"""
Returns whether running terraform plan differs from the current state
"""
return sh.success(
("sudo-sac", "terragrunt", "run-all", "plan", "--detailed-exitcode")
)

0 comments on commit ea1d00d

Please sign in to comment.