Skip to content

Commit

Permalink
Add support for Actions environment files (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruvmanila committed Dec 9, 2020
1 parent 0c2972a commit bdd9860
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 12 deletions.
19 changes: 19 additions & 0 deletions docs/actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,23 @@ This module is to help provide support for `GitHub Actions`_ when writing a
gidgethub.actions.command("warning", "Missing semicolon", file="app.js", line="1", col="5")


.. function:: setenv(name, value)

Creates or updates an environment variable for this action and all the subsequent actions
running in the job.

Note that no automatic string conversion is performed on any arguments.

.. versionadded:: 5.0.0


.. function:: addpath(path)

Prepends the given *path* to the system PATH variable for this action and all the
subsequent actions running in the job. The *path* argument can be either :class:`str` or
:class:`os.PathLike`/:class:`pathlib.Path`.

.. versionadded:: 5.0.0


.. _GitHub Actions: https://help.github.com/en/actions
7 changes: 5 additions & 2 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ Changelog
- Add :meth:`gidgethub.routing.Router.fetch` for obtaining a frozenset of functions
registered to the router that the event would be called on.
(`Issue #74 <https://github.com/brettcannon/gidgethub/issues/74>`_).
- Add support for GitHub Actions Environment Files with :meth:`gidgethub.actions.setenv`
and :meth:`gidgethub.actions.addpath`.
(`Issue #137 <https://github.com/brettcannon/gidgethub/issues/132>`_).
- Make router callback execution order non-deterministic to avoid relying on
registration order.
(`Issue #74 <https://github.com/brettcannon/gidgethub/issues/74>`_).
registration order.
(`Issue #74 <https://github.com/brettcannon/gidgethub/issues/74>`_).
- Fix mypy errors in ``gidgethub.httpx.GitHubAPI._request``
(`Issue #133 <https://github.com/brettcannon/gidgethub/issues/133>`_).

Expand Down
30 changes: 28 additions & 2 deletions gidgethub/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import json
import os
import pathlib
from typing import Any
import urllib.parse
from typing import Any, Union


@functools.lru_cache(maxsize=1)
Expand Down Expand Up @@ -43,3 +42,30 @@ def command(cmd: str, data: str = "", **parameters: str) -> None:
)
cmd_parts.append(f"::{data}")
print("".join(cmd_parts))


_DELIMITER = "__GIDGETHUB_DELIMITER__"


def setenv(name: str, value: str) -> None:
"""Create or update an environment variable.
The change applies to this action and future actions running in the job.
"""
# https://github.com/actions/toolkit/blob/af821474235d3c5e1f49cee7c6cf636abb0874c4/packages/core/src/core.ts#L35-L53
os.environ[name] = value
# https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#multiline-strings
write_value = f"{name}<<{_DELIMITER}{os.linesep}{value}{os.linesep}{_DELIMITER}"
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as file:
file.write(write_value + os.linesep)


def addpath(path: Union[str, "os.PathLike[str]"]) -> None:
"""Prepend to PATH.
This affects this action and all subsequent actions in the current job.
"""
# https://github.com/actions/toolkit/blob/af821474235d3c5e1f49cee7c6cf636abb0874c4/packages/core/src/core.ts#L63-L75
os.environ["PATH"] = f"{path!s}{os.pathsep}{os.environ['PATH']}"
with open(os.environ["GITHUB_PATH"], "a", encoding="utf-8") as file:
file.write(os.fspath(path) + os.linesep)
99 changes: 91 additions & 8 deletions tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest

from gidgethub import actions
from gidgethub.actions import _DELIMITER


@pytest.fixture
Expand All @@ -14,6 +15,22 @@ def tmp_webhook(tmp_path, monkeypatch):
return tmp_file_path


@pytest.fixture
def tmp_envfile(tmp_path, monkeypatch):
"""Create a temporary environment file."""
tmp_file_path = tmp_path / "setenv.txt"
monkeypatch.setenv("GITHUB_ENV", os.fspath(tmp_file_path))
return tmp_file_path


@pytest.fixture
def tmp_pathfile(tmpdir, tmp_path, monkeypatch):
"""Create a temporary system path file and a temporary directory path."""
tmp_file_path = tmp_path / "addpath.txt"
monkeypatch.setenv("GITHUB_PATH", os.fspath(tmp_file_path))
return tmp_file_path, tmpdir


class TestWorkspace:

"""Tests for gidgethub.actions.workspace()."""
Expand Down Expand Up @@ -61,18 +78,10 @@ def _stdout(self, capsys):
assert stdout
return stdout.strip()

def test_set_env(self, capsys):
actions.command("set-env", "yellow", name="action_state")
assert self._stdout(capsys) == "::set-env name=action_state::yellow"

def test_set_output(self, capsys):
actions.command("set-output", "strawberry", name="action_fruit")
assert self._stdout(capsys) == "::set-output name=action_fruit::strawberry"

def test_add_path(self, capsys):
actions.command("add-path", "/path/to/dir")
assert self._stdout(capsys) == "::add-path::/path/to/dir"

def test_debug(self, capsys):
actions.command(
"debug", "Entered octocatAddition method", file="app.js", line="1"
Expand Down Expand Up @@ -111,3 +120,77 @@ def test_stop_commands(self, capsys):
def test_resume_command(self, capsys):
actions.command("pause-logging")
assert self._stdout(capsys) == "::pause-logging::"


class TestSetenv:

"""Tests for gidgethub.actions.setenv()."""

def test_creating(self, tmp_envfile):
actions.setenv("HELLO", "WORLD")
data = tmp_envfile.read_text(encoding="utf-8")
assert os.environ["HELLO"] == "WORLD"
assert (
data == f"HELLO<<{_DELIMITER}{os.linesep}WORLD{os.linesep}"
f"{_DELIMITER}{os.linesep}"
)

def test_updating(self, tmp_envfile):
actions.setenv("CHANGED", "FALSE")
data = tmp_envfile.read_text(encoding="utf-8")
assert os.environ["CHANGED"] == "FALSE"
assert (
data == f"CHANGED<<{_DELIMITER}{os.linesep}FALSE{os.linesep}"
f"{_DELIMITER}{os.linesep}"
)
actions.setenv("CHANGED", "TRUE")
updated = tmp_envfile.read_text(encoding="utf-8")
assert os.environ["CHANGED"] == "TRUE"
# Rendering of the updated variable is done by GitHub.
assert updated.endswith(
f"CHANGED<<{_DELIMITER}{os.linesep}TRUE{os.linesep}"
f"{_DELIMITER}{os.linesep}"
)

def test_creating_multiline(self, tmp_envfile):
multiline = """This
is
a
multiline
string."""
actions.setenv("MULTILINE", multiline)
data = tmp_envfile.read_text(encoding="utf-8")
assert os.environ["MULTILINE"] == multiline
assert (
data == f"MULTILINE<<{_DELIMITER}{os.linesep}{multiline}"
f"{os.linesep}{_DELIMITER}{os.linesep}"
)


class TestAddpath:

"""Tests for gidgethub.actions.addpath()."""

def test_string_path(self, tmp_pathfile):
actions.addpath("/path/to/random/dir")
data = tmp_pathfile[0].read_text(encoding="utf-8")
assert f"/path/to/random/dir{os.pathsep}" in os.environ["PATH"]
assert data == f"/path/to/random/dir{os.linesep}"

def test_path_object(self, tmp_pathfile):
actions.addpath(tmp_pathfile[1])
data = tmp_pathfile[0].read_text(encoding="utf-8")
assert f"{tmp_pathfile[1]!s}{os.pathsep}" in os.environ["PATH"]
assert data == f"{tmp_pathfile[1]!s}{os.linesep}"

def test_multiple_paths(self, tmp_pathfile):
actions.addpath("/path/to/random/dir")
random_path = tmp_pathfile[1] / "random.txt"
actions.addpath(random_path)
data = tmp_pathfile[0].read_text(encoding="utf-8")
# Last path added comes first.
assert (
f"{random_path!s}{os.pathsep}/path/to/random/dir{os.pathsep}"
in os.environ["PATH"]
)
assert data == f"/path/to/random/dir{os.linesep}{random_path!s}{os.linesep}"

0 comments on commit bdd9860

Please sign in to comment.